"""
    X = sylvc(A,B,C)

Solve the continuous Sylvester matrix equation

                AX + XB = C

using the Bartels-Stewart Schur form based approach. `A` and `B` are
square matrices, and `A` and `-B` must not have common eigenvalues.

The following particular cases are also adressed:

    X = sylvc(α*I,B,C)  or  X = sylvc(α,B,C)

Solve the matrix equation `X(αI+B)  = C`.

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

Solve the matrix equation `(A+βI)X = C`.

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

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

    x = sylvc(α,β,γ)

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

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

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

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

julia> X = sylvc(A, B, C)
2×2 Array{Float64,2}:
 -4.46667   1.93333
  3.73333  -1.8

julia> A*X + X*B - C
2×2 Array{Float64,2}:
  2.66454e-15  1.77636e-15
 -3.77476e-15  4.44089e-16
```
"""
function sylvc(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix)
   """
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """

   m, n = size(C);
   [m; n] == LinearAlgebra.checksquare(A,B) || throw(DimensionMismatch("A, B and C have incompatible dimensions"))

   T2 = promote_type(eltype(A), eltype(B), eltype(C))
   T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (A = convert(Matrix{T2},A))
   eltype(B) == T2 || (B = convert(Matrix{T2},B))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))

   adjA = isa(A,Adjoint)
   adjB = isa(B,Adjoint)
   if adjA
      RA, QA = schur(A.parent)
   else
      RA, QA = schur(A)
   end
   if adjB
      RB, QB = schur(B.parent)
   else
      RB, QB = schur(B)
   end

   realcase = eltype(A) <: AbstractFloat
   if adjA
      RA, QA = schur(A.parent)
   else
      RA, QA = schur(A)
   end
   if adjB
      RB, QB = schur(B.parent)
   else
      RB, QB = schur(B)
   end
   D = adjoint(QA) * (C*QB)
   Y = sylvcs!(RA, RB, D, adjA = adjA, adjB = adjB)
   QA*(Y * adjoint(QB))
end
# solve X(B+α) = C or (α+β)X = C
sylvc(A::Union{Real,Complex,UniformScaling},B::Union{AbstractMatrix,UniformScaling},C::AbstractMatrix) = C/(A*I+B)
# solve (A+β)X = C
sylvc(A::AbstractMatrix,B::Union{Real,Complex,UniformScaling},C::AbstractMatrix) = (A+B*I)\C
# solve (α+β)X = C
sylvc(A::Union{Real,Complex},B::Union{Real,Complex},C::AbstractMatrix) = A+B == 0 ? throw(SingularException(1)) : C/(A+B)
# solve (α+β)x = γ
sylvc(A::Union{Real,Complex}, B::Union{Real,Complex}, C::Union{Real,Complex}) = A+B == 0 ? throw(SingularException(1)) : C/(A+B)
"""
    X = sylvd(A,B,C)

Solve the discrete Sylvester matrix equation

                AXB + X = C

using an extension of the Bartels-Stewart Schur form based approach.
`A` and `B` are square matrices, and `A` and `-B` must not have
common reciprocal eigenvalues.

The following particular cases are also adressed:

    X = sylvd(α*I,B,C)  or  X = sylvd(α,B,C)

Solve the matrix equation `X(αB+I)  = C`.

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

Solve the matrix equation `(βA+I)X = C`.

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

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

    x = sylvd(α,β,γ)

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

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

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

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

julia> X = sylvd(A, B, C)
2×2 Array{Float64,2}:
 -2.46667  -2.73333
  2.4       1.86667

julia> A*X*B + X - C
2×2 Array{Float64,2}:
  8.88178e-16   8.88178e-16
 -3.9968e-15   -5.55112e-15
```
"""
function sylvd(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix)
   """
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """

   m, n = size(C);
   [m; n] == LinearAlgebra.checksquare(A,B) || throw(DimensionMismatch("A, B and C have incompatible dimensions"))
   T2 = promote_type(eltype(A), eltype(B), eltype(C))
   T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (A = convert(Matrix{T2},A))
   eltype(B) == T2 || (B = convert(Matrix{T2},B))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))

   adjA = isa(A,Adjoint)
   adjB = isa(B,Adjoint)
   if adjA
      RA, QA = schur(A.parent)
   else
      RA, QA = schur(A)
   end
   if adjB
      RB, QB = schur(B.parent)
   else
      RB, QB = schur(B)
   end
   D = adjoint(QA) * (C*QB)
   Y = sylvds!(RA, RB, D, adjA = adjA, adjB = adjB)
   QA*(Y * adjoint(QB))
end
# solve X(αB+I) = C or X(αβ+1) = C
sylvd(A::Union{Real,Complex,UniformScaling},B::Union{AbstractMatrix,UniformScaling},C::AbstractMatrix) = C/(A*B+I)
# solve (Aβ+I)X = C
sylvd(A::AbstractMatrix,B::Union{Real,Complex,UniformScaling},C::AbstractMatrix) = (A*B+I)\C
# solve (αβ+1)X = C
sylvd(A::Union{Real,Complex},B::Union{Real,Complex},C::AbstractMatrix) = A*B+1 == 0 ? throw(SingularException(1)) : C/(A*B+1)
# solve (αβ+1)x = γ
sylvd(A::Union{Real,Complex}, B::Union{Real,Complex}, C::Union{Real,Complex}) = A*B+1 == 0 ? throw(SingularException(1)) : C/(A*B+one(C))
"""
    X = gsylv(A,B,C,D,E)

Solve the generalized Sylvester matrix equation

              AXB + CXD = E

using a generalized Schur form based approach. `A`, `B`, `C` and `D` are
square matrices. The pencils `A-λC` and `D+λB` must be regular and
must not have common eigenvalues.

The following particular cases are also adressed:

    X = gsylv(A,B,E)

Solve the generalized Sylvester matrix equation `AXB  = E`.

    X = gsylv(A,B,γ*I,E)  or  X = gsylv(A,B,γ,E)

Solve the generalized Sylvester matrix equation `AXB +γX = E`.

    X = gsylv(A,B,γ*I,D,E)  or  X = gsylv(A,B,γ,D,E)

Solve the generalized Sylvester matrix equation `AXB +γXD = E`.

    X = gsylv(A,B,C,δ*I,E)  or  X = gsylv(A,B,C,δ,E)

Solve the generalized Sylvester matrix equation `AXB +CXδ = E`.

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

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

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

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

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

julia> X = gsylv(A, B, C, D, E)
2×2 Array{Float64,2}:
 -0.52094   -0.0275792
 -0.168539   0.314607

julia> A*X*B + C*X*D - E
2×2 Array{Float64,2}:
 4.44089e-16  8.88178e-16
 6.66134e-16  0.0
```
"""
function gsylv(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::AbstractMatrix,E::AbstractMatrix)

    m, n = size(E);
    [m; n; m; n] == LinearAlgebra.checksquare(A,B,C,D) ||
       throw(DimensionMismatch("A, B, C, D and E have incompatible dimensions"))
    T2 = promote_type(eltype(A), eltype(B), eltype(C), eltype(D), eltype(E))
    T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(B) == T2 || (B = convert(Matrix{T2},B))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    eltype(D) == T2 || (D = convert(Matrix{T2},D))
    eltype(E) == T2 || (E = convert(Matrix{T2},E))

    adjA = isa(A,Adjoint)
    adjB = isa(B,Adjoint)
    adjC = isa(C,Adjoint)
    adjD = isa(D,Adjoint)
    adjAC = adjA && adjC
    adjBD = adjB && adjD

    if adjAC
       AS, CS, Z1, Q1 = schur(A.parent,C.parent)
    else
       if adjA
          A = copy(A)
       end
       if adjC
          C = copy(C)
       end
       AS, CS, Q1, Z1 = schur(A,C)
    end
    if adjBD
       BS, DS, Z2, Q2 = schur(B.parent,D.parent)
    else
      if adjB
          B = copy(B)
      end
      if adjD
          D = copy(D)
      end
       BS, DS, Q2, Z2 = schur(B,D)
    end
    Y = adjoint(Q1) * (E*Z2)
    gsylvs!(AS, BS, CS, DS, Y, adjAC = adjAC, adjBD = adjBD)
    Z1*(Y * adjoint(Q2))
end
# solve AXB = C
gsylv(A::Union{AbstractMatrix,UniformScaling,Real,Complex},B::Union{AbstractMatrix,UniformScaling,Real,Complex},E::AbstractMatrix) = (A\E)/B
# solve AXB+γX = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::Union{UniformScaling,Real,Complex},E::AbstractMatrix) =
size(A,1) == size(A,2) && size(B,1) == size(B,2) ? gsylv(A,B,Matrix{eltype(C)}(C*I,size(A)),Matrix{eltype(C)}(I,size(B)),E) :
throw(DimensionMismatch("A and B must be square matrices"))
# solve AXB+γXδ = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::Union{UniformScaling,Real,Complex},D::Union{UniformScaling,Real,Complex},E::AbstractMatrix) = gsylv(A,B,C*D,E)
# solve AXB+γXD = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::Union{UniformScaling,Real,Complex},D::AbstractMatrix,E::AbstractMatrix) =
size(A,1) == size(A,2) ? gsylv(A,B,Matrix{eltype(C)}(C*I,size(A)),D,E) :
throw(DimensionMismatch("A must be a square matrix"))
# solve AXB+CXδ = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::Union{UniformScaling,Real,Complex},E::AbstractMatrix) =
size(B,1) == size(B,2) ? gsylv(A,B,C,Matrix{eltype(D)}(D*I,size(B)),E) :
throw(DimensionMismatch("B must be a square matrix"))

"""
    (X,Y) = sylvsys(A,B,C,D,E,F)

Solve the Sylvester system of matrix equations

                AX + YB = C
                DX + YE = F,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues.
# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

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

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

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

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

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

julia> X, Y = sylvsys(A, B, C, D, E, F);

julia> X
2×2 Array{Float64,2}:
  1.388  -1.388
 -0.892   0.892

julia> Y
2×2 Array{Float64,2}:
 -1.788  0.192
  0.236  0.176

julia> A*X + Y*B - C
2×2 Array{Float64,2}:
  6.66134e-16  2.22045e-15
 -3.10862e-15  2.66454e-15

julia> D*X + Y*E - F
2×2 Array{Float64,2}:
  1.33227e-15  -2.22045e-15
 -4.44089e-16   4.44089e-16
```
"""
function sylvsys(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::AbstractMatrix,E::AbstractMatrix,F::AbstractMatrix)

    m, n = size(C);
    (m == size(F,1) && n == size(F,2)) ||
      throw(DimensionMismatch("C and F must have the same dimensions"))
    [m; n; m; n] == LinearAlgebra.checksquare(A,B,D,E) ||
       throw(DimensionMismatch("A, B, C, D, E and F have incompatible dimensions"))
    T2 = promote_type(eltype(A), eltype(B), eltype(C), eltype(D), eltype(E), eltype(F))
    T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(B) == T2 || (B = convert(Matrix{T2},B))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    eltype(D) == T2 || (D = convert(Matrix{T2},D))
    eltype(E) == T2 || (E = convert(Matrix{T2},E))
    eltype(F) == T2 || (F = convert(Matrix{T2},F))

    if isa(A,Adjoint)
      A = copy(A)
    end
    if isa(B,Adjoint)
      B = copy(B)
    end
    if isa(D,Adjoint)
      D = copy(D)
    end
    if isa(E,Adjoint)
      E = copy(E)
    end
    AS, DS, Q1, Z1 = schur(A,D)
    BS, ES, Q2, Z2 = schur(B,E)

    CS = adjoint(Q1) * (C*Z2)
    FS = adjoint(Q1) * (F*Z2)

    X, Y, scale =  tgsyl!('N',AS,BS,CS,DS,ES,FS)

    (rmul!(Z1*(X * adjoint(Z2)), inv(scale)), rmul!(Q1*(Y * adjoint(Q2)), inv(-scale)) )
