# Continuous Lyapunov equations
"""
    X = lyapc(A, C; blocksize = 64)

Compute `X`, the solution of the continuous Lyapunov equation

      AX + XA' + C = 0,

where `A` is a square real or complex matrix and `C` is a square matrix.
`A` must not have eigenvalues `α` and `β` such that `α+β' = 0`.
The solution `X` is symmetric or hermitian if `C` is symmetric or hermitian.

The keyword argument `blocksize` (default: `blocksize = 64`) specifies the 
block size when to switch in the employed recursive blocked algorithm 
to the core algorithm for solving small-sized matrix equations. 
If `blocksize ≤ 4`, the minimum value `blocksize = 4` is used.
Note: This option is only effective for data of types `Float64`, `Float32`, 
      `ComplexF64`, or `ComplexF32`.

The following particular cases are also adressed:

    X = lyapc(α*I,C)  or  X = lyapc(α,C)

Solve the matrix equation `(α+α')X + C = 0`.

    x = lyapc(α,γ)

Solve the equation `(α+α')x + γ = 0`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> C = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> X = lyapc(A, C)
2×2 Array{Float64,2}:
  0.5  -0.5
 -0.5   0.25

julia> A*X + X*A' + C
2×2 Array{Float64,2}:
 -8.88178e-16   2.22045e-16
  2.22045e-16  -4.44089e-16
```
"""
function lyapc(A::AbstractMatrix, C::AbstractMatrix; blocksize::Int = 64)
   """
   The Bartels-Steward Schur form based method in [1] is employed for matrix dimensions not exceeding
   the specified blocksize. For large dimensions, the recursive blocked algorithm in [2] is used.
   The Sylvester equations during the recursive solution are solved using a native algorithm 
   inspired by [2] for symmetric solutions and the iterative blocking based algorithm of [3] for 
   non-symmetric solutions. Blocking is employed only for BlasFloat type data.  

   Reference:
   [1] R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
       Comm. ACM, 15:820–826, 1972.

   [2] I. Jonsson and B. Kågström, Recursive blocked algorithms for solving triangular systems—
       Part I: One-sided and coupled Sylvester-type matrix equations, ACM Trans. Math. Software, 
       28 (2002), pp. 392–415.

   [3] A. Schwarz and C. C. Kjelgaard Mikkelsen, Robust task-parallel solution of the triangular 
       Sylvester equation. Lecture Notes in Computer Science, vol 12043, pp. 82-92, Springer, 2020.    
   """
   n = LinearAlgebra.checksquare(A)
   LinearAlgebra.checksquare(C) == n ||
      throw(DimensionMismatch("C must be a square matrix of dimension $n"))

   adj = isa(A,Adjoint)
   her = ishermitian(C)

   T2 = promote_type(eltype(A), eltype(C))
   T2 <: BlasFloat  || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (adj ? A = convert(AbstractMatrix{T2},A.parent)' : A = convert(AbstractMatrix{T2},A))
   eltype(C) == T2 || (C = convert(AbstractMatrix{T2},C))

   if isdiag(A) 
      if her
         return lyapcs!(A, copy(C))
      else
         return sylvcs!(A, A, copy(C))
      end
   end

   if ishermitian(A)
      # Reduce A to diagonal form and transform C
      AS, Q = schur(Hermitian(A))
   else
      # Reduce A to Schur form and transform C
      if adj
         AS, Q = schur(A.parent)
      else
         AS, Q = schur(A)
      end
   end
   #X = Q'*C*Q
   if her
      X = utqu(C,Q)
      if T2 <: BlasFloat
         lyapcs_blocked!(AS, X; adj, blocksize)
      else
         lyapcs!(AS, X; adj)
      end
      #X <- Q*X*Q'
      utqu!(X,Q',AS)
      return X
   else
      X = Q' * C * Q
      if T2 <: BlasFloat
         _lyapsylvcs_blocked!(AS, AS, X, adj, blocksize)
         return Q * X * Q'
      else
         sylvcs!(AS, AS, X, adjA = adj, adjB = !adj)
         return rmul!(Q * X * Q',-1)
      end
   end
end
# (α+α')X  + C = 0
lyapc(A::UniformScaling, C::AbstractMatrix) = -C/(A+A')
lyapc(A::Union{Real,Complex}, C::AbstractMatrix) = real(A) == 0 ? throw("ME:SingularException: A has an eigenvalue with zero real part") : -C/(A+A')
# (α+α')x + γ = 0
lyapc(A::Union{Real,Complex}, C::Union{Real,Complex})  = real(A) == 0 ? throw("ME:SingularException: A has an eigenvalue with zero real part") : -C/(A+A')
"""
    X = lyapc(A, E, C; blocksize = 64)

Compute `X`, the solution of the generalized continuous Lyapunov equation

     AXE' + EXA' + C = 0,

where `A` and `E` are square real or complex matrices and `C` is a square matrix.
The pencil `A-λE` must not have eigenvalues `α` and `β` such that `α+β = 0`.
The solution `X` is symmetric or hermitian if `C` is a symmetric or hermitian.

The keyword argument `blocksize` (default: `blocksize = 64`) specifies the 
block size when to switch in the employed recursive blocked algorithm 
to the core algorithm for solving small-sized matrix equations. 
If `blocksize ≤ 4`, the minimum value `blocksize = 4` is used.
Note: This option is only effective for data of types `Float64`, `Float32`, 
      `ComplexF64`, or `ComplexF32`.

The following particular cases are also adressed:

    X = lyapc(A,β*I,C)  or  X = lyapc(A,β,C)

Solve the matrix equation `AXβ' + βXA' + C = 0`.

    X = lyapc(α*I,E,C)  or  X = lyapc(α,E,C)

Solve the matrix equation `αXE' + EXα' + C = 0`.

    X = lyapc(α*I,β*I,C)  or  X = lyapc(α,β,C)

Solve the matrix equation `(αβ'+α'β)X + C = 0`.

    x = lyapc(α,β,γ)

Solve the equation `(αβ'+α'β)x + γ = 0`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> E = [ 1. 2.; 0. 1.]
2×2 Array{Float64,2}:
 1.0  2.0
 0.0  1.0

julia> C = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> X = lyapc(A, E, C)
2×2 Array{Float64,2}:
 -2.5   2.5
  2.5  -2.25

julia> A*X*E' + E*X*A' + C
2×2 Array{Float64,2}:
 -5.32907e-15  -2.66454e-15
 -4.44089e-15   0.0
```
"""
function lyapc(A::AbstractMatrix, E::AbstractMatrix, C::AbstractMatrix; blocksize::Int = 64)
   """
   The extension [1] of the Bartels-Steward method based on the generalized Schur form
   is employed for matrix dimensions not exceeding the specified blocksize. For large dimensions, 
   the recursive blocked algorithm in [2] is used. The generalized Sylvester equations 
   during the recursive solution are solved using the recursive blocking technique described in [2].
   Blocking is employed only for BlasFloat type data.  

   Reference:
   [1] T. Penzl. Numerical solution of generalized Lyapunov equations.
       Adv. Comput. Math., 8:33–48, 1998.

   [2] I. Jonsson and B. Kågström, Recursive blocked algorithms for solving triangular systems—
       Part I: One-sided and coupled Sylvester-type matrix equations, ACM Trans. Math. Software, 
       28 (2002), pp. 392–415.

   """
   T2 = promote_type(eltype(A), eltype(E), eltype(C))
   T2 <: BlasFloat  || (T2 = promote_type(Float64,T2))
   
   n = LinearAlgebra.checksquare(A)
   LinearAlgebra.checksquare(C) == n ||
      throw(DimensionMismatch("C must be a square matrix of dimension $n"))
   isequal(E,I) && size(E,1) == n && (return lyapc(A, C))
   LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a square matrix of dimension $n"))

   adjA = isa(A,Adjoint)
   adjE = isa(E,Adjoint)
   if adjA && !adjE
      A = copy(A)
      adjA = false
   elseif !adjA && adjE
      E = copy(E)
      adjE = false
   end

   adj = adjA & adjE
   her = ishermitian(C)

   eltype(A) == T2 || (adj ? A = convert(Matrix{T2},A.parent)' : A = convert(Matrix{T2},A))
   eltype(E) == T2 || (adj ? E = convert(Matrix{T2},E.parent)' : E = convert(Matrix{T2},E))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))

   # Reduce (A,E) to generalized Schur form and transform C
   # (as,es) = (q'*A*z, q'*E*z)
   if adj
      as, es, z, q = schur(A.parent,E.parent)
   else
      as, es, q, z = schur(A,E)
   end
   if her
      #x = q'*C*q
      x = utqu(C,q) 
      if T2 <: BlasFloat
         lyapcs_blocked!(as,es,x; adj, blocksize)
      else
         lyapcs!(as,es,x, adj = adj)
      end
      #x = z*x*z'
      utqu!(x,z',as)
      return x
   else
      if T2 <: BlasFloat
         WS = Matrix{T2}(undef,n,n)
         x = Matrix{T2}(undef,n,n)
         mul!(WS,q',C)
         mul!(x,WS,q) 
         lyapgsylvcs_blocked!(WS, as, es, es, as, x; adj, blocksize)
         mul!(WS,z,x)
         mul!(x,WS,z')
         return x
      else
         x = q' * C * q
         gsylvs!(as,es,es,as,x,adjAC = adj,adjBD=!adj,DBSchur = true)
         return rmul!(z*x*z', -1)
      end
   end
end
# Aβ'X + XA'β + C = 0
lyapc(A::AbstractMatrix, E::Union{Real,Complex,UniformScaling}, C::AbstractMatrix) =
      isa(A,Adjoint) ? lyapc((A.parent*E')',C) : lyapc(A*E',C)
# αXE' + EXα' + C = 0
lyapc(A::Union{Real,Complex,UniformScaling}, E::AbstractMatrix, C::AbstractMatrix) =
      isa(E,Adjoint) ? lyapc((E.parent*A')',C) : lyapc(A'*E,C)
# (αβ'+α'β)X + C = 0
lyapc(A::UniformScaling, E::UniformScaling, C::AbstractMatrix) = -(A*E'+A'*E)\C
lyapc(A::Union{Real,Complex}, E::Union{Real,Complex}, C::AbstractMatrix) = real(A*E') == 0 ? throw(SingularException(1)) : -C/(A*E'+A'*E)
# (αβ'+α'β)X  + γ = 0
lyapc(A::Union{Real,Complex}, E::Union{Real,Complex}, C::Union{Real,Complex}) = real(A*E') == 0 ? throw(SingularException(1)) : -C/(A*E'+A'*E)
# Discrete Lyapunov equations
"""
    X = lyapd(A, C; blocksize::Int = 64)

Compute `X`, the solution of the discrete Lyapunov equation

       AXA' - X + C = 0,

where `A` is a square real or complex matrix and `C` is a square matrix.
`A` must not have eigenvalues `α` and `β` such that `αβ = 1`.
The solution `X` is symmetric or hermitian if `C` is a symmetric or hermitian.

The keyword argument `blocksize` (default: `blocksize = 64`) specifies the 
block size when to switch in the employed recursive blocked algorithm 
to the standard algorithm for solving small-sized matrix equations. 
If `blocksize < 4`, the minimum value `blocksize = 4` is used.
Note: This option is only effective for data of types `Float64`, `Float32`, 
      `ComplexF64`, or `ComplexF32`.
   

The following particular cases are also adressed:

    X = lyapd(α*I,C)  or  X = lyapd(α,C)

Solve the matrix equation `(αα'-1)X + C = 0`.

    x = lyapd(α,γ)

Solve the equation `(αα'-1)x + γ = 0`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> C = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> X = lyapd(A, C)
2×2 Array{Float64,2}:
  0.2375  -0.2125
 -0.2125   0.1375

julia> A*X*A' - X + C
2×2 Array{Float64,2}:
 5.55112e-16  6.66134e-16
 2.22045e-16  4.44089e-16
```
"""
function lyapd(A::AbstractMatrix, C::AbstractMatrix; blocksize::Int = 64)
   """
   The discrete analog of the Bartels-Steward Schur form based method [1] is employed 
   for matrix dimensions not exceeding the specified blocksize. For large dimensions, 
   the recursive blocked algorithm in [2] is used.

   Reference:
   [1] G. Kitagawa. An Algorithm for solving the matrix equation X = F X F' + S,
       International Journal of Control, 25:745-753, 1977.

   [2] I. Jonsson and B. Kågström, Recursive blocked algorithms for solving triangular systems — 
       Part II: Two-sided and generalized Sylvester and Lyapunov matrix equations, 
       ACM Trans. Math. Software, 28 (2002), pp. 416–435.
   """
   n = LinearAlgebra.checksquare(A)
   LinearAlgebra.checksquare(C) == n ||
       throw(DimensionMismatch("C must be a square matrix of dimension $n"))

   adj = isa(A,Adjoint)
   her = ishermitian(C)

   T2 = promote_type(eltype(A), eltype(C))
   T2 <: BlasFloat  || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (adj ? A = convert(AbstractMatrix{T2},A.parent)' : A = convert(AbstractMatrix{T2},A))
   eltype(C) == T2 || (C = convert(AbstractMatrix{T2},C))

   if isdiag(A) 
      if her
         return lyapds!(A, copy(C))
      else
         return sylvds!(-A, A, copy(C))
      end
   end


   if ishermitian(A)
      # Reduce A to diagonal form and transform C
      AS, Q = schur(Hermitian(A))
   else
      # Reduce A to Schur form and transform C
      if adj
         AS, Q = schur(A.parent)
      else
         AS, Q = schur(A)
      end
   end
   #X = Q'*C*Q
   if her
      X = utqu(C,Q)
      if T2 <: BlasFloat
         lyapds_blocked!(AS, X; adj, blocksize)
      else
         lyapds!(AS, X; adj)
      end
      #X <- Q*X*Q'
      utqu!(X,Q',AS)
      return X
   else
      if T2 <: BlasFloat
         WS = Matrix{T2}(undef,n,n)
         X = Matrix{T2}(undef,n,n)
         mul!(WS,Q',C)
         mul!(X,WS,Q) 
         lyapsylvds_blocked!(WS, AS, AS, X; adj, isgn = -1, blocksize)
         mul!(WS,Q,X)
         mul!(X,WS,Q')
         return X
      else
         X = Q' * C * Q
         sylvds!(AS, AS, X, adjA = adj, adjB = !adj, isgn = -1)
         return rmul!(Q * X * Q',-1)
      end
   end
end
# (αα'-1)X + C = 0
lyapd(A::UniformScaling, C::AbstractMatrix) = (I-A'*A)\C
lyapd(A::Union{Real,Complex}, C::AbstractMatrix) = A*A' == 1 ? throw(SingularException(1)) : C/(1-A'*A)
# (αα'-1)x + γ = 0
lyapd(A::Union{Real,Complex}, C::Union{Real,Complex}) = A*A' == 1 ? throw(SingularException(1)) : C/(one(C)-A'*A)
"""
    X = lyapd(A, E, C)

Compute `X`, the solution of the generalized discrete Lyapunov equation

         AXA' - EXE' + C = 0,

where `A` and `E` are square real or complex matrices and `C` is a square matrix.
The pencil `A-λE` must not have eigenvalues `α` and `β` such that `αβ = 1`.
The solution `X` is symmetric or hermitian if `C` is a symmetric or hermitian.

The following particular cases are also adressed:

    X = lyapd(A,β*I,C)  or  X = lyapd(A,β,C)

Solve the matrix equation `AXA' - βXβ' + C = 0`.

    X = lyapd(α*I,E,C)  or  X = lyapd(α,E,C)

Solve the matrix equation `αXα' - EXE' + C = 0`.

    X = lyapd(α*I,β*I,C)  or  X = lyapd(α,β,C)

Solve the matrix equation `(αα'-ββ')X + C = 0`.

    x = lyapd(α,β,γ)

Solve the equation `(αα'-ββ')x + γ = 0`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> E = [ 1. 2.; 0. -1.]
2×2 Array{Float64,2}:
 1.0   2.0
 0.0  -1.0

julia> C = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> X = lyapd(A, E, C)
2×2 Array{Float64,2}:
  1.775  -1.225
 -1.225   0.775

julia> A*X*A' - E*X*E' + C
2×2 Array{Float64,2}:
 -2.22045e-16  -4.44089e-16
 -1.33227e-15   1.11022e-15
```
"""
function lyapd(A::AbstractMatrix, E::AbstractMatrix, C::AbstractMatrix; blocksize::Int = 64)
   """
   The extension of the Bartels-Stewart method based on the generalized Schur form
   is employed. For large dimensions, the recursive blocked algorithm in [2] is used.
   The generalized Sylvester equations during the recursive solution are solved using a native algorithm 
   described in [2]. Blocking is employed only for BlasFloat type data.  

   Reference:
   [1] T. Penzl. Numerical solution of generalized Lyapunov equations.
       Adv. Comput. Math., 8:33–48, 1998.

   [2] I. Jonsson and B. Kågström, Recursive blocked algorithms for solving triangular systems—
       Part I: One-sided and coupled Sylvester-type matrix equations, ACM Trans. Math. Software, 
       28 (2002), pp. 392–415.

   """
   T2 = promote_type(eltype(A), eltype(E), eltype(C))
   T2 <: BlasFloat  || (T2 = promote_type(Float64,T2))
   n = LinearAlgebra.checksquare(A)
   LinearAlgebra.checksquare(C) == n ||
      throw(DimensionMismatch("C must be a square matrix of dimension $n"))
   isequal(E,I) && size(E,1) == n && (return lyapd(A, C))
   LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a square matrix of dimension $n"))

   adjA = isa(A,Adjoint)
   adjE = isa(E,Adjoint)
   if adjA && !adjE
      A = copy(A)
      adjA = false
   elseif !adjA && adjE
      E = copy(E)
      adjE = false
   end

   adj = adjA & adjE
   her = ishermitian(C)

   eltype(A) == T2 || (adj ? A = convert(Matrix{T2},A.parent)' : A = convert(Matrix{T2},A))
   eltype(E) == T2 || (adj ? E = convert(Matrix{T2},E.parent)' : E = convert(Matrix{T2},E))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))

   # Reduce (A,E) to generalized Schur form and transform C
   # (as,es) = (q'*A*z, q'*E*z)
   if adj
      as, es, z, q = schur(A.parent,E.parent)
   else
      as, es, q, z = schur(A,E)
   end
   if her
      #x = q'*C*q
      x = utqu(C,q)
      if T2 <: BlasFloat
         lyapds_blocked!(as,es,x; adj, blocksize)
      else
         lyapds!(as,es,x, adj = adj)
      end
      #x = z*x*z'
      utqu!(x,z',as)
      return x
   else
      if T2 <: BlasFloat
         WS = Matrix{T2}(undef,n,n)
         x = Matrix{T2}(undef,n,n)
         WS2 = Matrix{T2}(undef,n,4)
         mul!(WS,q',C)
         mul!(x,WS,q) 
         lyapgsylvds_blocked!(WS, WS2, as, as, es, es, x; adj, blocksize)
         mul!(WS,z,x)
         mul!(x,WS,z')
         return x
      else
         x = q' * C * q
         gsylvs!(as, as, es, es, x, adjAC = adj, adjBD = !adj, isgn = -1)
         return rmul!(z*x*z', -1)
      end
   end
end
# AXA' - Xββ' + C = 0
lyapd(A::AbstractMatrix, E::Union{Real,Complex,UniformScaling}, C::AbstractMatrix) =
      isa(A,Adjoint) ? lyapd((A.parent/E)',C/(E*E')) : lyapd(A/E,C/(E*E'))
# αXα' - EXE' + C = 0
lyapd(A::Union{Real,Complex,UniformScaling}, E::AbstractMatrix, C::AbstractMatrix) =
      isa(E,Adjoint) ? lyapd((E.parent/A)',C/(-A*A')) : lyapd(E/A,C/(-A*A'))
# (α'α-β'β)X + C = 0
lyapd(A::UniformScaling, E::UniformScaling, C::AbstractMatrix) = C/(E'*E-A'*A)
lyapd(A::Union{Real,Complex}, E::Union{Real,Complex}, C::AbstractMatrix) = A*A'== E*E' ? throw(SingularException(1)) : C/(E'*E-A'*A)
# (α'α-β'β)X + γ  = 0
lyapd(A::Union{Real,Complex}, E::Union{Real,Complex}, C::Union{Real,Complex}) = A*A'== E*E' ? throw(SingularException(1)) : C/(E'*E-A'*A)
"""
    lyapcs!(A,C;adj = false)

Solve the continuous Lyapunov matrix equation

                op(A)X + Xop(A)' + C = 0,

where `op(A) = A` if `adj = false` and `op(A) = A'` if `adj = true`.
`A` is a square real matrix in a real Schur form, or a square complex matrix in a
complex Schur form and `C` is a symmetric or hermitian matrix.
`A` must not have eigenvalues `α` and `β` such that `α+β = 0`.
`C` contains on output the solution `X`.
"""
function lyapcs!(A::AbstractMatrix{T1},C::AbstractMatrix{T1}; adj = false) where {T1<:Real}
   n = LinearAlgebra.checksquare(A)
   # (LinearAlgebra.checksquare(C) == n && issymmetric(C)) ||
   #    throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
   if isdiag(A) 
      for i = 1:n
         C[i,i] = -C[i,i]/(2*A[i,i])
         isfinite(C[i,i]) || throw("ME:SingularException: A has eigenvalue(s) = 0")
         for j = i+1:n
            C[i,j] = -C[i,j]/(A[i,i]+A[j,j])
            isfinite(C[i,j]) || throw("ME:SingularException: A has eigenvalue(s) α and β such that α+β = 0")
            C[j,i] = C[i,j]
         end
      end
      return C[:,:]
   end      

   ONE = one(T1)

   # determine the structure of the real Schur form
   ba, p = sfstruct(A)

   Xw = Matrix{T1}(undef,4,4)
   Yw = Vector{T1}(undef,4)
   if adj
      # """
      # The (K,L)th block of X is determined starting from
      # upper-left corner column by column by

      # A(K,K)'*X(K,L) + X(K,L)*A(L,L) = -C(K,L) - R(K,L),

      # where
      #          K-1                    L-1
      # R(K,L) = SUM [A(I,K)'*X(I,L)] + SUM [X(K,J)*A(J,L)].
      #          I=1                    J=1
      # """
      j = 1
      for ll = 1:p
          dl = ba[ll]
          l = j:j+dl-1
          i = j
          for kk = ll:p
              dk = ba[kk]
              k = i:i+dk-1
              y = view(C,k,l)
              if kk > ll
                 ia = j:i-1
                 # y += A[ia,k]'*C[ia,l]
                 mul!(y,transpose(view(A,ia,k)),view(C,ia,l),ONE,ONE)
               end
               if i == j
                  lyapc2!(adj,y,dk,view(A,k,k),Xw,Yw)
               else
                  lyapcsylv2!(adj,y,dk,dl,view(A,k,k),view(A,l,l),Xw,Yw)
                  transpose!(view(C,l,k),y)
               end
               i += dk
          end
          j += dl
          if j <= n
             for jr = j:n
                for ir = jr:n
                    for lll = l
                        C[ir,jr] += C[ir,lll]*A[lll,jr] + A[lll,ir]*C[jr,lll]
                    end
                    C[jr,ir] = C[ir,jr]
                end
             end
          end
       end
   else
      # """
      # The (K,L)th block of X is determined starting from
      # bottom-right corner column by column by

      # A(K,K)*X(K,L) + X(K,L)*A(L,L)' = -C(K,L) - R(K,L),

      # where
      #           N                     N
      # R(K,L) = SUM [A(K,I)*X(I,L)] + SUM [X(K,J)*A(L,J)'].
      #         I=K+1                 J=L+1
      # """
      j = n
      for ll = p:-1:1
          dl = ba[ll]
          l = j-dl+1:j
          i = j
          for kk = ll:-1:1
              dk = ba[kk]
              i1 = i-dk+1
              k = i1:i
              y = view(C,k,l)
              if kk < ll
                 ia = i+1:j
                 #  y += A[k,ia]*C[ia,l]
                 mul!(y,view(A,k,ia),view(C,ia,l),ONE,ONE)
              end
              if i == j
                 lyapc2!(adj,y,dk,view(A,k,k),Xw,Yw)
              else
                 lyapcsylv2!(adj,y,dk,dl,view(A,k,k),view(A,l,l),Xw,Yw)
                 transpose!(view(C,l,k),y)
              end
              i -= dk
           end
           j -= dl
           if j >= 0
              for jr = 1:j
                 for ir = 1:jr
                     for lll = l
                         C[ir,jr] += C[ir,lll]*A[jr,lll] + A[ir,lll]*C[lll,jr]
                      end
                      C[jr,ir] = C[ir,jr]
                 end
              end
           end
       end
   end
end
function lyapcs_blocked!(A::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adj = false, blocksize::Integer = 64) where {T1<:BlasFloat}
    n = LinearAlgebra.checksquare(A)  
    n == LinearAlgebra.checksquare(C) 
    if T1 <: Real  
       # Check symmetry ONCE here
       issymmetric(C) ||  throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
    else
       ishermitian(C) ||  throw(DimensionMismatch("C must be a $n x $n Hermitian matrix"))
    end
   _lyapcs_blocked!(A, C, adj, blocksize)
    # Fill the lower triangle ONCE at the very end
    return LinearAlgebra.copytri!(C, 'U',T1 <: Complex)
end
function _lyapcs_blocked!(A::AbstractMatrix{T1},C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasReal}
   n = size(A, 1)
   ONE = one(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U')
      lyapcs!(A,C;adj)
   else
      # split A and C (by rows and columns)
      mid = n ÷ 2
      n1 =  A[mid+1, mid] != 0 ? mid + 1 : mid
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
      end
      if adj
         _lyapcs_blocked!(A11,C11,adj,blocksize)
         mul!(C12,Symmetric(C11),A12,1,1)
         #sylvcs!(A11, A22, C12, adjA = true)
         #rmul!(C12,-1)
         _lyapsylvcs_blocked!(A11, A22, C12, true, blocksize)
         BLAS.syr2k!('U', 'T', ONE, A12, C12, ONE, C22)
         _lyapcs_blocked!(A22,C22,adj,blocksize)
      else
         _lyapcs_blocked!(A22,C22,adj,blocksize)
         mul!(C12,A12,Symmetric(C22),1,1)
         # sylvcs!(A11, A22, C12, adjB = true)
         # rmul!(C12,-1)
         _lyapsylvcs_blocked!(A11, A22, C12, false, blocksize)
         BLAS.syr2k!('U', 'N', ONE, A12, C12, ONE, C11)
         _lyapcs_blocked!(A11,C11,adj,blocksize)
      end
   end
end

function lyapc2!(adj,C::AbstractMatrix{T},na::Int,A::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
# speed and reduced allocation oriented implementation of a solver for 1x1 or 2x2 continuous Lyapunov equations
#      A'*X + X*A = -C if adj = true  -> R = kron(I,A')+kron(A',I) = (kron(I,A)+kron(A,I))'
#      A*X + X*A' = -C if adj = false -> R = kron(I,A)+kron(A,I)
   if na == 1
      rmul!(C,inv(-2*A[1,1]))
      any(!isfinite, C) && throw("ME:SingularException: A has eigenvalue(s) ≈ 0")
      return C
   end
   ZERO = zero(T)
   @inbounds  Y[1] = -C[1,1]/2
   @inbounds  Y[2] = -C[2,1]
   @inbounds  Y[3] = -C[2,2]/2
   if adj
      # @inbounds R = [ A[1,1]    A[2,1]         0;
      #                 A[1,2]    A[1,1]+A[2,2]  A[2,1];
      #                 0         A[1,2]         A[2,2] ]
      @inbounds  R[1,1] = A[1,1]
      @inbounds  R[1,2] = A[2,1]
      @inbounds  R[1,3] = ZERO
      @inbounds  R[2,1] = A[1,2]
      @inbounds  R[2,2] = A[1,1]+A[2,2]
      @inbounds  R[2,3] = A[2,1]
      @inbounds  R[3,1] = ZERO
      @inbounds  R[3,2] = A[1,2]
      @inbounds  R[3,3] = A[2,2]
   else
      # @inbounds R = [ A[1,1]    A[1,2]         0;
      #                 A[2,1]    A[1,1]+A[2,2]  A[1,2];
      #                 0         A[2,1]         A[2,2] ]
      @inbounds  R[1,1] = A[1,1]
      @inbounds  R[1,2] = A[1,2]
      @inbounds  R[1,3] = ZERO
      @inbounds  R[2,1] = A[2,1]
      @inbounds  R[2,2] = A[1,1]+A[2,2]
      @inbounds  R[2,3] = A[1,2]
      @inbounds  R[3,1] = ZERO
      @inbounds  R[3,2] = A[2,1]
      @inbounds  R[3,3] = A[2,2]
   end
   luslv!(R,Y,3) && throw("ME:SingularException: A has eigenvalues α and β such that α+β' ≈ 0")
   @inbounds C[1,1] = Y[1]
   @inbounds C[1,2] = Y[2]
   @inbounds C[2,1] = Y[2]
   @inbounds C[2,2] = Y[3]
   return C
end

function lyapcsylv2!(adj,C::AbstractMatrix{T},na::Int,nb::Int,A::AbstractMatrix{T},B::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
# speed and reduced allocation oriented implementation of a solver for 1x1 and 2x2 Sylvester equations
# encountered in solving continuous Lyapunov equations:
#      A'*X + X*B = -C if adj = true  -> R = kron(I,A')+kron(B',I) = (kron(I,A)+kron(B,I))'
#      A*X + X*B' = -C if adj = false -> R = kron(I,A)+kron(B,I)
   if na == 1 && nb == 1
      temp = A[1,1] + B[1,1]
      rmul!(C,inv(-temp))
      any(!isfinite, C) && throw("ME:SingularException: A has eigenvalues α and β such that α+β ≈ 0")
      return C
   end
   ZERO = zero(T)
   if adj
      if na == 1
         # @inbounds R = [ A[1,1]+B[1,1]    B[2,1];
         #                      B[1,2]      A[1,1]+B[2,2]]
         @inbounds  R[1,1] = A[1,1]+B[1,1]
         @inbounds  R[1,2] = B[2,1]
         @inbounds  R[2,1] = B[1,2]
         @inbounds  R[2,2] = A[1,1]+B[2,2]
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
      else
         if nb == 1
            # @inbounds R = [ A[1,1]+B[1,1]       A[2,1];
            #                  A[1,2]          A[2,2]+B[1,1] ]
            @inbounds  R[1,1] = A[1,1]+B[1,1]
            @inbounds  R[1,2] = A[2,1]
            @inbounds  R[2,1] = A[1,2]
            @inbounds  R[2,2] = A[2,2]+B[1,1]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # @inbounds R = [ A[1,1]+B[1,1]       A[2,1]       B[2,1]           0;
            #                 A[1,2]        A[2,2]+B[1,1]      0             B[2,1];
            #                 B[1,2]              0       A[1,1]+B[2,2]     A[2,1];
            #                   0               B[1,2]       A[1,2]       A[2,2]+B[2,2]]
            @inbounds  R[1,1] = A[1,1]+B[1,1]
            @inbounds  R[1,2] = A[2,1]
            @inbounds  R[1,3] = B[2,1]
            @inbounds  R[1,4] = ZERO
            @inbounds  R[2,1] = A[1,2]
            @inbounds  R[2,2] = A[2,2]+B[1,1]
            @inbounds  R[2,3] = ZERO
            @inbounds  R[2,4] = B[2,1]
            @inbounds  R[3,1] = B[1,2]
            @inbounds  R[3,2] = ZERO
            @inbounds  R[3,3] = A[1,1]+B[2,2]
            @inbounds  R[3,4] = A[2,1]
            @inbounds  R[4,1] = ZERO
            @inbounds  R[4,2] = B[1,2]
            @inbounds  R[4,3] = A[1,2]
            @inbounds  R[4,4] = A[2,2]+B[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   else
      if na == 1
         # @inbounds R = [ A[1,1]+B[1,1]       B[1,2];
         #                    B[2,1]       A[1,1] + B[2,2]]
         @inbounds  R[1,1] = A[1,1]+B[1,1]
         @inbounds  R[1,2] = B[1,2]
         @inbounds  R[2,1] = B[2,1]
         @inbounds  R[2,2] = A[1,1]+B[2,2]
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
      else
         if nb == 1
            # @inbounds R = [ A[1,1]+B[1,1]       A[1,2];
            #                 A[2,1]        A[2,2]+B[1,1]]
            @inbounds  R[1,1] = A[1,1]+B[1,1]
            @inbounds  R[1,2] = A[1,2]
            @inbounds  R[2,1] = A[2,1]
            @inbounds  R[2,2] = A[2,2]+B[1,1]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # @inbounds R = [ A[1,1]+B[1,1]       A[1,2]       B[1,2]         0;
            #                 A[2,1]      A[2,2]+B[1,1]       0         B[1,2];
            #                  B[2,1]            0      A[1,1]+B[2,2]     A[1,2];
            #                  0             B[2,1]        A[2,1]    A[2,2]+B[2,2]]
            @inbounds  R[1,1] = A[1,1]+B[1,1]
            @inbounds  R[1,2] = A[1,2]
            @inbounds  R[1,3] = B[1,2]
            @inbounds  R[1,4] = ZERO
            @inbounds  R[2,1] = A[2,1]
            @inbounds  R[2,2] = A[2,2]+B[1,1]
            @inbounds  R[2,3] = ZERO
            @inbounds  R[2,4] = B[1,2]
            @inbounds  R[3,1] = B[2,1]
            @inbounds  R[3,2] = ZERO
            @inbounds  R[3,3] = A[1,1]+B[2,2]
            @inbounds  R[3,4] = A[1,2]
            @inbounds  R[4,1] = ZERO
            @inbounds  R[4,2] = B[2,1]
            @inbounds  R[4,3] = A[2,1]
            @inbounds  R[4,4] = A[2,2]+B[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   end
   luslv!(R,Y,na*nb) && throw("ME:SingularException: A has eigenvalues α and β such that α+β ≈ 0")
   if na == 1 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[1,2] = Y[2]
   elseif na == 2 && nb == 1
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
   else # na == 2 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
      @inbounds C[1,2] = Y[3]
      @inbounds C[2,2] = Y[4]
   end
   return C
end
function lyapcs!(A::AbstractMatrix{T1},C::AbstractMatrix{T1}; adj = false) where {T1<:Complex}
   n = LinearAlgebra.checksquare(A)
   (LinearAlgebra.checksquare(C) == n && ishermitian(C)) ||
      throw(DimensionMismatch("C must be a $n x $n hermitian matrix"))
   if isdiag(A) 
      if adj 
         for i = 1:n
            C[i,i] = -C[i,i]/(2*real(A[i,i]))
            isfinite(C[i,i]) || throw("ME:SingularException: A has eigenvalue(s) with zero real part(s)")
            for j = i+1:n
               C[i,j] = -C[i,j]/(A[i,i]'+A[j,j])
               isfinite(C[i,j]) || throw("ME:SingularException: A has eigenvalue(s) α and β such that α+β' = 0")
               C[j,i] = C[i,j]'
            end
         end
      else
         for i = 1:n
            C[i,i] = -C[i,i]/(2*real(A[i,i]))
            isfinite(C[i,i]) || throw("ME:SingularException: A has eigenvalue(s) with zero real part(s)")
            for j = i+1:n
               C[i,j] = -C[i,j]/(A[i,i]+A[j,j]')
               isfinite(C[i,j]) || throw("ME:SingularException: A has eigenvalue(s) α and β such that α+β' = 0")
               C[j,i] = C[i,j]'
            end
         end
      end   
      return C[:,:]
   end      

   if adj
      # """
      # The (K,L)th element of X is determined starting from
      # upper-left corner column by column by

      # A(K,K)'*X(K,L) + X(K,L)*A(L,L) = -C(K,L) - R(K,L),

      # where
      #          K-1                    L-1
      # R(K,L) = SUM [A(I,K)'*X(I,L)] + SUM [X(K,J)*A(J,L)].
      #          I=1                    J=1
      # """
      for l = 1:n
          for k = l:n
              y = C[k,l]
              for ia = l:k-1
                  y += A[ia,k]'*C[ia,l]
              end
              C[k,l] = -y/(A[k,k]'+A[l,l])
              isfinite(C[k,l]) || throw("ME:SingularException: A has eigenvalues α and β such that α+β ≈ 0")
              k == l ? (C[k,k] = real(C[k,k])) : (C[l,k] = C[k,l]')
           end
           for jr = l+1:n
               for ir = jr:n
                   C[ir,jr] += C[ir,l]*A[l,jr] + (A[l,ir]*C[jr,l])'
                   if jr != ir
                      C[jr,ir] = C[ir,jr]'
                   end
               end
           end
      end
   else
      # """
      # The (K,L)th element of X is determined starting from
      # bottom-right corner column by column by

      # A(K,K)*X(K,L) + X(K,L)*A(L,L)' = -C(K,L) - R(K,L),

      # where
      #           N                     N
      # R(K,L) = SUM [A(K,I)*X(I,L)] + SUM [X(K,J)*A(L,J)'].
      #         I=K+1                 J=L+1
      # """
      for l = n:-1:1
          for k = l:-1:1
              y = C[k,l]
              for ia = k+1:l
                 y += A[k,ia]*C[ia,l]
              end
              C[k,l] = -y/(A[k,k]+A[l,l]')
              isfinite(C[k,l]) || throw("ME:SingularException: A has eigenvalues α and β such that α+β ≈ 0")
              k == l ? (C[k,k] = real(C[k,k])) : (C[l,k] = C[k,l]')
           end
           for jr = 1:l-1
               for ir = 1:jr
                  C[ir,jr] += C[ir,l]*A[jr,l]' + A[ir,l]*C[l,jr]
                  if ir != jr
                     C[jr,ir] = C[ir,jr]'
                  end
               end
           end
       end
   end
end
function _lyapcs_blocked!(A::AbstractMatrix{T1},C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasComplex}
   n = size(A, 1)
   ONE = one(T1)
   RONE = real(ONE)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U',true)
      lyapcs!(A,C;adj)
   else
      # split A and C (by rows and columns)
      n1 = n ÷ 2
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
      end
      if adj
         _lyapcs_blocked!(A11,C11,adj,blocksize)
         mul!(C12,Hermitian(C11),A12,1,1)
         #sylvcs!(A11, A22, C12, adjA = true)
         #rmul!(C12,-1)
         _lyapsylvcs_blocked!(A11, A22, C12, true, blocksize)
         BLAS.her2k!('U', 'C', ONE, A12, C12, RONE, C22)
         _lyapcs_blocked!(A22,C22,adj,blocksize)
      else
         _lyapcs_blocked!(A22,C22,adj,blocksize)
         mul!(C12,A12,Hermitian(C22),1,1)
         # sylvcs!(A11, A22, C12, adjB = true)
         # rmul!(C12,-1)
         _lyapsylvcs_blocked!(A11, A22, C12, false, blocksize)
         BLAS.her2k!('U', 'N', ONE, A12, C12, RONE, C11)
         _lyapcs_blocked!(A11,C11,adj,blocksize)
      end
   end
end

function _lyapsylvcs_blocked!(A, B, C, adj::Bool, blocksize::Integer)
   m = size(A, 1)
   n = size(B, 1)
   if m <= max(blocksize,4) && n <= max(blocksize,4)
      sylvcs!(A,B,C;adjA = adj,adjB = !adj)
      rmul!(C, -1)
   else
      midm = m ÷ 2
      m1 = A[midm+1, midm] != 0 ? midm + 1 : midm
      ia1 = 1:m1; ia2 = m1+1:m 
      midn = n ÷ 2
      n1 = B[midn+1, midn] != 0 ? midn + 1 : midn
      ib1 = 1:n1; ib2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[ia1,ia1], A[ia1,ia2], A[ia2,ia2]
         B11, B12, B22 = B[ib1,ib1], B[ib1,ib2], B[ib2,ib2]
         C11, C12, C21, C22 = C[ia1,ib1], C[ia1,ib2], C[ia2,ib1], C[ia2,ib2]
      end
      if adj
         # solve A' X + X B + C = 0

         # X11 solver
         _lyapsylvcs_blocked!(A11, B11, C11, adj, blocksize)

         # C12 update: C12 = C12 + X11*B12
         mul!(C12,C11,B12,1,1)

         # X12 solver
         _lyapsylvcs_blocked!(A11, B22, C12, adj, blocksize)

         # C21 update: C21 = C21 + A12'*X11
         mul!(C21,A12',C11,1,1)

         # X21 solver
         _lyapsylvcs_blocked!(A22, B11, C21, adj, blocksize)

         # C22 update: C22 = C22 + A12'*X12 + X21*B12

         # C22 = C22 + A12'*X12
         mul!(C22, A12', C12, 1, 1)
         # C22 = C22 + X21*B12
         mul!(C22, C21, B12, 1, 1)

         # X22 solver
         _lyapsylvcs_blocked!(A22, B22, C22, adj, blocksize)
      else
         # solve A X  + X B' + C = 0

         # X22 solver
         _lyapsylvcs_blocked!(A22, B22, C22, adj, blocksize)

         # C12 update: C12 = C12 + A12*X22
         mul!(C12,A12,C22,1,1)

         # X12 solver
         _lyapsylvcs_blocked!(A11, B22, C12, adj, blocksize)

         # C21 update: C21 = C21 + X22*B12'
         mul!(C21,C22,B12',1,1)

         # X21 solver
         _lyapsylvcs_blocked!(A22, B11, C21, adj, blocksize)

         # C11 update: C11 = C11 + A12*X21 + X12*B12' 
         # C11 = C11 + A12*X21
         mul!(C11, A12, C21, 1, 1)
         # C11 = C11 + X12*B12'
         mul!(C11, C12, B12', 1, 1) 
 
         # X11 solver
         _lyapsylvcs_blocked!(A11, B11, C11, adj, blocksize)
      end
   end   
end


"""
    lyapcs!(A, E, C; adj = false)

Solve the generalized continuous Lyapunov matrix equation

                op(A)Xop(E)' + op(E)Xop(A)' + C = 0,

where `op(A) = A` and `op(E) = E` if `adj = false` and `op(A) = A'` and
`op(E) = E'` if `adj = true`. The pair `(A,E)` is in a generalized real or
complex Schur form and `C` is a symmetric or hermitian matrix.
The pencil `A-λE` must not have eigenvalues `α` and `β` such that `α+β = 0`.
The computed symmetric or hermitian solution `X` is contained in `C`.
"""
function lyapcs!(A::AbstractMatrix{T1},E::Union{AbstractMatrix{T1},UniformScaling{Bool}}, C::AbstractMatrix{T1}; adj = false) where {T1<:Real}
   n = LinearAlgebra.checksquare(A)
   (LinearAlgebra.checksquare(C) == n && issymmetric(C)) ||
      throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
   (typeof(E) == UniformScaling{Bool} || (isequal(E,I) && size(E,1) == n)) && (lyapcs!(A, C, adj = adj); return)
   LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))

   ONE = one(T1)
   ZERO = zero(T1)

   # determine the structure of the real Schur form
   ba, p = sfstruct(A)

   G = Matrix{T1}(undef,2,2)
   W = Array{T1,2}(undef,n,2)
   Xw = Matrix{T1}(undef,4,4)
   Yw = Vector{T1}(undef,4)
   if adj
      # """
      # The (K,L)th block of X is determined starting from the
      # upper-left corner column by column by

      # A(K,K)'*X(K,L)*E(L,L) + E(K,K)'*X(K,L)*A(L,L) = -C(K,L) - R(K,L),

      # where
      #           K           L-1
      # R(K,L) = SUM {A(I,K)'*SUM [X(I,J)*E(J,L)]} +
      #          I=1          J=1

      #           K           L-1
      #          SUM {E(I,K)'*SUM [X(I,J)*A(J,L)]} +
      #          I=1          J=1

      #          K-1
      #         {SUM [A(I,K)'*X(I,L)]}*E(L,L) +
      #          I=1

      #          K-1
      #         {SUM [E(I,K)'*X(I,L)]}*A(L,L).
      #          I=1
      # """
      i = 1
      for kk = 1:p
          dk = ba[kk]
          dkk = 1:dk
          k = i:i+dk-1
          j = 1
          ir = 1:i-1
          for ll = 1:kk
             dl = ba[ll]
             j1 = j+dl-1
             l = j:j1
             y = view(G,1:dk,1:dl)
             Ckl = view(C,k,l)
             copyto!(y,Ckl)
             if kk > 1
                 #   C[l,k] = C[l,ir]*A[ir,k]
                 #   W[l,dkk] = C[l,ir]*E[ir,k]
                 mul!(view(C,l,k),view(C,l,ir),view(A,ir,k))
                 mul!(view(W,l,dkk),view(C,l,ir),view(E,ir,k))
                 ic = 1:j1
                 #   y += C[ic,k]'*E[ic,l] + W[ic,dkk]'*A[ic,l]
                 mul!(y,transpose(view(C,ic,k)),view(E,ic,l),ONE,ONE)
                 mul!(y,transpose(view(W,ic,dkk)),view(A,ic,l),ONE,ONE)
             end
             if i == j
                lyapc2!(adj,y,dk,view(A,k,k),view(E,k,k),Xw,Yw)
                copyto!(Ckl,y)
               else
                lyapcsylv2!(adj,y,dk,dl,view(A,k,k),view(E,k,k),view(A,l,l),view(E,l,l),Xw,Yw)
                copyto!(Ckl,y)
             end
             j += dl
             if j <= i
                #  C[l,k] += C[k,l]'*A[k,k]
                #  W[l,dkk] += C[k,l]'*E[k,k]
                mul!(view(C,l,k),transpose(Ckl),view(A,k,k),ONE,ONE)
                mul!(view(W,l,dkk),transpose(Ckl),view(E,k,k),ONE,ONE)
             end
          end
          if kk > 1
             ir = 1:i-1
             #C[ir,k] = C[k,ir]'
             transpose!(view(C,ir,k),view(C,k,ir))
          end
          i += dk
      end
   else
      # """
      # The (K,L)th block of X is determined starting from
      # bottom-right corner column by column by

      # A(K,K)*X(K,L)*E(L,L)' + E(K,K)*X(K,L)*A(L,L)' = -C(K,L) - R(K,L),

      # where

      #           N            N
      # R(K,L) = SUM {A(K,I)* SUM [X(I,J)*E(L,J)']} +
      #          I=K         J=L+1

      #           N            N
      #          SUM {E(K,I)* SUM [X(I,J)*A(L,J)']} +
      #          I=K         J=L+1

      #             N
      #          { SUM [A(K,J)*X(J,L)]}*E(L,L)' +
      #           J=K+1

      #             N
      #          { SUM [E(K,J)*X(J,L)]}*A(L,L)'
      #           J=K+1
      # """
      j = n
      for ll = p:-1:1
        dl = ba[ll]
        l = j-dl+1:j
        dll = 1:dl
        i = n
        ir = j+1:n
        for kk = p:-1:ll
            dk = ba[kk]
            i1 = i-dk+1
            k = i1:i
            Clk = view(C,l,k)
            y = view(G,1:dl,1:dk)
            copyto!(y,Clk)
            if ll < p
               # C[k,l] = C[k,ir]*A[l,ir]'
               # W[k,dll] = C[k,ir]*E[l,ir]'
               mul!(view(C,k,l),view(C,k,ir),transpose(view(A,l,ir)))
               mul!(view(W,k,dll),view(C,k,ir),transpose(view(E,l,ir)))
               ic = i1:n
               # y += (E[k,ic]*C[ic,l] + A[k,ic]*W[ic,dll])'
               mul!(y,transpose(view(C,ic,l)),transpose(view(E,k,ic)),ONE,ONE)
               mul!(y,transpose(view(W,ic,dll)),transpose(view(A,k,ic)),ONE,ONE)
            end
            if i == j
               lyapc2!(adj,y,dk,view(A,k,k),view(E,k,k),Xw,Yw)
               copyto!(Clk,y)
            else
               lyapcsylv2!(adj,y,dl,dk,view(A,l,l),view(E,l,l),view(A,k,k),view(E,k,k),Xw,Yw)
               copyto!(Clk,y)
            end
            i -= dk
            if i >= j
               # C[k,l] += (A[l,l]*C[l,k])'
               # W[k,dll] += (E[l,l]*C[l,k])'
               mul!(view(C,k,l),transpose(Clk),transpose(view(A,l,l)),ONE,ONE)
               mul!(view(W,k,dll),transpose(Clk),transpose(view(E,l,l)),ONE,ONE)
            else
               break
            end
        end
        if ll < p
           ir = j+1:n
           # C[ir,l] = C[l,ir]'
           transpose!(view(C,ir,l),view(C,l,ir))
         end
        j -= dl
      end
   end
end
@inline function lyapc2!(adj,C::AbstractMatrix{T},na::Int,A::AbstractMatrix{T},E::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
#  @inline function lyapc2!(adj,C::AbstractMatrix{T},na::Int,A::AbstractMatrix{T},E::AbstractMatrix{T},Xw::AbstractMatrix{T},Yw::StridedVector{T}) where T <:BlasReal
      # speed and reduced allocation oriented implementation of a solver for 1x1 or 2x2 generalized continuous Lyapunov equations
# LAPACK generated diagonal structure of E is exploited when possible
#      A'*X*E + E'*X*A = -C if adj = true  -> R = kron(E',A')+kron(A',E') = (kron(E,A)+kron(A,E))'
#      A*X*E' + E*X*A' = -C if adj = false -> R = kron(E,A)+kron(A,E)
   if na == 1
      temp = E[1,1]*A[1,1]
      rmul!(C,inv(-2*temp))
      any(!isfinite, C) &&  throw("ME:SingularException: A-λE has zero or infinite eigenvalue(s)")
      return C
   end
   ZERO = zero(T)
   @inbounds  Y[1] = -C[1,1]/2
   @inbounds  Y[2] = -C[2,1]
   @inbounds  Y[3] = -C[2,2]/2
   if adj
      # Rt =
      #  [        a11*e11,                   a21*e11,         0
      #  a11*e12 + a12*e11, a11*e22 + a21*e12 + a22*e11,   a21*e22
      #          a12*e12,       a12*e22 + a22*e12, a22*e22]
      # iszero(E[1,2]) ?
      #    (@inbounds R = [  A[1,1]*E[1,1]             A[2,1]*E[1,1]               0 ;
      #                      A[1,2]*E[1,1]    A[1,1]*E[2,2]+A[2,2]*E[1,1]    A[2,1]*E[2,2];
      #                           0                   A[1,2]*E[2,2]          A[2,2]*E[2,2]]) :
      #    (@inbounds R = [  A[1,1]*E[1,1]                    A[2,1]*E[1,1]                              0 ;
      #          A[1,1]*E[1,2]+A[1,2]*E[1,1]    A[1,1]*E[2,2]+A[2,1]*E[1,2]+A[2,2]*E[1,1]    A[2,1]*E[2,2];
      #          A[1,2]*E[1,2]                    A[1,2]*E[2,2]+A[2,2]*E[1,2]                A[2,2]*E[2,2]] )
      @inbounds  R[1,1] = A[1,1]*E[1,1]
      @inbounds  R[1,2] = A[2,1]*E[1,1]
      @inbounds  R[1,3] = ZERO
      @inbounds  R[2,1] = A[1,1]*E[1,2]+A[1,2]*E[1,1]
      @inbounds  R[2,2] = A[1,1]*E[2,2]+A[2,1]*E[1,2]+A[2,2]*E[1,1]
      @inbounds  R[2,3] = A[2,1]*E[2,2]
      @inbounds  R[3,1] = A[1,2]*E[1,2]
      @inbounds  R[3,2] = A[1,2]*E[2,2]+A[2,2]*E[1,2]
      @inbounds  R[3,3] = A[2,2]*E[2,2]
   else
      # R =
      # [ a11*e11,       a11*e12 + a12*e11,         a12*e12
      # a21*e11, a11*e22 + a21*e12 + a22*e11, a12*e22 + a22*e12
      #       0,                   a21*e22,         a22*e22]
      # iszero(E[1,2]) ?
      #    (@inbounds  R = [  A[1,1]*E[1,1]            A[1,2]*E[1,1]                    0;
      #                       A[2,1]*E[1,1]    A[1,1]*E[2,2]+A[2,2]*E[1,1]     A[1,2]*E[2,2];
      #                             0                  A[2,1]*E[2,2]           A[2,2]*E[2,2]] ) :
      #    (@inbounds  R = [  A[1,1]*E[1,1]    A[1,1]*E[1,2]+A[1,2]*E[1,1]             A[1,2]*E[1,2];
      #           A[2,1]*E[1,1]    A[1,1]*E[2,2]+A[2,1]*E[1,2]+A[2,2]*E[1,1]     A[1,2]*E[2,2]+A[2,2]*E[1,2];
      #                 0                   A[2,1]*E[2,2]                              A[2,2]*E[2,2]] )
      @inbounds  R[1,1] = A[1,1]*E[1,1]
      @inbounds  R[1,2] = A[1,1]*E[1,2]+A[1,2]*E[1,1]
      @inbounds  R[1,3] = A[1,2]*E[1,2]
      @inbounds  R[2,1] = A[2,1]*E[1,1]
      @inbounds  R[2,2] = A[1,1]*E[2,2]+A[2,1]*E[1,2]+A[2,2]*E[1,1]
      @inbounds  R[2,3] = A[1,2]*E[2,2]+A[2,2]*E[1,2]
      @inbounds  R[3,1] = ZERO
      @inbounds  R[3,2] = A[2,1]*E[2,2]
      @inbounds  R[3,3] = A[2,2]*E[2,2]
   end
   luslv!(R,Y,3) && throw("ME:SingularException: A-λE has eigenvalues α and β such that α+β ≈ 0")
   @inbounds C[1,1] = Y[1]
   @inbounds C[1,2] = Y[2]
   @inbounds C[2,1] = Y[2]
   @inbounds C[2,2] = Y[3]
   return C
end
@inline function lyapcsylv2!(adj,C::AbstractMatrix{T},na::Int,nb::Int,A::AbstractMatrix{T},E::AbstractMatrix{T},B::AbstractMatrix{T},F::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
#  @inline function lyapcsylv2!(adj,C::AbstractMatrix{T},na::Int,nb::Int,A::AbstractMatrix{T},E::AbstractMatrix{T},B::AbstractMatrix{T},F::AbstractMatrix{T},Xw::AbstractMatrix{T},Yw::StridedVector{T}) where T <:BlasReal
# speed and reduced allocation oriented implementation of a solver for 1x1 and 2x2 Sylvester equations
# encountered in solving generalized continuous Lyapunov equations:
#      A'*X*F + E'*X*B = -C if adj = true  -> R = kron(F',A')+kron(B',E') = (kron(F,A)+kron(B,E))'
#      A*X*F' + E*X*B' = -C if adj = false -> R = kron(F,A)+kron(B,E)
   if na == 1 && nb == 1
      temp = A[1,1]*F[1,1] + E[1,1]*B[1,1]
      rmul!(C,inv(-temp))
      any(!isfinite, C) && throw("ME:SingularException: A-λE has eigenvalues α and β such that α+β ≈ 0")
      return C
   end
   ZERO = zero(T)
   if adj
      if na == 1
         # R12t =
         # [ a11*f11+b11*e11           b21*e11]
         # [ a11*f12+b12*e11 a11*f22+b22*e11]
         # @inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1]           B[2,1]*E[1,1];
         #                 A[1,1]*F[1,2]+B[1,2]*E[1,1] A[1,1]*F[2,2]+B[2,2]*E[1,1]]
         @inbounds  R[1,1] = A[1,1]*F[1,1]+B[1,1]*E[1,1]
         @inbounds  R[1,2] = B[2,1]*E[1,1]
         @inbounds  R[2,1] = A[1,1]*F[1,2]+B[1,2]*E[1,1]
         @inbounds  R[2,2] = A[1,1]*F[2,2]+B[2,2]*E[1,1]
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
      else
         if nb == 1
            # R21t =
            # [ a11*f11+b11*e11           a21*f11]
            # [ a12*f11+b11*e12 a22*f11+b11*e22]
            # @inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1]           A[2,1]*F[1,1];
            #                 A[1,2]*F[1,1]+B[1,1]*E[1,2] A[2,2]*F[1,1]+B[1,1]*E[2,2] ]
            @inbounds  R[1,1] = A[1,1]*F[1,1]+B[1,1]*E[1,1]
            @inbounds  R[1,2] = A[2,1]*F[1,1]
            @inbounds  R[2,1] = A[1,2]*F[1,1]+B[1,1]*E[1,2]
            @inbounds  R[2,2] = A[2,2]*F[1,1]+B[1,1]*E[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # Rt =
            # [ a11*f11+b11*e11           a21*f11           b21*e11                 0]
            # [ a12*f11+b11*e12 a22*f11+b11*e22           b21*e12           b21*e22]
            # [ a11*f12+b12*e11           a21*f12 a11*f22+b22*e11           a21*f22]
            # [ a12*f12+b12*e12 a22*f12+b12*e22 a12*f22+b22*e12 a22*f22+b22*e22]
            # (iszero(E[1,2]) && iszero(F[1,2])) ?
            #  (@inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1]           A[2,1]*F[1,1]           B[2,1]*E[1,1]                 0;
            #                A[1,2]*F[1,1]  A[2,2]*F[1,1]+B[1,1]*E[2,2]           0           B[2,1]*E[2,2];
            #                B[1,2]*E[1,1]        0  A[1,1]*F[2,2]+B[2,2]*E[1,1]           A[2,1]*F[2,2];
            #                0  B[1,2]*E[2,2] A[1,2]*F[2,2]+B[2,2]*E[1,2] A[2,2]*F[2,2]+B[2,2]*E[2,2]]) :
            #  (@inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1]           A[2,1]*F[1,1]           B[2,1]*E[1,1]                 0;
            #                A[1,2]*F[1,1]+B[1,1]*E[1,2] A[2,2]*F[1,1]+B[1,1]*E[2,2]           B[2,1]*E[1,2]           B[2,1]*E[2,2];
            #                A[1,1]*F[1,2]+B[1,2]*E[1,1]           A[2,1]*F[1,2] A[1,1]*F[2,2]+B[2,2]*E[1,1]           A[2,1]*F[2,2];
            #                A[1,2]*F[1,2]+B[1,2]*E[1,2] A[2,2]*F[1,2]+B[1,2]*E[2,2] A[1,2]*F[2,2]+B[2,2]*E[1,2] A[2,2]*F[2,2]+B[2,2]*E[2,2]])
            @inbounds  R[1,1] = A[1,1]*F[1,1]+B[1,1]*E[1,1]
            @inbounds  R[1,2] = A[2,1]*F[1,1]
            @inbounds  R[1,3] = B[2,1]*E[1,1]
            @inbounds  R[1,4] = ZERO
            @inbounds  R[2,1] = A[1,2]*F[1,1]+B[1,1]*E[1,2]
            @inbounds  R[2,2] = A[2,2]*F[1,1]+B[1,1]*E[2,2]
            @inbounds  R[2,3] = B[2,1]*E[1,2]
            @inbounds  R[2,4] = B[2,1]*E[2,2]
            @inbounds  R[3,1] = A[1,1]*F[1,2]+B[1,2]*E[1,1]
            @inbounds  R[3,2] = A[2,1]*F[1,2]
            @inbounds  R[3,3] = A[1,1]*F[2,2]+B[2,2]*E[1,1]
            @inbounds  R[3,4] = A[2,1]*F[2,2]
            @inbounds  R[4,1] = A[1,2]*F[1,2]+B[1,2]*E[1,2]
            @inbounds  R[4,2] = A[2,2]*F[1,2]+B[1,2]*E[2,2]
            @inbounds  R[4,3] = A[1,2]*F[2,2]+B[2,2]*E[1,2]
            @inbounds  R[4,4] = A[2,2]*F[2,2]+B[2,2]*E[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   else
      if na == 1
         # R12 =
         # [ a11*f11+b11*e11 a11*f12+b12*e11]
         # [           b21*e11 a11*f22+b22*e11]
         # @inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1] A[1,1]*F[1,2]+B[1,2]*E[1,1];
         #                    B[2,1]*E[1,1] A[1,1]*F[2,2]+B[2,2]*E[1,1]]
         @inbounds  R[1,1] = A[1,1]*F[1,1]+B[1,1]*E[1,1]
         @inbounds  R[1,2] = A[1,1]*F[1,2]+B[1,2]*E[1,1]
         @inbounds  R[2,1] = B[2,1]*E[1,1]
         @inbounds  R[2,2] = A[1,1]*F[2,2]+B[2,2]*E[1,1]
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*f11+b11*e11 a12*f11+b11*e12]
            # [           a21*f11 a22*f11+b11*e22]
            # @inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1] A[1,2]*F[1,1]+B[1,1]*E[1,2];
            #                  A[2,1]*F[1,1] A[2,2]*F[1,1]+B[1,1]*E[2,2]]
            @inbounds  R[1,1] = A[1,1]*F[1,1]+B[1,1]*E[1,1]
            @inbounds  R[1,2] = A[1,2]*F[1,1]+B[1,1]*E[1,2]
            @inbounds  R[2,1] = A[2,1]*F[1,1]
            @inbounds  R[2,2] = A[2,2]*F[1,1]+B[1,1]*E[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # R =
            # [ a11*f11+b11*e11 a12*f11+b11*e12 a11*f12+b12*e11 a12*f12+b12*e12]
            # [           a21*f11 a22*f11+b11*e22           a21*f12 a22*f12+b12*e22]
            # [           b21*e11           b21*e12 a11*f22+b22*e11 a12*f22+b22*e12]
            # [                 0           b21*e22           a21*f22 a22*f22+b22*e22]
            # (iszero(E[1,2]) && iszero(F[1,2])) ?
            # (@inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1] A[1,2]*F[1,1] B[1,2]*E[1,1] 0;
            #                A[2,1]*F[1,1] A[2,2]*F[1,1]+B[1,1]*E[2,2]           0   B[1,2]*E[2,2];
            #                B[2,1]*E[1,1]           0   A[1,1]*F[2,2]+B[2,2]*E[1,1] A[1,2]*F[2,2];
            #                0           B[2,1]*E[2,2]           A[2,1]*F[2,2] A[2,2]*F[2,2]+B[2,2]*E[2,2]]) :
            # (@inbounds R = [ A[1,1]*F[1,1]+B[1,1]*E[1,1] A[1,2]*F[1,1]+B[1,1]*E[1,2] A[1,1]*F[1,2]+B[1,2]*E[1,1] A[1,2]*F[1,2]+B[1,2]*E[1,2];
            #                A[2,1]*F[1,1] A[2,2]*F[1,1]+B[1,1]*E[2,2]           A[2,1]*F[1,2] A[2,2]*F[1,2]+B[1,2]*E[2,2];
            #                B[2,1]*E[1,1]           B[2,1]*E[1,2] A[1,1]*F[2,2]+B[2,2]*E[1,1] A[1,2]*F[2,2]+B[2,2]*E[1,2];
            #                0           B[2,1]*E[2,2]           A[2,1]*F[2,2] A[2,2]*F[2,2]+B[2,2]*E[2,2]])
            @inbounds  R[1,1] = A[1,1]*F[1,1]+B[1,1]*E[1,1]
            @inbounds  R[1,2] = A[1,2]*F[1,1]+B[1,1]*E[1,2]
            @inbounds  R[1,3] = A[1,1]*F[1,2]+B[1,2]*E[1,1]
            @inbounds  R[1,4] = A[1,2]*F[1,2]+B[1,2]*E[1,2]
            @inbounds  R[2,1] = A[2,1]*F[1,1]
            @inbounds  R[2,2] = A[2,2]*F[1,1]+B[1,1]*E[2,2]
            @inbounds  R[2,3] = A[2,1]*F[1,2]
            @inbounds  R[2,4] = A[2,2]*F[1,2]+B[1,2]*E[2,2]
            @inbounds  R[3,1] = B[2,1]*E[1,1]
            @inbounds  R[3,2] = B[2,1]*E[1,2]
            @inbounds  R[3,3] = A[1,1]*F[2,2]+B[2,2]*E[1,1]
            @inbounds  R[3,4] = A[1,2]*F[2,2]+B[2,2]*E[1,2]
            @inbounds  R[4,1] = ZERO
            @inbounds  R[4,2] = B[2,1]*E[2,2]
            @inbounds  R[4,3] = A[2,1]*F[2,2]
            @inbounds  R[4,4] = A[2,2]*F[2,2]+B[2,2]*E[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   end
   luslv!(R,Y,na*nb) && throw("ME:SingularException: A-λE has eigenvalues α and β such that α+β ≈ 0")
   if na == 1 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[1,2] = Y[2]
   elseif na == 2 && nb == 1
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
   else # na == 2 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
      @inbounds C[1,2] = Y[3]
      @inbounds C[2,2] = Y[4]
   end
   return C
end
function lyapcs!(A::AbstractMatrix{T1},E::Union{AbstractMatrix{T1},UniformScaling{Bool}}, C::AbstractMatrix{T1}; adj = false) where {T1<:Complex}
   n = LinearAlgebra.checksquare(A)
   (LinearAlgebra.checksquare(C) == n && ishermitian(C)) ||
      throw(DimensionMismatch("C must be a $n x $n hermitian matrix"))
   (typeof(E) == UniformScaling{Bool} || (isequal(E,I) && size(E,1) == n)) && (lyapcs!(A, C, adj = adj); return)
   LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))

   W = Array{T1,1}(undef,n)
   # Compute the hermitian solution
   if adj
      for k = 1:n
         for l = 1:k
            y = C[k,l]
            if k > 1
               C[l,k] = C[l,1]*A[1,k]
               W[l] = C[l,1]*E[1,k]
               for ir = 2:k-1
                  C[l,k] +=  C[l,ir]*A[ir,k]
                  W[l] += C[l,ir]*E[ir,k]
               end
               for ic = 1:l
                   y += C[ic,k]'*E[ic,l] + W[ic]'*A[ic,l]
                end
            end
            C[k,l] = -y/(A[k,k]'*E[l,l]+E[k,k]'*A[l,l])
            isfinite(C[k,l]) || throw("ME:SingularException: A-λE has eigenvalues α and β such that α+β ≈ 0")
            k == l && (C[k,k] = real(C[k,k]))
            if l < k
               C[l,k] += C[k,l]'*A[k,k]
               W[l] += C[k,l]'*E[k,k]
            end
         end
         for ir = 1:k-1
             C[ir,k] = C[k,ir]'
         end
      end
   else
      for l = n:-1:1
        for k = n:-1:l
            y = C[l,k]
            if l < n
               C[k,l] = C[k,l+1]*A[l,l+1]'
               W[k] = C[k,l+1]*E[l,l+1]'
               for ir = l+2:n
                   C[k,l] += C[k,ir]*A[l,ir]'
                   W[k] += C[k,ir]*E[l,ir]'
               end
               for ic = k:n
                   y += (E[k,ic]*C[ic,l] + A[k,ic]*W[ic])'
               end
            end
            C[l,k] = -y/(A[k,k]'*E[l,l]+E[k,k]'*A[l,l])
            isfinite(C[l,k]) ||  throw("ME:SingularException: A-λE has eigenvalues α and β such that α+β ≈ 0")
            k == l && (C[k,k] = real(C[k,k]))
            if k > l
               C[k,l] += (A[l,l]*C[l,k])'
               W[k] += (E[l,l]*C[l,k])'
            end
        end
        if l < n
           for ir = l+1:n
               C[ir,l] = C[l,ir]'
           end
        end
      end
   end
end
function lyapcs_blocked!(A::AbstractMatrix{T1}, E::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adj = false, blocksize::Integer = 64) where {T1<:BlasFloat}
    n = LinearAlgebra.checksquare(A)  
    LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))
    LinearAlgebra.checksquare(C) == n || throw(DimensionMismatch("C must be a $n x $n matrix or I"))
    mid = n ÷ 2
    if T1 <: Real  
       # Check symmetry ONCE here
       issymmetric(C) ||  throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
       mid += 2
    else
       ishermitian(C) ||  throw(DimensionMismatch("C must be a $n x $n Hermitian matrix"))
       mid += 1
    end
    
    # Allocate a full-sized WS and half-sized WS1 once. 
    WS = Matrix{T1}(undef,n,n)
    WS1 = Matrix{T1}(undef,mid,mid)
    _lyapcs_blocked!(WS, WS1, A, E, C, adj, blocksize)

    # Fill the lower triangle ONCE at the very end
    return LinearAlgebra.copytri!(C, 'U', T1 <: Complex)
end
function _lyapcs_blocked!(WS::AbstractMatrix{T1},WS1::AbstractMatrix{T1},A::AbstractMatrix{T1},E::AbstractMatrix{T1},
                          C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasReal}
   n = size(A, 1)
   ONE = one(T1)
   ZERO = zero(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U')
      lyapcs!(A,E,C;adj)
   else
      # split A, E and C (by rows and columns)
      mid = n ÷ 2
      n1 =  A[mid+1, mid] != 0 ? mid + 1 : mid
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         E11, E12, E22 = E[i1,i1], E[i1,i2], E[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
         WS11, WS12, WS22 = WS[i1,i1], WS[i1,i2], WS[i2,i2]
      end
      if adj
         # Adjoint Case: A' X E + E' X A + C = 0  
         
         # 1. Solve X11
        _lyapcs_blocked!(WS11, WS1, A11, E11, C11, adj, blocksize)

         # 2. Update C12 = C12 + A11' * X11 * E12 + E11' * X11 * A12
         mul!(WS12, Symmetric(C11, :U), E12)
         BLAS.gemm!('T', 'N', ONE, A11, WS12, ONE, C12)
    
         mul!(WS12, Symmetric(C11, :U), A12)
         BLAS.gemm!('T', 'N', ONE, E11, WS12, ONE, C12)    

         # 3. Solve Generalized Dual Sylvester for X12
         # Ensure your sylvester solver configuration treats A11 and E11 as transposed
         lyapgsylvcs_blocked!(WS1, A11, E22, E11, A22, C12; adj, blocksize)

         # 4. Update C22 = C22 + A12'*(X11*E12+X12*E22)+(X11*E12+X12*E22)'*A12
         mul!(WS12, Symmetric(C11, :U), E12)
         mul!(WS12, C12, E22, ONE, ONE)
         BLAS.syr2k!('U', 'T', ONE, A12, WS12, ONE, C22)

         # Update C22 = C22 + E12'*(X12*A22)+(X12*A22)'*E12
         mul!(WS12, C12, A22)
         BLAS.syr2k!('U', 'T', ONE, E12, WS12, ONE, C22)

         # 5. Solve X22
         _lyapcs_blocked!(WS22, WS1, A22, E22, C22, adj, blocksize)
      else
         # Standard Case: A X E' + E X A' + C = 0

         # X22 solver
         _lyapcs_blocked!(WS22, WS1, A22, E22, C22, adj, blocksize)

         # Update C12: C12 = C12 + A12 * X22 * E22' + E12 * X22 * A22'
         # Use WS[i1, i2] for (E12 * C22)
         BLAS.symm!('R', 'U', ONE, C22, E12, ZERO, WS12)
         BLAS.gemm!('N', 'T', ONE, WS12, A22, ONE, C12)
         # Use WS[i1, i2] for (A12 * C22)
         BLAS.symm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         BLAS.gemm!('N', 'T', ONE, WS12, E22, ONE, C12)

         # Solve generalized Sylvester: A11 * X12 * E22' + E11 X12 A22' + C12 = 0
         lyapgsylvcs_blocked!(WS1, A11, E22, E11, A22, C12; adj, blocksize)

         # Update C11: C11 = C11 + (A11*X12+A12*X22)*E12' + E12*(A11*X12+A12*X22)'
         # 1. WS12 = WS12 + A11*C12   [WS12 contains A11*C12+A12*C22]
         BLAS.gemm!('N', 'N', ONE, A11, C12, ONE, WS12)
         # 2. C11 = C11 + WS12*E12' + E12*WS12' -> syr2k
         BLAS.syr2k!('U', 'N', ONE, WS12, E12, ONE, C11)
  
         # 3. C11 = C11 + (E11*X12)*A12' + A12*(E11*X12)' -> syr2k
         BLAS.gemm!('N', 'N', ONE, E11, C12, ZERO, WS12)
         BLAS.syr2k!('U', 'N', ONE, WS12, A12, ONE, C11)

         # X11 solver
         _lyapcs_blocked!(WS11, WS1, A11, E11, C11, adj, blocksize)
      end
   end
end
function _lyapcs_blocked!(WS::AbstractMatrix{T1},WS1::AbstractMatrix{T1},A::AbstractMatrix{T1},E::AbstractMatrix{T1},
                          C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasComplex}
   n = size(A, 1)
   ONE = one(T1)
   RONE = real(ONE)
   ZERO = zero(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U',true)
      lyapcs!(A,E,C;adj)
   else
      # split A, E and C (by rows and columns)
      n1 = n ÷ 2
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         E11, E12, E22 = E[i1,i1], E[i1,i2], E[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
         WS11, WS12, WS22 = WS[i1,i1], WS[i1,i2], WS[i2,i2]
      end
      if adj
         # Adjoint Case: A' X E + E' X A + C = 0  
         
         # 1. Solve X11
        _lyapcs_blocked!(WS11, WS1, A11, E11, C11, adj, blocksize)

         # 2. Update C12 = C12 + A11' * X11 * E12 + E11' * X11 * A12
         mul!(WS12, Hermitian(C11, :U), E12)
         BLAS.gemm!('C', 'N', ONE, A11, WS12, ONE, C12)
    
         mul!(WS12, Hermitian(C11, :U), A12)
         BLAS.gemm!('C', 'N', ONE, E11, WS12, ONE, C12)    

         # 3. Solve Generalized Dual Sylvester for X12
         # Ensure your sylvester solver configuration treats A11 and E11 as transposed
         lyapgsylvcs_blocked!(WS1, A11, E22, E11, A22, C12; adj, blocksize)

         # 4. Update C22 = C22 + A12'*(X11*E12+X12*E22)+(X11*E12+X12*E22)'*A12
         mul!(WS12, Hermitian(C11, :U), E12)
         mul!(WS12, C12, E22, ONE, ONE)
         BLAS.her2k!('U', 'C', ONE, A12, WS12, RONE, C22)

         # Update C22 = C22 + E12'*(X12*A22)+(X12*A22)'*E12
         mul!(WS12, C12, A22)
         BLAS.her2k!('U', 'C', ONE, E12, WS12, RONE, C22)

         # 5. Solve X22
         _lyapcs_blocked!(WS22, WS1, A22, E22, C22, adj, blocksize)
      else
         # Standard Case: A X E' + E X A' + C = 0

         # X22 solver
         _lyapcs_blocked!(WS22, WS1, A22, E22, C22, adj, blocksize)

         # Update C12: C12 = C12 + A12 * X22 * E22' + E12 * X22 * A22'
         # Use WS[i1, i2] for (E12 * C22)
         BLAS.hemm!('R', 'U', ONE, C22, E12, ZERO, WS12)
         BLAS.gemm!('N', 'C', ONE, WS12, A22, ONE, C12)
         # Use WS[i1, i2] for (A12 * C22)
         BLAS.hemm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         BLAS.gemm!('N', 'C', ONE, WS12, E22, ONE, C12)

         # Solve generalized Sylvester: A11 * X12 * E22' + E11 X12 A22' + C12 = 0
         lyapgsylvcs_blocked!(WS1, A11, E22, E11, A22, C12; adj, blocksize)

         # Update C11: C11 = C11 + (A11*X12+A12*X22)*E12' + E12*(A11*X12+A12*X22)'
         # 1. WS12 = WS12 + A11*C12   [WS12 contains A11*C12+A12*C22]
         BLAS.gemm!('N', 'N', ONE, A11, C12, ONE, WS12)
         # 2. C11 = C11 + WS12*E12' + E12*WS12' -> syr2k
         BLAS.her2k!('U', 'N', ONE, WS12, E12, RONE, C11)
  
         # 3. C11 = C11 + (E11*X12)*A12' + A12*(E11*X12)' -> syr2k
         BLAS.gemm!('N', 'N', ONE, E11, C12, ZERO, WS12)
         BLAS.her2k!('U', 'N', ONE, WS12, A12, RONE, C11)

         # X11 solver
         _lyapcs_blocked!(WS11, WS1, A11, E11, C11, adj, blocksize)
      end
   end
end

function lyapgsylvcs_blocked!(WS::AbstractMatrix{T1}, A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}, D::AbstractMatrix{T1}, E::AbstractMatrix{T1}; adj = false, blocksize::Integer = 64) where {T1<:BlasFloat}
   m = LinearAlgebra.checksquare(A)  
   n = LinearAlgebra.checksquare(B)
   (m, n) == size(E) || throw(DimensionMismatch("E must be a $m x $n"))
   
   # Call the positional recursive worker
   _lyapgsylvcs_blocked!(WS, A, B, C, D, E, adj, blocksize)
end
function _lyapgsylvcs_blocked!(WS, A, B, C, D, E, adj::Bool, blocksize::Integer)
   m = size(A, 1)
   n = size(B, 1)
   if m <= max(blocksize,4) && n <= max(blocksize,4)
      gsylvs!(A,B,C,D,E;adjAC = adj,adjBD = !adj,DBSchur = true)
      rmul!(E, -1)
   else
      midm = m ÷ 2
      m1 = A[midm+1, midm] != 0 ? midm + 1 : midm
      ia1 = 1:m1; ia2 = m1+1:m 
      midn = n ÷ 2
      n1 = D[midn+1, midn] != 0 ? midn + 1 : midn
      ib1 = 1:n1; ib2 = n1+1:n 
      if eltype(A) <: Real
         @views begin
            A11, A12, A22 = A[ia1,ia1], A[ia1,ia2], A[ia2,ia2]
            B11, B12, B22 = UpperTriangular(B[ib1,ib1]), B[ib1,ib2], UpperTriangular(B[ib2,ib2])
            C11, C12, C22 = UpperTriangular(C[ia1,ia1]), C[ia1,ia2], UpperTriangular(C[ia2,ia2])
            D11, D12, D22 = D[ib1,ib1], D[ib1,ib2], D[ib2,ib2]
            E11, E12, E21, E22 = E[ia1,ib1], E[ia1,ib2], E[ia2,ib1], E[ia2,ib2]
            WS11, WS12, WS21, WS22 = WS[ia1,ib1], WS[ia1,ib2], WS[ia2,ib1], WS[ia2,ib2]
         end
      else
         @views begin
            A11, A12, A22 = UpperTriangular(A[ia1,ia1]), A[ia1,ia2], UpperTriangular(A[ia2,ia2])
            B11, B12, B22 = UpperTriangular(B[ib1,ib1]), B[ib1,ib2], UpperTriangular(B[ib2,ib2])
            C11, C12, C22 = UpperTriangular(C[ia1,ia1]), C[ia1,ia2], UpperTriangular(C[ia2,ia2])
            D11, D12, D22 =  UpperTriangular(D[ib1,ib1]), D[ib1,ib2],  UpperTriangular(D[ib2,ib2])
            E11, E12, E21, E22 = E[ia1,ib1], E[ia1,ib2], E[ia2,ib1], E[ia2,ib2]
            WS11, WS12, WS21, WS22 = WS[ia1,ib1], WS[ia1,ib2], WS[ia2,ib1], WS[ia2,ib2]
         end
      end
      if adj
         # solve A' X B + C' X D + E = 0

         # X11 solver
         _lyapgsylvcs_blocked!(WS11, A11, B11, C11, D11, E11, adj, blocksize)

         # E12 update: E12 = E12 + A11'*X11*B12 + C11'*X11*D12
         mul!(WS11, A11', E11)
         mul!(E12, WS11, B12, 1, 1)
         mul!(WS11, C11', E11)
         mul!(E12, WS11, D12, 1, 1)
 
         # X12 solver
         _lyapgsylvcs_blocked!(WS12, A11, B22, C11, D22, E12, adj, blocksize)

         # E21 update: E21 = E21 + A12'*X11*B11 + C12'*X11*D11
         #mul!(WS11,E11, UpperTriangular(B11))
         mul!(WS11,E11, B11)
         mul!(E21,A12',WS11,1,1)
         mul!(WS11, E11, D11)
         mul!(E21, C12', WS11,1,1)

         # X21 solver
         _lyapgsylvcs_blocked!(WS21, A22, B11, C22, D11, E21, adj, blocksize)

         # E22 update: E22 = E22 + A12'*X12*B22 + C12'*X12*D22 +
         #                   (A12'*X11+A22'*X21)*B12 + (C12'*X11+C22'*X21)*D12
         # WS21 = A12'*X11+A22'*X21
         # mul!(WS21, A12', E11)
         # mul!(WS21, A22', E21, 1, 1)
         mul!(WS21, A22', E21)
         mul!(WS21, A12', E11, 1, 1)
         # E22 = E22 + WS21 * B12
         mul!(E22, WS21, B12, 1, 1)
         # WS12 = C12'*X11+C22'*X22)
         # mul!(WS21, C12', E11)
         # mul!(WS21, C22', E21, 1, 1)
         mul!(WS21, C22', E21)
         mul!(WS21, C12', E11, 1, 1)
         # E22 = E22 + WS21 * D12
         mul!(E22, WS21, D12, 1, 1)

         # E22 = E22 + A12'*X12*B22
         mul!(WS12, E12, B22)
         mul!(E22, A12', WS12, 1, 1) 
         # E22 = E22 + A12'*X12*B22
         mul!(WS12, E12, D22)
         mul!(E22, C12', WS12, 1, 1) 

         # X22 solver
         _lyapgsylvcs_blocked!(WS22, A22, B22, C22, D22, E22, adj, blocksize)
      else
         # solve A X B' + C X D' + E = 0

         # X22 solver
         _lyapgsylvcs_blocked!(WS22, A22, B22, C22, D22, E22, adj, blocksize)

         # E12 update: E12 = E12 + A12*X22*B22' + C12*X22*D22'
         mul!(WS22,E22,B22')
         mul!(E12,A12,WS22,1,1)
         mul!(WS22,E22,D22')
         mul!(E12,C12,WS22,1,1)

         # X12 solver
         _lyapgsylvcs_blocked!(WS12, A11, B22, C11, D22, E12, adj, blocksize)

         # E21 update: E21 = E21 + A22*X22*B12' + C22*X22*D12'
         mul!(WS22,A22,E22)
         mul!(E21,WS22,B12',1,1)
         mul!(WS22,C22,E22)
         mul!(E21,WS22,D12',1,1)

         # X21 solver
         _lyapgsylvcs_blocked!(WS21, A22, B11, C22, D11, E21, adj, blocksize)


         # E11 update: E11 = E11 + A12*X21*B11' + C12*X21*D11' +
         #                   (A11*X12+A12*X22)*B12' + (C11*X12+C12*X22)*D12'
         # WS12 = A11*X12+A12*X22
         mul!(WS12, A11, E12)
         mul!(WS12, A12, E22, 1, 1)
         # E11 = E11 + WS12 * B12'
         mul!(E11, WS12, B12', 1, 1)
         # WS12 = C11*X12+C12*X22
         mul!(WS12, C11, E12)
         mul!(WS12, C12, E22, 1, 1)
         # E11 = E11 + WS12 * D12'
         mul!(E11, WS12, D12', 1, 1)

         # E11 = E11 + A12*X21*B11'
         mul!(WS21, E21, B11')
         mul!(E11, A12, WS21, 1, 1) 
         # E11 = E11 + C12*X21*D11'
         mul!(WS21, E21, D11')
         mul!(E11, C12, WS21, 1, 1) 

         # X11 solver
         _lyapgsylvcs_blocked!(WS11, A11, B11, C11, D11, E11, adj, blocksize)
      end
   end   
end


"""
    lyapds!(A, C; adj = false)

Solve the discrete Lyapunov matrix equation

                op(A)Xop(A)' - X + C = 0,

where `op(A) = A` if `adj = false` and `op(A) = A'` if `adj = true`.
`A` is in a real or complex Schur form and `C` is a symmetric or hermitian matrix.
`A` must not have eigenvalues `α` and `β` such that `αβ = 1`.
The computed symmetric or hermitian solution `X` is contained in `C`.
"""
function lyapds!(A::AbstractMatrix{T1},C::AbstractMatrix{T1}; adj = false) where {T1<:Real}
   n = LinearAlgebra.checksquare(A)
   (LinearAlgebra.checksquare(C) == n && issymmetric(C)) ||
      throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
   ONE = one(T1)  
   if isdiag(A) 
      for i = 1:n
         C[i,i] = -C[i,i]/(A[i,i]*A[i,i]-ONE)
         isfinite(C[i,i]) || throw("ME:SingularException: A has eigenvalue(s) = 1")
         for j = i+1:n
            C[i,j] = -C[i,j]/(A[i,i]*A[j,j]-ONE)
            isfinite(C[i,j]) || throw("ME:SingularException: A has eigenvalue(s) α and β such that αβ = 1")
            C[j,i] = C[i,j]
         end
      end
      return C[:,:]
   end      

   # determine the dimensions of the diagonal blocks of real Schur form

   G = Matrix{T1}(undef,2,2)
   Xw = Matrix{T1}(undef,4,4)
   Yw = Vector{T1}(undef,4)
   ba, p = sfstruct(A)
   if adj
      # """
      # The (K,L)th block of X is determined starting from the
      # upper-left corner column by column by

      # A(K,K)'*X(K,L)*A(L,L) - X(K,L) = -C(K,L) - R(K,L),

      # where
      #           K           L-1
      # R(K,L) = SUM {A(I,K)'*SUM [X(I,J)*A(J,L)]} +
      #          I=1          J=1

      #           K-1
      #          {SUM [A(I,K)'*X(I,L)]}*A(L,L).
      #           I=1
      # """
      i = 1
      @inbounds  for kk = 1:p
          dk = ba[kk]
          k = i:i+dk-1
          j = 1
          ir = 1:i-1
          for ll = 1:kk
              dl = ba[ll]
              j1 = j+dl-1
              l = j:j1
              Ckl = view(C,k,l)
              y = view(G,1:dk,1:dl)
              copyto!(y,Ckl)
              if kk > 1
                # C[l,k] = C[l,ir]*A[ir,k]
                 mul!(view(C,l,k),view(C,l,ir),view(A,ir,k))
                 ic = 1:j1
                 #y += C[ic,k]'*A[ic,l]
                 mul!(y,transpose(view(C,ic,k)),view(A,ic,l),ONE,ONE)
              end
              if kk == ll
                 lyapd2!(adj,y,dk,view(A,k,k),Xw,Yw)
                 copyto!(Ckl,y)
              else
                 lyapdsylv2!(adj,y,dk,dl,view(A,k,k),view(A,l,l),Xw,Yw)
                 copyto!(Ckl,y)
              end
              j += dl
              if ll < kk
                 # C[l,k] += C[k,l]'*A[k,k]
                 mul!(view(C,l,k),transpose(Ckl),view(A,k,k),ONE,ONE)
               end
          end
          if kk > 1
             # C[ir,k] = C[k,ir]'
             transpose!(view(C,ir,k),view(C,k,ir))
          end
          i += dk
      end
   else
      # """
      # The (K,L)th block of X is determined starting from
      # bottom-right corner column by column by

      # A(K,K)*X(K,L)*A(L,L)' - X(K,L) = -C(K,L) - R(K,L),

      # where

      #           N            N
      # R(K,L) = SUM {A(K,I)* SUM [X(I,J)*A(L,J)']} +
      #          I=K         J=L+1

      #             N
      #          { SUM [A(K,J)*X(J,L)]}*A(L,L)'
      #           J=K+1
      # """
      j = n
      for ll = p:-1:1
          dl = ba[ll]
          l = j-dl+1:j
          i = n
          ir = j+1:n
          for kk = p:-1:ll
              dk = ba[kk]
              i1 = i-dk+1
              k = i1:i
              Clk = view(C,l,k)
              y = view(G,1:dl,1:dk)
              copyto!(y,Clk)
              if ll < p
                 # C[k,l] = C[k,ir]*A[l,ir]'
                 mul!(view(C,k,l),view(C,k,ir),transpose(view(A,l,ir)))
                 ic = i1:n
                 # y += (A[k,ic]*C[ic,l])'
                 mul!(y,transpose(view(C,ic,l)),transpose(view(A,k,ic)),ONE,ONE)
              end
              if i == j
                 lyapd2!(adj,y,dk,view(A,k,k),Xw,Yw)
                 copyto!(Clk,y)
              else
                 lyapdsylv2!(adj,y,dl,dk,view(A,l,l),view(A,k,k),Xw,Yw)
                 copyto!(Clk,y)
              end
              i -= dk
              if i >= j
                 #C[k,l] += (A[l,l]*C[l,k])'
                 mul!(view(C,k,l),transpose(Clk),transpose(view(A,l,l)),ONE,ONE)
              else
                 break
              end
          end
          if ll < p
             ir = i+2:n
             # C[ir,l] = C[l,ir]'
             transpose!(view(C,ir,l),view(C,l,ir))
          end
          j -= dl
      end
   end
end

function lyapd2!(adj,C::AbstractMatrix{T},na::Int,A::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
   # speed and reduced allocation oriented implementation of a solver for 1x1 or 2x2 continuous Lyapunov equations
   #      A'*X*A - X = -C if adj = true  -> R = kron(A',A')-I = (kron(A,A)-I)'
   #      A*X*A' - X = -C if adj = false -> R = kron(A,A)-I
   MONE = -one(T)
   if na == 1
      temp = A[1,1]^2+MONE
      rmul!(C,inv(-temp))
      any(!isfinite, C) && throw("ME:SingularException: A has eigenvalue(s) with moduli ≈ 1")
      return C
   end
   TWO = 2*one(T)
   @inbounds  Y[1] = -C[1,1]
   @inbounds  Y[2] = -C[2,1]
   @inbounds  Y[3] = -C[2,2]
   if adj
      # Rt =
      # [ a11^2-1              2*a11*a21      a21^2]
      # [   a11*a12  a11*a22+a12*a21-1    a21*a22]
      # [     a12^2              2*a12*a22  a22^2-1]
      # @inbounds R = [ A[1,1]^2+MONE    TWO*A[1,1]*A[2,1]         A[2,1]^2;
      #                 A[1,1]*A[1,2]    A[1,1]*A[2,2]+A[1,2]*A[2,1]+MONE  A[2,1]*A[2,2];
      #                 A[1,2]^2         TWO*A[1,2]*A[2,2]         A[2,2]^2+MONE ]
      @inbounds  R[1,1] = A[1,1]^2+MONE
      @inbounds  R[1,2] = TWO*A[1,1]*A[2,1]
      @inbounds  R[1,3] = A[2,1]^2
      @inbounds  R[2,1] = A[1,1]*A[1,2]
      @inbounds  R[2,2] = A[1,1]*A[2,2]+A[1,2]*A[2,1]+MONE
      @inbounds  R[2,3] = A[2,1]*A[2,2]
      @inbounds  R[3,1] = A[1,2]^2
      @inbounds  R[3,2] = TWO*A[1,2]*A[2,2]
      @inbounds  R[3,3] = A[2,2]^2+MONE
   else
      # R =
      # [ a11^2-1              2*a11*a12      a12^2]
      # [   a11*a21  a11*a22+a12*a21-1    a12*a22]
      # [     a21^2              2*a21*a22  a22^2-1]

      # @inbounds R = [ A[1,1]^2+MONE    TWO*A[1,1]*A[1,2]         A[1,2]^2;
      #                 A[1,1]*A[2,1]    A[1,1]*A[2,2]+A[1,2]*A[2,1]+MONE  A[1,2]*A[2,2];
      #                 A[2,1]^2         TWO*A[2,1]*A[2,2]         A[2,2]^2+MONE ]
      @inbounds  R[1,1] = A[1,1]^2+MONE
      @inbounds  R[1,2] = TWO*A[1,1]*A[1,2]
      @inbounds  R[1,3] = A[1,2]^2
      @inbounds  R[2,1] = A[1,1]*A[2,1]
      @inbounds  R[2,2] = A[1,1]*A[2,2]+A[1,2]*A[2,1]+MONE
      @inbounds  R[2,3] = A[1,2]*A[2,2]
      @inbounds  R[3,1] = A[2,1]^2
      @inbounds  R[3,2] = TWO*A[2,1]*A[2,2]
      @inbounds  R[3,3] = A[2,2]^2+MONE
   end
   luslv!(R,Y,3) && throw("ME:SingularException: A has eigenvalues α and β such that αβ ≈ 1")
   @inbounds C[1,1] = Y[1]
   @inbounds C[1,2] = Y[2]
   @inbounds C[2,1] = Y[2]
   @inbounds C[2,2] = Y[3]
   return C
end
function lyapdsylv2!(adj::Bool,C::AbstractMatrix{T},na::Int,nb::Int,A::AbstractMatrix{T},B::AbstractMatrix{T},R::AbstractMatrix{T},Y::AbstractVector{T}) where T <: Real
#    # speed and reduced allocation oriented implementation of a solver for 1x1 and 2x2 Sylvester equations
#    # encountered in solving discrete Lyapunov equations:
#    #      A'*X*B - X = -C if adj = true  -> R = kron(B',A') - I = (kron(B,A)-I)'
#    #      A*X*B' - X = -C if adj = false -> R = kron(B,A)-I

   MONE = -one(T)
   if na == 1 && nb == 1
      temp = A[1,1]*B[1,1] + MONE
      rmul!(C,inv(-temp))
      any(!isfinite, C) && throw("ME:SingularException: A has eigenvalues α and β such that αβ ≈ 1")
      return C
   end
   if adj
      # @inbounds  R = kron(transpose(B),transpose(A)) - I
      if na == 1
         # R12t =
         # [ a11*b11-1      a11*b21]
         # [     a11*b12  a11*b22-1]
         # @inbounds R = [ A[1,1]*B[1,1]+MONE      A[1,1]*B[2,1];
         #                 A[1,1]*B[1,2]  A[1,1]*B[2,2]+MONE]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+MONE
         @inbounds  R[1,2] = A[1,1]*B[2,1]
         @inbounds  R[2,1] = A[1,1]*B[1,2]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+MONE
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
   else
         if nb == 1
            # R21t =
            # [ a11*b11-1      a21*b11]
            # [     a12*b11  a22*b11-1]
            # @inbounds R = [ A[1,1]*B[1,1]+MONE      A[2,1]*B[1,1];
            #                 A[1,2]*B[1,1]  A[2,2]*B[1,1]+MONE ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+MONE
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+MONE
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # Rt =
            # [ a11*b11-1      a21*b11      a11*b21      a21*b21]
            # [     a12*b11  a22*b11-1      a12*b21      a22*b21]
            # [     a11*b12      a21*b12  a11*b22-1      a21*b22]
            # [     a12*b12      a22*b12      a12*b22  a22*b22-1]
            # @inbounds R = [ A[1,1]*B[1,1]+MONE      A[2,1]*B[1,1]      A[1,1]*B[2,1]      A[2,1]*B[2,1];
            # A[1,2]*B[1,1]  A[2,2]*B[1,1]+MONE      A[1,2]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]      A[2,1]*B[1,2]  A[1,1]*B[2,2]+MONE      A[2,1]*B[2,2];
            # A[1,2]*B[1,2]      A[2,2]*B[1,2]      A[1,2]*B[2,2]  A[2,2]*B[2,2]+MONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+MONE
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[2,1]
            @inbounds  R[1,4] = A[2,1]*B[2,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+MONE
            @inbounds  R[2,3] = A[1,2]*B[2,1]
            @inbounds  R[2,4] = A[2,2]*B[2,1]
            @inbounds  R[3,1] = A[1,1]*B[1,2]
            @inbounds  R[3,2] = A[2,1]*B[1,2]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+MONE
            @inbounds  R[3,4] = A[2,1]*B[2,2]
            @inbounds  R[4,1] = A[1,2]*B[1,2]
            @inbounds  R[4,2] = A[2,2]*B[1,2]
            @inbounds  R[4,3] = A[1,2]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+MONE
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   else
      # @inbounds  R = kron(B,A) - I
      if na == 1
         # R12 =
         # [ a11*b11-1      a11*b12]
         # [     a11*b21  a11*b22-1]
         # @inbounds R = [ A[1,1]*B[1,1]+MONE      A[1,1]*B[1,2];
         #                 A[1,1]*B[2,1]  A[1,1]*B[2,2]+MONE]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+MONE
         @inbounds  R[1,2] = A[1,1]*B[1,2]
         @inbounds  R[2,1] = A[1,1]*B[2,1]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+MONE
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
   else
         if nb == 1
            # R21 =
            #    [ a11*b11-1      a12*b11]
            #    [     a21*b11  a22*b11-1]
            # @inbounds R = [ A[1,1]*B[1,1]+MONE      A[1,2]*B[1,1];
            #                 A[2,1]*B[1,1]  A[2,2]*B[1,1]+MONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+MONE
            @inbounds  R[1,2] = A[1,2]*B[1,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+MONE
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # R =
            # [ a11*b11-1      a12*b11      a11*b12      a12*b12]
            # [     a21*b11  a22*b11-1      a21*b12      a22*b12]
            # [     a11*b21      a12*b21  a11*b22-1      a12*b22]
            # [     a21*b21      a22*b21      a21*b22  a22*b22-1]
            # @inbounds R = [ A[1,1]*B[1,1]+MONE      A[1,2]*B[1,1]      A[1,1]*B[1,2]      A[1,2]*B[1,2];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]+MONE      A[2,1]*B[1,2]      A[2,2]*B[1,2];
            # A[1,1]*B[2,1]      A[1,2]*B[2,1]  A[1,1]*B[2,2]+MONE      A[1,2]*B[2,2];
            # A[2,1]*B[2,1]      A[2,2]*B[2,1]      A[2,1]*B[2,2]  A[2,2]*B[2,2]+MONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+MONE
            @inbounds  R[1,2] = A[1,2]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[1,2]
            @inbounds  R[1,4] = A[1,2]*B[1,2]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+MONE
            @inbounds  R[2,3] = A[2,1]*B[1,2]
            @inbounds  R[2,4] = A[2,2]*B[1,2]
            @inbounds  R[3,1] = A[1,1]*B[2,1]
            @inbounds  R[3,2] = A[1,2]*B[2,1]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+MONE
            @inbounds  R[3,4] = A[1,2]*B[2,2]
            @inbounds  R[4,1] = A[2,1]*B[2,1]
            @inbounds  R[4,2] = A[2,2]*B[2,1]
            @inbounds  R[4,3] = A[2,1]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+MONE
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   end
   luslv!(R,Y,na*nb) && throw("ME:SingularException: A-λE has eigenvalues α and β such that αβ ≈ 1")
   if na == 1 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[1,2] = Y[2]
   elseif na == 2 && nb == 1
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
   else # na == 2 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
      @inbounds C[1,2] = Y[3]
      @inbounds C[2,2] = Y[4]
   end
return C
end
function lyapds_blocked!(A::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adj = false, blocksize::Integer = 64) where {T1<:BlasFloat}
    n = LinearAlgebra.checksquare(A)  
    n == LinearAlgebra.checksquare(C) 
    mid = n ÷ 2
    if T1 <: Real  
       # Check symmetry ONCE here
       issymmetric(C) ||  throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
       mid += 2
    else
       ishermitian(C) ||  throw(DimensionMismatch("C must be a $n x $n Hermitian matrix"))
       mid += 1
    end
    
    # Allocate a full-sized WS and half-sized WS1 once. 
    WS = Matrix{T1}(undef,n,n)
    WS1 = Matrix{T1}(undef,mid,mid)
    _lyapds_blocked!(WS, WS1, A, C, adj, blocksize)

    # Fill the lower triangle ONCE at the very end
    return LinearAlgebra.copytri!(C, 'U', T1 <: Complex)
end
function _lyapds_blocked!(WS::AbstractMatrix{T1},WS1::AbstractMatrix{T1},A::AbstractMatrix{T1},C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasReal}
   n = size(A, 1)
   ONE = one(T1)
   ZERO = zero(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U',T1 <: Complex)
      lyapds!(A,C;adj)
   else
      # split A and C (by rows and columns)
      mid = n ÷ 2
      n1 =  A[mid+1, mid] != 0 ? mid + 1 : mid
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
         WS11, WS12, WS22 = WS[i1,i1], WS[i1,i2], WS[i2,i2]
      end
      if adj
         # Adjoint Case: A' X A - X + C = 0

         # C11 solver
         _lyapds_blocked!(WS11,WS1,A11,C11,adj,blocksize)

         # Update C12: C12 = C12 + A11' * C11 * A12
         # Use WS as a temporary for (C11 * A12)
         # Note: C11 is symmetric/hermitian         
         BLAS.symm!('L', 'U', ONE, C11, A12, ZERO, WS12)
         BLAS.gemm!('T', 'N', ONE, A11, WS12, ONE, C12)         

         # Solve discrete Sylvester: A11' X12 A22 - X12 + C12 = 0
         lyapsylvds_blocked!(WS1, A11, A22, C12; adj, isgn = -1, blocksize)

         # Update C22: C22 = C22 + A12' * C11 * A12 + A22' * X12' * A12 + A12' * X12 * A22
         # 1. C22 = C22 + A12' * (C11 * A12)  [The WS we calculated earlier]         
         BLAS.gemm!('T', 'N', ONE, A12, WS12, ONE, C22)
         # 2. C22 = C22 + (X12' * A11' * A12 ...) which simplifies to a rank-2k update
         # Using syr2k! with A12 and (X12 * A22)         
         BLAS.gemm!('N', 'N', ONE, C12, A22, ZERO, WS12)
         BLAS.syr2k!('U', 'T', ONE, A12, WS12, ONE, C22)

         # C22 solver
         _lyapds_blocked!(WS22, WS1, A22, C22, adj, blocksize)

      else
         # Standard Case: A X A' - X + C = 0

         # X22 solver
         _lyapds_blocked!(WS22, WS1, A22, C22, adj, blocksize)
         
         # Update C12: C12 = C12 + A12 * C22 * A22'
         # Use WS[i1, i2] for (A12 * C22)
         BLAS.symm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         BLAS.gemm!('N', 'T', ONE, WS12, A22, ONE, C12)

         # Solve discrete Sylvester: A11 * X12 * A22' - X12 + C12 = 0
         lyapsylvds_blocked!(WS1, A11, A22, C12; adj, isgn = -1, blocksize)

         # Update C11: C11 = C11 + A12*X22*A12' + A11*X12*A12' + A12*X12'*A11'
         # 1. C11 = C11 + (A12*C22) * A12'  [WS contains A12*C22]
         BLAS.gemm!('N', 'T', ONE, WS12, A12, ONE, C11)
    
         # 2. C11 = C11 + (A11*X12)*A12' + A12*(A11*X12)' -> syr2k
         BLAS.gemm!('N', 'N', ONE, A11, C12, ZERO, WS12)
         BLAS.syr2k!('U', 'N', ONE, WS12, A12, ONE, C11)

         # X11 solver
         _lyapds_blocked!(WS11, WS1, A11, C11, adj, blocksize)
      end
   end
end

function _lyapds_blocked!(WS::AbstractMatrix{T1},WS1::AbstractMatrix{T1},A::AbstractMatrix{T1},C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasComplex}
   n = size(A, 1)
   ONE = one(T1)
   RONE = real(ONE)
   ZERO = zero(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U',T1 <: Complex)
      lyapds!(A,C;adj)
   else
      # split A and C (by rows and columns)
      n1 = n ÷ 2
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
         WS11, WS12, WS22 = WS[i1,i1], WS[i1,i2], WS[i2,i2]
      end
      if adj
         # Adjoint Case: A' X A - X + C = 0

         # C11 solver
         _lyapds_blocked!(WS11,WS1,A11,C11,adj,blocksize)

         # Update C12: C12 = C12 + A11' * C11 * A12
         # Use WS as a temporary for (C11 * A12)
         # Note: C11 is symmetric/hermitian         
         BLAS.hemm!('L', 'U', ONE, C11, A12, ZERO, WS12)
         BLAS.gemm!('C', 'N', ONE, A11, WS12, ONE, C12)         

         # Solve discrete Sylvester: A11' X12 A22 - X12 + C12 = 0
         lyapsylvds_blocked!(WS1, A11, A22, C12; adj, isgn = -1, blocksize)

         # Update C22: C22 = C22 + A12' * C11 * A12 + A22' * X12' * A12 + A12' * X12 * A22
         # 1. C22 = C22 + A12' * (C11 * A12)  [The WS we calculated earlier]         
         BLAS.gemm!('C', 'N', ONE, A12, WS12, ONE, C22)
         # 2. C22 = C22 + (X12' * A11' * A12 ...) which simplifies to a rank-2k update
         # Using syr2k! with A12 and (X12 * A22)         
         BLAS.gemm!('N', 'N', ONE, C12, A22, ZERO, WS12)
         BLAS.her2k!('U', 'C', ONE, A12, WS12, RONE, C22)

         # C22 solver
         _lyapds_blocked!(WS22, WS1, A22, C22, adj, blocksize)

      else
         # Standard Case: A X A' - X + C = 0

         # X22 solver
         _lyapds_blocked!(WS22, WS1, A22, C22, adj, blocksize)
         
         # Update C12: C12 = C12 + A12 * C22 * A22'
         # Use WS[i1, i2] for (A12 * C22)
         BLAS.hemm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         BLAS.gemm!('N', 'C', ONE, WS12, A22, ONE, C12)

         # Solve discrete Sylvester: A11 * X12 * A22' - X12 + C12 = 0
         lyapsylvds_blocked!(WS1, A11, A22, C12; adj, isgn = -1, blocksize)

         # Update C11: C11 = C11 + A12*X22*A12' + A11*X12*A12' + A12*X12'*A11'
         # 1. C11 = C11 + (A12*C22) * A12'  [WS contains A12*C22]
         BLAS.gemm!('N', 'C', ONE, WS12, A12, ONE, C11)
    
         # 2. C11 = C11 + (A11*X12)*A12' + A12*(A11*X12)' -> syr2k
         BLAS.gemm!('N', 'N', ONE, A11, C12, ZERO, WS12)
         BLAS.her2k!('U', 'N', ONE, WS12, A12, RONE, C11)

         # X11 solver
         _lyapds_blocked!(WS11, WS1, A11, C11, adj, blocksize)
      end
   end
end

function lyapsylvds_blocked!(WS::AbstractMatrix{T1}, A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adj = false, isgn = -1, blocksize::Integer = 64) where {T1<:Real}
   m = LinearAlgebra.checksquare(A)  
   n = LinearAlgebra.checksquare(B)
   (m, n) == size(C) || throw(DimensionMismatch("C must be a $m x $n"))
   
   # Call the positional recursive worker
   _lyapsylvds_blocked!(WS, A, B, C, adj, isgn, blocksize)
end
function _lyapsylvds_blocked!(WS, A, B, C, adj::Bool, isgn::Integer, blocksize::Integer)
   m = size(A, 1)
   n = size(B, 1)
   if m <= max(blocksize,4) && n <= max(blocksize,4)
      sylvds!(A,B,C;adjA = adj,adjB = !adj,isgn)
      rmul!(C, -1)
   else
      midm = m ÷ 2
      m1 = A[midm+1, midm] != 0 ? midm + 1 : midm
      ia1 = 1:m1; ia2 = m1+1:m 
      midn = n ÷ 2
      n1 = B[midn+1, midn] != 0 ? midn + 1 : midn
      ib1 = 1:n1; ib2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[ia1,ia1], A[ia1,ia2], A[ia2,ia2]
         B11, B12, B22 = B[ib1,ib1], B[ib1,ib2], B[ib2,ib2]
         C11, C12, C21, C22 = C[ia1,ib1], C[ia1,ib2], C[ia2,ib1], C[ia2,ib2]
         WS11, WS12, WS21, WS22 = WS[ia1,ib1], WS[ia1,ib2], WS[ia2,ib1], WS[ia2,ib2]
      end
      if adj
         # solve A' X B + isgn X + C = 0

         # X11 solver
         _lyapsylvds_blocked!(WS11, A11, B11, C11, adj, isgn, blocksize)

         # C12 update: C12 = C12 + A11'*X11*B12
         mul!(WS11,A11',C11)
         mul!(C12,WS11,B12,1,1)

         # X12 solver
         _lyapsylvds_blocked!(WS12, A11, B22, C12, adj, isgn, blocksize)

         # C21 update: C21 = C21 + A12'*X11*B11
         mul!(WS11,C11,B11)
         mul!(C21,A12',WS11,1,1)

         # X21 solver
         _lyapsylvds_blocked!(WS21, A22, B11, C21, adj, isgn, blocksize)

         # C22 update: C22 = C22 + A12'*X11*B12 + A12'*X12*B22 + A22'*X21*B12

         # WS12 = X11*B12 + X12*B22
         mul!(WS12, C11, B12)
         mul!(WS12, C12, B22, 1, 1)
         # C22 = C22 + A12'*(WS12)
         mul!(C22, A12', WS12, 1, 1)
         # C22 = C22 + A22'*X21*B12
         mul!(WS22, C21, B12)
         mul!(C22, A22', WS22, 1, 1)

         # X22 solver
         _lyapsylvds_blocked!(WS22, A22, B22, C22, adj, isgn, blocksize)
      else
         # solve A X B' + isgn X + C = 0

         # X22 solver
         _lyapsylvds_blocked!(WS22, A22, B22, C22, adj, isgn, blocksize)

         # C12 update: C12 = C12 + A12*X22*B22'
         mul!(WS22,C22,B22')
         mul!(C12,A12,WS22,1,1)

         # X12 solver
         _lyapsylvds_blocked!(WS12, A11, B22, C12, adj, isgn, blocksize)

         # C21 update: C21 = C21 + A22*X22*B12'
         mul!(WS22,A22,C22)
         mul!(C21,WS22,B12',1,1)

         # X21 solver
         _lyapsylvds_blocked!(WS21, A22, B11, C21, adj, isgn, blocksize)

         # C11 update: C11 = C11 + A12*X21*B11' + A11*X12*B12' + A12*X22*B12'
         # WS21 = X21*B11' + X22*B12'
         mul!(WS21, C21, B11')
         mul!(WS21, C22, B12', 1, 1)
         # C11 = C11 + A12*(WS21)
         mul!(C11, A12, WS21, 1, 1)
         # C11 = C11 + A11*X12*B12'
         mul!(WS12, A11, C12)
         mul!(C11, WS12, B12', 1, 1) 
 
         # X11 solver
         _lyapsylvds_blocked!(WS11, A11, B11, C11, adj, isgn, blocksize)
      end
   end   
end

function lyapds_blocked!(A::AbstractMatrix{T1}, E::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adj = false, blocksize::Integer = 64) where {T1<:BlasFloat}
    n = LinearAlgebra.checksquare(A)  
    LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))
    LinearAlgebra.checksquare(C) == n || throw(DimensionMismatch("C must be a $n x $n matrix or I"))
    mid = n ÷ 2
    if T1 <: Real  
       # Check symmetry ONCE here
       issymmetric(C) ||  throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
       mid += 2
    else
       ishermitian(C) ||  throw(DimensionMismatch("C must be a $n x $n Hermitian matrix"))
       mid += 1
    end
    
    # Allocate a full-sized WS and half-sized WS1 once. 
    WS = Matrix{T1}(undef,n,n)
    WS1 = Matrix{T1}(undef,mid,mid)
    WS2 = Matrix{T1}(undef,n,4)
    _lyapds_blocked!(WS, WS1, WS2, A, E, C, adj, blocksize)

    # Fill the lower triangle ONCE at the very end
    return LinearAlgebra.copytri!(C, 'U', T1 <: Complex)
end

function _lyapds_blocked!(WS::AbstractMatrix{T1},WS1::AbstractMatrix{T1},WS2::AbstractMatrix{T1},A::AbstractMatrix{T1},E::AbstractMatrix{T1},
                          C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasReal}
   n = size(A, 1)
   ONE = one(T1)
   MONE = -ONE
   ZERO = zero(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U')
      lyapds!(A,E,C,view(WS2,1:n,1:2);adj)
   else
      # split A, E and C (by rows and columns)
      mid = n ÷ 2
      n1 =  A[mid+1, mid] != 0 ? mid + 1 : mid
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         E11, E12, E22 = E[i1,i1], E[i1,i2], E[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
         WS11, WS12, WS22 = WS[i1,i1], WS[i1,i2], WS[i2,i2]
      end
      if adj
         # Adjoint Case: A' X A - E' X E + C = 0  
         
         # X11 solver
         _lyapds_blocked!(WS11, WS1, WS2, A11, E11, C11, adj, blocksize)

         # Update C12 = C12 + A11' * X11 * A12 - E11' * X11 * E12
         BLAS.symm!('L', 'U', ONE, C11, A12, ZERO, WS12)
         BLAS.gemm!('T', 'N', ONE, A11, WS12, ONE, C12)
    
         BLAS.symm!('L', 'U', ONE, C11, E12, ZERO, WS12)
         BLAS.gemm!('T', 'N', MONE, E11, WS12, ONE, C12)    

         # Solve generalized dual Sylvester: A11' * X12 * A22 - E11' * X12 * E22 + C12 = 0 
         lyapgsylvds_blocked!(WS1, WS2, A11, A22, E11, E22, C12; adj, blocksize)

         # Update C22 = C22 + (X12*A22)'*A12 + A12'*(X12*A22)
         # 1. WS12 = C12*A22   
         mul!(WS12, C12, A22)
         # 2. C22 = C22 + WS12'*A12 + A12'*WS12 -> syr2k
         BLAS.syr2k!('U', 'T', ONE, WS12, A12, ONE, C22)

         # 2. WS12 = C12*E22   
         mul!(WS12, C12, E22)
         # 2. C22 = C22 - WS12'*E12 - E12'*WS12 -> syr2k
         BLAS.syr2k!('U', 'T', MONE, WS12, E12, ONE, C22)

         # 3. C22 = C22 + A12'*(X11*A12) - E12'*(X11*E12) 
          utqu_upd!(ONE, ONE, C22, A12', C11, WS12)
         # BLAS.symm!('L', 'U', ONE, C11, A12, ZERO, WS12)
         # BLAS.gemm!('T', 'N', ONE, A12, WS12, ONE, C22)
          utqu_upd!(ONE, MONE, C22, E12', C11, WS12)
         # BLAS.symm!('L', 'U', ONE, C11, E12, ZERO, WS12)
         # BLAS.gemm!('T', 'N', MONE, E12, WS12, ONE, C22)

         # X22 solver
         _lyapds_blocked!(WS22, WS1, WS2, A22, E22, C22, adj, blocksize)
      else
         # Standard Case: A X A' - E X E' + C = 0

         # X22 solver
         _lyapds_blocked!(WS22, WS1, WS2, A22, E22, C22, adj, blocksize)

         # Update C12: C12 = C12 + A12 * X22 * A22' - E12 * X22 * E22'
         # Use WS[i1, i2] for (A12 * C22)
         BLAS.symm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         BLAS.gemm!('N', 'T', ONE, WS12, A22, ONE, C12)
         # Use WS[i1, i2] for (E12 * C22)
         BLAS.symm!('R', 'U', ONE, C22, E12, ZERO, WS12)
         BLAS.gemm!('N', 'T', MONE, WS12, E22, ONE, C12)

         # Solve generalized Sylvester: A11 * X12 * A22' - E11 X12 E22' + C12 = 0
         lyapgsylvds_blocked!(WS1, WS2, A11, A22, E11, E22, C12; adj, blocksize)

         # Update C11: C11 = C11 + (A11*X12)*A12' + A12*(A11*X12)'
         # 1. WS12 = A11*C12   
         BLAS.gemm!('N', 'N', ONE, A11, C12, ZERO, WS12)
         # 2. C11 = C11 + WS12*A12' + A12*WS12' -> syr2k
         BLAS.syr2k!('U', 'N', ONE, WS12, A12, ONE, C11)

         # Update C11: C11 = C11 - (E11*X12)*E12' - E12*(E11*X12)'
         # 1. WS12 = E11*C12   
         BLAS.gemm!('N', 'N', ONE, E11, C12, ZERO, WS12)
         # 2. C11 = C11 - WS12*E12' - E12*WS12' -> syr2k
         BLAS.syr2k!('U', 'N', MONE, WS12, E12, ONE, C11)
  
         # 3. C11 = C11 + (A12*X22)*A12' - E12*(E12*X22)' 
          utqu_upd!(ONE, ONE, C11, A12, C22, WS12)
         # BLAS.symm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         # BLAS.gemm!('N', 'T', ONE, WS12, A12, ONE, C11)
          utqu_upd!(ONE, MONE, C11, E12, C22, WS12)
         # BLAS.symm!('R', 'U', ONE, C22, E12, ZERO, WS12)
         # BLAS.gemm!('N', 'T', MONE, WS12, E12, ONE, C11)

         # X11 solver
         _lyapds_blocked!(WS11, WS1, WS2, A11, E11, C11, adj, blocksize)
      end
   end
end
function _lyapds_blocked!(WS::AbstractMatrix{T1},WS1::AbstractMatrix{T1},WS2::AbstractMatrix{T1},A::AbstractMatrix{T1},E::AbstractMatrix{T1},
                          C::AbstractMatrix{T1}, adj, blocksize) where {T1<:BlasComplex}
   n = size(A, 1)
   ONE = one(T1)
   RONE = real(ONE)
   MONE = -ONE
   ZERO = zero(T1)
   if n <= max(blocksize,4)
      LinearAlgebra.copytri!(C,'U',true)
      # fix for small nonzero imaginary values!!! an alternative is to use better updating 
      #ishermitian(C) || (for i in 1:size(C, 1);  C[i, i] = Complex(real(C[i, i]), 0.0); end)
      lyapds!(A,E,C,view(WS2,1:n,1);adj)
   else
      # split A, E and C (by rows and columns)
      n1 = n ÷ 2
      i1 = 1:n1; i2 = n1+1:n 
      @views begin
         A11, A12, A22 = A[i1,i1], A[i1,i2], A[i2,i2]
         E11, E12, E22 = E[i1,i1], E[i1,i2], E[i2,i2]
         C11, C12, C22 = C[i1,i1], C[i1,i2], C[i2,i2]
         WS11, WS12, WS22, WS21 = WS[i1,i1], WS[i1,i2], WS[i2,i2], WS[i2,i1]
      end
      if adj
         # Adjoint Case: A' X A - E' X E + C = 0  
         
         # X11 solver
         _lyapds_blocked!(WS11, WS1, WS2, A11, E11, C11, adj, blocksize)

         # Update C12 = C12 + A11' * X11 * A12 - E11' * X11 * E12
         BLAS.hemm!('L', 'U', ONE, C11, A12, ZERO, WS12)
         BLAS.gemm!('C', 'N', ONE, A11, WS12, ONE, C12)
    
         BLAS.hemm!('L', 'U', ONE, C11, E12, ZERO, WS12)
         BLAS.gemm!('C', 'N', MONE, E11, WS12, ONE, C12)    

         # Solve generalized dual Sylvester: A11' * X12 * A22 - E11' * X12 * E22 + C12 = 0 
         lyapgsylvds_blocked!(WS1, WS2, A11, A22, E11, E22, C12; adj, blocksize)

         # Update C22 = C22 + (X12*A22)'*A12 + A12'*(X12*A22)
         # 1. WS12 = C12*A22   
         mul!(WS12, C12, A22)
         # 2. C22 = C22 + WS12'*A12 + A12'*WS12 -> her2k
         BLAS.her2k!('U', 'C', ONE, WS12, A12, RONE, C22)

         # 2. WS12 = C12*E22   
         mul!(WS12, C12, E22)
         # 2. C22 = C22 - WS12'*E12 - E12'*WS12 -> her2k
         BLAS.her2k!('U', 'C', MONE, WS12, E12, RONE, C22)

         # 3. C22 = C22 + A12'*(X11*A12) - E12'*(X11*E12) 
          utqu_upd!(RONE, RONE, C22, A12', C11, WS12)
         # BLAS.hemm!('L', 'U', ONE, C11, A12, ZERO, WS12)
         # BLAS.gemm!('C', 'N', ONE, A12, WS12, ONE, C22)
          utqu_upd!(RONE, -RONE, C22, E12', C11, WS12)
         # BLAS.hemm!('L', 'U', ONE, C11, E12, ZERO, WS12)
         # BLAS.gemm!('C', 'N', MONE, E12, WS12, ONE, C22)

         # X22 solver
         _lyapds_blocked!(WS22, WS1, WS2, A22, E22, C22, adj, blocksize)
      else
         # Standard Case: A X A' - E X E' + C = 0

         # X22 solver
         _lyapds_blocked!(WS22, WS1, WS2, A22, E22, C22, adj, blocksize)

         # Update C12: C12 = C12 + A12 * X22 * A22' - E12 * X22 * E22'
         # Use WS[i1, i2] for (A12 * C22)
         BLAS.hemm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         BLAS.gemm!('N', 'C', ONE, WS12, A22, ONE, C12)
         # Use WS[i1, i2] for (E12 * C22)
         BLAS.hemm!('R', 'U', ONE, C22, E12, ZERO, WS12)
         BLAS.gemm!('N', 'C', MONE, WS12, E22, ONE, C12)

         # Solve generalized Sylvester: A11 * X12 * A22' - E11 X12 E22' + C12 = 0
         lyapgsylvds_blocked!(WS1, WS2, A11, A22, E11, E22, C12; adj, blocksize)

         # Update C11: C11 = C11 + (A11*X12)*A12' + A12*(A11*X12)'
         # 1. WS12 = A11*C12   
         BLAS.gemm!('N', 'N', ONE, A11, C12, ZERO, WS12)
         # 2. C11 = C11 + WS12*A12' + A12*WS12' -> syr2k
         BLAS.her2k!('U', 'N', ONE, WS12, A12, RONE, C11)

         # Update C11: C11 = C11 - (E11*X12)*E12' - E12*(E11*X12)'
         # 1. WS12 = E11*C12   
         BLAS.gemm!('N', 'N', ONE, E11, C12, ZERO, WS12)
         # 2. C11 = C11 - WS12*E12' - E12*WS12' -> syr2k
         BLAS.her2k!('U', 'N', MONE, WS12, E12, RONE, C11)
  
         # 3. C11 = C11 + (A12*X22)*A12' - E12*(E12*X22)' 
          utqu_upd!(RONE, RONE, C11, A12, C22, WS12)
         # BLAS.hemm!('R', 'U', ONE, C22, A12, ZERO, WS12)
         # BLAS.gemm!('N', 'C', ONE, WS12, A12, ONE, C11)
          utqu_upd!(RONE, -RONE, C11, E12, C22, WS12)
         #  BLAS.hemm!('R', 'U', ONE, C22, E12, ZERO, WS12)
         # BLAS.gemm!('N', 'C', MONE, WS12, E12, ONE, C11)
         # LinearAlgebra.copytri!(C11,'U',true)
         # @show ishermitian(C11)

         # X11 solver
         _lyapds_blocked!(WS11, WS1, WS2, A11, E11, C11, adj, blocksize)
      end
   end
end



function lyapsylvds_blocked!(WS::AbstractMatrix{T1}, A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adj = false, isgn = -1, blocksize::Integer = 64) where {T1<:BlasComplex}
   m = LinearAlgebra.checksquare(A)  
   n = LinearAlgebra.checksquare(B)
   (m,n) == size(C) || throw(DimensionMismatch("C must be a $m x $n"))
   if m <= max(blocksize,4) && n <= max(blocksize,4)
      sylvds!(A,B,C;adjA = adj,adjB = !adj,isgn)
      rmul!(C, -1)
   else
      m1 = m ÷ 2
      ia1 = 1:m1; ia2 = m1+1:m 
      n1 = n ÷ 2
      ib1 = 1:n1; ib2 = n1+1:n 
      @views begin
         A12 = A[ia1,ia2]
         B12 = B[ib1,ib2]
         # A11, A12, A22 = A[ia1,ia1], A[ia1,ia2], A[ia2,ia2]
         # B11, B12, B22 = B[ib1,ib1], B[ib1,ib2], B[ib2,ib2]
         # Exploit upper-triangular shape of diagonal sub-blocks via type wrapping
         A11 = UpperTriangular(A[ia1,ia1])
         A22 = UpperTriangular(A[ia2,ia2])
         B11 = UpperTriangular(B[ib1,ib1])
         B22 = UpperTriangular(B[ib2,ib2])
         C11, C12, C21, C22 = C[ia1,ib1], C[ia1,ib2], C[ia2,ib1], C[ia2,ib2]
         WS11, WS12, WS21, WS22 = WS[ia1,ib1], WS[ia1,ib2], WS[ia2,ib1], WS[ia2,ib2]
      end
      if adj
         # solve A' X B + isgn X + C = 0

         # X11 solver
         lyapsylvds_blocked!(WS11, A11, B11, C11; adj, isgn, blocksize)

         # C12 update: C12 = C12 + A11'*X11*B12
         mul!(WS11,A11',C11)
         mul!(C12,WS11,B12,1,1)

         # X12 solver
         lyapsylvds_blocked!(WS12, A11, B22, C12; adj, isgn, blocksize)

         # C21 update: C21 = C21 + A12'*X11*B11
         mul!(WS11,C11,B11)
         mul!(C21,A12',WS11,1,1)

         # X21 solver
         lyapsylvds_blocked!(WS21, A22, B11, C21; adj, isgn, blocksize)

         # C22 update: C22 = C22 + A12'*X11*B12 + A12'*X12*B22 + A22'*X21*B12

         # WS12 = X11*B12 + X12*B22
         mul!(WS12, C11, B12)
         mul!(WS12, C12, B22, 1, 1)
         # C22 = C22 + A12'*(WS12)
         mul!(C22, A12', WS12, 1, 1)
         # C22 = C22 + A22'*X21*B12
         mul!(WS22, C21, B12)
         mul!(C22, A22', WS22, 1, 1)

         # X22 solver
         lyapsylvds_blocked!(WS22, A22, B22, C22; adj, isgn, blocksize)
      else
         # solve A X B' + isgn X + C = 0

         # X22 solver
         lyapsylvds_blocked!(WS22, A22, B22, C22; adj, isgn, blocksize)

         # C12 update: C12 = C12 + A12*X22*B22'
         mul!(WS22,C22,B22')
         mul!(C12,A12,WS22,1,1)

         # X12 solver
         lyapsylvds_blocked!(WS12, A11, B22, C12; adj, isgn, blocksize)

         # C21 update: C21 = C21 + A22*X22*B12'
         mul!(WS22,A22,C22)
         mul!(C21,WS22,B12',1,1)

         # X21 solver
         lyapsylvds_blocked!(WS21, A22, B11, C21; adj, isgn, blocksize)

         # C11 update: C11 = C11 + A12*X21*B11' + A11*X12*B12' + A12*X22*B12'

         # WS21 = X21*B11' + X22*B12'
         mul!(WS21, C21, B11')
         mul!(WS21, C22, B12', 1, 1)
         # C11 = C11 + A12*(WS21)
         mul!(C11, A12, WS21, 1, 1)
         # C11 = C11 + A11*X12*B12'
         mul!(WS12, A11, C12)
         mul!(C11, WS12, B12', 1, 1) 

         # X11 solver
         lyapsylvds_blocked!(WS11, A11, B11, C11; adj, isgn, blocksize)
      end
   end   
end

function lyapgsylvds_blocked!(WS::AbstractMatrix{T1}, WS2::AbstractMatrix{T1}, A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}, D::AbstractMatrix{T1}, E::AbstractMatrix{T1}; adj = false, blocksize::Integer = 64) where {T1<:BlasFloat}
   m = LinearAlgebra.checksquare(A)  
   n = LinearAlgebra.checksquare(B)
   (m, n) == size(E) || throw(DimensionMismatch("E must be a $m x $n"))
   
   # Call the positional recursive worker
   _lyapgsylvds_blocked!(WS, WS2, A, B, C, D, E, adj, blocksize)
end
function _lyapgsylvds_blocked!(WS, WS2, A, B, C, D, E, adj::Bool, blocksize::Integer)
   m = size(A, 1)
   n = size(B, 1)
   if m <= max(blocksize,4) && n <= max(blocksize,4)
      if eltype(A) <: Real
         WB = view(WS2,1:m,1:2); WD = view(WS2,1:m,3:4);   
      else
         WB = view(WS2,1:m,1); WD = view(WS2,1:m,2);   
      end
      gsylvs!(A,B,C,D,E,WB,WD;adjAC = adj,adjBD = !adj,isgn = -1)
      rmul!(E, -1)
  else
      midm = m ÷ 2
      m1 = A[midm+1, midm] != 0 ? midm + 1 : midm
      ia1 = 1:m1; ia2 = m1+1:m 
      midn = n ÷ 2
      n1 = B[midn+1, midn] != 0 ? midn + 1 : midn
      ib1 = 1:n1; ib2 = n1+1:n;  
      if eltype(A) <: Real
         @views begin
            A11, A12, A22 = A[ia1,ia1], A[ia1,ia2], A[ia2,ia2]
            B11, B12, B22 = B[ib1,ib1], B[ib1,ib2], B[ib2,ib2]
            C11, C12, C22 = UpperTriangular(C[ia1,ia1]), C[ia1,ia2], UpperTriangular(C[ia2,ia2])
            D11, D12, D22 = UpperTriangular(D[ib1,ib1]), D[ib1,ib2], UpperTriangular(D[ib2,ib2])
            E11, E12, E21, E22 = E[ia1,ib1], E[ia1,ib2], E[ia2,ib1], E[ia2,ib2]
            WS11, WS12, WS21, WS22 = WS[ia1,ib1], WS[ia1,ib2], WS[ia2,ib1], WS[ia2,ib2]
         end
      else
         @views begin
            A11, A12, A22 = UpperTriangular(A[ia1,ia1]), A[ia1,ia2], UpperTriangular(A[ia2,ia2])
            B11, B12, B22 = UpperTriangular(B[ib1,ib1]), B[ib1,ib2], UpperTriangular(B[ib2,ib2])
            C11, C12, C22 = UpperTriangular(C[ia1,ia1]), C[ia1,ia2], UpperTriangular(C[ia2,ia2])
            D11, D12, D22 = UpperTriangular(D[ib1,ib1]), D[ib1,ib2],  UpperTriangular(D[ib2,ib2])
            E11, E12, E21, E22 = E[ia1,ib1], E[ia1,ib2], E[ia2,ib1], E[ia2,ib2]
            WS11, WS12, WS21, WS22 = WS[ia1,ib1], WS[ia1,ib2], WS[ia2,ib1], WS[ia2,ib2]
         end
      end
      if adj
         # solve A' X B - C' X D + E = 0

         # X11 solver
         _lyapgsylvds_blocked!(WS11, WS2, A11, B11, C11, D11, E11, adj, blocksize)

         # E12 update: E12 = E12 + A11'*X11*B12 - C11'*X11*D12
         mul!(WS11, A11', E11)
         mul!(E12, WS11, B12, 1, 1)
         mul!(WS11, C11', E11)
         mul!(E12, WS11, D12, -1, 1)
 
         # X12 solver
         _lyapgsylvds_blocked!(WS12, WS2, A11, B22, C11, D22, E12, adj, blocksize)

         # E21 update: E21 = E21 + A12'*X11*B11 - C12'*X11*D11
         #mul!(WS11,E11, UpperTriangular(B11))
         mul!(WS11,E11, B11)
         mul!(E21,A12',WS11,1,1)
         mul!(WS11, E11, D11)
         mul!(E21, C12', WS11,-1,1)

         # X21 solver
         _lyapgsylvds_blocked!(WS21, WS2, A22, B11, C22, D11, E21, adj, blocksize)

         # E22 update: E22 = E22 + A12'*X12*B22 - C12'*X12*D22 +
         #                   (A12'*X11+A22'*X21)*B12 - (C12'*X11+C22'*X21)*D12
         # WS21 = A12'*X11+A22'*X21
         mul!(WS21, A22', E21)
         mul!(WS21, A12', E11, 1, 1)
         # E22 = E22 + WS21 * B12
         mul!(E22, WS21, B12, 1, 1)
         # WS12 = C12'*X11+C22'*X22)
         mul!(WS21, C22', E21)
         mul!(WS21, C12', E11, 1, 1)
         # E22 = E22 + WS21 * D12
         mul!(E22, WS21, D12, -1, 1)

         # E22 = E22 + A12'*X12*B22
         mul!(WS12, E12, B22)
         mul!(E22, A12', WS12, 1, 1) 
         # E22 = E22 - C12'*X12*D22
         mul!(WS12, E12, D22)
         mul!(E22, C12', WS12, -1, 1) 

         # X22 solver
         _lyapgsylvds_blocked!(WS22, WS2, A22, B22, C22, D22, E22, adj, blocksize)
      else
         # solve A X B' - C X D' + E = 0

         # X22 solver
         _lyapgsylvds_blocked!(WS22, WS2, A22, B22, C22, D22, E22, adj, blocksize)

         # E12 update: E12 = E12 + A12*X22*B22' - C12*X22*D22'
         mul!(WS22,E22,B22')
         mul!(E12,A12,WS22,1,1)
         mul!(WS22,E22,D22')
         mul!(E12,C12,WS22,-1,1)

         # X12 solver
         _lyapgsylvds_blocked!(WS12, WS2, A11, B22, C11, D22, E12, adj, blocksize)

         # E21 update: E21 = E21 + A22*X22*B12' - C22*X22*D12'
         mul!(WS22,A22,E22)
         mul!(E21,WS22,B12',1,1)
         mul!(WS22,C22,E22)
         mul!(E21,WS22,D12',-1,1)

         # X21 solver
         _lyapgsylvds_blocked!(WS21, WS2, A22, B11, C22, D11, E21, adj, blocksize)


         # E11 update: E11 = E11 + A12*X21*B11' - C12*X21*D11' +
         #                   (A11*X12+A12*X22)*B12' - (C11*X12+C12*X22)*D12'
         # WS12 = A11*X12+A12*X22
         mul!(WS12, A11, E12)
         mul!(WS12, A12, E22, 1, 1)
         # E11 = E11 + WS12 * B12'
         mul!(E11, WS12, B12', 1, 1)
         # WS12 = C11*X12+C12*X22
         mul!(WS12, C11, E12)
         mul!(WS12, C12, E22, 1, 1)
         # E11 = E11 + WS12 * D12'
         mul!(E11, WS12, D12', -1, 1)

         # E11 = E11 + A12*X21*B11'
         mul!(WS21, E21, B11')
         mul!(E11, A12, WS21, 1, 1) 
         # E11 = E11 - C12*X21*D11'
         mul!(WS21, E21, D11')
         mul!(E11, C12, WS21, -1, 1) 

         # X11 solver
         _lyapgsylvds_blocked!(WS11, WS2, A11, B11, C11, D11, E11, adj, blocksize)
      end
   end   
end


function lyapds!(A::AbstractMatrix{T1},C::AbstractMatrix{T1}; adj = false) where {T1<:Complex}
   n = LinearAlgebra.checksquare(A)
   (LinearAlgebra.checksquare(C) == n && ishermitian(C)) ||
      throw(DimensionMismatch("C must be a $n x $n hermitian matrix"))
   ONE = one(T1)
   RONE = real(ONE)
   if isdiag(A) 
      if adj 
         for i = 1:n
            C[i,i] = -C[i,i]/(abs(A[i,i])^2-RONE)
            isfinite(C[i,i]) || throw("ME:SingularException: A has eigenvalue(s) α such that |α| = 1")
            for j = i+1:n
               C[i,j] = -C[i,j]/(A[i,i]'*A[j,j]-ONE)
               isfinite(C[i,j]) || throw("ME:SingularException: A has eigenvalue(s) α and β such that αβ' = 1")
               C[j,i] = C[i,j]'
            end
         end
      else
         for i = 1:n
            C[i,i] = -C[i,i]/(abs(A[i,i])^2-RONE)
            for j = i+1:n
               C[i,j] = -C[i,j]/(A[i,i]*A[j,j]'-ONE)
               isfinite(C[i,j]) || throw("ME:SingularException: A has eigenvalue(s) α and β such that α+β = 0")
               C[j,i] = C[i,j]'
            end
         end
      end   
      return C[:,:]
   end      

   # Compute the hermitian solution
   if adj
      for k = 1:n
         for l = 1:k
            y = C[k,l]
            if k > 1
               C[l,k] = C[l,1]*A[1,k]
               for ir = 2:k-1
                  C[l,k] +=  C[l,ir]*A[ir,k]
               end
               for ic = 1:l
                  y += C[ic,k]'*A[ic,l]
               end
            end
            temp = ONE-A[k,k]'*A[l,l]
            C[k,l] = y/temp
            isfinite(C[k,l]) || throw("ME:SingularException: A has eigenvalues α and β such that αβ ≈ 1")
            k == l && (C[k,k] = real(C[k,k]))
            if l < k
               C[l,k] += C[k,l]'*A[k,k]
            end
         end
         for ir = 1:k-1
             C[ir,k] = C[k,ir]'
         end
      end
   else
      for l = n:-1:1
        for k = n:-1:l
            y = C[l,k]
            if l < n
               C[k,l] = C[k,l+1]*A[l,l+1]'
               for ir = l+2:n
                  C[k,l] += C[k,ir]*A[l,ir]'
               end
               for ic = k:n
                   y += (A[k,ic]*C[ic,l])'
               end
            end
            temp = ONE-A[k,k]'*A[l,l]
            C[l,k] = y/temp
            isfinite(C[l,k]) || throw("ME:SingularException: A has eigenvalues α and β such that αβ' ≈ 1")
            k == l && (C[k,k] = real(C[k,k]))
            if k > l
               C[k,l] += (A[l,l]*C[l,k])'
            end
        end
        if l < n
           for ir = l+1:n
               C[ir,l] = C[l,ir]'
           end
        end
      end
   end
end
"""
    lyapds!(A, E, C; adj = false)

Solve the generalized discrete Lyapunov matrix equation

                op(A)Xop(A)' - op(E)Xop(E)' + C = 0,

where `op(A) = A` and `op(E) = E` if `adj = false` and `op(A) = A'` and
`op(E) = E'` if `adj = true`. The pair `(A,E)` is in a generalized real or
complex Schur form and `C` is a symmetric or hermitian matrix.
The pencil `A-λE` must not have eigenvalues `α` and `β` such that `αβ = 1`.
The computed symmetric or hermitian solution `X` is contained in `C`.
"""
function lyapds!(A::AbstractMatrix{T1},E::Union{AbstractMatrix{T1},UniformScaling{Bool}}, C::AbstractMatrix{T1}, W::AbstractMatrix{T1} = Array{T1,2}(undef,size(A,1),2); adj = false) where {T1<:Real}
   n = LinearAlgebra.checksquare(A)
   (LinearAlgebra.checksquare(C) == n && issymmetric(C)) ||
      throw(DimensionMismatch("C must be a $n x $n symmetric matrix"))
   (typeof(E) == UniformScaling{Bool} || (isequal(E,I) && size(E,1) == n)) && (lyapds!(A, C, adj = adj); return)
   LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))

   ONE = one(T1)
   MONE = -ONE

   # determine the structure of the real Schur form
   ba, p = sfstruct(A)

   G = Matrix{T1}(undef,2,2)
   #W = Array{T1,2}(undef,n,2)
   Xw = Matrix{T1}(undef,4,4)
   Yw = Vector{T1}(undef,4)
   if adj
      # """
      # The (K,L)th block of X is determined starting from the
      # upper-left corner column by column by

      # A(K,K)'*X(K,L)*A(L,L) - E(K,K)'*X(K,L)*E(L,L) = -C(K,L) - R(K,L),

      # where
      #           K           L-1
      # R(K,L) = SUM {A(I,K)'*SUM [X(I,J)*A(J,L)]} -
      #          I=1          J=1

      #           K           L-1
      #          SUM {E(I,K)'*SUM [X(I,J)*E(J,L)]} +
      #          I=1          J=1

      #           K-1
      #          {SUM [A(I,K)'*X(I,L)]}*A(L,L) -
      #           I=1

      #           K-1
      #          {SUM [E(I,K)'*X(I,L)]}*E(L,L).
      #           I=1
      # """
      i = 1
      for kk = 1:p
          dk = ba[kk]
          dkk = 1:dk
          k = i:i+dk-1
          j = 1
          ir = 1:i-1
          for ll = 1:kk
             dl = ba[ll]
             j1 = j+dl-1
             l = j:j1
             #y = C[k,l]
             y = view(G,1:dk,1:dl)
             Ckl = view(C,k,l)
             copyto!(y,Ckl)
             if kk > 1
                 # C[l,k] = C[l,ir]*A[ir,k]
                 # W[l,dkk] = C[l,ir]*E[ir,k]
                 mul!(view(C,l,k),view(C,l,ir),view(A,ir,k))
                 mul!(view(W,l,dkk),view(C,l,ir),view(E,ir,k))
                 ic = 1:j1
                 # y += C[ic,k]'*A[ic,l] -W[ic,dkk]'*E[ic,l]
                 mul!(y,transpose(view(C,ic,k)),view(A,ic,l),ONE,ONE)
                 mul!(y,transpose(view(W,ic,dkk)),view(E,ic,l),MONE,ONE)
             end
             if i == j
                lyapd2!(adj,y,dk,view(A,k,k),view(E,k,k),Xw,Yw)
                copyto!(Ckl,y)
             else
                lyapdsylv2!(adj,y,dk,dl,view(A,k,k),view(E,k,k),view(A,l,l),view(E,l,l),Xw,Yw)
                copyto!(Ckl,y)
             end
             j += dl
             if j <= i
                 # C[l,k] += C[k,l]'*A[k,k]
                 # W[l,dkk] += C[k,l]'*E[k,k]
                 mul!(view(C,l,k),transpose(view(C,k,l)),view(A,k,k),ONE,ONE)
                 mul!(view(W,l,dkk),transpose(view(C,k,l)),view(E,k,k),ONE,ONE)
              end
          end
          if kk > 1
             # C[ir,k] = C[k,ir]'
             transpose!(view(C,ir,k),view(C,k,ir))
          end
          i += dk
      end
   else
      # """
      # The (K,L)th block of X is determined starting from
      # bottom-right corner column by column by

      # A(K,K)*X(K,L)*A(L,L)' - E(K,K)*X(K,L)*E(L,L)' = -C(K,L) - R(K,L),

      # where

      #           N            N
      # R(K,L) = SUM {A(K,I)* SUM [X(I,J)*A(L,J)']} -
      #          I=K         J=L+1

      #           N            N
      #          SUM {E(K,I)* SUM [X(I,J)*E(L,J)']} +
      #          I=K         J=L+1

      #             N
      #          { SUM [A(K,J)*X(J,L)]}*A(L,L)' -
      #           J=K+1

      #             N
      #          { SUM [E(K,J)*X(J,L)]}*E(L,L)'
      #           J=K+1
      # """
      j = n
      for ll = p:-1:1
        dl = ba[ll]
        l = j-dl+1:j
        dll = 1:dl
        i = n
        ir = j+1:n
        for kk = p:-1:ll
            dk = ba[kk]
            i1 = i-dk+1
            k = i1:i
            Clk = view(C,l,k)
            y = view(G,1:dl,1:dk)
            copyto!(y,Clk)
            if ll < p
               # C[k,l] = C[k,ir]*A[l,ir]'
               # W[k,dll] = C[k,ir]*E[l,ir]'
               mul!(view(C,k,l),view(C,k,ir),transpose(view(A,l,ir)))
               mul!(view(W,k,dll),view(C,k,ir),transpose(view(E,l,ir)))
               ic = i1:n
               # y += (A[k,ic]*C[ic,l] - E[k,ic]*W[ic,dll])'
               mul!(y,transpose(view(C,ic,l)),transpose(view(A,k,ic)),ONE,ONE)
               mul!(y,transpose(view(W,ic,dll)),transpose(view(E,k,ic)),MONE,ONE)
            end
            if i == j
               lyapd2!(adj,y,dk,view(A,k,k),view(E,k,k),Xw,Yw)
               copyto!(Clk,y)
            else
               lyapdsylv2!(adj,y,dl,dk,view(A,l,l),view(E,l,l),view(A,k,k),view(E,k,k),Xw,Yw)
               copyto!(Clk,y)
            end
            i -= dk
            if i >= j
               # C[k,l] += (A[l,l]*C[l,k])'
               # W[k,dll] += (E[l,l]*C[l,k])'
               mul!(view(C,k,l),transpose(view(C,l,k)),transpose(view(A,l,l)),ONE,ONE)
               mul!(view(W,k,dll),transpose(view(C,l,k)),transpose(view(E,l,l)),ONE,ONE)
            else
               break
            end
        end
        if ll < p
           ir = i+2:n
           # C[ir,l] = C[l,ir]'
           transpose!(view(C,ir,l),view(C,l,ir))
         end
        j -= dl
      end
   end
end
@inline function lyapd2!(adj,C::AbstractMatrix{T},na::Int,A::AbstractMatrix{T},E::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
   # speed and reduced allocation oriented implementation of a solver for 1x1 or 2x2 continuous Lyapunov equations
   #      A'*X*A - E'*X*E = -C if adj = true  -> R = kron(A',A')-kron(E',E') = (kron(A,A)-kron(E,E))'
   #      A*X*A' - E*X*E' = -C if adj = false -> R = kron(A,A)-kron(E,E)
   MONE = -one(T)
   if na == 1
      temp = E[1,1]^2-A[1,1]^2
      rmul!(C,inv(temp))
      any(!isfinite, C) &&  throw("ME:SingularException: A-λE has eigenvalue(s) with moduli equal to one")
      return C
   end
   TWO = 2*one(T)
   @inbounds  Y[1] = -C[1,1]
   @inbounds  Y[2] = -C[2,1]
   @inbounds  Y[3] = -C[2,2]
   if adj
      # Rt =
      # [     a11^2 - e11^2,                   2*a11*a21,         a21^2]
      # [ a11*a12 - e11*e12, a11*a22 + a12*a21 - e11*e22,       a21*a22]
      # [     a12^2 - e12^2,       2*a12*a22 - 2*e12*e22, a22^2 - e22^2]
      # iszero(E[1,2]) ?
      #    (@inbounds R = [ A[1,1]^2-E[1,1]^2    TWO*A[1,1]*A[2,1]         A[2,1]^2;
      #                 A[1,1]*A[1,2]    A[1,1]*A[2,2]+A[1,2]*A[2,1]-E[1,1]*E[2,2]  A[2,1]*A[2,2];
      #                 A[1,2]^2         TWO*A[1,2]*A[2,2]         A[2,2]^2-E[2,2]^2 ] ) :
         # (@inbounds R = [ A[1,1]^2-E[1,1]^2    TWO*A[1,1]*A[2,1]         A[2,1]^2;
         #              A[1,1]*A[1,2]-E[1,1]*E[1,2]    A[1,1]*A[2,2]+A[1,2]*A[2,1]-E[1,1]*E[2,2]  A[2,1]*A[2,2];
         #              A[1,2]^2-E[1,2]^2         TWO*(A[1,2]*A[2,2]-E[1,2]*E[2,2])         A[2,2]^2-E[2,2]^2 ])
      @inbounds  R[1,1] = A[1,1]^2-E[1,1]^2
      @inbounds  R[1,2] = TWO*A[1,1]*A[2,1]
      @inbounds  R[1,3] = A[2,1]^2
      @inbounds  R[2,1] = A[1,1]*A[1,2]-E[1,1]*E[1,2]
      @inbounds  R[2,2] = A[1,1]*A[2,2]+A[1,2]*A[2,1]-E[1,1]*E[2,2]
      @inbounds  R[2,3] = A[2,1]*A[2,2]
      @inbounds  R[3,1] = A[1,2]^2-E[1,2]^2
      @inbounds  R[3,2] = TWO*(A[1,2]*A[2,2]-E[1,2]*E[2,2])
      @inbounds  R[3,3] = A[2,2]^2-E[2,2]^2
   else
      # R =
      # [ a11^2 - e11^2,       2*a11*a12 - 2*e11*e12,     a12^2 - e12^2]
      # [       a11*a21, a11*a22 + a12*a21 - e11*e22, a12*a22 - e12*e22]
      # [         a21^2,                   2*a21*a22,     a22^2 - e22^2]
      # iszero(E[1,2]) ?
      #    (@inbounds R = [ A[1,1]^2-E[1,1]^2    TWO*A[1,1]*A[1,2]         A[1,2]^2;
      #                 A[1,1]*A[2,1]    A[1,1]*A[2,2]+A[1,2]*A[2,1]-E[1,1]*E[2,2]  A[1,2]*A[2,2];
      #                 A[2,1]^2         TWO*A[2,1]*A[2,2]         A[2,2]^2-E[2,2]^2 ] ) :

      #    (@inbounds R = [ A[1,1]^2-E[1,1]^2    TWO*(A[1,1]*A[1,2]-E[1,1]*E[1,2])         A[1,2]^2-E[1,2]^2;
      #                 A[1,1]*A[2,1]    A[1,1]*A[2,2]+A[1,2]*A[2,1]-E[1,1]*E[2,2]  A[1,2]*A[2,2]-E[1,2]*E[2,2];
      #                 A[2,1]^2         TWO*A[2,1]*A[2,2]         A[2,2]^2-E[2,2]^2 ] )
      @inbounds  R[1,1] = A[1,1]^2-E[1,1]^2
      @inbounds  R[1,2] = TWO*(A[1,1]*A[1,2]-E[1,1]*E[1,2])
      @inbounds  R[1,3] = A[1,2]^2-E[1,2]^2
      @inbounds  R[2,1] = A[1,1]*A[2,1]
      @inbounds  R[2,2] = A[1,1]*A[2,2]+A[1,2]*A[2,1]-E[1,1]*E[2,2]
      @inbounds  R[2,3] = A[1,2]*A[2,2]-E[1,2]*E[2,2]
      @inbounds  R[3,1] = A[2,1]^2
      @inbounds  R[3,2] = TWO*A[2,1]*A[2,2]
      @inbounds  R[3,3] = A[2,2]^2-E[2,2]^2
   end
   luslv!(R,Y,3) && throw("ME:SingularException: A-λE has eigenvalues α and β such that αβ ≈ 1")
   @inbounds C[1,1] = Y[1]
   @inbounds C[1,2] = Y[2]
   @inbounds C[2,1] = Y[2]
   @inbounds C[2,2] = Y[3]
   return C
end
@inline function lyapdsylv2!(adj,C::AbstractMatrix{T},na::Int,nb::Int,A::AbstractMatrix{T},E::AbstractMatrix{T},B::AbstractMatrix{T},F::AbstractMatrix{T},R::AbstractMatrix{T},Y::StridedVector{T}) where T <:Real
   # speed and reduced allocation oriented implementation of a solver for 1x1 and 2x2 Sylvester equations
   # encountered in solving discrete Lyapunov equations:
   #      A'*X*B - E'*X*F = -C if adj = true  -> R = kron(B',A') - kron(F',E') = (kron(B,A)-kron(F,E))'
   #      A*X*B' - E*X*F' = -C if adj = false -> R = kron(B,A)-kron(F,E)
   if na == 1 && nb == 1
      temp = E[1,1]*F[1,1] - A[1,1]*B[1,1]
      rmul!(C,inv(temp))
      any(!isfinite, C) && throw("ME:SingularException: A-λE has eigenvalues α and β such that αβ ≈ 1")
      return C
   end
   if adj
      if na == 1
         # R12t =
         # [ a11*b11 - e11*f11,           a11*b21]
         # [ a11*b12 - e11*f12, a11*b22 - e11*f22]
         # @inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[1,1]*B[2,1];
         #                 A[1,1]*B[1,2]-E[1,1]*F[1,2]  A[1,1]*B[2,2]-E[1,1]*F[2,2]]
         @inbounds  R[1,1] = A[1,1]*B[1,1]-E[1,1]*F[1,1]
         @inbounds  R[1,2] = A[1,1]*B[2,1]
         @inbounds  R[2,1] = A[1,1]*B[1,2]-E[1,1]*F[1,2]
         @inbounds  R[2,2] = A[1,1]*B[2,2]-E[1,1]*F[2,2]
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
      else
         if nb == 1
            # R21t =
            # [ a11*b11 - e11*f11,           a21*b11]
            # [ a12*b11 - e12*f11, a22*b11 - e22*f11]
            # @inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[2,1]*B[1,1];
            #                 A[1,2]*B[1,1]-E[1,2]*F[1,1]  A[2,2]*B[1,1]-E[2,2]*F[1,1] ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]-E[1,1]*F[1,1]
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]-E[1,2]*F[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]-E[2,2]*F[1,1]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # Rt =
            # [ a11*b11 - e11*f11,           a21*b11,           a11*b21,           a21*b21]
            # [ a12*b11 - e12*f11, a22*b11 - e22*f11,           a12*b21,           a22*b21]
            # [ a11*b12 - e11*f12,           a21*b12, a11*b22 - e11*f22,           a21*b22]
            # [ a12*b12 - e12*f12, a22*b12 - e22*f12, a12*b22 - e12*f22, a22*b22 - e22*f22]
            # (iszero(E[1,2]) && iszero(F[1,2])) ?
            # (@inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[2,1]*B[1,1]      A[1,1]*B[2,1]      A[2,1]*B[2,1];
            # A[1,2]*B[1,1]  A[2,2]*B[1,1]-E[2,2]*F[1,1]      A[1,2]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]      A[2,1]*B[1,2]  A[1,1]*B[2,2]-E[1,1]*F[2,2]      A[2,1]*B[2,2];
            # A[1,2]*B[1,2]      A[2,2]*B[1,2]      A[1,2]*B[2,2]  A[2,2]*B[2,2]-E[2,2]*F[2,2]]) :
            # (@inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[2,1]*B[1,1]      A[1,1]*B[2,1]      A[2,1]*B[2,1];
            # A[1,2]*B[1,1]-E[1,1]*F[1,2]  A[2,2]*B[1,1]-E[2,2]*F[1,1]      A[1,2]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]-E[1,1]*F[1,2]      A[2,1]*B[1,2]  A[1,1]*B[2,2]-E[1,1]*F[2,2]      A[2,1]*B[2,2];
            # A[1,2]*B[1,2]-E[1,2]*F[1,2]      A[2,2]*B[1,2]-E[2,2]*F[1,2]      A[1,2]*B[2,2]-E[1,2]*F[2,2]  A[2,2]*B[2,2]-E[2,2]*F[2,2]])
            @inbounds  R[1,1] = A[1,1]*B[1,1]-E[1,1]*F[1,1]
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[2,1]
            @inbounds  R[1,4] = A[2,1]*B[2,1]
            #@inbounds  R[2,1] = A[1,2]*B[1,1]-E[1,1]*F[1,2]
            @inbounds  R[2,1] = A[1,2]*B[1,1]-E[1,2]*F[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]-E[2,2]*F[1,1]
            @inbounds  R[2,3] = A[1,2]*B[2,1]
            @inbounds  R[2,4] = A[2,2]*B[2,1]
            @inbounds  R[3,1] = A[1,1]*B[1,2]-E[1,1]*F[1,2]
            @inbounds  R[3,2] = A[2,1]*B[1,2]
            @inbounds  R[3,3] = A[1,1]*B[2,2]-E[1,1]*F[2,2]
            @inbounds  R[3,4] = A[2,1]*B[2,2]
            @inbounds  R[4,1] = A[1,2]*B[1,2]-E[1,2]*F[1,2]
            @inbounds  R[4,2] = A[2,2]*B[1,2]-E[2,2]*F[1,2]
            @inbounds  R[4,3] = A[1,2]*B[2,2]-E[1,2]*F[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]-E[2,2]*F[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   else
      if na == 1
         # R12 =
         # [ a11*b11 - e11*f11, a11*b12 - e11*f12]
         # [           a11*b21, a11*b22 - e11*f22]
         # @inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[1,1]*B[1,2]-E[1,1]*F[1,2];
         #                 A[1,1]*B[2,1]  A[1,1]*B[2,2]-E[1,1]*F[2,2]]
         @inbounds  R[1,1] = A[1,1]*B[1,1]-E[1,1]*F[1,1]
         @inbounds  R[1,2] = A[1,1]*B[1,2]-E[1,1]*F[1,2]
         @inbounds  R[2,1] = A[1,1]*B[2,1]
         @inbounds  R[2,2] = A[1,1]*B[2,2]-E[1,1]*F[2,2]
         @inbounds  Y[1] = -C[1,1]
         @inbounds  Y[2] = -C[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 - e11*f11, a12*b11 - e12*f11]
            # [           a21*b11, a22*b11 - e22*f11]
            # @inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[1,2]*B[1,1]-E[1,2]*F[1,1];
            #                 A[2,1]*B[1,1]  A[2,2]*B[1,1]-E[2,2]*F[1,1]]
            @inbounds  R[1,1] = A[1,1]*B[1,1]-E[1,1]*F[1,1]
            @inbounds  R[1,2] = A[1,2]*B[1,1]-E[1,2]*F[1,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]-E[2,2]*F[1,1]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
         else
            # R =
            # [ a11*b11 - e11*f11, a12*b11 - e12*f11, a11*b12 - e11*f12, a12*b12 - e12*f12]
            # [           a21*b11, a22*b11 - e22*f11,           a21*b12, a22*b12 - e22*f12]
            # [           a11*b21,           a12*b21, a11*b22 - e11*f22, a12*b22 - e12*f22]
            # [           a21*b21,           a22*b21,           a21*b22, a22*b22 - e22*f22]
            # (iszero(E[1,2]) && iszero(F[1,2])) ?
            # (@inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[1,2]*B[1,1]      A[1,1]*B[1,2]      A[1,2]*B[1,2];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]-E[2,2]*F[1,1]      A[2,1]*B[1,2]      A[2,2]*B[1,2];
            # A[1,1]*B[2,1]      A[1,2]*B[2,1]  A[1,1]*B[2,2]-E[1,1]*F[2,2]      A[1,2]*B[2,2];
            # A[2,1]*B[2,1]      A[2,2]*B[2,1]      A[2,1]*B[2,2]  A[2,2]*B[2,2]-E[2,2]*F[2,2]]) :
            # (@inbounds R = [ A[1,1]*B[1,1]-E[1,1]*F[1,1]      A[1,2]*B[1,1]-E[1,2]*F[1,1]      A[1,1]*B[1,2]-E[1,1]*F[1,2]      A[1,2]*B[1,2]-E[1,2]*F[1,2];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]-E[2,2]*F[1,1]      A[2,1]*B[1,2]      A[2,2]*B[1,2]-E[2,2]*F[1,2];
            # A[1,1]*B[2,1]      A[1,2]*B[2,1]  A[1,1]*B[2,2]-E[1,1]*F[2,2]      A[1,2]*B[2,2]-E[1,2]*F[2,2];
            # A[2,1]*B[2,1]      A[2,2]*B[2,1]      A[2,1]*B[2,2]  A[2,2]*B[2,2]-E[2,2]*F[2,2]])
            @inbounds  R[1,1] = A[1,1]*B[1,1]-E[1,1]*F[1,1]
            @inbounds  R[1,2] = A[1,2]*B[1,1]-E[1,2]*F[1,1]
            @inbounds  R[1,3] = A[1,1]*B[1,2]-E[1,1]*F[1,2]
            @inbounds  R[1,4] = A[1,2]*B[1,2]-E[1,2]*F[1,2]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]-E[2,2]*F[1,1]
            @inbounds  R[2,3] = A[2,1]*B[1,2]
            @inbounds  R[2,4] = A[2,2]*B[1,2]-E[2,2]*F[1,2]
            @inbounds  R[3,1] = A[1,1]*B[2,1]
            @inbounds  R[3,2] = A[1,2]*B[2,1]
            @inbounds  R[3,3] = A[1,1]*B[2,2]-E[1,1]*F[2,2]
            @inbounds  R[3,4] = A[1,2]*B[2,2]-E[1,2]*F[2,2]
            @inbounds  R[4,1] = A[2,1]*B[2,1]
            @inbounds  R[4,2] = A[2,2]*B[2,1]
            @inbounds  R[4,3] = A[2,1]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]-E[2,2]*F[2,2]
            @inbounds  Y[1] = -C[1,1]
            @inbounds  Y[2] = -C[2,1]
            @inbounds  Y[3] = -C[1,2]
            @inbounds  Y[4] = -C[2,2]
         end
      end
   end
   luslv!(R,Y,na*nb) && throw("ME:SingularException: A-λE has eigenvalues α and β such that αβ ≈ 1")
   if na == 1 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[1,2] = Y[2]
   elseif na == 2 && nb == 1
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
   else # na == 2 && nb == 2
      @inbounds C[1,1] = Y[1]
      @inbounds C[2,1] = Y[2]
      @inbounds C[1,2] = Y[3]
      @inbounds C[2,2] = Y[4]
   end
   return C
end
function lyapds!(A::AbstractMatrix{T1},E::Union{AbstractMatrix{T1},UniformScaling{Bool}}, C::AbstractMatrix{T1},W::AbstractVector{T1} = Array{T1,1}(undef,size(A,1)); adj = false) where {T1<:Complex}
   n = LinearAlgebra.checksquare(A)
   LinearAlgebra.checksquare(C) == n || throw(DimensionMismatch("C must be a $n x $n matrix"))
   ishermitian(C) || throw(ArgumentError("C must be a hermitian matrix"))
   (typeof(E) == UniformScaling{Bool} || (isequal(E,I) && size(E,1) == n)) && (lyapds!(A, C, adj = adj); return)
   LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))

   # Compute the hermitian solution
   if adj
      for k = 1:n
         for l = 1:k
            y = C[k,l]
            if k > 1
               C[l,k] = C[l,1]*A[1,k]
               W[l] = C[l,1]*E[1,k]
               for ir = 2:k-1
                  C[l,k] +=  C[l,ir]*A[ir,k]
                  W[l] += C[l,ir]*E[ir,k]
               end
               for ic = 1:l
                   y += C[ic,k]'*A[ic,l] - W[ic]'*E[ic,l]
                end
            end
            C[k,l] = y/(E[k,k]'*E[l,l]-A[k,k]'*A[l,l])
            isfinite(C[k,l]) || throw("ME:SingularException: A-λE has eigenvalues α and β such that αβ ≈ 1")
            k == l && (C[k,k] = real(C[k,k]))
            if l < k
               C[l,k] += C[k,l]'*A[k,k]
               W[l] += C[k,l]'*E[k,k]
            end
         end
         for ir = 1:k-1
             C[ir,k] = C[k,ir]'
         end
      end
   else
      for l = n:-1:1
        for k = n:-1:l
            y = C[l,k]
            if l < n
               C[k,l] = C[k,l+1]*A[l,l+1]'
               W[k] = C[k,l+1]*E[l,l+1]'
               for ir = l+2:n
                  C[k,l] += C[k,ir]*A[l,ir]'
                  W[k] += C[k,ir]*E[l,ir]'
               end
               for ic = k:n
                   y += (A[k,ic]*C[ic,l] - E[k,ic]*W[ic])'
               end
            end
            C[l,k] = y/(E[k,k]'*E[l,l]-A[k,k]'*A[l,l])
            isfinite(C[l,k]) || throw("ME:SingularException: A-λE has eigenvalues α and β such that αβ ≈ 1")
            k == l && (C[k,k] = real(C[k,k]))
            if k > l
               C[k,l] += (A[l,l]*C[l,k])'
               W[k] += (E[l,l]*C[l,k])'
            end
        end
        if l < n
           for ir = l+1:n
               C[ir,l] = C[l,ir]'
           end
        end
      end
   end
end
# Lyapunov-like equations 
"""
    X = tlyapc(A,C, isig = 1; fast = true, atol::Real=0, rtol::Real=atol>0 ? 0 : N*ϵ)

Compute for `isig = ±1` a solution of the continuous T-Lyapunov matrix equation

                A*X + isig*transpose(X)*transpose(A) + C = 0

using explicit formulas based on full rank factorizations of `A`  (see [1] and [2]). 
`A` and `C` are `m×n` and `m×m` matrices, respectively, and `X` is an `n×m` matrix.
The matrix `C` must be symmetric if `isig = 1` and skew-symmetric if `isig = -1`.
`atol` and `rtol` are the absolute and relative tolerances, respectively, used for rank computation. 
The default relative tolerance is `N*ϵ`, where `N = min(m,n)` and ϵ is the machine precision of 
the element type of `A`.

The underlying rank revealing factorization of `A` is the QR-factorization with column pivoting, 
if `fast = true` (default), or the more reliable SVD-decomposition, if `fast = false`.

[1] H. W. Braden. The equations A^T X ± X^T A = B. SIAM J. Matrix Anal. Appl., 20(2):295–302, 1998.

[2] C.-Y. Chiang, E. K.-W. Chu, W.-W. Lin, On the ★-Sylvester equation AX ± X^★ B^★ = C.
    Applied Mathematics and Computation, 218:8393–8407, 2012.
"""
function tlyapc(A, C, isig = 1; fast::Bool = true, atol::Real = 0.0, rtol::Real = (min(size(A)...)*eps(real(float(one(eltype(A))))))*iszero(atol))
    m = LinearAlgebra.checksquare(C)
    ma, n = size(A)
    ma == m || throw(DimensionMismatch("A and C have incompatible dimensions"))
    abs(isig) == 1 || error(" isig must be either 1 or -1")
    if isig == 1
       issymmetric(C) || error("C must be symmetric for isig = 1")
       # temporary fix to avoid false results for DoubleFloats 
       # C == transpose(C) || error("C must be symmetric for isig = 1")
    else
       iszero(C+transpose(C)) || error("C must be skew-symmetric for isig = -1")
    end
    T2 = promote_type(eltype(A), eltype(C))
    T2 <: BlasFloat  || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    if fast 
       F = qr(A, ColumnNorm())
       tol = max(atol, rtol*norm(F.R,Inf))
       r = count(x -> abs(x) > tol, diag(F.R))
       Ct = F.Q'*C*conj(Matrix(F.Q))
       i1 = 1:r; j2 = r+1:m
       norm(view(Ct,j2,j2),Inf) <= tol || @warn "Incompatible equation: least-squares solution computed"
       return ([ldiv!(UpperTriangular(2*F.R[i1,i1]),view(Ct,i1,i1)) ldiv!(UpperTriangular(F.R[i1,i1]),view(Ct,i1,j2));
                zeros(T2,n-r,m)]*transpose(Matrix(F.Q)))[invperm(F.p),:]
   else
       m > n ? F = svd(A,full = true) : F = svd(A)
       tol = max(atol, rtol*F.S[1])
       r = count(x -> x > tol, F.S)
       Ct = F.U'*C*conj(F.U)
       i1 = 1:r; j2 = r+1:m
       norm(view(Ct,j2,j2),Inf) <= tol || @warn "Incompatible equation: least-squares solution computed"
       return view(F.Vt,i1,:)'*([Diagonal(F.S[i1]*2)\Ct[i1,i1] Diagonal(F.S[i1])\Ct[i1,j2]]*transpose(F.U))
    end
end 
"""
    X = hlyapc(A,C, isig = 1; atol::Real=0, rtol::Real=atol>0 ? 0 : N*ϵ)

Compute for `isig = ±1` a solution of the continuous H-Lyapunov matrix equation

                A*X + isig*adjoint(X)*adjoint(A) + C = 0

using explicit formulas based on full rank factorizations of `A`  (see [1] and [2]). 
`A` and `C` are `m×n` and `m×m` matrices, respectively, and `X` is an `n×m` matrix.
The matrix `C` must be hermitian if `isig = 1` and skew-hermitian if `isig = -1`.
`atol` and `rtol` are the absolute and relative tolerances, respectively, used for rank computation. 
The default relative tolerance is `N*ϵ`, where `N = min(m,n)` and ϵ is the machine precision 
of the element type of `A`.

The underlying rank revealing factorization of `Ae` (or of `A` if real) is the QR-factorization with column pivoting, 
if `fast = true` (default), or the more reliable SVD-decomposition, if `fast = false`.

[1]  H. W. Braden. The equations A^T X ± X^T A = B. SIAM J. Matrix Anal. Appl., 20(2):295–302, 1998.

[2] C.-Y. Chiang, E. K.-W. Chu, W.-W. Lin, On the ★-Sylvester equation AX ± X^★ B^★ = C.
    Applied Mathematics and Computation, 218:8393–8407, 2012.
"""
function hlyapc(A, C, isig = 1; fast::Bool = true, atol::Real = 0.0, rtol::Real = (min(size(A)...)*eps(real(float(one(eltype(A))))))*iszero(atol))
   promote_type(eltype(A),eltype(C)) <: Real && (return tlyapc(A, C; fast, atol, rtol))
   m = LinearAlgebra.checksquare(C)
   ma, n = size(A)
   ma == m || throw(DimensionMismatch("A and C have incompatible dimensions"))
   abs(isig) == 1 || error(" isig must be either 1 or -1")
   if isig == 1
      ishermitian(C) || error("C must be hermitian for isig = 1")
      # temporary fix to avoid false results for DoubleFloats 
      # C == adjoint(C) || error("C must be hermitian for isig = 1")
   else
      iszero(C+adjoint(C)) || error("C must be skew-hermitian for isig = -1")
   end
   T2 = promote_type(eltype(A), eltype(C))
   T2 <: BlasFloat  || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (A = convert(Matrix{T2},A))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))
   if fast 
      F = qr(A, ColumnNorm())
      tol = max(atol, rtol*norm(F.R,Inf))
      r = count(x -> abs(x) > tol, diag(F.R))
      Ct = F.Q'*C*F.Q
      i1 = 1:r; j2 = r+1:m
      norm(view(Ct,j2,j2),Inf) <= tol || @warn "Incompatible equation: least-squares solution computed"
      return ([ldiv!(UpperTriangular(2*F.R[i1,i1]),view(Ct,i1,i1)) ldiv!(UpperTriangular(F.R[i1,i1]),view(Ct,i1,j2));
               zeros(T2,n-r,m)]*F.Q')[invperm(F.p),:]
  else
      m > n ? F = svd(A,full = true) : F = svd(A)
      tol = max(atol, rtol*F.S[1])
      r = count(x -> x > tol, F.S)
      Ct = F.U'*C*F.U
      i1 = 1:r; j2 = r+1:m
      norm(view(Ct,j2,j2),Inf) <= tol || @warn "Incompatible equation: least-squares solution computed"
      return view(F.Vt,i1,:)'*[Diagonal(F.S[i1]*2)\Ct[i1,i1] Diagonal(F.S[i1])\Ct[i1,j2]]*F.U'
   end
end
"""
    X = tulyapc!(U, Q; adj = false)

Compute for an upper triangular `U` and a symmetric `Q` an upper triangular solution `X` of the continuous T-Lyapunov matrix equation

                transpose(U)*X + transpose(X)*U = Q  if adj = false, 

or

                U*transpose(X) + X*transpose(U) = Q   if adj = true.

The solution `X` overwrites the matrix `Q`, while `U` is unchanged.

If `U` is nonsingular, a backward elimination method is used, if `adj = false`, or a forward elimination method is used if `adj = true`, by adapting the approach 
employed in the function `dU_from_dQ!` of the [`DiffOpt.jl`](https://github.com/jump-dev/DiffOpt.jl) package. 
For a `n×n`  singular `U`, a least-squares upper-triangular solution `X` is determined using a conjugate-gradient based iterative method applied 
to a suitably defined T-Lyapunov linear operator `L:X -> Y`, which maps upper triangular matrices `X`
into upper triangular matrices `Y`, and the associated matrix `M = Matrix(L)` is ``n(n+1)/2 \\times n(n+1)/2``. 
In this case, the keyword argument `tol` (default: `tol = sqrt(eps())`) can be used to provide the desired tolerance for the accuracy of the computed solution and 
the keyword argument `maxiter` can be used to set the maximum number of iterations (default: maxiter = 1000). 
"""
function tulyapc!(U::AbstractMatrix{T}, Q::AbstractMatrix{T}; adj = false, abstol = zero(float(real(T))), reltol = sqrt(eps(float(real(T)))), maxiter = 1000) where {T}
   n = LinearAlgebra.checksquare(Q)
   n == LinearAlgebra.checksquare(Q) || throw(DimensionMismatch("U and Q have incompatible dimensions"))
   istriu(U) || throw(ArgumentError("U must be upper triangular"))
   issymmetric(Q) || throw(ArgumentError("Q must be symmetric"))
   if !any(iszero.(diag(U))) 
      if adj
         # U*transpose(X) + X*transpose(U) = Q
         for i in n:-1:1
            for j in n:-1:1
                if i < j
                   Q[i, j] -= transpose(view(U, i, j:n))*view(Q, j, j:n)
                elseif i == j
                   Q[i, j] /= 2
                else
                   Q[i, j] = 0
                end
            end
            Ut = transpose(LinearAlgebra.UpperTriangular(view(U, i:n, i:n)))
            LinearAlgebra.rdiv!(view(Q, i:i, i:n), Ut)
        end
     else
         # transpose(U)*X + transpose(X)*U = Q
         for j in 1:n
            for i in 1:n
                if i < j
                   Q[i, j] -= transpose(view(U, 1:i, j))*view(Q, 1:i, i)
                elseif i == j
                   Q[i, j] /= 2
                else
                   Q[i, j] = 0
                end
            end
            Ut = transpose(LinearAlgebra.UpperTriangular(view(U, 1:j, 1:j)))
            LinearAlgebra.ldiv!(Ut, view(Q, 1:j, j))
         end
      end
   else
      LT = tulyaplikeop(U; adj)
      xt, info = MatrixEquations.cgls(LT,triu2vec(Q); abstol, reltol, maxiter)
      info.flag == 1 || @warn "convergence issues: info = $info"
      Q[:] = vec2triu(xt)
   end
   return Q
end
"""
    X = hulyapc!(U, Q; adj = false)

Compute for an upper triangular `U` and a hermitian `Q` an upper triangular solution `X` of the continuous H-Lyapunov matrix equation

                U'*X + X'*U = Q   if adj = false, 

or

                U*X' + X*U' = Q   if adj = true.

The solution `X` overwrites the matrix `Q`, while `U` is unchanged.

If `U` is nonsingular, a backward elimination method is used, if `adj = false`, or a forward elimination method is used if `adj = true`, by adapting the approach 
employed in the function `dU_from_dQ!` of the [`DiffOpt.jl`](https://github.com/jump-dev/DiffOpt.jl) package. 
For a `n×n`  singular `U`, a least-squares upper-triangular solution `X` is determined using a conjugate-gradient based iterative method applied 
to a suitably defined T-Lyapunov linear operator `L:X -> Y`, which maps upper triangular matrices `X`
into upper triangular matrices `Y`, and the associated matrix `M = Matrix(L)` is ``n(n+1)/2 \\times n(n+1)/2``. 
In this case, the keyword argument `tol` (default: `tol = sqrt(eps())`) can be used to provide the desired tolerance for the accuracy of the computed solution and 
the keyword argument `maxiter` can be used to set the maximum number of iterations (default: maxiter = 1000). 
"""
function hulyapc!(U::AbstractMatrix{T}, Q::AbstractMatrix{T}; adj = false, abstol = zero(float(real(T))), reltol = sqrt(eps(float(real(T)))), maxiter = 1000) where {T}
   n = LinearAlgebra.checksquare(Q)
   n == LinearAlgebra.checksquare(Q) || throw(DimensionMismatch("U and Q have incompatible dimensions"))
   istriu(U) || throw(ArgumentError("U must be upper triangular"))
   ishermitian(Q) || throw(ArgumentError("Q must be hermitian"))
   if !any(iszero.(diag(U))) 
      if adj
         # U*X' + X*U' = Q
         for i in n:-1:1
            for j in n:-1:1
                if i < j
                   Q[i, j] -= view(Q, j, j:n)'*view(U, i, j:n)
                elseif i == j
                   Q[i, j] /= 2
                else
                   Q[i, j] = 0
                end
            end
            Ut = LinearAlgebra.UpperTriangular(view(U, i:n, i:n))'
            LinearAlgebra.rdiv!(view(Q, i:i, i:n), Ut)
        end
     else
         # U'*X + X'*U = Q
         for j in 1:n
            for i in 1:n
                if i < j
                   Q[i, j] -= view(Q, 1:i, i)'*view(U, 1:i, j)
                elseif i == j
                   Q[i, j] /= 2
                else
                   Q[i, j] = 0
                end
            end
            Ut = LinearAlgebra.UpperTriangular(view(U, 1:j, 1:j))'
            LinearAlgebra.ldiv!(Ut, view(Q, 1:j, j))
        end
     end
   else
      LT = hulyaplikeop(U; adj)
      xt, info = cgls(LT,triu2vec(Q); abstol, reltol, maxiter)
      info.flag == 1 || @warn "convergence issues: info = $info"
      Q[:] = vec2triu(xt)
   end
   return Q
end
