"""
    arec(A, G, Q = 0; as = false, rtol::Real = nϵ) -> (X, EVALS, Z)

Compute `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the continuous-time
algebraic Riccati equation

     A'X + XA - XGX + Q = 0,

where `G` and `Q` are hermitian/symmetric matrices or uniform scaling operators.
Scalar-valued `G` and `Q` are interpreted as appropriately sized uniform scaling operators `G*I` and `Q*I`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) eigenvalues of `A-GX`.
`Z = [ U; V ]` is an orthogonal basis for the stable/anti-stable deflating subspace such that `X = V/U`.

`Reference:`
Laub, A.J., A Schur Method for Solving Algebraic Riccati equations.
IEEE Trans. Auto. Contr., AC-24, pp. 913-921, 1979.

# Example
```jldoctest
julia> using LinearAlgebra

julia> A = [-6. -2. 1.; 5. 1. -1; -4. -2. -1.]
3×3 Array{Float64,2}:
 -6.0  -2.0   1.0
  5.0   1.0  -1.0
 -4.0  -2.0  -1.0

julia> G = [1. 0. 0.; 0. 5. 0.; 0. 0. 10.]
3×3 Array{Float64,2}:
 1.0  0.0   0.0
 0.0  5.0   0.0
 0.0  0.0  10.0

julia> X, CLSEIG = arec(A,G,2I);

julia> X
3×3 Array{Float64,2}:
  0.459589   0.333603   -0.144406
  0.333603   0.65916    -0.0999216
 -0.144406  -0.0999216   0.340483

julia> A'*X+X*A-X*G*X+2I
3×3 Array{Float64,2}:
  2.22045e-16  4.44089e-16  -1.77636e-15
  4.44089e-16  6.66134e-16   1.11022e-16
 -1.77636e-15  1.11022e-16  -1.33227e-15

julia> CLSEIG
3-element Array{Complex{Float64},1}:
 -4.411547592296008 + 2.4222082620381102im
 -4.411547592296008 - 2.4222082620381102im
 -4.337128244724371 + 0.0im

julia> eigvals(A-G*X)
3-element Array{Complex{Float64},1}:
 -4.4115475922960075 - 2.4222082620381076im
 -4.4115475922960075 + 2.4222082620381076im
  -4.337128244724374 + 0.0im
```
"""
function arec(A::AbstractMatrix, G::Union{AbstractMatrix,UniformScaling,Real,Complex}, Q::Union{AbstractMatrix,UniformScaling,Real,Complex} = zero(eltype(A));
              as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))))
    n = LinearAlgebra.checksquare(A)
    T = promote_type( eltype(A), eltype(G), eltype(Q) )
    if typeof(G) <: AbstractArray
       (LinearAlgebra.checksquare(G) == n && ishermitian(G)) ||
          throw(DimensionMismatch("G must be a symmetric/hermitian matrix of dimension $n"))
    else
       G = G*I
       iszero(imag(G.λ)) || throw("G must be a symmetric/hermitian matrix")
    end
    if typeof(Q) <: AbstractArray
       (LinearAlgebra.checksquare(Q) == n && ishermitian(Q)) ||
          throw(DimensionMismatch("Q must be a symmetric/hermitian matrix of dimension $n"))
    else
       Q = Q*I
       iszero(imag(Q.λ)) || throw("Q must be a symmetric/hermitian matrix")
    end
    T <: BlasFloat || (T = promote_type(Float64,T))
    TR = real(T)
    epsm = eps(TR)
    eltype(A) == T || (A = convert(Matrix{T},A))
    eltype(G) == T || (typeof(G) <: AbstractMatrix ? G = convert(Matrix{T},G) : G = convert(T,G.λ)*I)
    eltype(Q) == T || (typeof(Q) <: AbstractMatrix ? Q = convert(Matrix{T},Q) : Q = convert(T,Q.λ)*I)

    n == 0 && (return  zeros(T,0,0), zeros(T,0), zeros(T,m,0) )

    S = schur([A  -G; -Q  -copy(A')])

    as ? select = real(S.values) .> 0 : select = real(S.values) .< 0
    n == length(filter(y-> y == true,select)) || error("The Hamiltonian matrix is not dichotomic")
    ordschur!(S, select)

    n2 = n+n
    ix = 1:n
    F = _LUwithRicTest(S.Z[ix, ix],rtol)
    x = S.Z[n+1:n2, ix]/F
    return  (x+x')/2, S.values[ix], S.Z[:,ix]
end
function _LUwithRicTest(Z11::AbstractArray,rtol::Real)
   try
      F = LinearAlgebra.lu(Z11)
      Z11norm = opnorm(Z11,1)
      Z11norm > 2*rtol ? (rcond = LAPACK.gecon!('1',F.factors,Z11norm)) : (rcond = zero(eltype(Z11)))
      rcond <= rtol ? error("no finite solution exists for the Riccati equation") : (return  F)
    catch
      error("no finite solution exists for the Riccati equation")
   end
end
"""
    arec(A, B, R, Q, S; as = false, rtol::Real = nϵ, orth = false) -> (X, EVALS, F, Z)

Computes `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the continuous-time
algebraic Riccati equation

     A'X + XA - (XB+S)R^(-1)(B'X+S') + Q = 0,

where `R` and `Q` are hermitian/symmetric matrices or uniform scaling operators such that `R` is nonsingular.
Scalar-valued `R` and `Q` are interpreted as appropriately sized uniform scaling operators `R*I` and `Q*I`.
`S`, if not specified, is set to `S = zeros(size(B))`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) eigenvalues of `A-BF`.
`F` is the stabilizing or anti-stabilizing gain matrix `F = R^(-1)(B'X+S')`.
`Z = [ U; V; W ]` is a basis for the relevant stable/anti-stable deflating subspace such that `X = V/U` and `F = -W/U`.
An orthogonal basis Z can be determined, with an increased computational cost, by setting `orth = true`.

`Reference:`
Laub, A.J., A Schur Method for Solving Algebraic Riccati equations.
IEEE Trans. Auto. Contr., AC-24, pp. 913-921, 1979.

# Example
```jldoctest
julia> using LinearAlgebra

julia> A = [-6. -2. 1.; 5. 1. -1; -4. -2. -1.]
3×3 Array{Float64,2}:
 -6.0  -2.0   1.0
  5.0   1.0  -1.0
 -4.0  -2.0  -1.0

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

julia> R = [1. 0.; 0. 5.]
2×2 Array{Float64,2}:
 1.0  0.0
 0.0  5.0

julia> X, CLSEIG, F = arec(A,B,R,2I);

julia> X
3×3 Array{Float64,2}:
  0.522588   0.303007  -0.327227
  0.303007   0.650895  -0.132608
 -0.327227  -0.132608   0.629825

julia> A'*X+X*A-X*B*inv(R)*B'*X+2I
3×3 Array{Float64,2}:
 -2.66454e-15  -1.55431e-15   8.88178e-16
 -1.55431e-15   2.22045e-15  -2.9976e-15
  9.99201e-16  -2.9976e-15    4.44089e-16

julia> CLSEIG
3-element Array{Complex{Float64},1}:
   -4.37703628399912 + 2.8107164873731247im
   -4.37703628399912 - 2.8107164873731247im
 -1.8663764577096091 + 0.0im

julia> eigvals(A-B*F)
3-element Array{Complex{Float64},1}:
  -4.377036283999118 - 2.8107164873731234im
  -4.377036283999118 + 2.8107164873731234im
 -1.8663764577096063 + 0.0im
```
"""
function arec(A::AbstractMatrix, B::AbstractVecOrMat, R::Union{AbstractMatrix,UniformScaling,Real,Complex},
   Q::Union{AbstractMatrix,UniformScaling,Real,Complex}, S::AbstractVecOrMat = zeros(eltype(B),size(B));
   as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))), orth = false)
   if orth
      return garec(A, I, B, 0, R, Q, S; as = as, rtol = rtol)
   else
      return arec(A, B, 0, R, Q, S; as = as, rtol = rtol)
   end
end
"""
    arec(A, B, G, R, Q, S; as = false, rtol::Real = nϵ, orth = false) -> (X, EVALS, F, Z)

Computes `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the continuous-time
algebraic Riccati equation

     A'X + XA - XGX - (XB+S)R^(-1)(B'X+S') + Q = 0,

where `G`, `R` and `Q` are hermitian/symmetric matrices or uniform scaling operators such that `R` is nonsingular.
Scalar-valued `G`, `R` and `Q` are interpreted as appropriately sized uniform scaling operators `G*I`, `R*I` and `Q*I`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) eigenvalues of `A-BF-GX`.
`F` is the stabilizing or anti-stabilizing gain matrix `F = R^(-1)(B'X+S')`.
`Z = [ U; V; W ]` is a basis for the relevant stable/anti-stable deflating subspace such that `X = V/U` and `F = -W/U`.
An orthogonal basis Z can be determined, with an increased computational cost, by setting `orth = true`.

`Reference:`
Laub, A.J., A Schur Method for Solving Algebraic Riccati equations.
IEEE Trans. Auto. Contr., AC-24, pp. 913-921, 1979.
"""
function arec(A::AbstractMatrix, B::AbstractVecOrMat, G::Union{AbstractMatrix,UniformScaling,Real,Complex},
              R::Union{AbstractMatrix,UniformScaling,Real,Complex}, Q::Union{AbstractMatrix,UniformScaling,Real,Complex},
              S::AbstractVecOrMat; as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))), orth = false)
    orth && (return garec(A, I, B, G, R, Q, S; as = as, rtol = rtol))

    n = LinearAlgebra.checksquare(A)
    T = promote_type( eltype(A), eltype(B), eltype(G), eltype(Q), eltype(R), eltype(S) )
    typeof(B) <: AbstractVector ? (nb, m) = (length(B), 1) : (nb, m) = size(B)
    n == nb || throw(DimensionMismatch("B must be a matrix with row dimension $n or a vector of length $n"))
    if typeof(G) <: AbstractArray
      (LinearAlgebra.checksquare(G) == n && ishermitian(G)) ||
          throw(DimensionMismatch("G must be a symmetric/hermitian matrix of dimension $n"))
   else
     G = G*I
     iszero(imag(G.λ)) || throw("G must be a symmetric/hermitian matrix")
   end
   if typeof(R) <: AbstractArray
      (LinearAlgebra.checksquare(R) == m && ishermitian(R)) ||
         throw(DimensionMismatch("R must be a symmetric/hermitian matrix of dimension $m"))
   else
      R = R*I
      iszero(imag(R.λ)) || throw("R must be a symmetric/hermitian matrix")
   end
   if typeof(Q) <: AbstractArray
     (LinearAlgebra.checksquare(Q) == n && ishermitian(Q)) ||
        throw(DimensionMismatch("Q must be a symmetric/hermitian matrix of dimension $n"))
   else
     Q = Q*I
     iszero(imag(Q.λ)) || throw("Q must be a symmetric/hermitian matrix")
   end
   typeof(S) <: AbstractVector ? (ns, ms) = (length(S), 1) : (ns, ms) = size(S)
   (n == ns && m == ms) ||
      throw(DimensionMismatch("S must be a $n x $m matrix or a vector of length $n"))
   T <: BlasFloat || (T = promote_type(Float64,T))
   TR = real(T)
   epsm = eps(TR)
   eltype(A) == T || (A = convert(Matrix{T},A))
   eltype(B) == T || (typeof(B) <: AbstractVector ? B = convert(Vector{T},B) : B = convert(Matrix{T},B))
   if typeof(G) <: AbstractArray
       (LinearAlgebra.checksquare(G) == n && ishermitian(G)) ||
          throw(DimensionMismatch("G must be a symmetric/hermitian matrix of dimension $n"))
   else
      G = G*I
      iszero(imag(G.λ)) || throw("G must be a symmetric/hermitian matrix")
   end
   if typeof(R) <: AbstractArray
      (LinearAlgebra.checksquare(R) == m && ishermitian(R)) ||
         throw(DimensionMismatch("R must be a symmetric/hermitian matrix of dimension $m"))
   else
      R = R*I
      iszero(imag(R.λ)) || throw("R must be a symmetric/hermitian matrix")
   end
   if typeof(Q) <: AbstractArray
     (LinearAlgebra.checksquare(Q) == n && ishermitian(Q)) ||
        throw(DimensionMismatch("Q must be a symmetric/hermitian matrix of dimension $n"))
   else
      Q = Q*I
      iszero(imag(Q.λ)) || throw("Q must be a symmetric/hermitian matrix")
   end
   if eltype(S) != T
      if typeof(S) <: AbstractVector
         S = convert(Vector{T},S)
      else
         S = convert(Matrix{T},S)
      end
   end

   n == 0 && (return  zeros(T,0,0), zeros(T,0), zeros(T,m,0), zeros(T,m,0) )

   S0flag = iszero(S)
   typeof(R) <: UniformScaling && (R = Matrix{T}(R,m,m))
   SR = schur(R)
   D = real(diag(SR.T))
   Da = abs.(D)
   minDa, = findmin(Da)
   maxDa, = findmax(Da)
   minDa <= epsm*maxDa && error("R must be non-singular")
   if minDa > sqrt(epsm)*maxDa && maxDa > 100*eps(max(opnorm(A,1),opnorm(G,1),opnorm(Q,1)))
      #Dinv = diagm(0 => 1 ./ D)
      Dinv = Diagonal(1 ./ D)
      Bu = B*SR.Z
      #G = G + Bu*Dinv*Bu'
      #G = utqu(Dinv,Bu')
      G += utqu(Dinv,Bu')
      if S0flag
         sol = arec(A,G,Q; as = as, rtol = rtol)
         w2 = SR.Z*Dinv*Bu'
         f = w2*sol[1]
         z = [sol[3]; w2*(sol[3])[n+1:end,:]]
      else
         Su = S*SR.Z
         #Q -= Su*Dinv*Su'
         Q -= utqu(Dinv,Su')
         sol = arec(A-Bu*Dinv*Su',G,Q; as = as, rtol = rtol)
         w1 = SR.Z*Dinv*Su'
         w2 = SR.Z*Dinv*Bu'
         f = w1+w2*sol[1]
         #f = SR.Z*Dinv*(Bu'*sol[1]+Su')
         z = [sol[3]; [w1 w2]*sol[3] ]
      end
      return sol[1], sol[2], f, z
   else
      # use implicit form
      @warn "R nearly singular: using the orthogonal reduction method"
      return garec(A, I, B, G, R, Q, S; as = as, rtol = rtol)
   end
end
"""
    garec(A, E, G, Q = 0; as = false, rtol::Real = nϵ) -> (X, EVALS, Z)

Compute `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the generalized continuous-time
algebraic Riccati equation

    A'XE + E'XA - E'XGXE + Q = 0,

where `G` and `Q` are hermitian/symmetric matrices or uniform scaling operators and `E` is a nonsingular matrix.
Scalar-valued `G` and `Q` are interpreted as appropriately sized uniform scaling operators `G*I` and `Q*I`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) generalized eigenvalues of the pair `(A-GXE,E)`.
`Z = [ U; V ]` is an orthogonal basis for the stable/anti-stable deflating subspace such that `X = V/(EU)`.

`Reference:`
W.F. Arnold, III and A.J. Laub,
Generalized Eigenproblem Algorithms and Software for Algebraic Riccati Equations,
Proc. IEEE, 72:1746-1754, 1984.
"""
function garec(A::AbstractMatrix, E::Union{AbstractMatrix,UniformScaling}, G::Union{AbstractMatrix,UniformScaling,Real,Complex},
               Q::Union{AbstractMatrix,UniformScaling,Real,Complex} = zero(eltype(A));
               as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))))
    n = LinearAlgebra.checksquare(A)
    T = promote_type( eltype(A), eltype(G), eltype(Q) )
    eident = (E == I)
    if !eident
       LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))
       eident = isequal(E,I)
       eident ? (E = I) : (T = promote_type(T,eltype(E)))
    end
    if typeof(G) <: AbstractArray
       (LinearAlgebra.checksquare(G) == n && ishermitian(G)) ||
          throw(DimensionMismatch("G must be a symmetric/hermitian matrix of dimension $n"))
    else
      G = G*I
      iszero(imag(G.λ)) || throw("G must be a symmetric/hermitian matrix")
    end
    if typeof(Q) <: AbstractArray
       (LinearAlgebra.checksquare(Q) == n && ishermitian(Q)) ||
          throw(DimensionMismatch("Q must be a symmetric/hermitian matrix of dimension $n"))
    else
       Q = Q*I
       iszero(imag(Q.λ)) || throw("Q must be a symmetric/hermitian matrix")
    end
    T <: BlasFloat || (T = promote_type(Float64,T))
    TR = real(T)
    epsm = eps(TR)
    eltype(A) == T || (A = convert(Matrix{T},A))
    eident || eltype(E) == T || (E = convert(Matrix{T},E))
    eltype(G) == T || (typeof(G) <: AbstractMatrix ? G = convert(Matrix{T},G) : G = convert(T,G.λ)*I)
    eltype(Q) == T || (typeof(Q) <: AbstractMatrix ? Q = convert(Matrix{T},Q) : Q = convert(T,Q.λ)*I)

    n == 0 && (return  zeros(T,0,0), zeros(T,0), zeros(T,m,0) )

    if !eident
       Et = LinearAlgebra.LAPACK.getrf!(copy(E))
       LinearAlgebra.LAPACK.gecon!('1',Et[1],opnorm(E,1))  < epsm && error("E must be non-singular")
    end
    #  Method:  A stable/anti-stable deflating subspace Z1 = [Z11; Z21] of the pencil
    #       L -s P := [  A  -G ]  - s [ E  0  ]
    #                 [ -Q  -A']      [ 0  E' ]
    #  is determined and the solution X is computed as X = Z21*inv(E*Z11).
    L = [ A -G; -Q -A']
    P = [ E zeros(T,n,n); zeros(T,n,n) E']
    LPS = schur(L,P)
    as ? select = real.(LPS.α ./ LPS.β) .> 0 : select = real.(LPS.α ./ LPS.β) .< 0
    n == length(filter(y-> y == true,select)) ||
       error("The Hamiltonian/skew-Hamiltonian pencil is not dichotomic")
    ordschur!(LPS, select)
    i1 = 1:n
    i2 = n+1:2n
    F = _LUwithRicTest(LPS.Z[i1, i1],rtol)
    if eident
       x = LPS.Z[i2,i1]/F
    else
       x = LPS.Z[i2,i1]/(E*LPS.Z[i1,i1])
    end

    return  (x+x')/2, LPS.values[i1], LPS.Z[:,i1]
end
"""
    garec(A, E, B, R, Q, S; as = false, rtol::Real = nϵ) -> (X, EVALS, F, Z)

Compute `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the generalized continuous-time
algebraic Riccati equation

    A'XE + E'XA - (E'XB+S)R^(-1)(B'XE+S') + Q = 0,

where `R` and `Q` are hermitian/symmetric matrices such that `R` is nonsingular, and
`E` is a nonsingular matrix.
Scalar-valued `R` and `Q` are interpreted as appropriately sized uniform scaling operators `R*I` and `Q*I`.
`S`, if not specified, is set to `S = zeros(size(B))`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) generalized eigenvalues of the pair `(A-BF,E)`.
`F` is the stabilizing/anti-stabilizing gain matrix `F = R^(-1)(B'XE+S')`.
`Z = [ U; V; W ]` is an orthogonal basis for the relevant stable/anti-stable deflating subspace such that `X = V/(EU)` and `F = -W/U`.

`Reference:`
W.F. Arnold, III and A.J. Laub,
Generalized Eigenproblem Algorithms and Software for Algebraic Riccati Equations,
Proc. IEEE, 72:1746-1754, 1984.

# Example
```jldoctest
julia> using LinearAlgebra

julia> A = [-6. -2. 1.; 5. 1. -1; -4. -2. -1.]
3×3 Array{Float64,2}:
 -6.0  -2.0   1.0
  5.0   1.0  -1.0
 -4.0  -2.0  -1.0

julia> E = [10. 3. 0.; 0. 5. -1.; 0. 0. 10.]
3×3 Array{Float64,2}:
 10.0  3.0   0.0
  0.0  5.0  -1.0
  0.0  0.0  10.0

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

julia> R = [1. 0.; 0. 5.]
2×2 Array{Float64,2}:
 1.0  0.0
 0.0  5.0

julia> X, CLSEIG, F = garec(A,E,B,R,2I);

julia> X
3×3 Array{Float64,2}:
  0.0502214   0.0284089   -0.0303703
  0.0284089   0.111219    -0.00259162
 -0.0303703  -0.00259162   0.0618395

julia> A'*X*E+E'*X*A-E'*X*B*inv(R)*B'*X*E+2I
3×3 Array{Float64,2}:
  1.55431e-15  -1.9984e-15   -3.33067e-15
 -1.77636e-15   1.33227e-15  -3.33067e-15
 -2.88658e-15  -3.21965e-15   1.11022e-15

julia> CLSEIG
3-element Array{Complex{Float64},1}:
  -0.6184265391601464 + 0.2913286844595737im
  -0.6184265391601464 - 0.2913286844595737im
 -0.21613059964451786 + 0.0im

julia> eigvals(A-B*F,E)
3-element Array{Complex{Float64},1}:
 -0.6184265391601462 - 0.29132868445957383im
 -0.6184265391601462 + 0.2913286844595739im
  -0.216130599644518 + 0.0im
```
"""
function garec(A::AbstractMatrix, E::Union{AbstractMatrix,UniformScaling}, B::AbstractVecOrMat, R::Union{AbstractMatrix,UniformScaling,Real,Complex},
   Q::Union{AbstractMatrix,UniformScaling,Real,Complex}, S::AbstractVecOrMat = zeros(eltype(B),size(B));
   as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))))
   garec(A, E, B, 0, R, Q, S; as = as, rtol = rtol)