end
"""
    (X,Y) = dsylvsys(A,B,C,D,E,F)

Solve the dual Sylvester system of matrix equations

       AX + DY = C
       XB + YE = F ,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues.

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

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

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

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

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

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

julia> X, Y = dsylvsys(A, B, C, D, E, F);

julia> X
2×2 Array{Float64,2}:
  2.472  -1.648
 -1.848   1.232

julia> Y
2×2 Array{Float64,2}:
 -0.496  -0.336
  0.264   0.824

julia> A*X + D*Y - C
2×2 Array{Float64,2}:
  4.44089e-16  0.0
 -3.55271e-15  1.55431e-15

julia> X*B + Y*E - F
2×2 Array{Float64,2}:
 -8.88178e-16   0.0
  8.88178e-16  -4.44089e-16
```
"""
function dsylvsys(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::AbstractMatrix,E::AbstractMatrix,F::AbstractMatrix)

    m, n = size(C);
    (m == size(F,1) && n == size(F,2)) ||
      throw(DimensionMismatch("C and F must have the same dimensions"))
    [m; n; m; n] == LinearAlgebra.checksquare(A,B,D,E) ||
       throw(DimensionMismatch("A, B, C, D, E and F have incompatible dimensions"))

    T2 = promote_type(eltype(A), eltype(B), eltype(C), eltype(D), eltype(E), eltype(F))
    T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(B) == T2 || (B = convert(Matrix{T2},B))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    eltype(D) == T2 || (D = convert(Matrix{T2},D))
    eltype(E) == T2 || (E = convert(Matrix{T2},E))
    eltype(F) == T2 || (F = convert(Matrix{T2},F))
    realcase = T2 <: AbstractFloat
    transsylv = isa(A,Adjoint) && isa(B,Adjoint) && isa(D,Adjoint) && isa(E,Adjoint)
    realcase ? trans = 'T' : trans = 'C'
    if transsylv
       AS, DS, Q1, Z1 = schur(A.parent,D.parent)
       BS, ES, Q2, Z2 = schur(B.parent,E.parent)
       CS = adjoint(Z1) * (C*Z2)
       FS = adjoint(Q1) * (F*Q2)
       X, Y, scale =  tgsyl!(trans,AS,BS,CS,DS,ES,-FS)
       (rmul!(Q1*(X * adjoint(Z2)), inv(scale)), rmul!(Q1*(Y * adjoint(Z2)), inv(scale)) )
    else
       AS, DS, Q1, Z1 = schur(copy(A'),copy(D'))
       BS, ES, Q2, Z2 = schur(copy(B'),copy(E'))
       CS = adjoint(Z1) * (C*Z2)
       FS = adjoint(Q1) * (F*Q2)

       X, Y, scale =  tgsyl!(trans,AS,BS,CS,DS,ES,-FS)

       (rmul!(Q1*(X * adjoint(Z2)), inv(scale)), rmul!(Q1*(Y * adjoint(Z2)), inv(scale)) )
    end
end
"""
    sylvcs!(A,B,C; adjA = false, adjB = false)

Solve the continuous Sylvester matrix equation

                op(A)X + Xop(B) =  C,

where `op(A) = A` or `op(A) = A'` if `adjA = false` or `adjA = true`, respectively,
and `op(B) = B` or `op(B) = B'` if `adjB = false` or `adjB = true`, respectively.
`A` and `B` are square matrices in Schur forms, and `A` and `-B` must not have
common eigenvalues. `C` contains on output the solution `X`.

_Note:_ This is an enhanced interface to `LAPACK.trsyl!` also covering the case when
`A` and `B` are real matrices and `C` is a complex matrix.
"""
function sylvcs!(A::T1, B::T1, C::Union{T1,T2}; adjA = false, adjB = false) where
     {T1<:Union{Matrix{Float32},Matrix{Float64}}, T2<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}} }
   """
   This is a wrapper to the LAPACK.trsylv! function for real matrices A and B,
   based on the Bartels-Stewart Schur form based approach.
   For a complex C, two calls of trsyl! are performed (for the real and imaginary parts).
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   T = eltype(A)
   TC = eltype(C)
   TR = real(TC)
   T == TR || error("TypeError: for real part of C expected Type{$T}, got Type{$TR}")
    cmplx = TC <: Complex
   adjA ? TA = 'T' : TA = 'N'
   adjB ? TB = 'T' : TB = 'N'
   if cmplx
      YR = real(C)
      YR, scale = LAPACK.trsyl!(TA, TB, A, B, YR)
      rmul!(YR, inv(scale))
      YI = imag(C)
      YI, scale = LAPACK.trsyl!(TA, TB, A, B, YI)
      rmul!(YI, inv(scale))
      C[:,:] = YR + im*YI
      return C
   else
      C, scale = LAPACK.trsyl!(TA, TB, A, B, C)
      rmul!(C, inv(scale))
      return C
   end
end
function sylvcs!(A::T, B::T, C::T; adjA = false, adjB = false) where  {T<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}}}
   """
   This is a wrapper to the LAPACK.trsylv! function, based on the Bartels-Stewart Schur form based approach.
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   adjA ? TA = 'C' : TA = 'N'
   adjB ? TB = 'C' : TB = 'N'
   C, scale = LAPACK.trsyl!(TA, TB, A, B, C)
   rmul!(C, inv(scale))
end
"""
    sylvds!(A,B,C; adjA = false, adjB = false)

Solve the discrete Sylvester matrix equation

                op(A)Xop(B) + X =  C,

where `op(A) = A` or `op(A) = A'` if `adjA = false` or `adjA = true`, respectively,
and `op(B) = B` or `op(B) = B'` if `adjB = false` or `adjB = true`, respectively.
`A` and `B` are square matrices in Schur forms, and `A` and `-B` must not have
common reciprocal eigenvalues. `C` contains on output the solution `X`.
"""
function sylvds!(A::T1, B::T1, C::Union{T1,T2}; adjA = false, adjB = false) where
   {T1<:Union{Matrix{Float32},Matrix{Float64}}, T2<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}} }
