#   Unit "cca.jl" of the Diagonalization.jl Package for Julia language
#
#   MIT License
#   Copyright (c) 2019-2021,
#   Marco Congedo, CNRS, Grenoble, France:
#   https://sites.google.com/site/marcocongedo/home

# ? CONTENTS :
#   This unit implements Maximum Covariance Analysys (MCA) and
#   Canonical Correlation Analysis(CCA) filters, that is,
#   filters based on the singular value decomposition.

"""
```julia
(1)
function mca(Cxy :: Mat;
             eVar      :: TeVaro = ○,
             eVarMeth  :: Function = searchsortedfirst,
             simple    :: Bool = false)

(2)
function mca(X::Mat, Y::Mat;
             dims     :: Into = ○,
             meanX    :: Tmean = 0,
             meanY    :: Tmean = 0,
             wXY      :: Tw = ○,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false)

(3)
function mca(𝐗::VecMat, 𝐘::VecMat;
             dims     :: Into = ○,
             meanX    :: Into = 0,
             meanY    :: Into = 0,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false)
```

Return a [LinearFilter](@ref) object:

**(1) Maximum covariance analysis** with real or complex covariance matrix
`Cxy` of dimension ``n_x⋅n_y`` as input.

Differently from [PCA](@ref) and [Whitening](@ref), `Cxy`
is a generic `Matrix` object since it is not symmetric/Hermitian,
left alone square.

`eVar` and `eVarMeth` are keyword optional arguments for defining the
[subspace dimension](@ref) ``p`` using the `.arev` vector given by Eq. [mca.2].

The default values are:
- `eVar=0.999`
- `eVarMeth=searchsortedfirst`

If `simple` is set to `true`, ``p`` is set equal to ``min(n_x, n_y)``
and only the fields `.F` and `.iF` are written in the constructed object.
This option is provided for low-level work when you don't need to define
a subspace dimension or you want to define it by your own methods.

**(2) Maximum covariance analysis**
with real or complex data matrices `X` and `Y` as input.

`dims`, `meanX`, `meanY` and `wXY` are optional keyword arguments to
regulate the estimation of the cross-covariance matrix ``C_{xy}``.
Particularly (see [covariance matrix estimations](@ref)),
- `meanX` is the `mean` argument for data matrix `X`.
- `meanY` is the `mean` argument for data matrix `Y`.
- `wXY` is the weight argument for estimating a weighted cross-covariance matrix
  ``C_{XY}``.

Once the cross-covariance matrix estimated, method (1) is invoked
with optional keyword arguments `eVar`, `eVarMeth` and `simple`.
See method (1) for details.

**(3) Maximum covariance analysis**
with two vectors of real or complex data matrices
`𝐗` and `𝐘` as input. `𝐗` and `𝐘` must hold the same number of matrices
and corresponding pairs of matrices therein must comprise the same
number of samples.

`dims`, `meanX` and `meanY` are optional keyword arguments to
regulate the estimation of the cross-covariance matrices for
all pairs of corresponding data matrices in `𝐗` and `𝐘`.
See method (2) and [covariance matrix estimations](@ref).

The arithmetic mean of these cross-covariance matrices
is computed and method (1) is invoked with optional
keyword arguments `eVar`, `eVarMeth` and `simple`.
See method (1) for details.

**See also:** [PCA](@ref), [CCA](@ref), [gMCA](@ref), [mAJD](@ref).

**Examples:**

```julia
using Diagonalizations, LinearAlgebra, PosDefManifold, Test

# Method (1) real
n, t=10, 100
X=genDataMatrix(n, t)
Y=genDataMatrix(n, t)
Cx=Symmetric((X*X')/t)
Cy=Symmetric((Y*Y')/t)
Cxy=(X*Y')/t
mC=mca(Cxy, simple=true)
@test Cxy≈mC.F[1]*mC.D*mC.F[2]'
D=mC.F[1]'Cxy*mC.F[2]
@test norm(D-Diagonal(D))+1. ≈ 1.

# Method (1) complex
Xc=genDataMatrix(ComplexF64, n, t)
Yc=genDataMatrix(ComplexF64, n, t)
Cxc=Symmetric((Xc*Xc')/t)
Cyc=Symmetric((Yc*Yc')/t)
Cxyc=(Xc*Yc')/t
mCc=mca(Cxyc, simple=true)
@test Cxyc≈mCc.F[1]*mCc.D*mCc.F[2]'
Dc=mCc.F[1]'Cxyc*mCc.F[2]
@test norm(Dc-Diagonal(Dc))+1. ≈ 1.

# Method (2) real
mXY=mca(X, Y, simple=true)
D=mXY.F[1]'*Cxy*mXY.F[2]
@test norm(D-Diagonal(D))+1≈1.
@test mXY==mC

# Method (2) complex
mXYc=mca(Xc, Yc, simple=true)
Dc=mXYc.F[1]'*Cxyc*mXYc.F[2]
@test norm(Dc-Diagonal(Dc))+1. ≈ 1.
@test mXYc==mCc

# Method (3) real
# maximum covariance analysis of the average covariance and cross-covariance
k=10
Xset=[genDataMatrix(n, t) for i=1:k]
Yset=[genDataMatrix(n, t) for i=1:k]

m=mca(Xset, Yset)

# ... selecting subspace dimension allowing an explained variance = 0.5
m=mca(Xset, Yset; eVar=0.5)

# ... subtracting the mean from the matrices in Xset and Yset
m=mca(Xset, Yset; meanX=nothing, meanY=nothing, eVar=0.5)

# mca on the average of the covariance and cross-covariance matrices
# computed along dims 1
m=mca(Xset, Yset; dims=1, eVar=0.5)

# name of the filter
m.name

using Plots
# plot regularized accumulated eigenvalues
plot(m.arev)

# plot the original cross-covariance matrix and the rotated
# cross-covariance matrix
 Cmax=maximum(abs.(Cxy));
 h1 = heatmap(Cxy, clim=(-Cmax, Cmax), yflip=true, c=:bluesreds, title="Cxy");
 D=mC.F[1]'*Cxy*mC.F[2];
 Dmax=maximum(abs.(D));
 h2 = heatmap(D, clim=(0, Dmax), yflip=true, c=:amp, title="F[1]'*Cxy*F[2]");
 📈=plot(h1, h2, size=(700,300))
# savefig(📈, homedir()*"\\Documents\\Code\\julia\\Diagonalizations\\docs\\src\\assets\\FigMCA.png")
```

 ![Figure MCA](assets/FigMCA.png)

```julia
# Method (3) complex
# maximum covariance analysis of the average covariance and cross-covariance

k=10
Xcset=[genDataMatrix(ComplexF64, n, t) for i=1:k]
Ycset=[genDataMatrix(ComplexF64, n, t) for i=1:k]

mc=mca(Xcset, Ycset)

# ... selecting subspace dimension allowing an explained variance = 0.5
mc=mca(Xcset, Ycset; eVar=0.5)

# ... subtracting the mean from the matrices in Xset and Yset
mc=mca(Xcset, Ycset; meanX=nothing, meanY=nothing, eVar=0.5)

# mca on the average of the covariance and cross-covariance matrices
# computed along dims 1
mc=mca(Xcset, Ycset; dims=1, eVar=0.5)

# name of the filter
mc.name
```

"""
function mca(Cxy :: Mat;
             eVar      :: TeVaro = ○,
             eVarMeth  :: Function = searchsortedfirst,
             simple    :: Bool = false)

  args=("Maximum Covariance Anaysis", false)
  M=Matrix
  U, λ, V = svd(Cxy; full=true)
  λ = _checkλ(λ) # make sure no imaginary noise is present (for complex data)

  # typecast V, U', V' U1' and U2' as `Matrix` since they are of the Adjoint type
  simple ? LF([U, M(V)], [M(U'), M(V')], Diagonal(λ), ○, ○, ○, args...) :
  begin
     eVar, D, U1, U2, p, arev=_ssdxy!(eVar, λ, U, M(V), _minDim(Cxy), eVarMeth)
     LF([U1, U2], [M(U1'), M(U2')], D, eVar, λ, arev, args...)
  end
end

# MCA with data as input (separated data): API
function mca(X::Mat, Y::Mat;
             dims     :: Into = ○,
             meanX    :: Tmean = 0,
             meanY    :: Tmean = 0,
             wXY      :: Tw = ○,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false)

   dims===○ && (dims=_set_dims(X, Y))
   _check_data(X, Y, dims, SCM, meanX, meanY, wXY)===○ && return

   return mca(_cov(X, Y, dims, meanX, meanY, wXY);
              eVar=eVar, eVarMeth=eVarMeth, simple=simple)
end

function mca(𝐗::VecMat, 𝐘::VecMat;
             dims     :: Into = ○,
             meanX    :: Into = 0,
             meanY    :: Into = 0,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false)

   dims===○ && (dims=_set_dims(𝐗, 𝐘))
   _check_data(𝐗, 𝐘, dims, SCM, meanX, meanY, ○)===○ && return

   𝐂=_cov(𝐗, 𝐘; dims=dims, meanX=meanX, meanY=meanY)

   mca(PosDefManifold.fVec(mean, Vector{Matrix}(𝐂)),
       eVar=eVar, eVarMeth=eVarMeth, simple=simple)
end

