"""
    fihess(A, E; fast = true, finite_infinite = false, atol1 = 0, atol2 = 0, rtol, withQ = true, withZ = true) -> (At, Et, Q, Z, ν, blkdims)

Reduce the regular matrix pencil `A - λE` to an equivalent form `At - λEt = Q'*(A - λE)*Z` using
orthogonal or unitary transformation matrices `Q` and `Z` such that the transformed matrices `At` and `Et` are in one of the
following block upper-triangular forms:

(1) if `finite_infinite = false`, then

                   | Ai-λEi |   *     |
        At - λEt = |--------|---------|,
                   |    O   | Af-λEf  |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary divisors and
the `nf x nf` subpencil `Af-λEf` contains the finite eigenvalues of the pencil `A-λE`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vector `ν` contains the dimensions of the diagonal blocks
of the staircase form  `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[i]-ν[i+1]` for `i = 1, 2, ..., nb` is the number of infinite elementary divisors of degree `i`
(with `ν[nb+1] = 0`).

The subpencil `Af-λEf` is with `Af` in an upper Hessenberg form and `Ef` nonsingular and upper triangular.

The dimensions of the diagonal blocks are returned in `blkdims = (ni, nf)`.

(2) if `finite_infinite = true`, then

                   | Af-λEf |   *    |
        At - λEt = |--------|--------|,
                   |   O    | Ai-λEi |

where the `nf x nf` subpencil `Af-λEf`, with `Ef` nonsingular and upper triangular,
contains the finite eigenvalues of the pencil `A-λE` and the `ni x ni` subpencil `Ai-λEi`
contains the infinite elementary divisors.

The subpencil `Af-λEf` is with `Af` in an upper Hessenberg form and `Ef` nonsingular and upper triangular.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular  and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vectors `ν` contains the dimensions of the diagonal blocks
of the staircase form `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[nb-j+1]-ν[nb-j]` for `j = 1, 2, ..., nb` is the number of infinite elementary
divisors of degree `j` (with `ν[0] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (nf, ni)`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, 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`.

The reduction is performed using rank decisions based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The performed left orthogonal or unitary transformations are accumulated in the matrix `Q` if `withQ = true`.
Otherwise, `Q` is set to `nothing`.
The performed right orthogonal or unitary transformations are accumulated in the matrix `Z` if `withZ = true`.
Otherwise, `Z` is set to `nothing`.
"""
function fihess(A::AbstractMatrix, E::AbstractMatrix;
                   fast::Bool = true, finite_infinite::Bool = false,
                   atol1::Real = zero(real(eltype(A))), atol2::Real = zero(real(eltype(E))),
                   rtol::Real = (size(A,1)*eps(real(float(one(eltype(A))))))*iszero(min(atol1,atol2)),
                   withQ::Bool = true, withZ::Bool = true)

    n = LinearAlgebra.checksquare(A)
    n == LinearAlgebra.checksquare(E) || throw(DimensionMismatch("A and E must have the same dimensions"))
    T = promote_type(eltype(A), eltype(E))
    T <: BlasFloat || (T = promote_type(Float64,T))

    A1 = copy_oftype(A,T)
    E1 = copy_oftype(E,T)

    withQ ? (Q = Matrix{T}(I,n,n)) : (Q = nothing)
    withZ ? (Z = Matrix{T}(I,n,n)) : (Z = nothing)

    ν, blkdims = fisplit!(A1, E1, Q, Z, missing, missing;
                         fast  = fast, finite_infinite = finite_infinite,
                         atol1 = atol1, atol2 = atol2, rtol = rtol, withQ = withQ, withZ = withZ)
    (ilo, ihi) = finite_infinite ? (1, blkdims[1]) : (blkdims[1]+1, n)
    ilo > ihi || gghrd!(withQ ? 'V' : 'N', withZ ? 'V' : 'N', ilo, ihi, A1, E1, Q, Z)

    return A1, E1, Q, Z, ν, blkdims
end
"""
    fischur(A, E; fast = true, finite_infinite = false, atol1 = 0, atol2 = 0, rtol, withQ = true, withZ = true) -> (At, Et, Q, Z, ν, blkdims)

Reduce the regular matrix pencil `A - λE` to an equivalent form `At - λEt = Q'*(A - λE)*Z` using
orthogonal or unitary transformation matrices `Q` and `Z` such that the transformed matrices `At` and `Et` are in one of the
following block upper-triangular forms:

(1) if `finite_infinite = false`, then

                   | Ai-λEi |   *     |
        At - λEt = |--------|---------|,
                   |    O   | Af-λEf  |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary divisors and
the `nf x nf` subpencil `Af-λEf` contains the finite eigenvalues of the pencil `A-λE`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vector `ν` contains the dimensions of the diagonal blocks
of the staircase form  `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[i]-ν[i+1]` for `i = 1, 2, ..., nb` is the number of infinite elementary divisors of degree `i`
(with `ν[nb+1] = 0`).

The subpencil `Af-λEf` is with the pair `(Af,Ef)` in a generalized Schur form, with `Af` (quasi) upper triangular and
`Ef` nonsingular and upper triangular.

The dimensions of the diagonal blocks are returned in `blkdims = (ni, nf)`.

(2) if `finite_infinite = true`, then

                   | Af-λEf |   *    |
        At - λEt = |--------|--------|,
                   |   O    | Ai-λEi |

where the `nf x nf` subpencil `Af-λEf`, with `Ef` nonsingular and upper triangular,
contains the finite eigenvalues of the pencil `A-λE` and the `ni x ni` subpencil `Ai-λEi`
contains the infinite elementary divisors.

The subpencil `Af-λEf` is with the pair `(Af,Ef)` in a generalized Schur form, with `Af` (quasi) upper triangular and
`Ef` nonsingular and upper triangular.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular
and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vectors `ν` contains the dimensions of the diagonal blocks
of the staircase form `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[nb-j+1]-ν[nb-j]` for `j = 1, 2, ..., nb` is the number of infinite elementary
divisors of degree `j` (with `ν[0] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (nf, ni)`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, 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`.

The reduction is performed using rank decisions based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The performed left orthogonal or unitary transformations are accumulated in the matrix `Q` if `withQ = true`.
Otherwise, `Q` is set to `nothing`.
The performed right orthogonal or unitary transformations are accumulated in the matrix `Z` if `withZ = true`.
Otherwise, `Z` is set to `nothing`.
"""
function fischur(A::AbstractMatrix, E::AbstractMatrix;
                      fast::Bool = true, finite_infinite::Bool = false,
                      atol1::Real = zero(real(eltype(A))), atol2::Real = zero(real(eltype(E))),
                      rtol::Real = (size(A,1)*eps(real(float(one(eltype(A))))))*iszero(min(atol1,atol2)),
                      withQ::Bool = true, withZ::Bool = true)

    n = LinearAlgebra.checksquare(A)
    n == LinearAlgebra.checksquare(E) || throw(DimensionMismatch("A and E must have the same dimensions"))
    T = promote_type(eltype(A), eltype(E))
    T <: BlasFloat || (T = promote_type(Float64,T))

    A1 = copy_oftype(A,T)
    E1 = copy_oftype(E,T)

    withQ ? (Q = Matrix{T}(I,n,n)) : (Q = nothing)
    withZ ? (Z = Matrix{T}(I,n,n)) : (Z = nothing)

    ν, blkdims = fisplit!(A1, E1, Q, Z, missing, missing;
                         fast  = fast, finite_infinite = finite_infinite,
                         atol1 = atol1, atol2 = atol2, rtol = rtol, withQ = withQ, withZ = withZ)
    (ilo, ihi) = finite_infinite ? (1, blkdims[1]) : (blkdims[1]+1, n)
    if ilo < ihi
        compq = withQ ? 'V' : 'N'
        compz = withZ ? 'V' : 'N'
        gghrd!(compq, compz, ilo, ihi, A1, E1, Q, Z)
        hgeqz!(compq, compz, ilo, ihi, A1, E1, Q, Z)
    end

    return A1, E1, Q, Z, ν, blkdims
end
"""
    fischursep(A, E; smarg, disc = false, fast = true, finite_infinite = false, stable_unstable = false, atol1 = 0, atol2 = 0, rtol, withQ = true, withZ = true) -> (At, Et, Q, Z, ν, blkdims)

Reduce the regular matrix pencil `A - λE` to an equivalent block upper triangular form `At - λEt = Q'*(A - λE)*Z`
using orthogonal or unitary transformation matrices `Q` and `Z` such that the transformed matrices `At` and `Et`
have separated infinite, stable and unstable eigenvalues with respect to a stability domain `Cs` defined by the stability margin parameter `smarg` and
the stability type parameter `disc`. If `disc = false`, `Cs` is the set of complex numbers with real parts less than `smarg`,
while if `disc = true`, `Cs` is the set of complex numbers with moduli less than `smarg` (i.e., the interior of a disc
of radius `smarg` centered in the origin). If `smarg = missing`, the default value used is `smarg = 0`, if  `disc = false`,
and `smarg = 1`, if `disc = true`.

The pencil `At - λEt` results in one of the following block upper-triangular forms:

(1) if `finite_infinite = false`, then

                   | Ai-λEi   *      *    |
        At - λEt = |    O   A1-λE1   *    |
                   |    0     0    A2-λE2 |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary divisors,
the `n1 x n1` subpencil `A1-λE1` is with the pair `(A1,E1)` in a generalized Schur form, and the
`n2 x n2` subpencil `A2-λE2` is with the pair `(A2,E2)` in a generalized Schur form.
The pencil `A1-λE1` has unstable finite eigenvalues and `A2-λE2` has stable finite eigenvalues if `stable_unstable = false`,
while `A1-λE1` has stable finite eigenvalues and `A2-λE2` has unstable finite eigenvalues if `stable_unstable = true`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vector `ν` contains the dimensions of the diagonal blocks
of the staircase form  `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[i]-ν[i+1]` for `i = 1, 2, ..., nb` is the number of infinite elementary divisors of degree `i`
(with `ν[nb+1] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (ni, n1, n2)`.

(2) if `finite_infinite = true`, then

                   | A1-λE1   *      *    |
        At - λEt = |    O   A2-λE2   *    |
                   |    0     0    Ai-λEi |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary divisors,
the `n1 x n1` subpencil `A1-λE1` is with the pair `(A1,E1)` in a generalized Schur form, and the
`n2 x n2` subpencil `A2-λE2` is with the pair `(A2,E2)` in a generalized Schur form.
The pencil `A1-λE1` has unstable finite eigenvalues and `A2-λE2` has stable finite eigenvalues if `stable_unstable = false`,
while `A1-λE1` has stable finite eigenvalues and `A2-λE2` has unstable finite eigenvalues if `stable_unstable = true`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular
and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vectors `ν` contains the dimensions of the diagonal blocks
of the staircase form `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[nb-j+1]-ν[nb-j]` for `j = 1, 2, ..., nb` is the number of infinite elementary
divisors of degree `j` (with `ν[0] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (n1, n2, ni)`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, 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`.

The reduction is performed using rank decisions based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The performed left orthogonal or unitary transformations are accumulated in the matrix `Q` if `withQ = true`.
Otherwise, `Q` is set to `nothing`.
The performed right orthogonal or unitary transformations are accumulated in the matrix `Z` if `withZ = true`.
Otherwise, `Z` is set to `nothing`.
"""
function fischursep(A::AbstractMatrix, E::AbstractMatrix;
                    smarg::Union{Real,Missing} = missing, disc::Bool = false,
                    fast::Bool = true, finite_infinite::Bool = false, stable_unstable::Bool = false,
                    atol1::Real = zero(real(eltype(A))), atol2::Real = zero(real(eltype(E))),
                    rtol::Real = (size(A,1)*eps(real(float(one(eltype(A))))))*iszero(min(atol1,atol2)),
                    withQ::Bool = true, withZ::Bool = true)

    n = LinearAlgebra.checksquare(A)
    n == LinearAlgebra.checksquare(E) || throw(DimensionMismatch("A and E must have the same dimensions"))
    disc && (!ismissing(smarg) && smarg < 0 && error("sdeg must be non-negative if disc = true"))

    T = promote_type(eltype(A), eltype(E))
    T <: BlasFloat || (T = promote_type(Float64,T))

    A1 = copy_oftype(A,T)
    E1 = copy_oftype(E,T)

    withQ ? (Q = Matrix{T}(I,n,n)) : (Q = nothing)
    withZ ? (Z = Matrix{T}(I,n,n)) : (Z = nothing)

    ν, blkdims = fisplit!(A1, E1, Q, Z, missing, missing;
                         fast  = fast, finite_infinite = finite_infinite,
                         atol1 = atol1, atol2 = atol2, rtol = rtol, withQ = withQ, withZ = withZ)
    (ilo, ihi) = finite_infinite ? (1, blkdims[1]) : (blkdims[1]+1, n)
    if ilo <= ihi
        compq = withQ ? 'V' : 'N'
        compz = withZ ? 'V' : 'N'
        gghrd!(compq, compz, ilo, ihi, A1, E1, Q, Z)
        _, _, α, β, _, _ = hgeqz!(compq, compz, ilo, ihi, A1, E1, Q, Z)
        i2 = ilo:ihi
        ismissing(smarg) && (smarg = disc ? one(real(T)) : zero(real(T)))
        select2 = disc ? abs.(α[i2]) .< smarg*abs.(β[i2]) : real.(α[i2] ./ β[i2]) .< smarg
        stable_unstable || (select2 = .!select2)

        if finite_infinite
           n3 = blkdims[2]
           n1 = length(select2[select2 .== true])
           n2 = n-n3-n1
           select = [Int.(select2); zeros(Int,n3)]
        else
           n1 = blkdims[1]
           n2 = length(select2[select2 .== true])
           n3 = n-n2-n1
           select = [ones(Int,n1);Int.(select2)]
        end
        tgsen!(withQ, withZ, select, A1, E1, Q, Z)
    else
        (n1,n2,n3) = finite_infinite ? (0,0,blkdims[2]) : (blkdims[1],0,0)
    end

    return A1, E1, Q, Z, ν, (n1,n2,n3)