#function sylvds!(A::T, B::T, C::T; adjA = false, adjB = false) where  {T<:Union{Array{Float64,2},Array{Float32,2}}}
   """
   An extension of the Bartels-Stewart Schur form based approach is employed.

   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   m, n = LinearAlgebra.checksquare(A,B)
   (size(C,1) == m && size(C,2) == n ) || throw(DimensionMismatch("C must be an $m x $n matrix"))

   T = eltype(A)
   TC = eltype(C)
   TR = real(TC)
   T == TR || error("TypeError: for real part of C expected Type{$T}, got Type{$TR}")

   # determine the structure of the real Schur form of A
   ba = fill(1,m,1)
   pa = 1
   if m > 1
      d = [diag(A,-1);zeros(1)]
      i = 1
      pa = 0
      while i <= m
         pa += 1
         if d[i] != 0
            ba[pa] = 2
            i += 1
         end
         i += 1
      end
   end
   # determine the structure of the real Schur form of B
   bb = fill(1,n,1)
   pb = 1
   if n > 1
      d = [diag(B,-1);zeros(1)]
      i = 1
      pb = 0
      while i <= n
         pb += 1
         if d[i] != 0
            bb[pb] = 2
            i += 1
         end
         i += 1
      end
   end

   W = fill(zero(eltype(C)),m,2)
   if !adjA && !adjB
      """
      The (K,L)th block of X is determined starting from
      bottom-left corner column by column by

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

      where
                             M
                 R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L) +
                           J=K+1
                             M             L-1
                            SUM { A(K,J) * SUM [X(J,I)*B(I,L)] }.
                            J=K            I=1
      """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = m
          for kk = pa:-1:1
              dk = ba[kk]
              dkk = 1:dk
              i1 = i-dk+1
              k = i1:i
              y = C[k,l]
              if kk < pa
                 ir = i+1:m
                 W1 = A[k,ir]*C[ir,l]
                 y -= W1*B[l,l]
              end
              if ll > 1
                 ic = i1:m
                 W[k,dll] = C[k,il1]*B[il1,l]
                 y -= A[k,ic]*W[ic,dll]
              end
              Z = (kron(transpose(B[l,l]),A[k,k])+I)\(y[:])
              isfinite(maximum(abs.(Z))) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
              i -= dk
          end
          j += dl
      end
   elseif !adjA && adjB
         """
         The (K,L)th block of X is determined starting from
         bottom-right corner column by column by

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

         where
                                M
                    R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L)' +
                              J=K+1
                                M              N
                               SUM { A(K,J) * SUM [X(J,I)*B(L,I)'] }.
                               J=K           I=L+1
         """
         j = n
         for ll = pb:-1:1
             dl = bb[ll]
             dll = 1:dl
             il1 = j+1:n
             l = j-dl+1:j
             i = m
             for kk = pa:-1:1
                 dk = ba[kk]
                 dkk = 1:dk
                 i1 = i-dk+1
                 k = i1:i
                 y = C[k,l]
                 if kk < pa
                    ir = i+1:m
                    W1 = A[k,ir]*C[ir,l]
                    y -= W1*B[l,l]'
                 end
                 if ll < pb
                    ic = i1:m
                    W[k,dll] = C[k,il1]*B[l,il1]'
                    y -= A[k,ic]*W[ic,dll]
                 end
                 Z = (kron(B[l,l],A[k,k])+I)\(y[:])
                 isfinite(maximum(abs.(Z))) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
                 i -= dk
             end
             j -= dl
         end
   elseif adjA && !adjB
      """
      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)*B(L,L) + X(K,L) = C(K,L) - R(K,L),

      where
                            K-1
                 R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L) +
                            J=1
                             K              L-1
                            SUM A(J,K)' * { SUM [X(J,I)*B(I,L)] }.
                            J=1             I=1
      """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              y = C[k,l]
              if kk > 1
                 ir = 1:i-1
                 W1 = A[ir,k]'*C[ir,l]
                 y -= W1*B[l,l]
              end
              if ll > 1
                 ic = 1:i1
                 W[k,dll] = C[k,il1]*B[il1,l]
                 y -= A[ic,k]'*W[ic,dll]
              end
              Z = (kron(transpose(B[l,l]),transpose(A[k,k]))+I)\(y[:])
              isfinite(maximum(abs.(Z))) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
              i += dk
          end
          j += dl
      end
   elseif adjA && adjB
      """
      The (K,L)th block of X is determined starting from the
      lower-left corner column by column by

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

      where
                            K-1
                 R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L)' +
                            J=1
                             K               N
                            SUM A(J,K)' * { SUM [X(J,I)*B(L,I)'] }.
                            J=1            I=L+1
      """
      j = n
      for ll = pb:-1:1
          dl = bb[ll]
          dll = 1:dl
          il1 = j+1:n
          l = j-dl+1:j
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              y = C[k,l]
              if kk > 1
                 ir = 1:i-1
                 W1 = A[ir,k]'*C[ir,l]
                 y -= W1*B[l,l]'
              end
              if ll < pb
                 ic = 1:i1
                 W[k,dll] = C[k,il1]*B[l,il1]'
                 y -= A[ic,k]'*W[ic,dll]
              end
              Z = (kron(B[l,l],transpose(A[k,k]))+I)\(y[:])
              isfinite(maximum(abs.(Z))) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
              i += dk
          end
          j -= dl
      end
   end
   return C
end
function sylvds!(A::T, B::T, C::T; adjA = false, adjB = false) where {T<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}}}
   """
   An extension of the Bartels-Stewart Schur form based approach is employed.

   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   m, n = LinearAlgebra.checksquare(A,B)
   (size(C,1) == m && size(C,2) == n ) || throw(DimensionMismatch("C must be an $m x $n matrix"))

   W = fill(zero(eltype(C)),m,1)
   if !adjA && !adjB
      """
      The (K,L)th element of X is determined starting from
      bottom-left corner column by column by

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

      where
                             M
                 R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L) +
                           J=K+1
                             M             L-1
                            SUM { A(K,J) * SUM [X(J,I)*B(I,L)] }.
                            J=K            I=1
      """
      for l = 1:n
          il1 = 1:l-1
          ll = l:l
          for k = m:-1:1
              y = C[k,l]
              kk = k:k
              if k < m
                 ir = k+1:m
                 W1 = A[kk,ir]*C[ir,ll]
                 y -= W1[1]*B[l,l]
              end
              if l > 1
                 ic = k:m
                 Z = C[kk,il1]*B[il1,ll]
                 W[k,1] = Z[1]
                 TA = A[kk,ic]*W[ic,1]
                 y -= TA[1]
              end
              Z = y/(B[l,l]*A[k,k]+I)
              isfinite(Z) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
             end
      end
   elseif !adjA && adjB
         """
         The (K,L)th element of X is determined starting from
         bottom-right corner column by column by

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

         where
                                M
                    R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L)' +
                              J=K+1
                                M              N
                               SUM { A(K,J) * SUM [X(J,I)*B(L,I)'] }.
                               J=K           I=L+1
         """
         for l = n:-1:1
             ll = l:l
             il1 = l+1:n
             for k = m:-1:1
                 kk = k:k
                 y = C[k,l]
                 if k < m
                    ir = k+1:m
                    W1 = A[kk,ir]*C[ir,ll]
                    y -= W1[1]*B[l,l]'
                 end
                 if l < n
                    ic = k:m
                    Z = C[kk,il1]*B[ll,il1]'
                    W[k,1] = Z[1]
                    TA = A[kk,ic]*W[ic,1]
                    y -= TA[1]
                 end
                 Z = y/(B[l,l]'*A[k,k]+I)
                 isfinite(Z) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
             end
         end
   elseif adjA && !adjB
      """
      The (K,L)th element of X is determined starting from the
      upper-left corner column by column by

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

      where
                            K-1
                 R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L) +
                            J=1
                             K              L-1
                            SUM A(J,K)' * { SUM [X(J,I)*B(I,L)] }.
                            J=1             I=1
      """
      for l = 1:n
          ll = l:l
          il1 = 1:l-1
          for k = 1:m
              kk = k:k
              y = C[k,l]
              if k > 1
                 ir = 1:k-1
                 W1 = A[ir,kk]'*C[ir,ll]
                 y -= W1[1]*B[l,l]
              end
              if l > 1
                 ic = 1:m
                 Z = C[kk,il1]*B[il1,ll]
                 W[k,1] = Z[1]
                 TA = A[ic,kk]'*W[ic,1]
                 y -= TA[1]
              end
              Z = y/(B[l,l]*A[k,k]'+I)
              isfinite(Z) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
          end
      end
   elseif adjA && adjB
      """
      The (K,L)th element of X is determined starting from the
      upper-right corner column by column by

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

      where
                            K-1
                 R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L)' +
                            J=1
                             K               N
                            SUM A(J,K)' * { SUM [X(J,I)*B(L,I)'] }.
                            J=1            I=L+1
      """
      for l = n:-1:1
          ll = l:l
          il1 = l+1:n
          for k = 1:m
              kk = k:k
              y = C[k,l]
              if k > 1
                 ir = 1:k-1
                 W1 = A[ir,kk]'*C[ir,ll]
                 y -= W1[1]*B[l,l]'
              end
              if l < n
                 ic = 1:m
                 Z = C[kk,il1]*B[ll,il1]'
                 W[k,1] = Z[1]
                 TA = A[ic,kk]'*W[ic,1]
                 y -= TA[1]
              end
              Z = y/(B[l,l]'*A[k,k]'+I)
              isfinite(Z) ? C[k,l] = Z : throw("SingularException: A and -B have common or close reciprocal eigenvalues")
          end
      end
   end
   return C
end
"""
    X = gsylvs!(A,B,C,D,E; adjAC=false, adjBD=false, DBSchur = false)

Solve the generalized Sylvester matrix equation

                op1(A)Xop2(B) + op1(C)Xop2(D) = E,

where `A`, `B`, `C` and `D` are square matrices, and

`op1(A) = A` and `op1(C) = C` if `adjAC = false`;

`op1(A) = A'` and `op1(C) = C'` if `adjAC = true`;

`op2(B) = B` and `op2(D) = D` if `adjBD = false`;

`op2(B) = B'` and `op2(D) = D'` if `adjBD = true`.

The matrix pair `(A,C)` is in a generalized real or complex Schur form.
The matrix pair `(B,D)` is in a generalized real or complex Schur form if `DBSchur = false`
or the matrix pair `(D,B)` is in a generalized real or complex Schur form if `DBSchur = true`.
The pencils `A-λC` and `D+λB` must be regular and must not have common eigenvalues.
"""
function gsylvs!(A::T1, B::T1, C::T1, D::T1, E::Union{T1,T2}; adjAC = false, adjBD = false, CASchur = false, DBSchur = false) where
   {T1<:Union{Matrix{Float32},Matrix{Float64}}, T2<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}} }
   """
   An extension proposed in [1] of the Bartels-Stewart Schur form based approach [2] is employed.

   References:
   [1] K.-W. E. Chu. The solution of the matrix equation AXB – CXD = E and
       (YA – DZ, YC– BZ) = (E, F). Lin. Alg. Appl., 93:93-105, 1987.
   [2] R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
       Comm. ACM, 15:820–826, 1972.
   """
   m, n = size(E);
   [m; n; m; n] == LinearAlgebra.checksquare(A,B,C,D) ||
      throw(DimensionMismatch("A, B, C, D and E have incompatible dimensions"))

   T = eltype(A)
   TE = eltype(E)
   TR = real(TE)
   T == TR || error("TypeError: for real part of E expected Type{$T}, got Type{$TR}")

   # determine the structure of the generalized real Schur form of (A,C)
   ba = fill(1,m,1)
   pa = 1
   if m > 1
      CASchur ? d = [diag(C,-1);zeros(1)] : d = [diag(A,-1);zeros(1)]
      i = 1
      pa = 0
      while i <= m
         pa += 1
         if d[i] != 0
            ba[pa] = 2
            i += 1
         end
         i += 1
      end
   end
   # determine the structure of the generalized real Schur form of (B,D)
   bb = fill(1,n,1)
   pb = 1
   if n > 1
      DBSchur ? d = [diag(D,-1);zeros(1)] : d = [diag(B,-1);zeros(1)]
      i = 1
      pb = 0
      while i <= n
         pb += 1
         if d[i] != 0
            bb[pb] = 2
            i += 1
         end
         i += 1
      end
   end

   WB = fill(zero(eltype(E)),m,2)
   WD = fill(zero(eltype(E)),m,2)
   if !adjAC && !adjBD
      """
      The (K,L)th block of X is determined starting from
      bottom-left corner column by column by

            A(K,K)*X(K,L)*B(L,L) + C(K,K)*X(K,L)*D(L,L) = E(K,L) - R(K,L)

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

                             M
                          { SUM [C(K,J)*X(J,L)] } * D(L,L) +
                           J=K+1
                             M             L-1
                            SUM { C(K,J) * SUM [X(J,I)*D(I,L)] }.
                            J=K            I=1
      """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = m
          for kk = pa:-1:1
              dk = ba[kk]
              dkk = 1:dk
              i1 = i-dk+1
              k = i1:i
              y = E[k,l]
              if kk < pa
                 ir = i+1:m
                 W1 = A[k,ir]*E[ir,l]
                 y -= W1*B[l,l]
                 W1 = C[k,ir]*E[ir,l]
                 y -= W1*D[l,l]
              end
              if ll > 1
                 ic = i1:m
                 WB[k,dll] = E[k,il1]*B[il1,l]
                 WD[k,dll] = E[k,il1]*D[il1,l]
                 y -= (A[k,ic]*WB[ic,dll] + C[k,ic]*WD[ic,dll])
              end
              Z = (kron(transpose(B[l,l]),A[k,k])+kron(transpose(D[l,l]),C[k,k]))\(y[:])
              isfinite(maximum(abs.(Z))) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
              i -= dk
          end
          j += dl
      end
   elseif !adjAC && adjBD
         """
          The (K,L)th block of X is determined starting from
          bottom-right corner column by column by

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

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

                               M
                            { SUM [C(K,J)*X(J,L)] } * D(L,L)' +
                             J=K+1
                               M              N
                              SUM { C(K,J) * SUM [X(J,I)*D(L,I)'] }.
                              J=K           I=L+1
         """
         j = n
         for ll = pb:-1:1
             dl = bb[ll]
             dll = 1:dl
             il1 = j+1:n
             l = j-dl+1:j
             i = m
             for kk = pa:-1:1
                 dk = ba[kk]
                 dkk = 1:dk
                 i1 = i-dk+1
                 k = i1:i
                 y = E[k,l]
                 if kk < pa
                    ir = i+1:m
                    W1 = A[k,ir]*E[ir,l]
                    y -= W1*B[l,l]'
                    W2 = C[k,ir]*E[ir,l]
                    y -= W2*D[l,l]'
                 end
                 if ll < pb
                    ic = i1:m
                    WB[k,dll] = E[k,il1]*B[l,il1]'
                    WD[k,dll] = E[k,il1]*D[l,il1]'
                    y -= (A[k,ic]*WB[ic,dll]+C[k,ic]*WD[ic,dll])
                 end
                 Z = (kron(B[l,l],A[k,k])+kron(D[l,l],C[k,k]))\(y[:])
                 isfinite(maximum(abs.(Z))) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
                 i -= dk
             end
             j -= dl
         end
   elseif adjAC && !adjBD
      """
      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)*B(L,L) + C(K,K)'*X(K,L)*D(L,L) = E(K,L) - R(K,L),

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

                            K-1
                          { SUM [C(J,K)'*X(J,L)] } * D(L,L) +
                            J=1
                             K              L-1
                            SUM C(J,K)' * { SUM [X(J,I)*D(I,L)] }.
                            J=1             I=1
      """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              y = E[k,l]
              if kk > 1
                 ir = 1:i-1
                 W1 = A[ir,k]'*E[ir,l]
                 y -= W1*B[l,l]
                 W2 = C[ir,k]'*E[ir,l]
                 y -= W2*D[l,l]
              end
              if ll > 1
                 ic = 1:i1
                 WB[k,dll] = E[k,il1]*B[il1,l]
                 y -= A[ic,k]'*WB[ic,dll]
                 WD[k,dll] = E[k,il1]*D[il1,l]
                 y -= C[ic,k]'*WD[ic,dll]
              end
              Z = (kron(transpose(B[l,l]),transpose(A[k,k]))+kron(transpose(D[l,l]),transpose(C[k,k])))\(y[:])
              isfinite(maximum(abs.(Z))) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
              i += dk
          end
          j += dl
      end
   elseif adjAC && adjBD
      """
      The (K,L)th block of X is determined starting from
      upper-rght corner column by column by

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

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

                            K-1
                          { SUM [C(J,K)'*X(J,L)] } * D(L,L)' +
                            J=1
                             K               N
                            SUM C(J,K)' * { SUM [X(J,I)*D(L,I)'] }.
                            J=1            I=L+1
      """
      j = n
      for ll = pb:-1:1
          dl = bb[ll]
          dll = 1:dl
          il1 = j+1:n
          l = j-dl+1:j
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              y = E[k,l]
              if kk > 1
                 ir = 1:i-1
                 W1 = A[ir,k]'*E[ir,l]
                 y -= W1*B[l,l]'
                 W2 = C[ir,k]'*E[ir,l]
                 y -= W2*D[l,l]'
              end
              if ll < pb
                 ic = 1:i1
                 WB[k,dll] = E[k,il1]*B[l,il1]'
                 WD[k,dll] = E[k,il1]*D[l,il1]'
                 y -= (A[ic,k]'*WB[ic,dll] + C[ic,k]'*WD[ic,dll])
              end
              Z = (kron(B[l,l],transpose(A[k,k]))+kron(D[l,l],transpose(C[k,k])))\(y[:])
              isfinite(maximum(abs.(Z))) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
              i += dk
          end
          j -= dl
      end
   end
   return E