"""
```julia
(1)
function cca(Cx :: SorH, Cy :: SorH, Cxy :: Mat;
             eVarCx   :: TeVaro=○,
             eVarCy   :: TeVaro=○,
             eVar     :: TeVaro=○,
             eVarMeth :: Function=searchsortedfirst,
             simple   :: Bool=false)

(2)
function cca(X::Mat, Y::Mat;
             covEst   :: StatsBase.CovarianceEstimator=SCM,
             dims     :: Into = ○,
             meanX    :: Tmean = 0,
             meanY    :: Tmean = 0,
             wX       :: Tw = ○,
             wY       :: Tw = ○,
             wXY      :: Tw = ○,
          eVarCx   :: TeVaro = ○,
          eVarCy   :: TeVaro = ○,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false)

(3)
function cca(𝐗::VecMat, 𝐘::VecMat;
             covEst   :: StatsBase.CovarianceEstimator=SCM,
             dims     :: Into = ○,
             meanX    :: Into = 0,
             meanY    :: Into = 0,
          eVarCx   :: TeVaro = ○,
          eVarCy   :: TeVaro = ○,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false,
       metric   :: Metric = Euclidean,
       wCx      :: Vector = [],
       wCy      :: Vector = [],
       ✓w       :: Bool = true,
       initCx   :: SorHo = nothing,
       initCy   :: SorHo = nothing,
       tol      :: Real = 0.,
       verbose  :: Bool = false)
```

Return a [LinearFilter](@ref) object:

**(1) Canonical correlation analysis**
with, as input, real or complex:
- covariance matrix `Cx` of dimension ``n_x⋅n_x``,
- covariance matrix `Cy` of dimension ``n_y⋅n_y`` and
- cross-covariance matrix `Cxy` of dimension ``n_x⋅n_y``.

`Cx` and `Cy` must be flagged as `Symmetric`, if real or `Hermitian`,
if real or complex, see [data input](@ref). Instead `Cxy`
is a generic `Matrix` object since it is not symmetric/Hermitian,
left alone square.

`eVarCx`, `eVarCy`, `eVar` and `eVarMeth` are keyword optional arguments
for defining the [subspace dimension](@ref) ``p``. Particularly:
- `eVarCx` and `eVarCy` are used to determine a subspace dimension
  in the whitening of `Cx` and `Cy`, i.e., in the first step of the two-step
  procedure descrived above in the **solution** section.
- `eVar` is used to determine the final subspace dimension using the `.arev`
  vector given by Eq. [cca.5] here above.
- `eVarMeth` applies to `eVarCx`, `eVarCy` and `eVar`.

The default values are:
- `eVarCx=0.999`
- `eVarCy=0.999`
- `eVar=0.999`
- `eVarMeth=searchsortedfirst`

If `simple` is set to `true`, ``p`` is set equal to ``min(n_x, n_y)``
and only the fields `.F` and `.iF` are written in the constructed object.
This option is provided for low-level work when you don't need to define
a subspace dimension or you want to define it by your own methods.

**(2) Canonical correlation analysis**
with real or complex data matrices `X` and `Y` as input.

`covEst`, `dims`, `meanX`, `meanY`,  `wX`, `wY` and `wXY` are optional
keyword arguments to regulate the estimation of the
covariance matrices of `X` and `Y` and their cross-covariance matrix ``C_{xy}``.
Particularly (See [covariance matrix estimations](@ref)),
- `meanX` is the `mean` argument for data matrix `X`.
- `meanY` is the `mean` argument for data matrix `Y`.
- `wX` is the `w` argument for estimating a weighted covariance matrix ``C_x``.
- `wY` is the `w` argument for estimating a weighted covariance matrix ``C_y``.
- `wXY` is the `w` argument for estimating a weighted
   cross-covariance matrix ``C_{XY}``.
- `covEst` applies only to the estimation of ``C_x`` and ``C_y``.

Once ``C_x``, ``C_y`` and ``C_{xy}`` estimated, method (1) is invoked
with optional keyword arguments `eVar`, `eVarCx`, `eVarCy`,
`eVarMeth` and `simple`. See method (1) for details.

**(3) Canonical correlation analysis**
with two vectors of real or complex data matrices
`𝐗` and `𝐘` as input. `𝐗` and `𝐘` must hold the same number of matrices
and corresponding pairs of matrices therein must comprise the same
number of samples.

`covEst`, `dims`, `meanX` and `meanY` are optional keyword arguments to
regulate the estimation of the covariance matrices of all matrices in
`𝐗` and `𝐘` and the cross-covariance matrices for
all pairs of their corresponding data matrices.
See method (2) and [covariance matrix estimations](@ref).

A mean of these covariance matrices is computed.
For the cross-covariance matrices the arithmetic mean is used.
For the covariance matrices of `𝐗` and `𝐘`,
optional keywords arguments `metric`, `wCx`, `wCy`, `✓w`,
`initCx`, `initCy`, `tol` and `verbose` are used to allow
non-Euclidean mean estimations. Particularly
(see [mean covariance matrix estimations](@ref)),
- `wCx` are the weights for the covariance matrices of `𝐗`,
- `wCy` are the weights for the covariance matrices of `𝐘`,
- `initCx` is the initialization for the mean of the covariance matrices of `𝐗`,
- `initCy` is the initialization for the mean of the covariance matrices of `𝐘`.
By default, the arithmetic mean is computed.

Once the mean covariance and cross-covariance matrices are estimated,
method (1) is invoked with optional
keyword arguments `eVarCx`, `eVarCy`, `eVar`, `eVarMeth` and `simple`.
See method (1) for details.

**See also:** [Whitening](@ref), [MCA](@ref), [gCCA](@ref), [mAJD](@ref).

**Examples:**

```julia
using Diagonalizations, LinearAlgebra, PosDefManifold, Test

# Method (1) real
n, t=10, 100
X=genDataMatrix(n, t)
Y=genDataMatrix(n, t)
Cx=Symmetric((X*X')/t)
Cy=Symmetric((Y*Y')/t)
Cxy=(X*Y')/t
cC=cca(Cx, Cy, Cxy, simple=true)
@test cC.F[1]'*Cx*cC.F[1]≈I
@test cC.F[2]'*Cy*cC.F[2]≈I
D=cC.F[1]'*Cxy*cC.F[2]
@test norm(D-Diagonal(D))+1. ≈ 1.

# Method (1) complex
Xc=genDataMatrix(ComplexF64, n, t)
Yc=genDataMatrix(ComplexF64, n, t)
Cxc=Hermitian((Xc*Xc')/t)
Cyc=Hermitian((Yc*Yc')/t)
Cxyc=(Xc*Yc')/t
cCc=cca(Cxc, Cyc, Cxyc, simple=true)
@test cCc.F[1]'*Cxc*cCc.F[1]≈I
@test cCc.F[2]'*Cyc*cCc.F[2]≈I
Dc=cCc.F[1]'*Cxyc*cCc.F[2]
@test norm(Dc-Diagonal(Dc))+1. ≈ 1.

# Method (2) real
cXY=cca(X, Y, simple=true)
@test cXY.F[1]'*Cx*cXY.F[1]≈I
@test cXY.F[2]'*Cy*cXY.F[2]≈I
D=cXY.F[1]'*Cxy*cXY.F[2]
@test norm(D-Diagonal(D))+1. ≈ 1.
@test cXY==cC

# Method (2) complex
cXYc=cca(Xc, Yc, simple=true)
@test cXYc.F[1]'*Cxc*cXYc.F[1]≈I
@test cXYc.F[2]'*Cyc*cXYc.F[2]≈I
Dc=cXYc.F[1]'*Cxyc*cXYc.F[2]
@test norm(Dc-Diagonal(Dc))+1. ≈ 1.
@test cXYc==cCc

# Method (3) real
# canonical correlation analysis of the average covariance and cross-covariance
k=10
Xset=[genDataMatrix(n, t) for i=1:k]
Yset=[genDataMatrix(n, t) for i=1:k]

c=cca(Xset, Yset)

# ... selecting subspace dimension allowing an explained variance = 0.9
c=cca(Xset, Yset; eVar=0.9)

# ... subtracting the mean from the matrices in Xset and Yset
c=cca(Xset, Yset; meanX=nothing, meanY=nothing, eVar=0.9)

# cca on the average of the covariance and cross-covariance matrices
# computed along dims 1
c=cca(Xset, Yset; dims=1, eVar=0.9)

# name of the filter
c.name

using Plots
# plot regularized accumulated eigenvalues
plot(c.arev)

# plot the original covariance and cross-covariance matrices
# and their transformed counterpart
 CxyMax=maximum(abs.(Cxy));
 h1 = heatmap(Cxy, clim=(-CxyMax, CxyMax), title="Cxy", yflip=true, c=:bluesreds);
 D=cC.F[1]'*Cxy*cC.F[2];
 Dmax=maximum(abs.(D));
 h2 = heatmap(D, clim=(0, Dmax), title="F1'CxyF2", yflip=true, c=:amp);
 CxMax=maximum(abs.(Cx));
 h3 = heatmap(Cx, clim=(-CxMax, CxMax), title="Cx", yflip=true, c=:bluesreds);
 h4 = heatmap(cC.F[1]'*Cx*cC.F[1], clim=(0, 1), title="F1'CxF1", yflip=true, c=:amp);
 CyMax=maximum(abs.(Cy));
 h5 = heatmap(Cy, clim=(-CyMax, CyMax), title="Cy", yflip=true, c=:bluesreds);
 h6 = heatmap(cC.F[2]'*Cy*cC.F[2], clim=(0, 1), title="F2'CyF2", yflip=true, c=:amp);
 📈=plot(h3, h5, h1, h4, h6, h2, size=(800,400))
# savefig(📈, homedir()*"\\Documents\\Code\\julia\\Diagonalizations\\docs\\src\\assets\\FigCCA.png")

```

![Figure CCA](assets/FigCCA.png)

```julia
# Method (3) complex
# canonical correlation analysis of the average covariance and cross-covariance
k=10
Xcset=[genDataMatrix(ComplexF64, n, t) for i=1:k]
Ycset=[genDataMatrix(ComplexF64, n, t) for i=1:k]

cc=cca(Xcset, Ycset)

# ... selecting subspace dimension allowing an explained variance = 0.9
cc=cca(Xcset, Ycset; eVar=0.9)

# ... subtracting the mean from the matrices in Xset and Yset
cc=cca(Xcset, Ycset; meanX=nothing, meanY=nothing, eVar=0.9)

# cca on the average of the covariance and cross-covariance matrices
# computed along dims 1
cc=cca(Xcset, Ycset; dims=1, eVar=0.9)

# name of the filter
cc.name
```

"""
function cca(Cx :: SorH, Cy :: SorH, Cxy :: Mat;
             eVarCx   :: TeVaro=○,
             eVarCy   :: TeVaro=○,
             eVar     :: TeVaro=○,
             eVarMeth :: Function=searchsortedfirst,
             simple   :: Bool=false)

   size(Cx, 1)==size(Cxy, 1) || throw(ArgumentError(📌*", cca function: Matrices `Cx` and `Cxy` must have the same number of columns"))
   size(Cy, 2)==size(Cxy, 2) || throw(ArgumentError(📌*", cca function: Matrices `Cy` and `Cxy` must have the same number of rows"))
   if eVar isa Int
      eVarCx isa Int && eVar>eVarCx && throw(ArgumentError(📌*", cca function: `eVar` cannot be larger than `eVarCx`"))
      eVarCy isa Int && eVar>eVarCy && throw(ArgumentError(📌*", cca function: `eVar` cannot be larger than `eVarCy`"))
   end

   args=("Canonical Correlation Analysis", false)

   x=whitening(Cx; eVar=eVarCx, eVarMeth=eVarMeth)
   y=whitening(Cy; eVar=eVarCy, eVarMeth=eVarMeth)

   # alert user if eVar passed as an integer exceeds the minimum whitening dim
   whiteDim=min(size(x.F, 2), size(y.F, 2))
   if eVar isa Int && eVar>whiteDim
      @warn(📌*", cca function: the whitening step reduced the rank to $(whiteDim); `eVar` has been lowered to this value.")
      eVar=whiteDim
   end

   m=mca(x.F'*Cxy*y.F; eVar=eVar, eVarMeth=eVarMeth)

   if simple
      LF([x.F*m.F[1], y.F*m.F[2]], [m.iF[1]*x.iF, m.iF[2]*y.iF], m.D, ○, ○, ○, args...)
   else
      e, D, U, V, p, arev=_ssdxy!(eVar, diag(m.D), m.F[1], m.F[2], _minDim(Cxy), eVarMeth)
      LF([x.F*U, y.F*V], [U'*x.iF, V'*y.iF], D, e, diag(m.D), arev, args...)
   end
end

function cca(X::Mat, Y::Mat;
             covEst   :: StatsBase.CovarianceEstimator=SCM,
             dims     :: Into = ○,
             meanX    :: Tmean = 0,
             meanY    :: Tmean = 0,
             wX       :: Tw = ○,
             wY       :: Tw = ○,
             wXY      :: Tw = ○,
          eVarCx   :: TeVaro = ○,
          eVarCy   :: TeVaro = ○,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false)

   dims===○ && (dims=_set_dims(X, Y))
   _check_data(X, Y, dims, covEst, meanX, meanY, wXY)===○ && return

   cca(_cov(X, covEst, dims, meanX, wX),
       _cov(Y, covEst, dims, meanY, wY),
       _cov(X, Y, dims, 0, 0, wXY);
       eVarCx=eVarCx, eVarCy=eVarCy, eVar=eVar, eVarMeth=eVarMeth,
       simple=simple)
end

function cca(𝐗::VecMat, 𝐘::VecMat;
             covEst   :: StatsBase.CovarianceEstimator=SCM,
             dims     :: Into = ○,
             meanX    :: Into = 0,
             meanY    :: Into = 0,
          eVarCx   :: TeVaro = ○,
          eVarCy   :: TeVaro = ○,
          eVar     :: TeVaro = ○,
          eVarMeth :: Function = searchsortedfirst,
          simple   :: Bool = false,
       metric   :: Metric = Euclidean,
       wCx      :: Vector = [],
       wCy      :: Vector = [],
       ✓w       :: Bool = true,
       initCx   :: SorHo = nothing,
       initCy   :: SorHo = nothing,
       tol      :: Real = 0.,
       verbose  :: Bool = false)

   Metric==VonNeumann && throw(ArgumentError(📌*", cca function: A solution for the mean is not available for the Von Neumann metric. Use another metric as `metric` argument"))
   dims===○ && (dims=_set_dims(𝐗, 𝐘))
   _check_data(𝐗, 𝐘, dims, covEst, meanX, meanY, ○)===○ && return

   𝐂x= _cov(𝐗; covEst=covEst, dims = dims, meanX = meanX)
   𝐂y= _cov(𝐘; covEst=covEst, dims = dims, meanX = meanX)
   𝐂xy=_cov(𝐗, 𝐘; dims=dims, meanX=0, meanY=0)

   cca(mean(metric, 𝐂x;
            w = wCx, ✓w = ✓w,
            init = initCx===○ ? ○ : Hermitian(initCx), #just init here when you upfate PodDefManifold
            tol = tol, verbose = verbose),
       mean(metric, 𝐂y;
            w = wCy, ✓w = ✓w,
            init = initCy===○ ? ○ : Hermitian(initCy), #just init here when you upfate PodDefManifold
            tol = tol, verbose = verbose),
       PosDefManifold.fVec(mean, Vector{Matrix}(𝐂xy)), # multi-threaded Euclidean mean
       eVarCx=eVarCx, eVarCy=eVarCy, eVar=eVar, eVarMeth=eVarMeth,
       simple=simple)
end


##################################################################
# function to simulate one iteration of the experiment. For a    #
# given number of beetles, function determines if and when each  #
# beetle dies.                                                   #
##################################################################

# total_beetle: positive integer, refers to total number of beetles in experiment
# α_i, β_i, γ_i, p_i: positive reals, parameter values
# z_0: positive real, initial dose of insecticide

# returns length 14 vector where first 13 values correspond
# to the number of beetles that died on that day and final
# value corresponds to the number of beetles that survived.
function beetle_sim(total_beetle, α_i, β_i, γ_i, p_i, z_0)

    results = zeros(14)

    S_prev = 1

    for i = 1:total_beetle
        # number of releases occuring up to or on 13th day
        V_count = rand(Poisson(α_i * 13))

        # if no releases, count beetle as survived
        if V_count == 0
            results[14] = results[14] + 1
            continue
        end

        # draw inter-arrival times for releases
        T_inter = rand(Exponential(1/α_i), V_count)

        # set amount of insecticide
        z_i = z_0

        # death status of beetle
        death = false

        # time in loop begins at 0
        current_t = 0

        # calculate probability of death for each inter-release
        # period. Then remove amount of toxin released at end of
        # period and move on to next period
        for j = 1:(V_count + 1)

            # last iteration, for time between last release and
            # end of 13th day
            if j == V_count + 1

                # prob of death in inter-release interval
                death_p = z_i * γ_i + p_i * z_i *
                        (2*current_t*(13 - T_inter[j-1]) +
                        (13 - T_inter[j-1])^2) / 2

                if death_p > rand(Uniform(0,1))
                    # if the beetle dies, set death true and
                    # break out of for loop
                    death = true
                    # add to death counter of final interval
                    results[13] = results[13] + 1
                end

                break
            end

            # update time for next loop
            current_t = sum(T_inter[1:j])

            # make sure we haven't gone above 13 days
            if current_t > 13
                break
            end

            S_cur = exp(-(γ_i * current_t * z_i + z_i * p_i * current_t^2 / 2))

            death_p = S_prev - S_cur

            # set S_prev for next iteration
            S_prev = S_cur

            # use Uniform to generate random deaths
            if death_p > rand(Uniform(0,1))
                # if the beetle dies, set death true and
                # break out of for loop
                death = true
                # add to death counter
                index = convert(Int,round(current_t, RoundUp))

                results[index] = results[index] + 1
                break
            end

            # remove insecticide released at end of this period
            z_i = z_i - rand(Exponential(1 / β_i))

            # stop loop if negative insecticide
            if z_i <= 0
                break
            end

        end

        if death == false
            results[14] = results[14] + 1
        end

    end

    return results

end

function beetle_sim_ext(total_beetle, α_i, β_i, γ_i, p_i, z_0)

    results = zeros(14)

    for i = 1:total_beetle
        # number of releases occuring up to or on 13th day
        V_count = rand(Poisson(α_i * 13))
        if V_count == 0
            results[14] = results[14] + 1
            continue
        end

        # draw inter-arrival times for first release
        T_inter = rand(Exponential(1/α_i))

        # prob of death in inter-release interval
        death_p = p_i * z_0 *
                        (T_inter^2) / 2

        if death_p > rand(Uniform(0,1))
            # if the beetle dies, set death true and
            # break out of for loop
            death = true
            # add to death counter
            index = convert(Int,round(T_inter, RoundUp))
            if index > 14
                results[14] = results[14] + 1
                continue
            end

            results[index] = results[index] + 1
        else
            results[14] = results[14] + 1
        end

    end

    return results

end

# ABC simulation function for the beetle experiment. Takes as inputs
# original data from experiment, number of iterations, and initial
# dose of insecticide. For each iteration it records the parameter
# values drawn from the priors and the distance of the simulated
# data from the observed data. Returns a dataframe with those values
# for each iteration.

function abc_sim(data, z_0, n, beetle_n, α_prior, β_prior, γ_prior, p_prior)
    results = DataFrame( α = Float64[], β = Float64[], γ = Float64[],
                p = Float64[], dist = Float64[])

    # these lines put data in same format given in Diggle 1984
    sum_1 = sum(data[6:7])
    sum_2 = sum(data[8:13])
    data = vcat(data[1:5], sum_1, sum_2, data[14])
    for i = 1:n

        # draw instances of parameters from prior distributions
        α_i = rand(α_prior)
        β_i = rand(β_prior) + 5
        γ_i = rand(γ_prior)
        p_i = rand(p_prior)

        # run the experiment simulation
        sim_i = beetle_sim(beetle_n, α_i, β_i, γ_i, p_i,z_0)

        # convert data into form from Diggle 1984
        sum_i1 = sum(sim_i[6:7])
        sum_i2 = sum(sim_i[8:13])
        sim_i  = vcat(sim_i[1:5], sum_i1, sum_i2, sim_i[14])

        # calculate distance b/t simulated and observed data
        dist = norm(sim_i - data, 1)

        # record results
        push!(results, [α_i, β_i, γ_i, p_i, dist])

    end
    return results
end


### A Pluto.jl notebook ###
# v0.14.6

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 839c36b1-1318-4892-84ab-389b40b2ea48
using PlutoUI, Plots

# ╔═╡ efaf0218-42bd-46dd-9f93-de666df37100
md"## Julia Lang

* A relatively new dynamic programming language (≈ 3 years since 1.0)
* General purpose and high level language
* Core paradigm is *Multiple Dispatch*
* Performance comparable to C/C++/Fortran
* For example: *for loops* don't have a performance penalty

### Reasons for Speed
* JIT - Just in Time compilation
* type stable functions
* because of the type system Julia can compile efficient code

### Helpfuls resources
* However, severalc differences to other languages
* Consult [performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/)
* [Noteworthy differences to other language](https://docs.julialang.org/en/v1/manual/noteworthy-differences/)
* Ecosystem for image processing for microscopy data is small

### Some important packages
* [Bioformatsloader.jl](https://github.com/ahnlabb/BioformatsLoader.jl)
* [View5D.jl](https://github.com/RainerHeintzmann/View5D.jl)
* [DeconvOptim.jl](https://github.com/roflmaostc/DeconvOptim.jl)
* [Images.jl](https://github.com/JuliaImages/Images.jl/)
* [Napari.jl](https://github.com/mkitti/Napari.jl)
* [FourierTools.jl](https://github.com/bionanoimaging/FourierTools.jl)
* [TestImages.jl](https://github.com/JuliaImages/TestImages.jl)
"

# ╔═╡ ad907ec3-7f30-4508-9599-a734c18593af
md"#### sqrt example which fails in Julia because of the types"

# ╔═╡ f8d94633-d125-4da1-a446-bbf4e8fd43a9
sqrt(-1)

# ╔═╡ 8c83a90b-642a-4e9c-abe6-0e1ce0c819b5
sqrt(1) |> typeof

# ╔═╡ d78a922c-efa2-4935-881a-b5dccaa09caa
md"## Multiple Dispatch

* In general no type signatures are needed
* However we can specialize if we want
"

# ╔═╡ 8f247848-6258-4538-9bea-df4897d5716a
md"""
x = $(@bind x Slider(0:0.1:10; default=8, show_value=true))
z = $(@bind z Slider(1:0.01:10; default=8, show_value=true))
"""

# ╔═╡ 6b5f335d-15af-43f2-afc4-1b51acc693cc
# However, we can specialize if we want
function f(x::Int, y::Int)
	return 42
end

# ╔═╡ 8021d1d9-773d-46d4-9804-6fbe7978f4df
ϵ = 10

# ╔═╡ fc08b77e-565a-4edc-a2dd-0b7dc7acd155
md"## Arrays"

# ╔═╡ 26f41519-9f11-46e6-aa27-abd1142b00fb
arr = ones(Float32, (10, 10));

# ╔═╡ 708f34f7-1308-4946-9ace-998476606f9c
arr * arr # matrix multiplication

# ╔═╡ c24d72a7-e4f5-427e-a55a-6ca6b7ec9b9e
arr .* arr # point wise

# ╔═╡ 7183ba48-5785-41af-afc6-f80bc07916cf
md"## Small for loop array example

Consider an array where we want to sum up all positive numbers
"

# ╔═╡ aa72b577-18f6-4871-b232-d17c2bb5c7fd
Γ = randn((100_000_000, ));

# ╔═╡ 1e79336c-9f63-48ca-aa47-5c96bc7dfd9c
result1 = sum(Γ[ Γ .> 0.0])

# ╔═╡ dec2cd2e-2263-4139-b461-1c4c5c49976f
md"## Type Example"

# ╔═╡ 35e81c4d-8673-4b2a-aa0f-457aaefcddb7
abstract type Bit end

# ╔═╡ 6c57ae37-12d0-43c7-9043-3e5dbb0b85e3
struct One <: Bit end

# ╔═╡ e779870d-966a-4ed7-9ab4-54e534518642
struct Zero <: Bit end

# ╔═╡ 214b7930-763b-4034-bf50-05a0c369c02d
begin
	import Base.+
	+(::Bit, ::Bit) = Zero()
	+(::One, ::Zero) = One()
	+(::Zero, ::One) = One()
end

# ╔═╡ 3b8c572c-3d2e-4f68-96f3-b4552ac2d20b
# no type signatures needed
function f(x, y)
	return x + y
end

# ╔═╡ 6ea00fdb-7659-4bd5-83a1-09af4c6f31d7
f(x, z * 1im)

# ╔═╡ 853f3cd3-20c6-4479-8846-0c6b7a34e410
begin
	r = 0:0.005:10
	plot(r, sin.(x .* r .+ z))
end

# ╔═╡ 034676c5-8a30-488f-a685-d0d4c20d078b
f(ϵ, x + z)

# ╔═╡ b71710ee-09fe-4568-9aa7-fc5eaa4b0cb3
function sum_positive(Γ)
	res = zero(eltype(Γ))
	for entry in Γ
		if entry > 0
			res += entry
		end
	end
	return res
end

# ╔═╡ 76f78b1c-b796-4ec5-b857-12635be06a0d
result2 = sum_positive(Γ)

# ╔═╡ f9f7a2fe-c0e4-412d-bc88-22b2a8cf9f4c
result1 ≈ result2

# ╔═╡ b0c6f430-3ade-40ef-9468-9701391be788
One() + Zero()

# ╔═╡ e04b7ae2-3282-4093-a35e-1357d48cdff9
Zero() + Zero()

# ╔═╡ Cell order:
# ╠═839c36b1-1318-4892-84ab-389b40b2ea48
# ╟─efaf0218-42bd-46dd-9f93-de666df37100
# ╠═ad907ec3-7f30-4508-9599-a734c18593af
# ╠═f8d94633-d125-4da1-a446-bbf4e8fd43a9
# ╠═8c83a90b-642a-4e9c-abe6-0e1ce0c819b5
# ╠═d78a922c-efa2-4935-881a-b5dccaa09caa
# ╠═3b8c572c-3d2e-4f68-96f3-b4552ac2d20b
# ╟─8f247848-6258-4538-9bea-df4897d5716a
# ╠═6ea00fdb-7659-4bd5-83a1-09af4c6f31d7
# ╠═6b5f335d-15af-43f2-afc4-1b51acc693cc
# ╠═8021d1d9-773d-46d4-9804-6fbe7978f4df
# ╠═034676c5-8a30-488f-a685-d0d4c20d078b
# ╠═853f3cd3-20c6-4479-8846-0c6b7a34e410
# ╠═fc08b77e-565a-4edc-a2dd-0b7dc7acd155
# ╠═26f41519-9f11-46e6-aa27-abd1142b00fb
# ╠═708f34f7-1308-4946-9ace-998476606f9c
# ╠═c24d72a7-e4f5-427e-a55a-6ca6b7ec9b9e
# ╠═7183ba48-5785-41af-afc6-f80bc07916cf
# ╠═aa72b577-18f6-4871-b232-d17c2bb5c7fd
# ╠═1e79336c-9f63-48ca-aa47-5c96bc7dfd9c
# ╠═76f78b1c-b796-4ec5-b857-12635be06a0d
# ╠═b71710ee-09fe-4568-9aa7-fc5eaa4b0cb3
# ╠═f9f7a2fe-c0e4-412d-bc88-22b2a8cf9f4c
# ╠═dec2cd2e-2263-4139-b461-1c4c5c49976f
# ╠═35e81c4d-8673-4b2a-aa0f-457aaefcddb7
# ╠═6c57ae37-12d0-43c7-9043-3e5dbb0b85e3
# ╠═e779870d-966a-4ed7-9ab4-54e534518642
# ╠═214b7930-763b-4034-bf50-05a0c369c02d
# ╠═b0c6f430-3ade-40ef-9468-9701391be788
# ╠═e04b7ae2-3282-4093-a35e-1357d48cdff9


function ADC(VT,ALT)
   R0 = 2.377E-3
   TFAC = 1.0 - 0.703E-5*ALT
   T = 519.0*TFAC
   if ALT > 35000.0
      T= 390.0
   end
   RHO = R0*(TFAC^4.14)
   dum = 1.4*1716.3*T
   MACH = VT/sqrt(dum)
   QBAR = 0.5*RHO*VT*VT
   PS = 1715.0*RHO*T
return MACH, QBAR
end


using AcousticFWI,Seismic,PyPlot

function main()

    ns = 10
    ng = 250
    nz = 150
    nx = 250
    nt = 1024
    nf = 1024
    dz = 0.01
    dx = 0.01
    dt = 0.002
    f0 = 10.
    fmin = 1.
    fmax = 30.
    ext = 50
    atten_max = 2.5
    GNiter = 8
    CGiter = 20

    vp = 2.1*ones(nz,nx)
    vp[61:100,:] = 3.0
    vp[101:end,:] = 2.5
    for (i,iz) in enumerate(61:100)
        vp[iz,1:105+i] = 2.7
    end

    ot = zeros(ns)
    isz = 3*ones(Int,ns)
    isx = round(Int,collect(linspace(2,nx-1,ns)))
    igz = 3*ones(Int,ng)
    igx = round(Int,collect(linspace(1,nx,ng)))

    wav = Ricker(f0=f0,dt=dt)
    d = zeros(nt,ng,ns)
    for ishot = 1:ns
        u = Helmholtz(isz[ishot],isx[ishot],ot[ishot],vp,wav,fmin,fmax,nf,nt,dz,dx,dt,ext,atten_max)
        d[:,:,ishot] = u[:,3,igx]
    end

    vp0 = 2.3*ones(vp)
    vp_est,cost,f_inv = FWI(vp0,d,wav,isz,isx,igz,igx,ot,fmin,fmax,nf,dz,dx,dt,ext,atten_max,GNiter,CGiter)

    SeisPlot(d[:,:,round(Int,ns/2)],pclip=98,dx=1,dy=dt,xlabel="Trace",ylabel="t (s)",cmap="gray")
    figure(2) ; vmin = 1000*min(minimum(vp),minimum(vp0),minimum(vp_est))
              ; vmax = 1000*max(maximum(vp),maximum(vp0),maximum(vp_est))
              ; subplot(221) ; imshow(1000*vp,vmin=vmin,vmax=vmax,cmap="YlOrBr",extent=[0.,(nx-1)*1000*dx,(nz-1)*1000*dz,0.])
                             ; xlabel("x (m)")
                             ; ylabel("z (m)")
                             ; colorbar(aspect=15,ticks=[2100,2300,2500,2700,2900,3100])
                             ; ax = gca()
                             ; ax[:text](-600.,-100., "a)", fontsize=15, fontweight="bold")
                             ; ax[:set_xticks]([0,500,1000,1500,2000])
                             ; ax[:set_yticks]([0,300,600,900,1200,1500])
              ; subplot(222) ; imshow(1000*vp0,vmin=vmin,vmax=vmax,cmap="YlOrBr",extent=[0.,(nx-1)*1000*dx,(nz-1)*1000*dz,0.])
                             ; xlabel("x (m)")
                             ; ylabel("z (m)")
                             ; colorbar(aspect=15,ticks=[2100,2300,2500,2700,2900,3100])
                             ; ax = gca()
                             ; ax[:text](-600.,-100., "b)", fontsize=15, fontweight="bold")
                             ; ax[:set_xticks]([0,500,1000,1500,2000])
                             ; ax[:set_yticks]([0,300,600,900,1200,1500])
              ; subplot(223) ; imshow(1000*vp_est,vmin=vmin,vmax=vmax,cmap="YlOrBr",extent=[0.,(nx-1)*1000*dx,(nz-1)*1000*dz,0.])
                             ; xlabel("x (m)")
                             ; ylabel("z (m)")
                             ; colorbar(aspect=15,ticks=[2100,2300,2500,2700,2900,3100])
                             ; ax = gca()
                             ; ax[:text](-600.,-100., "c)", fontsize=15, fontweight="bold")
                             ; ax[:set_xticks]([0,500,1000,1500,2000])
                             ; ax[:set_yticks]([0,300,600,900,1200,1500])
              ; subplot(224) ; plot(1:length(cost[1]),cost[1],color="black",linewidth=2)
                             ; xlabel("Iteration")
                             ; ylabel("Normalized Misfit")
                             ; ax = gca()
                             ; ax[:text](-0.4,1.05, "d)", fontsize=15, fontweight="bold")
                             ; ax[:set_xticks]([1,2,3,4,5,6,7,8])
                             ; ax[:set_yticks]([0.2,0.4,0.6,0.8,1.0])

end

main()


### A Pluto.jl notebook ###
# v0.12.16

using Markdown
using InteractiveUtils

# ╔═╡ f84a00b0-3d55-11eb-1560-17af63da3f95
using Plots

# ╔═╡ e49da79c-3d55-11eb-2090-b1649653782c
begin
@userplot CirclePlot
@recipe function f(cp::CirclePlot)
    x, y, i = cp.args
    n = length(x)
    inds = circshift(1:n, 1 - i)
    linewidth --> range(0, 10, length = n)
    seriesalpha --> range(0, 1, length = n)
    aspect_ratio --> 1
    label --> false
    x[inds], y[inds]
end

n = 150
t = range(0, 2π, length = n)
x = sin.(t)
y = cos.(t)

anim = @animate for i ∈ 1:n
    circleplot(x, y, i)
end
gif(anim, "anim_fps15.gif", fps = 15)
end

# ╔═╡ Cell order:
# ╠═f84a00b0-3d55-11eb-1560-17af63da3f95
# ╠═e49da79c-3d55-11eb-2090-b1649653782c


# ---
# title: 942. DI String Match
# id: problem942
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math
# link: <https://leetcode.com/problems/di-string-match/description/>
# hidden: true
# ---
#
# Given a string `S` that **only** contains "I" (increase) or "D" (decrease),
# let `N = S.length`.
#
# Return **any** permutation `A` of `[0, 1, ..., N]` such that for all `i = 0,
# ..., N-1`:
#
#   * If `S[i] == "I"`, then `A[i] < A[i+1]`
#   * If `S[i] == "D"`, then `A[i] > A[i+1]`
#
#
#
# **Example 1:**
#
#
#
#     Input: "IDID"
#     Output: [0,4,1,3,2]
#
#
# **Example 2:**
#
#
#
#     Input: "III"
#     Output: [0,1,2,3]
#
#
# **Example 3:**
#
#
#
#     Input: "DDI"
#     Output: [3,2,0,1]
#
#
#
# **Note:**
#
#   1. `1 <= S.length <= 10000`
#   2. `S` only contains characters `"I"` or `"D"`.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


#!/usr/bin/env -S julia -iq
#
# Jerzy Wroczyński (nr. indeksu 250075)
# Obliczenia Naukowe, Laboratorium
# Lista 4., Zadanie 4. — podstawowy test
#

include("interpolation.jl")
using .Interpolation

Interpolation.rysujNnfx(x->sin(x) + cos(x), 0.0, 5.0, 5)

println("end the interactive session to close all plots")


using Plots, LaTeXStrings
gr(colorbar=false,size=(600,150),legend=false,grid=false)

using FourierGPE

## Initialize simulation
L = (40.0,)
N = (2048,)
sim = Sim(L,N)
@unpack_Sim sim

## set simulation parameters
μ = 25.0
γ = 0.0
g = -1.0
n = 5.0
tf = pi/2
Nt = 200
ti = 0.0
t = LinRange(ti,tf,Nt)

## soliton wavefunction
x = X[1]
ψs(x) = n*sech(x)
ψi = ψs.(x)
plot(x,abs2.(ψi),lw=10,alpha=0.5)
ϕi = kspace(ψi,sim)

## Set all fields
@pack_Sim! sim

## Evolve in k space
sol = runsim(sim)

## plot
plot(x,abs2.(ψi),lw=3,c=:red)
y = abs2.(xspace(sol[Nt],sim))
plot!(x,y,lw=3,c=c3,fill=(0, 0.4,c3))
xlims!(-5,5)

## animate
anim = @animate for i=1:Nt
    ψ = xspace(sol[i],sim)
    y = abs2.(ψ)
    # plot(x,abs2.(ψi),lw=3,c=:red,alpha=0.4)
    plot(x,y,c=c3,fill=(0, 0.4,c3),size=(400,300),xticks=false,yticks=false,axis=false)
    xlims!(-2.2,2.2)
    ylims!(0,220)
end

gif(anim, "./examples/1d_temporal_soliton.gif", fps = 25)


# Author: Lukasz Bratos

# Typy zmiennopozycyjne dla ktorych bedziemy wykonywac testy
types = [Float16, Float32, Float64]

# Funkcja wyznacza iteracyjnie epsilon maszynowy dla danego typu zmiennopozycyjnego.
function macheps(type)
    x = one(type)
    while one(type) + x / 2 != one(type)
        x = x / 2
    end
    return x
end

# Funkcja wyznacza iteracyjnie wartosc eta dla danego typy zmiennopozycyjnego.
function eta(type)
    x = one(type)
    while x / 2 > 0
        x = x / 2
    end
    return x
end

# Funkcja wyznacza iteracyjnie maksymalna wartosc dla danego typu zmiennopozycyjnego.
function maxFloat(type)
    x = one(type)
    while !isinf(x * 2)
        x *= 2
    end
    y = x / 2
    while !isinf(x + y) && y >= 1.0
        x += y
        y /= 2
    end
    return x
end

for type in types
    println("Calculated macheps for $type: ", macheps(type), " vs eps($type): ", eps(type))
    println("Calculated eta for $type: ", eta(type), " vs nextfloat($type): ", nextfloat(type(0.0)))
    println("Calculated MAX for $type: ", maxFloat(type), " vs floatmax($type): ", floatmax(type))
end


#=
  Hidden Markov Models

  This is from the BLOG distribution hmm.dblog
  """
  A hidden Markov model with four states and four output symbols.
  The parameters of this model are completely made-up.
  DBLOG model
  """

A:1 C:2 G:3 T:4

Distributions of variable S1 (num:0)
2.00000 =>    7524  (0.752400)
4.00000 =>     853  (0.085300)
3.00000 =>     813  (0.081300)
1.00000 =>     810  (0.081000)

Distributions of variable S2 (num:0)
1.00000 =>    9778  (0.977800)
2.00000 =>     176  (0.017600)
3.00000 =>      40  (0.004000)
4.00000 =>       6  (0.000600)

Distributions of variable S3 (num:0)
1.00000 =>    9226  (0.922600)
4.00000 =>     640  (0.064000)
2.00000 =>      84  (0.008400)
3.00000 =>      50  (0.005000)

Distributions of variable S4 (num:0)
1.00000 =>    8574  (0.857400)
4.00000 =>    1006  (0.100600)
2.00000 =>     300  (0.030000)
3.00000 =>     120  (0.012000)

Distributions of variable S5 (num:0)
3.00000 =>    7383  (0.738300)
4.00000 =>    1365  (0.136500)
1.00000 =>     724  (0.072400)
2.00000 =>     528  (0.052800)

Distributions of variable S6 (num:0)
4.00000 =>    3381  (0.338100)
2.00000 =>    2506  (0.250600)
1.00000 =>    2320  (0.232000)
3.00000 =>    1793  (0.179300)

Distributions of variable S7 (num:0)
1.00000 =>    2828  (0.282800)
4.00000 =>    2816  (0.281600)
2.00000 =>    2467  (0.246700)
3.00000 =>    1889  (0.188900)

Distributions of variable S8 (num:0)
2.00000 =>    3246  (0.324600)
3.00000 =>    2758  (0.275800)
4.00000 =>    2169  (0.216900)
1.00000 =>    1827  (0.182700)

  Cf ~/webppl/hmm1.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function hmm1()
    n = 8

    A = 1
    C = 2
    G = 3
    T = 4
    states = [A,C,G,T]

    AA ~ Uniform(0,1)
    AC ~ Uniform(0,1)
    AG ~ Uniform(0,1)
    AT ~ Uniform(0,1)

    CA ~ Uniform(0,1)
    CC ~ Uniform(0,1)
    CG ~ Uniform(0,1)
    CT ~ Uniform(0,1)

    GA ~ Uniform(0,1)
    GC ~ Uniform(0,1)
    GG ~ Uniform(0,1)
    GT ~ Uniform(0,1)

    TA ~ Uniform(0,1)
    TC ~ Uniform(0,1)
    TG ~ Uniform(0,1)
    TT ~ Uniform(0,1)

    S = tzeros(n)
    for t in 1:n
        if t == 1
            S[t] ~ Categorical([0.3,0.2,0.1,0.4])
        else
            prev_t = S[t-1]
            S[t] ~
                (prev_t == A) ? Categorical(simplex([AA,AC,AG,AT])) :
                (prev_t == C) ? Categorical(simplex([CA,CC,CG,CT])) :
                (prev_t == G) ? Categorical(simplex([GA,GC,GG,GT])) :
                (prev_t == T) ? Categorical(simplex([TA,TC,TG,TT])) : Dirac(5)
        end
    end

    ResultA = 1
    ResultC = 2
    ResultG = 3
    ResultT = 4
    O = tzeros(n)
    for t in 1:n
        St = S[t]
        O[t] ~ (St == A) ? Categorical([0.85,0.05,0.05, 0.05]) : # [ResultA,ResultC,ResultG,ResultT]
               (St == C) ? Categorical([0.05,0.85,0.05, 0.05]) :
               (St == G) ? Categorical([0.05,0.05,0.85, 0.05]) :
               (St == T) ? Categorical([0.05,0.05,0.05, 0.85]) : Dirac(5)
    end

    # Evidence for the Hidden Markov Model.
    true ~ Dirac(O[1] == ResultC)
    true ~ Dirac(O[2] == ResultA)
    true ~ Dirac(O[3] == ResultA)
    true ~ Dirac(O[4] == ResultA)
    true ~ Dirac(O[5] == ResultG)

    # """
    # Queries for the Hiddem Markov Model, given the evidence.
    # Note that we can query S(5) even though our observations only
    # went up to time 4.
    # """
    # Note: I changed from BLOG/WebPPL's 0-based to 1-based.
    S1 ~ Dirac(S[1])
    S2 ~ Dirac(S[2])
    S3 ~ Dirac(S[3])
    S4 ~ Dirac(S[4])
    S5 ~ Dirac(S[5])
    S6 ~ Dirac(S[6])
    S7 ~ Dirac(S[7])
    S8 ~ Dirac(S[8])

end

model = hmm1()

num_chains = 4

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
chns = sample(model, PG(15), 10_000)
# chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

display(chns)
# display(plot(chns))

println("A:1 C:2 G:3 T:4")
show_var_dist_pct(chns,:S1)
show_var_dist_pct(chns,:S2)
show_var_dist_pct(chns,:S3)
show_var_dist_pct(chns,:S4)
show_var_dist_pct(chns,:S5)
show_var_dist_pct(chns,:S6)
show_var_dist_pct(chns,:S7)
show_var_dist_pct(chns,:S8)


#=
    The File contains tests for find_basis Algorithms
=#

#------------------------------------------------------------------------------

include("../src/wiedemannchik.jl")

#------------------------------------------------------------------------------

if !isdefined(Main, :testset)
    using Test
    using TestSetExtensions
end

#------------------------------------------------------------------------------

function test_minimal_polynomial(minpoly)
    #=
        Only annihilating property is tested
        Minimality property is ommited
    =#

    S, x = QQ["x"]

    # hardcoded tests
    A = from_COO(1, 1, [(1, 1, 1)], QQ)
    f = minpoly(A, S)
    @test !iszero(f) && iszero(evaluate(f, A))

    A = from_dense([1 2; 3 4;], QQ)
    f = minpoly(A, S)
    @test !iszero(f) && iszero(evaluate(f, A))

    A = from_dense([1 0 0; 0 1 0; 0 0 0], QQ)
    f = minpoly(A, S)
    @test !iszero(f) && iszero(evaluate(f, A))

    A = from_dense([3 -3 2; -1 5 -2; -1 3 0], QQ)
    f = minpoly(A, S)
    @test !iszero(f) && iszero(evaluate(f, A))

    A = from_dense([0 0; 0 0;], QQ)
    @test_throws DomainError minpoly(A, S)

    # some random tests
    # - dense case
    sizes = [3, 5, 10]
    densities = [0.2, 0.5, 1.0]
    ZZ = GF(2^31 - 1)
    S, _ = ZZ["x"]
    for n in sizes
        for λ in densities
            A = random_sparsik((n, n), ZZ, density=λ)
            try
                f = minpoly(A, S)
                @test !iszero(f) && iszero(evaluate(f, A))
            catch DomainError
                !iszero(A) && rethrow()
            end
        end
    end

    # - highly singular case
    n = 5
    S, _ = QQ["x"]
    for _ in 1 : n^2
        i, j, k, r = rand(1:n^2, 4)
        # sum of four random unit matrices
        A = sum(unit_sparsik((n, n), pos, QQ) for pos in (i, j, k, r))
        f = minpoly(A, S)
        @test !iszero(f) && iszero(evaluate(f, A))
    end

end

#------------------------------------------------------------------------------

@testset "minpolynomial, basic deterministic" begin

    test_minimal_polynomial(__deterministic_simple_minpoly)

end

@info "OK"


"""
optimalorder(hc::Hclust, dm::Array{Float64,2})

Given a hierarchical cluster, use fast algorithm to determine optimal leaf order
minimizing the distance between adjacent leaves. This is done using a heuristic
where, when combining multi-leaf sub branches, only the outermost leaves are
compared (a maximum of 4 comparisons per intersection).

Sub branches are flipped if necessary to minimize the distance between adjacent
nodes, and then the combined branches are treated as a block for future
comparisons.

Based on:
[Bar-Joseph et. al. "Fast optimal leaf ordering for hierarchical clustering." _Bioinformatics_. (2001)](https://doi.org/10.1093/bioinformatics/17.suppl_1.S22)
"""
function optimalorder(hc::Hclust, dm::Array{Float64,2})
    ord = deepcopy(hc)
    optimalorder!(ord, dm)
    return ord
end

function optimalorder!(hc::Hclust, dm::Array{Float64,2})
    ord = hc.order
    orderleaves!(ord, hc, dm)
end

function orderleaves!(order::Vector{Int}, hcl::Hclust, dm::Array{Float64,2})
    extents = Tuple{Int,Int}[]
    for (vl, vr) in zip(hcl.merge[:,1], hcl.merge[:,2])
        (u, m, uidx, midx) = leaflocs(vl, order, extents)
        (k, w, kidx, widx) = leaflocs(vr, order, extents)
        if vl < 0 && vr < 0
            # Nothing needs to be done
        elseif vl < 0
            flp = flip1(m, k, w, dm)
            flp == 2 && reverse!(order, kidx, widx)
        elseif vr < 0
            flp = flip1(k, m, u, dm)
            flp == 2 && reverse!(order, uidx, midx)
        elseif vl > 0 && vr > 0
            flp = flip2(u, m, k, w, dm)
            (flp == 2 || flp == 4) && reverse!(order, uidx, midx)
            (flp == 3 || flp == 4) && reverse!(order, kidx, widx)
        else
            error("invalid 'merge' order in Hclust: ($vl, $vr) ")
        end
        push!(extents, (uidx, widx))
    end
end

function leaflocs(v::Int, order::Vector{Int}, extents::Vector{Tuple{Int,Int}})
    ##################
    if v < 0
        leftextent = findfirst(abs(v) .== order)
        rightextent = leftextent
    elseif v > 0
        leftextent = extents[v][1]
        rightextent = extents[v][2]
    else
        error("leaf position cannot be zero")
    end
    left = order[leftextent]
    right = order[rightextent]
    return left, right, leftextent, rightextent
end

"""
For 1 multi-leaf branch and a leaf, determine if flipping branch is required
1 = do not flip
2 = flip right
"""
function flip1(m::Int, k::Int, w::Int, dm::Array{Float64,2})
    dm[m,k] <= dm[m,w] ? 1 : 2
end

"""
For 2 multi-leaf branches, determine if one or two flips is required
1 = do not flip
2 = flip left
3 = flip right
4 = flip both
"""
function flip2(u::Int, m::Int, k::Int, w::Int, dm::Array{Float64,2})
    argmin([dm[m,k], dm[u,k], dm[m,w], dm[u,w]])
end


"""
    north!(proposedxy, initialxy, dx, dy, start, endup) -> Array{Float64,1}

Corrects displacement vectors with head and tail in two different layers and
crossing the horizontal boundary between water-enzyme, water-ppd, or ppd-enzyme.
"""
function north!(proposedxy::Array{Float64,1},
                initialxy::Array{Float64,1},
                dx::Float64, dy::Float64,
                start::String,
                endup::String)
    initx, inity = initialxy
    propx, propy = proposedxy
    scale = STEP_SIZE_DICT[start]
    scale2 = STEP_SIZE_DICT[endup]
    slope = (propx - initx) / (propy - inity)           # Reciprocal of traditional slope (delta x over delta y)

    if start == "ppd" || endup == "ppd"
        yintersection = PPD_MAX_Y
    else
        yintersection = ENZYME_MAX_Y
    end

    xintersection = slope * (yintersection - inity) + initx         # x-component of the head of first segment, ending at the border crossing
                # Reciprocal slope lets us multiply the change in y to obtain the change in x
    ydistleft = scale * dy + (inity - yintersection)                # y-component legnth of the original second segment
    xdistleft = scale * dx + (initx - xintersection)                # x-component length of the original second segment
    # First divide the x- and y- component lengths of the original segment steplength, scale -> recover the unit vector length of second segment
    # Then multiply the x- and y-components of the unit vector length of  second semester by scale2 -> obtain correct length of the second segment in new layer
    # Lastly append the correct lengths of the new second segment to the head of the first segment vector
    proposedxy[1] = xintersection + xdistleft * scale2/scale
    proposedxy[2] = yintersection + ydistleft * scale2/scale
    return proposedxy
end

"""
    eastwest!(proposedxy, initialxy, dx, dy, start, endup, EastORWest) -> Array{Float64,1}

Corrects displacement vectors with head and tail in two different layers and
crossing the vertical boundary between water-enzyme, ppd-enzyme.
"""
function eastwest!(proposedxy::Array{Float64,1},
                    initialxy::Array{Float64,1},
                    dx::Float64, dy::Float64,
                    start::String, endup::String,
                    EastOrWest::String)
    initx, inity = initialxy
    propx, propy = proposedxy
    scale = STEP_SIZE_DICT[start]
    scale2 = STEP_SIZE_DICT[endup]
    slope = (propy - inity) / (propx - initx)           # Traditional slope (delta y over delta x)

    if EastOrWest == "E"
        xintersection = ENZYME_RIGHT_X
    else
        xintersection = ENZYME_LEFT_X
    end

    yintersection = slope * (xintersection - initx) + inity         # y-component of the head of first segment, ending at the border crossing
                # Multiply the slope and the change in y to obtain the change in x
    xdistleft = scale * dx + (initx - xintersection)                # x-component legnth of the original second segment
    ydistleft = scale * dy + (inity - yintersection)                # y-component legnth of the original second segment
    # Divide the x- and y- component lengths of the original segment steplength, scale -> recover the unit vector length of second segment
    # Multiply the x- and y-components of the unit vector length of  second semester by scale2 -> obtain correct length of the second segment in new layer
    # Append the correct lengths of the new second segment to the head of the first segment vector
    proposedxy[1] = xintersection + xdistleft * scale2/scale
    proposedxy[2] = yintersection + ydistleft * scale2/scale
    return proposedxy
end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#   Test conversion from osculating to mean elements using J2 osc. orbit
#   propagator.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# File: ./src/orbit/mean_elements/j2osc.jl
# ========================================

# Function rv_to_mean_elements_j2osc
# ----------------------------------

@testset "Function rv_to_mean_elements_j2osc" begin
    # We just need to run the J2 osc. orbit propagator, obtain the osculating
    # elements, convert them to mean elements, and compare with the original
    # ones.
    jd₀ = date_to_jd(2021, 6, 19, 19, 35, 35)
    orb_input = KeplerianElements(
        jd₀,
        7130.982e3,
        0.001111,
        deg2rad(98.405),
        compute_RAAN_lt(jd₀, 22.5),
        deg2rad(90),
        deg2rad(10)
    )

    # Generate the osculating elements.
    orbp   = init_orbit_propagator(Val(:J2osc), orb_input)
    vr, vv = propagate!(orbp, 0:10:12000)
    vjd    = get_epoch(orbp) .+ (0:10:12000) ./ 86400

    # Obtain the mean elements.
    orb, ~ = rv_to_mean_elements_j2osc(
        vjd,
        vr,
        vv;
        mean_elements_epoch = :begin,
        print_debug         = false,
        max_iterations      = 1000,
        atol                = 1e-10,
        rtol                = 1e-10
    )

    # Compare.
    @test orb.t == orb_input.t
    @test orb.a ≈  orb_input.a atol = 1e-4
    @test orb.e ≈  orb_input.e atol = 1e-11
    @test orb.i ≈  orb_input.i atol = 1e-10
    @test orb.Ω ≈  orb_input.Ω atol = 1e-10
    @test orb.ω ≈  orb_input.ω atol = 1e-7
    @test orb.f ≈  orb_input.f atol = 1e-7
end


"""
    savitsky_golay(x::Vector, windowSize::Integer, polyOrder::Integer; deriv::Integer=0)

Polynomial smoothing with the Savitsky Golay filters.

Copied from https://github.com/blakejohnson/Qlab.jl/blob/master/src/SavitskyGolay.jl

#### Sources

Theory: http://www.ece.rutgers.edu/~orfanidi/intro2sp/orfanidis-i2sp.pdf
Python example: http://wiki.scipy.org/Cookbook/SavitzkyGolay
"""
function savitsky_golay(x::Vector, windowSize::Integer, polyOrder::Integer; deriv::Integer=0)

    #Some error checking
    @assert isodd(windowSize) "Window size must be an odd integer."
    @assert polyOrder < windowSize "Polynomial order must me less than window size."

    halfWindow = round(Int, (windowSize-1) / 2)

    #Setup the S matrix of basis vectors.
    S = zeros(windowSize, polyOrder+1)
    for ct = 0:polyOrder
        S[:,ct+1] = collect(-halfWindow:halfWindow).^(ct)
    end

    #Compute the filter coefficients for all orders
    #From the scipy code it seems pinv(S) and taking rows should be enough
    G = S*pinv(S'*S)

    #Slice out the derivative order we want
    filterCoeffs = G[:,deriv+1] * factorial(deriv);

    #Pad the signal with the endpoints and convolve with filter
    paddedX = [x[1]*ones(halfWindow); x; x[end]*ones(halfWindow)]
    y = conv(filterCoeffs[end:-1:1], paddedX)

    #Return the valid midsection
    return y[2*halfWindow+1:end-2*halfWindow]

end

"""
    trend(::SSR, channel::Int)

Determine the trend for a steady state response signal.
"""
function trend(a::SSR, chan::Int; secs = 0.5, order = 2, kwargs...)

    ls = size(a.data, 1)

    windowSize = round(Int, 8192 * secs)
    if iseven(windowSize)
        windowSize += 1
    end

    savitsky_golay(vec(a.data[:, chan]), windowSize, order)
end

"""
    detrend(::SSR)

Remove the trend from a steady state response.
"""
function detrend(a::SSR; kwargs...)

    for c in 1:size(a.data, 2)
        a.data[:, c] = a.data[:, c] - trend(a, c; kwargs...)
    end

    return a
end


using Plots
using ControlSystems, RobustAndOptimalControl
"""
This is a simple SISO example with a pole in the origin, corresponding to the
DC servos used in the Lund laboratories. It serves to exeplify how the syntheis
can be done for simple SISO systems, and also demonstrates how we chan verify
if the problem is feasible to solve using the ARE method.

The example can be set to visualize plots using the variables
  makeplots - true/false (true if plots are to be generated, false for testing)
"""
makeplots = true

# Define the process
Gtrue   = tf([11.2], [1, 0.12,0])

# Sensitivity weight function
M, wB, A = 1.5, 20.0, 1e-8
WS = tf([1/M, wB],[1, wB*A])

# Output sensitivity weight function
WU = ss(1.0)

# Complementary sensitivity weight function
WT = []

# Form the P in the LFT F_l(P,C) as a partitioned state-space object
P = hinfpartition(Gtrue, WS, WU, WT)

# Check if the system is feasible for synythesis
flag = hinfassumptions(P, verbose=false)

# Since it is not, modify the plant desciption
epsilon = 1e-5
G = tf([11.2], [1, 0.12]) * tf([1], [1, epsilon])

# Form the P in the LFT Fl(P,C) as a partitioned state-space object
P = hinfpartition(G, WS, WU, WT)

# Check if the problem is feasible
flag = hinfassumptions(P)

# Synthesize the H-infinity optimal controller
flag, C, γ = hinfsynthesize(P, γrel=1)

# Extract the transfer functions defining some signals of interest
Pcl, S, CS, T = hinfsignals(P, G, C)

## Plot the specifications
if makeplots
  specificationplot([S, CS, T], [WS, WU, WT], γ) |> display
  ## Plot the closed loop gain from w to z
  specificationplot(Pcl, γ; s_labels=["\$\\sigma(P_{cl}(j\\omega))\$"], w_labels=["\$\\gamma\$"])
  ylims!((0.1, 10))

end


# Copyright (c) 2021, R. Dong, C. Goodbreak, H. Harrington, G. Pogudin
# Copyright (c) 2020, A. Ovchinnikov, A. Pillay, G. Pogudin, T. Scanlon

# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# ------------------------------------------------------------------------------

"""
    differentiate_solution(ode, params, ic, inputs, prec)

Input:
- the same as for `power_series_solutions`

Output:
- a tuple consisting of the power series solution and a dictionary of the form `(u, v) => power series`, where `u` is a state variable
  `v` is a state or parameter, and the power series is the partial derivative of
  the function `u` w.r.t. `v` evaluated at the solution
"""
function differentiate_solution(
        ode::ODE{P},
        params::Dict{P,T},
        ic::Dict{P,T},
        inputs::Dict{P,Array{T,1}},
        prec::Int
    ) where {T <: Generic.FieldElem,P <: MPolyElem{T}}
    @debug "Computing the power series solution of the system"
    ps_sol = power_series_solution(ode, params, ic, inputs, prec)
    ps_ring = parent(first(values(ps_sol)))
    for p in ode.parameters
        ps_sol[p] = ps_ring(params[p])
    end

    @debug "Building the variational system at the solution"
    # Y' = AY + B
    vars = vcat(ode.x_vars, ode.parameters)
    SA = AbstractAlgebra.MatrixSpace(ps_ring, length(ode.x_vars), length(ode.x_vars))
    A = SA([
        eval_at_dict(derivative(ode.x_equations[vars[i]], vars[j]), ps_sol)
        for i in 1:length(ode.x_vars), j in 1:length(ode.x_vars)
    ])
    SB = AbstractAlgebra.MatrixSpace(ps_ring, length(ode.x_vars), length(vars))
    B = zero(SB)
    for i in 1:length(ode.x_vars)
        for j in (length(ode.x_vars) + 1):length(vars)
            B[i, j] = eval_at_dict(derivative(ode.x_equations[vars[i]], vars[j]), ps_sol)
        end
    end
    # TODO: make use of one() function (problems modulo prime)
    initial_condition = zero(Nemo.MatrixSpace(base_ring(ode.poly_ring), length(ode.x_vars), length(vars)))
    for i in 1:length(ode.x_vars)
        initial_condition[i, i] = 1
    end

    @debug "Solving the variational system and forming the output"
    sol_var_system = ps_matrix_linear_de(A, B, initial_condition, prec)
    return (
        ps_sol,
        Dict(
            (vars[i], vars[j]) => sol_var_system[i, j]
            for i in 1:length(ode.x_vars), j in 1:length(vars)
        )
    )
end

# ------------------------------------------------------------------------------

"""
    differentiate_output(ode, params, ic, inputs, prec)

Similar to `differentiate_solution` but computes partial derivatives of a prescribed outputs
returns a dictionary of the form `y_function => Dict(var => dy/dvar)` where `dy/dvar` is the derivative
of `y_function` with respect to `var`.
"""
function differentiate_output(
        ode::ODE{P},
        params::Dict{P,T},
        ic::Dict{P,T},
        inputs::Dict{P,Array{T,1}},
        prec::Int
    ) where {T <: Generic.FieldElem,P <: MPolyElem{T}}
    @debug "Computing partial derivatives of the solution"
    ps_sol, sol_diff = differentiate_solution(ode, params, ic, inputs, prec)
    ps_ring = parent(first(values(ps_sol)))
    for p in ode.parameters
        ps_sol[p] = ps_ring(params[p])
    end

    @debug "Evaluating the partial derivatives of the outputs"
    result = Dict()
    for (y, g) in ode.y_equations
        result[y] = Dict()
        for x in ode.x_vars
            result[y][x] = sum(eval_at_dict(derivative(g, xx), ps_sol) * sol_diff[(xx, x)] for xx in ode.x_vars)
        end
        for p in ode.parameters
            result[y][p] = sum(eval_at_dict(derivative(g, xx), ps_sol) * sol_diff[(xx, p)] for xx in ode.x_vars)
            result[y][p] += eval_at_dict(derivative(g, p), ps_sol)
        end
    end

    return result
end

# ------------------------------------------------------------------------------

"""
    get_degree_and_coeffsize(f)

for `f` being a polynomial/rational function over rationals (`QQ`) returns a tuple
`(degree, max_coef_size)`
"""
function get_degree_and_coeffsize(f::MPolyElem{Nemo.fmpq})
    if length(f) == 0
        return (0, 1)
    end
    max_coef = 1
    for c in coefficients(f)
        max_coef = max(max_coef, 2 * height_bits(c))
    end
    return (total_degree(f), max_coef)
end

function get_degree_and_coeffsize(f::Generic.Frac{<: MPolyElem{Nemo.fmpq}})
    num_deg, num_coef = get_degree_and_coeffsize(numerator(f))
    den_deg, den_coef = get_degree_and_coeffsize(denominator(f))
    return (max(num_deg, den_deg), max(num_coef, den_coef))
end

# ------------------------------------------------------------------------------
"""
    assess_local_identifiability(function assess_local_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], p::Float64=0.99, type=:SE)

Input:
- `ode` - the ODESystem object from ModelingToolkit
- `measured_quantities` - the measureable outputs of the model
- `funcs_to_check` - functions of parameters for which to check identifiability
- `p` - probability of correctness
- `type` - identifiability type (`:SE` for single-experiment, `:ME` for multi-experiment)

Output:
- for `type=:SE`, the result is a dictionary from each parameter to boolean;
- for `type=:ME`, the result is a tuple with the dictionary as in `:SE` case and array of number of experiments.

The function determines local identifiability of parameters in `funcs_to_check` or all possible parameters if `funcs_to_check` is empty

The result is correct with probability at least `p`.

`type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability).
The return value is a tuple consisting of the array of bools and the number of experiments to be performed.
"
"""
function assess_local_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], p::Float64=0.99, type=:SE)
    if length(measured_quantities)==0
        if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(ode))
            @info "Measured quantities are not provided, trying to find the outputs in input ODE."
            measured_quantities = filter(eq->(ModelingToolkit.isoutput(eq.lhs)), ModelingToolkit.equations(ode))
        else
            throw(error("Measured quantities (output functions) were not provided and no outputs were found."))
        end
    end
    if length(funcs_to_check) == 0
        funcs_to_check = ModelingToolkit.parameters(ode)
    end
    ode, syms, gens_ = PreprocessODE(ode, measured_quantities)
    funcs_to_check = [eval_at_nemo(x, Dict(syms .=> gens_)) for x in funcs_to_check]
    return assess_local_identifiability(ode, funcs_to_check, p, type)
end
# ------------------------------------------------------------------------------
"""
    assess_local_identifiability(ode::ODE{P}, p::Float64 = 0.99, type=:SE) where P <: MPolyElem{Nemo.fmpq}

Input:
- `ode` - the ODE model
- `p` - probability of correctness
- `type` - identifiability type (`:SE` for single-experiment, `:ME` for multi-experiment)

Output:
- for `type=:SE`, the result is a dictionary from each parameter to boolean;
- for `type=:ME`, the result is a tuple with the dictionary as in `:SE` case and array of number of experiments.

The main entrypoint for local identifiability checks.
Call this function to automatically take care of local identifiability of all parameters and initial conditions.
The result is correct with probability at least `p`.

`type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability).
The return value is a tuple consisting of the array of bools and the number of experiments to be performed.
"""
function assess_local_identifiability(ode::ODE{P}, p::Float64=0.99, type=:SE) where P <: MPolyElem{Nemo.fmpq}
    funcs_to_check = ode.parameters
    if type == :SE
        funcs_to_check = vcat(funcs_to_check, ode.x_vars)
    end
    result = assess_local_identifiability(ode, funcs_to_check, p, type)
    if type == :SE
        return Dict(a => b for (a, b) in zip(funcs_to_check, result))
    end
    return (
        Dict(a => b for (a, b) in zip(funcs_to_check, result[1])),
        result[2]
    )
end

"""
    assess_local_identifiability(ode::ODE{P}, funcs_to_check::Array{<: Any, 1}, p::Float64=0.99, type=:SE) where P <: MPolyElem{Nemo.fmpq}

Checks the local identifiability/observability of the functions in `funcs_to_check`. The result is correct with probability at least `p`.

Call this function if you have a specific collection of parameters of which you would like to check local identifiability.

`type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability).
If the type is `:ME`, states are not allowed to appear in the `funcs_to_check`.
"""
function assess_local_identifiability(ode::ODE{P}, funcs_to_check::Array{<: Any,1}, p::Float64=0.99, type=:SE) where P <: MPolyElem{Nemo.fmpq}

    # Checking whether the states appear in the ME case
    if type == :ME
        for f in funcs_to_check
            num, den = unpack_fraction(f)
            for v in vcat(vars(num), vars(den))
                if !(v in ode.parameters)
                    @error "Multi-experiment identifiability is not properly defined for the states"
                    throw(ArgumentError("State variable $v appears in $f"))
                end
            end
        end
    end

    # Computing the prime using Proposition 3.3 from https://doi.org/10.1006/jsco.2002.0532
    @debug "Computing the prime number"
    d, h = 1, 1
    for f in vcat(collect(values(ode.x_equations)), collect(values(ode.y_equations)))
        df, hf = get_degree_and_coeffsize(f)
        d = max(d, df)
        h = max(h, hf)
    end
    p_per_func = 1 - (1 - p) / length(funcs_to_check)
    mu = ceil(1 / (1 - sqrt(p_per_func)))

    n = length(ode.x_vars)
    # if type == ME, we take into account the largest possible replica of the system to be considered
    if type == :ME
        n *= length(ode.parameters)
    end

    m = length(ode.y_vars)
    r = length(ode.u_vars)
    ell = length(ode.parameters)
    D = 4 * (n + ell)^2 * (n + m) * d
    Dprime = D * (2 * log(n + ell + r + 1) + log(mu * D)) + 4 * (n + ell)^2 * ((n + m) * h + log(2 * n * D))
    prime = Primes.nextprime(Int(ceil(2 * mu * Dprime)))
    @debug "The prime is $prime"
    F = Nemo.GF(prime)

    @debug "Extending the model"
    ode_ext = add_outputs(ode, Dict("loc_aux_$i" => f for (i, f) in enumerate(funcs_to_check)))

    @debug "Reducing the system modulo prime"
    ode_red = reduce_ode_mod_p(ode_ext, prime)

    @debug "Coumpting the observbaility matrix (and, if ME, the bound)"
    prec = length(ode.x_vars) + length(ode.parameters)

    # Parameter values are the same across all the replicas
    params_vals = Dict(p => F(rand(1:prime)) for p in ode_red.parameters)

    prev_defect = length(ode.parameters)
    num_exp = 0
    Jac = zero(Nemo.MatrixSpace(F, length(ode.parameters), 1))
    output_derivatives = undef
    while true
        ic = Dict(x => F(rand(1:prime)) for x in ode_red.x_vars)
        inputs = Dict{Nemo.gfp_mpoly,Array{Nemo.gfp_elem,1}}(u => [F(rand(1:prime)) for i in 1:prec] for u in ode_red.u_vars)

        @debug "Computing the output derivatives"
        output_derivatives = differentiate_output(ode_red, params_vals, ic, inputs, prec)

        @debug "Building the matrices"
        newJac = vcat(Jac, zero(Nemo.MatrixSpace(F, length(ode.x_vars), ncols(Jac))))
        newJac = hcat(newJac, zero(Nemo.MatrixSpace(F, nrows(newJac), prec * length(ode.y_vars))))
        xs_params = vcat(ode_red.x_vars, ode_red.parameters)
        for (i, y) in enumerate(ode.y_vars)
            y_red = str_to_var(var_to_str(y), ode_red.poly_ring)
            offset = 1 + num_exp * prec * length(ode.y_vars)
            for j in 1:prec
                for (k, p) in enumerate(ode_red.parameters)
                    newJac[k, offset + (i - 1) * prec + j] = coeff(output_derivatives[y_red][p], j - 1)
                end
                for (k, x) in enumerate(ode_red.x_vars)
                    newJac[end - k + 1, offset + (i - 1) * prec + j] = coeff(output_derivatives[y_red][x], j - 1)
                end
            end
        end
        if type == :SE
            Jac = newJac
            break
        end

        new_defect = length(ode.parameters) + LinearAlgebra.rank(newJac[(length(ode.parameters) + 1):end, :]) - LinearAlgebra.rank(newJac)
        if new_defect == prev_defect
            break
        end
        prev_defect = new_defect
        num_exp += 1
        Jac = newJac
    end

    @debug "Computing the result"
    base_rank = LinearAlgebra.rank(Jac)
    result = Array{Bool,1}()
    for i in 1:length(funcs_to_check)
        for (k, p) in enumerate(ode_red.parameters)
            Jac[k, 1] = coeff(output_derivatives[str_to_var("loc_aux_$i", ode_red.poly_ring)][p], 0)
        end
        for (k, x) in enumerate(ode_red.x_vars)
            Jac[end - k + 1, 1] = coeff(output_derivatives[str_to_var("loc_aux_$i", ode_red.poly_ring)][x], 0)
        end
        push!(result, LinearAlgebra.rank(Jac) == base_rank)
    end

    if type == :SE
        return result
    end
    return (result, num_exp)
end

# ------------------------------------------------------------------------------


using OptimizationProblems

probs = filter(name -> name != :OptimizationProblems
               && name != :penalty2
               && name != :penalty3
               , names(OptimizationProblems))

mpb_probs = (MathProgNLPModel(eval(p)(n_max),  name=string(p) )   for p in probs)


const AbstractNumOrArray = Union{AbstractVecOrMat,Number}
"""
    DescriptorStateSpace{T}(A::Matrix{T}, E::Union{Matrix{T},UniformScaling},
                            B::Matrix{T}, C::Matrix{T}, D::Matrix{T},
                            Ts::Real) where T <: Number

Construct a descriptor state-space model from a quintuple of matrices `(A,E,B,C,D)` and a sampling time `Ts`.

If `SYS::DescriptorStateSpace{T}` is a descriptor system model object
defined by the 4-tuple `SYS = (A-λE,B,C,D)`, then:

`SYS.A` is the `nx × nx` state matrix `A` with elements of type `T`.

`SYS.E` is the `nx × nx` descriptor matrix `E` with elements of type `T`.
 For a standard state-space system `SYS.E = I`, the `UniformScaling` of type `Bool`.

`SYS.B` is the `nx × nu` system input matrix `B` with elements of type `T`.

`SYS.C` is the `ny × nx` system output matrix `C` with elements of type `T`.

`SYS.D` is the `ny × nu` system feedthrough matrix `D` with elements of type `T`.

`SYS.Ts` is the real sampling time `Ts`, where `Ts = 0` for a continuous-time system,
    and `Ts > 0` or `Ts = -1` for a discrete-time system.
    `Ts = -1` indicates a discrete-time system with an unspecified sampling time.

The dimensions `nx`, `ny` and `nu` can be obtained as `SYS.nx`, `SYS.ny` and `SYS.nu`, respectively.
"""
struct DescriptorStateSpace{T} <: AbstractDescriptorStateSpace
    A::Matrix{T}
    E::Union{Matrix{T},UniformScaling}
    B::Matrix{T}
    C::Matrix{T}
    D::Matrix{T}
    Ts::Float64
    function DescriptorStateSpace{T}(A::Matrix{T}, E::Union{Matrix{T},UniformScaling},
                                     B::Matrix{T}, C::Matrix{T}, D::Matrix{T},  Ts::Real) where T
        dss_validation(A, E, B, C, D, Ts)
        new{T}(A, E, B, C, D, Float64(Ts))
    end
end
function dss_validation(A::Matrix{T}, E::Union{Matrix{T},UniformScaling},
                        B::Matrix{T}, C::Matrix{T}, D::Matrix{T},  Ts::Real) where T
    nx = LinearAlgebra.checksquare(A)
    (ny, nu) = size(D)

    # Validate dimensions
    if typeof(E) <: Matrix
       nx == LinearAlgebra.checksquare(E) || error("A and E must have the same size")
    end
    size(B, 1) == nx ||  error("B must have the same row size as A")
    size(C, 2) == nx ||  error("C must have the same column size as A")
    nu == size(B, 2) ||  error("D must have the same column size as B")
    ny == size(C, 1) ||  error("D must have the same row size as C")

    # Validate sampling time
    Ts >= 0 || Ts == -1 || error("Ts must be either a positive number, 0
                                (continuous system), or -1 (unspecified)")
    return nx, nu, ny
end
function Base.getproperty(sys::DescriptorStateSpace, d::Symbol)
    if d === :nx
        return size(sys.A,1)
    elseif d === :ny
        return size(sys.C,1)
    elseif d === :nu
        return size(sys.B,2)
    else
        getfield(sys, d)
    end
end
Base.propertynames(sys::DescriptorStateSpace) =
    (:nx, :ny, :nu, fieldnames(typeof(sys))...)

# Base.ndims(::AbstractDescriptorStateSpace) = 2
# Base.size(sys::AbstractDescriptorStateSpace) = size(sys.D)
# Base.length(sys::AbstractDescriptorStateSpace) = length(sys.D)
# Base.size(sys::AbstractDescriptorStateSpace, d::Integer) = d <= 2 ? size(sys)[d] : 1
Base.eltype(sys::DescriptorStateSpace) = eltype(sys.A)

function Base.getindex(sys::DST, inds...) where DST <: DescriptorStateSpace
    size(inds, 1) != 2 &&
        error("Must specify 2 indices to index descriptor state-space model")
    rows, cols = index2range(inds...)
    return DescriptorStateSpace{eltype(sys)}(copy(sys.A), copy(sys.E), sys.B[:, cols], sys.C[rows, :], sys.D[rows, cols], sys.Ts)
end
index2range(ind1, ind2) = (index2range(ind1), index2range(ind2))
index2range(ind::T) where {T<:Number} = ind:ind
index2range(ind::T) where {T<:AbstractArray} = ind
index2range(ind::Colon) = ind
function Base.lastindex(sys::DST, dim::Int) where DST <: DescriptorStateSpace
    lastindex(sys.D,dim)
end

# Basic Operations
function ==(sys1::DST1, sys2::DST2) where {DST1<:DescriptorStateSpace, DST2<:DescriptorStateSpace}
    # fieldnames(DST1) == fieldnames(DST2) || (return false)
    return all(getfield(sys1, f) == getfield(sys2, f) for f in fieldnames(DST1))
end

function isapprox(sys1::DST1, sys2::DST2; atol = zero(real(eltype(sys1))),
                  rtol = rtol::Real =  ((max(size(sys1.A)...))+1)*eps(real(float(one(real(eltype(sys1))))))*iszero(atol)) where
                  {DST1<:DescriptorStateSpace,DST2<:DescriptorStateSpace}
    #fieldnames(DST1) == fieldnames(DST2) || (return false)
    return all(isapprox(getfield(sys1, f), getfield(sys2, f); atol = atol, rtol = rtol) for f in fieldnames(DST1))
end

# sum sys1+sys2
function +(sys1::DescriptorStateSpace{T1}, sys2::DescriptorStateSpace{T2}) where {T1,T2}
    #Ensure systems have same dimensions and sampling times
    size(sys1) == size(sys2) || error("The systems have different shapes.")
    Ts = promote_Ts(sys1.Ts,sys2.Ts)

    T = promote_type(T1, T2)
    n1 = sys1.nx
    n2 = sys2.nx
    A = [sys1.A  zeros(T,n1,n2);
         zeros(T,n2,n1) sys2.A]
    B = [sys1.B ; sys2.B]
    C = [sys1.C sys2.C;]
    D = [sys1.D + sys2.D;]
    if sys1.E == I && sys2.E == I
        E = I
    else
        E = [sys1.E  zeros(T,n1,n2);
             zeros(T,n2,n1) sys2.E]
    end

    return DescriptorStateSpace{T}(A, E, B, C, D, Ts)
end
# difference sys1-sys2
function -(sys1::DescriptorStateSpace{T1}, sys2::DescriptorStateSpace{T2}) where {T1,T2}
    #Ensure systems have same dimensions and sampling times
    size(sys1) == size(sys2) || error("The systems have different shapes.")
    Ts = promote_Ts(sys1.Ts,sys2.Ts)

    T = promote_type(T1, T2)
    n1 = sys1.nx
    n2 = sys2.nx

    A = [sys1.A  zeros(T,n1,n2);
         zeros(T,n2,n1) sys2.A]
    B = [sys1.B ; sys2.B]
    C = [sys1.C -sys2.C;]
    D = [sys1.D - sys2.D;]
    if sys1.E == I && sys2.E == I
        E = I
    else
        E = [sys1.E  zeros(T,n1,n2);
             zeros(T,n2,n1) sys2.E]
    end

    return DescriptorStateSpace{T}(A, E, B, C, D, Ts)
end
# negation -sys
function -(sys::DescriptorStateSpace{T}) where T
    return DescriptorStateSpace{T}(sys.A, sys.E, sys.B, -sys.C, -sys.D, sys.Ts)
end
# sys+mat and mat+sys
function +(sys::DescriptorStateSpace{T1}, mat::VecOrMat{T2}) where {T1,T2}
    p, m = typeof(mat) <: Vector ? (length(mat),1) : size(mat)
    size(sys) == (p, m) || error("The input-output dimensions of system does not match the shape of matrix.")
    T = promote_type(T1, T2)
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T), sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T), copy_oftype(sys.C,T),
                                   copy_oftype(sys.D,T) + mat, sys.Ts)
end
+(mat::VecOrMat{T1}, sys::DescriptorStateSpace{T2}) where {T1,T2} = +(sys,mat)
# sys+I and I+sys
function +(sys::DescriptorStateSpace{T1}, mat::UniformScaling{T2}) where {T1,T2}
    size(sys,1) == size(sys,2) || error("The system must have the same number of inputs and outputs")
    T = promote_type(T1, T2)
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T), sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T), copy_oftype(sys.C,T),
                                   copy_oftype(sys.D,T) + mat, sys.Ts)
end

# sys-mat and mat-sys
function -(sys::DescriptorStateSpace{T1}, mat::VecOrMat{T2}) where {T1,T2}
    p, m = typeof(mat) <: Vector ? (length(mat),1) : size(mat)
    size(sys) == (p, m) || error("The input-output dimensions of system does not match the shape of matrix.")
    T = promote_type(T1, T2)
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T), sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T), copy_oftype(sys.C,T),
                                   copy_oftype(sys.D,T) - mat, sys.Ts)
