"""
    grasol(sysg, sysf[, γ]; L2sol = false, nehari = false, reltol = 0.0001, mindeg = false, poles, sdeg,
           fast = true, offset = β, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, info)

Determine for the descriptor systems `sysg = (Ag-λEg,Bg,Cg,Dg)` and `sysf = (Af-λEf,Bf,Cf,Df)`
with the transfer function matrices `G(λ)` and `F(λ)`,
respectively, the descriptor system `sysx` with the transfer function matrix `X(λ)`
such that `X(λ)` is the approximate solution of the linear rational equation `G(λ)X(λ) = F(λ)`,
which achieves the minimum error norm ``{\\small \\text{mindist} := \\min \\|G(λ)X(λ) - F(λ)\\|}``.
The resulting `X(λ)` has all poles stable or lying on the boundary of the stability domain `Cs`.
If `L2sol = false` (default) then the `L∞`-norm optimal solution is computed, while if `L2sol = true` the
`L2`-norm optimal solution is computed.
`sysg` and `sysf` must not have poles on the boundary of the stability domain `Cs`.

If  `γ > 0` is a desired sub-optimality degree, then the `γ`-suboptimal model-matching problem

```math
     \\text{mindist} := \\|G(λ)X(λ) - F(λ) \\| < γ
```
is solved and `mindist` is the achieved suboptimal distance.

The call with

    grasol(sysgf[, mf[, γ]]; L2sol = false, nehari = false, reltol = 0.0001, mindeg = false, poles, sdeg,
           fast = true, offset = β, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, info)

uses the compound descriptor system `sysgf = (A-λE,[Bg Bf],C,[Dg Df])`,
where `Bf` and `Df` have `mf` columns, to define
the descriptor systems `sysg = (A-λE,Bg,C,Dg)` and `sysf = (A-λE,Bf,C,Df)`
(i.e., `Ag-λEg = Af-λEf = A-λE` and `Cg = Cf = C`).
`sysgf` must not have poles on the boundary of the stability domain `Cs`.

If `nehari = true`, the optimal or suboptimal Nehari approximation is used to
compute a `L∞`-suboptimal solution of the underlying _least-distance problem_ (`LDP`).
If `nehari = false` (default), the `L∞`-optimal solution is computed using the `γ`-iteration
in the underlying `LDP` [2].

If `mindeg = true`, a minimum order solution is determined (if possible),
while if `mindeg = false` (default) a particular solution of non-minimal order is determined.

The resulting named tuple `info` contains additional information:
`info.nrank` is the normal rank of `G(λ)`,
`info.nr` is the number of freely assignable poles of the solution `X(λ)`,
`info.mindist` is the achieved approximation error norm and
`info.nonstandard` is `true` for a non-standard problem, with `G(λ)`
having zeros on the boundary of the stability domain, and `false` for
a standard problem, when `G(λ)` has no zeros on the boundary
of the stability domain.

The keyword argument `reltol` specifies the relative tolerance for the desired accuracy of
the `γ`-iteration employed to solve the underlying least-distance problem.
The iterations are performed until the current estimations of maximum ``γ_u`` and minimum ``γ_l`` of
the optimal distance satisfies
``{\\small γ_u-γ_l \\leq \\text{reltol} * \\text{gap}}``, where `gap` is the initial estimation of the error gap.

To assess the presence of poles on the boundary of the stability domain `Cs`, a boundary offset  `β`
can be specified via the keyword parameter `offset = β`.
Accordingly, for a continuous-time setting,
the boundary of `Cs` contains the complex numbers with real parts within the interval `[-β,β]`,
while for a discrete-time setting, the boundary of `Cs` contains
the complex numbers with moduli within the interval `[1-β,1+β]`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The vector `poles` specified as a keyword argument, can be used to specify the desired poles
of `sysx` alternatively to or jointly with enforcing a desired stability degree `sdeg` of poles.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, the absolute tolerance for the
nonzero elements of `Ag`, `Af`, `A`, `Bg`, `Bf`, `Cg`, `Cf`, `Dg`, `Df`,
the absolute tolerance for the nonzero elements of `Eg`, `Ef`, and the relative tolerance
for the nonzero elements of all above matrices. The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sysg`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol` and `atol2 = atol`.

The rank decisions in the underlying pencil manipulation algorithms are
based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

_Method:_  An extension of the approach of [1] to descriptor systems is used.

_References:_

[1]  B. A. Francis. A Course in H-infinity Theory,
       Vol. 88 of Lecture Notes in Control and Information Sciences,
       Springer-Verlag, New York, 1987.

[2] C.-C. Chu, J. C. Doyle, and E. B. Lee.
    The general distance problem in H∞  optimal control theory,
    Int. J. Control, vol 44, pp. 565-596, 1986.

"""
function grasol(sysgf::DescriptorStateSpace{T}, mf::Int, γ::Union{Real,Missing} = missing;
                L2sol::Bool = false, nehari::Bool = false, offset::Real = sqrt(eps(float(real(T)))),
                poles::Union{AbstractVector,Missing} = missing, sdeg::Union{Real,Missing} = missing,
                atol::Real = zero(real(T)), atol1::Real = atol, atol2::Real = atol,
                rtol::Real = (sysgf.nx*eps(real(float(one(T)))))*iszero(max(atol1,atol2)),
                reltol::Real = 0.0001, fast::Bool = true, mindeg::Bool = false) where T
   p, mgf = size(sysgf);
   (mf <= mgf && mf >= 0) || throw(DimensionMismatch("mf must be at most $mgf, got $mf"))
   m = mgf-mf
   return grasol(sysgf[:,1:m], sysgf[:,m+1:mgf], γ; L2sol = L2sol, nehari = nehari, offset = offset,
                 reltol = reltol, poles = poles, sdeg = sdeg, fast = fast,
                 mindeg = mindeg, atol1 = atol1, atol2 = atol2, rtol = rtol)