end
function gsylvs!(A::T, B::T, C::T, D::T, E::T; adjAC = false, adjBD = false, CASchur = false, DBSchur = false) where {T<:Union{Array{Complex{Float64},2},Array{Complex{Float32},2}}}
   """
   An extension proposed in [1] of the Bartels-Stewart Schur form based approach [2] is employed.

   References:
   [1] K.-W. E. Chu. The solution of the matrix equation AXB – CXD = E and
       (YA – DZ, YC– BZ) = (E, F). Lin. Alg. Appl., 93:93-105, 1987.
   [2] R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
       Comm. ACM, 15:820–826, 1972.
   """
   m, n = size(E);
   [m; n; m; n] == LinearAlgebra.checksquare(A,B,C,D) ||
      throw(DimensionMismatch("A, B, C, D and E have incompatible dimensions"))

   WB = fill(zero(eltype(E)),m,1)
   WD = fill(zero(eltype(E)),m,1)
   if !adjAC && !adjBD
      """
      The (K,L)th element of X is determined starting from
      bottom-left corner column by column by

            A(K,K)*X(K,L)*B(L,L) +C(K,K)*X(K,L)*D(L,L) = E(K,L) - R(K,L)

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

                            M
                         { SUM [C(K,J)*X(J,L)] } * D(L,L) +
                          J=K+1
                            M             L-1
                           SUM { C(K,J) * SUM [X(J,I)*D(I,L)] } +
                           J=K            I=1
      """
      for l = 1:n
          il1 = 1:l-1
          ll = l:l
          for k = m:-1:1
              y = E[k,l]
              kk = k:k
              if k < m
                 ir = k+1:m
                 W1 = A[kk,ir]*E[ir,ll]
                 W2 = C[kk,ir]*E[ir,ll]
                 y -= (W1[1]*B[l,l]+W2[1]*D[l,l])
              end
              if l > 1
                 ic = k:m
                 ZB = E[kk,il1]*B[il1,ll]
                 ZD = E[kk,il1]*D[il1,ll]
                 WB[k,1] = ZB[1]
                 WD[k,1] = ZD[1]
                 TB = A[kk,ic]*WB[ic,1]
                 TD = C[kk,ic]*WD[ic,1]
                 y -= (TB[1]+TD[1])
              end
              Z = y/(B[l,l]*A[k,k]+D[l,l]*C[k,k])
              isfinite(Z) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
          end
      end
   elseif !adjAC && adjBD
         """
          The (K,L)th element of X is determined starting from
          bottom-right corner column by column by

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

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

                               M
                            { SUM [C(K,J)*X(J,L)] } * D(L,L)' +
                             J=K+1
                               M              N
                              SUM { C(K,J) * SUM [X(J,I)*D(L,I)'] }.
                              J=K           I=L+1
         """
         for l = n:-1:1
             ll = l:l
             il1 = l+1:n
             for k = m:-1:1
                 kk = k:k
                 y = E[k,l]
                 if k < m
                    ir = k+1:m
                    W1 = A[kk,ir]*E[ir,ll]
                    W2 = C[kk,ir]*E[ir,ll]
                    y -= (W1[1]*B[l,l]'+W2[1]*D[l,l]')
                 end
                 if l < n
                    ic = k:m
                    ZB = E[kk,il1]*B[ll,il1]'
                    ZD = E[kk,il1]*D[ll,il1]'
                    WB[k,1] = ZB[1]
                    WD[k,1] = ZD[1]
                    TB = A[kk,ic]*WB[ic,1]
                    TD = C[kk,ic]*WD[ic,1]
                    y -= (TB[1]+TD[1])
                 end
                 Z = y/(B[l,l]'*A[k,k]+D[l,l]'*C[k,k])
                 isfinite(Z) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
             end
         end
   elseif adjAC && !adjBD
      """
      The (K,L)th element of X is determined starting from the
      upper-left corner column by column by

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

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

                            K-1
                          { SUM [C(J,K)'*X(J,L)] } * D(L,L) +
                            J=1
                             K              L-1
                            SUM C(J,K)' * { SUM [X(J,I)*D(I,L)] }.
                            J=1             I=1
      """
      for l = 1:n
          ll = l:l
          il1 = 1:l-1
          for k = 1:m
              kk = k:k
              y = E[k,l]
              if k > 1
                 ir = 1:k-1
                 W1 = A[ir,kk]'*E[ir,ll]
                 W2 = C[ir,kk]'*E[ir,ll]
                 y -= (W1[1]*B[l,l] + W2[1]*D[l,l])
              end
              if l > 1
                 ic = 1:m
                 ZB = E[kk,il1]*B[il1,ll]
                 ZD = E[kk,il1]*D[il1,ll]
                 WB[k,1] = ZB[1]
                 WD[k,1] = ZD[1]
                 TB = A[ic,kk]'*WB[ic,1]
                 TD = C[ic,kk]'*WD[ic,1]
                 y -= (TB[1]+TD[1])
              end
              Z = y/(B[l,l]*A[k,k]'+D[l,l]*C[k,k]')
              isfinite(Z) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
          end
      end
   elseif adjAC && adjBD
      """
      The (K,L)th element of X is determined starting from
      upper-rght corner column by column by

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

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

                            K-1
                          { SUM [C(J,K)'*X(J,L)] } * D(L,L)' +
                            J=1
                             K               N
                            SUM C(J,K)' * { SUM [X(J,I)*D(L,I)'] }.
                            J=1            I=L+1
      """
      for l = n:-1:1
          ll = l:l
          il1 = l+1:n
          for k = 1:m
              kk = k:k
              y = E[k,l]
              if k > 1
                 ir = 1:k-1
                 W1 = A[ir,kk]'*E[ir,ll]
                 W2 = C[ir,kk]'*E[ir,ll]
                 y -= (W1[1]*B[l,l]'+W2[1]*D[l,l]')
              end
              if l < n
                 ic = 1:m
                 ZB = E[kk,il1]*B[ll,il1]'
                 ZD = E[kk,il1]*D[ll,il1]'
                 WB[k,1] = ZB[1]
                 WD[k,1] = ZD[1]
                 TB = A[ic,kk]'*WB[ic,1]
                 TD = C[ic,kk]'*WD[ic,1]
                 y -= (TB[1]+TD[1])
              end
              Z = y/(B[l,l]'*A[k,k]'+D[l,l]'*C[k,k]')
              isfinite(Z) ? E[k,l] = Z : throw("SingularException: A-λC and D+λB have common or close eigenvalues")
          end
      end
   end
   return E
end
"""
    (X,Y) = sylvsyss!(A,B,C,D,E,F)

Solve the Sylvester system of matrix equations

                AX + YB = C
                DX + YE = F,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size in generalized Schur forms.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues. The computed
solution `(X,Y)` is contained in `(C,F)`.

_Note:_ This is an enhanced interface to the `LAPACK.tgsyl!` function to also cover the case when
`A`, `B`, `D` and `E` are real matrices and `C` and `F` are complex matrices.
"""
function sylvsyss!(A::T, B::T, C::T, D::T, E::T, F::T) where {T<:Union{Array{Complex{Float64},2},Array{Complex{Float32},2}}}
   """
   This is a wrapper to the complex LAPACK.tgsyl! function with `trans = 'N'`.
   """
   C, F, scale =  tgsyl!('N',A,B,C,D,E,F)
   return rmul!(C,inv(scale)), rmul!(F,inv(-scale))
end
function sylvsyss!(A::T1, B::T1, C::Union{T1,T2}, D::T1, E::T1, F::Union{T1,T2}) where
   {T1<:Union{Matrix{Float32},Matrix{Float64}}, T2<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}} }
   """
   This is an enhanced interface to the real LAPACK.tgsyl! function with `trans = 'N'`.
   For a complex pair (C,F), two calls of tgsyl! are performed (for the real and imaginary parts).
   """
   T = eltype(A)
   TC = promote_type(eltype(C),eltype(F))
   TR = real(TC)
   T == TR || error("TypeError: for real parts of (C,F) expected Type{$T}, got Type{$TR}")
   cmplx = TC <: Complex
   if cmplx
      XR = real(C)
      XI = imag(C)
      YR = real(F)
      YI = imag(F)
      XR, YR, scale =  tgsyl!('N',A,B,XR,D,E,YR)
      rmul!(XR,inv(scale))
      rmul!(YR,inv(-scale))
      XI, YI, scale =  tgsyl!('N',A,B,XI,D,E,YI)
      rmul!(XI,inv(scale))
      rmul!(YI,inv(-scale))
      C = XR+im*XI
      F = YR+im*YI
      return C[:,:], F[:,:]
   else
      C, F, scale =  tgsyl!('N',A,B,C,D,E,F)
      return rmul!(C,inv(scale)), rmul!(F,inv(-scale))
   end