end
-(mat::VecOrMat{T1}, sys::DescriptorStateSpace{T2}) where {T1,T2} = +(-sys,mat)

# sys-I and I-sys
function -(sys::DescriptorStateSpace{T1}, mat::UniformScaling{T2}) where {T1,T2}
    size(sys,1) == size(sys,2) || error("The system must have the same number of inputs and outputs")
    T = promote_type(T1, T2)
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T), sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T), copy_oftype(sys.C,T),
                                   copy_oftype(sys.D,T) - mat, sys.Ts)
end
+(mat::UniformScaling{T1}, sys::DescriptorStateSpace{T2}) where {T1,T2} = +(sys,mat)
-(mat::UniformScaling{T1}, sys::DescriptorStateSpace{T2}) where {T1,T2} = +(-sys,mat)
# sys ± n and n ± sys
function +(sys::DescriptorStateSpace{T1}, n::Number) where T1
    T = promote_type(T1, eltype(n))
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T), sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T), copy_oftype(sys.C,T),
                                   copy_oftype(sys.D,T) .+ n, sys.Ts)
end
+(n::Number, sys::DescriptorStateSpace{T1}) where T1 = +(sys, n)
-(n::Number, sys::DescriptorStateSpace{T1}) where T1 = +(-sys, n)
-(sys::DescriptorStateSpace{T1},n::Number) where T1 = +(sys, -n)