end
"""
    sfischursep(A, E; smarg, disc = false, fast = true, finite_infinite = false, stable_unstable = false, atol1 = 0, atol2 = 0, rtol, withQ = true, withZ = true) -> (At, Et, Q, Z, ν, blkdims)

Reduce the regular matrix pencil `A - λE` to an equivalent block upper triangular form `At - λEt = Q'*(A - λE)*Z`
using orthogonal or unitary transformation matrices `Q` and `Z` such that the transformed matrices `At` and `Et`
have separated infinite, stable and unstable eigenvalues with respect to a stability domain `Cs` defined by the stability margin parameter `smarg` and
the stability type parameter `disc`. If `disc = false`, `Cs` is the set of complex numbers with real parts less than `smarg`,
while if `disc = true`, `Cs` is the set of complex numbers with moduli less than `smarg` (i.e., the interior of a disc
of radius `smarg` centered in the origin). If `smarg = missing`, the default value used is `smarg = 0`, if  `disc = false`,
and `smarg = 1`, if `disc = true`.

The pencil `At - λEt` results in one of the following block upper-triangular forms:

(1) if `finite_infinite = true`, then

                   | Ai1   *      *       *     |
        At - λEt = |  0  A1-λE1   *       *     |
                   |  0    0    A2-λE2    *     |
                   |  0    0      0    Ai2-λEi2 |

where the `ni1 x ni1` matrix `Ai1` and the  `ni2 x ni2` subpencil `Ai2-λEi2` contain the infinite elementary divisors,
the `n1 x n1` subpencil `A1-λE1` is with the pair `(A1,E1)` in a generalized Schur form, and the
`n2 x n2` subpencil `A2-λE2` is with the pair `(A2,E2)` in a generalized Schur form.
The pencil `A1-λE1` has unstable finite eigenvalues and `A2-λE2` has stable finite eigenvalues if `stable_unstable = false`,
while `A1-λE1` has stable finite eigenvalues and `A2-λE2` has unstable finite eigenvalues if `stable_unstable = true`.

The subpencil `Ai2-λEi2` is in a staircase form, with `Ai2` nonsingular and upper triangular and `Ei2` nilpotent and upper triangular.
The `nb`-dimensional vector `ν` contains the dimensions of the diagonal blocks
of the staircase form  `Ai2-λEi2` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[nb-j+2]-ν[nb-j+1]` for `j = 1, 2, ..., nb+1` is the number of infinite elementary
divisors of degree `j` (with `ν[0] := 0` and `ν[nb+1] := ni1`).

The dimensions of the diagonal blocks are returned in `blkdims = (ni1, n1, n2, ni2)`.

(2) if `finite_infinite = false`, then

                   | Ai1-λEi1   *      *     *  |
                   |    0     A1-λE1   *     *  |
        At - λEt = |    0       0    A2-λE2  *  |
                   |    0       0      0    Ai2 |

where the `ni1 x ni1` subpencil `Ai1-λEi1` and the `ni2 x ni2` upper triangular and nonsingular matrix `Ai2` contain
the infinite elementary divisors of `A-λE`,
the `n1 x n1` subpencil `A1-λE1` is with the pair `(A1,E1)` in a generalized Schur form, and the
`n2 x n2` subpencil `A2-λE2` is with the pair `(A2,E2)` in a generalized Schur form.
The pencil `A1-λE1` has unstable finite eigenvalues and `A2-λE2` has stable finite eigenvalues if `stable_unstable = false`,
while `A1-λE1` has stable finite eigenvalues and `A2-λE2` has unstable finite eigenvalues if `stable_unstable = true`.

The subpencil `Ai1-λEi1` is in a staircase form, with `Ai1` nonsingular and upper triangular
and `Ei1` nilpotent and upper triangular.
The `nb`-dimensional vectors `ν` contains the dimensions of the diagonal blocks
of the staircase form `Ai1-λEi1` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[i]-ν[i+1]` for `i = 0, 1, 2, ..., nb` is the number of infinite elementary divisors of degree `i`
(with `ν[nb+1] = 0` and `ν[0] = ni2`).

The dimensions of the diagonal blocks are returned in `blkdims = (ni1, n1, n2, ni2)`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, 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`.

The reduction is performed using rank decisions based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

The performed left orthogonal or unitary transformations are accumulated in the matrix `Q` if `withQ = true`.
Otherwise, `Q` is set to `nothing`.
The performed right orthogonal or unitary transformations are accumulated in the matrix `Z` if `withZ = true`.
Otherwise, `Z` is set to `nothing`.
"""
function sfischursep(A::AbstractMatrix, E::AbstractMatrix;
                    smarg::Union{Real,Missing} = missing, disc::Bool = false,
                    fast::Bool = true, finite_infinite::Bool = false, stable_unstable::Bool = false,
                    atol1::Real = zero(real(eltype(A))), atol2::Real = zero(real(eltype(E))),
                    rtol::Real = (size(A,1)*eps(real(float(one(eltype(A))))))*iszero(min(atol1,atol2)),
                    withQ::Bool = true, withZ::Bool = true)

    n = LinearAlgebra.checksquare(A)
    n == LinearAlgebra.checksquare(E) || throw(DimensionMismatch("A and E must have the same dimensions"))
    disc && (!ismissing(smarg) && smarg < 0 && error("sdeg must be non-negative if disc = true"))

    T = promote_type(eltype(A), eltype(E))
    T <: BlasFloat || (T = promote_type(Float64,T))

    A1 = copy_oftype(A,T)
    E1 = copy_oftype(E,T)

    withQ ? (Q = Matrix{T}(I,n,n)) : (Q = nothing)
    withZ ? (Z = Matrix{T}(I,n,n)) : (Z = nothing)

    ν, blkdims1 = sfisplit!(A1, E1, Q, Z, missing, missing;
                         fast  = fast, finite_infinite = finite_infinite,
                         atol1 = atol1, atol2 = atol2, rtol = rtol, withQ = withQ, withZ = withZ)
    ni1 = blkdims1[1]
    nf  = blkdims1[2]
    ni2 = blkdims1[3]
    (ilo, ihi) = (ni1+1, ni1+nf)
    if ilo <= ihi
        compq = withQ ? 'V' : 'N'
        compz = withZ ? 'V' : 'N'
        gghrd!(compq, compz, ilo, ihi, A1, E1, Q, Z)
        _, _, α, β, _, _ = hgeqz!(compq, compz, ilo, ihi, A1, E1, Q, Z)
        i2 = ilo:ihi
        ismissing(smarg) && (smarg = disc ? one(real(T)) : zero(real(T)))
        select2 = disc ? abs.(α[i2]) .< smarg*abs.(β[i2]) : real.(α[i2] ./ β[i2]) .< smarg
        stable_unstable || (select2 = .!select2)

        select = [ones(Int,ni1); Int.(select2); zeros(Int,ni2) ]
        n1 = length(select2[select2 .== true])
        n2 = nf-n1
        blkdims = (ni1, n1, n2, ni2)
        tgsen!(withQ, withZ, select, A1, E1, Q, Z)
    else
        blkdims = (ni1, 0, 0, ni2)
    end

    return A1, E1, Q, Z, ν, blkdims
end
"""
    fiblkdiag(A, E, B, C; fast = true, finite_infinite = false, trinv = false, atol1 = 0, atol2 = 0, rtol, withQ = true, withZ = true) -> (At, Et, Bt, Ct, Q, Z, ν, blkdims, sep)

Reduce the regular matrix pencil `A - λE` to an equivalent form `At - λEt = Q*(A - λE)*Z` using
the transformation matrices `Q` and `Z` such that the transformed matrices `At` and `Et` are in one of the
following block diagonal forms:

(1) if `finite_infinite = false`, then

                   | Ai-λEi |   0     |
        At - λEt = |--------|---------|,
                   |    O   | Af-λEf  |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary
divisors and the `nf x nf` subpencil `Af-λEf`, with `Ef` nonsingular and upper triangular, contains the finite eigenvalues of the pencil `A-λE`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vector `ν` contains the dimensions of the diagonal blocks
of the staircase form  `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[i]-ν[i+1]` for `i = 1, 2, ..., nb` is the number of infinite elementary divisors of degree `i`
(with `ν[nb+1] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (ni, nf)`.

(2) if `finite_infinite = true`, then

                   | Af-λEf |   0    |
        At - λEt = |--------|--------|,
                   |   O    | Ai-λEi |

where the `nf x nf` subpencil `Af-λEf`, with `Ef` nonsingular and upper triangular,
contains the finite eigenvalues of the pencil `A-λE` and the `ni x ni` subpencil `Ai-λEi`
contains the infinite elementary divisors.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular  and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vectors `ν` contains the dimensions of the diagonal blocks
of the staircase form `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[nb-j+1]-ν[nb-j]` for `j = 1, 2, ..., nb` is the number of infinite elementary
divisors of degree `j` (with `ν[0] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (nf, ni)`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, 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`.

The reduction is performed using rank decisions based on rank revealing QR-decomdiagpositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

If `withQ = true`, `Q` contains the left transformation matrix, if `trinv = false`, or its inverse, if `trinv = true`.
If `withQ = false`, `Q` is set to `nothing`.
If `withZ = true`, `Z` contains the right transformation matrix, if `trinv = false`, or its inverse, if `trinv = true`.
If `withZ = false`, `Z` is set to `nothing`.

`Bt = Q*B`, unless `B = missing`, in which case `Bt = missing` is returned, and `Ct = C*Z`,
unless `C = missing`, in which case `Ct = missing` is returned .

An estimation of the separation of the spectra of `Ai-λEi` and `Af-λEf` is returned in `sep`, where  `0 < sep ≤ 1`.
"""
function fiblkdiag(A::AbstractMatrix, E::AbstractMatrix, B::Union{AbstractMatrix,Missing}, C::Union{AbstractMatrix,Missing};
                   fast::Bool = true, finite_infinite::Bool = false, trinv::Bool = false,
                   atol1::Real = zero(real(eltype(A))), atol2::Real = zero(real(eltype(E))),
                   rtol::Real = (size(A,1)*eps(real(float(one(eltype(A))))))*iszero(min(atol1,atol2)),
                   withQ::Bool = true, withZ::Bool = true)

    compq = withQ || !ismissing(B)
    compz = withZ || !ismissing(C)
    A1, E1, Q, Z, ν, blkdims = fischur(A, E; fast = fast, finite_infinite = finite_infinite, atol1 = atol1, atol2 = atol2,
                                      rtol = rtol, withQ = compq, withZ = compz)
    T = eltype(A1)
    ONER = one(real(T))
    ismissing(B) ? B1 = missing : B1 = Q'*copy_oftype(B,T)
    ismissing(C) ? C1 = missing : C1 = copy_oftype(C,T)*Z

    minimum(blkdims) == 0 && (return A1, E1, B1, C1, withQ ? (trinv ? Q : adjis!(Q)) : nothing,
                              withZ ? (trinv ? adjis!(Z) : Z) : nothing, ν, blkdims, ONER)

    trinv ? (withZ && adjis!(Z)) : (withQ && adjis!(Q))

    n1 = blkdims[1]
    i1 = 1:n1
    i2 = n1+1:size(A,1)
    X = view(E1,i1,i2)
    Y = view(A1,i1,i2)
    _, _, scale = tgsyl!(view(A1,i1,i1), view(A1,i2,i2), Y, view(E1,i1,i1), view(E1,i2,i2), X)

    ONE = one(T)
    ZERO = zero(T)
    scale != 0 && (scale = ONE/scale)

    ismissing(B) || mul!(view(B1,i1,:),X,view(B1,i2,:),scale,ONE)
    ismissing(C) || mul!(view(C1,:,i2),view(C1,:,i1),Y,-scale,ONE)

    if trinv
       withQ && mul!(view(Q,:,i2),view(Q,:,i1),X,-scale,ONE)
       withZ && mul!(view(Z,i1,:),Y,view(Z,i2,:),scale,ONE)
    else
       withQ && mul!(view(Q,i1,:),X,view(Q,i2,:),scale,ONE)
       withZ && mul!(view(Z,:,i2),view(Z,:,i1),Y,-scale,ONE)
    end

    scale == 0 ? sep = ONER : sep = ONER/max(1+norm(X)*abs(scale),1+norm(Y)*abs(scale))

    fill!(X,ZERO)
    fill!(Y,ZERO)

    return A1, E1, B1, C1, Q, Z, ν, blkdims, sep