end
"""
    (X,Y) = dsylvsyss!(A,B,C,D,E,F)

Solve the dual Sylvester system of matrix equations

    A'X + D'Y = C
    XB' + YE' = F,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size in generalized Schur forms.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues. The computed
solution `(X,Y)` is contained in `(C,F)`.

_Note:_ This is an enhanced interface to the `LAPACK.tgsyl!` function to also cover the case when
`A`, `B`, `D` and `E` are real matrices and `C` and `F` are complex matrices.
"""
function dsylvsyss!(A::T, B::T, C::T, D::T, E::T, F::T) where {T<:Union{Array{Complex{Float64},2},Array{Complex{Float32},2}}}
   """
   This is an interface to the complex LAPACK.tgsyl! function with `trans = 'C'`.
   """
   MF = -F
   E, F, scale =  tgsyl!('C',A,B,C,D,E,MF)
   F = MF
   return rmul!(C[:,:],inv(scale)), rmul!(F[:,:],inv(scale))
end
function dsylvsyss!(A::T1, B::T1, C::Union{T1,T2}, D::T1, E::T1, F::Union{T1,T2}) where
   {T1<:Union{Matrix{Float32},Matrix{Float64}}, T2<:Union{Matrix{Complex{Float64}},Matrix{Complex{Float32}}} }
   """
   This is an interface to the real LAPACK.tgsyl! function with `trans = 'T'`.
   For a complex pair (C,F), two calls of tgsyl! are performed (for the real and imaginary parts).
   """
   T = eltype(A)
   TC = promote_type(eltype(C),eltype(F))
   TR = real(TC)
   T == TR || error("TypeError: for real parts of (C,F) expected Type{$T}, got Type{$TR}")
   cmplx = TC <: Complex
   if cmplx
      XR = real(C)
      XI = imag(C)
      YR = -real(F)
      YI = -imag(F)
      XR, YR, scale =  tgsyl!('T',A,B,XR,D,E,YR)
      rmul!(XR,inv(scale))
      rmul!(YR,inv(scale))
      XI, YI, scale =  tgsyl!('T',A,B,XI,D,E,YI)
      rmul!(XI,inv(scale))
      rmul!(YI,inv(scale))
      C = XR+im*XI
      F = YR+im*YI
      return C[:,:], F[:,:]
   else
      MF = -F
      C, MF, scale =  tgsyl!('T',A,B,C,D,E,MF)
      F = MF
      return rmul!(C[:,:],inv(scale)), rmul!(F[:,:],inv(scale))
   end
end


"""
    step_size(::ForwardMode, x::AbstractFloat)

# Description

Determine the step-size to use for forward-mode finite differences.

# Arguments

* `::ForwardMode`: An instance of the `ForwardMode` type.
* `x::AbstractFloat`: A point at which the derivative of a function will be
    approximated. Its type must implement `eps`.

# Returns

* `ϵ::Real`: The step-size to use for finite differences at `x`.

# References

See Section 5.7 of Numerical Recipes in C for the mathematical justification
for working with `sqrt(eps(typeof(x)))`.

# Examples

```julia
import FiniteDiff: step_size, ForwardMode
ϵ = step_size(ForwardMode(), 0.0)
```
"""
@inline function step_size(::ForwardMode, x::AbstractFloat)::Real
    return sqrt(eps(typeof(x))) * max(one(typeof(x)), abs(x))
end

"""
    step_size(::BackwardMode, x::AbstractFloat)

# Description

Determine the step-size to use for backward-mode finite differences.

# Arguments

* `::BackwardMode`: An instance of the `BackwardMode` type.
* `x::AbstractFloat`: A point at which the derivative of a function will be
    approximated. Its type must implement `eps`.

# Returns

* `ϵ::Real`: The step-size to use for finite differences at `x`.

# References

See Section 5.7 of Numerical Recipes in C for the mathematical justification
for working with `sqrt(eps(typeof(x)))`.

# Examples

```julia
import FiniteDiff: step_size, BackwardMode
ϵ = step_size(BackwardMode(), 0.0)
```
"""
@inline function step_size(::BackwardMode, x::AbstractFloat)::Real
    return sqrt(eps(typeof(x))) * max(one(typeof(x)), abs(x))
end

"""
    step_size(::CentralMode, x::AbstractFloat)

# Description

Determine the step-size to use for central-mode finite differences.

# Arguments

* `::CentralMode`: An instance of the `CentralMode` type.
* `x::AbstractFloat`: A point at which the derivative of a function will be
    approximated. Its type must implement `eps`.

# Returns

* `ϵ::Real`: The step-size to use for finite differences at `x`.

# References

See Section 5.7 of Numerical Recipes in C for the mathematical justification
for working with `cbrt(eps(typeof(x)))`.

# Examples

```julia
import FiniteDiff: step_size, CentralMode
ϵ = step_size(CentralMode(), 0.0)
```
"""
@inline function step_size(::CentralMode, x::AbstractFloat)::Real
    return cbrt(eps(typeof(x))) * max(one(typeof(x)), abs(x))
end

"""
    step_size(::ComplexMode, x::AbstractFloat)

# Description

Determine the step-size to use for complex-mode finite differences.

# Arguments

* `::ComplexMode`: An instance of the `ComplexMode` type.
* `x::AbstractFloat`: A point at which the derivative of a function will be
    approximated. Its type must implement `eps`.

# Returns

* `ϵ::Real`: The step-size to use for finite differences at `x`.

# References

See "The Complex-Step Derivative Approximation" by Martins, Sturdza and Alonso
(2003) for the mathematical justification for working with `eps(x)`.

# Examples

```julia
import FiniteDiff: step_size, ComplexMode
ϵ = step_size(ComplexMode(), 0.0)
```
"""
@inline function step_size(::ComplexMode, x::AbstractFloat)::Real
    return eps(typeof(x))
end