# multiplication sys1*sys2
function *(sys1::DescriptorStateSpace{T1}, sys2::DescriptorStateSpace{T2}) where {T1,T2}
    sys1.nu == sys2.ny || error("sys1 must have same number of inputs as sys2 has outputs")
    Ts = promote_Ts(sys1.Ts, sys2.Ts)
    T = promote_type(T1, T2)
    n1 = sys1.nx
    n2 = sys2.nx

    A = [sys1.A    sys1.B*sys2.C;
         zeros(T,n2,n1)   sys2.A]
    B = [sys1.B*sys2.D ; sys2.B]
    C = [sys1.C   sys1.D*sys2.C;]
    D = [sys1.D*sys2.D;]
    if sys1.E == I && sys2.E == I
        E = I
    else
        E = [sys1.E  zeros(T,n1,n2);
             zeros(T,n2,n1) sys2.E]
    end

    return DescriptorStateSpace{T}(A, E, B, C, D, Ts)
end
# sys*mat
function *(sys::DescriptorStateSpace{T1}, mat::VecOrMat{T2}) where {T1,T2}
    p, m = typeof(mat) <: Vector ? (length(mat),1) : size(mat)
    sys.nu == p || error("The input dimension of system does not match the number of rows of the matrix.")
    T = promote_type(T1, T2)
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T),
                                   sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T)*to_matrix(T,mat), copy_oftype(sys.C,T),
                                   copy_oftype(sys.D,T)*to_matrix(T,mat), sys.Ts)