end
"""
    gsblkdiag(A, E, B, C; smarg, disc = false, fast = true, finite_infinite = false, stable_unstable = false, trinv = false,
              atol1 = 0, atol2 = 0, rtol, withQ = true, withZ = true) -> (At, Et, Bt, Ct, Q, Z, ν, blkdims, sep)

Reduce the regular matrix pencil `A - λE` to an equivalent block diagonal triangular form `At - λEt = Q*(A - λE)*Z`
using the transformation matrices `Q` and `Z` such that the transformed matrices `At` and `Et`
have separated infinite, stable and unstable eigenvalues with respect to a stability domain `Cs`
defined by the stability margin parameter `smarg` and the stability type parameter `disc`.
If `disc = false`, `Cs` is the set of complex numbers with real parts less than `smarg`,
while if `disc = true`, `Cs` is the set of complex numbers with moduli less than `smarg` (i.e., the interior of a disc
of radius `smarg` centered in the origin). If `smarg = missing`, the default value used is `smarg = 0`, if  `disc = false`,
and `smarg = 1`, if `disc = true`.

The pencil `At - λEt` results in one of the following block upper-triangular forms:

(1) if `finite_infinite = false`, then

                   | Ai-λEi   *      0    |
        At - λEt = |    O   A1-λE1   0    |
                   |    0     0    A2-λE2 |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary divisors,
the `n1 x n1` subpencil `A1-λE1` is with the pair `(A1,E1)` in a generalized Schur form, and the
`n2 x n2` subpencil `A2-λE2` is with the pair `(A2,E2)` in a generalized Schur form.
The pencil `A1-λE1` has unstable finite eigenvalues and `A2-λE2` has stable finite eigenvalues if `stable_unstable = false`,
while `A1-λE1` has stable finite eigenvalues and `A2-λE2` has unstable finite eigenvalues if `stable_unstable = true`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vector `ν` contains the dimensions of the diagonal blocks
of the staircase form  `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[i]-ν[i+1]` for `i = 1, 2, ..., nb` is the number of infinite elementary divisors of degree `i`
(with `ν[nb+1] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (ni, n1, n2)`.

(2) if `finite_infinite = true`, then

                   | A1-λE1   0      0    |
        At - λEt = |    O   A2-λE2   *    |
                   |    0     0    Ai-λEi |

where the `ni x ni` subpencil `Ai-λEi` contains the infinite elementary divisors,
the `n1 x n1` subpencil `A1-λE1` is with the pair `(A1,E1)` in a generalized Schur form, and the
`n2 x n2` subpencil `A2-λE2` is with the pair `(A2,E2)` in a generalized Schur form.
The pencil `A1-λE1` has unstable finite eigenvalues and `A2-λE2` has stable finite eigenvalues if `stable_unstable = false`,
while `A1-λE1` has stable finite eigenvalues and `A2-λE2` has unstable finite eigenvalues if `stable_unstable = true`.

The subpencil `Ai-λEi` is in a staircase form, with `Ai` nonsingular and upper triangular
and `Ei` nilpotent and upper triangular.
The `nb`-dimensional vectors `ν` contains the dimensions of the diagonal blocks
of the staircase form `Ai-λEi` such that `i`-th block has dimensions `ν[i] x ν[i]`.
The difference `ν[nb-j+1]-ν[nb-j]` for `j = 1, 2, ..., nb` is the number of infinite elementary
divisors of degree `j` (with `ν[0] = 0`).

The dimensions of the diagonal blocks are returned in `blkdims = (n1, n2, ni)`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, 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`.

The reduction is performed using rank decisions based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

If `withQ = true`, `Q` contains the left transformation matrix, if `trinv = false`, or its inverse, if `trinv = true`.
If `withQ = false`, `Q` is set to `nothing`.
If `withZ = true`, `Z` contains the left transformation matrix, if `trinv = false`, or its inverse, if `trinv = true`.
If `withZ = false`, `Z` is set to `nothing`.

`Bt = Q*B`, unless `B = missing`, in which case `Bt = missing` is returned, and `Ct = C*Z`,
unless `C = missing`, in which case `Ct = missing` is returned .

An estimation of the separation of the spectra of the two underlying diagonal blocks is returned in `sep`,
where  `0 ≤ sep ≤ 1`. A value `sep ≈ 0` indicates that `A1-λE1` and `A2-λE2` have some almost equal eigenvalues.
"""
function gsblkdiag(A::AbstractMatrix, E::AbstractMatrix, B::Union{AbstractMatrix,Missing}, C::Union{AbstractMatrix,Missing};
                   smarg::Union{Real,Missing} = missing, disc::Bool = false, trinv::Bool = false,
                   fast::Bool = true, finite_infinite::Bool = false, stable_unstable::Bool = false,
                   atol1::Real = zero(real(eltype(A))), atol2::Real = zero(real(eltype(E))),
                   rtol::Real = (size(A,1)*eps(real(float(one(eltype(A))))))*iszero(min(atol1,atol2)),
                   withQ::Bool = true, withZ::Bool = true)

    compq = withQ || !ismissing(B)
    compz = withZ || !ismissing(C)
    A1, E1, Q, Z, ν, blkdims = fischursep(A, E; smarg = smarg, disc = disc,
                                      fast = fast, finite_infinite = finite_infinite, stable_unstable = stable_unstable,
                                      atol1 = atol1, atol2 = atol2, rtol = rtol, withQ = compq, withZ = compz)
    T = eltype(A1)
    ONER = one(real(T))
    ismissing(B) ? B1 = missing : B1 = Q'*copy_oftype(B,T)
    ismissing(C) ? C1 = missing : C1 = copy_oftype(C,T)*Z

    finite_infinite ? n1 = blkdims[1] : n1 = blkdims[1] + blkdims[2]
    (n1 == 0 || size(A1,1) == n1) && (return A1, E1, B1, C1, withQ ? (trinv ? Q : adjis!(Q)) : nothing,
               withZ ? (trinv ? adjis!(Z) : Z) : nothing, ν, blkdims,ONER)

    trinv ? (withZ && adjis!(Z)) : (withQ && adjis!(Q))

    i1 = 1:n1
    i2 = n1+1:size(A,1)
    X = view(E1,i1,i2)
    Y = view(A1,i1,i2)
    _, _, scale = tgsyl!(view(A1,i1,i1), view(A1,i2,i2), Y, view(E1,i1,i1), view(E1,i2,i2), X)

    ONE = one(T)
    ZERO = zero(T)
    scale != 0 && (scale = ONE/scale)

    ismissing(B) || mul!(view(B1,i1,:),X,view(B1,i2,:),scale,ONE)
    ismissing(C) || mul!(view(C1,:,i2),view(C1,:,i1),Y,-scale,ONE)

    if trinv
       withQ && mul!(view(Q,:,i2),view(Q,:,i1),X,-scale,ONE)
       withZ && mul!(view(Z,i1,:),Y,view(Z,i2,:),scale,ONE)
    else
       withQ && mul!(view(Q,i1,:),X,view(Q,i2,:),scale,ONE)
       withZ && mul!(view(Z,:,i2),view(Z,:,i1),Y,-scale,ONE)
    end

    scale == 0 ? sep = ONER : sep = ONER/max(1+norm(X)*abs(scale),1+norm(Y)*abs(scale))

    fill!(X,ZERO)
    fill!(Y,ZERO)

    return A1, E1, B1, C1, Q, Z, ν, blkdims, sep

end
@inline function adjis!(A::AbstractMatrix)
    # in-situ adjoint of a square matrix
    n = LinearAlgebra.checksquare(A)
    for i = 1:n
        for j = i:n
            t = A[i,j]'
            A[i,j] = A[j,i]'
            A[j,i] = t
        end
    end
    return A