end
"""
    garec(A, E, B, G, R, Q, S; as = false, rtol::Real = nϵ) -> (X, EVALS, F, Z)

Compute `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the generalized continuous-time
algebraic Riccati equation

    A'XE + E'XA - E'XGXE - (E'XB+S)R^(-1)(B'XE+S') + Q = 0,

where `G`, `Q` and `R` are hermitian/symmetric matrices such that `R` is nonsingular, and
`E` is a nonsingular matrix.
Scalar-valued `G`, `R` and `Q` are interpreted as appropriately sized uniform scaling operators `G*I`, `R*I` and `Q*I`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) generalized eigenvalues of the pair `(A-BF-GXE,E)`.
`F` is the stabilizing/anti-stabilizing gain matrix `F = R^(-1)(B'XE+S')`.
`Z = [ U; V; W ]` is an orthogonal basis for the relevant stable/anti-stable deflating subspace such that `X = V/(EU)` and `F = -W/U`.

`Reference:`
W.F. Arnold, III and A.J. Laub,
Generalized Eigenproblem Algorithms and Software for Algebraic Riccati Equations,
Proc. IEEE, 72:1746-1754, 1984.
"""
function garec(A::AbstractMatrix, E::Union{AbstractMatrix,UniformScaling}, B::AbstractVecOrMat,
               G::Union{AbstractMatrix,UniformScaling,Real,Complex}, R::Union{AbstractMatrix,UniformScaling,Real,Complex},
               Q::Union{AbstractMatrix,UniformScaling,Real,Complex}, S::AbstractVecOrMat;
               as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))))
    n = LinearAlgebra.checksquare(A)
    T = promote_type( eltype(A), eltype(B), eltype(G), eltype(Q), eltype(R), eltype(S) )
    typeof(B) <: AbstractVector ? (nb, m) = (length(B), 1) : (nb, m) = size(B)
    n == nb || throw(DimensionMismatch("B must be a matrix with row dimension $n or a vector of length $n"))
    eident = (E == I)
    if !eident
       LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))
       eident = isequal(E,I)
       eident ? (E = I) : (T = promote_type(T,eltype(E)))
    end
    if typeof(G) <: AbstractArray
       (LinearAlgebra.checksquare(G) == n && ishermitian(G)) ||
           throw(DimensionMismatch("G must be a symmetric/hermitian matrix of dimension $n"))
    else
      G = G*I
      iszero(imag(G.λ)) || throw("G must be a symmetric/hermitian matrix")
    end
    if typeof(R) <: AbstractArray
       (LinearAlgebra.checksquare(R) == m && ishermitian(R)) ||
          throw(DimensionMismatch("R must be a symmetric/hermitian matrix of dimension $m"))
    else
       R = R*I
       iszero(imag(R.λ)) || throw("R must be a symmetric/hermitian matrix")
    end
    if typeof(Q) <: AbstractArray
      (LinearAlgebra.checksquare(Q) == n && ishermitian(Q)) ||
         throw(DimensionMismatch("Q must be a symmetric/hermitian matrix of dimension $n"))
    else
      Q = Q*I
      iszero(imag(Q.λ)) || throw("Q must be a symmetric/hermitian matrix")
    end
    typeof(S) <: AbstractVector ? (ns, ms) = (length(S), 1) : (ns, ms) = size(S)
    (n == ns && m == ms) || throw(DimensionMismatch("S must be a $n x $m matrix or a vector of length $n"))
    T <: BlasFloat || (T = promote_type(Float64,T))
    TR = real(T)
    epsm = eps(TR)
    eltype(A) == T || (A = convert(Matrix{T},A))
    eident || eltype(E) == T || (E = convert(Matrix{T},E))
    if !eident
       Et = LinearAlgebra.LAPACK.getrf!(copy(E))
       LinearAlgebra.LAPACK.gecon!('1',Et[1],opnorm(E,1))  < epsm && error("E must be non-singular")
    end
    eltype(B) == T || (typeof(B) <: AbstractVector ? B = convert(Vector{T},B) : B = convert(Matrix{T},B))
    eltype(G) == T || (typeof(G) <: AbstractMatrix ? G = convert(Matrix{T},G) : G = convert(T,G.λ)*I)
    eltype(Q) == T || (typeof(Q) <: AbstractMatrix ? Q = convert(Matrix{T},Q) : Q = convert(T,Q.λ)*I)
    eltype(R) == T || (typeof(R) <: AbstractMatrix ? R = convert(Matrix{T},R) : R = convert(T,R.λ)*I)
    eltype(S) == T || (typeof(S) <: AbstractVector ? S = convert(Vector{T},S) : S = convert(Matrix{T},S))

    n == 0 && (return  zeros(T,0,0), zeros(T,0), zeros(T,m,0), zeros(T,m,0) )

    cond(R)*epsm < 1 || error("R must be non-singular")

    #  Method:  A stable/ant-stable deflating subspace Z1 = [Z11; Z21; Z31] of the pencil
    #               [  A  -G    B ]      [ E  0  0 ]
    #      L -s P = [ -Q  -A'  -S ]  - s [ 0  E' 0 ]
    #               [  S'  B'   R ]      [ 0  0  0 ]
    # is determined and the solution X and feedback F are computed as
    #          X = Z21*inv(E*Z11),   F = -Z31*inv(Z11).

    #deflate m simple infinite eigenvalues
    n2 = n+n;
    W = qr(Matrix([S; B; R]));
    cond(W.R) * epsm  < 1 || error("The extended Hamiltonian/skew-Hamiltonian pencil is not regular")

    #z = W.Q[:,m+1:m+n2]
    z = W.Q*[fill(false,m,n2); I ]

    iric = 1:n2
    i1 = 1:n
    i2 = n+1:n2
    i3 = n2+1:n2+m
    L11 = [ A -G B; -Q -A' -S]*z
    P11 = [ E*z[i1,:]; E'*z[i2,:] ]
    LPS = schur(L11,P11)
    as ? select = real.(LPS.α ./ LPS.β) .> 0 : select = real.(LPS.α ./ LPS.β) .< 0
    n == length(filter(y-> y == true,select)) ||
         error("The extended Hamiltonian/skew-Hamiltonian pencil is not dichotomic")
    ordschur!(LPS, select)

    z[:,i1] = z[:,iric]*LPS.Z[:,i1];

    F = _LUwithRicTest(z[i1,i1],rtol)
    if eident
       x = z[n+1:end,i1]/F
       f = -x[n+1:end,:]
       x = x[i1,:]
    else
       f = -z[i3,i1]/F
       x = z[i2,i1]/(E*z[i1,i1])
    end

    return  (x+x')/2, LPS.values[i1] , f, z[:,i1]
end

"""
    ared(A, B, R, Q, S; as = false, rtol::Real = nϵ) -> (X, EVALS, F, Z)

Compute `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the discrete-time algebraic Riccati equation

    A'XA - X - (A'XB+S)(R+B'XB)^(-1)(B'XA+S') + Q = 0,

where `R` and `Q` are hermitian/symmetric matrices.
Scalar-valued `R` and `Q` are interpreted as appropriately sized uniform scaling operators `R*I` and `Q*I`.
`S`, if not specified, is set to `S = zeros(size(B))`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable) eigenvalues of `A-BF`.
`F` is the stabilizing gain matrix `F = (R+B'XB)^(-1)(B'XA+S')`.
`Z = [ U; V; W ]` is an orthogonal basis for the relevant stable/anti-stable deflating subspace such that `X = V/(EU)` and `F = -W/U`.

`Reference:`
W.F. Arnold, III and A.J. Laub,
Generalized Eigenproblem Algorithms and Software for Algebraic Riccati Equations,
Proc. IEEE, 72:1746-1754, 1984.

# Example
```jldoctest
julia> using LinearAlgebra

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

julia> B = [ 0.; sqrt(2.) ]
2-element Array{Float64,1}:
 0.0
 1.4142135623730951

julia> R = 1.
1.0

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

julia> X, CLSEIG, F = ared(A,B,R,Q);

julia> X
2×2 Array{Float64,2}:
  1.0  -1.0
 -1.0   1.5

julia> A'*X*A-X-A'*X*B*inv(R+B'*X*B)*B'*X*A+Q
2×2 Array{Float64,2}:
  0.0          -3.33067e-16
 -3.33067e-16   8.88178e-16

julia> CLSEIG
2-element Array{Complex{Float64},1}:
 0.4999999999999998 - 0.0im
               -0.0 - 0.0im

julia> eigvals(A-B*F)
2-element Array{Float64,1}:
 -2.7755575615628914e-16
  0.5
```
"""
function ared(A::AbstractMatrix, B::AbstractVecOrMat, R::Union{AbstractMatrix,UniformScaling,Real,Complex},
              Q::Union{AbstractMatrix,UniformScaling,Real,Complex}, S::AbstractVecOrMat = zeros(eltype(B),size(B));
              as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))))
    gared(A, I, B, R, Q, S; as = as, rtol = rtol)