end
# mat*sys
function *(mat::VecOrMat{T1}, sys::DescriptorStateSpace{T2}) where {T1,T2}
    p, m = typeof(mat) <: Vector ? (length(mat),1) : size(mat)
    sys.ny == m || error("The output dimension of system does not match the number of columns of the matrix.")
    T = promote_type(T1, T2)
    return DescriptorStateSpace{T}(copy_oftype(sys.A,T),
                                   sys.E == I ? I : copy_oftype(sys.E,T),
                                   copy_oftype(sys.B,T), to_matrix(T,mat)*copy_oftype(sys.C,T),
                                   to_matrix(T,mat)*copy_oftype(sys.D,T), sys.Ts)
end
# sI*sys
function *(s::Union{UniformScaling,Number}, sys::DescriptorStateSpace{T}) where T
    T1 = promote_type(eltype(s),T)
    return DescriptorStateSpace{T1}(copy_oftype(sys.A,T1), sys.E == I ? I : copy_oftype(sys.E,T1), copy_oftype(sys.B,T1),
                                   lmul!(s,copy_oftype(sys.C,T1)), lmul!(s,copy_oftype(sys.D,T1)), sys.Ts)

end
# sys*sI
function *(sys::DescriptorStateSpace{T},s::Union{UniformScaling,Number}) where T
    T1 = promote_type(eltype(s),T)
    return DescriptorStateSpace{T1}(copy_oftype(sys.A,T1), sys.E == I ? I : copy_oftype(sys.E,T1), rmul!(copy_oftype(sys.B,T1),s),
                                   copy_oftype(sys.C,T1), rmul!(copy_oftype(sys.D,T1),s), sys.Ts)