end
"""
    ssblkdiag(A, B, C; smarg, disc = false, stable_unstable = false, withQ = true, withZ = true) -> (At, Bt, Ct, Q, Z, blkdims, sep)

Reduce the regular matrix pencil `A - λI` to an equivalent block diagonal triangular form `At - λI = Q*(A - λI)*Z`
using the transformation matrices `Q` and `Z`, where `Q = inv(Z)`, such that the transformed matrix `At` have
separated stable and unstable eigenvalues with respect to a stability domain `Cs` defined by the
stability margin parameter `smarg` and the stability type parameter `disc`.
If `disc = false`, `Cs` is the set of complex numbers with real parts less than `smarg`,
while if `disc = true`, `Cs` is the set of complex numbers with moduli less than `smarg` (i.e., the interior of a disc
of radius `smarg` centered in the origin). If `smarg = missing`, the default value used is `smarg = 0`, if  `disc = false`,
and `smarg = 1`, if `disc = true`.
The matrix `At` results in the following block diagonal form

        At = | A1  0  |
             | 0   A2 |

where the `n1 x n1` matrix `A1` and the `n2 x n2` matrix `A2` are in Schur form.
The matrix `A1` has unstable eigenvalues and `A2` has stable eigenvalues if `stable_unstable = false`,
while `A1` has stable eigenvalues and `A2` has unstable eigenvalues if `stable_unstable = true`.
The dimensions of the diagonal blocks are returned in `blkdims = (n1, n2)`.
If `withQ = true`, `Q` contains the left transformation matrix. If `withQ = false`, `Q` is set to `nothing`.
If `withZ = true`, `Z` contains the right transformation matrix. If `withZ = false`, `Z` is set to `nothing`.
`Bt = Q*B`, unless `B = missing`, in which case `Bt = missing` is returned, and `Ct = C*Z`,
unless `C = missing`, in which case `Ct = missing` is returned .
An estimation of the separation of the spectra of the two underlying diagonal blocks is returned in `sep`,
where  `0 ≤ sep ≤ 1`. A value `sep ≈ 0` indicates that `A1` and `A2` have some almost equal eigenvalues.
"""
function ssblkdiag(A::AbstractMatrix{T}, B::Union{AbstractMatrix{T},Missing}, C::Union{AbstractMatrix{T},Missing};
                   smarg::Union{Real,Missing} = missing, disc::Bool = false, stable_unstable::Bool = false,
                   withQ::Bool = true, withZ::Bool = true ) where T <: BlasFloat

    n = LinearAlgebra.checksquare(A)
    S = schur(A)
    ONER = real(float(one(T)))
    if disc
       ismissing(smarg) && (smarg = ONER)
       stable_unstable ? select = abs.(S.values) .< smarg : select = abs.(S.values) .> smarg
    else
       ismissing(smarg) && (smarg = real(float(zero(T))))
       stable_unstable ? select = real(S.values) .< smarg : select = real(S.values) .> smarg
    end
    n1 = length(filter(y-> y == true,select))

    (n1 == 0 || n1 == n) && (return A, B, C, withQ ? copy(S.Z') : nothing, withZ ? S.Z : nothing, (n1,n-n1), ONER)

    ordschur!(S, select)

    i1 = 1:n1
    i2 = n1+1:size(A,1)
    Y = view(S.T,i1,i2)  # Y will contain the negative of solution -Y
    _, scale = LAPACK.trsyl!('N', 'N', view(S.T,i1,i1), view(S.T,i2,i2), Y, -1)

    ONE = one(T)
    ZERO = zero(T)
    scale != 0 && (scale = ONE/scale)

    ismissing(B) ? B1 = missing : (B1 = S.Z'*B; mul!(view(B1,i1,:),Y,view(B1,i2,:),scale,ONE))
    ismissing(C) ? C1 = missing : (C1 = C*S.Z; mul!(view(C1,:,i2),view(C1,:,i1),Y,-scale,ONE))

    withQ ? Q = copy(S.Z') : Q = nothing
    withQ && mul!(view(Q,i1,:),Y,view(Q,i2,:),scale,ONE)
    withZ && mul!(view(S.Z,:,i2),view(S.Z,:,i1),Y,-scale,ONE)

    scale == 0 ? sep = zero(real(T)) : sep = ONER/(1+norm(Y)*abs(scale))

    fill!(Y,ZERO)
    return S.T, B1, C1, Q, withZ ? S.Z : nothing, (n1, n-n1), sep
end
# fallback for versions prior 1.3
VERSION > v"1.3.0" || (mul!(C::StridedMatrix{T}, A::StridedMatrix{T}, B::StridedMatrix{T}, α::T, β::T) where {T<:BlasFloat} =
                           BLAS.gemm!('N', 'N', α, A, B, β, C))


"""
A basis of the classical Jacobi polynomials on the interval `[-1,1]`.
These polynomials are orthogonal with respect to the weight function
```
w(x) = (1-x)^α (1+x)^β.
```
"""
struct Jacobi{T} <: OPS{T}
    n       ::  Int
    α       ::  T
    β       ::  T

    Jacobi{T}(n, α = 0, β = 0) where {T} = new{T}(n, α, β)
end

Jacobi(n::Int; α = 0, β = 0) = Jacobi(n, α, β)
Jacobi(n::Int, α, β) = Jacobi(n, promote(α, β)...)
Jacobi(n::Int, α::T, β::T) where {T <: AbstractFloat} = Jacobi{T}(n, α, β)
Jacobi(n::Int, α::S, β::S) where {S} = Jacobi(n, float(α), float(β))

similar(b::Jacobi, ::Type{T}, n::Int) where {T} = Jacobi{T}(n, b.α, b.β)

support(b::Jacobi{T}) where {T} = ChebyshevInterval{T}()

first_moment(b::Jacobi{T}) where {T} = (b.α+b.β+1≈0) ?
    T(2).^(b.α+b.β+1)*gamma(b.α+1)*gamma(b.β+1) :
    T(2).^(b.α+b.β+1)*gamma(b.α+1)*gamma(b.β+1)/(b.α+b.β+1)/gamma(b.α+b.β+1)
    # 2^(b.α+b.β) / (b.α+b.β+1) * gamma(b.α+1) * gamma(b.β+1) / gamma(b.α+b.β+1)

jacobi_α(b::Jacobi) = b.α
jacobi_β(b::Jacobi) = b.β

measure(b::Jacobi) = JacobiWeight(b.α, b.β)

iscompatible(d1::Jacobi, d2::Jacobi) = d1.α == d2.α && d1.β == d2.β

isorthogonal(dict::Jacobi, measure::JacobiWeight) =
	dict.α == measure.α && dict.β == measure.β

gauss_rule(dict::Jacobi) = GaussJacobi(length(dict), dict.α, dict.β)

interpolation_grid(dict::Jacobi) = JacobiNodes(length(dict), dict.α, dict.β)
iscompatible(dict::Jacobi, grid::JacobiNodes) =  length(dict) == length(grid) && dict.α ≈ grid.α && dict.β ≈ grid.β
isorthogonal(dict::Jacobi, measure::GaussJacobi) = jacobi_α(dict) ≈ jacobi_α(measure) && jacobi_β(dict) ≈ jacobi_β(measure) && opsorthogonal(dict, measure)

issymmetric(dict::Jacobi) = jacobi_α(dict)≈jacobi_β(dict)

# See DLMF (18.9.2)
# http://dlmf.nist.gov/18.9#i
function rec_An(b::Jacobi{T}, n::Int) where {T}
    if (n == 0) && (b.α + b.β+1 == 0)
        one(T)/2*(b.α+b.β)+1
    else
        T(2*n + b.α + b.β + 1) * (2*n + b.α + b.β + 2) / T(2 * (n+1) * (n + b.α + b.β + 1))
    end
end

function rec_Bn(b::Jacobi{T}, n::Int) where {T}
    if (n == 0) && ((b.α + b.β + 1 == 0) || (b.α+b.β == 0))
        one(T)/2*(b.α-b.β)
    else
        T(b.α^2 - b.β^2) * (2*n + b.α + b.β + 1) / T(2 * (n+1) * (n + b.α + b.β + 1) * (2*n + b.α + b.β))
    end
end

rec_Cn(b::Jacobi{T}, n::Int) where {T} =
    T(n + b.α) * (n + b.β) * (2*n + b.α + b.β + 2) / T((n+1) * (n + b.α + b.β + 1) * (2*n + b.α + b.β))

function innerproduct_native(d1::Jacobi, i::PolynomialDegree, d2::Jacobi, j::PolynomialDegree, measure::JacobiWeight; options...)
	T = coefficienttype(d1)
	if iscompatible(d1, d2) && isorthogonal(d1, measure)
		if i == j
			a = d1.α
			b = d1.β
			n = convert(T, value(i))
			2^(a+b+1)/(2n+a+b+1) * gamma(n+a+1)*gamma(n+b+1)/factorial(n)/gamma(n+a+b+1)
		else
			zero(T)
		end
	else
		innerproduct1(d1, i, d2, j, measure; options...)
	end
end

## Printing
function show(io::IO, b::Jacobi{Float64})
	if jacobi_α(b) == 0 && jacobi_β(b) == 0
		print(io, "Jacobi($(length(b)))")
	else
		print(io, "Jacobi($(length(b)); α = $(jacobi_α(b)), β = $(jacobi_β(b)))")
	end
end

function show(io::IO, b::Jacobi{T}) where T
	if jacobi_α(b) == 0 && jacobi_β(b) == 0
		print(io, "Jacobi{$(T)}($(length(b)))")
	else
		print(io, "Jacobi{$(T)}($(length(b)); α = $(jacobi_α(b)), β = $(jacobi_β(b)))")
	end
end

# TODO: move to its own file and make more complete
# Or better yet: implement in terms of Jacobi polynomials
struct UltrasphericalBasis{T} <: OPS{T}
	n		::	Int
	alpha	::	T
end

jacobi_α(b::UltrasphericalBasis) = b.α
jacobi_β(b::UltrasphericalBasis) = b.α

weightfun(b::UltrasphericalBasis, x) = (1-x)^(b.α) * (1+x)^(b.α)


# === PRIVATE FUNCTIONS THAT ARE NOT EXPORTED =============================================================== #
# =========================================================================================================== #

# === PUBLIC FUNCTIONS THAT ARE EXPORTED ==================================================================== #
"""
    z_score_transform_data_set(dataFrame::DataFrame,colNameArray::Array{String,1})::VLResult
"""
function z_score_transform_data_set(dataFrame::DataFrame,colNameArray::Array{String,1})::VLResult

    # initialize -
    (number_of_rows, number_of_cols) = size(dataFrame)
    z_score_array = Array{Float64,2}(undef,number_of_rows,length(colNameArray))

    try

        # ok:  for each col, lets z-center the data -
        for (col_index,col_name) in enumerate(colNameArray)

            # grab the data -
            data_col = dataFrame[!,col_name]

            # transform -
            tmp = parse.(Float64,data_col)

            # compute the mean, std -
            µ = mean(tmp)
            σ = std(tmp)

            # compute the z-score -
            z_score_col = (tmp .- μ)./σ

            # add -
            for (row_index,value) in enumerate(z_score_col)
                z_score_array[row_index,col_index] = value
            end
        end

        # return -
        return VLResult(z_score_array)
    catch error
        return VLResult(error)
    end
end

"""
    z_score_transform_array(dataArray::Array{Float64,2})::VLResult
"""
function z_score_transform_array(dataArray::Array{Float64,2})::VLResult

    # initialize -
    (number_of_rows, number_of_cols) = size(dataArray)
    z_score_array = Array{Float64,2}(undef, number_of_rows, number_of_cols)

    try

        for col_index = 1:number_of_cols

            # grab the data col -
            data_col = dataArray[:,col_index]

            # zscale this col -
            zscale_vector_result = z_score_transform_vector(data_col)
            if (isa(zscale_vector_result.value, Exception) == true)
                throw(zscale_vector_result.value)
            end
            scaled_col_data = zscale_vector_result.value

            # package, and go around again ...
            for (row_index, scaled_value) in enumerate(scaled_col_data)
                z_score_array[row_index,col_index] = scaled_value
            end
        end

        # return -
        return VLResult(z_score_array)
    catch error
        return VLResult(error)
    end
end

"""
    z_score_transform_vector(dataVector::Array{Float64,1})::VLResult
"""
function z_score_transform_vector(dataVector::Array{Float64,1})::VLResult

    # initalize -
    number_of_elements = length(dataVector)
    z_score_array = Array{Float64,1}(undef, number_of_elements)

    try

        # compute the mean, std -
        µ = mean(dataVector)
        σ = std(dataVector)

        # scale -
        for index = 1:number_of_elements

            # get the raw value -
            raw_value = dataVector[index]

            # scale -
            z_score_array[index] =  (raw_value - µ)/σ
        end

        # rerurn -
        return VLResult(z_score_array)

    catch error
        return VLResult(error)
    end
end
# =========================================================================================================== #


using Pkg
Pkg.activate(".")
Pkg.instantiate()

using DifferentialEquations
using LinearAlgebra
using Plots

f(u,p,t) = 0.98*u

u0 = 1.0
tspan = (0.0, 1.0)

prob = ODEProblem(f, u0, tspan)

sol = solve(prob)

plot(sol; label="")

sol(0.8)

function lorenz(u, p, t)
    σ, ρ, β = p
    x_t = σ*(u[2]-u[1])
    y_t = u[1]*(ρ-u[3]) - u[2]
    z_t = u[1]*u[2] - β*u[3]
    return [x_t; y_t; z_t]
end

u0 = [1.0; 0.0; 0.0]
p = [10; 28; 8/3]

tspan = (0.0, 100.0)
prob = ODEProblem(lorenz, u0, tspan, p)


# Write functions as pure math
cartesian(r, θ, ϕ) = r*cos(θ)*sin(ϕ), r*sin(θ)*sin(ϕ), r*cos(ϕ)

f(x) = 2x^2+5x-10
∇f(x) = 4x+5
∇²f(x) = 4

# Set notation are defined operators
10 ∈ [12,13,14,15] # returns false


# Holds the possible QP-like objective structs

#############################################################
###  Structs for Quadratic and Linear Objective Functions ###
#############################################################

abstract type objectiveFunc end
abstract type objectiveQP_abstract <: objectiveFunc end

using SparseArrays

@doc raw"""
    objectiveQP(Q, p)

Quadratic (and Linear) Objective/Cost Function
----

```math
\frac{1}{2} x^{⊤} Q x + p^{⊤} x
```

`Q` is an nxn Symmetric Matrix (that is positive definite for convex)

`p` is an nx1 vector

See also: [`objectiveQP`](@ref), [`fObjQP`](@ref), [`dfdxQP`](@ref)
"""
struct objectiveQP <: objectiveQP_abstract
    "Positive Definite Symmetric Matrix"
    Q
    p
end

@doc raw"""
    objectiveQ(Q)

Quadratic (only) Objective/Cost Function
----

```math
\frac{1}{2} x^{⊤} Q x
```

`Q` is an nxn Symmetric Matrix (that is positive definite for convex)

See also: [`objectiveQP`](@ref) [`fObjQP`](@ref), [`dfdxQP`](@ref)
"""
struct objectiveQ <: objectiveQP_abstract
    "Positive Definite Symmetric Matrix"
    Q
end

@doc raw"""
    objectiveP(P)

Linear (only) Objective/Cost Function
----

```math
p^{⊤} x
```

`p` is an nx1 vector

See also: [`objectiveQP`](@ref), [`fObjQ`](@ref), [`dfdxQ`](@ref)
"""
struct objectiveP <: objectiveQP_abstract
    "Positive Definite Symmetric Matrix"
    p
end

#############################################################
###       Evaluate Quadratic and Linear Objectives        ###
#############################################################

"""
    fObjQP(qp::objectiveQP, x)

Evaluates a quadratic function at the input `x`. The input `x` should be a
*column* vector (i.e. `x = [4; 3]`)
"""
function fObjQP(qp::objectiveQP, x)
    return (1/2) * x' * (qp.Q) * x + (qp.p)' * x
end

"""
    fObjQP(q::objectiveQ, x)

Evaluates a quadratic (only) function at the input `x`. The input `x` should
be a *column* vector (i.e. `x = [4; 3]`)
"""
function fObjQP(q::objectiveQ, x)
    return (1/2) * x' * (q.Q) * x
end

"""
    fObjQP(p::objectiveP, x)

Evaluates a linear function at the input `x`. The input `x` should be a
*column* vector (i.e. `x = [4; 3]`)
"""
function fObjQP(p::objectiveP, x)
    return (p.p)' * x
end

#############################################################
###     Gradients for Quadratic and Linear Objectives     ###
#############################################################

@doc raw"""
    dfdxQP(qp::objectiveQP, x)

Evaluates the derivative of a quadratic function at input `x`. The input `x`
should be a *column* vector (i.e. `x = [4; 3]`)

```math
\frac{d}{dx} (\frac{1}{2} x^{⊤} Q x + p^{⊤} x) = Qx + p
```
"""
function dfdxQP(qp::objectiveQP, x)
    return (qp.Q) * x + qp.p
end

@doc raw"""
    dfdxQ(q::objectiveQ, x)

Evaluates the derivative of a quadratic (only) function at input `x`. The
input `x` should be a *column* vector (i.e. `x = [4; 3]`)

```math
\frac{d}{dx} (\frac{1}{2} x^{⊤} Q x) = Qx
```
"""
function dfdxQP(q::objectiveQ, x)
    return (q.Q) * x
end

@doc raw"""
    dfdxQP(p::objectiveP, x = 0)

Evaluates the derivative of a linear function. The `x` is unused, but is kept
to maintain the same function header.

```math
\frac{d}{dx} (p^{⊤} x) = p
```
"""
function dfdxQP(p::objectiveP, x = 0)
    return p.p
end

#############################################################
###      Hessians for Quadratic and Linear Objectives     ###
#############################################################

@doc raw"""
    hessQP(qp::objectiveQP)

Evaluates the hessian of a quadratic function.

```math
H(\frac{1}{2} x^{⊤} Q x + p^{⊤} x) = Q
```

Note that Q is already square, symmetric, and (generally) positive definite.
"""
function hessQP(qp::objectiveQP)
    return qp.Q
end

@doc raw"""
    hessQP(q::objectiveQ)

Evaluates the hessian of a quadratic (only) function.

```math
H(\frac{1}{2} x^{⊤} Q x) = Q
```

Note that Q is already square, symmetric, and (generally) positive definite.
"""
function hessQP(q::objectiveQ)
    return q.Q
end

@doc raw"""
    hessQP(q::objectiveQ)

Evaluates the hessian of a linear (only) function. (which is zero)

```math
H(p^{⊤} x) = 0
```
"""
function hessQP(p::objectiveP)
    n = size(p.p, 1)
    return spzeros(n, n)
end

#############################################################


struct PolynomialBasis
    polynomial::AbstractVector{T} where T <: AbstractPolynomialLike
    x::AbstractVector{T} where T <: AbstractPolynomialLike
end

"""
# Notes
`with_bias=true` provides polynomials with the degree of from 0 to `d`.
`with_bias=false` provides polynomials with the degree of `d`.
# Variables
n ∈ N: length of array, i.e., x ∈ Rⁿ
d ∈ N: degree
"""
function PolynomialBasis(n::Int, d::Int; with_bias=true)
    @assert n >= 1 && d >= 0
    _n = with_bias ? n+1 : n
    exponents = multiexponents(_n, d)
    @polyvar x[1:n]
    _x = with_bias ? [x..., 1] : x
    polynomial = exponents |> Map(exponent -> prod(_x.^exponent)) |> collect
    PolynomialBasis(polynomial, x)
end

function (a::PolynomialBasis)(x::AbstractArray)
    a.polynomial |> Map(poly -> poly(a.x => x)) |> collect
end


"""
    generate_image(size, porosity, distribution)

Generate an image of overlapping `n`-dimensional balls with radii
sampled from `distribution`. The image is generated with specified
`size` and `porosity`.
"""
function generate_image(size,
                        porosity     :: AbstractFloat,
                        distribution :: Distribution)
    image     = zeros(Bool, size) |> BitArray
    cur_pores = prod(size)
    pores     = porosity * cur_pores

    indices    = CartesianIndices(image)
    fidx, lidx = first(indices), last(indices)
    uidx       = oneunit(fidx)

    while true
        radius  = rand(distribution)
        iradius = radius |> ceil |> Int
        center  = rand(indices)

        from = max(center - iradius*uidx, fidx)
        to   = min(center + iradius*uidx, lidx)

        old_pores = count(iszero, image[from:to])

        for idx in from:to
            r = sqrt(sum(x^2 for x in Tuple(idx - center)))
            if r <= radius
                image[idx] = 1
            end
        end

        new_pores = count(iszero, image[from:to])
        cur_pores += new_pores - old_pores

        if cur_pores < pores
            return image
        end
    end
end


"""
```
Model(y, H, A, Q, R, x, P)
```

The type Model contains the measurements matrix as well as the kalman setup:
Measurement:  y(t)     = H * x(t) + e(t) ~ N(0, R)
State:        x(t + 1) = A * x(t) + n(t) ~ N(0, Q)

where both y(t) and x(t+1) are vectors of length p and m, respectively.
The state transition matrix, A, is (m x m) while the measurement model matrix, H, is (p x m).

Time-varying H and A are not currently implemented. Similarly, the covariances
R and Q are constant across time.
"""

mutable struct Model
    y::Matrix{<:Real}
    H::Matrix{<:Real}
    A::Matrix{<:Real}
    Q::Matrix{<:Real}
    R::Matrix{<:Real}
    x::Vector{<:Real}
    P::Matrix{<:Real}
    predicted::Predicted
    filtered::Filtered
    smoothed::Smoothed
    function Model(y, H, A, Q, R, x, P)
        str = "y is $(size(y, 1))x$(size(y, 2)), " *
              "H is $(size(H, 1))x$(size(H, 2)), " *
              "A is $(size(A, 1))x$(size(A, 2)), " *
              "Q is $(size(Q, 1))x$(size(Q, 2)), " *
              "R is $(size(R, 1))x$(size(R, 2)), " *
              "x is $(length(x)), " *
              "P is $(size(P, 1))x$(size(P, 2))"
        !(size(H, 2) == size(A, 1) == size(A, 2) == size(Q, 1) == size(Q, 2) == length(x) == size(P, 1) == size(P, 2)) && throw(DimensionMismatch(str))
        !(size(y, 2) == size(H, 1) == size(R, 1) == size(R, 2)) && throw(DimensionMismatch(str))

        @assert all(diag(R) .> 0) "All diagonal elements of R must be strictly positive."
        @assert all(diag(Q) .> 0) "All diagonal elements of Q must be strictly positive."
        @assert all(diag(P) .> 0) "All diagonal elements of P must be strictly positive."

        predicted = Predicted(size(y, 1), size(y, 2), length(x))
        filtered  = Filtered( size(y, 1), size(y, 2), length(x))
        smoothed  = Smoothed( size(y, 1), size(y, 2), length(x))

        return new(y, H, A, Q, R, x, P, predicted, filtered, smoothed)
    end
end

# Outer constructors for univariate measurement
Model(y::Vector{<:Real}, H::Vector{<:Real}, A::Matrix{<:Real}, Q::Matrix{<:Real}, R::Real, x::Vector{<:Real}, P::Matrix{<:Real}) = Model(repeat(y, 1, 1), reshape(H, 1, length(H)), A, Q, fill(R, 1, 1), x, P)
Model(y::Vector{<:Real}, H::Real, A::Real, Q::Real, R::Real, x::Real, P::Real) = Model(repeat(y, 1, 1), fill(H, 1, 1), fill(A, 1, 1), fill(Q, 1, 1), fill(R, 1, 1), [x], fill(P, 1, 1))

# Univariate state
Model(y::Matrix{<:Real}, H::Matrix{<:Real}, A::Real, Q::Real, R::Matrix{<:Real}, x::Real, P::Real) = Model(y, H, fill(A, 1, 1), fill(Q, 1, 1), R, [x], fill(P, 1, 1))

# Special constructors
LocalLevel(y::Vector{<:Real}, Q::Real, R::Real, x::Real, P::Real) = Model(repeat(y, 1, 1), ones(1, 1), ones(1, 1), fill(Q, 1, 1), fill(R, 1, 1), [x], fill(P, 1, 1))
LocalLevel(y::Matrix{<:Real}, Q::Real, R::Matrix, x::Real, P::Real) = Model(y, ones(size(y, 2), 1), ones(1, 1), fill(Q, 1, 1), R, [x], fill(P, 1, 1))

LocalLevel(y::Vector{<:Real}, Q::Real, R::Real) = Model(repeat(y, 1, 1), ones(1, 1), ones(1, 1), fill(Q, 1, 1), fill(R, 1, 1), [y[1]], fill(1000.0, 1, 1))
LocalLevel(y::Matrix{<:Real}, Q::Real, R::Matrix) = Model(repeat(y, 1, 1), ones(1, 1), ones(1, 1), fill(Q, 1, 1), R, [0.0], fill(1000.0, 1, 1))

"""
```
Output
```

The type Output contains the means and covariances for the measurements
and states, respectively, at each time step, t = 1,...,T:
x is a (T x m) matrix of state means
P is a (T x m x m) array of state covariances
μ is a (T x p) matrix of measurement means
Σ is a (T x p x p) matrix of measurement covariances
"""

abstract type Output end

mutable struct Predicted <: Output
    x::Matrix{AbstractFloat}
    P::Array{AbstractFloat, 3}
    μ::Matrix{AbstractFloat}
    Σ::Array{AbstractFloat, 3}
end

mutable struct Filtered <: Output
    x::Matrix{AbstractFloat}
    P::Array{AbstractFloat, 3}
    μ::Matrix{AbstractFloat}
    Σ::Array{AbstractFloat, 3}
end

mutable struct Smoothed <: Output
    x::Matrix{AbstractFloat}
    P::Array{AbstractFloat, 3}
    μ::Matrix{AbstractFloat}
    Σ::Array{AbstractFloat, 3}
end

Predicted(T::Int, p::Int, m::Int) = Predicted(zeros(T, m), zeros(T, m, m), zeros(T, p), zeros(T, p, p))
Filtered(T::Int, p::Int, m::Int)  = Filtered(zeros(T, m), zeros(T, m, m), zeros(T, p), zeros(T, p, p))
Smoothed(T::Int, p::Int, m::Int)  = Smoothed(zeros(T, m), zeros(T, m, m), zeros(T, p), zeros(T, p, p))


using JuMP
using GLPK

model = Model(GLPK.Optimizer)
@variable(model, x >= 0)
@variable(model, y >= 0)
@constraint(model, 6x + 8y >= 100)
@constraint(model, 7x + 12y >= 120)
@objective(model, Min, 12x + 20y)

optimize!(model)

@show value(x);
@show value(y);
@show objective_value(model);

using JuMP

using GLPK

model = Model(GLPK.Optimizer);

@variable(model, x >= 0)
@variable(model, y >= 0);

@constraint(model, 6x + 8y >= 100)
@constraint(model, 7x + 12y >= 120);

@objective(model, Min, 12x + 20y);

optimize!(model)

@show value(x);
@show value(y);
@show objective_value(model);


# Using the BBP formula
#
# The Bailey–Borwein–Plouffe formula (BBP formula) is a formula for π

#=
Computing Bailey–Borwein–Plouffe formula for Pi
to ascertain the performance of FastRational{BigInt}
relative to Rational{BigInt} by BPP iterations

FastRational{BigInt}s perform better below n=328
Rational{BigInt}s     perform better above n=330

As BPP iterations increase, numerator and denominator grow.
After some digit count in numerator aor denominator, ~10_000,
The extra time spent in divgcd reductions becomes less than
the time spent performing arithmetic on very large integers;
so reducing the numerator and denominator begins to win.
=#

using FastRationals
using BenchmarkTools

const BT=BenchmarkTools.DEFAULT_PARAMETERS;
BT.overhead=BenchmarkTools.estimate_overhead();
BT.evals=1; ; BT.time_tolerance = 5.0e-7; BT.samples = 100;

const big1 = BigInt(1)
const big2 = BigInt(2)
const big4 = BigInt(4)
const big5 = BigInt(5)
const big6 = BigInt(6)
const big8 = BigInt(8)
const big16 = BigInt(16)

function bpp(::Type{T}, n) where {T}
    result = zero(T)
    for k = 0:n
       eightk = big8 * k
       cur = T(big4,eightk+1) -
             T(big2,eightk+4) -
             T(big1,eightk+5) -
             T(big1,eightk+6)
       cur = T(big1, big16^k) * cur
       result = result + cur
    end
    return result
end

systemqtime = @belapsed bpp(Rational{BigInt},   15);
fastqtime = @belapsed bpp(FastRational{BigInt}, 15);
bpp15 = round(systemqtime/fastqtime, digits=1)

systemqtime = @belapsed bpp(Rational{BigInt},   125);
fastqtime = @belapsed bpp(FastRational{BigInt}, 125);
bpp125 = round(systemqtime/fastqtime, digits=1)

systemqtime = @belapsed bpp(Rational{BigInt},   250);
fastqtime = @belapsed bpp(FastRational{BigInt}, 250);
bpp250 = round(systemqtime/fastqtime, digits=1)

# relspeeds meet at n=328

systemqtime = @belapsed bpp(Rational{BigInt},   500);
fastqtime = @belapsed bpp(FastRational{BigInt}, 500);
bpp500 = round(systemqtime/fastqtime, digits=1)

systemqtime = @belapsed bpp(Rational{BigInt},   1000);
fastqtime = @belapsed bpp(FastRational{BigInt}, 1000);
bpp1000 = round(systemqtime/fastqtime, digits=1)

systemqtime = @belapsed bpp(Rational{BigInt},   2000);
fastqtime = @belapsed bpp(FastRational{BigInt}, 2000);
bpp2000 = round(systemqtime/fastqtime, digits=1)

systemqtime = @belapsed bpp(Rational{BigInt},   3000);
fastqtime = @belapsed bpp(FastRational{BigInt}, 3000);
bpp3000 = round(systemqtime/fastqtime, digits=1)

relspeeds = (bpp15=bpp15, bpp125=bpp125, bpp250=bpp250,
             bpp500=bpp500, bpp1000=bpp1000, bpp2000=bpp2000,
             bpp3000=bpp3000)

relspeeds = (bpp15 = 0.3, bpp125 = 0.3, bpp250 = 0.7, bpp500 = 2.4, bpp1000 = 8.1, bpp2000 = 20.9, bpp3000 = 36.0)

xs = [15,125,250,500,1000,2000,3000];
ys = [relspeeds...,];

using GR
plot(xs, ys, size=(500,500))


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.5.0
#   kernelspec:
#     display_name: Julia 1.4.2
#     language: julia
#     name: julia-1.4
# ---

# # Gradient descent (勾配法)
#
# 勾配法の練習として下記の簡単な設定のもとで勾配法を逐次適用することで目的関数の極値問題を解く際の点の動きを可視化する.
#
# $A$ を 2x2 の実対称行列とし, $f=f(x,y)$ を次で定める二変数関数とする.
#
# $$
# f(x,y) =  \begin{bmatrix}x & y\end{bmatrix} A \begin{bmatrix} x \\ y \end{bmatrix}
# $$
#
# このとき $f$ の勾配ベクトルは $\nabla f$ は
#
# $$
# \nabla f= \begin{bmatrix} \frac{\partial f}{\partial x} \\ \frac{\partial f}{\partial y} \end{bmatrix} = A \begin{bmatrix} x \\ y \end{bmatrix}
# $$
#
# と書ける. 初期位置を $v_0\in\mathbb{R}^2$, 学習率を $\eta$ とすれば $i$ 番目のステップで得られる点は次のようになる.
#
# $$
# v_{i} = v_{i-1} - \eta \nabla f|_{(x,y)=v_{i-1}} \quad (i=1,2,\dots)
# $$

using Colors
using LinearAlgebra
using Plots

# +
A = [
    2.0  0.2
    0.2  1.0
]

f(v) =  dot(v,A,v)
f(x, y) = f([x, y])
∇f(v) = A * v
η = 0.05 # learning rate
# -

# ## 等高線の描画

x = range(-10,stop=10,length=50)
y = range(-10,stop=10,length=50)
p = contour(x, y, f)

# ## 勾配法を試す

# +
x = range(-5, stop=5, length=50)
y = range(-5, stop=5, length=50)
p = contour(x, y, f, levels=range(0, stop=100, step=3))

v = [-5., -5.] # initial point
for i in 1:100
    v .-= η .* ∇f(v)
    scatter!(
        p, [v[1]], [v[2]],
        label=:none, color=:blue, msize=3,
    )
end

plot(p)
# -

# ## ランダムに点を配置しそれを初期値とする勾配法

# +
x = range(-5, stop=5, length=50)
y = range(-5, stop=5, length=50)
p = contour(x, y, f, levels=range(0,stop=100,step=3))
for _ in 1:10
    v = rand(range(-5,stop=5,step=0.01), 2)
    c =　RGB(rand(3)...)
    for _ in 1:30 # num steps
        v .-= η .* ∇f(v)
        scatter!(
            p, [v[1]], [v[2]],
            label=:none, color=c, msize=3,
        )
    end
end

plot(p)


"""
    Ricker(; <keyword arguments>)

Create a Ricker wavelet.

# Keyword arguments
* `dt::Real=0.002`: sampling interval in secs.
* `f0::Real=20.0`: central frequency in Hz.

# Examples
```julia
julia> w = Ricker(); plot(w);
julia> w = Ricker(dt=0.004, f0=20); plot(w);
```

# Reference
Sheriff, Robert, 2002, Encyclopedic Dictionary of Applied Geophysics, fourth
ed.: Society of Exploration Geophysicists. Geophysical Reference Series No. 13.
"""

function Ricker(; dt::Real=0.002, f0::Real=20.0)

    nw = 2.0/(f0*dt)
    nc = floor(Int, nw/2)
    t = dt*collect(-nc:1:nc)
    b = (pi*f0*t).^2
    w = (1.-2.*b).*exp(-b)

end


# This program computes Feigenbaum's alpha constant using
# Feigenbaum's functional equation derived via renormalization
# group methods.
# Oct 2017 -- Stuart Brorson, sdb@cloud9.net

using ForwardDiff

# I define the helper fcns first.

#-------------------------------------------------------
# Define universal fcn g(z, a).  It is expressed as a power
# series, coefficients a.  Only even powers are needed since
# g(z) is even.
function g(z, a::Vector)
  #println("---> Entered g\n")
  s = 0;
  for i=length(a):-1:1
    # println("i = $i, s = $s\n")
    s = z*z*(s + a[i]);
  end
  #println("<--- Leaving g\n")
  return (1+s)
end

#---------------------------------------------------------
# Define relation obeyed by g(z).  Name it f(z, a)
function f(z, a::Vector)
  #println("---> Entered f\n")
  const one = big(1.0)
  alpha = one/g(one, a);
  r = g(z, a) - alpha*g(g(z/alpha, a), a);
  #println("<--- Leaving g\n")
  return r
end

#--------------------------------------------------------
# Define fcn which takes gradient of f w.r.t. a.
# Returns row vector which is gradient of f w.r.t. a,
# evaluated at z, a.
function gradf(z, a::Vector)
  #println("---> Entered gradf\n")
  # Specialize to position z.  Result is fcn of coeffs a only.
  fbeta(a) = f(z, a);

  # Compute gradient w.r.t. beta and return it
  gf1 = ForwardDiff.gradient(fbeta, a)
  #println("<--- Leaving gradf\n")
  return gf1'
end

# ------------------------------------------------------------
# The main event -- a computation of alpha using Newton's method.
function compute_alpha(Numdigs, N, betain)

  # First set up variables, including BigFloat settings
  # Setting precision to 5x Numdigs seems to keep Newton's method
  # from wandering away.  Smaller values don't.  More fidding might
  # improve things here.
  setprecision(5*Numdigs);

  # Set stopping tol for Newton's method so I get the number of digits
  # I want.
  const tol = BigFloat(10.0)^-(Numdigs);

  const zi = big.(linspace(1/N, 1, N));   # Grid of sample points zi
  fn = zeros(BigFloat, N);             # Function f vector
  Jn = zeros(BigFloat, N, N);        # Jacobian

  # Need expansion coeffs beta.  Start with the one passed in, betain.
  # N should always be larger than length(betain), so we are adding to the
  # betas we computed last time.
  betan = zeros(BigFloat, N);
  for i = 1:length(betain)
    betan[i] = betain[i];
  end

  # println("-----------------------------\n")
  sttim = now();  # Keep track of loop timing.
  # Now enter Newton loop.
  for cnt = 1:50
    nowtim = now();  # Keep track of loop timing
    println("Newton iteration = $cnt, time elapsed = $(nowtim-sttim)\n")

    # Compute new f and Jacobian upon each iteration
    for i = 1:N
      fn[i] = f(zi[i], betan);
      Jn[i,:] = gradf(zi[i], betan);
    end

    # Now compute step to take, then take it.
    sn = Jn\fn;
    betanp1 = betan - sn;

    # Check for convergence
    if (norm(sn) < tol)
      println("\nDone!  Converged after $cnt iterations.\n")
      # compute alpha
      myalpha = 1/g(1, betanp1);
      return myalpha, betanp1;
    end

    # Move values back
    betan = betanp1

  end
  println("!!!!!!!  Didn't converge!  !!!!!!!!\n")

end

#--------------------------------------------------
function iterate_alpha()
  # This is a runner -- call this fcn.  This fcn calls
  # compute_alpha asking for different number of digits
  # at different precision/tolerance levels.
  # It calls with the beta coefficients computed in the
  # last iteration as the starting point to help Newton's
  # method from wandering away.

  # Seed betan with 3 values.
  betan = zeros(BigFloat, 3);
  betan[1] = big(-1.52763883147);
  betan[2] = big(1.048327004372e-1);
  betan[3] = big(2.669121419134012e-2);

  # Step up number of pts to compute on.  Change this to get
  # more digits
  for N = 10:20:1700
    println("=================================\n")
    println("N = $N\n")
    Numdigs = Int(floor(2*N));

    # Call the function which computes alpha at this level
    # of precision.
    myalpha, betan = compute_alpha(Numdigs, N, betan)

    # Print out myalpha to N digits of precision.  Must add 3
    # to account for negative sign and "2."
    a = string(myalpha);
    println("myalpha = $(a[1:N+3])\n")

    f = open("alpha.dat", "a");
    write(f, "==========\nN = $N\n$(a[1:N+3])\n")
    close(f)

    # Compare against the version from
    # http://www.plouffe.fr/simon/constants/feigenbaum.txt
    oeisalpha = oeis_alpha(3*Numdigs);
    diff = Float64(abs(myalpha) - abs(oeisalpha));
    println("diff = $diff\n")
  end

end

#------------------------------------------------
function oeis_alpha(Numdigs)
  # Return up to 1018 digits of alpha from
  # http://www.plouffe.fr/simon/constants/feigenbaum.txt

  if (Numdigs <= 1018)
    setprecision(Numdigs);
  else
    setprecision(1018);
  end

  str = "-2."
  str = str*"5029078750958928222839028732182157863812713767271499773361920567"
  str = str*"7923546317959020670329964974643383412959523186999585472394218237"
  str = str*"7785445179272863314993372578112163594879503744781260997380598671"
  str = str*"2397117373289276654044010306698313834600094139322364490657889951"
  str = str*"2205843172507873377463087853424285351988587500042358246918740820"
  str = str*"4281700901714823051821621619413199856066129382742649709844084470"
  str = str*"1008054549677936760888126446406885181552709324007542506497157047"
  str = str*"0475419932831783645332562415378693957125097066387979492654623137"
  str = str*"6745918909813116752434221110130913127837160951158341230841503716"
  str = str*"4997020224681219644081216686527458043026245782561067150138521821"
  str = str*"6449532543349873487413352795815351016583605455763513276501810781"
  str = str*"1948369459574850237398235452625632779475397269902012891516645793"
  str = str*"9420198920248803394051699686551494477396533876979741232354061781"
  str = str*"9896112494095990353128997733611849847377946108428833293833903950"
  str = str*"9008914086351525626803381414669279913310743349705143545201344643"
  str = str*"4264752001621384610729922641994332772918977769053802596851"

  alpha = parse(BigFloat, str)

end


using LinearAlgebra
using DataFrames
using CSV

#This file contains various renditions of  which perform numerical
#quadrature given integration endpoints and a function handle
#More importantly, it provides a composite type which stores nodes and weight.

"""
Data-type which stores quadrature nodes and weights
"""

mutable struct nodesWeights{T1<:Array{Float64}, T2<:Array{Float64}}
    nodes::T1
    weights::T2
end

"""
Apply affine transformation to nodes to integration over range r (linear change of variables)
"""
function affineTransformNodes(nw::nodesWeights{O}, r::Array{<:Real}) where O<:Union{Array{Float64, 2}, Array{Float64, 1}}
    b = r[2]; a = r[1]
    nw.nodes = (b-a)/2 .* nw.nodes .+ (b + a)/2
end

"""
Uses the Golub-Welsch eigenvalue method to compute
Gauss-Legendre quadrature nodes and weights for the domain [-1, 1]

INPUTS: n, the number of desired nodes
OUTPUTS: x and w, the nodes and weights
"""
function gausslegpts(n)
    x = zeros(n-1)
    for i = 1:n-1
        x[i] = i/sqrt(4*i^2-1)
    end
    x = SymTridiagonal(zeros(n), x) #Jacobi matrix
    d, V = eigen(x)
    w = 2*V[1,:].^2 #factor of 2 comes from the fact that integral of measure is 2
    return d, w
end

#precomute various integration nodes/weights
dm0, wm0 = gausslegpts(24)
dm, wm = gausslegpts(12)
dm2, wm2 = gausslegpts(50)
dm3, wm3 = gausslegpts(100)

function getGaussQuadraturedata()
    nodesWeights(dm, wm)
end

"""
INPUTS:
f is a function handle, a and b are integration endpoints (a<b), and n
is the number of quadrature nodes (defaults to 10). Note that f can be
multi-output, in which case int1D integrates each entry individually. This way,
the function lends itself to being composed with Gauss-Turan integration.
"""
function int1D(f, arr, num="1")
    if num=="1"
        x, w= dm, wm
    elseif num=="2"
        x, w= dm2, wm2
    elseif num=="0"
        x, w = dm0, wm0
    else
        x, w = dm3, wm3
    end
    sample = f(arr[1])
    res = zeros(size(sample))
    for i = 1:length(x)
        expr = (arr[2]-arr[1])/2 * x[i] .+ (arr[1]+arr[2])/2
        #println(res)
        #println(f(expr) .* w[i])
        res = res .+ f(expr) .* w[i]
    end
    res = (arr[2]-arr[1])/2 .* res
end

function int1D_print(f, arr, num="0")
    if num=="1"
        x, w= dm, wm
    elseif num=="2"
        x, w= dm2, wm2
    elseif num=="0"
        x, w = dm0, wm0
    else
        x, w = dm3, wm3
    end
    sample = f(arr[1])
    res = zeros(size(sample))
    for i = 1:length(x)
        expr = (arr[2]-arr[1])/2 * x[i] .+ (arr[1]+arr[2])/2
        println(res)
        println(f(expr) .* w[i])
        res = res .+ f(expr) .* w[i]
    end
    res = (arr[2]-arr[1])/2 .* res
end

"""
Integrates function over 2D domain using Gauss-Legendre Quadrature
arr[:, 1] are lower bounds, while arr[:, 2] are upper bounds
"""
function int2D(f, arr)
    d = size(arr, 1) #number of dimensions
    int = 0.0
    x, w = dm, wm
    avg1 = (arr[1, 2] - arr[1, 1])/2
    avg2 = (arr[2, 2] - arr[2, 1])/2
    for i = 1:length(x)
        for j = 1:length(x)
           expr1 = avg1*x[i] + (arr[1, 2]+arr[1, 1])/2
           expr2 = avg2*x[j] + (arr[2, 2]+arr[2, 1])/2
           int = int + w[i]*w[j]*f(expr1, expr2)
        end
    end
    int = int*avg1*avg2
end

data50 = DataFrame(CSV.File("nodes_weights_50.csv", header=0))
nodes50 = convert(Array, data50[:,1]) #integration nodes for Gauss-Turan Quadrature
weights50 = convert(Matrix, data50[:, 2:end]) #integration weights

data20 = DataFrame(CSV.File("nodes_weights_20.csv", header=0))
nodes20  = convert(Array, data20[:,1])
weights20 = convert(Array, data20[:,2:end])

data12 = DataFrame(CSV.File("nodes_weights_12.csv", header=0))
nodes12  = convert(Array, data12[:,1])
weights12 = convert(Array, data12[:,2:end])

function getTuranQuadratureData()
    return nodesWeights(nodes12, weights12)
end

"""
Gauss-Turan integration with 2 derivatives
INPUTS:
arr contains enpoints of integration interval
"""
function Gauss_Turan(F, arr, nodes = nodes12, weights=weights12)
    a = arr[1]
    b = arr[2]
    nodes = (b-a)/2 .* nodes .+ (b+a)/2
    T = zeros(length(nodes), 3)
    time = @elapsed begin
    for i = 1:length(nodes)
        (T[i, 1], T[i, 2], T[i, 3]) = F(nodes[i])
    end
    end
    println(time)
    fn = T[:, 1]
    dfn = T[:, 2]
    d2fn = T[:, 3]
    return (fn'*weights[:, 1] + dfn'*weights[:, 2] + d2fn'*weights[:, 3])*(b-a)/2
end

"""
n-dimensional Gauss-Legendre integration
"""
function intnD(f, arr, n)

end


function chobac!(kb::Matrix{Float64}, loads::Vector{Float64})
  #local l, m
  n = size(kb, 1)
  iw = size(kb, 2) - 1
  loads[1] = loads[1] / kb[1, iw+1]
  for i in 2:n
    x = 0.0
    k = 1
    if i <= (iw + 1)
      k = iw - i + 2
    end
    for j in k:iw
      x += kb[i, j]*loads[i+j-iw-1]
    end
    loads[i] = (loads[i] - x) / kb[i, iw+1]
  end
  loads[n] = loads[n] / kb[n, iw+1]
  for i in n-1:-1:1
    x = 0.0
    l = i + iw
    m = l - 1
    if i > (n - iw)
      l = n
      m = i + 1
    end
    for j in m:l
      x += kb[j, iw+i-j+1] * loads[j]
    end
    loads[i] = (loads[i] - x) / kb[i, iw+1]
  end
end


# [18](https://projecteuler.net/problem=18) / [67](https://projecteuler.net/problem=67)

#In a triangle like this:
#
#      a
#     b c
#    d e f
#
#the best way to find the anwser is not to get the maximum from the summaries of
#all possible branches from top to bottom.
#
#There is such a recursive pattern
#
#a + max( b+max(b,c), c+max(e,f)
#
# tri.txt
#75
#95 64
#17 47 82
#18 35 87 10
#20 04 82 47 65
#19 01 23 75 03 34
#88 02 77 73 07 63 67
#99 65 04 28 06 16 70 92
#41 41 26 56 83 40 80 70 33
#41 48 72 33 47 32 37 16 94 29
#53 71 44 65 25 43 91 52 97 51 14
#70 11 33 28 77 73 17 78 39 68 17 57
#91 71 52 38 17 14 91 43 58 50 27 29 48
#63 66 04 68 89 53 67 30 73 16 69 87 40 31
#04 62 98 27 23 09 70 98 73 93 38 53 60 04 23

import Base.zero
zero(::SubString{String}) = 0 # Julia 0.5

ZeroString(::SubString{String}) = 0
ZeroString(x::Int64) = x

A = readdlm("tri.txt")
A = ZeroString.(A)

function myreduction(m)
	if size(m)[1] == 1
		return m[1,1]
	else
		mprime = m[1:(end-1), :]
		for k in 1:(size(m, 1)-1)
			mprime[size(mprime, 1), k] += max(
									 m[size(m, 1),k], m[size(m, 1),k+1])
		end
		return myreduction(mprime)
	end
end

println(myreduction(A))


## This function computes the Generalized Laguerre polynomials using the Tridiagonal matrix
include("orthopoly_evaluate.jl")
function compute_laguerre(n, a, x, all=true)
    # Evaluate Orthonormalized Laguerre polynomials at x
    #  Laguerre:
    #     w(x)=x^a exp(-x) with integral gamma(a+1)  (a>-1)
    #     Equivalent Mathematica:LaguerreL[n,a,x]/Sqrt[Gamma[n+a+1]/Gamma[n+1]]
    ## Laguerre Bidiagonal matrix
    B = diagm(sqrt((a + 1 +(0:n)))) # diagonal
    B = B - diagm(sqrt((1:n)), 1)   # superdiagonal
    T = B'*B
    c = gamma(a+1) #Integral of weight function
    ## Default to 0 through n computation
    if all
        phi = orthopoly_evaluate_all(T, x)
    else
        phi = orthopoly_evaluate_n(T, x)
    end
    phi/sqrt(c)
end


function sieve2(n :: Int)
    ni = (n - 1) ÷ 2
    isprime = trues(ni)
    for i in 1:ni
        if isprime[i]
            j = 2i * (i + 1)
            if j > ni
                m = findall(isprime)
                map!((i::Int) -> 2i + 1, m, m)
                return pushfirst!(m, 2)
            else
                p = 2i + 1
                while j <= ni
                  isprime[j] = false
                  j += p
                end
            end
        end
    end
end


module Day05

using OffsetArrays

export day05

function parseLine(line)
    raw = split.(split(line, " -> "), ",")
    return complex(parse.(Int64, raw[1])...), complex(parse.(Int64, raw[2])...)
end

function readInput(inputfile="inputs/input05.txt")
    return [parseLine(line) for line in eachline(inputfile)]
end

function day05(ventdata = readInput("inputs/input05.txt"))
    minx = minimum((a) -> min(real(a[1]), real(a[2])), ventdata)
    miny = minimum((a) -> min(imag(a[1]), imag(a[2])), ventdata)
    maxx = maximum((a) -> max(real(a[1]), real(a[2])), ventdata)
    maxy = maximum((a) -> max(imag(a[1]), imag(a[2])), ventdata)

    ## previous version used a Dict here
    ## that's ~25x slower
    grid = OffsetArray(zeros(UInt8, maxx-minx+1, maxy-miny+1), minx:maxx, miny:maxy)

    mask = ishorizontal.(ventdata)
    mapvents!(grid, ventdata[mask])
    part1 = count(>=(2), grid)

    mapvents!(grid, ventdata[.!(mask)])
    part2 = count(>=(2), grid)
    return part1, part2
end

function mapvents!(grid, data)
    for (start, stop) in data
        Δ = stop-start
        dist = distance(Δ)
        δ = intdiv(Δ, dist)
        for l in 0:dist
            pos = start+δ*l
            grid[real(pos), imag(pos)] += 1
        end
    end
    return grid
end

ishorizontal((from, to)) = real(from) == real(to) || imag(from) == imag(to)
distance(x::Complex) = let r = abs(real(x)); r == 0 ? abs(imag(x)) : r end
intdiv(x::Complex, by::Int) = complex(div(real(x), by), div(imag(x), by))

count_crossings(grid) = count(>=(2), values(grid))

end #module


push!(LOAD_PATH,joinpath(@__DIR__, "../src/"))
using LinearAlgebra
using StableApproxEPW

# ## Target of the approximation problem

# We consider the Helmholtz solution in the unit disk, with wavenumber
k = 5;
# We need to define the maximum Fourier mode number `P` in the approximation
# target:
P = 25;
# The target approximation will be of the form
# ```math
# u = \mathbf{x} ↦ \sum_{|p| \leq P} u_p b_{p}(\mathbf{x}).
# ```
# Next we construct the vector of coefficients in the basis `b_p`
# for `p` in `[-P,P]`:
U = zeros(ComplexF64, 2P+1);
U[P+1]     = 0.5;  # This is the constant mode (`p=0`)
U[P+1 + P] = 1im;  # This is mode `p = P`
# Here the only non-zero coefficients are ``u_0 = \frac{1}{2}`` and
# ``u_P = \imath`` so that
# ```math
# u = \frac{1}{2} b_{0} + \imath b_{P}.
# ```
# The target of the approximation problem can then be constructed as:
u = solution_surrogate(U; k=k);
# ``u`` can be evaluated at any `(r,θ)` point.
# Alternatively, the target ``u`` could have been a single mode.
# For instance, to get the circular wave with mode number `p=15`, simply set
# ``u = bp(15; k=k)``.
# Of course, any other function (defined in polar coordinates) can be defined
# by the user as target of the approximation problem.

# ## Reconstruction method

# The approximation will be reconstructed by sampling the target on the
# boundary of the unit disk.
# To do so, we need to know now the dimension `N` of the approximation sets
# that we are going to use:
N = 100;
# We can determine the number of sampling nodes necessary for a successful
# reconstruction, based on `N`, `P` (to avoid aliasing) and the oversampling
# ratio `η`:
S = number_of_boundary_sampling_nodes(N; η=2, P=P);
# The (equispaced) boundary nodes are then constructed as:
X = boundary_sampling_nodes(S);
# The right-hand-side of the linear system can then be readily constructed:
b = samples_from_nodes(u, X);

# ## Approximation with **propagative** plane waves

# We construct the approximation set of PPW:
Φppw = approximation_set(N; k=k);
# The matrix and its (SVD) factorization
Appw = samples_from_nodes(Φppw, X);
iAppw = RegularizedSVDPseudoInverse(Appw; ϵ=1e-14);
# The coefficients of the approximation are computed:
ξppw = solve_via_regularizedSVD(iAppw, b);
ũppw = (r,θ) -> sum([ξi * ϕi(r,θ) for (ξi, ϕi) in zip(ξppw, Φppw)]);
# Absolute error function
eppw = (r,θ) -> ũppw(r,θ) - u(r,θ); eppw(1,π/2)
# Let's compute the relative residual:
resppw = norm(Appw * ξppw - b) / norm(b)
# We did not obtained any accuracy whatsoever!
# The reason is that the coefficients are too large:
nrmppw = norm(ξppw) / norm(U)

# ## Approximation with **evanescent** plane waves

# We construct the approximation set of EPW:
Φepw = approximation_set(N, P, sobol_sampling; k=k);
# It is possible to choose other types of sampling methods, instead of
# `sobol_sampling`, for instance `uniform_sampling` and `random_sampling`.
# The matrix and its (SVD) factorization
Aepw = samples_from_nodes(Φepw, X);
iAepw = RegularizedSVDPseudoInverse(Aepw; ϵ=1e-14);
# The coefficients of the approximation are computed:
ξepw = solve_via_regularizedSVD(iAepw, b);
ũepw = (r,θ) -> sum([ξi * ϕi(r,θ) for (ξi, ϕi) in zip(ξepw, Φepw)]);
# Absolute error function
eepw = (r,θ) -> ũepw(r,θ) - u(r,θ); eepw(1,π/2)
# Let's compute the relative residual:
resepw = norm(Aepw * ξepw - b) / norm(b)
# We get almost 8 digits of accuracy!
# The size of the coefficients remains quite high:
nrmepw = norm(ξepw) / norm(U)

# ### Down to machine precision

# Let's double the number of EPW
N = 200
# The above approximation process can be obtained much more rapidly with the
# following convenience function
_, ξepw, ũepw, resepw, nrmepw, eepw = Dirichlet_sampling(k, U, N; smpl_type=sobol_sampling);
# One can check that we obtain now a relative residual very close to machine
# precision (13 digits of accuracy):
resepw
# The size of the coefficients is also greatly reduced:
nrmepw


_clamp(u, u_min, u_max) = max(min(u, u_max), u_min)
_dead_zone(u, u_min, u_max) = ifelse(u > u_max, u - u_max, ifelse(u < u_min, u - u_min, 0))

"""
Limit the range of a signal.

# Parameters:
- `y_max`: Maximum of output signal
- `y_min`: Minimum of output signal
"""
function Limiter(;name, y_max, y_min=y_max > 0 ? -y_max : -Inf)
    y_max ≥ y_min || throw(ArgumentError("`y_min` must be smaller than `y_max`"))
    @named siso = SISO()
    @unpack u, y = siso
    pars = @parameters y_max=y_max y_min=y_min
    eqs = [
        y ~ _clamp(u, y_min, y_max)
    ]
    extend(ODESystem(eqs, t, [], pars; name=name), siso)
end

"""
    DeadZone(; u_max, u_min=-u_max, name)

The DeadZone block defines a region of zero output.
If the input is within uMin ... uMax, the output is zero. Outside of this zone, the output is a linear function of the input with a slope of 1.
```
       y▲
        │     /
        │    /
  u_min │   /
─────|──┼──|───────► u
    /   │   u_max
   /    │
  /     │
```
"""
function DeadZone(; name, u_max, u_min=-u_max)
    if !ModelingToolkit.isvariable(u_max)
        u_max ≥ u_min || throw(ArgumentError("`u_min` must be smaller than `u_max`"))
    end
    @named siso = SISO()
    @unpack u, y = siso
    pars = @parameters u_max=u_max u_min=u_min
    eqs = [
        y ~ _dead_zone(u, u_min, u_max)
    ]
    extend(ODESystem(eqs, t, [], pars; name=name), siso)
end

"""
    SlewRateLimiter(;name, rising=1, falling=-rising, Td=0.001, y_start=0.0)

Limits the slew rate of a signal.

# Parameters:
- `Rising`: Maximum rising slew rate
- `falling`: Maximum falling slew rate
- `Td`: Derivative time constant
"""
function SlewRateLimiter(;name, rising=1, falling=-rising, Td=0.001, y_start=0.0)
    rising ≥ falling || throw(ArgumentError("`rising` must be smaller than `falling`"))
    Td > 0 || throw(ArgumentError("Time constant `Td` must be strictly positive"))
    @named siso = SISO(y_start=y_start)
    @unpack u, y = siso
    pars = @parameters rising=rising falling=falling
    eqs = [
        D(y) ~ max(min((u-y) / Td, rising), falling)
    ]
    extend(ODESystem(eqs, t, [], pars; name=name), siso)
end


Text provided under a Creative Commons Attribution license, CC-BY.  All code is made available under the FSF-approved BSD-3 license.  (c) Lorena A. Barba, Gilbert F. Forsyth 2017. Thanks to NSF for support via CAREER award #1149784.
[@LorenaABarba](https://twitter.com/LorenaABarba)

12 steps to Navier–Stokes
=====
***

For a moment, recall the Navier–Stokes equations for an incompressible fluid, where $\vec{v}$ represents the velocity field:

$$
\begin{eqnarray*}
\nabla \cdot\vec{v} &=& 0 \\
\frac{\partial \vec{v}}{\partial t}+(\vec{v}\cdot\nabla)\vec{v} &=& -\frac{1}{\rho}\nabla p + \nu \nabla^2\vec{v}
\end{eqnarray*}
$$

The first equation represents mass conservation at constant density. The second equation is the conservation of momentum. But a problem appears: the continuity equation for incompressble flow does not have a dominant variable and there is no obvious way to couple the velocity and the pressure. In the case of compressible flow, in contrast, mass continuity would provide an evolution equation for the density $\rho$, which is coupled with an equation of state relating $\rho$ and $p$.

In incompressible flow, the continuity equation $\nabla \cdot\vec{v}=0$ provides a *kinematic constraint* that requires the pressure field to evolve so that the rate of expansion $\nabla \cdot\vec{v}$ should vanish everywhere. A way out of this difficulty is to *construct* a pressure field that guarantees continuity is satisfied; such a relation can be obtained by taking the divergence of the momentum equation. In that process, a Poisson equation for the pressure shows up!

Step 10: 2D Poisson Equation
----
***

Poisson's equation is obtained from adding a source term to the right-hand-side of Laplace's equation:

$$\frac{\partial ^2 p}{\partial x^2} + \frac{\partial ^2 p}{\partial y^2} = b$$

So, unlinke the Laplace equation, there is some finite value inside the field that affects the solution. Poisson's equation acts to "relax" the initial sources in the field.

In discretized form, this looks almost the same as [Step 9](./12_Step_9.ipynb), except for the source term:

$$\frac{p_{i+1,j}^{n}-2p_{i,j}^{n}+p_{i-1,j}^{n}}{\Delta x^2}+\frac{p_{i,j+1}^{n}-2 p_{i,j}^{n}+p_{i,j-1}^{n}}{\Delta y^2}=b_{i,j}^{n}$$

As before, we rearrange this so that we obtain an equation for $p$ at point $i,j$. Thus, we obtain:

$$p_{i,j}^{n}=\frac{(p_{i+1,j}^{n}+p_{i-1,j}^{n})\Delta y^2+(p_{i,j+1}^{n}+p_{i,j-1}^{n})\Delta x^2-b_{i,j}^{n}\Delta x^2\Delta y^2}{2(\Delta x^2+\Delta y^2)}$$

We will solve this equation by assuming an initial state of $p=0$ everywhere, and applying boundary conditions as follows:

$p=0$ at $x=0, \ 2$ and $y=0, \ 1$

and the source term consists of two initial spikes inside the domain, as follows:

$b_{i,j}=100$ at $i=\frac{1}{4}nx, j=\frac{1}{4}ny$

$b_{i,j}=-100$ at $i=\frac{3}{4}nx, j=\frac{3}{4}ny$

$b_{i,j}=0$ everywhere else.

The iterations will advance in pseudo-time to relax the initial spikes. The relaxation under Poisson's equation gets slower and slower as they progress. *Why?*

Let's look at one possible way to write the code for Poisson's equation. As always, we load our favorite Python libraries. We also want to make some lovely plots in 3D. Let's get our parameters defined and the initialization out of the way. What do you notice of the approach below?

```python
import numpy
from matplotlib import pyplot, cm
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
```

```python
# Parameters
nx = 50
ny = 50
nt  = 100
xmin = 0
xmax = 2
ymin = 0
ymax = 1

dx = (xmax - xmin) / (nx - 1)
dy = (ymax - ymin) / (ny - 1)

# Initialization
p  = numpy.zeros((ny, nx))
pd = numpy.zeros((ny, nx))
b  = numpy.zeros((ny, nx))
x  = numpy.linspace(xmin, xmax, nx)
y  = numpy.linspace(xmin, xmax, ny)

# Source
b[int(ny / 4), int(nx / 4)]  = 100
b[int(3 * ny / 4), int(3 * nx / 4)] = -100
```

With that, we are ready to advance the initial guess in pseudo-time. How is the code below different from the function used in [Step 9](./12_Step_9.ipynb) to solve Laplace's equation?

```python
for it in range(nt):

    pd = p.copy()

    p[1:-1,1:-1] = (((pd[1:-1, 2:] + pd[1:-1, :-2]) * dy**2 +
                    (pd[2:, 1:-1] + pd[:-2, 1:-1]) * dx**2 -
                    b[1:-1, 1:-1] * dx**2 * dy**2) /
                    (2 * (dx**2 + dy**2)))

    p[0, :] = 0
    p[ny-1, :] = 0
    p[:, 0] = 0
    p[:, nx-1] = 0
```

Maybe we could reuse our plotting function from [Step 9](./12_Step_9.ipynb), don't you think?

```python
def plot2D(x, y, p):
    fig = pyplot.figure(figsize=(11, 7), dpi=100)
    ax = fig.gca(projection='3d')
    X, Y = numpy.meshgrid(x, y)
    surf = ax.plot_surface(X, Y, p[:], rstride=1, cstride=1, cmap=cm.viridis,
            linewidth=0, antialiased=False)
    ax.view_init(30, 225)
    ax.set_xlabel('$x$')
    ax.set_ylabel('$y$')

```

```python
plot2D(x, y, p)
```

![png](output_14_0.png)

Ah! The wonders of code reuse! Now, you probably think: "Well, if I've written this neat little function that does something so useful, I want to use it over and over again. How can I do this without copying and pasting it each time? —If you are very curious about this, you'll have to learn about *packaging*. But this goes beyond the scope of our CFD lessons. You'll just have to Google it if you really want to know.

***

## Learn More

To learn more about the role of the Poisson equation in CFD, watch **Video Lesson 11** on You Tube:

```python
from IPython.display import YouTubeVideo
YouTubeVideo('ZjfxA3qq2Lg')
```

<iframe
    width="400"
    height="300"
    src="https://www.youtube.com/embed/ZjfxA3qq2Lg"
    frameborder="0"
    allowfullscreen
></iframe>

```python
from IPython.core.display import HTML
def css_styling():
    styles = open("../styles/custom.css", "r").read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>
<style>
    @font-face {
        font-family: "Computer Modern";
        src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
    }
    div.cell{
        width:800px;
        margin-left:16% !important;
        margin-right:auto;
    }
    h1 {
        font-family: 'Alegreya Sans', sans-serif;
    }
    h2 {
        font-family: 'Fenix', serif;
    }
    h3{
		font-family: 'Fenix', serif;
        margin-top:12px;
        margin-bottom: 3px;
       }
	h4{
		font-family: 'Fenix', serif;
       }
    h5 {
        font-family: 'Alegreya Sans', sans-serif;
    }
    div.text_cell_render{
        font-family: 'Alegreya Sans',Computer Modern, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
        line-height: 135%;
        font-size: 120%;
        width:600px;
        margin-left:auto;
        margin-right:auto;
    }
    .CodeMirror{
            font-family: "Source Code Pro";
			font-size: 90%;
    }
/*    .prompt{
        display: None;
    }*/
    .text_cell_render h1 {
        font-weight: 200;
        font-size: 50pt;
		line-height: 100%;
        color:#CD2305;
        margin-bottom: 0.5em;
        margin-top: 0.5em;
        display: block;
    }
    .text_cell_render h5 {
        font-weight: 300;
        font-size: 16pt;
        color: #CD2305;
        font-style: italic;
        margin-bottom: .5em;
        margin-top: 0.5em;
        display: block;
    }

    .warning{
        color: rgb( 240, 20, 20 )
        }
</style>
<script>
    MathJax.Hub.Config({
                        TeX: {
                           extensions: ["AMSmath.js"]
                           },
                tex2jax: {
                    inlineMath: [ ['$','$'], ["\\(","\\)"] ],
                    displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
                },
                displayAlign: 'center', // Change this to 'center' to center equations.
                "HTML-CSS": {
                    styles: {'.MathJax_Display': {"margin": 4}}
                }
        });
</script>

> (The cell above executes the style for this notebook.)


#-----------------------------#
# see page 136 in Sarkka book #
#-----------------------------#

function smoothingrecursion(y; A = A, H = H, Q = Q, R = R, m = m, P = P)

    N = length(y); @assert(N == length(m) == length(P))

    # Store here state distribution

    μstate, Σstate = Array{Vector{Float64}}(undef, N), Array{Matrix{Float64}}(undef, N)

    # Start recursion with prior mean and prior covariance

    mˢₖ₊₁, Pˢₖ₊₁ = m[end], P[end]

    μstate[N], Σstate[N] = mˢₖ₊₁, Pˢₖ₊₁

    for k in N-1:-1:1

        m⁻ₖ₊₁ = A * m[k]

        P⁻ₖ₊₁ = A * P[k] * A' + Q

        Gₖ = (P[k] * A') / P⁻ₖ₊₁

        mˢₖ = m[k] + Gₖ * (mˢₖ₊₁ - m⁻ₖ₊₁)

        Pˢₖ = P[k] + Gₖ * (Pˢₖ₊₁ - P⁻ₖ₊₁) * (Gₖ)'

        # store mean and covariance of Gaussian distribution N(xₖ|mˢₖ, Pˢₖ)
        Pˢₖ = (Pˢₖ + (Pˢₖ)') / 2

        μstate[k], Σstate[k] = mˢₖ, Pˢₖ

        # previous state

        mˢₖ₊₁, Pˢₖ₊₁ = mˢₖ, Pˢₖ

    end

    return μstate, Σstate

end

function testsmoothing(seed=1)

    yclean, y, x, A, H, Q, R, m₀, P₀ = simulatedata(seed=seed)

    μfilter, Σfilter = filteringrecursion(y; A = A, H = H, Q = Q, R = R, m₀ = m₀, P₀ = P₀)

    μsmooth, Σsmooth = smoothingrecursion(y; A = A, H = H, Q = Q, R = R, m = μfilter, P = Σfilter)

    figure()
    subplot(211)
    plot([yᵢ[1]        for yᵢ in yclean], label = "simulated 1")
    plot([(H*xᵢ)[1]    for xᵢ in x], label = "simulated 1")
    legend()

    subplot(212)
    plot([xᵢ[1]    for xᵢ in x], label = "simulated 1")
    plot([xᵢ[1]    for xᵢ in μfilter], label = "filtered 1")
    plot([xᵢ[1]    for xᵢ in μsmooth], "--", label = "smoothed 1")
    legend()

    acc = accfilter = accsmooth = 0.0

    for i in 1:length(y)

        acc += sum((yclean[i] - H*x[i]).^2) / length(y)

        accfilter += sum((yclean[i] - H*μfilter[i]).^2)/ length(y)

        accsmooth += sum((yclean[i] - H*μsmooth[i]).^2)/ length(y)

    end

    @show acc, accfilter, accsmooth

    accx = accfilterx = accsmoothx = 0.0

    for i in 1:length(y)

        accx += sum((x[i] - x[i]).^2)

        accfilterx += sum((x[i] - μfilter[i]).^2)

        accsmoothx += sum((x[i] - μsmooth[i]).^2)

    end

    @show accx, accfilterx, accsmoothx

    x, μfilter, μsmooth

end


"""
    twoD(X)

Project `X` to two dmensions using PCA
"""
function twoD(X)
    X = X .- mean(X,dims=1)
    X = X ./ std(X,dims=1)
    s = svd(X)
    Y = s.U[:,1:2]#.*s.S[1:2]'
    Y[:,1], Y[:,2]
end

"""
    threeD(X)

Project `X` to three dimensions using PCA
"""
function threeD(X)
    X = X .- mean(X,dims=1)
    X = X ./ std(X,dims=1)
    s = svd(X)
    Y = s.U[:,1:3]#.*s.S[1:2]'
    Y[:,1], Y[:,2], Y[:,3]
end

"""
    s1(x, dims=:)

normalize x sums to 1
"""
function s1(x, dims=:)
    if isderiving()
        m = minimum(real(x), dims=dims)
        if any(<(0), m)
            x = x .- m
        end
        return x ./ sum(x, dims=dims)
    else
        m = minimum(x, dims=dims)
        x = float.(x)
        if any(<(0), m)
            x .-= m
        end
        x ./= sum(x, dims=dims)
    end
end

"""
    n1(x)

normalize x norm 1
"""
n1(x) = x./norm(x)
n1(x::AbstractMatrix, dims) = mapslices(n1, x, dims=dims)
"""
    v1(x, dims=:)

normalize x var 1
"""
function v1(x, dims=:)
    x = x .- mean(x, dims=dims)
    x .= x./std(x, dims=dims)
end

function v1!(x, dims=:)
    x .-= mean(x, dims=dims)
    x .= x./std(x, dims=dims)
end

"""
    median1(x, dims=:)

normalize x median 0, median absolute deviation = 1
"""
function m1(x, dims=:)
    x = x .- median(x, dims=dims)
    # x .= x./mapslices(mad, x, dims=dims)
end
function m1(x::AbstractVector)
    x = x .- median(x)
    x .= x./mad(x, normalize=true)
end

"""
    bp_filter(x, passband)

Band-pass filter, passband is tuple, `fs` assumed = 1
"""
function bp_filter(x, passband)
    responsetype = Bandpass(passband..., fs=1)
    designmethod = Butterworth(2)
    filt(digitalfilter(responsetype, designmethod), x)
end

"""
    lp_filter(x, cutoff)

Low-pass filter, `fs` assumed = 1
"""
function lp_filter(x, cutoff)
    responsetype = Lowpass(cutoff, fs=1)
    designmethod = Butterworth(2)
    filt(digitalfilter(responsetype, designmethod), x)
end


"""
## Convex hull

Calculation f convex hull of a sequence of points represented through an Float64
Array of N rows and 2 columns
"""
function convexhull{T <: Real}(points::Array{T,2})
    n = size(points,1)
    # Sort the points by x-coordinate, resulting in a sequence p 1 , . . . , p n
    p = sortrows(points)
    # Put the points p_1 and p_2 in a list L_upper , with p_1 as the first point
    upper = [p[1,:], p[2,:]]

    for i in 3:n
        while size(upper,1) >= 2 && direction(upper[end-1], upper[end], p[i,:]) <= 0
            pop!(upper)
        end

        push!(upper,p[i,:])
    end

    lower = [p[end,:], p[end-1,:]]

    for i in n-2:-1:1
        while size(lower,1) >= 2 && direction(lower[end-1], lower[end], p[i,:]) <= 0
            pop!(lower)
        end

        push!(lower,p[i,:])
    end

    append!(upper, lower[2:end-1])
end

function convexhull{T <: Real}(points::Array{Point2D{T}, 1})
    n = size(points,1)
    # Sort the points by x-coordinate, resulting in a sequence p 1 , . . . , p n
    p = sort(points)
    # Put the points p_1 and p_2 in a list L_upper , with p_1 as the first point
    upper = [p[1], p[2]]

    for i in 3:n
        while size(upper,1) >= 2 && direction(upper[end-1], upper[end], p[i]) <= 0
            pop!(upper)
        end

        push!(upper,p[i])
    end

    lower = [p[end], p[end-1]]

    for i in n-2:-1:1
        while size(lower,1) >= 2 && direction(lower[end-1], lower[end], p[i]) <= 0
            pop!(lower)
        end

        push!(lower,p[i])
    end

    append!(upper, lower[2:end-1])
end