end
"""
    gared(A, E, B, R, Q, S; as = false, rtol::Real = nϵ) -> (X, EVALS, F, Z)

Compute `X`, the hermitian/symmetric stabilizing solution (if `as = false`) or
anti-stabilizing solution (if `as = true`) of the generalized discrete-time
algebraic Riccati equation

    A'XA - E'XE - (A'XB+S)(R+B'XB)^(-1)(B'XA+S') + Q = 0,

where `R` and `Q` are hermitian/symmetric matrices, and `E` ist non-singular.
Scalar-valued `R` and `Q` are interpreted as appropriately sized uniform scaling operators `R*I` and `Q*I`.
`S`, if not specified, is set to `S = zeros(size(B))`.

By default, the lower bound for the 1-norm reciprocal condition number `rtol` is `n*ϵ`, where `n` is the order of `A`
and `ϵ` is the _machine epsilon_ of the element type of `A`.

`EVALS` is a vector containing the (stable or anti-stable) generalized eigenvalues of the pair `(A-BF,E)`.
`F` is the stabilizing/anti-stabilizing gain matrix `F = (R+B'XB)^(-1)(B'XA+S')`.
`Z = [ U; V; W ]` is an orthogonal basis for the relevant stable/anti-stable deflating subspace such that `X = V/(EU)` and `F = -W/U`.

`Reference:`
W.F. Arnold, III and A.J. Laub,
Generalized Eigenproblem Algorithms and Software for Algebraic Riccati Equations,
Proc. IEEE, 72:1746-1754, 1984.

# Example
```jldoctest
julia> using LinearAlgebra

julia> A = [-6. -2. 1.; 5. 1. -1; -4. -2. -1.]
3×3 Array{Float64,2}:
 -6.0  -2.0   1.0
  5.0   1.0  -1.0
 -4.0  -2.0  -1.0

julia> E = [10. 3. 0.; 0. 5. -1.; 0. 0. 10.]
3×3 Array{Float64,2}:
 10.0  3.0   0.0
  0.0  5.0  -1.0
  0.0  0.0  10.0

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

julia> R = [1. 0.; 0. 5.]
2×2 Array{Float64,2}:
 1.0  0.0
 0.0  5.0

julia> X, CLSEIG, F = gared(A,E,B,R,2I);

julia> X
3×3 Array{Float64,2}:
  0.065865   -0.0147205  -0.0100407
 -0.0147205   0.0885939   0.0101422
 -0.0100407   0.0101422   0.0234425

julia> A'*X*A-E'*X*E-A'*X*B*inv(R+B'*X*B)*B'*X*A+2I
3×3 Array{Float64,2}:
 -1.33227e-15  -2.48412e-15   1.38778e-16
 -2.498e-15    -4.44089e-16  -6.50521e-16
  1.80411e-16  -5.91541e-16  -1.33227e-15

julia> CLSEIG
3-element Array{Complex{Float64},1}:
  -0.084235615751339 - 0.0im
  -0.190533552034239 - 0.0im
 -0.5238922629921539 - 0.0im

julia> eigvals(A-B*F,E)
3-element Array{Float64,1}:
 -0.5238922629921539
 -0.19053355203423886
 -0.08423561575133902
```
"""
function gared(A::AbstractMatrix, E::Union{AbstractMatrix,UniformScaling}, B::AbstractVecOrMat,
               R::Union{AbstractMatrix,UniformScaling,Real,Complex}, Q::Union{AbstractMatrix,UniformScaling,Real,Complex},
               S::AbstractVecOrMat = zeros(eltype(B),size(B)); as = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))))
    n = LinearAlgebra.checksquare(A)
    T = promote_type( eltype(A), eltype(B), eltype(R), eltype(Q), eltype(S) )
    typeof(B) <: AbstractVector ? (nb, m) = (length(B), 1) : (nb, m) = size(B)
    n == nb || throw(DimensionMismatch("B must be a matrix with row dimension $n or a vector of length $n"))
    if typeof(E) <: UniformScaling{Bool}
       eident = true
       E = I
    else
       LinearAlgebra.checksquare(E) == n || throw(DimensionMismatch("E must be a $n x $n matrix or I"))
       eident = isequal(E,I)
       eident ? E = I : T = promote_type(T,eltype(E))
     end
    if typeof(R) <: AbstractArray
       (LinearAlgebra.checksquare(R) == m && ishermitian(R)) ||
         throw(DimensionMismatch("R must be a symmetric/hermitian matrix of dimension $m"))
    else
      R = R*I
      iszero(imag(R.λ)) || throw("R must be a symmetric/hermitian matrix")
    end
    if typeof(Q) <: AbstractArray
       (LinearAlgebra.checksquare(Q) == n && ishermitian(Q)) ||
          throw(DimensionMismatch("Q must be a symmetric/hermitian matrix of dimension $n"))
    else
       Q = Q*I
       iszero(imag(Q.λ)) || throw("Q must be a symmetric/hermitian matrix")
    end
    typeof(S) <: AbstractVector ? (ns, ms) = (length(S), 1) : (ns, ms) = size(S)
    (n == ns && m == ms) || throw(DimensionMismatch("S must be a $n x $m matrix or a vector of length $n"))
    T <: BlasFloat || (T = promote_type(Float64,T))
    TR = real(T)
    epsm = eps(TR)
    eltype(A) == T || (A = convert(Matrix{T},A))
    eident || eltype(E) == T || (E = convert(Matrix{T},E))
    if !eident
       Et = LinearAlgebra.LAPACK.getrf!(copy(E))
       LinearAlgebra.LAPACK.gecon!('1',Et[1],opnorm(E,1))  < epsm && error("E must be non-singular")
    end
    eltype(B) == T || (typeof(B) <: AbstractVector ? B = convert(Vector{T},B) : B = convert(Matrix{T},B))
    eltype(Q) == T || (typeof(Q) <: AbstractMatrix ? Q = convert(Matrix{T},Q) : Q = convert(T,Q.λ)*I)
    eltype(R) == T || (typeof(R) <: AbstractMatrix ? R = convert(Matrix{T},R) : R = convert(T,R.λ)*I)
    eltype(S) == T || (typeof(S) <: AbstractVector ? S = convert(Vector{T},S) : S = convert(Matrix{T},S))

    n == 0 && (return  zeros(T,0,0), zeros(T,0), zeros(T,m,0), zeros(T,m,0) )

    if !eident
      Et = LinearAlgebra.LAPACK.getrf!(copy(E))
      LinearAlgebra.LAPACK.gecon!('1',Et[1],opnorm(E,1))  < epsm && error("E must be non-singular")
    end
    #  Method:  A stable deflating subspace Z1 = [Z11; Z21; Z31] of the pencil
    #                   [  A   0    B ]      [ E  0  0 ]
    #          L -z P = [ -Q   E'  -S ]  - z [ 0  A' 0 ]
    #                   [ S'   0    R ]      [ 0 -B' 0 ]
    #  is computed and the solution X and feedback F are computed as
    #          X = Z21*inv(E*Z11),   F = Z31*inv(Z11).
    n2 = n+n;
    F = qr([A'; -B'])
    L2 = F.Q'*[-Q  E' -S; copy(S') zeros(T,m,n) R]
    P2 = [zeros(T,n,n) F.R zeros(T,n,m)]

    G = qr(L2[n+1:n+m,:]')
    cond(G.R) * epsm  < 1 || error("The extended symplectic pencil is not regular")
    z = (G.Q*I)[:,[m+1:m+n2; 1:m]]

    i1 = 1:n
    i2 = n+1:n2
    i3 = n2+1:n2+m
    iric = 1:n2

    L1 = [ A zeros(T,n,n) B; L2[i1,:]]*z
    P1 = [ E zeros(T,n,n+m); P2]*z

    as ? PLS = schur(L1[iric,iric],P1[iric,iric]) : PLS = schur(P1[iric,iric],L1[iric,iric])
    select = abs.(PLS.α) .> abs.(PLS.β)

    n == length(filter(y-> y == true,select)) || error("The extended simplectic pencil is not dichotomic")

    ordschur!(PLS, select)
    z[:,i1]= z[:,iric]*PLS.Z[:,i1]

    F = _LUwithRicTest(z[i1,i1],rtol)
    if eident
       x = z[n+1:end,i1]/F
       f = -x[n+1:end,:]
       x = x[i1,:]
    else
       f = -z[i3,i1]/F
       x = z[i2,i1]/(E*z[i1,i1])
    end

    as ? iev = i2 : iev = i1
    clseig = PLS.β[iev] ./ PLS.α[iev]
    if as && T <: Complex
      clseig =  conj(clseig)
    end
    return  (x+x')/2, clseig, f, z[:,i1]
end


using jInv.Mesh
using DivSigGrad
using Multigrid

println("************************************************* Example 2D ******************************************************");

domain = [0.0, 1.0, 0.0, 1.0];
n = [50,50];
Mr = getRegularMesh(domain,n)
m  = exp.(randn(prod(n)));
Ar = getDivSigGradMatrix(vec(m),Mr);
Ar = Ar + 1e-8*norm(Ar,1)*speye(size(Ar,2));

levels      = 3;
numCores 	= 8;
maxIter     = 3;
relativeTol = 1e-2;
relaxType   = "SPAI";
relaxParam  = 1.0;
relaxPre 	= 2;
relaxPost   = 2;
cycleType   ='V';
coarseSolveType = "NoMUMPS";

MG = getMGparam(levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType);

N = size(Ar,2);

b = Ar*rand(N,3);
x = zeros(N,3);
SA_AMGsetup(Ar,MG,Float64,true,size(b,2),true);

println("****************************** Stand-alone AMG: ******************************")
solveMG(MG,b,x,true);

x[:] = 0.0;
println("****************************** CG preconditioned with AMG: ******************************")
solveCG_MG(Ar,MG,b,x,true)

println("****************************** CG preconditioned with BiCGSTAB: ******************************")
solveBiCGSTAB_MG(Ar,MG,b,x,true);

println("****************************** transposeHierarchy *********************************");
transposeHierarchy(MG,true);

println("****************************** GMRES preconditioned with AMG: (only one rhs...) ******************************")
x[:] = 0.0;
b1 = vec(b[:,1]);
x1 = zeros(N);
MG = getMGparam(levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType);
SA_AMGsetup(Ar,MG,Float64,true,1,true);
solveGMRES_MG(Ar,MG,b1,x1,true,2)

x1 = zeros(N);
MG.maxOuterIter = 10;
solveBiCGSTAB_MG(Ar,MG,b1,x1,true)

MG = getMGparam(levels,numCores,maxIter,relativeTol,"Jac-GMRES",relaxParam,relaxPre,relaxPost,'K',coarseSolveType);
SA_AMGsetup(Ar,MG,Float64,true,size(b,2),true);
x[:] = 0.0;
solveGMRES_MG(Ar,MG,b,x,true,2)

Ar = 0;
b = 0;
x = 0;
Mr = 0;


using Turing
using Distributions
using Base.Test

num_samples = Int(1e6)

resSystematic = Turing.resampleSystematic( [0.3, 0.4, 0.3], num_samples )
resStratified = Turing.resampleStratified( [0.3, 0.4, 0.3], num_samples )
resMultinomial= Turing.resampleMultinomial( [0.3, 0.4, 0.3], num_samples )
resResidual   = Turing.resampleResidual( [0.3, 0.4, 0.3], num_samples )
Turing.resample( [0.3, 0.4, 0.3])
resSystematic2=Turing.resample( [0.3, 0.4, 0.3], num_samples )

@test_approx_eq_eps sum(resSystematic .== 2) (num_samples * 0.4) 1e-3*num_samples
@test_approx_eq_eps sum(resSystematic2 .== 2) (num_samples * 0.4) 1e-3*num_samples
@test_approx_eq_eps sum(resStratified .== 2) (num_samples * 0.4) 1e-3*num_samples
@test_approx_eq_eps sum(resMultinomial .== 2) (num_samples * 0.4) 1e-2*num_samples
@test_approx_eq_eps sum(resResidual .== 2) (num_samples * 0.4) 1e-2*num_samples


@symbol_func function K_VT(cur_reactor::AbstractReactor)
  cur_nu_n = cur_reactor.nu_n

  cur_nu_T = cur_reactor.nu_T

  cur_K = K_n(cur_reactor)

  cur_K *= cur_reactor.epsilon ^ 2

  cur_K *= g_p(cur_reactor)

  cur_K *= ( 1 + cur_reactor.f_D )

  cur_K *= ( 1 + cur_nu_n )

  cur_K *= ( 1 + cur_nu_T )

  cur_K /= 1 + cur_nu_n + cur_nu_T

  cur_K
end


"""
Implementation of local response normalization.
"""
module LRNs
using Flux

export LRN

"""
    LRN{T,I}

Local response normalization layer. Input `i` is processed according to
out(x,y,f,b) = x * [b + α * sum( i(x, y, f-k÷2:f+k÷2, b)^2 )]^(-β)

Todo: β is currently ignored (always set to 0.5)
"""
struct LRN{T,I}
    b::T
    α::T
    β::T
    k::I
end

"""
    (l::LRN)(i)

Applies a local response normalization layer according to:
out(x,y,f,b) = x * [c + α * sum( i(x, y, f-k÷2:f+k÷2, b)^2 )]^(-β)

Todo: β is currently ignored (always set to 0.5)
"""
function (l::LRN)(x)
    ω = similar(x)
    fsize = size(x, 3)
    depth = l.k ÷ 2
    buffer = similar(x, size(x, 1), size(x, 2), 2*depth+1, size(x, 4))
    for i ∈ 1:fsize
        f_min = max(1, i - depth)
        f_max = min(fsize, i + depth)
        buffer = Flux.Tracker.data(x[:, :, f_min:f_max, :])
        ω[:, :, i, :] = sum(buffer.^2, dims=3)
    end
    return x ./ sqrt.(l.b .+ l.α .* ω)
end # function (l::LRN)

Flux.@treelike LRN

# convenience constructor with default arguments
function LRN(; b=1.0, α=1.0, β=0.5, k=5)
    return LRN(b, α, β, k)
end # function LRN
end # module LRNs


##################################
# ALGORITMO GENÉTICO ESTACIONARIO
##################################
using Random

include("generar-cromosoma.jl")
include("torneo-binario.jl")
include("../utils/distancias.jl")
include("../utils/funcion-objetivo.jl")
"""
    AuxCrucePorIndices(i,j,g, FunciónCruce)
Función auxiliar para ahorrarse el cruce si i==j
"""
function AuxCrucePorIndices(i,j,g, FunciónCruce)
    if i == j
        return [g[i], g[i]]
    else
        return FunciónCruce(g[i], g[j])
    end
end
"""
    AGE(   evaluaciones_máximas_FE,
            numero_cromosomas_por_generación,
            probabilidad_mutación,
            tamaño_cromosoma,
            función_evaluación,
            función_cruce,
            función_mutación)
    Algoritmo genético generacional, tras las iteraciones convenientes devuelve el cromosoma mejor y su función de valuación
"""
function AGE(   evaluaciones_máximas_FE,
                numero_cromosomas_por_generación,
                # probabilidad_cruce, siempre es 1
                probabilidad_mutación,
                tamaño_cromosoma,
                función_evaluación,
                función_cruce,
                función_mutación)

    # Generamos la primera generación
    generación = [GeneraCromosoma(tamaño_cromosoma, 0.1) for i in 1:numero_cromosomas_por_generación]
    evaluaciones_función_evaluación = map(x -> función_evaluación(x), generación)

    # Variable que acumula el número de evaluaciones de la función de evaluación
    evaluaciones = numero_cromosomas_por_generación
    # Array que contendrá a los elementos seleccionados
    Seleccionados = generación
    while evaluaciones < evaluaciones_máximas_FE

        # Paso 1: Selección (torneo binario)
        índice_seleccionado1 = TorneoBinarioEntreDos(evaluaciones_función_evaluación, numero_cromosomas_por_generación)
        índice_seleccionado2 = TorneoBinarioEntreDos(evaluaciones_función_evaluación, numero_cromosomas_por_generación)

        # Paso 2: Cruce
        Seleccionados =
            AuxCrucePorIndices(
                índice_seleccionado1,
                índice_seleccionado2,
                generación,
                función_cruce
        )
        # Paso 3: Mutación
        for i in 1:2
            if(rand()< probabilidad_mutación)
                Seleccionados[i] = función_mutación(Seleccionados[i])
            end
        end
        # Paso 4: Reemplazo
        # Sustituimos los dos peores cromosomas por los dos nuevos
        índice_peor_1 = argmin(evaluaciones_función_evaluación)
        # para que no vuelva a cogerlo, lo ponemos al valor máximo
        evaluaciones_función_evaluación[índice_peor_1] = 100
        índice_peor_2 = argmin(evaluaciones_función_evaluación)

        # actualizamos los cromosomas
        generación[índice_peor_1] = Seleccionados[1]
        generación[índice_peor_2] = Seleccionados[2]
        # y su valor de evaluación
        evaluaciones_función_evaluación[índice_peor_1] = función_evaluación(generación[índice_peor_1])
        evaluaciones_función_evaluación[índice_peor_2] = función_evaluación(generación[índice_peor_2])

        evaluaciones += 2
    end

    # Devolvemos el mejor cromosoma encontrado
    índice_mejor = argmax(evaluaciones_función_evaluación)
    evaluación_mejor = evaluaciones_función_evaluación[índice_mejor]
    mejor_cromosa = generación[índice_mejor]
    return mejor_cromosa, evaluación_mejor
end

function AGE_LearnerOneNN(data::Matrix{<:Real},labels,
        evaluaciones_máximas_FE,
        numero_cromosomas_por_generación,
        probabilidad_cruce,
        tamaño_cromosoma,
        función_cruce,
        función_mutación)
     # Creamos función objetivo
    a = 0.5
    umbral_tasa_reduccion = 0.1
    función_evaluación = CrearFuncionObjetivo(data, labels, a, umbral_tasa_reduccion )

    # Buscamos pesos de acorde al algoritmo AGG
    w, f_w = AGE(evaluaciones_máximas_FE,
        numero_cromosomas_por_generación,
        probabilidad_cruce,
        tamaño_cromosoma,
        función_evaluación,
        función_cruce,
        función_mutación)
    return WeightedLearnerEuclideanOneNN(w, data, labels), f_w, w

end


module ConfusionTest

using Test
using ForwardDiff

using LinearAlgebra

# Perturbation Confusion (Issue #83) #
#------------------------------------#

D = ForwardDiff.derivative

@test D(x -> x * D(y -> x + y, 1), 1) == 1
@test ForwardDiff.gradient(v -> sum(v) * D(y -> y * norm(v), 1), [1]) == ForwardDiff.gradient(v -> sum(v) * norm(v), [1])

const A = rand(10,8)
y = rand(10)
x = rand(8)

@test A == ForwardDiff.jacobian(x) do x
    ForwardDiff.gradient(y) do y
        dot(y, A*x)
    end
end

# Issue #238                         #
#------------------------------------#

m,g = 1, 9.8
t = 1
q = [1,2]
q̇ = [3,4]
L(t,q,q̇) = m/2 * dot(q̇,q̇) - m*g*q[2]

∂L∂q̇(L, t, q, q̇) = ForwardDiff.gradient(a->L(t,q,a), q̇)
Dqq̇(L, t, q, q̇) = ForwardDiff.jacobian(a->∂L∂q̇(L,t,a,q̇), q)
@test Dqq̇(L, t, q, q̇)  == fill(0.0, 2, 2)

q = [1,2]
p = [5,6]
function Legendre_transformation(F, w)
    z = fill(0.0, size(w))
    M = ForwardDiff.hessian(F, z)
    b = ForwardDiff.gradient(F, z)
    v = cholesky(M)\(w-b)
    dot(w,v) - F(v)
end
function Lagrangian2Hamiltonian(Lagrangian, t, q, p)
    L = q̇ -> Lagrangian(t, q, q̇)
    Legendre_transformation(L, p)
end

Lagrangian2Hamiltonian(L, t, q, p)
@test ForwardDiff.gradient(a->Lagrangian2Hamiltonian(L, t, a, p), q) == [0.0,g]

#267: let scoping
@noinline f83a(z, x) = x[1]
z83a = ([(1, (2), [(3, (4, 5, [1, 2, (3, (4, 5), [5])]), (5))])])
let z = z83a
    g = x -> f83a(z, x)
    h = x -> g(x)
    @test ForwardDiff.hessian(h, [1.]) == zeros(1, 1)
end

end # module


"""
    LinearKoopman(K, B, Q, P, discrete)

An approximation of the koopman operator which is linear in the states.

`K` is the array representing the operator, `B` is the (possible present) array
representing the influence of exogenuos inputs on the evolution.

`Q` and `P` are matrices used for updating the operator with new measurements.
`discrete` indicates if the operator is discrete or continuous.

The koopman operator is callable with the typical signature of `f(u,p,t)` and `f(du,u,p,t)` respectively.

# Example

```julia
k = LinearKoopman([1.0 0; 0 0.3], [], [], [], true)

u = k([2.0; 0.5], nothing, nothing)
du = similiar(u)
k(du, u, nothing, nothing)
```
"""
mutable struct LinearKoopman <: AbstractKoopmanOperator
    operator::AbstractArray
    input::AbstractArray

    Q::AbstractArray
    P::AbstractArray

    discrete::Bool
end

outputmap(k::LinearKoopman) = throw(AssertionError("Linear Koopman Operator has no output map."))

function (k::LinearKoopman)(u, p, t)
    return k.operator*u
end

function (k::LinearKoopman)(du, u, p, t)
    mul!(du, k.operator, u)
end

"""
    update!(k, X, Y; threshold = eps())

Update the Koopman `k` given new data `X` and `Y`. The operator is updated in place, if
the L2 error of the prediction exceeds the `threshold`.

"""
function update!(k::LinearKoopman, X::AbstractArray, Y::AbstractArray; threshold::T = eps()) where {T <: Real}
    @assert updateable(k) "Linear Koopman is not updateable."

    ϵ = norm(Y-operator(k)*X, 2)

    if ϵ < threshold
        return
    end

    k.Q += Y*X'
    k.P += X*X'
    k.operator .= k.Q*inv(k.P)
    return
end


function rilt(t,y,smin,smax,N,α=missing;kwargs...)
    s = smin * (smax / smin).^range(0, 1, length=N + 1)
   invert(t,y,s,(t,s) -> exp(-t*s),Tikhonov(α),kwargs...)
end
function invert(t,y,s,kernel::Function,aamethod::AutoRegMethod;kwargs...)
    reg = auto_reg(t,y,s,kernel::Function,aamethod;kwargs...)
    invert(t,y,s,kernel,reg;kwargs...)
end

"""
    invert(s,y,t,k::Function,reg;yoffset=true,tdomain=:realplus)

Compute the discretized form a(s) in y(t) = ∫a(s)k(t,s)ds. `s` and `y` represents
the data wich we want to invert on discrete points `t`.

# Examples
```julia-repl
julia> bar([1, 2], [1, 2])
1
```
"""

function invert(s,y,t,k::Function,reg;yoffset=true,kwargs...)
    AR,yr = build_ar(s,y,t,k,reg,yoffset)
    yt=get_yt(AR,yr;kwargs...)
    reshape
    return (t,yt[1:length(t),1],s,(AR*yt)[1:length(s),1])

end

function get_yt(AR,yr;tdomain=:realplus)

    if tdomain ==:real
        sy = (AR \ yr)
        return  sy
    end
    return nonneg_lsq(AR,yr;alg=:fnnls)

end

function build_ar(t,y,s,kernel,reg,yoffset)
    #TODO: whole function does allocate like hell when AutoRegMethod is present
    #   cacheing must be implemented
    # central points

    sc=  (s[1:end - 1] + s[2:end]) / 2
    N=length(sc)
    ds = diff(s)
    dd=ones(length(ds))
    L = get_regularization_matrix(reg,N,dd)

    #Build Matrix approximation of kernel
    sType = eltype(s)
    lType = eltype(L)
    aType =promote_type(sType,lType,eltype(t),eltype(ds))
    A = aType[kernel(i, sc[j])*ds[j] for i in t, j in eachindex(sc)]

    if reg.α isa AbstractArray
        L= L*(Diagonal(reg.α))
    else
        L= L*(Diagonal(reg.α*ones(aType,length(sc))))
    end

    if yoffset
        #add a ofset column
        A= hcat(A,ones(size(A,1)))
        #Do not regularize the y-offset
        L= hcat(L,zeros(size(L,1)))
       # push!(ds, )
        push!(sc, 0)
    end

    AR = vcat(A, L)

    # add  entry to y to store the regularization
    yr = vcat(y, zeros(size(L,1 )))
    yr=convert.((eltype(AR),),yr) #allows for automatic dfferentation  NNLS

    return (AR,yr)
end


# This file implements methods from the paper
# "Boltzmann Encoded Adversarial Machines" of Fisher et al. (2018)

function nearestneighbourcritic(xdata::M, xmodel::M, k::Int,
      distanceweighted::Bool = true) where {M <: AbstractArray{Float64,2}}

   ndatasamples = size(xdata, 1)
   if ndatasamples != size(xmodel, 1)
      error("Number of samples in `xdata` and `xmodel` must be equal.")
   end

   x = vcat(xdata, xmodel)

   nsamples = 2 * ndatasamples

   # Distancematrix is filled with the distances
   # between all combinations of samples in x.
   distancematrix = Matrix{Float64}(undef, nsamples, nsamples)
   for i = 1:nsamples
      for j = (i+1):nsamples
         distancematrix[i, j] = distancematrix[j, i] = norm(x[j, :] - x[i, :])
      end
      distancematrix[i, i] = 0.0
   end

   ret = Vector{Float64}(undef, ndatasamples)
   eps = 0.1
   if distanceweighted
      for i = 1:ndatasamples
         knearestindices = partialsortperm(distancematrix[i, :], (1:k) .+ 1)
         nearestdatasamplesindices = knearestindices[knearestindices .<= ndatasamples]
         datasamplesdistances = distancematrix[i, nearestdatasamplesindices]
         ret[i] = 2.0 * ( sum(1.0 ./ (datasamplesdistances .+ eps)) /
               sum(1.0 ./ (distancematrix[i, knearestindices] .+ eps))) - 1
      end
   else
      for i = 1:ndatasamples
         knearestindices = partialsortperm(distancematrix[i, :], (1:k) .+ 1)
         nnearestdatasamples = sum(knearestindices .<= ndatasamples)
         ret[i] = 2 * nnearestdatasamples / k - 1
      end
   end

   ret
end


# MFCC

function mfcc(x::StridedVector{Cdouble}, order=13, samplerate=16000;
              α::Float64=0.97,
              eps::Float64=1.0,
              windowlen::Int=length(x),
              framelen::Int=length(x),
              numfilterbunks::Int=20,
              cepslift::Int=22,
              usedft::Bool=true,
              usehamming::Bool=true,
              czero::Bool=false,
              power::Bool=false)
    if order+1 > numfilterbunks
        throw(ArgumentError("order+1 must be less than or equal to the number of filterbanks"))
    end

    # order of MFCC + 0-th + power
    cc = zeros(order+2)

    ccall((:mfcc, libSPTK), Void,
          (Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cdouble, Cdouble,
           Cint, Cint, Cint, Cint, Cint, Bool, Bool),
          x, cc, samplerate, α, eps,
          windowlen, framelen, order+1, numfilterbunks, cepslift,
          usedft, usehamming)

    # after ccall we get
    # mfcc[0], mfcc[1], mfcc[2], ... mfcc[m-1], E(C0), Power

    if !czero && power
        cc[endof(cc)-1] = cc[endof(cc)]
    end

    if !power
        cc = cc[1:endof(cc)-1]
    end

    if !czero
        cc = cc[1:endof(cc)-1]
    end

    cc
end


module TestGraphGenerators
using Test

using Catlab.Graphs.BasicGraphs, Catlab.Graphs.GraphGenerators
using Catlab.CategoricalAlgebra: homomorphisms

# Path graphs
#------------

n = 5
g = path_graph(Graph, n)
@test (nv(g), ne(g)) == (n, n-1)
g = path_graph(SymmetricGraph, n)
@test (nv(g), ne(g)) == (n, 2(n-1))

# Cycle graphs
#-------------

g = cycle_graph(Graph, n)
@test (nv(g), ne(g)) == (n, n)
g = cycle_graph(SymmetricGraph, n)
@test (nv(g), ne(g)) == (n, 2n)

# Complete graphs
#----------------

for T in (Graph, SymmetricGraph)
  g = complete_graph(T, n)
  @test (nv(g), ne(g)) == (n, n*(n-1))
end
for T in (ReflexiveGraph, SymmetricReflexiveGraph)
  g = complete_graph(T, n)
  @test (nv(g), ne(g)) == (n, n*n)
end

# Star graphs
#------------

g = star_graph(Graph, n)
@test (nv(g), ne(g)) == (n, n-1)
@test length(unique(src(g,e) for e in edges(g))) == 1
g = star_graph(SymmetricGraph, n)
@test (nv(g), ne(g)) == (n, 2(n-1))

# Wheel graphs
#-------------

g = wheel_graph(Graph, n)
@test (nv(g), ne(g)) == (n, 2(n-1))
triangle = Graph(3)
add_edges!(triangle, [1,2,1], [2,3,3])
@test length(homomorphisms(triangle, g)) == n-1

g = wheel_graph(SymmetricGraph, n)
@test (nv(g), ne(g)) == (n, 4(n-1))
triangle = cycle_graph(SymmetricGraph, 3)
@test length(homomorphisms(triangle, g)) == 6(n-1) # == 3! * (n-1)

end


######
##FEM Heat Nonlinear Test
######
using FiniteElementDiffEq, DiffEqProblemLibrary, DiffEqPDEBase

#Define a parabolic problem
prob = prob_femheat_birthdeath

#Solve it with a bunch of different algorithms, plot solution
println("Euler")
sol = solve(prob,FEMDiffEqHeatEuler())

println("Semi-implicit Euler")
sol = solve(prob,FEMDiffEqHeatSemiImplicitEuler())

println("Semi-implicit Crank Nicholson")
sol = solve(prob,FEMDiffEqHeatSemiImplicitCrankNicholson())

dx = 1//2^(2)
dt = 1//2^(3)
T = 1
fem_mesh = parabolic_squaremesh([0 1 0 1],dx,dt,T,:neumann)
println("Implicit Euler")
sol = solve(prob,FEMDiffEqHeatImplicitEuler(),autodiff=true)
#println("Implicit Euler Autodiff False")
#sol = solve(prob,FEMDiffEqHeatImplicitEuler(),autodiff=false)
#plot(sol)

#Returns true if nonlinear solver is correct
bool1 = maximum(abs.(sol[end] .- .777))<.01

### Stochastic Tests

#Define a parabolic problem
T = 1
dx = 1//2^(3)
dt = 1//2^(7)
fem_mesh = parabolic_squaremesh([0 1 0 1],dx,dt,T,:neumann)
prob = prob_femheat_stochasticbirthdeath

#Solve it with a bunch of different algorithms, plot solution
println("Euler")
sol = solve(prob,FEMDiffEqHeatEuler())

println("Semi-implicit Euler")
sol = solve(prob,FEMDiffEqHeatSemiImplicitEuler())

#=
# CG and GMRES require size 1 vector, breaks with numvars change
println("Semi-implicit Crank Nicholson")
sol = solve(prob,alg=:SemiImplicitCrankNicholson,solver=:CG)

println("Semi-implicit Crank Nicholson GMRES")
sol = solve(prob,alg=:SemiImplicitCrankNicholson,solver=:GMRES)
=#

#Define a quicker problem
prob =  prob_femheat_stochasticbirthdeath_fast
println("Implicit Euler")
sol = solve(prob,FEMDiffEqHeatImplicitEuler(),autodiff=true)
sol = solve(prob,FEMDiffEqHeatImplicitEuler(),autodiff=false)
#plot(sol)

bool1


instructions = readlines("day12/input.txt")

mutable struct Ship
    x::Int
    y::Int
    angle::Int
end

ship = Ship(0, 0, 0)

function step_instruction(instruction)
    global ship
    command, amount = instruction[1], parse(Int, instruction[2:end])
    if command == 'N'
        ship.y += amount
    elseif command == 'S'
        ship.y -= amount
    elseif command == 'E'
        ship.x += amount
    elseif command == 'W'
        ship.x -= amount
    elseif command == 'L'
        ship.angle = mod((ship.angle + amount), 360)
    elseif command == 'R'
        ship.angle = mod((ship.angle - amount), 360)
    elseif command == 'F'
        directions = Dict(90 => 'N', 180 => 'W', 270 => 'S', 0 => 'E')
        step_instruction(directions[ship.angle] * string(amount))
    end
end

for ins in instructions
    step_instruction(ins)
end

println(abs(ship.x)+abs(ship.y))


include("HWLC.jl")
using .HWLC
using Plots, Distributions, MAT

#Set plot parameters
gr(xlabel="F(X) (pN)", label="",ylabel="F'(X) (pN/nm)",
    grid=false,guidefont=("Arial",10),
    legend=:topleft,
    legendfont=("Arial",10),scale=:log10)

#Import experiment data for comparison
data=matread("Experimental_data/U2OS_ForceStiffnessStd.mat")
mean_experiment=vec(data["k_avg"])
std_exp=vec(data["k_err"])
fs_exp=vec(data["f_ax"])

#Set parameters
kT=4.22
Marko_Siggia=true

f_min,f_max,N_f=extrema(fs_exp)...,500
forces=exp.(range(log(f_min),log(f_max),length=N_f))

N=7        #Number of component chains
L=1200.     #Total length of assembly

#Power-law distribution for persistence lengths
slope=0.86        #Slope of stiffness-force graph
# Set parameters for f_c distribution
minfc, maxfc=9.,1000.
λ=160.

"""
Show individual and total curves for one assembly
"""
fcs=rand(truncated(Exponential(λ),minfc,maxfc),N)
persistence_lengths=fcs.^(-1)*kT
#ls=ones(N)*L/N  #All equal length
ls=rand(Normal(L/N,L/N*0.1),N) #Alternatively sample from normal distr

total_ext,extensions=force_extensions(forces,ls,persistence_lengths,kT,Marko_Siggia)
total_stiffness=stiffnesses(forces,total_ext)[1]
individual_stiffnesses=stiffnesses(forces,extensions)

#Plot individual and total response
plot(forces[2:end],total_stiffness, label="Assembly");
plot!([20,200],[10^-1.5,(200/20)^slope*10^-1.5], color="black",label="Slope $slope");
for ind in individual_stiffnesses
    plot!(forces[2:end],ind, color="lightgrey")
end
display(current())
png("Plots/exp_distr_individual_responses")

"""
Sample many assemblies, and compare mean
stiffening behaviour to data
"""
n_samples=15
sim_data=zeros(length(forces)-1,n_samples)
for i in 1:n_samples
    #sample assembly
    sample_ls=rand(Normal(L/N,L/N*0.1),N) #comment if using constant lengths
    sample_ps=rand(truncated(Exponential(λ),minfc,maxfc),N).^(-1)*kT
    tot,exts=force_extensions(forces,sample_ls,sample_ps,kT,Marko_Siggia)
    sim_data[:,i]=stiffnesses(forces,tot)[1]
end

mean_sim=mean(sim_data,dims=2)
std_sim=std(sim_data,dims=2)

plot(fs_exp,mean_experiment, label="Experimental mean",legend=:topleft, color="blue");
plot!(fs_exp,mean_experiment.+std_exp, color="lightblue");
plot!(fs_exp[findall(>(0.),mean_experiment.-std_exp)],(mean_experiment.-std_exp)[findall(>(0.),mean_experiment.-std_exp)], color="lightblue");
plot!(forces[2:end],mean_sim, label="Sim mean", color="orange", ribbon=std_sim)
png("Plots/exp_distr_compare_data_sim")


module BHTsne

#using ArgParse
#using StrPack
using PyCall
using Statistics
using LinearAlgebra
# @pyimport struct # error, replace with
pystruct = pyimport("struct")

const BH_TSNE_path = joinpath(dirname(@__FILE__), "cpp/bh_tsne")

export bh_tsne

function __init__()
    # if OS_NAME == :Windows
    #     error("Sorry, BHTsne.jl doesn't support Windows now")
    # end
    if !isfile(BH_TSNE_path)
        cd(dirname(BH_TSNE_path)) do
            run(`g++ sptree.cpp tsne.cpp -o bh_tsne -O2`)
        end
    end
end

function read_unpack(fmt, f)
    # struct.unpack(fmt, read(f, struct.calcsize(fmt)))
    pystruct[:unpack](fmt, read(f, pystruct[:calcsize](fmt)))
end

function bh_tsne(samples;no_dims=2, initial_dims=50, perplexity=50,
                 theta=0.5, randseed=-1, verbose=false)

    samples = broadcast(-, samples, mean(samples))
    cov_x = samples' * samples
    eig_val,eig_vec = eigen(cov_x)

    eig_vec = eig_vec[:,sortperm(eig_val,rev=true)]

    num_eigvec = size(eig_vec, 2)
    if initial_dims > num_eigvec
        initial_dims = num_eigvec
    end

    eig_vec = eig_vec[:,1:initial_dims]
    samples = samples * eig_vec

    sample_count, sample_dim = size(samples)

    mktempdir() do temp_dir

        open(joinpath(temp_dir, "data.dat"),"w") do data_file
            write(data_file, pystruct[:pack]("iiddi", sample_count,sample_dim, theta, perplexity,no_dims))
            nrow,ncol = size(samples)
            fmt = repeat("d",ncol)
            for i = 1:nrow
                # data_packed = mapreduce(x->struct.pack("d",x), *, samples[i,:])
                data_packed = mapreduce(x->pystruct[:pack]("d",x), *, samples[i,:])
                write(data_file, data_packed)
            end
            if randseed != -1
                # write(data_file, struct.pack("i",randseed))
                write(data_file, pystruct[:pack]("i",randseed))
            end
        end

        cd(temp_dir) do
            if verbose
                try
                    run(pipeline(`$BH_TSNE_path`, stdout=STDERR))
                catch excp
                    warn(excp)
                    error("ERROR: Call to bh_tsne exited with a non-zero return code exit status,
                          please refer to the bh_tsne output for further details")
                end
            else
                try
                    run(pipeline(`$BH_TSNE_path`, stdout=DevNull))
                catch excp
                    warn(excp)
                    error("ERROR: Call to bh_tsne exited with a non-zero return code exit status,
                          please enable verbose mode and refer to the bh_tsne output for further details")
                end
            end
        end

        open(joinpath(temp_dir, "result.dat"),"r") do output_file
            result_samples, result_dims = read_unpack("ii", output_file)
            results = [read_unpack(repeat("d", result_dims), output_file) for _ in 1:result_samples]
            results = [(read_unpack("i", output_file),e) for e in results]
            sort!(results)
            ret = Array{Float64,2}(result_dims, result_samples)
            for i in 1:result_samples
                ret[:,i] = collect(results[i][2])
            end
            return ret'
        end

    end

end

#=
function parse_commandline()
    s = ArgParseSettings()

    @add_arg_table s begin
        "-d","--no_dims"
            arg_type = Int64
        "-p","--perplexity"
            arg_type = Float64
            default = 50
        "-t","--theta"
            arg_type = Float64
            default = 0.5
        "-r","--randseed"
            arg_type = Int64
            default = -1
        "-n","--initial_dims"
            arg_type = Int64
            default = 50
        "-v","--verbose"
            action = :store_true
        "-i","--input"
            arg_type = ASCIIString
            default = STDIN
        "-o","--output"
            arg_type = ASCIIString
            default = STDOUT
    end

    parse_args(s)
end
=#

end


using CompScienceMeshes
using Test

m1 = meshrectangle(1.0, 0.5, 0.1, 3)
m1 = CompScienceMeshes.translate(m1, [0.0, 0.5, 0.0])
m2 = meshrectangle(1.0, 0.5, 0.1, 3)

nc1 = numcells(m1)
nc2 = numcells(m2)

nv1 = numvertices(m1)
nv2 = numvertices(m2)

M1 = weld(m1,m2)
@test numcells(M1) == nc1 + nc2
@test numvertices(M1) == nv1 + nv2 - 11

M2 = weld(m1,-m2)
@test numcells(M2) == nc1 + nc2
@test numvertices(M2) == nv1 + nv2 - 11


"""
PDE_aa(n=31, m=3; Vstore=Float64[], pdata=nothing, beta=1.0, maxit=40)
Solve preconditioned convection-diffusion equation with hardwired left
preconditioner using Anderson acceleration.

If you're putting this in a loop, you should allocate Vstore to
zeros(n*n,2*(mmax+1)). Otherwise I will make a decision for you and
allocate for Vstore with each call to this function. The story on
pdata is the same. If you are calling this several times with the
same value of n, build pdata outside the call.
"""
function PDE_aa(n=31, m=3; Vstore=Float64[], pdata=nothing, beta=1.0, maxit=40)
#
# Process Vstore and pdata
#
(pdata != nothing) || (pdata=pdeinit(n));
(length(Vstore)>0) || (Vstore=zeros(n*n,3*m + 3))
(mv, nv)  = size(Vstore)
dimvtest= ((mv == n*n) && (nv >= 2*m + 4))
dimvtest || error("Vstore too small")
#
# Call aasol and return the results.
#
u0=zeros(n*n);
rtol = 1.e-8;
atol = 1.e-8;
aout = aasol(hardleftFix!, u0, m, Vstore; pdata=pdata, maxit=maxit,
             rtol=rtol, atol=atol, beta=beta);
return aout
end


"""
Created on Mon Apr 13 2020

@author: Yoann Pradat

Running NMF algorithms on a matrix that can be factorized exactly.
"""

include("../../../src/VNMF.jl")
using .VNMF
using Distributions
using Random
include("../util/plot_cost.jl")

gr() # set the backend to GR

println("current working directory: " * pwd())

#### # 1. SIMULATE DATA
#### # ################################################################################################################

#### simulate matrix for testing NMF
F = 10
N = 25
K = 5
rng = MersenneTwister(123)

#### truncated-normal factors with normal noise
dist_W = truncated(Normal(1, 1), 0, Inf)
dist_H = truncated(Normal(1, 1), 0, Inf)

W = rand(rng, dist_W, F, K)
H = rand(rng, dist_H, K, N)

V = W * H

fo = "../../plot/simulation/nmf_exact"
mkpath(fo)

#### global params
global_params = NMFParams(
    init          = :random,
    dist          = Uniform(0,1),
    rank          = K,
    max_iter      = 20_000,
    stopping_crit = :none,
    verbose       = true,
)

#### # 2. FEVOTTE-IDIER NMF ALGORITHMS WITH KNOWN RANK
#### # ################################################################################################################

list_res = Array{NMFResults{Float64}, 1}()
list_lab = Array{String, 1}()
list_ls  = Array{Symbol, 1}()
list_col = Array{String, 1}()

local_params = FIParams(
    scale_W_iter = true,
    scale_W_last = true,
    α_H          = 0,
    α_W          = 0,
)

####
#### 2.1 β divergences, FI algorithms
####

for (β, col) in zip([2, 1, 0], ["black", "red", "green"])
    local_params.β = β

    run_mm = true
    run_me = true
    run_h  = true

    if β == 2
        run_h = false
    elseif β == 1
        run_h  = false
        run_me = false
    end

    if run_mm
        local_params.alg = :mm

        push!(list_res, nmf_FI(V, global_params, local_params))
        push!(list_lab, raw"$\mathrm{MM} \quad \beta =" * "$(β)" * raw"$")
        push!(list_ls, :dash)
        push!(list_col, col)
    end

    if run_me
        local_params.alg = :me

        push!(list_res, nmf_FI(V, global_params, local_params))
        push!(list_lab, raw"$\mathrm{ME} \quad \beta =" * "$(β)" * raw"$")
        push!(list_ls, :solid)
        push!(list_col, col)
    end

    if run_h
        local_params.alg = :h

        push!(list_res, nmf_FI(V, global_params, local_params))
        push!(list_lab, raw"$\mathrm{H} \quad \beta =" * "$(β)" * raw"$")
        push!(list_ls, :dot)
        push!(list_col, col)
    end

end

####
#### 2.2 PLOT
####

fn = joinpath(fo, "nmf_FI_exact.pdf")
plot_cost(list_res, list_lab, list_ls, list_col, fn)

#### # 3. MU NMF ALGORITHMS WITH KNOWN RANK
#### # ################################################################################################################

list_res = Array{NMFResults{Float64}, 1}()
list_lab = Array{String, 1}()
list_ls  = Array{Symbol, 1}()
list_col = Array{String, 1}()

####
#### 2.1 β divergences
####

for (β, col) in zip([2, 1, 0], ["black", "red", "green"])
    local_params = MUParams(
        β       = β,
        scale_W = true,
        div     = :β,
        alg     = :mu
    )

    push!(list_res, nmf_MU(V, global_params, local_params))
    push!(list_lab, raw"$\mathrm{MU} \quad \beta =" * "$(β)" * raw"$")
    push!(list_ls, :solid)
    push!(list_col, col)
end

####
#### 2.2 α divergences
####

for (α, col) in zip([2, 1, 0], ["black", "red", "green"])
    local_params = MUParams(
        α            = α,
        scale_W_iter = true,
        scale_W_last = true,
        div          = :α,
        alg          = :mu
    )

    push!(list_res, nmf_MU(V, global_params, local_params))
    push!(list_lab, raw"$\mathrm{MU} \quad \alpha =" * "$(α)" * raw"$")
    push!(list_ls, :dash)
    push!(list_col, col)
end

####
#### 2.3 PLOT
####

fn = joinpath(fo, "nmf_MU_exact.pdf")
plot_cost(list_res, list_lab, list_ls, list_col, fn)


# Try to approximate exponential-sine-squared kernel:
using PyPlot
using Optim

function expsin2_approx()
nexp = 5
ngamma = 100
coeff_grid = zeros(ngamma,nexp)
#gamma_grid = logspace(-2,0.5,ngamma)
gamma_grid = linspace(0.01,3.0,ngamma)
ntime = 200
time = collect(linspace(0,2pi,ntime))

gamma = 1.0
gaussian=exp(-gamma.*sin(time).^2)

function compute_chi(x)
model = zeros(ntime)
for k=1:nexp
  model += x[k].*cos((k-1)*2.*time)
end
return sum((gaussian-model).^2)
end

npar = nexp

for i=1:ngamma
gamma = gamma_grid[i]
gaussian=exp(-gamma.*sin(time).^2)

abest=zeros(npar)
abest = [0.6,0.2,0.01,0.05,.025,.025]

atrial = zeros(npar)
for k=1:npar
  atrial[k] = abest[k]
end
model = zeros(ntime)
for k=1:nexp
  model += atrial[k].*cos((k-1)*2.*time)
end
clf()
plot(time,gaussian)
plot(time,model)
#chibest=sum((gaussian-model).^2)
chibest=compute_chi(atrial)

println("Initial chi-square: ",chibest)
#read(STDIN,Char)

result = optimize(compute_chi, atrial, BFGS(), OptimizationOptions(autodiff = true))

println(Optim.minimizer(result),Optim.minimum(result))
abest= Optim.minimizer(result)
clf()
plot(time,gaussian)
model = zeros(ntime)
for k=1:nexp
  model += abest[k].*cos((k-1)*2.*time)
end
plot(time,model)
plot(time,(gaussian-model))
chi=sum((gaussian-model).^2)
println("New minimum: ",chi," ",abest," ",std(gaussian-model)," ",maximum(abs(gaussian-model)))
#read(STDIN,Char)
coeff_grid[i,:] = abest
end

#coeff1 = log10(vec(coeff_grid[:,1]))

#function coeff1_fit(x)
## Fit the first coefficient:
#model= x[1]+x[2].*gamma_grid.^x[3]
#return sum((coeff1-model).^2)
#end

#x1 = [0.0,-1.75,0.2]
#result = optimize(coeff1_fit, x1, BFGS(), OptimizationOptions(autodiff = true))
#clf()
#loglog(gamma_grid,coeff_grid[:,1])
#x1 = Optim.minimizer(result)
##loglog(gamma_grid,10.^(-x1[2].*exp(log10(gamma_grid)*x1[1])),"r-")
#loglog(gamma_grid,10.^(x1[1]+x1[2].*gamma_grid.^x1[3]),"r-")
#println(Optim.minimizer(result),Optim.minimum(result))
#println("Fit to first coefficient")
#read(STDIN,Char)

# Now fit the grid of coefficients:
coeff2 = vec(coeff_grid[:,1])

ord = 1.0
function coeff2_fit(x)
# Fit the second-fourth coefficient:
model= x[1]+x[2].*exp(-gamma_grid.^x[3].*x[4])
return sum((coeff2-model).^2)
end

x1 = [0.45,0.4,1.0,1.0]
result = optimize(coeff2_fit, x1, BFGS(), OptimizationOptions(autodiff = true))
x1 = Optim.minimizer(result)
clf()
plot(gamma_grid,coeff2)
plot(gamma_grid,x1[1]+x1[2].*exp(-gamma_grid.^x1[3].*x1[4]))
println(Optim.minimizer(result),Optim.minimum(result))
println("Fit to first coefficient")
read(STDIN,Char)

coeff2 = vec(coeff_grid[:,2])
ord = 1.0
x2 = [0.45,0.4,1.0,1.0]
result = optimize(coeff2_fit, x2, BFGS(), OptimizationOptions(autodiff = true))
x2 = Optim.minimizer(result)
clf()
plot(gamma_grid,coeff2)
plot(gamma_grid,x2[1]+x2[2].*exp(-gamma_grid.^x2[3].*x2[4]))
println(Optim.minimizer(result),Optim.minimum(result))
println("Fit to second coefficient")
read(STDIN,Char)

coeff2 = vec(coeff_grid[:,3])
ord = 2.0
x3 = [0.45,0.4,1.0,1.0]
result = optimize(coeff2_fit, x3, BFGS(), OptimizationOptions(autodiff = true))
x3 = Optim.minimizer(result)
clf()
plot(gamma_grid,coeff2)
plot(gamma_grid,x3[1]+x3[2].*exp(-gamma_grid.^x3[3].*x3[4]))
println(Optim.minimizer(result),Optim.minimum(result))
println("Fit to third coefficient")
read(STDIN,Char)

coeff2 = vec(coeff_grid[:,4])
ord = 3.0
x4 = [0.45,0.4,1.0,1.0]
result = optimize(coeff2_fit, x4, BFGS(), OptimizationOptions(autodiff = true))
x4 = Optim.minimizer(result)
clf()
plot(gamma_grid,coeff2)
plot(gamma_grid,x4[1]+x4[2].*exp(-gamma_grid.^x4[3].*x4[4]))
println(Optim.minimizer(result),Optim.minimum(result))
println("Fit to fourth coefficient")
read(STDIN,Char)

coeff2 = vec(coeff_grid[:,5])
ord = 4.0
x5 = [0.45,0.4,1.0,1.0]
result = optimize(coeff2_fit, x5, BFGS(), OptimizationOptions(autodiff = true))
x5 = Optim.minimizer(result)
clf()
plot(gamma_grid,coeff2)
plot(gamma_grid,x5[1]+x5[2].*exp(-gamma_grid.^x5[3].*x5[4]))
println(Optim.minimizer(result),Optim.minimum(result))
println("Fit to fifth coefficient")
read(STDIN,Char)

# Now test with random numbers:
ntest = 10000
dev = zeros(ntest)
for i=1:ntest
  time = rand()*2pi
  gamma = rand()*3.0
  model = 0.0
  model += x1[1]+x1[2].*exp(-gamma.^x1[3].*x1[4])
  model += (x2[1]+x2[2].*exp(-gamma.^x2[3].*x2[4]))*cos(2*time)
  model += (x3[1]+x3[2].*exp(-gamma.^x3[3].*x3[4]))*cos(4*time)
  model += (x4[1]+x4[2].*exp(-gamma.^x4[3].*x4[4]))*cos(6*time)
  model += (x5[1]+x5[2].*exp(-gamma.^x5[3].*x5[4]))*cos(8*time)
  value = exp(-gamma*sin(time)^2)
  dev[i]=value-model
  println(i," ",gamma," ",time," ",value," ",model," ",value-model)
end
println("Maximum deviation:  ",maximum(abs(dev)))
println("Standard deviation: ",std(dev))
read(STDIN,Char)

return gamma_grid,x1,x2,x3,x4,x5
end


using Random
using MLJ
using SigmaRidgeRegression

id_design = BlockCovarianceDesign([IdentityCovarianceDesign(), IdentityCovarianceDesign()])
ps = [100; 100]
grp = GroupedFeatures(ps)
design = set_groups(id_design, grp)
αs = [1.0; 1.0]

ntest = 20_000
ntrain = 400

ridge_sim = GroupRidgeSimulationSettings(;
    groups = grp,
    ntrain = ntrain,
    ntest = ntest,
    Σ = design,
    response_model = RandomLinearResponseModel(; αs = αs, grp = grp),
)

Random.seed!(1)
sim_res = simulate(ridge_sim)

@test length.(sim_res.resampling_idx[1]) == (ntrain, ntest)

X_train = sim_res.X[sim_res.resampling_idx[1][1], :]
Y_train = sim_res.Y[sim_res.resampling_idx[1][1]]

X_test = sim_res.X[sim_res.resampling_idx[1][2], :]
Y_test = sim_res.Y[sim_res.resampling_idx[1][2]]

λs = [1.0; 2.0]
multiridge =
    MultiGroupRidgeRegressor(; groups = grp, λs = λs, center = false, scale = false)

mse = mse_ridge(
    StatsBase.fit(MultiGroupRidgeRegressor(; groups = grp, λs = λs), X_train, Y_train, grp),
    X_test,
    Y_test,
)

_mach = machine(multiridge, MLJ.table(sim_res.X), sim_res.Y)
_eval = evaluate!(_mach, resampling = sim_res.resampling_idx, measure = l2)

@test _eval.measurement[1] == mse


# Weighted metrics

###########################################################
#
#   Metric types
#
###########################################################

type WeightedEuclidean{T<:FloatingPoint} <: Metric
    weights::Vector{T}
end

type WeightedSqEuclidean{T<:FloatingPoint} <: SemiMetric
    weights::Vector{T}
end

type WeightedCityblock{T<:FloatingPoint} <: Metric
    weights::Vector{T}
end

type WeightedMinkowski{T<:FloatingPoint} <: Metric
    weights::Vector{T}
    p::Real
end

type WeightedHamming{T<:FloatingPoint} <: Metric
    weights::Vector{T}
end

result_type{T}(::WeightedEuclidean{T}, T1::Type, T2::Type) = T
result_type{T}(::WeightedSqEuclidean{T}, T1::Type, T2::Type) = T
result_type{T}(::WeightedCityblock{T}, T1::Type, T2::Type) = T
result_type{T}(::WeightedMinkowski{T}, T1::Type, T2::Type) = T
result_type{T}(::WeightedHamming{T}, T1::Type, T2::Type) = T

###########################################################
#
#   Specialized distances
#
###########################################################

# Weighted squared Euclidean

function wsumsqdiff(w::AbstractVector, a::AbstractVector, b::AbstractVector)
    n = get_common_len(w, a, b)::Int
    s = 0.
    for i = 1:n
        @inbounds s += abs2(a[i] - b[i]) * w[i]
    end
    s
end

evaluate{T<:FloatingPoint}(dist::WeightedSqEuclidean{T}, a::AbstractVector, b::AbstractVector) = wsumsqdiff(dist.weights, a, b)
wsqeuclidean(a::AbstractVector, b::AbstractVector, w::AbstractVector) = evaluate(WeightedSqEuclidean(w), a, b)

function pairwise!{T<:FloatingPoint}(r::AbstractMatrix, dist::WeightedSqEuclidean{T}, a::AbstractMatrix, b::AbstractMatrix)
    w = dist.weights
    m::Int, na::Int, nb::Int = get_pairwise_dims(length(w), r, a, b)

    sa2 = wsumsq_percol(w, a)
    sb2 = wsumsq_percol(w, b)
    At_mul_B!(r, a, b .* w)

    for j = 1 : nb
        for i = 1 : na
            @inbounds r[i,j] = sa2[i] + sb2[j] - 2 * r[i,j]
        end
    end
    r
end

function pairwise!{T<:FloatingPoint}(r::AbstractMatrix, dist::WeightedSqEuclidean{T}, a::AbstractMatrix)
    w = dist.weights
    m::Int, n::Int = get_pairwise_dims(length(w), r, a)

    sa2 = wsumsq_percol(w, a)
    At_mul_B!(r, a, a .* w)

    for j = 1 : n
        for i = 1 : j-1
            @inbounds r[i,j] = r[j,i]
        end
        @inbounds r[j,j] = 0
        for i = j+1 : n
            @inbounds r[i,j] = sa2[i] + sa2[j] - 2 * r[i,j]
        end
    end
    r
end

# Weighted Euclidean

function evaluate{T<:FloatingPoint}(dist::WeightedEuclidean{T}, a::AbstractVector, b::AbstractVector)
    sqrt(evaluate(WeightedSqEuclidean(dist.weights), a, b))
end

weuclidean(a::AbstractVector, b::AbstractVector, w::AbstractVector) = evaluate(WeightedEuclidean(w), a, b)

function colwise!{T<:FloatingPoint}(r::AbstractArray, dist::WeightedEuclidean{T}, a::AbstractMatrix, b::AbstractMatrix)
    sqrt!(colwise!(r, WeightedSqEuclidean(dist.weights), a, b))
end

function colwise!{T<:FloatingPoint}(r::AbstractArray, dist::WeightedEuclidean{T}, a::AbstractVector, b::AbstractMatrix)
    sqrt!(colwise!(r, WeightedSqEuclidean(dist.weights), a, b))
end

function pairwise!{T<:FloatingPoint}(r::AbstractMatrix, dist::WeightedEuclidean{T}, a::AbstractMatrix, b::AbstractMatrix)
    sqrt!(pairwise!(r, WeightedSqEuclidean(dist.weights), a, b))
end

function pairwise!{T<:FloatingPoint}(r::AbstractMatrix, dist::WeightedEuclidean{T}, a::AbstractMatrix)
    sqrt!(pairwise!(r, WeightedSqEuclidean(dist.weights), a))
end

# Weighted Cityblock

function evaluate{T<:FloatingPoint}(dist::WeightedCityblock{T}, a::AbstractVector, b::AbstractVector)
    w = dist.weights
    n = get_common_len(w, a, b)::Int
    s = 0.
    for i = 1:n
        @inbounds s += w[i] * abs(a[i] - b[i])
    end
    s
end
wcityblock(a::AbstractVector, b::AbstractVector, w::AbstractVector) = evaluate(WeightedCityblock(w), a, b)

# WeightedMinkowski

function evaluate{T<:FloatingPoint}(dist::WeightedMinkowski{T}, a::AbstractVector, b::AbstractVector)
    w = dist.weights
    p = dist.p
    n = get_common_len(w, a, b)
    s = 0.
    for i = 1:n
        @inbounds s += w[i] * (abs(a[i] - b[i]) .^ p)
    end
    s .^ inv(p)
end

wminkowski(a::AbstractVector, b::AbstractVector, w::AbstractVector, p::Real) = evaluate(WeightedMinkowski(w, p), a, b)

# WeightedHamming

function evaluate{T<:FloatingPoint}(dist::WeightedHamming{T}, a::AbstractVector, b::AbstractVector)
    n = length(a)
    w = dist.weights

    r = zero(T)
    for i = 1 : n
        @inbounds if a[i] != b[i]
            r += w[i]
        end
    end
    return r
end

whamming(a::AbstractVector, b::AbstractVector, w::AbstractVector) = evaluate(WeightedHamming(w), a, b)


"
Author: Daniel O'Connor
Email: dt.oconnor@outlook.com

File used to profile and benchmark the algorithms implmented.
"

include("samplers/tools.jl")
include("samplers/SA.jl")
include("samplers/SVMC.jl")
include("samplers/SVMC_TF.jl")
include("samplers/pimc.jl")
using Random
using ..SA
using ..SVMC
using ..SVMCTF
using ..PIMC
using ..SamplingTools
using LinearAlgebra
using Profile
using BenchmarkTools
using NPZ
using Interpolations

function main()
    # test 0 =  SA, test 1 = SVMC, test 2 = SVMCTF, test 3 = PIMC
    test = 3

    # simple PFC problem
    d = 0.1
    h = Dict([(1, -1.0), (2, 1.0 - d), (3, 1.0-d), (4, -1.0)])
    J = Dict([((1, 2), -1.0), ((2, 3), -1.0), ((3, 4), -1.0)])

    num_samples = 1000
    num_sweeps = 10000
    num_slices = 20
    nspins = length(h)
    temp = 0.01226 * 1.380649e-23 / (1e9 * 6.62607015e-34)

    a_sch= 1.0 .- collect(LinRange(0, 1, num_sweeps))
    b_sch= collect(LinRange(0, 1, num_sweeps))

    # schedule_data = npzread("data\\DWAVE_schedule.npz")
    # a_fn = LinearInterpolation(schedule_data[:, 1], 0.5.*schedule_data[:, 2])
    # b_fn = LinearInterpolation(schedule_data[:, 1], 0.5.*schedule_data[:, 3])
    # a_sch = a_fn.(collect(LinRange(0, 1, num_sweeps)))
    # b_sch = b_fn.(collect(LinRange(0, 1, num_sweeps)))

    a_sch_dict = Dict([(1, deepcopy(a_sch)), (2, 0.0 .* deepcopy(a_sch)),
                       (3, deepcopy(a_sch)), (4, deepcopy(a_sch)),])
    b_sch_dict = Dict([(1, deepcopy(b_sch)), (2, deepcopy(b_sch)),
                       (3, deepcopy(b_sch)), (4, deepcopy(b_sch)),])

    # initialize the problem parameter
    JM, M, degs = SamplingTools.hj_info(h, J)
    nbs_idxs, nbs_vals = SamplingTools.generate_nbr(nspins, M, maximum(degs) + 1)

    hot_t, cold_t = SamplingTools.default_temp_range(JM)
    temp_schedule = collect(LinRange(hot_t, cold_t, num_sweeps))

    # initialize state
    initial_state = Array{Float64, 1}([1.0, -1.0, -1.0, -1.0])
    if test == 0
        # spin_vectors = [deepcopy(initial_state) for i =1:num_samples]
        initial_state = rand([-1.0, 1.0], nspins, num_samples)
        spin_vectors = [initial_state[:, i] for i =1:num_samples]
        _fn = SA.anneal!
        args_in = [nbs_idxs, nbs_vals, temp_schedule]
    elseif test == 1 || test == 2
        # spin_vectors = [acos.(deepcopy(initial_state)) for i =1:num_reads]
        spin_vectors = fill(0.5 * pi, nspins, num_samples)
        spin_vectors = [spin_vectors[:, i] for i =1:num_samples]
        if test == 1
            _fn = SVMC.anneal!
        else
            _fn = SVMCTF.anneal!
        end
        args_in = [nbs_idxs, nbs_vals, a_sch, b_sch, temp]
    elseif  test == 3
        # spin_array = repeat(initial_state', num_slices)'
        # spin_vectors = [copy(spin_array) for i =1:num_samples]
        initial_state = rand([-1.0, 1.0], nspins, num_slices, num_samples)
        spin_vectors = [initial_state[:, :, i] for i =1:num_samples]
        _fn = PIMC.anneal!
        args_in = [nbs_idxs, nbs_vals, a_sch, b_sch, temp]
    end

    function run()
        @inbounds for i = 1:num_samples
            _fn(spin_vectors[i], args_in...)
        end
    end

    run()

    if test == 0
        print(discrete_aggregation(spin_vectors, num_samples, collect(UpperTriangular(JM))))
    elseif test == 1 || test == 2
        print(rotor_aggregation(spin_vectors, num_samples, collect(UpperTriangular(JM))))
    elseif  test == 3
        print(pimc_aggregation(spin_vectors, num_samples, collect(UpperTriangular(JM))))
    end

    p = @profiler run()
    Profile.print()

    return  @benchmark $run()
end


function createrandoms(n)
  i = 1
  a = zeros(n)
  while (i <= n)
    r1 = rand()
    r2 = rand()
    if (r2 < asin(sqrt(r1)) / (pi / 2))
      a[i] = r1
      i += 1
    end
  end
  return a
end

function tofile(a)
  outfile = open("generated_rand_numbers", "w")
  for x in a
    write(outfile, join(x), "\n")
  end
  close(outfile)
end

function fromfile()
  infile = open("generated_rand_numbers", "r")
  s = 0
  while !eof(infile)
    s += float(readline(infile))
  end
  close(infile)
  return s
end

n = 2000000

print("io.jl\n")

print("generation... ")
@time (a = createrandoms(n))

print("o time... ")
@time (tofile(a))

print("i time... ")
@time (s = fromfile())
print("result = ", s / n)
print('\n')


using CUDA
using PaddedViews

function k_index_kernel_fft_2d(data, out,offx, offy)
    dx = (blockIdx().x-1)*blockDim().x + threadIdx().x
    dy = (blockIdx().y-1)*blockDim().y + threadIdx().y
    #dz = (blockIdx().z-1)*blockDim().z + threadIdx().z

    #sx,sy,sz = size(data)
    # if dx > sx || dy > sy || dz > sz
    #     return nothing
    # end
    sx,sy = size(data)
    sx = sx - offx
    sy = sy - offy
    if dx > sx || dy > sy
        return nothing
    end

    out[dx,dy] = data[dx+offx, dy+offy]

    return nothing
end

function k_zeroing_2d(data, offx, offy)
    sx,sy = size(data)
    dx = (blockIdx().x-1)*blockDim().x + threadIdx().x
    dy = threadIdx().y
    if dx <= sx && dy <= offy
        @inbounds data[dx, end-dy+1] = 0
    end
    if dx <= sy && dy <= offx
        @inbounds data[end-dy+1, dx] = 0
    end
    return nothing
end

function cu_offset_result_2d(data, out, offx, offy)
    dims = size(data)
    tx = 32; ty =32
    bx = 1 + dims[1]÷tx
    by = 1 + dims[2]÷ty

    @cuda blocks=(bx,by) threads=(tx,ty) k_index_kernel_fft_2d(data,out,offx,offy)

    @show ty = 2*max(offy,offx)
    @show bx = 1 + max(dims...)÷tx
    @show by = 1
    @cuda blocks=(bx,by) threads=(tx,ty) k_zeroing_2d(out,2offx,2offy)
end

function k_elem_mul_2d!(A, B)
    tx = (blockIdx().x-1)*blockDim().x + threadIdx().x
    ty = (blockIdx().y-1)*blockDim().y + threadIdx().y
    sx,sy = size(A)
    if tx > sx || ty > sy
        return nothing
    end
    @inbounds a = A[tx,ty]
    @inbounds b = B[tx,ty]
    @inbounds A[tx,ty] = a*b
    return nothing
end

function elem_mul_2d!(A,B)
    sx,sy = size(A)
    tx = 32
    ty = 32
    bx = 1 + sx÷tx
    by = 1 + sy÷ty
    @cuda blocks=(bx,by) threads=(tx,ty) k_elem_mul_2d!(A,B)
    nothing
end

function k_cmplx_to_real_2d!(A)
    dx = (blockIdx().x-1)*blockDim().x + threadIdx().x
    dy = (blockIdx().y-1)*blockDim().y + threadIdx().y
    sx,sy = size(A)
    if dx > sx || dy > sy
        return nothing
    end
    a = A[dx,dy]
    A[dx,dy] = a.re
    return nothing
end

function cmplx_to_real_2d!(A)
    sx,sy = size(A)
    tx = 32
    ty = 32
    bx = 1 + sx÷tx
    by = 1 + sy÷ty
    @cuda blocks=(bx,by) threads=(tx,ty) k_cmplx_to_real_2d!(A)
    nothing
end

function fft_stencil_2d(data, template, t_steps=1)
    # template is a 2d square array
    r2 = size(template,1)
    r = 32
    while r < r2
        r += 32
    end
    println("FFT EXTRA RADIUS: ", r)
    r = r + 1
    print(template)
    print((size(data)))
    dx,dy = size(data)
    padded_data = PaddedView(0, data, (1:dx+r-1, 1:dy+r-1), (1:dx,1:dy))
    padded_template = PaddedView(0, template, (1:dx+r-1, 1:dy+r-1,), (1:r2,1:r2))
    ## Initial Upload
    fd = CuArray{CUDA.cuFloatComplex}(padded_data)
    ft = CuArray{CUDA.cuFloatComplex}(padded_template)
    temp = similar(ft)
    CUFFT.fft!(fd)
    CUFFT.fft!(ft)
    elem_mul_2d!(fd,ft)
    ## Time Loop
    CUFFT.ifft!(fd)
    r2 = r2÷2 + 1
    rn = r2:(r2+dx-1)
    return real(Array(fd))[rn,rn]
end


import Base: *

"""
    CircularConvolution{M, N, T}

A preplanned, circular convolution operator on an M × N matrix of data of type T

# Fields
- `Ĝ`: DFT coefficients of the convolution kernel
- `F`: preplanned rFFT operator
- `F⁻¹`: preplanned irFFT operator
- `paddedSpace`: scratch space to zero-pad the input matrix
- `Â`: scratch space to store the DFT coefficients of the zero-padded input matrix

# Constructors:

- `CircularConvolution(G::Matrix{T})`

# Example:
```jldoctest
julia> G = repeat(1.0:3,1,4)
3×4 Array{Float64,2}:
 1.0  1.0  1.0  1.0
 2.0  2.0  2.0  2.0
 3.0  3.0  3.0  3.0

julia> C = CircularConvolution(G)
Circular convolution on a 3 × 4 matrix of data type Float64

julia> C*reshape(1:12, 3, 4)
3×4 Array{Int64,2}:
 164  164  164  164
 130  130  130  130
 148  148  148  148
```
"""
struct CircularConvolution{M, N, T, K, KI}
    Ĝ::Matrix{ComplexF64}
    F::K
    F⁻¹::KI

    paddedSpace::Matrix{T}
    Â::Matrix{ComplexF64}
end

function Base.show(io::IO, c::CircularConvolution{M, N, T}) where {M, N, T}
    print(io, "Circular convolution on a $M × $N matrix of data type $T")
end

function CircularConvolution(G::AbstractMatrix{T},fftw_flags = FFTW.ESTIMATE; dtype = Float64) where {T}
    FFTW.set_num_threads(2)

    M, N = size(G)
    #paddedSpace = Matrix{Float64}(undef, 2M-1, 2N-1)
    paddedSpace = Matrix{dtype}(undef, 2M, 2N)

    if dtype == ComplexF64
      F = FFTW.plan_fft(paddedSpace, flags = fftw_flags)
    else
      F = FFTW.plan_rfft(paddedSpace, flags = fftw_flags)
    end

    mirror!(paddedSpace, G)
    Ĝ = F * paddedSpace

    Â = similar(Ĝ)
    #F⁻¹ = FFTW.plan_irfft(Â, 2M - 1, flags = fftw_flags)
    if dtype == ComplexF64
      F⁻¹ = FFTW.plan_ifft(Â, flags = fftw_flags)
    else
      F⁻¹ = FFTW.plan_irfft(Â, 2M, flags = fftw_flags)
    end

    CircularConvolution{M, N, dtype, typeof(F), typeof(F⁻¹)}(Ĝ, F, F⁻¹, paddedSpace, Â)
end

function mul!(out, C::CircularConvolution{M, N, T}, B) where {M, N, T}
    FFTW.set_num_threads(2)
    @assert size(out) == size(B) == (M, N)

    inds = CartesianIndices((M,N))
    fill!(C.paddedSpace, 0)
    copyto!(C.paddedSpace, inds, B, inds)
    mul!(C.Â, C.F, C.paddedSpace)

    C.Â .*= C.Ĝ

    mul!(C.paddedSpace, C.F⁻¹, C.Â)

    #copyto!(out, inds, C.paddedSpace, CartesianIndices((M:2M-1,N:2N-1)))
    copyto!(out, inds, C.paddedSpace, CartesianIndices((M+1:2M,N+1:2N)))

end

C::CircularConvolution * B = mul!(similar(B), C, B)

function mirror!(A, a::AbstractArray{T,2}) where {T}
    Nr, Nc = size(a)
    #A[1:Nr-1, 1:Nc-1] .= a[Nr:-1:2, Nc:-1:2]
    #A[1:Nr-1, Nc:end] .= a[Nr:-1:2, 1:Nc]
    #A[Nr:end, 1:Nc-1] .= a[1:Nr, Nc:-1:2]
    #A[Nr:end, Nc:end] .= a
    A .= 0
    A[2:Nr, 2:Nc] .= a[Nr:-1:2, Nc:-1:2]
    A[2:Nr, Nc+1:end] .= a[Nr:-1:2, 1:Nc]
    A[Nr+1:end, 2:Nc] .= a[1:Nr, Nc:-1:2]
    A[Nr+1:end, Nc+1:end] .= a[1:Nr, 1:Nc]
    A
end

function mirror(a::AbstractArray{T,2}) where {T}
    Nr, Nc = size(a)
    mirror!(zeros(T, 2Nr-1, 2Nc-1), a)
end


module SpecialPolynomials

using LinearAlgebra
#import LinearAlgebra: dot, ⋅
import SpecialFunctions: gamma

using Polynomials
import Polynomials:
    basis, isconstant, constantterm, assert_same_variable, StandardBasisPolynomial, ⟒
export basis

#import Intervals
#import Intervals: Open, Closed, Unbounded, bounds_types
import Polynomials: domain, Interval, Open, Closed, Unbounded, bounds_types
using QuadGK
using Memoize

using HypergeometricFunctions

include("utils.jl")
include("abstract.jl")

include("Orthogonal/abstract.jl")
include("Orthogonal/orthogonal.jl")
include("Orthogonal/cop.jl")
include("Orthogonal/ccop.jl")

include("Orthogonal/Bessel.jl")
include("Orthogonal/Chebyshev.jl")
include("Orthogonal/Hermite.jl")
include("Orthogonal/Laguerre.jl")
include("Orthogonal/Gegenbauer.jl")
include("Orthogonal/Jacobi.jl")
include("Orthogonal/Legendre.jl")
include("Orthogonal/WeightFunction.jl")

include("Orthogonal/Discrete/discrete-orthogonal.jl")
include("Orthogonal/Discrete/cdop.jl")
include("Orthogonal/Discrete/FallingFactorial.jl")
include("Orthogonal/Discrete/Hahn.jl")
include("Orthogonal/Discrete/Meixner.jl")
include("Orthogonal/Discrete/Krawchouk.jl")
include("Orthogonal/Discrete/Charlier.jl")
include("Orthogonal/Discrete/DiscreteChebyshev.jl")

include("Orthogonal/connection.jl")
include("Orthogonal/glaser-liu-rokhlin.jl")
include("Orthogonal/comrade.jl")

include("Interpolating/interpolating.jl")
include("Interpolating/Lagrange.jl")
include("Interpolating/Newton.jl")

include("Bernstein.jl")

using Requires
function __init__()
    @require FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c" include(
        "fasttransforms.jl",
    )
    @require FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838" include(
        "fastgaussquadrature.jl",
    )
end

end # module


module lewd # labour estimators, wrongly done

using Distributions
using LinearAlgebra
using Infiltrator
using StatsBase
using DataFrames
using Plots
using CategoricalArrays
using ShiftedArrays
using Chain
using DataFramesMeta
using LightGraphs
# using TikzGraphs
using Optim
using Random

# Find all movers defined as those with max spell > 0
function find_movers(df::DataFrame)
    move_df = @chain df begin
       groupby(:w_id)
       transform(:spell => maximum)
       subset(:spell_maximum => x -> x .> 0)
    end

   return move_df
end

# Get the next firm the mover is moving to
# by leading the firm column and extracting
# just the firm and next firm as one observation
function find_firm_links(mover_df::DataFrame)
    firm_link_df = @chain mover_df begin
        sort([:w_id, :t])
        groupby(:w_id)
        transform(:j => lead => :j_next)
        transform([:j, :j_next] => .==)
        subset(:j_j_next_BroadcastFunction => x -> x .== false, skipmissing = true)
        select(:j, :j_next)
    end
    return firm_link_df
end

function create_M_flows(link_df)
    M = @chain link_df begin
        groupby([:j, :j_next])
        combine(nrow => :count)
        unique()
    end
    return M
end

# %%

function create_adjacency_matrix(firm_link_df::DataFrame, df::DataFrame; count = false)
    adjacency_matrix = zeros(Int, maximum(df.j), maximum(df.j))
    if count == false
        firm_link_df[!, "count"] .= 1
    end
    for firm_a in unique(df.j), firm_b in unique(df.j)
        subset_a_df = firm_link_df[(firm_link_df.j .== firm_a) .& (firm_link_df.j_next .== firm_b), :]
        if size(subset_a_df)[1] != 0
            adjacency_matrix[firm_b, firm_a] = subset_a_df.count[1]
        end
    end
    return adjacency_matrix
end

function create_fixed_point_matrices(M_df, df)
    sum_flows = @chain M_df begin
        groupby(:j)
        combine(:count => sum)
        sort(:j)
        unique()
    end
    S_kk = Diagonal(sum_flows.count_sum)
    M_0 = create_adjacency_matrix(M_df, df, count = true)
    return S_kk, M_0
end

function create_fixed_point_matrices(df)
    link_df = find_firm_links(find_movers(df))
    M_df = create_M_flows(link_df)

    M_0 = create_adjacency_matrix(M_df, df, count = true)
    S_kk = Diagonal(sum(M_0, dims = 1)[:])
    return S_kk, M_0
end

function create_adjacency_matrix(firm_link_df::DataFrame, df::DataFrame)
    adjacency_matrix = zeros(Int, maximum(df.j), maximum(df.j))
    for firm_a in unique(df.j), firm_b in unique(df.j)
        subset_a_df = firm_link_df[(firm_link_df.j .== firm_a) .& (firm_link_df.j_next .== firm_b), :]
        if size(subset_a_df)[1] != 0
            adjacency_matrix[firm_a, firm_b] = 1
            adjacency_matrix[firm_b, firm_a] = 1
        end
    end
    return adjacency_matrix
end

function create_connected_df(df::DataFrame)
    move_df = find_movers(df)
    firm_df = find_firm_links(move_df)
    adjacency_matrix = create_adjacency_matrix(firm_df, move_df)

    simple_graph = SimpleGraph(adjacency_matrix);

    connected_network = connected_components(simple_graph)

    connected_set = connected_network[1]

    println("We have only $(length(connected_set)) firms fully connected $(length(connected_set)/maximum(df.j)) of the market...") # Double check we might be wrong...

    df_connected = df[in(connected_set).(df.j),:]
    return df_connected
end

function estimate_rank(S_kk, M_0; tol= 1e-6)
    N_connected = size(S_kk, 1)
    S_inv = inv(S_kk)
    initial_V_EE = fill(0.5, N_connected)
    lhs = S_inv * M_0 * exp.(initial_V_EE)
    rhs = exp.(initial_V_EE)
    i = 0
    max_diff = Inf
    while max_diff > tol
        i += 1
        rhs = lhs
        lhs = S_inv*M_0 * rhs
        max_diff = maximum(abs.(lhs .- rhs))
        println("max diff: $max_diff")
    end
    return rhs
end
end


@doc """
	ExactState <: QState
# Structure
- `site::ITensor`: An order N tensor for a N-qubit system.
""" ->
ExactState

@doc """
	ExactState(N::Int, init::Vector{T}) where T<:Number
Construct a N-qubit Exact state with all qubit initialized to given initial value.
""" ->
ExactState(N::Int, init::Vector{T} where T<:Number)

@doc """
	ExactState(N::Int)
Construct an ExactState with N qubits all initialized to |0>.
""" ->
ExactState(N::Int)

@doc """
	copy(state::ExactState)
Make a copy of given state.
""" ->
copy(state::ExactState)

@doc """
	isapprox(state1::ExactState, state2::ExactState)
Do an approximate comparison between two states.
""" ->
isapprox(state1::ExactState, state2::ExactState)

@doc """
	numQubits(state::ExactState)
Return number of qubits in given state.
""" ->
numQubits(state::ExactState)

@doc """
	toMPSState(state::ExactState; kwargs...)
Turn given ExactState into a MPSState.
# Keyword Arguments
* The same as keywords taken by `svd`. See also **svd**.
""" ->
toMPSState(state::ExactState; kwargs...)

@doc """
	ρ(state::ExactState, config::Vector{Int})
Return probability density of given configuration.
""" ->
ρ(state::ExactState, config::Vector{Int})


__precompile__(true)
module GaussianFilters

using LinearAlgebra
using ForwardDiff
using Random
import Random: rand

# Kalman, Extended Kalman, Unscented Kalman Filters

export
	DynamicsModel,
	LinearDynamicsModel,
	NonlinearDynamicsModel,
	ObservationModel,
	LinearObservationModel,
	NonlinearObservationModel,
	predict,
	measure
include("models.jl")

export
	AbstractFilter,
	KalmanFilter,
	ExtendedKalmanFilter,
	UnscentedKalmanFilter,
	GaussianBelief

include("kf_classes.jl")

export
	simulate_step,
	simulation,
	run_filter,
	unpack

include("simulate.jl")

export	update
include("kf.jl")
include("ekf.jl")

export
	unscented_transform,
	unscented_transform_inverse
include("ukf.jl")

# Gaussian Mixture PHD Filter

export
	Measurement,
	Dynamics,
	GaussianMixture,
	Spawn,
	PHDFilter
include("gmphd_classes.jl")

export
	update,
	predict,
	measure,
	prune
include("gmphd.jl")

export
	multiple_target_state_extraction
include("gmphd_extraction.jl")

# TODO: Add Labeled Multi-Bernoulli Filter

# Utilities
export
	belief_ellipse
include("utils.jl")

end # module