end

# right division sys1/sys2 = sys1*inv(sys2)
/(n::Union{UniformScaling,Number}, sys::DescriptorStateSpace) = n*inv(sys)
/(sys::DescriptorStateSpace, n::Number) = sys*(1/n)
/(sys::DescriptorStateSpace, n::UniformScaling) = sys*(1/n.λ)

# left division sys1\sys2 = inv(sys1)*sys2
\(n::Number, sys::DescriptorStateSpace) = (1/n)*sys
\(n::UniformScaling, sys::DescriptorStateSpace) = (1/n.λ)*sys
\(sys::DescriptorStateSpace, n::Union{UniformScaling,Number}) = inv(sys)*n

# display sys
Base.print(io::IO, sys::DescriptorStateSpace) = show(io, sys)
Base.show(io::IO, sys::DescriptorStateSpace) = show(io, MIME("text/plain"), sys)

function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, sys::DescriptorStateSpace)
    summary(io, sys); println(io)
    n = size(sys.A,1)
    typeof(sys.D) <: Vector ? ((p,m) = (length(sys.D),1)) : ((p, m) = size(sys.D))
    if n > 0
       println(io, "\nState matrix A:")
       show(io, mime, sys.A)
       if sys.E != I
          println(io, "\n\nDescriptor matrix E:")
          show(io, mime, sys.E)
       end
       m > 0 ? println(io, "\n\nInput matrix B:") : println(io, "\n\nEmpty input matrix B.")
       show(io, mime, sys.B)
       p > 0 ? println(io, "\n\nOutput matrix C:") : println(io, "\n\nEmpty output matrix C.")
       show(io, mime, sys.C)
       (m > 0 && p > 0) ? println(io, "\n\nFeedthrough matrix D:") : println(io, "\n\nEmpty feedthrough matrix D.")
       show(io, mime, sys.D)
       sys.Ts < 0 && println(io, "\n\nSample time: unspecified.")
       sys.Ts > 0 && println(io, "\n\nSample time: $(sys.Ts) second(s).")
       iszero(sys.Ts) ? println(io, "\n\nContinuous-time state-space model.") :
                        println(io, "Discrete-time state-space model.")
    elseif m > 0 && p > 0
       println(io, "\nFeedthrough matrix D:")
       show(io, mime, sys.D)
       println(io, "\n\nStatic gain.")
    else
       println(io, "\nEmpty state-space model.")
    end