"""
    step_size(::HessianMode, x::AbstractFloat)

# Description

Determine the step-size to use for finite differences of hessians.

# Arguments

* `::HessianMode`: An instance of the `HessianMode` type.
* `x::AbstractFloat`: A point at which the derivative of a function will be
    approximated. Its type must implement `eps`.

# Returns

* `ϵ::Real`: The step-size to use for finite differences at `x`.

# References

See Section 5.7 of Numerical Recipes in C for the mathematical justification
for working with `eps(x)^(1 // 4)`.

# Examples

```julia
import FiniteDiff: step_size, HessianMode
ϵ = step_size(HessianMode(), 0.0)
```
"""
@inline function step_size(::HessianMode, x::AbstractFloat)::Real
    return eps(typeof(x))^(1 // 4) * max(one(typeof(x)), abs(x))
end


"""
    evalcompositelogisticprobit(x::T, a, b)::T where T <: Real

evaluates the map from (0,1) to (0,1).
returns 1/(1 + exp(-a*(log(x/(1-x))-b)))

    evalcompositelogisticprobit(x::T, a, b, lb, ub)::T where T <: Real
evaluates the map from (lb, ub) to (lb, ub), by mapping (lb,ub) to (0,1), run evalcompositelogisticprobit(), then map (0,1) to (lb,ub).
"""
function evalcompositelogisticprobit(x::T, a, b)::T where T <: Real

    return 1/(1 + exp(-a*(log(x/(1-x))-b)))
end

function evalcompositelogisticprobit(x_inp::T, a, b, lb, ub)::T where T <: Real

    x = convertcompactdomain(x_inp, lb, ub, zero(T), one(T))
    y = evalcompositelogisticprobit(x, a, b)
    y_out = convertcompactdomain(y, zero(T), one(T), lb, ub)

    return y_out
end

function evalinversecompositelogisticprobit(y::T, a, b)::T where T <: Real
    return exp(b)/(exp(b) + (-1 + 1/y)^(1/a))
end

function evalinversecompositelogisticprobit(y_inp::T, a, b, lb, ub)::T where T <: Real

    y = convertcompactdomain(y_inp, lb, ub, zero(T), one(T))
    x = evalinversecompositelogisticprobit(y, a, b)
    x_out = convertcompactdomain(x, zero(T), one(T), lb, ub)

    return x_out
end

function eval1Dnumericalinverse(f::Function,
    y::T,
    x0::T,
    a::T,
    b::T,
    max_iters::Int) where T <: Real

    @assert a < b

    obj_func = xx->((f(xx[1])-y)^2)::T

    op = Optim.Options( iterations = max_iters,
                         store_trace = false,
                         show_trace = false)

    results = Optim.optimize(   obj_func,
                                [x0],
                                Optim.NewtonTrustRegion(),
                                op)

    x_star = results.minimizer
    x_out = clamp(x_star[1], a, b)

    return x_out, results
end


function fibonacci(x::Int)
  if x <= 1
      return x
  else
      return fibonacci(x - 1) + fibonacci(x - 2)
  end
  return 0
end


# Support Vector Machines Module
module SVM
export WeightsLinearSVM, train_linear
export WeightsSVM, train
export cost, predict, predict_proba

import Random
import Statistics

include("../tools.jl")
import .JuTools

"""
The weight data struct for training linear SVM
"""
mutable struct WeightsLinearSVM
    C::AbstractFloat
    w::Array{T} where T<:AbstractFloat
    b::AbstractFloat
end

"""
Cost function for linear SVM\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`weights.w` has shape (N,)\\
Returns the computed loss
"""
function cost(X_data::Array{T} where T<:Number, Y_data::Array{T} where T<:Number, weights::WeightsLinearSVM)::AbstractFloat
    @assert ndims(Y_data) == ndims(weights.w) == 1
    @assert size(X_data) == (size(Y_data)[1], size(weights.w)[1])
    loss_w = 0.5 * (weights.w' * weights.w)
    loss_inner = 1.0 .- Y_data .* vec(X_data * weights.w .+ weights.b)
    loss_inner .= map(m->max(0.0,m), loss_inner)
    loss = loss_w + weights.C * sum(loss_inner) / size(X_data)[1]
    return loss
end

"""
Learning function for linear SVM, using Gradient Descent\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`weights.w` has shape (N,)\\
Update the weights in place
"""
function learn!(X_data::Array{T} where T<:Number, Y_data::Array{T} where T<:Number, weights::WeightsLinearSVM, momentum::WeightsLinearSVM, alpha::AbstractFloat)
    @assert ndims(Y_data) == ndims(weights.w) == 1
    @assert size(X_data) == (size(Y_data)[1], size(weights.w)[1])
    # compute deciding feature
    decide = (Y_data .* (X_data * weights.w .+ weights.b)) .< 1 # (? < 1) will be 1, otherwise 0
    # update w
    gradient_w = weights.w .+ (weights.C / size(X_data)[1]) .* vec(-(Y_data .* decide)' * X_data)
    gradient_w .= gradient_w .* alpha
    momentum.w .= gradient_w .+ (0.9 .* momentum.w)
    weights.w .= weights.w .- momentum.w
    # update b
    gradient_b = (weights.C / size(X_data)[1]) * sum(-(Y_data .* decide))
    gradient_b *= alpha
    momentum.b = gradient_b + (0.9 * momentum.b)
    weights.b = weights.b - momentum.b
    return nothing
end

"""
Prediction function for linear SVM\\
If `X_data` has shape (M,N)\\
`weights.w` has shape (N,)\\
Returns the actual computed values
"""
function predict_proba(X_predict::Array{T} where T<:Number, weights::WeightsLinearSVM)::Array
    @assert 1 <= ndims(X_predict) <= 2
    if ndims(X_predict) == 1
        X_predict = reshape(X_predict, (1, size(X_predict)[1]))
    end
    @assert size(X_predict)[2] == size(weights.w)[1]
    prediction = vec(X_predict * weights.w .+ weights.b)
    return prediction
end

"""
Prediction function for linear SVM\\
If `X_data` has shape (M,N)\\
`weights.w` has shape (N,)\\
Returns the converted predictions, in {-1, 1}
"""
function predict(X_predict::Array{T} where T<:Number, weights::WeightsLinearSVM)::Array
    @assert 1 <= ndims(X_predict) <= 2
    if ndims(X_predict) == 1
        X_predict = reshape(X_predict, (1, size(X_predict)[1]))
    end
    @assert size(X_predict)[2] == size(weights.w)[1]
    prediction = vec(X_predict * weights.w .+ weights.b)
    prediction .= map(m -> m >= 0 ? 1.0 : -1.0, prediction)
    return prediction
end

"""
Training function for linear SVM\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`C` is the constraint for SVM decision margin\\
`max_iter` should be >= 0
------
Returns trained weights as `WeightsLinearSVM` object

------
Set `early_stop` to `false`, to force run maximum iteractions\\
Set `random_weights` to `false` to initialize weights of 0.0
"""
function train_linear(X_data::Array{T} where T<:Number, Y_data::Array{T} where T<:Number, C::AbstractFloat;
        learning_rate::AbstractFloat=0.1, max_iter::Integer=1000, verbose::Bool=false, random_weights::Bool=true)::WeightsLinearSVM
    @assert ndims(X_data) == ndims(Y_data) + 1 == 2
    @assert size(X_data)[1] == size(Y_data)[1]
    @assert max_iter >= 0
    X_data = Float64.(X_data)
    Y_data = Float64.(Y_data)
    # is it better to use zero weights than normal weights ?
    weights = nothing
    if random_weights
        weights = WeightsLinearSVM(C, Random.randn(size(X_data)[2]), Random.randn())
    else
        weights = WeightsLinearSVM(C, zeros(size(X_data)[2]), 0.0)
    end
    momentum = WeightsLinearSVM(C, zeros(size(X_data)[2]), 0.0)
    best_weights = WeightsLinearSVM(C, copy(weights.w), weights.b)
    best_cost = nothing
    for i in 1:max_iter
        JuTools.shuffle_data!(X_data, Y_data)
        learn!(X_data, Y_data, weights, momentum, learning_rate)
        new_cost = cost(X_data, Y_data, weights)
        if verbose
            acc = JuTools.compute_accuracy(predict(X_data, weights), Y_data)
            println("Iter: $i")
            println("Cost = $new_cost")
            println("Accuracy = $acc")
            println()
        end
        if best_cost === nothing
            best_cost = new_cost
        else
            if new_cost <= best_cost
                best_cost = min(new_cost, best_cost)
                best_weights.w = copy(weights.w)
                best_weights.b = weights.b
            end
        end
    end
    return best_weights
end

"""
The weight data struct for training common SVM (with kernels)
"""
mutable struct WeightsSVM
    C::AbstractFloat                # constraint
    b::AbstractFloat                # threshold
    gamma::AbstractFloat            # parameter used for polynomial, rbf, and sigmoid kernels
    r::AbstractFloat                # parameter used for polynomial, and sigmoid kernels
    d::AbstractFloat                # parameter used for polynomial kernel
    tol_alpha::AbstractFloat        # tolerance for alpha
    tol_error::AbstractFloat        # tolerance for error
    alpha::Array{T} where T<:Number # alpha array
    error::Array{T} where T<:Number # array for error cache
    kernel::String                  # kernel function name
end

"""
Kernel Function (linear)
"""
function kernel_linear(X1::Array{T} where T<:Number, X2::Array{T} where T<:Number)::Array
    @assert ndims(X1) == ndims(X2) == 2
    @assert size(X1)[2] == size(X2)[2]
    result = X1 * X2'
    return result
end
"""
Kernel Function (polynomial)\\
`d` is `degree` parameter in training function\\
`r` is `coef` parameter in training function\\
`gamma` is computed from `gamma` parameter in training function
"""
function kernel_polynomial(X1::Array{T} where T<:Number, X2::Array{T} where T<:Number;
        d::AbstractFloat=1.0, r::AbstractFloat=0.0, gamma::AbstractFloat=1.0)::Array
    @assert ndims(X1) == ndims(X2) == 2
    @assert size(X1)[2] == size(X2)[2]
    result = (gamma .* (X1 * X2') .+ r) .^ d
    return result
end
"""
Kernel Function (rbf)\\
`gamma` is computed from `gamma` parameter in training function
"""
function kernel_rbf(X1::Array{T} where T<:Number, X2::Array{T} where T<:Number; gamma::AbstractFloat=1.0)::Array
    @assert ndims(X1) == ndims(X2) == 2
    @assert size(X1)[2] == size(X2)[2]
    result = (sum(X1 .^ 2, dims=2) * ones(size(X2)[1])') .+ (ones(size(X1)[1]) * sum(X2 .^ 2, dims=2)') .- 2.0 .* (X1 * X2')
    result .= broadcast(m->max(0.0, m), result) # ignore very small negative outputs, due to precision
    result .= sqrt.(result)
    result .= (-gamma) .* result
    result .= exp.(result)
    return result
end
"""
Kernel Function (sigmoid)\\
`r` is `coef` parameter in training function\\
`gamma` is computed from `gamma` parameter in training function
"""
function kernel_sigmoid(X1::Array{T} where T<:Number, X2::Array{T} where T<:Number; gamma::AbstractFloat=1.0, r::AbstractFloat=0.0)::Array
    @assert ndims(X1) == ndims(X2) == 2
    @assert size(X1)[2] == size(X2)[2]
    result = gamma .* (X1 * X2') .+ r
    result .= tanh.(result)
    return result
end

"""
Cost function for SVM\\
If `X_data` has shape (M,N)\\
`weights.alpha` has shape (M,)\\
Returns the computed cost\\
__Note__:\\
Cost is the opposite of objective value
"""
function cost(X_data::Array{T} where T<:Number, weights::WeightsSVM)::AbstractFloat
    @assert ndims(X_data) == ndims(weights.alpha) + 1 == 2
    @assert size(X_data)[1] == size(weights.alpha)[1]
    result = nothing
    if weights.kernel == "linear"
        result = kernel_linear(X_data, X_data)
    elseif weights.kernel == "polynomial"
        result = kernel_polynomial(X_data, X_data, d=weights.d, r=weights.r, gamma=weights.gamma)
    elseif weights.kernel == "rbf"
        result = kernel_rbf(X_data, X_data, gamma=weights.gamma)
    elseif weights.kernel == "sigmoid"
        result = kernel_sigmoid(X_data, X_data, gamma=weights.gamma, r=weights.r)
    else
        throw(ArgumentError("Error: kernel function $weights.kernel is not recognized"))
    end
    result = 0.5 * (weights.alpha' * result * weights.alpha) - sum(weights.alpha)
    return result
end

"""
Step function for learning SVM\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`weights.alpha` has shape (M,)\\
`weights.error` has shape (M,)\\
`id1` and `id2` are index in `weights.alpha`\\
Tries to update `weights.alpha` in place\\
Returns `1` if step further, else `0`
"""
function learn_step!(X_data::Array{T} where T<:Number, Y_data::Array{T} where T<:Number,
        weights::WeightsSVM, id1::Integer, id2::Integer)::Integer
    @assert ndims(X_data) == ndims(Y_data) + 1 == 2
    @assert size(X_data)[1] == size(Y_data)[1]
    @assert size(X_data)[1] == size(weights.alpha)[1]
    @assert size(weights.error) == size(weights.alpha)
    @assert id1 >= 1
    @assert id2 >= 1
    # if choosing same alpha, skip
    if id1 == id2
        return 0
    end
    # prepare data
    alpha1 = weights.alpha[id1]
    alpha2 = weights.alpha[id2]
    Y1 = Y_data[id1]
    Y2 = Y_data[id2]
    error1 = weights.error[id1]
    error2 = weights.error[id2]
    # compute L & H
    L = nothing
    H = nothing
    if Y1 != Y2
        L = max(0.0, alpha2 - alpha1)
        H = min(weights.C, weights.C + alpha2 - alpha1)
    else
        L = max(0.0, alpha1 + alpha2 - weights.C)
        H = min(weights.C, alpha1 + alpha2)
    end
    if L == H
        return 0
    end
    # compute kernel results and 2nd derivative eta
    k11 = nothing
    k12 = nothing
    k22 = nothing
    n_features = size(X_data)[2]
    X_id1 = reshape(X_data[id1, :], (1, n_features))
    X_id2 = reshape(X_data[id2, :], (1, n_features))
    if weights.kernel == "linear"
        k11 = kernel_linear(X_id1, X_id1)[1]
        k12 = kernel_linear(X_id1, X_id2)[1]
        k22 = kernel_linear(X_id2, X_id2)[1]
    elseif weights.kernel == "polynomial"
        k11 = kernel_polynomial(X_id1, X_id1, d=weights.d, r=weights.r, gamma=weights.gamma)[1]
        k12 = kernel_polynomial(X_id1, X_id2, d=weights.d, r=weights.r, gamma=weights.gamma)[1]
        k22 = kernel_polynomial(X_id2, X_id2, d=weights.d, r=weights.r, gamma=weights.gamma)[1]
    elseif weights.kernel == "rbf"
        k11 = kernel_rbf(X_id1, X_id1, gamma=weights.gamma)[1]
        k12 = kernel_rbf(X_id1, X_id2, gamma=weights.gamma)[1]
        k22 = kernel_rbf(X_id2, X_id2, gamma=weights.gamma)[1]
    elseif weights.kernel == "sigmoid"
        k11 = kernel_sigmoid(X_id1, X_id1, gamma=weights.gamma, r=weights.r)[1]
        k12 = kernel_sigmoid(X_id1, X_id2, gamma=weights.gamma, r=weights.r)[1]
        k22 = kernel_sigmoid(X_id2, X_id2, gamma=weights.gamma, r=weights.r)[1]
    else
        throw(ArgumentError("Error: kernel function $weights.kernel is not recognized"))
    end
    eta = 2.0 * k12 - k11 - k22
    # compute new alpha2 (a2)
    a2 = nothing
    if eta < 0.0
        a2 = alpha2 - Y2 * (error1 - error2) / eta
        a2 = min(a2, H)
        a2 = max(a2, L)
    else
        c1 = eta / 2.0
        c2 = Y2 * (error1 - error2) - eta * alpha2
        Lobj = c1 * L * L + c2 * L
        Hobj = c1 * H * H + c2 * H
        if Lobj > (Hobj + weights.tol_alpha)
            a2 = L
        elseif Lobj < (Hobj - weights.tol_alpha)
            a2 = H
        else
            a2 = alpha2
        end
    end
    # push to 0 or C
    if a2 < 1e-8
        a2 = 0.0
    elseif a2 > (weights.C - 1e-8)
        a2 = weights.C
    end
    # skip if cannot be optimized
    if abs(a2 - alpha2) < weights.tol_alpha * (a2 + alpha2 + weights.tol_alpha)
        return 0
    end
    # compute new alpha1 (a1)
    a1 = alpha1 + (Y1 * Y2) * (alpha2 - a2)
    if a1 < 0.0
        a2 += (Y1 * Y2) * a1
        a1 = 0.0
    elseif a1 > weights.C
        a2 += (Y1 * Y2) * (a1 - weights.C)
        a1 = weights.C
    end
    # update threshold
    b1 = error1 + Y1 * (a1 - alpha1) * k11 + Y2 * (a2 - alpha2) * k12 + weights.b
    b2 = error2 + Y1 * (a1 - alpha1) * k12 + Y2 * (a2 - alpha2) * k22 + weights.b
    b_new = nothing
    if 0 < a1 < weights.C
        b_new = b1
    elseif 0 < a2 < weights.C
        b_new = b2
    else
        b_new = (b1 + b2) * 0.5
    end
    # update error cache
    non_optimized_ids = [i for i in 1:size(X_data)[1] if (0 < weights.alpha[i] < weights.C)]
    kerr1 = nothing
    kerr2 = nothing
    if weights.kernel == "linear"
        kerr1 = vec(kernel_linear(X_id1, X_data[non_optimized_ids, :]))
        kerr2 = vec(kernel_linear(X_id2, X_data[non_optimized_ids, :]))
    elseif weights.kernel == "polynomial"
        kerr1 = vec(kernel_polynomial(X_id1, X_data[non_optimized_ids, :], d=weights.d, r=weights.r, gamma=weights.gamma))
        kerr2 = vec(kernel_polynomial(X_id2, X_data[non_optimized_ids, :], d=weights.d, r=weights.r, gamma=weights.gamma))
    elseif weights.kernel == "rbf"
        kerr1 = vec(kernel_rbf(X_id1, X_data[non_optimized_ids, :], gamma=weights.gamma))
        kerr2 = vec(kernel_rbf(X_id2, X_data[non_optimized_ids, :], gamma=weights.gamma))
    elseif weights.kernel == "sigmoid"
        kerr1 = vec(kernel_sigmoid(X_id1, X_data[non_optimized_ids, :], gamma=weights.gamma, r=weights.r))
        kerr2 = vec(kernel_sigmoid(X_id2, X_data[non_optimized_ids, :], gamma=weights.gamma, r=weights.r))
    end
    weights.error[non_optimized_ids] .= weights.error[non_optimized_ids] .+
        ((Y1*(a1-alpha1)) .* kerr1) .+ ((Y2*(a2-alpha2)) .* kerr2) .- (b_new - weights.b)
    weights.error[id1] = 0.0
    weights.error[id2] = 0.0
    # update alpha and b
    weights.b = b_new
    weights.alpha[id1] = a1
    weights.alpha[id2] = a2
    return 1
end

"""
Learning function for SVM, using SMO\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`weights.alpha` has shape (M,)\\
Tries to update `weights.alpha` with step function\\
Returns `1` if step further, else `0`
"""
function learn!(X_data::Array{T} where T<:Number, Y_data::Array{T} where T<:Number,
        weights::WeightsSVM, id::Integer; verbose::Bool=false)::Integer
    @assert ndims(X_data) == ndims(Y_data) + 1 == 2
    @assert size(X_data)[1] == size(Y_data)[1]
    @assert size(X_data)[1] == size(weights.alpha)[1]
    @assert size(weights.error) == size(weights.alpha)
    @assert id >= 1
    Y = Y_data[id]
    alpha = weights.alpha[id]
    error = weights.error[id]
    r = error * Y
    if ((r < -weights.tol_error) && (alpha < weights.C)) || ((r > weights.tol_error) && (alpha > 0))
        alpha_target = [i for (i, m) in enumerate(weights.alpha) if (0.0 < m < weights.C)]
        # try argmax E1 - E2
        if verbose
            println("Trying argmax(abs(E1 - E2))")
        end
        new_id = 0
        tmax = 0
        for i in alpha_target
            tmp = abs(error - weights.error[i])
            if(tmp > tmax)
                tmax = tmp
                new_id = i
            end
        end
        if new_id >= 1
            step = learn_step!(X_data, Y_data, weights, new_id, id)
            if step > 0
                return step
            end
        end
        # loop non-bound alphas, randomly
        if verbose
            println("Trying random non-bound alphas")
        end
        for new_id in alpha_target[Random.randperm(length(alpha_target))]
            step = learn_step!(X_data, Y_data, weights, new_id, id)
            if step > 0
                return step
            end
        end
        # else loop all alphas, randomly
        if verbose
            println("Trying random remaining alphas")
        end
        for new_id in Random.randperm(length(weights.alpha))
            if new_id in alpha_target
                continue # skip the alpha ids that already looked at
            end
            step = learn_step!(X_data, Y_data, weights, new_id, id)
            if step > 0
                return step
            end
        end
    end
    return 0
end

"""
Prediction function for SVM\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`weights.alpha` has shape (M,)\\
`X_predict` has shape (k,N)\\
Returns the actual predictions
"""
function predict_proba(X_predict::Array{T} where T<:Number, X_data::Array{T} where T<:Number,
        Y_data::Array{T} where T<:Number, weights::WeightsSVM)::Array
    if ndims(X_predict) == 1
        X_predict = reshape(X_predict, (1, size(X_predict)[1]))
    end
    @assert ndims(X_predict) == ndims(X_data) == ndims(Y_data) + 1 == 2
    @assert size(X_predict)[2] == size(X_data)[2]
    result = nothing
    if weights.kernel == "linear"
        result = kernel_linear(X_data, X_predict)
    elseif weights.kernel == "polynomial"
        result = kernel_polynomial(X_data, X_predict, d=weights.d, r=weights.r, gamma=weights.gamma)
    elseif weights.kernel == "rbf"
        result = kernel_rbf(X_data, X_predict, gamma=weights.gamma)
    elseif weights.kernel == "sigmoid"
        result = kernel_sigmoid(X_data, X_predict, gamma=weights.gamma, r=weights.r)
    else
        throw(ArgumentError("Error: kernel function $weights.kernel is not recognized"))
    end
    prediction = vec((weights.alpha .* Y_data)' * result)
    return prediction
end

"""
Prediction function for SVM\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`weights.alpha` has shape (M,)\\
`X_predict` has shape (k,N)\\
Returns the converted predictions, in {-1, 1}
"""
function predict(X_predict::Array{T} where T<:Number, X_data::Array{T} where T<:Number,
        Y_data::Array{T} where T<:Number, weights::WeightsSVM)::Array
    if ndims(X_predict) == 1
        X_predict = reshape(X_predict, (1, size(X_predict)[1]))
    end
    @assert ndims(X_predict) == ndims(X_data) == ndims(Y_data) + 1 == 2
    @assert size(X_predict)[2] == size(X_data)[2]
    result = nothing
    if weights.kernel == "linear"
        result = kernel_linear(X_data, X_predict)
    elseif weights.kernel == "polynomial"
        result = kernel_polynomial(X_data, X_predict, d=weights.d, r=weights.r, gamma=weights.gamma)
    elseif weights.kernel == "rbf"
        result = kernel_rbf(X_data, X_predict, gamma=weights.gamma)
    elseif weights.kernel == "sigmoid"
        result = kernel_sigmoid(X_data, X_predict, gamma=weights.gamma, r=weights.r)
    else
        throw(ArgumentError("Error: kernel function $weights.kernel is not recognized"))
    end
    prediction = vec((weights.alpha .* Y_data)' * result)
    prediction .= map(m -> m >= 0 ? 1.0 : -1.0, prediction)
    return prediction
end

"""
Training function for linear SVM\\
If `X_data` has shape (M,N)\\
`Y_data` has shape (M,)\\
`C` is the constraint for SVM decision margin\\
`tol_alpha` sets tolerance for alpha array\\
`tol_error` sets tolerance for error cache\\
`kernel` selects a kernel function, possible values are:
* "rbf"
* "linear"
* "sigmoid"
* "polynomial"

`gamma` selects a way to compute gamma, possible values are:
* "scale"
* "auto"

`degree` defines the degree in `polynomial` kernel function\\
`coef` defines the `r` in `polynomial` and `sigmoid` kernel functions
------
Returns trained alphas as `WeightsSVM` object
"""
function train(X_data::Array{T} where T<:Number, Y_data::Array{T} where T<:Number, C::AbstractFloat;
        tol_alpha::AbstractFloat=0.01, tol_error::AbstractFloat=0.01, kernel::String="rbf", gamma::String="scale",
        degree::AbstractFloat=1.0, coef::AbstractFloat=0.0, verbose::Bool=false)::WeightsSVM
    @assert ndims(X_data) == ndims(Y_data) + 1 == 2
    @assert size(X_data)[1] == size(Y_data)[1]
    X_data = Float64.(X_data)
    Y_data = Float64.(Y_data)
    # gamma is computed the same way sklearn does
    gamma_num = nothing
    if gamma == "scale"
        gamma_num = 1.0 / (size(X_data)[2] * Statistics.var(X_data))
    elseif gamma == "auto"
        gamma_num = 1.0 / size(X_data)[2]
    else
        throw(ArgumentError("Error: gamma $gamma is not recognized, possible values are 'scale' and 'auto'"))
    end
    weights = WeightsSVM(C, 0.0, gamma_num, coef, degree, tol_alpha, tol_error, Float64.(zeros(size(X_data)[1])), -copy(Y_data), kernel)
    num_changed = 0
    examine_all = true
    total_steps = 0
    while (num_changed > 0) || examine_all
        num_changed = 0
        if examine_all
            if verbose
                println("Scanning all training data")
            end
            for i in 1:size(X_data)[1]
                step = learn!(X_data, Y_data, weights, i, verbose=verbose)
                num_changed += step
                if step > 0 && verbose
                    obj = -cost(X_data, weights)
                    println("1 step further, objective = $obj")
                end
            end
        else
            if verbose
                println("Scanning data whose alpha is not at limit")
            end
            alpha_target = [i for (i, m) in enumerate(weights.alpha) if (m != 0.0 && m != weights.C)]
            for i in alpha_target
                step = learn!(X_data, Y_data, weights, i, verbose=verbose)
                num_changed += step
                if step > 0 && verbose
                    obj = -cost(X_data, weights)
                    println("1 step further, objective = $obj")
                end
            end
        end
        if examine_all
            examine_all = false
        elseif num_changed <= 0
            examine_all = true
        end
        total_steps += num_changed
    end
    if verbose
        println("Training Complete\nTotal steps: $total_steps")
    end
    return weights
end

end


using Primes

function ulamspiral(ord::Int)
    # Possible directions
    dirs = [[0, 1], [-1, 0], [0, -1], [1, 0]]
    # fdir = ["→", "↑", "←", "↓"] # for debug pourpose
    cur = maxsteps = 1  # starting direction & starting max steps
    steps = n = 0       # starting steps     & starting number in cell
    pos = [ord ÷ 2 + 1, isodd(ord) ? ord ÷ 2 + 1 : ord ÷ 2] # starting position
    M = Matrix{Bool}(ord, ord) # result matrix
    while n < ord ^ 2  # main loop (stop when the matrix is filled)
        n += 1
        M[pos[1], pos[2]] = isprime(n)
        steps += 1
        # Debug print
        # @printf("M[%i, %i] = %5s (%2i), step %i/%i, nxt %s\n", pos[1], pos[2], isprime(n), n, steps, maxsteps, fdir[cur])
        pos  .+= dirs[cur] # increment position
        if steps == maxsteps # if reached max number of steps in that direction...
            steps = 0        # ...reset steps
            if iseven(cur) maxsteps += 1 end # if the current direction is even increase the number of steps
            cur  += 1        # change direction
            if cur > 4 cur -= 4 end # correct overflow
        end
    end
    return M
end

mprint(m::Matrix) = for i in 1:size(m, 1) println(join(el ? " ∙ " : "   " for el in m[i, :]), '\n') end

M = ulamspiral(9)
mprint(M)


using LinearAlgebra

## Iterate Riccati difference equation
function Riccati(A_tld,B_tld,R,Q_tld,tol)

    P = ones(size(A_tld)[2],size(A_tld)[1]);
    F = ones(size(R)[1],size(W)[1]);

    pDiff = 10;
    fDiff = 10;
    iter = 0;

    while pDiff >= tol.*opnorm(P,1) && fDiff > tol.*opnorm(F,1)
        A = R+B_tld'*P*B_tld;

        P1 = Q_tld+A_tld'*P*A_tld-A_tld'*P*B_tld*(A\B_tld')*P*A_tld;
        pDiff = opnorm(P1-P,1);

        F1 = A\B_tld'*P*A_tld;
        fDiff = opnorm(F1-F,1);

        iter = iter + 1;

        P = copy(P1)
        F = copy(F1)
    end

    return P,F

end


##### ----- Power Law ----- #####
# Calculate probability density for a residence time τ given α of power law,
# minimum and maximum residence times τ₋ and τ₊
function E_powerLaw(τ, (q, α, τ₋, τ₊, k)) # Note - destructured arguments so that all pdfs have τ as first argument and p as second
    if(τ < τ₋)
        0
    elseif(τ > τ₊)
        0
    else
        ((τ^(-α) - τ₊^(-α))*(-α+1.0)/(τ₊^(-α+1.0)-τ₋^(-α+1.0)-τ₊^(-α)*(τ₊ - τ₋)*(1.0-α)))
    end
    # else
    #     (α-1)*τ₋^(α-1)*τ^(-α)
    # end
end

# Calculate rate of hyporheic exchange [T^-1] given a ratio of hyporheic volume to channel volume (V_frac)
# and parameters describing the pdf of the residence time distribution
function qCalc_powerLaw(α, τ₋, τ₊, V_frac)
    V_frac/quadgk(τ -> (τ₊^(-α+1) - τ^(-α+1)- (τ₊^(-α)*(τ₊-τ)*(-α+1)))/(τ₊^(-α+1) - τ₋^(-α+1)- (τ₊^(-α)*(τ₊-τ₋)*(-α+1))), τ₋, τ₊)[1]
    # V_frac/quadgk(τ -> (-τ^(-α+1))/(- τ₋^(-α+1)), τ₋, Inf)[1]
end

##### ----- Exponential ----- #####


module CYCLOPS_v6_2a_CircularStats_U

export Circular_Mean
export Fischer_Circular_CorrelationMeasures
export Jammalamadka_Circular_CorrelationMeasures
export Circular_Error
export Circular_Error_List

using MultivariateStats
using StatsBase

############################################################
function Circular_Mean(phases::Array{Float64,1})
  sinterm=sum(sin.(phases))
  costerm=sum(cos.(phases))
  atan2(sinterm,costerm)
end

############################################################
function Fischer_Circular_Correlations(rphases,sphases)
	n1=length(rphases)
	n2=length(sphases)

	num=n1

	rphases=mod.(rphases+2*pi,2*pi)
	sphases=mod.(sphases+2*pi,2*pi)

	numtot=0.
	d1tot=0.
	d2tot=0.
	for i in 1:num
		for j in (i+1):num
			numeratorterm=sin(sphases[i]-sphases[j])*sin(rphases[i]-rphases[j])
			denomterm1=(sin(sphases[i]-sphases[j]))^2
			denomterm2=(sin(rphases[i]-rphases[j]))^2
			numtot=numtot+numeratorterm
			d1tot=d1tot+denomterm1
			d2tot=d2tot+denomterm2
		end
	end
	fischercor=numtot/(sqrt(d1tot)*sqrt(d2tot))
	fischercor
end
############################################################
function Jammalamadka_Circular_Correlations(rphases,sphases)
	numtot=0.
	d1tot=0.
	d2tot=0.

	rbar=mod.(2*pi+Circular_Mean(rphases),2*pi)
	sbar=mod.(2*pi+Circular_Mean(sphases),2*pi)

	numtot=sum(sin.(rphases-rbar) .* sin.(sphases-sbar))
	d1tot=sqrt(sum( sin.(rphases-rbar) .^ 2))
	d2tot=sqrt(sum( sin.(sphases-sbar) .^ 2))
	Jammalamadka=numtot/(d1tot*d2tot)
	Jammalamadka
end

############################################################
function Circular_Rank_Phases(rphases)
	number=length(rphases)
	rphases=mod.(rphases+2*pi,2*pi)
	rranks=tiedrank(rphases)
	rrankphases=rranks*2*pi/number
	rrankphases
end

############################################################
function Jammalamadka_Rank_Circular_Correlations(rphases,sphases)

	rphases=Circular_Rank_Phases(rphases)
	sphases=Circular_Rank_Phases(sphases)

	r_minus_s_bar=mod(atan2(sum(sin.(rphases-sphases)),sum(cos.(rphases-sphases))),2*pi)
	r_plus_s_bar=mod(atan2(sum(sin.(rphases+sphases)),sum(cos.(rphases+sphases))),2*pi)

	Ntot=length(rphases)

	term1=cos.(rphases-sphases-r_minus_s_bar)
	term2=cos.(rphases+sphases-r_plus_s_bar)

	Jammalamadka=1/Ntot*(sum(term1))-1/Ntot*(sum(term2))
	Jammalamadka
end
############################################################
##########################################################

function FindComponentAngles(angle_sum,angle_diff)
    rang=(angle_sum + angle_diff)/2
    sang=(angle_sum - angle_diff)/2
	[rang,sang]
end
##########################################################
function Jammalamadka_Uniform_Circular_Correlations(rphases,sphases)
# This is a modification of he Jammalamadka Circular Correlation
# Described in Topics in Circular Statistics
# It is required beacuse the circular average is not well defined with circular uniform data
# This measure should only be used when 1 or both of the data sets being compared are uniform

	rphases=mod.(rphases,2*pi)
	sphases=mod.(sphases,2*pi)

	r_minus_s_bar=mod(atan2(sum(sin.(rphases-sphases)),sum(cos.(rphases-sphases))),2*pi)
	r_plus_s_bar=mod(atan2(sum(sin.(rphases+sphases)),sum(cos.(rphases+sphases))),2*pi)

	bars=FindComponentAngles(r_plus_s_bar,r_minus_s_bar)
	rbar=bars[1]
	sbar=bars[2]

	numtot=sum(sin.(rphases-rbar) .* sin.(sphases-sbar))
	d1tot=sqrt(sum( sin.(rphases-rbar) .^ 2))
	d2tot=sqrt(sum( sin.(sphases-sbar) .^ 2))

	Jammalamadka=numtot/(d1tot*d2tot)
	Jammalamadka
end

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

function Fischer_Circular_CorrelationMeasures(rphases,sphases)

	rrankphases=Circular_Rank_Phases(rphases)
	srankphases=Circular_Rank_Phases(sphases)

	F =Fischer_Circular_Correlations(rphases,sphases)

	FR =Fischer_Circular_Correlations(rrankphases,srankphases)

	[F,FR]
end

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

function Jammalamadka_Circular_CorrelationMeasures(rphases,sphases)

	J =Jammalamadka_Circular_Correlations(rphases,sphases)

	JU =Jammalamadka_Uniform_Circular_Correlations(rphases,sphases)

	JR =Jammalamadka_Rank_Circular_Correlations(rphases,sphases)

	[J, JU, JR]
end
############################################################

function Circular_Error(truth,estimate)
	truth=mod(2*pi+truth,2*pi)
	estimate=mod(2*pi+estimate,2*pi)

	diff1=mod((truth-estimate),2*pi)
	diff2=diff1-(2*pi)

	if (abs(diff1)<=abs(diff2))
		diff=diff1
	end

	if (abs(diff2)<abs(diff1))
		diff=diff2
	end

	e=diff
	e
end
############################################################

function Circular_Error_List(true_list,estimate_list)
	n1=length(true_list)
	n2=length(estimate_list)
	if (n1 != n2) print("Error Warning") end

	error_list=zeros(n1)
	for count in 1:n1
		error_list[count]=Circular_Error(true_list[count],estimate_list[count])
	end
	error_list
end

############################################################
end


function Sigmoid(x)
   g=1 ./ (1 .+exp.(-x))
    return g
end

function SigmoidGradient(x)

    g=Sigmoid(x).*(1 .- Sigmoid(x))
    return g

end


"""
    gen_qary!(matrix::Matrix{T},k::Int,q::Int) where {T}

...Maybe this should not try to operate in place...
"""
function gen_qary!(matrix::Matrix{T},k::Int,q) where {T}
    d,n = size(matrix)
    d==n || error("n!=d")

    matrix[1:d-k,1:d-k] = Matrix{T}(I,d-k,d-k)
    matrix[d-k+1:d,1:d-k] = rand(T(0):q-1,k,d-k)
    matrix[1:d-k,d-k+1:d] = zeros(T,k,d-k)
    matrix[d-k+1:d,d-k+1:d] = q*Matrix{T}(I,k,k)
end

"""
    b= gen_qary_b(T, d::Int,k::Int,b::Int)

Generate a q-ary lattice given an element type `T`, dimension `d`, parameter
`k`, and bit-depth `b`. Specifically, find a `d` by `d` matrix which has the
block structure `[I zeros(T,k,d-k); H q*I]]`, where the `k` by `d-k` matrix
H is sampled from `0:q-1` and q is sampled uniformly from `1:big(2)^b-1`

These bases correspond to the SIS/LWE q-ary lattices; see D. Micciancio and
O. Regev. Post-Quantum Cryptography. Chapter of Lattice-based Cryptography,
147-191 (2009) and latticegen in https://github.com/fplll/fplll

# Examples
```
julia> b=gen_qary_b(Int64,2,1,6)
2×2 Matrix{Int64}:
 1   0
 7  32

```
"""
function gen_qary_b(T, d::Int,k::Int,b::Int)
    if T!=BigInt && big(2)^b-1>typemax(T)
        error("Type $(T) can only handle b=$(log2(typemax(T)+1)) "*
              "bits, and you're asking for $b. Try again with "*
              "a different type or smaller bit depth b.")
    end
    q = T(rand(1:big(2)^b-1))
    matrix = Matrix{T}(undef,d,d)
    gen_qary!(matrix,k,q)
    return matrix
end