end
function grasol(sysg::DescriptorStateSpace{T1}, sysf::DescriptorStateSpace{T2},
                γ::Union{Real,Missing} = missing; L2sol::Bool = false, nehari::Bool = false,
                offset::Real = sqrt(eps(float(real(T1)))), reltol::Real = 0.0001,
                poles::Union{AbstractVector,Missing} = missing, sdeg::Union{Real,Missing} = missing,
                atol::Real = zero(real(T1)), atol1::Real = atol, atol2::Real = atol,
                rtol::Real = (sysg.nx*eps(real(float(one(T1)))))*iszero(max(atol1,atol2)),
                fast::Bool = true, mindeg::Bool = false) where {T1,T2}
   T = promote_type(T1,T2)
   T <: BlasFloat || ( T = promote_type(Float64,T) )

   p, m = size(sysg)
   pf, mf = size(sysf)
   p == pf|| throw(DimensionMismatch("sysg and sysf must have the same number of outputs"))
   Ts = promote_Ts(sysg.Ts,sysf.Ts)
   disc = !iszero(Ts)

   #compute the extended inner-quasi-outer factorization
   Gi, Go, info1 = giofac(sysg, atol1 = atol1, atol2 = atol2, atol3 = atol1, rtol = rtol,
                          fast = fast, minphase = true, offset = offset)
   ro = info1.nrank

   #detect nonstandard problem
   nonstandard = (info1.nfuz + info1.niuz > 0)

   #define and solve the LDP
   # ensure stabilizability of F
   F = gir(Gi'*sysf, atol1 = atol1, atol2 = atol2, rtol = rtol)
   if L2sol
      #set tolerance for feedthrough matrix
      atol1 > 0 ? told = atol1 : told = 1.e4*eps()
      disc || norm(F.D[ro+1:end,:],Inf) > told || (F.D[ro+1:end,:] = zeros(T1,p-ro,mf))
      #solve the H2-LDP min||[ F1-Xt; F2 ] ||_2
      Xt, Xtu = gsdec(F[1:ro,:], job = "stable")
      gopt = gl2norm(grcfid(gir([Xtu; F[ro+1:end,:] ], fast = fast, atol1 = atol1, atol2 = atol2))[1],
                     atolinf = told)
   else
      #solve the H_inf-LDP min ||[ F1-Xt; F2 ] ||_inf
      nehari || (nehari = iszero(F[ro+1:end,:]; atol1, atol2, rtol))
      Yt, gopt = glinfldp(gdual(F), p-ro, γ; nehari = nehari, fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol, offset = offset, reltol = reltol);
      Xt = gdual(Yt)
   end

   if ro == m
      nr = 0
      sysx = gir(Go\Xt, noseig = true, atol1 = atol1, atol2 = atol2)
   else
      sysx, info1, = grsol(Go, Xt, mindeg = mindeg, poles = poles, sdeg = sdeg,
                           atol1 = atol1, atol2 = atol2, rtol = rtol)
      if min(sysx.ny,sysx.nu) > 0
         ev = gpole(sysx, atol1 = atol1, atol2 = atol2);
         disc || (ev = ev[isfinite.(ev)])
         if !isempty(ev) && !nonstandard
            if (!disc && maximum(real.(ev)) > -offset) || (disc && maximum(abs.(ev)) > 1-offset)
               ismissing(poles) && ismissing(sdeg) && (sdeg = disc ? 0.95 : -0.05)
               sysx = grsol(Go, Xt, mindeg = false, poles = poles, sdeg = sdeg,
                            atol1 = atol1, atol2 = atol2, rtol = rtol)[1]
            end
         end
      end
      nr = info1.nr
   end

   info = (nrank = ro, nr = nr, mindist = gopt, nonstandard = nonstandard)
   return sysx, info

   #end GRASOL
end

"""
    glasol(sysg, sysf[, γ]; L2sol = false, nehari = false, reltol = 0.0001, mindeg = false, poles, sdeg,
           fast = true, offset = β, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, info)

Determine for the descriptor systems `sysg = (Ag-λEg,Bg,Cg,Dg)` and
`sysf = (Af-λEf,Bf,Cf,Df)` with the transfer function matrices `G(λ)` and `F(λ)`,
respectively, the descriptor system `sysx` with the transfer function matrix `X(λ)`
such that `X(λ)` is the approximate solution of the linear rational equation `X(λ)G(λ) = F(λ)`,
which achieves the minimum error norm ``{\\small \\text{mindist} := \\min \\|X(λ)G(λ) - F(λ)\\|}``.
The resulting `X(λ)` has all poles stable or lying on the boundary of the stability domain `Cs`.
If `L2sol = false` (default) then the `L∞`-norm optimal solution is computed, while if `L2sol = true` the
`L2`-norm optimal solution is computed.
`sysg` and `sysf` must not have poles on the boundary of the stability domain `Cs`.

If  `γ > 0` is a desired sub-optimality degree, then the `γ`-suboptimal model-matching problem

```math
     \\text{mindist} := \\|X(λ)G(λ) - F(λ) \\| < γ
```

is solved and `mindist` is the achieved suboptimal distance.

The call with

    glasol(sysgf[, pf[, γ]]; L2sol = false, nehari = false, reltol = 0.0001, mindeg = false, poles, sdeg,
           fast = true, offset = β, atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, info)

uses the compound descriptor system `sysgf = (A-λE,B,[Cg; Cf],[Dg; Df])`,
where `Cf` and `Df` have `pf` rows, to define
the descriptor systems `sysg = (A-λE,B,Cg,Dg)` and `sysf = (A-λE,B,Cf,Df)`
(i.e., `Ag-λEg = Af-λEf = A-λE` and `Bg = Bf = B`).
`sysgf` must not have poles on the boundary of the stability domain `Cs`.

If `nehari = true`, the optimal or suboptimal Nehari approximation is used to solve the
 underlying _least-distance problem_ (`LDP`).
If `nehari = false` (default), the optimal solution is computed using the `γ`-iteration
in the underlying `LDP` [2].

If `mindeg = true`, a minimum order solution is determined (if possible),
while if `mindeg = false` (default) a particular solution of non-minimal order is determined.

The resulting named tuple `info` contains additional information:
`info.nrank` is the normal rank of `G(λ)`,
`info.nl` is the number of freely assignable poles of the solution `X(λ)`,
`info.mindist` is the achieved approximation error norm and
`info.nonstandard` is `true` for a non-standard problem, with `G(λ)`
having zeros on the boundary of the stability domain, and `false` for
a standard problem, when `G(λ)` has no zeros on the boundary
of the stability domain.

The keyword argument `reltol` specifies the relative tolerance for the desired accuracy of
the `γ`-iteration employed to solve the underlying least-distance problem.
The iterations are performed until the current estimations of maximum ``γ_u`` and minimum ``γ_l`` of
the optimal distance satisfies
``{\\small γ_u-γ_l < \\text{reltol}* \\text{gap}}``, where `gap` is the initial estimation of the error gap.

To assess the presence of poles on the boundary of the stability domain `Cs`, a boundary offset  `β`
can be specified via the keyword parameter `offset = β`.
Accordingly, for a continuous-time setting,
the boundary of `Cs` contains the complex numbers with real parts within the interval `[-β,β]`,
while for a discrete-time setting, the boundary of `Cs` contains
the complex numbers with moduli within the interval `[1-β,1+β]`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The vector `poles` specified as a keyword argument, can be used to specify the desired poles
of `sysx` alternatively to or jointly with enforcing a desired stability degree `sdeg` of poles.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, the absolute tolerance for the
nonzero elements of `Ag`, `Af`, `A`, `Bg`, `Bf`, `Cg`, `Cf`, `Dg`, `Df`,
the absolute tolerance for the nonzero elements of `Eg`, `Ef`, and the relative tolerance
for the nonzero elements of all above matrices. The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sysg`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol` and `atol2 = atol`.

The rank decisions in the underlying pencil manipulation algorithms are
based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

_Method:_  An extension of the approach of [1] to descriptor systems is used.

_References:_

[1]  B. A. Francis. A Course in H-infinity Theory,
       Vol. 88 of Lecture Notes in Control and Information Sciences,
       Springer-Verlag, New York, 1987.

[2] C.-C. Chu, J. C. Doyle, and E. B. Lee.
    The general distance problem in H∞  optimal control theory,
    Int. J. Control, vol 44, pp. 565-596, 1986.

"""
function glasol(sysgf::DescriptorStateSpace{T}, pf::Int, γ::Union{Real,Missing} = missing;
                L2sol::Bool = false, nehari::Bool = false, offset::Real = sqrt(eps(float(real(T)))),
                poles::Union{AbstractVector,Missing} = missing, sdeg::Union{Real,Missing} = missing,
                atol::Real = zero(real(T)), atol1::Real = atol, atol2::Real = atol,
                rtol::Real = (sysgf.nx*eps(real(float(one(T)))))*iszero(max(atol1,atol2)),
                reltol::Real = 0.0001, fast::Bool = true, mindeg::Bool = false) where T
   pgf, m = size(sysgf);
   (pf <= pgf && pf >= 0) || throw(DimensionMismatch("pf must be at most $pgf, got $pf"))
   p = pgf-pf
   return glasol(sysgf[1:p,:], sysgf[p+1:pgf,:], γ; L2sol = L2sol, nehari = nehari, offset = offset,
                 reltol = reltol, poles = poles, sdeg = sdeg, fast = fast,
                 mindeg = mindeg, atol1 = atol1, atol2 = atol2, rtol = rtol)
end
function glasol(sysg::DescriptorStateSpace{T1}, sysf::DescriptorStateSpace{T2},
                γ::Union{Real,Missing} = missing; L2sol::Bool = false, nehari::Bool = false,
                offset::Real = sqrt(eps(float(real(T1)))), reltol::Real = 0.0001,
                poles::Union{AbstractVector,Missing} = missing, sdeg::Union{Real,Missing} = missing,
                atol::Real = zero(real(T1)), atol1::Real = atol, atol2::Real = atol,
                rtol::Real = (sysg.nx*eps(real(float(one(T1)))))*iszero(max(atol1,atol2)),
                fast::Bool = true, mindeg::Bool = false) where {T1,T2}
   T = promote_type(T1,T2)
   T <: BlasFloat || ( T = promote_type(Float64,T) )

   p, m = size(sysg)
   pf, mf = size(sysf)
   m == mf|| throw(DimensionMismatch("sysg and sysf must have the same number of inputs"))
   # isstable(sysf, atol1 = atol1, atol2 = atol2, rtol = rtol, offset = offset) ||
   #          error("sysf must be a stable system")
   Ts = promote_Ts(sysg.Ts,sysf.Ts)
   disc = !iszero(Ts)

   #compute the extended quasi-co-outer-co-inner factorization
   Gi, Go, info1 = goifac(sysg; atol1, atol2, atol3 = atol1, rtol, fast, minphase = true, offset)
   ro = info1.nrank

   #detect nonstandard problem
   #nonstandard = order(gcrange(Go,struct('tol',tol,'zeros','s-unstable'))) > 0;
   nonstandard = (info1.nfuz + info1.niuz > 0)

   #define and solve the LDP
   # ensure detectability of F
   F = gir(sysf*Gi', atol1 = atol1, atol2 = atol2, rtol = rtol)
   if L2sol
      #set tolerance for feedthrough matrix
      atol1 > 0 ? told = atol1 : told = 1.e4*eps()
      disc || norm(F.D[:,ro+1:end],Inf) > told || (F.D[:,ro+1:end] = zeros(T1,pf,m-ro))
      #solve the H2-LDP min||[ F1-Xt F2 ] ||_2
      Xt, Xtu = gsdec(F[:,1:ro],job = "stable")
      Y = glcfid(gir([Xtu F[:,ro+1:end] ], atol1 = atol1, atol2 = atol2))[1]
      gopt = gl2norm(glcfid(gir([Xtu F[:,ro+1:end] ], fast = fast, atol1 = atol1, atol2 = atol2))[1],
                     atolinf = told)
   else
      #solve the H_inf-LDP min ||[ F1-Xt F2 ] ||_inf
      nehari || (nehari = iszero(F[:,ro+1:end]; atol1, atol2, rtol))
      Xt, gopt = glinfldp(F, m-ro, γ; nehari = nehari, fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol, offset = offset, reltol = reltol);
   end

   if ro == p
      nl = 0
      sysx = gir(Xt/Go, noseig = true, atol1 = atol1, atol2 = atol2)
   else
      sysx, info1, = glsol(Go, Xt, mindeg = mindeg, poles = poles, sdeg = sdeg,
                    atol1 = atol1, atol2 = atol2, rtol = rtol)
      if min(sysx.ny,sysx.nu) > 0
         ev = gpole(sysx, atol1 = atol1, atol2 = atol2);
         disc || (ev = ev[isfinite.(ev)])
         if !isempty(ev) && !nonstandard
            if (!disc && maximum(real.(ev)) > -offset) || (disc && maximum(abs.(ev)) > 1-offset)
               ismissing(poles) && ismissing(sdeg) && (sdeg = disc ? 0.95 : -0.05)
               sysx = glsol(Go, Xt, mindeg = false, poles = poles, sdeg = sdeg,
                            atol1 = atol1, atol2 = atol2, rtol = rtol)[1]
            end
         end
      end
      nl = info1.nl
   end

   info = (nrank = ro, nl = nl, mindist = gopt, nonstandard = nonstandard)
   return sysx, info

   #end GLASOL
end
"""
    glinfldp(sys1, sys2, [, γ]; nehari = false, reltol = 0.0001, fast = true, offset = β,
             atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, mindist)

Determine for the descriptor systems `sys1 = (A1-λE1,B1,C1,D1)` and
`sys2 = (A2-λE2,B2,C2,D2)` with the transfer function matrices ``G_1(λ)`` and ``G_2(λ)``,
respectively, the descriptor system `sysx` with the transfer function matrix ``X(λ)``
such that ``X(λ)`` is the solution of the 2-block `L∞` _least distance problem_ (`LDP`)

```math
      \\text{mindist} := \\min \\|G_1(λ)-X(λ) \\mid   G_2(λ) \\|_\\infty
```

`mindist` is the achieved minimum distance corresponding to the optimal solution.
If `sys2 = []`, an 1-block `LDP` is solved.
`sys1` and `sys2` must not have poles on the boundary of the stability domain `Cs`.
It is assumed that `sys2` has no poles on the boundary of the stability domain (see below).
The resulting solution is stable, provided `sys1` has no poles on the boundary
of the stability domain as well.

If  ``{\\small γ > \\|G_2(λ)\\|_\\infty}`` is a desired sub-optimality degree, then the
`γ`-suboptimal `LDP`

```math
     \\text{mindist} := \\|G_1(λ)-X(λ) \\mid G_2(λ) \\|_\\infty < γ
```

is solved and `mindist` is the achieved suboptimal distance.

The call with

    glinfldp(sys[, m2[, γ]]; nehari = false, fast = true, offset = β,
             atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, mindist)

uses the compound descriptor system `sys = (A-λE,[B1 B2],C,[D1 D2])`,
where `B2` has `m2` columns, to define
the descriptor systems `sys1 = (A-λE,B1,C,D1)` and `sys2 = (A-λE,B2,C,D2)`
(i.e., `A1-λE1 = A2-λE2 = A-λE` and `C1 = C2 = C`).
If `m2 = 0`, an 1-block `LDP` is solved.
`sys2` must not have poles on the boundary of the stability domain `Cs`.

If `nehari = true`, the optimal or suboptimal Nehari approximation is used to solve the `LDP`.
If `nehari = false` (default), the optimal solution is computed using the `γ`-iteration [1].

The keyword argument `reltol` specifies the relative tolerance for the desired accuracy of `γ`-iteration.
The iterations are performed until the current estimations of maximum ``γ_u`` and minimum ``γ_l`` of
the optimal distance ``γ_o``, ``γ_l \\leq γ_o \\leq γ_u``, satisfies

```math
     γ_u-γ_l \\leq \\text{reltol} * \\text{gap} ,
```
where `gap` is the original gap (internally determined).

To assess the presence of poles on the boundary of the stability domain `Cs`, a boundary offset  `β`
can be specified via the keyword parameter `offset = β`.
Accordingly, for a continuous-time setting,
the boundary of `Cs` contains the complex numbers with real parts within the interval `[-β,β]`,
while for a discete-time setting, the boundary of `Cs` contains
the complex numbers with moduli within the interval `[1-β,1+β]`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, the absolute tolerance for the
nonzero elements of `A`, the absolute tolerance for the nonzero elements of `E`,  and the relative tolerance
for the nonzero elements of `A` and `E`. The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol` and `atol2 = atol`.

The rank decisions in the underlying pencil manipulation algorithms are
based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

_Method:_ The approach of [1] is used for the solution of the 2-block least distance problem.
Necessary conditions for solvability of the LDP with a stable solution is that `sys1` and `sys2` have no poles
on the boundary of the stability domain `Cs`.

_References:_
[1] C.-C. Chu, J. C. Doyle, and E. B. Lee
    The general distance problem in H∞  optimal control theory,
    Int. J. Control, vol 44, pp. 565-596, 1986.
"""
function glinfldp(sys1::DescriptorStateSpace{T1}, sys2::DescriptorStateSpace{T2},
                  γ::Union{Real,Missing} = missing; nehari::Bool = false, offset::Real = sqrt(eps(float(real(T1)))),
                  atol::Real = zero(real(T1)), atol1::Real = atol, atol2::Real = atol,
                  rtol::Real = (sys1.nx*eps(real(float(one(T1)))))*iszero(max(atol1,atol2)),
                  reltol::Real = 0.0001, fast::Bool = true) where {T1,T2}
   size(sys1,1) == size(sys2,1) || throw(DimensionMismatch("sys1 and sys2 must have the same number of outputs"))
   return glinfldp(gir([sys1 sys2]; fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol), size(sys2,2),γ;
                   nehari = nehari, offset = offset, reltol = reltol, fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol)
end
function glinfldp(sys::DescriptorStateSpace{T}, m2::Int, γ::Union{Real,Missing} = missing;  nehari::Bool = false,
                  offset::Real = sqrt(eps(float(real(T)))), reltol::Real = 0.0001,
                  atol::Real = zero(real(T)), atol1::Real = atol, atol2::Real = atol,
                  rtol::Real = (sys.nx*eps(real(float(one(T)))))*iszero(max(atol1,atol2)),
                  fast::Bool = true) where T

   T1 = T <: BlasFloat ? T : promote_type(Float64,T)
   p, m = size(sys);
   (m2 <= m && m2 >= 0) || throw(DimensionMismatch("m2 must be at most $m, got $m2"))
   m1 = m-m2
   tol = sqrt(eps(real(float(one(T1)))))
   disc = (sys.Ts != 0)

   # set stability margin to separate stable and unstable parts
   smarg = disc ? 1+offset : offset

   if nehari
      # compute a suboptimal solution as a Nehari approximation of G1
      sysx, s1 = gnehari(sys[:,1:m1], γ; offset = -offset, atol1, atol2, rtol, fast)
      mindist = glinfnorm(gir(sys-sysx*eye(m1,m); atol1, atol2, rtol, fast); offset, atol1, atol2, rtol)[1]
      isinf(mindist) && error("sys2 has poles on the boundary of stability domain")
      return sysx, mindist
   end

   subopt = !ismissing(γ)
   gl1 = m2 > 0 ? (glinfnorm(sys[:,m1+1:end]; offset, atol1, atol2, rtol)[1]) : 0 # prevent failure of opnorm
   isinf(gl1) && error("sys2 has poles on the boundary of stability domain")

   # address constant case
   if order(sys) == 0 || m1 == 0
      # solve a 2-block Parrott problem
      # handle opnorm bug for 1 x 0 matrices
      return sys[:,1:m1], m1 == m ? 0 : opnorm(sys.D[:,m1+1:end])
   end

   subopt && gl1 > γ && error("γ must be chosen greater than $gl1")
   if disc
      hlu = ghanorm(gsdec(sys[:,1:m1]; job = "unstable", smarg, atol1, atol2, rtol, fast)[1]')[1]
   else
      hlu = ghanorm(gsdec(gsdec(sys[:,1:m1]; job = "finite", atol1, atol2, rtol, fast)[1]; job = "unstable", smarg, atol1, atol2, rtol, fast)[1]')[1]
   end
   if m2 == 0 || hlu <= atol || gl1 <= tol*hlu
      # solve one block LDP
      if subopt
         # solve sub-optimal Nehari problem
         sysx,  = gnehari(sys[:,1:m1], γ; offset, atol1, atol2, rtol, fast)
         mindist = glinfnorm(sys[:,1:m1]-sysx; offset, atol1, atol2, rtol)[1]
      else
        # solve optimal Nehari problem
        sysx, mindist = gnehari(sys[:,1:m1]; offset = -offset, atol1, atol2, rtol, fast)
      end
   else
      # solve 2-block LDP
      if subopt
         # solve sub-optimal LDP
         gam = max(γ,gl1*1.01);
         V = glsfg(sys[:,m1+1:end], gam; stabilize = true, offset, atol1, atol2, rtol, fast)
         syse = V\sys[:,1:m1]
         hlu = ghanorm(gsdec(syse; job = "unstable", smarg, atol1, atol2, rtol, fast)[1]')[1];
         # solve the sub-optimal Nehari problem
         if hlu < 1
            sysx =  gbalmr(V*gnehari(syse, hlu; offset = -offset, atol1, atol2, rtol, fast)[1];
                           offset, atol1, atol2, rtol, fast)[1];
         else
            subopt = false;
         end
         mindist = glinfnorm(sys-sysx*eye(m1,m); offset, atol1, atol2, rtol)[1]
      end
      if !subopt
         # solve optimal LDP using gamma-iteration
         # initialize the gamma-iteration
         # compute upper bound and inital gap
         # hlu = ghanorm(gsdec(sys[:,1:m1], job = "unstable", atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast)[1]')[1];
         gu = hypot(hlu,gl1); gl = max(gl1,hlu)
         gap = max(1,gu-gl);
         gam = (gl+gu)/2;
         # stabilize G2 only once
         G2s = grcfid(sys[:,m1+1:end], mininf = true, fast = fast, offset = offset,
                      atol1 = atol1, atol2 = atol2, atol3 = atol1, rtol = rtol)[1]
         # perform the gamma-iteration
         iter = 0; iterl = 0; g0 = gam; hlu = 1;
         while gu-gl > reltol*gap && iter <= 50
            iter += 1
            V = glsfg(G2s, gam, stabilize = false, fast = fast, offset = offset,
                      atol1 = atol1, atol2 = atol1, rtol = rtol)
            # fail = false
            # V = try
            #    V = glsfg(G2s, gam, stabilize = false, fast = fast, offset = offset,
            #           atol1 = atol1, atol2 = atol1, rtol = rtol)
            #    g0 = gam
            #    fail = false
            #    V
            # catch
            #    if iter > 1
            #       gam = g0
            #       V = glsfg(G2s, gam, stabilize = false, fast = fast, offset = offset,
            #                 atol1 = atol1, atol2 = atol1, rtol = rtol)
            #       fail = true
            #       @warn "Desired accuracy of γ-iteration not achievable"
            #    else
            #       error("Failure of the minimum-phase spectral factorization")
            #    end
            # end

            # compute inv(V)*G1
            syse = gminreal(V\sys[:,1:m1],atol=1.e-7)
            hlu = ghanorm(gsdec(syse; job = "unstable", smarg, atol1, atol2, rtol, fast)[1]')[1];
            #fail && break
            if hlu < 1
               gu = gam;           # gamma > gamma_opt
            else
               gl = gam;           # gamma <= gamma_opt
               iterl += 1
            end
            gam = (gl+gu)/2;
         end
         if iter > 0
            if hlu >= 1
              # recompute spectral factorization if gamma < gamma_opt
              V = glsfg(G2s, gu, stabilize = false)
              # compute inv(V)*G1
              syse = V\sys[:,1:m1]
              gam = gu
            end
         else
            # compute spectral factorization for increased gamma
            gam = min(gu, gam*(1+reltol))
            V = glsfg(G2s, gam, stabilize = false, offset = offset,
                     atol1 = atol1, atol2 = atol1, rtol = rtol)
            # compute inv(V)*G1
            syse = V\sys[:,1:m1]
         end
         if iterl == 0 && gam - gl < reltol*gl1
            # handle the case gam = norm(G2)
            @warn "Optimal LDP solution has poles near to the boundary of stability domain"
            # set offset to machine precision
            offset = eps(float(real(T1)))
         end

         # solve the Nehari problem for the unstable part and compute solution
         # t1, t2 = gsdec(syse, job = "unstable", fast = fast, atol1 = atol1, atol2 = atol2, rtol = rtol)
         # tt1 = gnehari(t1; offset, atol1, atol2, rtol, fast)[1]
         # sysx = gminreal(V*(tt1+t2); atol1, atol2, rtol, fast)
         # sysx = gminreal(V*gnehari(gminreal(syse,atol=1.e-7), offset = offset, atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast)[1],
         #                atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast);
         sysx = gminreal(V*gnehari(syse; offset = -offset, atol1, atol2, rtol, fast)[1]; atol1, atol2, rtol, fast)
         mindist = glinfnorm(sys-sysx*eye(m1,m), offset = offset, atol1 = atol1, atol2 = atol2, rtol = rtol)[1]

      end
   end
   return sysx, mindist
   # end of GLINFLDP
end
glinfldp(sys::DescriptorStateSpace{T}, γ::Union{Real,Missing} = missing;  kwargs...) where T =
         glinfldp(sys, 0, γ; kwargs...)
"""
    gnehari(sys[, γ]; fast = true, offset = β,
                    atol = 0, atol1 = atol, atol2 = atol, rtol = n*ϵ) -> (sysx, σ1)

Compute for the descriptor system `sys = (A-λE,B,C,D)` with the transfer function matrix `G(λ)`
the optimal or suboptimal Nehari approximation `sysx = (Ax-λEx,Bx,Cx,Dx)`
with the transfer function matrix `X(λ)`. The optimal Nehari approximation  `X(λ)` satisfies

```math
     \\| G(\\lambda) - X(\\lambda) \\|_\\infty = \\| G^{*}_u(\\lambda) \\|_H := \\sigma_1,
```

where ``{\\small G_u(\\lambda)}`` is the proper antistable part of `G(λ)`.
The resulting ``σ_1`` is the Hankel-norm of ``{\\small G^{*}_u(\\lambda)}``
(also the L∞-norm of the optimal approximation error).
For a given ``γ > σ_1``, the suboptimal approximation satisfies

```math
     \\| G(\\lambda) - X(\\lambda) \\|_\\infty \\leq \\gamma .
```
The Nehari approximation is stable if  `G(λ)` has no poles on the boundary of the stability domain
`Cs`. In the continuous-time, `Cs` is the set of complex numbers with negative real parts and its boundary
is the extended imaginary axis (containig also the poit at infinity),
while, in the discrete-time case, `Cs` is the set complex number with moduli less than one and its boundary
is the unit circle centered in the origin.

To assess the presence of poles in `Cs`, a boundary offset  `β`
can be specified via the keyword parameter `offset = β`.
Accordingly, for a continuous-time system, the stable poles in
`Cs` have real parts less than or equal to `β`,
while for a discrete-time system, they have moduli less than or equal to 1+β`.
The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision.
For a negative values of `β` (e.g., `β = -sqrt(ϵ)`), an extended stability domain corresponding to the closure of `Cs`
is used instead of `Cs`.

The keyword arguments `atol1`, `atol2`, and `rtol`, specify, respectively, the absolute tolerance for the
nonzero elements of `A`, the absolute tolerance for the nonzero elements of `E`,  and the relative tolerance
for the nonzero elements of `A` and `E`. The default relative tolerance is `n*ϵ`, where `ϵ` is the working machine epsilon
and `n` is the order of the system `sys`. The keyword argument `atol` can be used
to simultaneously set `atol1 = atol` and `atol2 = atol`.

The separation of the finite and infinite eigenvalues is performed using
rank decisions based on rank revealing QR-decompositions with column pivoting
if `fast = true` or the more reliable SVD-decompositions if `fast = false`.

_Method:_ Let `G(λ) = Gs(λ) + Gu(λ)` be an additive decomposition of `G(λ)`
such that `Gs(λ)` has only poles in the closure of `Cs` (or its closure if `β < 0`)
and `Gu(λ)` is the antistable part having only
unstable poles.   The Hankel-norm approximation methods of [1] and [2],
with extensions for descriptor systems, are used to compute a stable Nehari approximation `Gn(λ)`
of the unstable part `Gu(λ)` and the resulting solution is computed as `X(λ) = Gs(λ) + Gn(λ)`.

_References:_

[1] K. Glover. All optimal Hankel-norm approximations of linear
       multivariable systems and their L∞ error bounds,
       Int. J. Control, vol. 39, pp. 1115-1193, 1984.

[2] M. G. Safonov, R. Y. Chiang, and D. J. N. Limebeer.
       Optimal Hankel model reduction for nonminimal systems.
       IEEE Trans. Automat. Control, vol. 35, pp. 496–502, 1990.
"""
function gnehari(sys::DescriptorStateSpace{T}, γ::Union{Real,Missing} = missing;
                 offset::Real = sqrt(eps(float(real(T)))), atol::Real = zero(float(real(T))),
                 atol1::Real = atol, atol2::Real = atol, atol3::Real = atol,
                 rtol::Real = sys.nx*eps(real(float(one(T))))*iszero(max(atol1,atol2,atol3)),
                 fast::Bool = true) where T
   sys.nx == 0 && (return sys, 0)
   subopt_flg = !ismissing(γ)

   ONE = one(T)
   disc = (sys.Ts != 0)

   # set stability margin to separate stable and unstable parts
   smarg = disc ? 1-offset : -offset

   # Stable/antistable separation; possible eigenvalues on the boundary of
   # the stability domain are included in the unstable part
   #sysx, sysu = gsdec(sys; job = "stable", smarg = smarg, fast = fast,  atol1 = atol1, atol2 = atol2, rtol = rtol)
   if disc
      sysu, sysx = gsdec(sys; job = "unstable", smarg, fast, atol1, atol2, rtol)
   else
      tfin, t1 = gsdec(sys; job = "finite", fast, atol1, atol2, rtol)
      sysu, t2 = gsdec(tfin; job = "unstable", smarg, fast, atol1, atol2, rtol)
      sysx = t1+t2
      sysx.D[:,:] = sysx.D + sysu.D
      sysu.D[:,:] = zeros(T, sys.ny, sys.nu)
   end
   s2eps = sqrt(eps(real(float(one(T)))))
   # only proper systems are handled in the continuous-time case
   # if !disc && sysu.E != I && (norm(sysu.E) < atol2 || rcond(sysu.E) < s2eps)
   #    sysu = gss2ss(sysu, atol2 = s2eps)[1]
   #    if sysu.E != I && rcond(sysu.E) < s2eps
   #        error("gnehari: improper continuous-time system sys")
   #    else
   #        sysx.D[:,:] = sysx.D + sysu.D
   #        sysu.D[:,:] = zeros(T, sys.ny, sys.nu)
   #    end
   # end

   if offset > 0
      #e == I ? (isqtriu(a) ? ev = ordeigvals(a) : ev = eigvals(a)) : ev = ordeigvals(a,e)[1]
      sysx.E == I ? (isqtriu(sysx.A) ? ev = ordeigvals(sysx.A) : ev = eigvals(sysx.A)) : ev = ordeigvals(sysx.A,sysx.E)[1]
      if disc
         any(abs.(ev) .>= 1-offset) &&
             error("gnehari: the system has possibly poles on the unit circle")
      else
         any(isinf.(ev)) && error("gnehari: the system is possibly improper")
         any(real.(ev) .>= -offset) &&
             error("gnehari: the system has possibly poles on the imaginary axis")
      end
   end

   # exit if the system has only stable part
   order(sysu) == 0 && (return sysx, zero(float(real(T))))

   a, e, b, c, d = dssdata(sysu)

   if disc
      # use the bilinear transformation to compute an equivalent unstable,
      # possibly non-minimal, continuous-time system
      sqrt2 = sqrt(2)
      esave = copy(e)
      e += a; a -= esave
      c = c/e
      mul!(d,c,b,-ONE,ONE)
      lmul!(sqrt2,b); c = (sqrt2*c)*esave;
   end
   # compute a balanced minimal realization of antistable part and the
   # Hankel singular values
   sysr, hs = gbalmr(dss(-a,e,-b,c,d), balance = true, atolhsv = s2eps, atol1 = atol1, atol2 = atol2, rtol = rtol, fast = fast)
   a, e, b, c, d = dssdata(sysr)

   na = size(a,1)
   p, m = size(d)

   s1 = hs[1];  # expected L-inf norm of the optimal approximation error

   # Determine the type of required approximation
   if subopt_flg
      if abs(γ-s1) <= s1*s2eps
         subopt_flg = false; # enforce optimal approximation
      elseif γ < s1
        error("Suboptimal antistable Hankel approximation cannot be computed")
      end
   else
      γ = s1
   end

   if subopt_flg
      sk = γ; r = 0
   else
      sk = s1; ep = s1*s2eps;
      if na > 0
         r = 1;
         for i = 2:na
             abs(sk-hs[i]) > ep && break
             r += 1;
         end
      else
         r = 0;
      end
   end
   #          ^
   #  Compute F(s)
   ns = na-r; sk2 = sk*sk; i1 = r+1:na;
   # make system square by padding with zeros rows or column
   Tw = eltype(a)
   pm = max(p,m)
   if m < p
       b = [b zeros(Tw,na,p-m)]
   elseif m > p
       c = [c; zeros(Tw,m-p,na)]
   end
   if subopt_flg
      u = zeros(Tw,pm,pm);
      b1 = b; c1 = c;
   else
      i2 = 1:r;
      SV1 = svd(view(b,i2,:),full=true); # b[i2,:] = U1*Σ1*V1'
      SV2 = svd(view(c,:,i2)*SV1.U,full=true)
      #sig = [diag(SV2.Vt) .< 0; falses(pm-r)]
      sig = [real.(diag(SV2.Vt)) .< 0; falses(pm-r)]
      SV2.U[:,sig] = -SV2.U[:,sig]
      u = -SV2.U*SV1.Vt
      b1 = b[i1,:]; c1 = c[:,i1]
   end
   jm = 1:m; jp = 1:p;
   if ns > 0
      # optimal solution
      hsd = Diagonal(view(hs,i1))
      e = Matrix(hsd*hsd - sk2*I)
      a = -(sk2*a[i1,i1]' + hsd*a[i1,i1]*hsd - sk*c1'*u*b1')
      b = -(hsd*b1[:,jm] + sk*c1'*u[:,jm])
      c = c1[jp,:]*hsd + sk*u[jp,:]*b1'
   else
      a = zeros(Tw,0,0); e = I; b = zeros(Tw,0,m); c = zeros(Tw,p,0);
   end
   if na > 0
      d -= sk*u[jp,jm]
   end

   if disc
      # compute the equivalent discrete-time solution
      esave = copy(e)
      e -= a; a += esave
      c = c/e
      mul!(d,c,b,ONE,ONE)
      lmul!(sqrt2,b); c = sqrt2*c*esave;
   end

   return sysx + dss(a, e, b, c, d, Ts = sys.Ts), s1

   # end GNEHARI
end


"""
    rmgap(X; indexcol, k = 5)
    rmgap!(X; indexcol, k = 5)
Remove vertical gaps in spectra (rows of matrix `X`), e.g. for ASD.
* `X` : X-data.
* `indexcol` : The indexes of the columns where are located the gaps.
* `k` : The number of columns used on the left side
        of the gaps for fitting the linear regressions.

The correction is done by extrapolation from simple linear regressions
computed on the left side of the gaps.

For instance, If two gaps are observed between indexes 651-652 and
between indexes 1425-1426, respectively, then the syntax should
be `indexcol = [651 ; 1425]`.
"""
function rmgap(X; indexcol, k = 5)
    rmgap!(copy(X); indexcol, k)
end

function rmgap!(X; indexcol, k = 5)
    X = ensure_mat(X)
    size(X, 2) == 1 ? X = reshape(X, 1, :) : nothing
    p = size(X, 2)
    k = max(k, 2)
    ngap = length(indexcol)
    @inbounds for i = 1:ngap
        ind = indexcol[i]
        wl = max(ind - k + 1, 1):ind
        fm = mlr(Float64.(wl), X[:, wl]')
        pred = predict(fm, ind + 1).pred
        bias = X[:, ind + 1] .- pred'
        X[:, (ind + 1):p] .= X[:, (ind + 1):p] .- bias
    end
    X
end


# using Dates
# using DelimitedFiles
# using CSV
# using Plots
using Dates

#calculates the potential Evaporation based on the equation by Hargreaves-Samani

function epot_hargreaves(Temp_min::Float64, Temp::Float64, Temp_max::Float64, KT::Float64, Radiation::Float64)
    #Radiation in mm/day
    # KT 0.162 for interior regions where land mass dominates, and
    # 0.190 for coastal regions, where air masses are influenced by a nearby water body
    # 0.17 for Salt Lake City
    Rs = KT * ((Temp_max - Temp_min)^0.5) * Radiation #global solar radiation in mm/day
    Epot = 0.0135 * Rs * (Temp + 17.8)  #potential Evaporation in mm/day
    #Epot = 0.0135 * Rs * ((Temp_max + Temp_min)/2 + 17.8)
    return Epot, Rs

end

function radiation(Latitude::Float64, Day::Int)
    # formula derived from Crop evapotranspiration-Guidelines for computing crop water requirements- FAO Irrigation and drainage paper 56
    # Latitude should be in the form 13°44' = 13+44/66
    #Latitude = floor(Latitude) + mod(Latitude,1) * 10 /60
    Solar_Constant = 0.0820 #MJ/m2/min
    Distance_Earth_Sun = 1 + 0.033 * cos(2 * pi / 365 * Day)
    Latitude = Latitude * pi / 180  #to convert degree to rad
    Solar_Declination = 0.409 * sin((2 * pi/ 365) * Day -1.39) # in rad
    Sunsethour_angle = acos(-tan(Latitude) * tan(Solar_Declination)) # in rad
    Radiation = 24 * 60 / pi * Solar_Constant * Distance_Earth_Sun * (Sunsethour_angle * sin(Latitude) * sin(Solar_Declination) + cos(Latitude) * cos(Solar_Declination) * sin(Sunsethour_angle))
    Radiation = 0.408 * Radiation #to convert it from MJ/m2/day to mm/day
    return Radiation::Float64
end

function getEpot(Temp_min::Array{Float64, 1}, Temp::Array{Float64, 1}, Temp_max::Array{Float64, 1}, KT::Float64, Timeseries::Array{Date, 1}, Latitude::Float64)
    Evaporation = Float64[]
    Radiation = Float64[]
    for (t, current_date) in enumerate(Timeseries)
        #current_date = Date(current_date, dateformat"y,m,d")
        Day = Dates.dayofyear(current_date)
        Current_Radiation = radiation(Latitude, Day)
        Current_Evaporation, Current_Rs = epot_hargreaves(Temp_min[t], Temp[t], Temp_max[t], KT, Current_Radiation)
        push!(Evaporation, Current_Evaporation)
        push!(Radiation, Current_Radiation)
    end
    return Evaporation::Array{Float64,1}, Radiation::Array{Float64,1}
end

# data from https://www.worlddata.info/europe/austria/sunset.php

# Thornthwaite Method
# the monthly mean temperature needs to be calculated
function monthlytemp(Timeseries, Temp::Array{Float64, 1})
    #calculates the mean monthly temperatures of months in the timeseries
    @assert length(Timeseries[:,1]) == length(Temp)
    sum_Temp = 0
    monthly_Temp = Float64[]
    for (i, current_date) in enumerate(Timeseries)
        sum_Temp += Temp[i]
        #current_date = Date(current_date, dateformat"y,m,d")
        day = Dates.day(current_date)
        if day == Dates.daysinmonth(current_date)
            monthly_Temp_current = sum_Temp / Dates.daysinmonth(current_date)
            push!(monthly_Temp, monthly_Temp_current)
            sum_Temp = 0
        end
    end
    #print(length(monthly_Temp))
    return monthly_Temp::Array{Float64, 1}
end

function heatindex(monthly_Temp::Array{Float64, 1})
    #calculates the annual heat index
    @assert mod(length(monthly_Temp), 12) == 0
    Heatindex = Float64[]
    sum = 0
    for (index, current_Temp) in enumerate(monthly_Temp)
        I = Complex(current_Temp/ 5).^1.514
        I = real.(I)
        @assert imag.(I) == 0im
        sum += I
        #at the end of each year the annual heat index is given as an output
        if index > 2 && mod(index,12)  == 0
            push!(Heatindex, sum)
            sum = 0
        end
    end
    return Heatindex::Array{Float64, 1}
end

function epot_thornthwaite(Temp_daily::Float64, Heatindex::Float64, daysinmonth::Int64, sunhours::Float64)
    #calculates the potential evaporation by Thornthwaite method
    # Thornthwaite method assumes no evaporation below zero degrees (COMPUTER PROGRAM FOR ESTIMATING EVAPOTRANSPIRATION USING THE THORNTHWAITE METHOD)
    a = 675 * 10^(-9) * Heatindex^3 - 771 * 10^(-7) * Heatindex^2 + 1792 * 10^(-5) * Heatindex + 0.49239
    if Temp_daily < 0
        Temp_daily = 0
    end
    E_gross = 16 * (10 * Temp_daily / Heatindex)^a
    Epot = E_gross * (sunhours * daysinmonth / 360)
    Epot_daily = Epot/daysinmonth
    # if 16 * (10 * Temp_daily / Heatindex) <= 0
    #     Epot_daily = 0
    # else
    #     E_gross = 16 * (10 * Temp_daily / Heatindex)^a
    #     Epot = E_gross * (sunhours * daysinmonth / 360)
    #     Epot_daily = Epot/daysinmonth
    # end
    return Epot_daily
end

function getEpot_thornthwaite(Temp::Array{Float64, 1}, Timeseries::Array{Date, 1}, sunhours::Array{Float64, 1})
    # assertion for that the timeseries contain whole years
    @assert length(Timeseries) == length(Temp)
    Evaporation = Float64[]
    # calculate the mean monthly temperatures of the timeseries
    Temp_month = monthlytemp(Timeseries, Temp)
    #calculate the annual heat indices of the timeseries
    annual_heatindex = heatindex(Temp_month)
    first_year = Dates.year(Timeseries[1])
    for (i, current_date) in enumerate(Timeseries)
        #current_date = Date(current_date, dateformat"y,m,d")
        #get the number of days of the current month
        daysinmonth = Dates.daysinmonth(current_date)
        #print(daysinmonth)
        datetuple = Dates.yearmonthday(current_date)
        #print(datetuple[2])
        #get the sunhours of the current month
        sunhours_current = sunhours[datetuple[2]]
        # get the monthly evaporation for current month in the timeseries
        j = 12 * abs(datetuple[1]- first_year) + datetuple[2]
        Temp_monthly_current = Temp_month[j]
        # get the annual heat index of the current year
        current_annual_heatindex = annual_heatindex[abs(datetuple[1]-first_year) + 1]
        Epot = epot_thornthwaite(Temp_monthly_current, current_annual_heatindex, daysinmonth, sunhours_current)
        #Epot = Epot/daysinmonth
        push!(Evaporation, Epot)
    end
    return Evaporation::Array{Float64,1}
end

function getEpot_Daily_thornthwaite(Temp::Array{Float64, 1}, Timeseries::Array{Date, 1}, sunhours::Array{Float64, 1})
    # assertion for that the timeseries contain whole years
    @assert length(Timeseries) == length(Temp)
    Evaporation = Float64[]
    #calculate the annual heat indices of the timeseries
    Temp_month = monthlytemp(Timeseries, Temp)
    annual_heatindex = heatindex(Temp_month)
    first_year = Dates.year(Timeseries[1])
    for (i, current_date) in enumerate(Timeseries)
        #current_date = Date(current_date, dateformat"y,m,d")
        #get the number of days of the current month
        daysinmonth = Dates.daysinmonth(current_date)
        #print(daysinmonth)
        datetuple = Dates.yearmonthday(current_date)
        #print(datetuple[2])
        #get the sunhours of the current month
        sunhours_current = sunhours[datetuple[2]]
        # get the annual heat index of the current year
        current_annual_heatindex = annual_heatindex[abs(datetuple[1]-first_year) + 1]
        Epot = epot_thornthwaite(Temp[i], current_annual_heatindex, daysinmonth, sunhours_current)
        #Epot = Epot/daysinmonth
        push!(Evaporation, Epot)
    end
    return Evaporation::Array{Float64,1}
end


using vOptSpecific

n  =  3
C1 = [ 3  9  7 ; 16 10  6 ; 2  7 11]
C2 = [16 15  6 ;  5  7 13 ; 1  2 13]

myInstance = set2LAP( n , C1 , C2 )
algorithm = LAP_Przybylski2008( )
z1, z2, S = vSolve( myInstance , algorithm )

id = set2LAP(n, C1, C2)
z1,z2,solutions = vSolve(id)

for i = 1:length(z1)
    println("(" , z1[i], " | ", z2[i], ") : ", solutions[i,:])
end


"""
    RobustMeans

Simple package implementing some Robust mean estimator.
"""
module RobustMeans

using Statistics
using StatsBase: harmmean
using Roots

import Statistics: mean

abstract type MeanEstimator end
abstract type RobustMean <: MeanEstimator end

include("means.jl")
include("mean_delta.jl")
# include("delta_mean.jl")
include("bounds.jl")

export MeanEstimator, RobustMean
export EmpiricalMean
export MedianOfMean, TrimmedMean
export Z_Estimator, Catoni, Huber
export LeeValiant, MinskerNdaoud
export bound

end


"""
    abstract type to include all layers
"""
abstract type Layer end

export Layer

### FCLayer

@doc raw"""
    FCLayer(channels=0, actFun=:noAct, [layerInput = nothing; keepProb = 1.0])

Fully-connected layer (equivalent to Dense in TensorFlow etc.)

# Arguments

- `channels` := (`Integer`) is the number of nodes in the layer

- `actFun` := (`Symbol`) is the activation function of this layer

- `layerInput` := (`Layer` or `Array`) the input of this array (optional don't need to assign it)

- `keepProb` := (`AbstractFloat`) the keep probability (1 - prob of the dropout rate)

---------

# Summary

    mutable struct FCLayer <: Layer

-------

# Fields

- `channels::Integer` := is the number of nodes in the layer

- `actFun::Symbol` := the activation function of this layer

- `inputS::Tuple{Integer, Integer}` := input size of the layer, of the shape (channels of the previous layer, size of mini-batch)

- `outputS::Tuple{Integer, Integer}` := output size of the layer, of the shape (channels of this layer, size of mini-batch)

- `keepProb::AbstractFloat` := the keep probability (rate) of the drop-out operation `<1.0`

- `W::Array{T,2} where {T}` := the scaling parameters of this layer `W * X`, of the shape (channels of this layer, channels of the previous layer)

- `B::Array{T,2} where {T}` := the bias of this layer `W * X .+ B`, of the shape (channels of this layer, 1)

- `dW::Array{T,2} where {T}` := the derivative of the loss function to the W parameters $\frac{dJ}{dW}$

- `dB::Array{T,2} where {T}` := the derivative of the loss function to the B parameters $\frac{dJ}{dB}$

- `forwCount::Integer` := forward propagation counter

- `backCount::Integer` := backward propagation counter

- `updateCount::Integer` := update parameters counter

- `prevLayer::L where {L<:Union{Layer,Nothing}}` := the previous layer which is
the input of this layer

- `nextLayers::Array{Layer,1}` := An array of the next `layer`(s)

---------

# Supertype Hierarchy

    FCLayer <: Layer <: Any

---------

# Examples

```julia
X_Input = Input(X_train)
X = FCLayer(20, :relu)(X_Input)

```

In the previous example the variable `X_Input` is a pointer to the `Input` layer, and `X` is an pointer to the `FCLayer(20, :relu)` layer.
**Note** that the layer instance can be used as a connecting function.

"""
mutable struct FCLayer <: Layer
    channels::Integer
    actFun::Symbol

    inputS::Tuple{Integer, Integer}
    outputS::Tuple{Integer, Integer}

    """
        drop-out keeping node probability
    """
    keepProb::AbstractFloat
    W::Array{T,2} where {T}
    B::Array{T,2} where {T}
    dW::Array{T,2} where {T}
    dB::Array{T,2} where {T}
    # ### adding Z & A place holder for recursive calling
    # ### and a counter for how many it was called
    # Z::Array{T,2} where {T}
    # dA::Array{T,2} where {T}
    # A::Array{T,2} where {T}

    V::Dict{Symbol,Array{T,2} where {T,N}}
    S::Dict{Symbol,Array{T,2} where {T,N}}

    forwCount::Integer
    backCount::Integer
    updateCount::Integer
    """
        pointer to previous layer
    """
    prevLayer::L where {L<:Union{Layer,Nothing}}
    nextLayers::Array{Layer,1}
    function FCLayer(channels=0, actFun=:noAct, layerInput = nothing; keepProb = 1.0)
        # W, B
        if isa(layerInput, Layer)
            T = eltype(layerInput.W)
            nl = channels
            nl_1 = size(layerInput.W)[1]
            prevLayer = layerInput
        elseif isa(layerInput, Array)
            T = eltype(layerInput)
            nl = channels
            nl_1 = size(layerInput)[1]
            prevLayer = nothing
        else
            T = Any
            nl = 0
            nl_1 = 0
            prevLayer = nothing
        end
        new(
            channels,
            actFun,
            (0,0), #inputS
            (0,0), #outputS
            keepProb,
            Matrix{T}(undef, 0, 0),
            Matrix{T}(undef, 0, 0),
            Matrix{T}(undef, 0, 0),
            Matrix{T}(undef, 0, 0),
            # Matrix{T}(undef, 0, 0),
            # Matrix{T}(undef, 0, 0),
            # Matrix{T}(undef, 0, 0),
            Dict(:dw => Matrix{T}(undef, 0, 0), :db => Matrix{T}(undef, 0, 0)),
            Dict(:dw => Matrix{T}(undef, 0, 0), :db => Matrix{T}(undef, 0, 0)),
            0,
            0,
            0,
            prevLayer,
            Array{Layer,1}(undef,0)
        )#
    end #FCLayer
end #struct FCLayer

Base.show(io::IO, l::FCLayer) = print(io, "FCLayer($(l.channels), $(l.actFun), $(l.inputS), $(l.outputS), ...)")

export FCLayer

### Mulit-Input Layer MILayer

export MILayer

abstract type MILayer <: Layer end

### AddLayer

@doc raw"""
    AddLayer(; [channels = 0])

Layer performs and addition of multiple previous layers

# Arguments

- `channels` := (`Integer`) number of channels/nodes of this array which equals to the same of the previous layer(s)

---------

# Summary

    mutable struct AddLayer <: MILayer

# Fields

- `channels::Integer` := is the number of nodes or `channels` in the layer

- `inputS::Tuple` := input size of the layer

- `outputS::Tuple` := output size of the layer

- `forwCount::Integer` := forward propagation counter

- `backCount::Integer` := backward propagation counter

- `updateCount::Integer` := update parameters counter

- `nextLayers::Array{Layer,1}` := An array of the next `layer`(s)

- `prevLayer::Array{Layer,1}` := An array of the previous `layer`(s) to be added

---------

# Supertype Hierarchy

    AddLayer <: MILayer <: Layer <: Any

-------

# Examples

```julia
XIn1 = Input(X_train)
X1 = FCLayer(10, :relu)(XIn1)
XIn2 = Input(X_train)
X2 = FCLayer(10, :tanh)(XIn2)

Xa = AddLayer()([X1,X2])
```

"""
mutable struct AddLayer <: MILayer
    channels::Integer

    inputS::Tuple
    outputS::Tuple

    # A::Array{T,N} where {T,N}
    # dA::Array{T,N} where {T,N}

    forwCount::Integer
    backCount::Integer
    updateCount::Integer

    nextLayers::Array{Layer,1}
    prevLayer::Array{Layer,1}
    function AddLayer(; channels = 0)
        # channels = l1.channels
        # T = eltype(l1.W)
        new(
            channels,
            (0,), #inputS
            (0,), #outputS
            # Matrix{Nothing}(undef, 0, 0),
            # Matrix{Nothing}(undef, 0, 0),
            0,
            0,
            0,
            Array{Layer,1}(undef,0), #nextLayers
            Array{Layer,1}(undef,0), #prevLayer
            )
    end #function AddLayer
end

Base.show(io::IO, l::AddLayer) = print(io, "AddLayer($(l.channels), $(l.inputS), $(l.outputS), ...)")

export AddLayer

### ConcatLayer

export ConcatLayer

@doc raw"""
    ConcatLayer(; channels = 0)

Perform concatenation of group of previous `Layer`s

# Summary

    mutable struct ConcatLayer <: MILayer

# Fields

    channels    :: Integer
    inputS      :: Tuple
    outputS     :: Tuple
    forwCount   :: Integer
    backCount   :: Integer
    updateCount :: Integer
    nextLayers  :: Array{Layer,1}
    prevLayer   :: Array{Layer,1}

# Supertype Hierarchy

    ConcatLayer <: MILayer <: Layer <: Any
"""
mutable struct ConcatLayer <: MILayer
    channels::Integer

    inputS::Tuple
    outputS::Tuple

    # A::Array{T,N} where {T,N}
    # dA::Array{T,N} where {T,N}

    forwCount::Integer
    backCount::Integer
    updateCount::Integer

    nextLayers::Array{Layer,1}
    prevLayer::Array{Layer,1}

    LSlice::Dict{Layer,UnitRange{Integer}}
    function ConcatLayer(; channels = 0)
        # channels = l1.channels
        # T = eltype(l1.W)
        new(
            channels,
            (0,), #inputS
            (0,), #outputS
            # Matrix{Nothing}(undef, 0, 0),
            # Matrix{Nothing}(undef, 0, 0),
            0,
            0,
            0,
            Array{Layer,1}(undef,0), #nextLayers
            Array{Layer,1}(undef,0), #prevLayer
            Dict{Layer,UnitRange{Integer}}(),
            )
    end #function ConcatLayer
end #mutable struct ConcatLayer

Base.show(io::IO, l::ConcatLayer) = print(io, "ConcatLayer($(l.channels), $(l.inputS), $(l.outputS), ...)")

### Activation

@doc raw"""
    Activation(actFun)

# Arguments

- `actFun::Symbol` := the activation function of this layer

---------

# Summary

mutable struct Activation <: Layer

--------

# Fields

- `actFun::Symbol` := the activation function of this layer

- `channels::Integer` := is the number of nodes or `channels` in the layer

- `inputS::Tuple` := input size of the layer

- `outputS::Tuple` := output size of the layer

- `forwCount::Integer` := forward propagation counter

- `backCount::Integer` := backward propagation counter

- `updateCount::Integer` := update parameters counter

- `nextLayers::Array{Layer,1}` := An array of the next `layer`(s)

- `prevLayer::Array{Layer,1}` := An array of the previous `layer`(s) to be added

---------

# Supertype Hierarchy

    Activation <: Layer <: An

---------

# Examples

```julia
X_Input = Input(X_train)
X = FCLayer(10, :noAct)(X_Input)
X = Activation(:relu)(X)
```

"""
mutable struct Activation <: Layer
    actFun::Symbol
    channels::Integer

    inputS::Tuple
    outputS::Tuple

    forwCount::Integer
    backCount::Integer
    updateCount::Integer

    nextLayers::Array{Layer,1}
    prevLayer::L where {L<:Union{Layer,Nothing}}

    function Activation(actFun=:relu)
        new(
            actFun,
            0, #channels
            (0,), #inputS
            (0,), #outputS
            0,
            0,
            0,
            Array{Layer,1}(undef,0),
            nothing,
            )
    end #function Activation
end #mutable struct Activation

Base.show(io::IO, l::Activation) = print(io, "Activation($(l.channels), $(l.actFun), $(l.inputS), $(l.outputS), ...)")

export Activation

### Input

@doc raw"""
    Input(X_shape::Tuple)

`Input` `Layer` that is used as a pointer to the input array(s).

# Arguments

- `X_shape::Tuple` := shape of the input Array

-------

# Summary

    mutable struct Input <: Layer

# Fields

- `channels::Integer` := is the number of nodes or `channels` in the layer

- `inputS::Tuple` := input size of the layer

- `outputS::Tuple` := output size of the layer

- `forwCount::Integer` := forward propagation counter

- `backCount::Integer` := backward propagation counter

- `updateCount::Integer` := update parameters counter

- `nextLayers::Array{Layer,1}` := An array of the next `layer`(s)

- `prevLayer::Array{Layer,1}` := An array of the previous `layer`(s) to be added

------

# Supertype Hierarchy

    Input <: Layer <: Any

--------

# Examples

```julia
X_Input = Input(size(X_train))
X = FCLayer(10, :relu)(X_Input)
```

It is possible to use the Array instead of its size `NumNN` will take care of the rest

```julia
X_Input = Input(X_train)
X = FCLayer(10, :relu)(X_Input)
```

"""
mutable struct Input <: Layer
    channels::Integer

    inputS::Tuple
    outputS::Tuple

    forwCount::Integer
    backCount::Integer
    updateCount::Integer

    nextLayers::Array{Layer,1}
    prevLayer::L where {L<:Union{Layer,Nothing}}

    function Input(X_shape::Tuple)
        N = length(X_shape)
        if N==2
            channels = X_shape[1]
        elseif N==3
            channels = X_shape[2]
        elseif N==4
            channels = X_shape[3]
        elseif N==5
            channels = X_shape[4]
        end

        new(
            channels,
            X_shape, #inputS
            X_shape, #outputS
            0,
            0,
            0,
            Array{Layer,1}(undef,0),
            nothing,
            )
    end #function Layer
end #mutable struct Input

function Input(X::Array{T,N}) where {T,N}
    X_shape = size(X)
    Input(X_shape)
end #function Input(X::Array{T,N}) where {T,N}

Base.show(io::IO, l::Input) = print(io, "Input($(l.channels), $(l.inputS), $(l.outputS), ...)")

export Input

### BatchNorm

@doc raw"""
    BatchNorm(;dim=1, ϵ=1e-10)

Batch Normalization `Layer` that is used ot normalize across the dimensions specified by the argument `dim`.

# Arguments

- `dim::Integer` := is the dimension to normalize across

- `ϵ::AbstractFloat` := is a backup constant that is used to prevent from division on zero when ``σ^2`` is zero

---------

# Summary

    mutable struct BatchNorm <: Layer

-------

# Fields

- `channels::Integer` := is the number of nodes in the layer

- `inputS::Tuple{Integer, Integer}` := input size of the layer, of the shape (channels of the previous layer, size of mini-batch)

- `outputS::Tuple{Integer, Integer}` := output size of the layer, of the shape (channels of this layer, size of mini-batch)

- `dim::Integer` := the dimension to normalize across

- `ϵ::AbstractFloat` := backup constant to protect from dividing on zero when ``σ^2 = 0``

- `W::Array{T,2} where {T}` := the scaling parameters of this layer `W * X`, same shape of the mean `μ`

- `B::Array{T,2} where {T}` := the bias of this layer `W * X .+ B`, same shape of the variance ``σ^2``

- `dW::Array{T,2} where {T}` := the derivative of the loss function to the W parameters $\frac{dJ}{dW}$

- `dB::Array{T,2} where {T}` := the derivative of the loss function to the B parameters $\frac{dJ}{dB}$

- `forwCount::Integer` := forward propagation counter

- `backCount::Integer` := backward propagation counter

- `updateCount::Integer` := update parameters counter

- `prevLayer::L where {L<:Union{Layer,Nothing}}` := the previous layer which is
the input of this layer

- `nextLayers::Array{Layer,1}` := An array of the next `layer`(s)

---------

# Supertype Hierarchy

    BatchNorm <: Layer <: Any

---------

# Examples

```julia
X_train = rand(14,14,3,32) #input of shape `14×14` with channels of `3` and mini-batch size `32`

X_Input = Input(X_train)
X = Conv2D(10, (3,3))(X_Input)
X = BatchNorm(dim=3) #to normalize across the channels dimension
X = Activation(:relu)
```

```julia
X_train = rand(128,5,32) #input of shape `128` with channels of `5` and mini-batch size `32`

X_Input = Input(X_train)
X = Conv1D(10, 5)(X_Input)
X = BatchNorm(dim=2) #to normalize across the channels dimension
X = Activation(:relu)
``

```julia
X_train = rand(64*64,32) #input of shape `64*64` and mini-batch size `32`

X_Input = Input(X_train)
X = FCLayer(10, :noAct)(X_Input)
X = BatchNorm(dim=1) #to normalize across the features dimension
X = Activation(:relu)
````

"""
mutable struct BatchNorm <: Layer
    channels::Integer

    inputS::Tuple
    outputS::Tuple

    dim::Integer

    ϵ::AbstractFloat

    W::Array{T, N} where {T,N}
    dW::Array{T, N} where {T,N}

    B::Array{T, N} where {T,N}
    dB::Array{T, N} where {T,N}

    V::Dict{Symbol,Array}
    S::Dict{Symbol,Array}

    forwCount::Integer
    backCount::Integer
    updateCount::Integer

    nextLayers::Array{Layer,1}
    prevLayer::L where {L<:Union{Layer,Nothing}}

    function BatchNorm(;dim=1, ϵ=1e-7)
        new(
            0, #channels
            (0,), #inputS
            (0,), #outputS
            dim,
            ϵ,
            Array{Any,1}(undef,0), #W
            Array{Any,1}(undef,0), #dW
            Array{Any,1}(undef,0), #B
            Array{Any,1}(undef,0), #dB
            Dict(:dw=>Array{Any,1}(undef,0),
                 :db=>Array{Any,1}(undef,0)), #V
            Dict(:dw=>Array{Any,1}(undef,0),
                 :db=>Array{Any,1}(undef,0)), #S
            0, #forwCount
            0, #backCount
            0, #updateCount
            Array{Layer,1}(undef,0), #nextLayers
            nothing, #prevLayer
            )
    end #function BatchNorm

end #mutable struct BatchNorm

Base.show(io::IO, l::BatchNorm) = print(io, "BatchNorm($(l.channels), dim = $(l.dim), $(l.inputS), $(l.outputS), ...)")

export BatchNorm

### Flatten

export Flatten

@doc raw"""
    Flatten()

Flatten the input into 2D `Array`

# Summary

    mutable struct Flatten <: Layer

# Fields

    channels    :: Integer
    inputS      :: Tuple
    outputS     :: Tuple
    forwCount   :: Integer
    backCount   :: Integer
    updateCount :: Integer
    nextLayers  :: Array{Layer,1}
    prevLayer   :: Union{Nothing, Layer}

# Supertype Hierarchy

    Flatten <: Layer <: Any
"""
mutable struct Flatten <: Layer
    channels::Integer

    inputS::Tuple
    outputS::Tuple

    forwCount::Integer
    backCount::Integer
    updateCount::Integer

    nextLayers::Array{Layer,1}
    prevLayer::L where {L<:Union{Layer,Nothing}}

    function Flatten()

        return new(
            0, #channels
            (0,), #inputS
            (0,), #outputS
            0, #forwCount
            0, #backCount
            0, #updateCount
            Array{Layer,1}(undef,0), #nextLayers
            nothing,
        )
    end

end

Base.show(io::IO, l::Flatten) = print(io, "Flatten($(l.channels), $(l.inputS), $(l.outputS), ...)")

### Model

@doc raw"""

    function Model(
        X,
        Y,
        inLayer::Layer,
        outLayer::Layer,
        α;
        optimizer = :gds,
        β1 = 0.9,
        β2 = 0.999,
        ϵAdam = 1e-8,
        regulization = 0,
        λ = 1.0,
        lossFun = :categoricalCrossentropy,
        paramsDtype::DataType = Float64,
    )

# Summary

    mutable struct Model <: Any

# Fields

    inLayer      :: Layer
    outLayer     :: Layer
    lossFun      :: Symbol
    paramsDtype  :: DataType
    regulization :: Integer
    λ            :: AbstractFloat
    α            :: AbstractFloat
    optimizer    :: Symbol
    ϵAdam        :: AbstractFloat
    β1           :: AbstractFloat
    β2           :: AbstractFloat
"""
mutable struct Model
    # layers::Array{Layer,1}
    inLayer::Layer
    outLayer::Layer
    lossFun::Symbol

    paramsDtype::DataType

    """
        regulization type
            0 : means no regulization
            1 : L1 regulization
            2 : L2 regulization
    """
    regulization::Integer

    """
        regulization constant
    """
    λ::AbstractFloat

    """
        learning rate
    """
    α::AbstractFloat

    optimizer::Symbol
    ϵAdam::AbstractFloat
    β1::AbstractFloat
    β2::AbstractFloat

    function Model(
        X,
        Y,
        inLayer::Layer,
        outLayer::Layer,
        α;
        optimizer = :gds,
        β1 = 0.9,
        β2 = 0.999,
        ϵAdam = 1e-8,
        regulization = 0,
        λ = 1.0,
        lossFun = :categoricalCrossentropy,
        paramsDtype::DataType = eltype(X),
    )

        deepInitWB!(outLayer; dtype = paramsDtype)
        resetCount!(outLayer, :forwCount)
        deepInitVS!(outLayer, optimizer)
        resetCount!(outLayer, :forwCount)
        @assert regulization in [0, 1, 2]
        return new(
            inLayer,
            outLayer,
            lossFun,
            paramsDtype,
            regulization,
            λ,
            α,
            optimizer,
            ϵAdam,
            β1,
            β2,
        )
    end #inner-constructor

end #Model

export Model


#lowerBoundsUnimodal

#Based on Theorem 3
function _lower_bound_unimodal(D, M)
    if D/M <= 1/3
        return 1 / (1 - D/M)
    end

    return 8D / M / (1 + D/M )^2
end

####
## Separators for the Unimodal Case
####
#min_{t in [l, u]} a * H(t) + bt
#assumes h is convex, H(t) = 1/(mode-t) Int_t^mode h
function _sep_LB_Unimodal_1(h, H, a, b, mode, l, u)
	#check endpoints.
	val_star, t_star = a * H(l) + b * l, l

	if a * H(u) + b * u < val_star
		val_star = a * H(u) + b * u
		t_star = u
	end

	if a > 0 #convex case
		#dirty hack to get around t == mode
		function deriv(t)
			step = 1e-8
			if t == mode  #analytically = h_prime(mode)/ 2
				return a/4/step * (h(mode + step) - h(mode - step)) + b
			end
			a/(mode - t) * (H(t) - h(t)) + b
		end
		val_crit, t_crit = Inf, l
		if  deriv(l) * deriv(u) < 0 #critical point in interval
			t_crit = find_zero(deriv, (l,u), Bisection())
			val_crit = a * H(t_crit) + b * t_crit
		end
		if val_crit < val_star
			t_star = t_crit
			val_star = val_crit
		end
	end

	return val_star, t_star
end

#min_{t in [l, u]} a t^2 + b t + c int_[t, mode] h
#specifically for MAD:  h(t) = M/2 * abs(t-1) - D
#Notice this is specialized ot MAD bc otherwise optimization might be tricky
function _sep_LB_Unimodal_MAD_2(M, D, a, b, c, mode, l, u)
	h(t) = M/2 * abs(t-1) - D
	H_un(t) = quadgk(h, t, mode)[1]

	#check endpoints first
	t_star = l
	val_star = a * t_star^2 + b * t_star + c * H_un(t_star)

	val_u = a * u^2 + b * u + c * H_un(u)
	if val_u < val_star
		val_star = val_u
		t_star = u
	end

	#the <=1 critical point
	t_less = (-2b - 2c * D + c * M) / (4a + c * M)
	val_less = +Inf
	if t_less <=1 && l <= t_less <= u
		val_less = a * t_less^2 + b * t_less + c * H_un(t_less)
	end
	if val_less < val_star
		val_star = val_less
		t_star = t_less
	end

	#the >= 1 critcical point
	t_greater = (-2b  - 2c * D - c * M) / (4a - c * M)
	val_greater = Inf
	if t_greater >= 1 && l <= t_greater <= u
		val_greater = a * t_greater^2 + b * t_greater + c * H_un(t_greater)
	end
	if val_greater < val_star
		val_star = val_greater
		t_star = t_greater
	end

	return val_star, t_star
end

#min_{t in [l, u]} a t^2 + b t + c int_[t, mode] h
#specifically for CV:  h(t) = M^2 (t-1)^2 - C^2
#Notice this is specialized to CV bc otherwise optimization might be tricky
function _sep_LB_Unimodal_CV_2(M, C, a, b, c, mode, l, u)
	#define the functions
	h(t) = M^2 * (t-1)^2 - C^2
	H_unc(t) = quadgk(h, t, mode)[1]
	f(t) = a * t^2 + b*t + c * H_unc(t)

	#check end points
	t_star = l
	val_star = f(t_star)

	if f(u) < val_star
		t_star = u
		val_star = f(u)
	end

	#compute critical points and check feasibility
	disc = a^2 + c * (-2a + b - C^2) * M^2
	if disc >= 0
		t1 = a - c*M^2 - sqrt(disc)
		t1 /= c * M^2
		if l <= t1 <= u && f(t1) < val_star
			val_star = f(t1)
			t_star = t1
		end

		t2 = a - c*M^2 + sqrt(disc)
		t2 /= c*M^2
		if l <= t2 <= u && f(t2) < val_star
			val_star = f(t2)
			t_star = t2
		end
	end

	return val_star, t_star
end

#min_{t in [l, u]} a t^2 + b t + c int_[t, mode] h
#specifically for GM:  h(t) = -log(M ( t-1) + 1) + log(Bbar)
#Notice this is specialized to GM bc otherwise optimization might be tricky
#Bbar  = B/mu
function _sep_LB_Unimodal_GM_2(M, Bbar, a, b, c, mode, l, u)
	#define the functions
	h(t) = -log(M * (t - 1) + 1 ) + log(Bbar)
	H_unc(t) = quadgk(h, t, mode)[1]
	f(t) = a * t^2 + b * t + c * H_unc(t)

	#check end points
	t_star, val_star = l, f(l)

	if f(u) < val_star
		t_star, val_star = u, f(u)
	end

	#compute critical points and check feasibility
	#Degenerate case
	if c == 0.
		tp = -b /2/a
		if l <= tp <= u && f(tp) < val_star
			t_star, val_star = tp, f(tp)
		end
		return val_star, t_star
	end

	#else real work to do
	inner = 2a/c/M * Bbar * exp(2a/c * (1/M - 1) - b/c)

	#no interior solution
	if inner < -1/MathConstants.e
		return val_star, t_star
	end

	if inner < 0
		#check the -1 branch
		tp = 1 - 1/M + c/2/a * lambertw(inner)
		if l <= tp <= u && f(tp) < val_star
			val_star, t_star = f(tp), tp
		end
	end
	#always check the principal branch
	tp = 1 - 1/M + c/2/a * lambertw0(inner)
	if l <= tp <= u && f(tp) < val_star
		val_star, t_star = f(tp), tp
	end

	return val_star, t_star
end

#####################
#Convenience call that integrates h and calls workhorse
#INtegration routines can introduce numerical error. Better ot use other method when integrals known analytically
function _vopp_lb_unimodal_(S, mode, h, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
	#Integrated functions
	function H_un(t)
		if min(mode, t) <= 1 <= max(mode, t)
			return quadgk(h, t, 1, mode)[1]
		else
			return quadgk(h, t, mode)[1]
		end
	end
	H(t) = t == mode ? h(mode) : H_un(t) / (mode - t)
	return _vopp_lb_unimodal_(S, mode, h, H, H_un, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
end

#computes the maximum over the geometric grid
#sep_1(a, b, l, u) solves  min_{t in [l, u]}  a H(t) + b t
#sep_2(a, b, c, l, u) solves min_{t in [l, u]} a t^2 + b t + c int_{t,m} h
#user must ensure sep_1, sep_2 and h are consistent.
#both return val, tstar
#Note that H is normalized in sep 1 and the integral is UNnormalized in sep2
function _vopp_lb_unimodal_(S, mode, h, H, H_un, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
	rstar = -Inf
	pstar = 0.
	rj = 0.

	for pj in ps
		#dispatch to appropriate case
		print_trace && println("\n Price Pj:\t", pj)
		if pj < mode
			rj = _rev_p_lb_unimodal_case1(S, mode, h, sep_1, sep_2, H, H_un, pj, numCuts, print_trace, TOL)
			print_trace && println("\t \t", "Rj:\t", rj, "\n")
		elseif pj == mode
			rj = _rev_p_lb_unimodal_case2(S, mode, h, sep_1, sep_2, H, H_un, pj, numCuts, print_trace, TOL)
			print_trace && println("\t \t", "Rj:\t", rj, "\n")
		else
			rj = _rev_p_lb_unimodal_case3(S, mode, h, sep_1, sep_2, H, H_un, pj, numCuts, print_trace, TOL)
			print_trace && println("\t \t", "Rj:\t", rj, "\n")
		end
		if rj > rstar
			rstar = rj
			pstar = pj
		end
	end
	return 1/rstar, pstar
end

#called by _vopp_lb_unimodal_
function _rev_p_lb_unimodal_case1(S, mode, h, sep_1, sep_2, H, H_un, pj, numCuts, print_trace, TOL)
	@assert pj < mode "Wrong Case called for LB Unimodal: price point: $pj mode: $mode"

	m = Model(solver=GurobiSolver(OutputFlag=false))

	@variable(m, theta)
	@variable(m, lam1)
	@variable(m, lam2)
	@objective(m, Min, theta + (2 - mode) * lam2)

	#add endpoints to get started
	#t = 0
	@constraint(m, theta * mode  + lam1 * H_un(0) >= pj * (mode - pj))
	#t = pj
	@constraint(m, theta + lam1 * H(pj) + lam2 * pj >= pj)
	#t = S
	@constraint(m, theta + lam1 * H(S) + lam2 * S >= pj)

	#Add a silly constraint to prevent unboundedness
	@constraint(m, theta + (2 - mode) * lam2 >= -100)

	for iter = 1:numCuts
		status = solve(m)
		feasible = true

		print_trace && println("\n   Iteration: \t", iter, "\t Status:\t", status)

		#1st constraint corresponds to second separator
		a = -getvalue(lam2)
		b = getvalue(lam2) * mode - getvalue(theta)
		c = getvalue(lam1)
		rhs = pj * (mode - pj) - getvalue(theta) * mode

		val, tstar = sep_2(a, b, c, 0, pj)
		print_trace && println("\t Iter: $iter  \t Sep2: LHS-RHS >=? 0:  $(val-rhs) : tstar $tstar")

		if val < rhs - TOL
			@constraint(m, theta * (mode - tstar) +
							lam1 * H_un(tstar) +
							lam2 * tstar * (mode - tstar) >= pj * (mode - pj)
							)
			feasible = false
		end

		#2nd constraint corresponds to first separator
		val, tstar = sep_1(getvalue(lam1), getvalue(lam2), pj, S)
		rhs = pj - getvalue(theta)
		print_trace && println("\t Iter: $iter  \t Sep1: LHS-RHS >=? 0:  $(val-rhs) : tstar $tstar")

		if val < rhs - TOL
			###DEBUG
			###VG Confirm that the constraint is actually violated right now?
			temp_lhs = getvalue(lam1) * H(tstar) + getvalue(lam2) * tstar
			temp_rhs = pj - getvalue(theta)
			print_trace && println("Violation check: tstar $tstar \n",
					"LHS >=? RHS: \t", temp_lhs, "\t", temp_rhs, "\t", temp_lhs - temp_rhs)

			print_trace && println("Violation Check 2\n",
				"val:\t $val", "temp_lhs:\t", temp_lhs)
			### END DEBUG

			cnst = @constraint(m, theta + lam1 * H(tstar) + lam2 * tstar >= pj)
			feasible = false

		end

		if feasible
			if status != :Optimal
				println("Problem likely infeasible.  Dual Status:\t", status)
				return -1.0
			else #correctly solved
				return getobjectivevalue(m)
			end
		end
	end
	##Exhausted iteration count
	print_trace && println("Max Iteration Count Reached. \n",
							"theta:\t", getvalue(theta),
							"\t lam1:\t", getvalue(lam1),
							"\t lam2:\t", getvalue(lam2))
	throw("Max Iteration Count Reached")
	return -1.
end

function _rev_p_lb_unimodal_case2(S, mode, h, sep_1, sep_2, H, H_un, pj, numCuts, print_trace, TOL)
	@assert abs(pj - mode)<= 1e-10 "Wrong Case called for LB Unimodal: price point: $pj mode: $mode"

	m = Model(solver=GurobiSolver(OutputFlag=false))
	@variable(m, theta)
	@variable(m, lam1)
	@variable(m, lam2)
	@objective(m, Min, theta + (2 - mode) * lam2)

	#add endpoints to get started
	#t = 0
	@constraint(m, theta + lam1 * H(0) >= 0)
	#t = mode
	@constraint(m, theta + lam1 * H(mode) + lam2 * mode >= mode)
	#t = S
	@constraint(m, theta + lam1 * H(S) + lam2 * S >= mode)

	#A trivial (bad) constraint for unboundedness?
	@constraint(m, theta + (2-mode) * lam2 >= -100)

	for iter = 1:numCuts
		status = solve(m)
		feasible = true

		print_trace && println("\n   Iteration: \t", iter, "Status:\t", status)
		#1st constraint corresponds to first opt type
		val, tstar = sep_1(getvalue(lam1), getvalue(lam2), 0, mode)
		rhs = -1 * getvalue(theta)

		print_trace && println("\t Iter: $iter  \t Sep1: LHS -RHS >=? 0:  $(val-rhs) :\t tstar $tstar")

		if val < rhs - TOL
			@constraint(m, theta + lam1 * H(tstar) + lam2 * tstar >= 0)
			feasible = false
		end

		rhs = mode - getvalue(theta)
		val, tstar = sep_1(getvalue(lam1), getvalue(lam2), mode, S)

		print_trace && println("\t Iter: $iter \t Sep1: LHS - RHS ?>= 0:  $(val-rhs) :\t tstar $tstar")

		if val < rhs - TOL
			@constraint(m, theta + lam1 * H(tstar) + lam2 * tstar >= mode)
			feasible = false
		end

		if feasible
			if status != :Optimal
				println("Problem likely infeasible.  Dual Status:\t", status)
				return -1.0
			else #correctly solved

				###DEBUG
				if getobjectivevalue(m) < 0
					print_trace && println("Weird Objective\n",
						"theta:\t", getvalue(theta),
						"\t lam1:\t", getvalue(lam1),
						"\t lam2:\t", getvalue(lam2))
				end

				return getobjectivevalue(m)
			end
		end
	end
	##Exhausted iteration count
	print_trace && println("Max Iteration Count Reached. \n",
							"theta:\t", getvalue(theta),
							"\t lam1:\t", getvalue(lam1),
							"\t lam2:\t", getvalue(lam2))
	throw("Max Iteration Count Reached")
	return -1.
end

function _rev_p_lb_unimodal_case3(S, mode, h, sep_1, sep_2, H, H_un, pj, numCuts, print_trace, TOL)
	@assert pj > mode "Wrong Case called for LB Unimodal: price point: $pj mode: $mode"

	m = Model(solver=GurobiSolver(OutputFlag=false))
	@variable(m, theta)
	@variable(m, lam1)
	@variable(m, lam2)

	@objective(m, Min, theta + (2 - mode) * lam2)

	#add endpoints to get started
	#t = 0
	@constraint(m, theta  + lam1 * H(0) >= 0)
	#t = pj
	@constraint(m, theta + lam1 * H(pj) + lam2 * pj >= 0)
	#t = S
	@constraint(m, theta * (S - mode) - lam1 * H_un(S) + lam2 * S * (S - mode) >= pj * (S - pj))

	#VG Try to prevent unbounded errors.
	@constraint(m, theta + (2 - mode) * lam2 >= -10.)

	for iter = 1:numCuts
		status = solve(m)
		feasible = true

		print_trace && println("\nIteration: \t", iter, "\t Status: ", status)
		#1st constraint corresponds to first separator
		val, tstar = sep_1(getvalue(lam1), getvalue(lam2), 0, pj)
		rhs = -getvalue(theta)
		print_trace && println("\t Iter: $iter Sep1 val -rhs ?>= 0: ", val-rhs, " : ", tstar)

		if val < rhs - TOL
			cnst = @constraint(m, theta + lam1 * H(tstar) + lam2 * tstar >= 0)
			feasible = false
		end

		#2nd constraint corresponds to second separator
		a = getvalue(lam2)
		b = getvalue(theta) - getvalue(lam2) * mode - pj
		c = -getvalue(lam1)
		rhs = getvalue(theta) * mode - pj^2

		val, tstar = sep_2(a, b, c, pj, S)
		print_trace && println("\t Iter: $iter Sep 2 val-rhs ?>= 0: ", val-rhs, " : ", tstar)

		if val < rhs - TOL
			# print_trace && println("\t VG Extra Detailed Debug")
			# print_trace && println("\t", getvalue(theta), "\t", getvalue(lam1), "\t", getvalue(lam2) )
			# print_trace && println("\t tstar:\t", tstar, "\t H_un", H_un(tstar), "\t mode\t", mode)

			@constraint(m, theta * (tstar - mode) -
							lam1 * H_un(tstar) +
							lam2 * tstar * (tstar - mode) >= pj * (tstar - pj)
							)
			feasible = false
		end

		if feasible
			if status != :Optimal
				println("Problem likely infeasible.  Dual Status:\t", status)
				return -1.0
			else #correctly solved
				return getobjectivevalue(m)
			end
		end
	end
	##Exhausted iteration count
	throw("Max Iteration Count Reached")
	return -1.
end

#safe_fail forces a return of -1 if D seems infeasible
function vopp_lb_unimodal_MAD(mu, S, M, D, mode;
			method=:Opt, delta = S/100, numCuts=100, print_trace=false, TOL=1e-6, safe_fail=false, pj = -1)
	#standardize
	c = mu * (1 - M)
	Sc = vopp.comp_Sc(S, M)
	mode_c = (mode - c) / (mu - c)

	if D/M > .25
		println("Deviation $(D/M) > .25, problem likely infeasible")
		if safe_fail
			println("Safe_fail set to true. Aborting Calc")
			return -1.0
		end
	end

	if method == :Formula
		return _lower_bound_unimodal(D, M)
	end
	if method != :Opt
		throw("Method must be one of :Opt or :Formula")
	end

	#create the separators
	h(t) = M/2 * abs(t-1) - D
	H(t) = mode_c == t ? h(mode_c) : quadgk(h, t, mode_c)[1] / (mode_c - t)
	sep_1(a, b, l, u) = _sep_LB_Unimodal_1(h, H, a, b, mode_c, l, u)
	sep_2(a, b, c, l, u) = _sep_LB_Unimodal_MAD_2(M, D, a, b, c, mode_c, l, u)

	if pj > 0
		ps = [ (pj - c)/(mu - c) ]
	else
		#search over the a geometric price ladder
		ps = geom_price_ladder(S, delta)
	end

	#kick it to the workhorse
	_vopp_lb_unimodal_(Sc, mode_c, h, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
end

function vopp_lb_unimodal_CV(mu, S, M, C, mode; delta = S/100, numCuts=100, print_trace=false, TOL=1e-6, pj = -1)
	#standardize
	c = mu * (1 - M)
	Sc = vopp.comp_Sc(S, M)
	mode_c = (mode - c) / (mu - c)

	if C > max_cv(S, M)
		println("Warning:  Desired Coeff of Variation might exceed max-possible for uniform distributions.")
	end

	if pj > 0
		ps = [ (pj - c)/(mu - c) ]
	else
		#search over the a geometric price ladder
		ps = geom_price_ladder(S, delta)
	end

	#create the separators
	h(t) = M^2 * (t - 1)^2 - C^2
	H(t) = mode_c == t ? h(mode_c) : quadgk(h, t, mode_c)[1] / (mode_c - t)
	sep_1(a, b, l, u) = _sep_LB_Unimodal_1(h, H, a, b, mode_c, l, u)
	sep_2(a, b, c, l, u) = _sep_LB_Unimodal_CV_2(M, C, a, b, c, mode_c, l, u)

	#kick it to the workhorse
	_vopp_lb_unimodal_(Sc, mode_c, h, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
end

function vopp_lb_unimodal_GM(mu, S, M, B, mode; delta = S/100, numCuts=100, print_trace=false, TOL=1e-6, pj = -1)
	#standardize
	c = mu * (1 - M)
	Sc = vopp.comp_Sc(S, M)
	mode_c = (mode - c) / (mu - c)
	Bbar = B/mu

	if Bbar > 1 || B < min_gm(S, M, mu)
		println("Warning:  Desired Geometric Mean might be infeasible.  $B not in [$(min_gm(S, M, mu)), 1]")
	end

	if pj > 0
		ps = [ (pj - c)/(mu - c) ]
	else
		#search over the a geometric price ladder
		ps = geom_price_ladder(S, delta)
	end

	#create the separators
	h(t) = -log(M * (t - 1) + 1) + log(Bbar)
	#notice scaled
	H(t) = mode_c == t ? h(mode_c) : quadgk(h, t, mode_c)[1] / (mode_c - t)

	#First separator works because of convexity
	sep_1(a, b, l, u) = _sep_LB_Unimodal_1(h, H, a, b, mode_c, l, u)
	sep_2(a, b, c, l, u) = _sep_LB_Unimodal_GM_2(M, Bbar, a, b, c, mode_c, l, u)

	#kick it to the workhorse
	_vopp_lb_unimodal_(Sc, mode_c, h, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
end

function vopp_lb_unimodal_IC(mu, S, M, phat, q, mode; delta = S/100, numCuts=100, print_trace=false, TOL=1e-6, pj = -1)
	#standardize
	c = mu * (1 - M)
	Sc = vopp.comp_Sc(S, M)
	mode_c = (mode - c) / (mu - c)
	v0 = (phat - 1)/ M + 1

	@assert c <= phat <= S "Price outside valuation bounds:\t $phat not in [$c , $S ]"
	@assert 0 <= q <= 1 "Market fraction not $q not in [0, 1]"

	if pj > 0
		ps = [(pj - c)/(mu - c)]
	else
		#search over the a geometric price ladder
		ps = geom_price_ladder(S, delta)
		ps = [ps; range(max(0, phat/2), stop=min(2 * phat, 1), length=100)]
		push!(ps, v0)
		push!(ps, mode_c)
		sort!(ps)
	end

	#create the separators
	h(t) = t >= v0 ? 1 - q : -q
	#notice scaled
	function H(t)  #G(v_0, mode_c, t) - q
    	if v0 > max(mode_c, t)
        	return -q
    	elseif v0 < min(mode_c, t)
        	return 1 - q
    	elseif mode_c == t
        	return mode_c >= v0 ? 1 - q : -q
    	end
    	#non-trivial answer only if v0 in interval [mode_c, t]
    	return (max(mode_c, t) - v0) / abs(mode_c - t) - q
	end

	#sep_1(a, b, l, u) solves  min_{t in [l, u]}  a H(t) + b t
	function sep_1(a, b, l, u)
		f(t) = a * H(t) + b * t
		#check endpoints
		val, tstar = f(l), l
		if f(u) < val
			val, tstar = f(u), u
		end

		#check mode if its feasible
		if l <= mode_c <= u && f(mode_c) < val
			val, tstar = f(mode_c), mode_c
		end

		#check v0 if its feasible
		if l <= v0 <= u && f(v0) < val
			val, tstar = f(v0), v0
		end

		if a * b < 0
			if v0 > mode_c
				t1 = mode_c + sqrt( a/b * (mode_c - v0) )
				if l <= t1 <= u && f(t1) < val
					val, tstar = f(t1), t1
				end
			end
			if v0 < mode_c
				t4 = mode_c - sqrt( a/b * (v0 - mode_c) )
				if l <= t4 <= u && f(t4) < val
					val, tstar = f(t4), t4
				end
			end
		end  #end check for internal optimizers
		return val, tstar
	end  #ends def of separator

	#sep_2(a, b, c, l, u) solves min_{t in [l, u]} a t^2 + b t + c int_{t,m} h
	#Notice this reverses rolls of a, b from document

	H_un(t) = H(t) * (mode_c - t)
	function sep_2(a, b, c, l, u)
		obj(t) = a * t^2 + b * t + c * H_un(t)

		#check endpoints
		tstar, val = l, obj(l)
		if obj(u) < val
			tstar, val = u, obj(u)
		end

		#handle degenerate case where c == 0
		if c == 0.
			tp = -b/2/a
			if l <= tp <= u && obj(tp) < val
					val, tstar = obj(tp), tp
			end
			return val, tstar
		end

		#else real work to be done
		#check the two possible critical points
		if a != 0
			t1 = c/2/a * (1 - q - b/c)
			if t1 >= v0 && l <= t1 < u && obj(t1) < val
					val, tstar = obj(t1), t1
			end

			t0 = -c/2/a * (q + b/c)
			if t0 < v0 && l <= t0 < u && obj(t0) < val
					val, tstar = obj(t0), t0
			end
		end
		return val, tstar
	end  #end def of separator

	#kick it to the workhorse
	_vopp_lb_unimodal_(Sc, mode_c, h, H, H_un, delta, sep_1, sep_2, numCuts, TOL, print_trace, ps)
end


# adjust sl by subtracting median (prevents old change points to affect the estimation of new change points)
function _adjustslope!(sl::AbstractVector{<:AbstractFloat}, indmax::Int, N::Int)

    med1 = median(sl[1:indmax])
    for i in 1:indmax
        sl[i] -= med1
    end

    med2 = median(sl[indmax+1:end])
    for i in indmax+1:N
        sl[i] -= med2
    end
end

# adjust sum of the ranks by the amount expected on average
function _adjustsumrank!(SA::AbstractVector{<:AbstractFloat}, sl::AbstractVector{<:AbstractFloat}, N::Int)
    for i in 1:N
        SA[i] = abs(2*sum(tiedrank(sl)[1:i]) - i * (N + 1))
    end
end

function _findchange(sl::AbstractVector{<:AbstractFloat})

    niter = 3
    N = length(sl)
    SA  = Array{Float64}(undef, N)
    pva = Array{Union{Missing,Float64}}(missing, niter)  # stores p values
    τ = Array{Union{Missing,Int}}(missing, niter)    # stores ind

    if N >= 10
        for j in 1:niter

            _adjustsumrank!(SA, sl, N)

            indmax = argmax(SA)
            xn1 = sl[1:indmax]
            xn2 = sl[indmax+1:end]

            p = pvalue(MannWhitneyUTest(xn1, xn2))

            # at least 10 point both ends. Reccomended by Lanzante 1996
            if ( (5 <= indmax <= N-5) && (p < 0.05) )

                pva[j] = p
                τ[j] = indmax

                _adjustslope!(sl, indmax, N)
            end
        end
    end

    return pva, τ
end

"""
    mbass(mags, ΔM)

Apply the Median-Based Analysis of the Segment Slope method to estimate the
magnitude of completeness.

# Arguments
* `mags`: array storing the magnitude values of an incomplete catalog.
* `ΔM`: bin width to be used to group the magnitude values.`

 # References
* Amorese 2007. Applying a Change-Point Detection Method on Frequency-Magnitude
  Distributions. Bulletin of the Seismological Society of America 97 (5): 1742–1749.
  https://doi.org/10.1785/0120060181
* Lanzante 1996. Resistant, Robust and Non‐Parametric Techniques for the Analysis
  of Climate Data: Theory and Examples, Including Applications to Historical
  Radiosonde Station Data. Int. J. Climatol., 16: 1197-1226. https://doi.org/10.1002/(SICI)1097-0088(199611)16:11<1197::AID-JOC89>3.0.CO;2-L
"""
function mbass(mags::AbstractVector{T}, ΔM::S) where {T,S<:Real}

    bincenters, binevents = histmag(mags, ΔM)

    Mc = mbass(bincenters, binevents)

    return Mc
end

"""
    mbass(bincenters, binevents)

Apply the Median-Based Analysis of the Segment Slope method to estimate the
magnitude of completeness.

# Arguments
* `bincenters`: array storing the central values of the magnitude bins.
* `binevents`: array storing the number of events in the magnitude bins.

`bincenters` and `binevents` can be obtained with  [`histmag(mag, ΔM)`](@ref).
"""
function mbass(bincenters::AbstractVector{T}, binevents::AbstractVector{S}) where {T,S<:Real}

    if length(bincenters) != length(binevents)
        error("The two input arrays must have the same number of elements")
    end

    # remove zero values to avoid Inf after log10
    goodind = binevents .!= 0
    x = bincenters[goodind]
    y = binevents[goodind]

    # Estimate slope/first derivative for each point (Segment slope)
    sl = diff(log10.(y)) ./ diff(x)
    x2 = x[2:end] #(x[2:end] .+ x[1:end-1]) ./ 2.

    pva, τ = _findchange(sl)

    if all(q->ismissing(q),pva)
        #@warn "Unable to identify change point. Number of points is probably not large enough. Try to add more events or change ΔM"
        return NaN
    else
        argpmin = argmin(skipmissing(pva))
        mainBreak = x2[ τ[argpmin] ]

        return mainBreak
    end
end


module CompositionalNetworks

# Usings
using ConstraintDomains
using Dictionaries
import Distances
using Evolutionary
using JuliaFormatter
using Memoization
using OrderedCollections
using Random
using ThreadPools
using ThreadSafeDicts
using Unrolled

export Composition
export ICN
export aggregation_layer
export arithmetic_layer
export code
export comparison_layer
export compose
export compose_to_file!
export composition
export composition_to_file!
export explore
export explore_learn_compose
export hamming
export lazy
export lazy_param
export learn_compose
export manhattan
export max_icn_length
export minkowski
export optimize!
export regularization
export show_layers
export symbols
export transformation_layer

# Include utils
include("utils.jl")
include("metrics.jl")

# Includes layers
include("layer.jl")
include("transformation.jl")
include("arithmetic.jl")
include("aggregation.jl")
include("comparison.jl")

# Include ICN
include("icn.jl")
include("composition.jl")

# Genetic Algorithm and learning
include("genetic.jl")
include("explore.jl")
include("learn.jl")

end


#=
Very heavily inspired by Calculus.jl, but with an emphasis on performance and DiffEq API convenience.
=#

#=
Compute the finite difference interval epsilon.
Reference: Numerical Recipes, chapter 5.7.
=#
@inline function compute_epsilon(::Type{Val{:forward}}, x::T, eps_sqrt=sqrt(eps(real(T)))) where T<:Number
    eps_sqrt * max(one(real(T)), abs(x))
end

@inline function compute_epsilon(::Type{Val{:central}}, x::T, eps_cbrt=cbrt(eps(real(T)))) where T<:Number
    eps_cbrt * max(one(real(T)), abs(x))
end

@inline function compute_epsilon(::Type{Val{:complex}}, x::T, ::Union{Nothing,T}=nothing) where T<:Real
    eps(T)
end

@inline function compute_epsilon_factor(fdtype::DataType, ::Type{T}) where T<:Number
    if fdtype==Val{:forward}
        return sqrt(eps(real(T)))
    elseif fdtype==Val{:central}
        return cbrt(eps(real(T)))
    else
        return one(real(T))
    end
end

function fdtype_error(funtype::Type{T}=Float64) where T
    if funtype<:Real
        error("Unrecognized fdtype: valid values are Val{:forward}, Val{:central} and Val{:complex}.")
    elseif funtype<:Complex
        error("Unrecognized fdtype: valid values are Val{:forward} or Val{:central}.")
    else
        error("Unrecognized returntype: should be a subtype of Real or Complex.")
    end
end


using NeuralPDE
using Test, Flux
using StochasticDiffEq
using LinearAlgebra

d = 1
m = Chain(Dense(3 , 16, elu),Dense(16, 16, elu) , Dense(16 , 5 , elu) , Dense(5 , 1))
ensemblealg = EnsembleThreads()
γ_mu_prototype = nothing
γ_sigma_prototype = zeros(d , d , 1)
γ_phi_prototype = nothing

sdealg = EM()
tspan = (0.00 , 1.00)
trajectories = 10000
function phi(x , y_phi)
  x.^2
end
g(x , γ_sigma) = γ_sigma[: , : , 1]
f(x , γ_mu_1 ,γ_mu_2 ) = [0.00]
xspan = (0.00 , 3.00)
y_domain = KolmogorovParamDomain((0.00 , 2.00) , (0.00 , 0.00) , (0.00 , 0.00) )
dt = 0.01
dx = 0.01
dy = 0.01
opt = Flux.ADAM(1e-2)
prob = ParamKolmogorovPDEProblem(f , g , phi , xspan , tspan , d  , y_domain  ; Y_sigma_prototype = γ_sigma_prototype)
sol = solve(prob, NNParamKolmogorov(m,opt , sdealg,ensemblealg) , verbose = true, dt = 0.01,
            abstol=1e-10, dx = 0.01 , trajectories = trajectories ,  maxiters = 150 , use_gpu = false)

x_test = rand(xspan[1]:dx:xspan[2] , d , 1  , 1000)
t_test = rand(tspan[1]:dt:tspan[2] ,  1 , 1000)
γ_sigma_test = rand(0.3:dy:0.3 , d , d,  1, 1000)

function getTestData(x_test, t_test, γ_sigma_test)
  X_test  = reshape([], 3 ,0)
  for i in 1:length(t_test)
    K = vcat(t_test[i] , x_test[: , : , i] , reshape(γ_sigma_test[: , : , : , i] , d^2*(size(γ_sigma_prototype))[3] , 1))
    X_test = hcat(X_test , K)
  end
  return X_test
end

X_test = getTestData(x_test, t_test, γ_sigma_test)

function analytical(x , t , y)
  #return sum(x.^2) + t*tr(y*y) for multidimensional
  return x^2 + t*(y*y)
end
function y_analytical(X)
  y_test  = reshape([], 1 ,0)
  for i in 1:length(t_test)
    y_test = hcat(y_test , analytical(X[: , i][2] , X[: , i][1] , γ_sigma_test[: , : , 1, i][1] ) )
  end
  return y_test
end

y_test = y_analytical(X_test)

m(X_test)

@test Flux.mse(m(X_test), y_test) < 0.1


module GraphModularDecomposition

export StrongModuleTree, strong_modules,
    graph_factorizing_permutation,
    digraph_factorizing_permutation,
    symgraph_factorizing_permutation,
    tournament_factorizing_permutation

using LinearAlgebra
using LinearAlgebra: checksquare
using SparseArrays

include("StrongModuleTrees.jl")
using .StrongModuleTrees

const \ = setdiff

function symgraph_factorizing_permutation(G::AbstractMatrix, V::Vector{Int}=collect(1:checksquare(G)))
    P = [V]
    center::Int = 0
    pivots::Vector{Vector{Int}} = []
    modules::Vector{Vector{Int}} = []
    first_pivot = Dict{Vector{Int},Int}()

    N_adj(x, X=V) = [y for y in X if y != x && G[x,y] != 0]
    N_non(x, X=V) = [y for y in X if y != x && G[x,y] == 0]

    smaller_larger(A, B) = length(A) <= length(B) ? (A, B) : (B, A)

    function refine!(P, S, x)
        i, between = 0, false
        while (i += 1) <= length(P)
            X = P[i]
            if center in X || x in X
                between = !between
                continue
            end
            Xₐ = X ∩ S
            isempty(Xₐ) && continue
            X = X \ Xₐ
            isempty(X) && continue
            P[i] = X
            insert!(P, i + between, Xₐ)
            add_pivot(X, Xₐ)
            i += 1
        end
    end

    function add_pivot(X, Xₐ)
        if X in pivots
            push!(pivots, Xₐ)
        else
            S, L = smaller_larger(X, Xₐ)
            push!(pivots, S)
            i = findfirst(isequal(X), modules)
            if i !== nothing
                modules[i] = L
            else
                push!(modules, L)
            end
        end
    end

    function partition_refinement!(P)
        while init_partition!(P)
            while !isempty(pivots)
                E = pop!(pivots)
                for x in E
                    S = N_adj(x) \ E
                    refine!(P, S, x)
                end
            end
        end
    end

    function init_partition!(P)
        maximum(length, P) <= 1 && return false
        if isempty(modules)
            for (i, X) in enumerate(P)
                length(X) > 1 || continue
                x = get(first_pivot, X, first(X))
                A, N = N_adj(x, X), N_non(x, X)
                splice!(P, i, filter!(!isempty, [A, [x], N]))
                S, L = smaller_larger(A, N)
                center = x
                push!(pivots, S)
                push!(modules, L)
                break
            end
        else
            X = popfirst!(modules)
            x = first(X)
            push!(pivots, [x])
            first_pivot[X] = x
        end
        return true
    end

    partition_refinement!(P)
    return map(first, P)
end

function tournament_factorizing_permutation(
        T::AbstractMatrix, # matrix representation of a tournament digraph
        V::Vector{Int} = collect(1:checksquare(T)),
    )
    n, P = length(V), [V]
    for x = V
        i = findfirst(C->x in C, P)
        C = P[i]
        B = filter(y->x != y && T[x,y] < T[y,x], C)
        A = filter(y->x != y && T[y,x] < T[x,y], C)
        splice!(P, i, filter!(!isempty, [B, [x], A]))
    end
    return map(first, P)
end

overlap(A::AbstractVector, B::AbstractVector) =
    # TODO: efficient overlap checking for sorted vectors
    # return true as soon as one elt of each is found:
    # x ∩ y, x \ y, y \ x
    A !== B && !isempty(A \ B) && !isempty(A ∩ B) && !isempty(B \ A)

function overlap_components(
    s::StrongModuleTree,
    t::StrongModuleTree,
    M = strong_modules(s),
    N = strong_modules(t),
)
    # TODO: implement linear time algorithm from this thesis:
    ## Dalhaus 1998: "Parallel algorithms for hierarchical clustering and
    ##  applications to split decomposition and parity graph recognition"
    ## There's also a 2000 paper by the same name, but the PDF is jumbled

    O = M ∪ N
    n = length(O)
    R = SparseMatrixCSC{Int}(I, n, n)
    for i = 1:n-1, j = i+1:n
        overlap(O[i], O[j]) || continue
        R[i,j] = R[j,i] = 1
    end
    while true
        R′ = min.(1, R^2)
        R′ == R && break
        R .= R′
    end
    for (i, j) in map(Tuple, findall(!iszero, R))
        i < j || continue
        O[i] = O[j] = sort!(O[i] ∪ O[j])
    end
    return unique(O)
end

function intersect_permutation(
    V::AbstractVector{E},   # vertices
    s::StrongModuleTree{E}, # 1st strong module tree
    t::StrongModuleTree{E}, # 2nd strong module tree
) where E
    Ms = strong_modules(s)
    Mt = strong_modules(t)
    U = filter!(overlap_components(s, t, Ms, Mt)) do X
        (X in Ms || parent_node(s, X).kind != :prime) &&
        (X in Mt || parent_node(t, X).kind != :prime)
    end
    for x in V; push!(U, [x]); end
    R = Dict()
    for X in U
        S = parent_node(t, X)
        T = parent_node(s, X)
        union!(get!(()->Set{Int}(), R, (S, T)), X)
    end
    N = U ∪ map(sort!∘collect, values(R))
    N = filter!(X->length(X) > 1, N)
    T = Any[[] for x in V]
    for node in sort!(N, by=length, rev=true)
        an = Vector{Any}(node)
        for x in node
            push!(T[x], an)
        end
    end
    for x in V, i = 1:length(T[x])-1
        parent = T[x][i]
        child = T[x][i+1]
        child in parent && continue
        filter!(y->!(y in child), parent)
        push!(parent, child)
    end
    T = T[1][1]
    p = E[]
    record_vals(v::Vector) = foreach(record_vals, v)
    record_vals(x::E) = push!(p, x)
    record_vals(T)
    return p
end

function digraph_factorizing_permutation(G::AbstractMatrix)
    n = checksquare(G)
    Gs = G .| G'
    Gd = G .& G'
    H = Gs .+ Gd
    ps = graph_factorizing_permutation(Gs)
    pd = graph_factorizing_permutation(Gd)
    s = StrongModuleTree(Gs, ps)
    t = StrongModuleTree(Gd, pd)
    p = intersect_permutation(1:n, s, t)
    h = StrongModuleTree(H, p)
    function sort_leaves!(h)
        for x in h.nodes
            x isa StrongModuleTree && sort_leaves!(x)
        end
        h.kind == :complete || return
        if h.edge == (1,1) # tournament node
            X = map(first_leaf, h.nodes)
            q = tournament_factorizing_permutation(G, X)
            o = Dict(x => i for (i, x) in enumerate(q))
            sort!(h.nodes, by=x->o[first_leaf(x)])
        else # 0/2-complete node
            d = Dict()
            c = (1:n)\leaves(h)
            for x in h.nodes
                i = first_leaf(x)
                push!(get!(d, G[c,i], []), x)
            end
            h.nodes .= [x for v in values(d) for x in v]
        end
    end
    sort_leaves!(h)
    return leaves(h)
end

function graph_factorizing_permutation(G::AbstractMatrix)
    a, b = extrema(G)
    0 ≤ a ≤ b ≤ 1 ||
        error("factoring multi-color two-structures not supported")
    issymmetric(G) ?
        symgraph_factorizing_permutation(G) :
        digraph_factorizing_permutation(G)
end

end # module