end


@doc """H, w, = freqresp(sys, w)

Evaluate the frequency response

`H(iw) = C*((iwI -A)^-1)*B + D`

of system `sys` over the frequency vector `w`.""" ->
function freqresp{S<:Real}(sys::LTISystem, w::AbstractVector{S})
    ny, nu = size(sys)
    nw = length(w)
    # Create imaginary freq vector s
    if !iscontinuous(sys)
        Ts = sys.Ts == -1 ? 1.0 : sys.Ts
        s = exp(w*im*Ts)
    else
        s = im*w
    end
    #Evil but nessesary type instability here
    sys = _preprocess_for_freqresp(sys)
    resp = Array(Complex128, nw, ny, nu)
    for i=1:nw
        # TODO : This doesn't actually take advantage of Hessenberg structure
        # for statespace version.
        resp[i, :, :] = evalfr(sys, s[i])
    end
    return resp, w
end

# Implements algorithm found in:
# Laub, A.J., "Efficient Multivariable Frequency Response Computations",
# IEEE Transactions on Automatic Control, AC-26 (1981), pp. 407-408.
function _preprocess_for_freqresp(sys::StateSpace)
    A, B, C, D = sys.A, sys.B, sys.C, sys.D
    F = hessfact(A)
    H = F[:H]::Matrix{Float64}
    T = full(F[:Q])
    P = C*T
    Q = T\B
    StateSpace(H, Q, P, D, sys.Ts, sys.statenames, sys.inputnames,
        sys.outputnames)
end

function _preprocess_for_freqresp(sys::TransferFunction)
    map(sisotf -> _preprocess_for_freqresp(sisotf), sys.matrix)
end

_preprocess_for_freqresp(sys::SisoTf) = sys

@doc """
`evalfr(sys, x)` Evaluate the transfer function of the LTI system sys
at the complex number s=x (continuous-time) or z=x (discrete-time).

For many values of `x`, use `freqresp` instead.
""" ->
function evalfr(sys::StateSpace, s::Number)
    S = promote_type(typeof(s), Float64)
    try
        R = Diagonal(S[s for i=1:sys.nx]) - sys.A
        sys.D + sys.C*((R\sys.B)::Matrix{S})  # Weird type stability issue
    catch
        fill(convert(S, Inf), size(sys))
    end
end

function evalfr(sys::TransferFunction, s::Number)
    S = promote_type(typeof(s), Float64)
    mat = sys.matrix
    ny, nu = size(mat)
    res = Array(S, ny, nu)
    for j = 1:nu
        for i = 1:ny
            res[i, j] = evalfr(mat[i, j], s)
        end
    end
    return res
end

evalfr(mat::Matrix, s::Number) = map(sys -> evalfr(sys, s), mat)

@doc """
`F(s)`, `F(omega, true)`, `F(z, false)`

Notation for frequency response evaluation.
- F(s) evaluates the continuous-time transfer function F at s.
- F(omega,true) evaluates the discrete-time transfer function F at i*Ts*omega
- F(z,false) evaluates the discrete-time transfer function F at z
""" ->
function (sys::TransferFunction)(s)
    evalfr(sys,s)
end

function (sys::TransferFunction)(z_or_omega::Number, map_to_unit_circle::Bool)
    @assert !iscontinuous(sys) "It makes no sense to call this function with continuous systems"
    if map_to_unit_circle
        isreal(z_or_omega) ? evalfr(sys,exp(im*z_or_omega.*sys.Ts)) : error("To map to the unit circle, omega should be real")
    else
        evalfr(sys,z_or_omega)
    end
end

function (sys::TransferFunction)(s::AbstractVector, map_to_unit_circle::Bool)
    @assert !iscontinuous(sys) "It makes no sense to call this function with continuous systems"
    freqresp(sys,s)
end

@doc """`mag, phase, w = bode(sys[, w])`

Compute the magnitude and phase parts of the frequency response of system `sys`
at frequencies `w`

`mag` and `phase` has size `(length(w), ny, nu)`""" ->
function bode(sys::LTISystem, w::AbstractVector)
    resp = freqresp(sys, w)[1]
    return abs(resp), rad2deg(unwrap!(angle(resp),1)), w
end
bode(sys::LTISystem) = bode(sys, _default_freq_vector(sys, :bode))

@doc """`re, im, w = nyquist(sys[, w])`

Compute the real and imaginary parts of the frequency response of system `sys`
at frequencies `w`

`re` and `im` has size `(length(w), ny, nu)`""" ->
function nyquist(sys::LTISystem, w::AbstractVector)
    resp = freqresp(sys, w)[1]
    return real(resp), imag(resp), w
end
nyquist(sys::LTISystem) = nyquist(sys, _default_freq_vector(sys, :nyquist))

@doc """`sv, w = sigma(sys[, w])`

Compute the singular values of the frequency response of system `sys` at
frequencies `w`

`sv` has size `(length(w), max(ny, nu))`""" ->
function sigma(sys::LTISystem, w::AbstractVector)
    resp = freqresp(sys, w)[1]
    nw, ny, nu = size(resp)
    sv = Array(Float64, nw, min(ny, nu))
    for i=1:nw
        sv[i, :] = svdvals(resp[i, :, :])
    end
    return sv, w
end
sigma(sys::LTISystem) = sigma(sys, _default_freq_vector(sys, :sigma))

function _default_freq_vector{T<:LTISystem}(systems::Vector{T}, plot::Symbol)
    min_pt_per_dec = 60
    min_pt_total = 200
    nsys = length(systems)
    bounds = Array(Float64, 2, nsys)
    for i=1:nsys
        # TODO : For now we ignore the feature information. In the future,
        # these can be used to improve the frequency vector near features.
        bounds[:, i] = _bounds_and_features(systems[i], plot)[1]
    end
    w1 = minimum(bounds)
    w2 = maximum(bounds)
    nw = round(Int, max(min_pt_total, min_pt_per_dec*(w2 - w1)))
    return logspace(w1, w2, nw)
end
_default_freq_vector(sys::LTISystem, plot::Symbol) = _default_freq_vector(
        LTISystem[sys], plot)

_default_freq_vector{T<:TransferFunction{SisoGeneralized}}(sys::Vector{T}, plot::Symbol) =
    logspace(-2,2,400)
_default_freq_vector(sys::TransferFunction{SisoGeneralized} , plot::Symbol) =
    logspace(-2,2,400)

function _bounds_and_features(sys::LTISystem, plot::Symbol)
    # Get zeros and poles for each channel
    if plot != :sigma
        zs, ps = zpkdata(sys)
        # Compose vector of all zs, ps, positive conjugates only.
        zp = vcat([vcat(i, j) for (i, j) in zip(zs, ps)]...)
        zp = zp[imag(zp) .>= 0.0]
    else
        # For sigma plots, use the MIMO poles and zeros
        zp = [tzero(sys); pole(sys)]
    end
    # Get the frequencies of the features, ignoring low frequency dynamics
    fzp = log10(abs(zp))
    fzp = fzp[fzp .> -4]
    fzp = sort!(fzp)
    # Determine the bounds on the frequency vector
    if !isempty(fzp)
        w1 = floor(fzp[1] - 0.2)
        w2 = ceil(fzp[end] + 0.2)
        # Expand the range for nyquist plots
        if plot == :nyquist
            w1 -= 1
            w2 += 1
        end
    else
        w1 = 0
        w2 = 2
    end
    return [w1, w2], zp
end


using Base.Test
using StochasticProcesses

t=linspace(0, 1, 10)

let process = BrownianMotion(1.0)
  @test_approx_eq initial(process) 1.0

  # [0] time grid
  @test_approx_eq sim(process,    [0])      [1.0]
  @test_approx_eq sim(process,    [0], 3)   [1.0, 1.0, 1.0]
  @test_approx_eq cumsim(process, [0])      [1.0]
  @test_approx_eq cumsim(process, [0], 3)   [1.0, 1.0, 1.0]

  @test size(sim(process, [0, 1]))      == (1,)
  @test size(sim(process, [0, 1], 3))   == (1,3)
  @test size(cumsim(process, [0,1]))    == (2,)
  @test size(cumsim(process, [0,1], 3)) == (2,3)

  distribution(process, 0)
  distribution(process, 10)
  @test size(rand(process, t, 10)) == (10,)
end

# multivariate bm
let process = BrownianMotion([1.0, 2.0])
  @test initial(process) == [1.0, 2.0]

  @test_approx_eq sim(process, [0]) [1.0 2.0]
  @test_approx_eq sim(process, [0], 3) [1.0 1.0 1.0; 2.0 2.0 2.0]
  @test_approx_eq cumsim(process, [0]) [1.0 2.0]
  @test_approx_eq cumsim(process, [0], 3) [1.0 1.0 1.0; 2.0 2.0 2.0]

  @test size(sim(process, [0, 1]))      == (2,)
  @test size(sim(process, [0, 1], 3))   == (2,3)
  @test size(cumsim(process, [0, .5, 1]))    == (3,2)
  @test size(cumsim(process, [0, .5, 1], 4)) == (3,2,4)

  distribution(process, 0)
  distribution(process, 10)

  @test size(rand(process, t, 10)) == (2, 10)
end


#ENV["CPLEX_STUDIO_BINARIES"]="/home/software/cplex/128/cplex/bin/x86-64_linux"

######################################################################################################################################################
#### Note: you will need to manually change the miop_formulation file to set a time limit of 120s before running this code, to reproduce output.
######################################################################################################################################################

using DelimitedFiles, LinearAlgebra, Random
include("src/core.jl")
function getProblemDataBeasley_minreturn(pathName)
    testData=readdlm(pathName)
    n=testData[1,1]
    μ=testData[2:n+1, 1]
    μ=convert(Array{Float64}, μ)
    σ=testData[2:n+1, 2]
    Σ=zeros(n,n)
    for iter=n+2:size(testData,1)
        # This loop inserts diagonal elements twice. This is ok as benign, and a "fix" would require more lines of code.
        Σ[testData[iter, 1], testData[iter, 2]]=σ[testData[iter, 1]]*σ[testData[iter, 2]]*testData[iter, 3]
        Σ[testData[iter, 2], testData[iter, 1]]=σ[testData[iter, 1]]*σ[testData[iter, 2]]*testData[iter, 3]
    end
    X=sqrt(Σ)
    k=5
    γ=100/sqrt(n)*ones(n)

    # Compute r_min and r_max, in order to add minimum return constraint.

    # r_min
    m=Model(solver=MosekSolver(MSK_IPAR_LOG=0, MSK_IPAR_MAX_NUM_WARNINGS=0));
    @variable(m, x[1:n]>=0.0);
    @constraint(m, sum(x)==1.0);
    @objective(m, Min,  ((1.0/(2.0*γ[1]))*x'*x +0.5*x'*Σ*x)');
    solve(m)
    r_min=μ'*getvalue(x)
    # r_max
    m2=Model(solver=MosekSolver(MSK_IPAR_LOG=0, MSK_IPAR_MAX_NUM_WARNINGS=0))
    @variable(m2, x2[1:n]>=0.0)
    @constraint(m2, sum(x2)==1.0)
    @objective(m2, Max,  (μ'*x2-(1.0/(2.0*γ[1]))*x2'*x2)')
    solve(m2)
    r_max=μ'*getvalue(x2)
    r_bar=r_min+0.3*(r_max-r_min)

    A=μ'
    l=[r_bar]
    u=[1e12]
    min_investment=zeros(n)
    f=size(X, 2)
    m=size(A, 1)
    # Constraining minimum return instead, so these entries all become 0.
    μ=zeros(n)
    Y=(X*X')\(X*μ)
    d=X'*Y-μ
    d=round.(d, digits=8) #avoids round-off errors when Σ is full rank

    return SparsePortfolioData(μ, Y, d, X, A, l, u, k, n, m, f, min_investment, γ)
end

for portfolioNumber=2:4
     pathName = "/Users/Ryancw2/Dropbox (MIT)/Sparse Portfolios Project/OptimalMarkowitzPortfolios.jl/data/port"
     pathName=pathName*string(portfolioNumber)*".txt"
     thePortfolioData=getProblemDataBeasley_minreturn(pathName)

     sparse_Portfolio=cutting_planes_portfolios(thePortfolioData; useSOCPLB=false, minReturnConstraint=true, useHeuristic=false);
     @show @test(abs.(sum(sparse_Portfolio.w)-1.0)<1e-4)
     println("Variance is:", sparse_Portfolio.portfolioVariance)
     println("Return is:", sparse_Portfolio.expectedReturn)
     println("Reg term is:", 0.5*sum(sparse_Portfolio.w[i]^2/thePortfolioData.γ[sparse_Portfolio.indices[i]] for i=1:size(sparse_Portfolio.indices,1)))
     open("results/Table5ResultsOutput.csv","a") do fp
     println(fp, "Instance, cardinality, regularizer, method, solvetime, gap, nodecount, iters")
     println(fp, "beasley"*string(portfolioNumber), ",", thePortfolioData.k, ",",
                       "100/sqrt(n)", ",", "OA", ",",  sparse_Portfolio.solveTime, ",", sparse_Portfolio.boundGap, ",", sparse_Portfolio.nodeCount, ",", sparse_Portfolio.numIters)
     end
     # You will need to manually check there are 50 (not 200) cuts applied here.
     sparse_Portfolio=cutting_planes_portfolios(thePortfolioData; useSOCPLB=false, minReturnConstraint=true, useHeuristic=false, usingKelleyPrimal=true, maxCutCallbacks=0);
     @show @test(abs.(sum(sparse_Portfolio.w)-1.0)<1e-4)
     println("Variance is:", sparse_Portfolio.portfolioVariance)
     println("Return is:", sparse_Portfolio.expectedReturn)
     println("Reg term is:", 0.5*sum(sparse_Portfolio.w[i]^2/thePortfolioData.γ[sparse_Portfolio.indices[i]] for i=1:size(sparse_Portfolio.indices,1)))
     open("results/Table5ResultsOutput_b.csv","a") do fp
     println(fp, "Instance, cardinality, regularizer, method, solvetime, gap, nodecount, iters")
     println(fp, "beasley"*string(portfolioNumber), ",", thePortfolioData.k, ",",
                       "100/sqrt(n)", ",", "OA-Kelley (50 cuts)", ",",  sparse_Portfolio.solveTime, ",", sparse_Portfolio.boundGap, ",", sparse_Portfolio.nodeCount, ",", sparse_Portfolio.numIters)
     end

     #Test against Big-M for correctness
     sparse_Portfolio_Cplex_bigM=cplex_raw_bigM(thePortfolioData, 120.0, 1e-8, 1)
     @show @test(abs.(sum(sparse_Portfolio_Cplex_bigM.w)-1.0)<1e-4)
     println("Variance is:", sparse_Portfolio_Cplex_bigM.portfolioVariance)
     println("Return is:", sparse_Portfolio_Cplex_bigM.expectedReturn)
     println("Reg term is:", 0.5*sum(sparse_Portfolio_Cplex_bigM.w[i]^2/thePortfolioData.γ[sparse_Portfolio_Cplex_bigM.indices[i]] for i=1:size(sparse_Portfolio_Cplex_bigM.indices,1)))
     open("results/Table5ResultsOutput.csv","a") do fp
     println(fp, "Instance, cardinality, regularizer, method, solvetime, gap, nodecount")
     println(fp, "beasley"*string(portfolioNumber), ",", thePortfolioData.k, ",",
                     "100/sqrt(n)", ",", "CPLEX Big-M", ",",  sparse_Portfolio_Cplex_bigM.solveTime, ",", sparse_Portfolio_Cplex_bigM.boundGap, ",", sparse_Portfolio_Cplex_bigM.nodeCount)
    end

    # #  # Test against MISOCP for correctness
    sparse_Portfolio_Cplex=cplex_raw_MISOCP(thePortfolioData, 120.0, 1e-8, 1)
    @show @test(abs.(sum(sparse_Portfolio_Cplex.w)-1.0)<1e-4)
    println("Variance is:", sparse_Portfolio_Cplex.portfolioVariance)
    println("Return is:", sparse_Portfolio_Cplex.expectedReturn)
    println("Reg term is:", 0.5*sum(sparse_Portfolio_Cplex.w[i]^2/thePortfolioData.γ[sparse_Portfolio_Cplex.indices[i]] for i=1:size(sparse_Portfolio_Cplex.indices,1)))
    open("results/Table5ResultsOutput.csv","a") do fp
    println(fp, "Instance, cardinality, regularizer, method, solvetime, gap, nodecount")
    println(fp, "beasley"*string(portfolioNumber), ",", thePortfolioData.k, ",",
                        "100/sqrt(n)", ",", "CPLEX MISOCP", ",",  sparse_Portfolio_Cplex.solveTime, ",", sparse_Portfolio_Cplex.boundGap, ",", sparse_Portfolio_Cplex.nodeCount)
    end
end