# This is an implementation of the CMSA-ES as described in the paper:
#  H. Beyer and B. Sendhoff, "Covariance Matrix Adaptation Revisited –
#  the CMSA Evolution Strategy", 2008
#

function normalize_utilities(utilities)
  utilities / sum(utilities)
end

function linear_utilities(mu, lambda)
  normalize_utilities(hcat(ones(1, mu), zeros(1, lambda-mu)))
end

function log_utilities(mu, lambda)
  normalize_utilities(hcat((log(mu+1) - log(1:mu))', zeros(1, lambda-mu)))
end

# Optimize the n-dimensional objective func with a (mu,lambda) CMSA-ES.
function cmsa_es(p;
  trace = true,

  # Stopping criteria related
  max_seconds = 4*numdims(p), max_evals_per_dim = 1e7,
  ftol = 1e-7, xtol = 1e-10, stol = 1e-10,
  max_rounds_without_improvement = 500,

  # Starting points, will be random unless specified
  xmean = false,

  # Algorithm specific params:
  covarMatrixSampler = CholeskyCovarSampler,
  utilitiesFunc = log_utilities,
  lambda = 4*numdims(p),
  mu = int(max(ceil(lambda/rand(4:20)), 1.0)),
  tau = 1 / sqrt(2*numdims(p)), # Equation (1) on page 5 in Beyer2008
  tau_c = 1 + numdims(p) * (numdims(p) + 1) / (2 * mu),  # Equation (2) on page 5 in Beyer2008
  sigma = 0.05*rand(1:8)*minimum(diameters(search_space(p))),
  decompose_covar_prob = 0.4

  )

  N = numdims(p)
  ss = search_space(p)
  max_evals = max_evals_per_dim * N
  a = 1 - 1 / tau_c
  C = covarMatrixSampler(N)
  utilities = utilitiesFunc(mu, lambda)
  xbest = xmean = rand_individual(ss)   # Current best mean value.
  fbest = eval1(xbest, p)
  num_fevals = 1
  archive = BlackBoxOptim.TopListArchive(N, 10)
  add_candidate!(archive, fbest, xbest[:], num_fevals)
  fevals_last_best = num_fevals
  next_print_covar = 100
  termination_reason = "?"

  start_time = time()

  # Now lets optimize! Ensure we run at least one iteration.
  while(true)

    if (time() - start_time) > max_seconds
      termination_reason = "Exceeded time budget"
      break
    end

    if num_fevals > max_evals
      termination_reason = "Exceeded function eval budget"
      break
    end

    # Decompose only with a given probability => saves time
    if rand() <= decompose_covar_prob
      decompose!(C)
    end

    # Generate new population
    sigmas = sigma * exp( tau * randn(1, lambda) )  # 1*lambda
    s = multivariate_normal_sample(C, N, lambda)
    z = broadcast(*, sigmas, s)                     # n*lambda
    xs = repmat(xmean, 1, lambda) + z               # n*lambda

    # Evaluate fitness
    fitnesses = eval_fitnesses(p, xs, lambda)
    num_fevals += lambda

    # Check if best new fitness is best ever and print some info if tracing.
    indbest = argmin(fitnesses)
    fbest_new = fitnesses[indbest]

    if fbest_new < fbest
      xbest = xs[:, indbest]
      fbest = fbest_new
      add_candidate!(archive, fbest, xbest, num_fevals)
      fevals_last_best = num_fevals

      if fitness_is_within_ftol(p, ftol, fbest)
        termination_reason = "Within ftol"
        break
      end

      if trace
        println("$(num_fevals): Best fitness = $(fbest)")
        if num_fevals > next_print_covar
          next_print_covar = num_fevals + 100
          println("covar summary: ", sumstats(C.C, (x) -> @sprintf("%.2e", x)))
          println("sigma: ", sigma)
        end
      end
    else
      if (num_fevals - fevals_last_best) > max_rounds_without_improvement * lambda
        termination_reason = "Max rounds without improvement reached"
        break
      end
    end

    # Assign weights to the best individuals according to the utilities vector.
    weights = assign_weights(lambda, fitnesses, utilities)

    # Calculate new mean value based on weighted version of steps in population.
    xmean += (z * weights)

    # Update the covariance matrix
    uc = zeros(Float64, N, N)
    for i in 1:lambda
      if weights[i] > 0.0
        se = s[:,i]
        uc += weights[i] * (se * se')
      end
    end
    update_covariance_matrix!(C, uc, a)
    #ws = broadcast(*, weights', s)
    #update_covariance_matrix!(C, (ws * ws'), covar_learning_rate)

    # Adapt sigma for next round
    sigma = sigmas * weights

    # Terminate if sigma very small
    #println("sigma = $(sigma[1,1])")
    if sigma[1,1] < stol
      termination_reason = "Sigma too small"
      break
    end
  end

  return xbest, fbest, num_fevals, termination_reason, archive
end

function eval_fitnesses(problem, xs, lambda = size(xs, 2))
  fitnesses = zeros(lambda)
  for i in 1:lambda
    fitnesses[i] = eval1(xs[:,i], problem)
  end
  fitnesses
end

function assign_weights(n, fitnesses, utilities; minimize = true)
  us_ordered = zeros(n, 1)
  perms = sortperm(fitnesses, rev = !minimize)
  for i in 1:n
    us_ordered[perms[i]] = utilities[i]
  end
  us_ordered
end

# We create different types of Covariance matrix samplers based on different
# decompositions.
abstract type CovarianceMatrixSampler end

function update_covariance_matrix!(cms::CovarianceMatrixSampler, delta, a)
  C = a * cms.C + (1 - a) * delta
  cms.C = triu(C) + triu(C,1)' # Ensure C is symmetric. Should not be needed, investigate...
end

mutable struct EigenCovarSampler <: CovarianceMatrixSampler
  C::Array{Float64,2}
  B::Array{Float64,2}
  diagD::Array{Float64,1}

  EigenCovarSampler(n) = begin
    new(Matrix{Float64}(I, n,n), Matrix{Float64}(I, n,n), ones(n))
  end
end

function decompose!(cms::EigenCovarSampler)
  try
    EV, B = eig(cms.C)
    cms.B = B
    cms.diagD = sqrt(EV)
  catch
    # We don't update if there is some problem
  end
end

function multivariate_normal_sample(cms::CovarianceMatrixSampler, n, m)
  cms.B * (cms.diagD .* randn(n, m))
end

mutable struct CholeskyCovarSampler <: CovarianceMatrixSampler
  C::Array{Float64,2}
  sqrtC::Array{Float64,2}

  CholeskyCovarSampler(n) = begin
    new(Matrix{Float64}(I, n,n), Matrix{Float64}(I, n,n))
  end
end

function decompose!(cms::CholeskyCovarSampler)
  try
    cms.sqrtC = chol(cms.C)'
  catch error
    # We don't update if there is some problem
  end
end

function multivariate_normal_sample(cms::CholeskyCovarSampler, n, m)
  cms.sqrtC * randn(n, m)
end


"""
Lazy representation of a scaled map `λ * A = A * λ` with real or complex map
`A <: LinearMap{RealOrComplex}` and real or complex scaling factor
`λ <: RealOrComplex`.
"""
struct ScaledMap{T, S<:RealOrComplex, L<:LinearMap} <: LinearMap{T}
    λ::S
    lmap::L
    function ScaledMap{T}(λ::S, A::L) where {T, S <: RealOrComplex, L <: LinearMap{<:RealOrComplex}}
        @assert Base.promote_op(*, S, eltype(A)) == T "target type $T cannot hold products of $S and $(eltype(A)) objects"
        new{T,S,L}(λ, A)
    end
end

# constructor
ScaledMap(λ::RealOrComplex, lmap::LinearMap{<:RealOrComplex}) =
    ScaledMap{Base.promote_op(*, typeof(λ), eltype(lmap))}(λ, lmap)

# basic methods
Base.size(A::ScaledMap) = size(A.lmap)
Base.axes(A::ScaledMap) = axes(A.lmap)
Base.isreal(A::ScaledMap) = isreal(A.λ) && isreal(A.lmap)
LinearAlgebra.issymmetric(A::ScaledMap) = issymmetric(A.lmap)
LinearAlgebra.ishermitian(A::ScaledMap) = isreal(A.λ) && ishermitian(A.lmap)
LinearAlgebra.isposdef(A::ScaledMap) = isposdef(A.λ) && isposdef(A.lmap)

Base.transpose(A::ScaledMap) = A.λ * transpose(A.lmap)
Base.adjoint(A::ScaledMap) = conj(A.λ) * adjoint(A.lmap)

# comparison (sufficient, not necessary)
Base.:(==)(A::ScaledMap, B::ScaledMap) =
    eltype(A) == eltype(B) && A.lmap == B.lmap && A.λ == B.λ

# scalar multiplication and division
Base.:(*)(α::RealOrComplex, A::LinearMap{<:RealOrComplex}) = ScaledMap(α, A)
Base.:(*)(A::LinearMap{<:RealOrComplex}, α::RealOrComplex) = ScaledMap(α, A)

Base.:(*)(α::Number, A::ScaledMap) = (α * A.λ) * A.lmap
Base.:(*)(A::ScaledMap, α::Number) = A.lmap * (A.λ * α)
# needed for disambiguation
Base.:(*)(α::RealOrComplex, A::ScaledMap) = (α * A.λ) * A.lmap
Base.:(*)(A::ScaledMap, α::RealOrComplex) = (A.λ * α) * A.lmap
Base.:(-)(A::LinearMap) = -1 * A

# composition (not essential, but might save multiple scaling operations)
Base.:(*)(A::ScaledMap, B::ScaledMap) = (A.λ * B.λ) * (A.lmap * B.lmap)
Base.:(*)(A::ScaledMap, B::LinearMap) = A.λ * (A.lmap * B)
Base.:(*)(A::LinearMap, B::ScaledMap) = (A * B.lmap) * B.λ

# multiplication with vectors/matrices
for (In, Out) in ((AbstractVector, AbstractVecOrMat),
                  (AbstractMatrix, AbstractMatrix))
    @eval begin
        # commutative case
        function _unsafe_mul!(y::$Out, A::ScaledMap, x::$In{<:RealOrComplex})
            return _unsafe_mul!(y, A.lmap, x, A.λ, false)
        end
        function _unsafe_mul!(y::$Out, A::ScaledMap, x::$In{<:RealOrComplex}, α::Number, β::Number)
            return _unsafe_mul!(y, A.lmap, x, A.λ * α, β)
        end
        # non-commutative case
        function _unsafe_mul!(y::$Out, A::ScaledMap, x::$In)
            return lmul!(A.λ, _unsafe_mul!(y, A.lmap, x))
        end
    end
end


using LinearAlgebra
using Polynomials
include("Modeling.jl")

mutable struct Controller
    C::Matrix{Float64}
    a::Matrix{Float64}
    A::Matrix{Float64}
    α::Matrix{Float64}
    K::Matrix{Float64}
    kr::Float64

    function get_C(ss::StateSpace)
	C = zeros(size(ss.A)...)
	for i in 1:length(ss.B)
	    C[:,i] = ss.A ^ (i - 1) * ss.B
	end
	if det(C) == 0
	    throw("NOT CONTROLLABLE")
	end
	return C
    end

    function get_a(A::Matrix{Float64})
	return Matrix(reverse(coeffs(fromroots(A)))[2:end]')
    end

    function get_A(a::Matrix{Float64})
	A = Matrix(1.0I, length(a), length(a))
	for i in 1:(length(a) - 1)
	    for j in (i + 1):length(a)
		A[i, j] = a[j - i]
	    end
	end
	return A
    end

    function Controller(ss::StateSpace)
	this = new()
	this.C = get_C(ss)
	this.a = get_a(ss.A)
	this.A = get_A(this.a)
	return this
    end

    function Controller(ss::StateSpace, poles::Vector)
	this = Controller(ss)
	set_poles!(this, ss, poles)
	return this
    end
end

function set_poles!(c::Controller, ss::StateSpace, poles::Vector{Float64})
    c.α = Matrix(reverse(coeffs(fromroots(poles)))[2:end]')
    c.K = (c.α - c.a) * inv(c.A) * inv(c.C)
    c.kr = -1 / (ss.C * inv(ss.A - ss.B * c.K) * ss.B)[1]
end

function set_poles!(c::Controller, ss::StateSpace, poles::Vector{ComplexF64})
    Δ = coeffs(fromroots(poles))
    if all(isreal, Δ)
	c.α = Matrix(reverse(Δ)[2:end]')
	c.K = (c.α - c.a) * inv(c.A) * inv(c.C)
	c.kr = -1 / (ss.C * inv(ss.A - ss.B * c.K) * ss.B)[1]
    else
	throw("COMPLEX POLES MUST BE CONJUGATE PAIRS")
    end
end

function get_u(c::Controller, x::Vector{Float64}, r::Float64)
    return c.kr * r - (c.K * x)[1]
end


package ln

import (
	"math"
	"math/rand"
)

struct Vector
    x::Float64
    y::Float64
    z::Float64
end

type Vector struct {
	X, Y, Z float64
}

func RandomUnitVector() Vector {
	for {
		x := rand.Float64()*2 - 1
		y := rand.Float64()*2 - 1
		z := rand.Float64()*2 - 1
		if x*x+y*y+z*z > 1 {
			continue
		}
		return Vector{x, y, z}.Normalize()
	}
}

func (a Vector) Length() float64 {
	return math.Sqrt(a.X*a.X + a.Y*a.Y + a.Z*a.Z)
}

func (a Vector) Distance(b Vector) float64 {
	return a.Sub(b).Length()
}

func (a Vector) LengthSquared() float64 {
	return a.X*a.X + a.Y*a.Y + a.Z*a.Z
}

func (a Vector) DistanceSquared(b Vector) float64 {
	return a.Sub(b).LengthSquared()
}

func (a Vector) Dot(b Vector) float64 {
	return a.X*b.X + a.Y*b.Y + a.Z*b.Z
}

func (a Vector) Cross(b Vector) Vector {
	x := a.Y*b.Z - a.Z*b.Y
	y := a.Z*b.X - a.X*b.Z
	z := a.X*b.Y - a.Y*b.X
	return Vector{x, y, z}
}

func (a Vector) Normalize() Vector {
	d := a.Length()
	return Vector{a.X / d, a.Y / d, a.Z / d}
}

func (a Vector) Add(b Vector) Vector {
	return Vector{a.X + b.X, a.Y + b.Y, a.Z + b.Z}
}

func (a Vector) Sub(b Vector) Vector {
	return Vector{a.X - b.X, a.Y - b.Y, a.Z - b.Z}
}

func (a Vector) Mul(b Vector) Vector {
	return Vector{a.X * b.X, a.Y * b.Y, a.Z * b.Z}
}

func (a Vector) Div(b Vector) Vector {
	return Vector{a.X / b.X, a.Y / b.Y, a.Z / b.Z}
}

func (a Vector) AddScalar(b float64) Vector {
	return Vector{a.X + b, a.Y + b, a.Z + b}
}

func (a Vector) SubScalar(b float64) Vector {
	return Vector{a.X - b, a.Y - b, a.Z - b}
}

func (a Vector) MulScalar(b float64) Vector {
	return Vector{a.X * b, a.Y * b, a.Z * b}
}

func (a Vector) DivScalar(b float64) Vector {
	return Vector{a.X / b, a.Y / b, a.Z / b}
}

func (a Vector) Min(b Vector) Vector {
	return Vector{math.Min(a.X, b.X), math.Min(a.Y, b.Y), math.Min(a.Z, b.Z)}
}

func (a Vector) Max(b Vector) Vector {
	return Vector{math.Max(a.X, b.X), math.Max(a.Y, b.Y), math.Max(a.Z, b.Z)}
}

func (a Vector) MinAxis() Vector {
	x, y, z := math.Abs(a.X), math.Abs(a.Y), math.Abs(a.Z)
	switch {
	case x <= y && x <= z:
		return Vector{1, 0, 0}
	case y <= x && y <= z:
		return Vector{0, 1, 0}
	}
	return Vector{0, 0, 1}
}

func (a Vector) MinComponent() float64 {
	return math.Min(math.Min(a.X, a.Y), a.Z)
}

func (p Vector) SegmentDistance(v Vector, w Vector) float64 {
	l2 := v.DistanceSquared(w)
	if l2 == 0 {
		return p.Distance(v)
	}
	t := p.Sub(v).Dot(w.Sub(v)) / l2
	if t < 0 {
		return p.Distance(v)
	}
	if t > 1 {
		return p.Distance(w)
	}
	return v.Add(w.Sub(v).MulScalar(t)).Distance(p)
}


# Obtained from https://ccrma.stanford.edu/~jos/filters/Matlab_listing_tmps_m.html
spec = [1 1 1 0 0 0 1 1]'; # Lowpass cutting off at fs*3/8
@show mps(spec)
@show abs(mps(spec))
@show ifft(spec)
@show ifft(mps(spec))


module ExampleFunctions

using ..DZOptimization: unsafe_sqrt

function rosenbrock_objective(v::AbstractVector{T}) where {T}
    @inbounds x, y = v[1], v[2]
    t1 = 1 - x
    t2 = y - x * x
    return t1 * t1 + 100 * (t2 * t2)
end

function rosenbrock_gradient!(g::AbstractVector{T},
                              v::AbstractVector{T}) where {T}
    @inbounds x, y = v[1], v[2]
    t1 = 1 - x
    t2 = y - x * x
    @inbounds g[1] = -2 * t1 - 400 * x * t2
    @inbounds g[2] = 200 * t2
    return g
end

function riesz_energy(points::AbstractMatrix{T}) where {T}
    dim, num_points = size(points)
    energy = zero(T)
    @inbounds for i = 2 : num_points
        for j = 1 : i-1
            dist_sq = zero(T)
            @simd ivdep for k = 1 : dim
                dist = points[k,i] - points[k,j]
                dist_sq += dist * dist
            end
            energy += inv(unsafe_sqrt(dist_sq))
        end
    end
    return energy
end

function riesz_gradient!(
        grad::AbstractMatrix{T}, points::AbstractMatrix{T}) where {T}
    dim, num_points = size(points)
    @inbounds for j = 1 : num_points
        @simd ivdep for k = 1 : dim
            grad[k,j] = zero(T)
        end
        for i = 1 : num_points
            if i != j
                dist_sq = zero(T)
                @simd ivdep for k = 1 : dim
                    dist = points[k,i] - points[k,j]
                    dist_sq += dist * dist
                end
                inv_dist_cubed = unsafe_sqrt(dist_sq) / (dist_sq * dist_sq)
                @simd ivdep for k = 1 : dim
                    grad[k,j] += (points[k,i] - points[k,j]) * inv_dist_cubed
                end
            end
        end
    end
    return grad
end

function riesz_hessian!(
        hess::AbstractArray{T,4}, points::AbstractMatrix{T}) where {T}
    dim, num_points = size(points)
    @inbounds for k = 1 : num_points
        for s = 1 : num_points
            if s == k
                for l = 1 : dim
                    @simd ivdep for t = 1 : dim
                        hess[t,s,l,k] = zero(T)
                    end
                end
                for j = 1 : num_points
                    if j != k
                        dist_sq = zero(T)
                        @simd ivdep for d = 1 : dim
                            temp = points[d,k] - points[d,j]
                            dist_sq += temp * temp
                        end
                        dist = unsafe_sqrt(dist_sq)
                        dist_cb = dist * dist_sq
                        for l = 1 : dim
                            @simd ivdep for t = 1 : dim
                                plkj = points[l,k] - points[l,j]
                                ptkj = points[t,k] - points[t,j]
                                temp = (plkj * ptkj) / (dist_sq * dist_cb)
                                hess[t,s,l,k] += (temp + temp + temp)
                            end
                            hess[l,s,l,k] -= inv(dist_cb)
                        end
                    end
                end
            else
                dist_sq = zero(T)
                @simd ivdep for d = 1 : dim
                    temp = points[d,k] - points[d,s]
                    dist_sq += temp * temp
                end
                dist = unsafe_sqrt(dist_sq)
                dist_cb = dist * dist_sq
                for l = 1 : dim
                    @simd ivdep for t = 1 : dim
                        plks = points[l,k] - points[l,s]
                        ptsk = points[t,s] - points[t,k]
                        temp = (plks * ptsk) / (dist_sq * dist_cb)
                        hess[t,s,l,k] = (temp + temp + temp)
                    end
                    hess[l,s,l,k] += inv(dist_cb)
                end
            end
        end
    end
    return hess
end

function constrain_riesz_gradient_sphere!(
        grad::AbstractMatrix{T}, points::AbstractMatrix{T}) where {T}
    dim, num_points = size(points)
    @inbounds for i = 1 : num_points
        overlap = zero(T)
        @simd ivdep for k = 1 : dim
            overlap += points[k,i] * grad[k,i]
        end
        @simd ivdep for k = 1 : dim
            grad[k,i] -= overlap * points[k,i]
        end
    end
    return grad
end

function constrain_riesz_hessian_sphere!(
        hess::AbstractArray{T,4}, points::AbstractMatrix{T},
        unconstrained_grad::AbstractMatrix{T}) where {T}
    dim, num_points = size(points)
    @inbounds for s = 1 : num_points
        for t = 1 : num_points
            for i = 1 : dim
                temp = zero(T)
                @simd ivdep for j = 1 : dim
                    temp += hess[j,s,i,t] * points[j,s]
                end
                @simd ivdep for j = 1 : dim
                    hess[j,s,i,t] -= temp * points[j,s]
                end
            end
        end
        for t = 1 : num_points
            for i = 1 : dim
                temp = zero(T)
                @simd ivdep for j = 1 : dim
                    temp += hess[i,t,j,s] * points[j,s]
                end
                @simd ivdep for j = 1 : dim
                    hess[i,t,j,s] -= temp * points[j,s]
                end
            end
        end
    end
    @inbounds for s = 1 : num_points
        for i = 1 : dim
            gis = unconstrained_grad[i,s]
            pis = points[i,s]
            for j = 1 : dim
                pjs = points[j,s]
                for k = 1 : dim
                    pks = points[k,s]
                    temp = pis * pjs * pks
                    temp = temp + temp + temp
                    if j == k; temp -= pis; end
                    if i == k; temp -= pjs; end
                    if i == j; temp -= pks; end
                    hess[k,s,j,s] += gis * temp
                end
            end
        end
    end
    return hess
end

end # module ExampleFunctions


function compute()
	DIGITS=1000
	prev=BigInt(1)
	cur=BigInt(0)
	index=0;

	while true

		if length(string(cur))>DIGITS
			throw(RuntimeError("Not found"))
		elseif length(string(cur))==DIGITS
			return string(index)
		end

		prev,cur=cur,prev+cur
		index+=1
	end

end

println(compute())


using CrossDecomposition
using Base.Test

# write your own tests here
include("canoncor.jl")


abstract type PeriodicOrbitAlgorithm end
"""
	guessFromHopf(br, ind_hopf, eigsolver::AbstractEigenSolver, M, amplitude; phase = 0)

This function returns several useful quantities regarding a Hopf bifurcation point. More precisely, it returns:
- the parameter value at which a Hopf bifurcation occurs
- the period of the bifurcated periodic orbit
- a guess for the bifurcated periodic orbit
- the equilibrium at the Hopf bifurcation point
- the eigenvector at the Hopf bifurcation point.

The arguments are
- `br`: the continuation branch which lists the Hopf bifurcation points
- `ind_hopf`: index of the bifurcation branch, as in `br.specialpoint`
- `eigsolver`: the eigen solver used to find the eigenvectors
- `M` number of time slices in the periodic orbit guess
- `amplitude`: amplitude of the periodic orbit guess
"""
function guessFromHopf(br, ind_hopf, eigsolver::AbstractEigenSolver, M, amplitude; phase = 0)
	hopfpoint = HopfPoint(br, ind_hopf)
	specialpoint = br.specialpoint[ind_hopf]

	# parameter value at the Hopf point
	p_hopf = hopfpoint.p[1]

	# frequency at the Hopf point
	ωH  = hopfpoint.p[end] |> abs

	# vec_hopf is the eigenvector for the eigenvalues iω
	vec_hopf = geteigenvector(eigsolver, br.eig[specialpoint.idx][2], specialpoint.ind_ev-1)
	vec_hopf ./=  norm(vec_hopf)

	 orbitguess = [real.(hopfpoint.u .+ amplitude .* vec_hopf .* exp(-2pi * complex(0, 1) .* (ii/(M-1) - phase))) for ii=0:M-1]

	return p_hopf, 2pi/ωH, orbitguess, hopfpoint, vec_hopf
end
####################################################################################################
# Amplitude of the u component of the cycle
amplitude(x::AbstractMatrix, n) =  maximum(x[1:n, :]) - minimum(x[1:n, :])

function amplitude(x::AbstractVector, n, M; ratio = 1)
	xc = reshape(x[1:end-1], ratio * n, M)
	amplitude(xc, n)
end

function maximumPOTrap(x::AbstractVector, n, M; ratio = 1)
	xc = reshape(x[1:end-1], ratio * n, M)
	maximum(x[1:n, :])
end


using PiecewiseDeterministicMarkovProcesses, LinearAlgebra, Random, DifferentialEquations, Sundials
	const PDMP = PiecewiseDeterministicMarkovProcesses

function AnalyticalSampleCHV(xc0, xd0, ti, nj::Int64)
	xch = [xc0[1]]
	xdh = [xd0[1]]
	th  = [ti]
	t = ti
	while length(th)<nj
		xc = xch[end]
		xd = xdh[end]
		S = -log(rand())
		if mod(xd,2) == 0
			t += 1/10*log(1+10*S/xc)
			push!(xch,xc + 10 * S )
		else
			t += 1/(3xc)*(exp(3S)-1)
			push!(xch,xc * exp(-3S) )
		end
		push!(xdh,xd + 1 )
		push!(th,t)

		S = -log(rand())
	end
	return th, xch, xdh
end

function F!(ẋ, xc, xd, parms, t)
	if mod(xd[1], 2)==0
		ẋ[1] = 10xc[1]
	else
		ẋ[1] = -3xc[1]^2
	end
end

R(x) = x

function R!(rate, xc, xd, parms, t, issum::Bool)
	# rate function
	if issum == false
		rate[1] = R(xc[1])
		rate[2] = parms[1]
		return 0., parms[1] + 50.
	else
		return R(xc[1]) + parms[1], parms[1] + 50.
	end
end

xc0 = [1.0]
	xd0 = [0, 0]

	nu = [1 0;0 -1]
	parms = [.0]
	ti = 0.322156
	tf = 100000.
	nj = 50

errors = Float64[]

Random.seed!(8)
	res_a_chv = AnalyticalSampleCHV(xc0,xd0,ti,nj)

problem = PDMP.PDMPProblem(F!, R!, nu, xc0, xd0, parms, (ti, tf))
println("\n\nComparison of solvers")
	for ode in [(:cvode,"cvode"),(:lsoda,"lsoda"),(CVODE_BDF(),"CVODEBDF"),(CVODE_Adams(),"CVODEAdams"),(Tsit5(),"tsit5"),(Rodas4P(autodiff=false),"rodas4P-noAutoDiff"),(Rodas4P(),"rodas4P-AutoDiff"),(Rosenbrock23(),"RS23"),(AutoTsit5(Rosenbrock23(autodiff=true)),"AutoTsit5-RS23")]
	Random.seed!(8)
	res =  PDMP.solve(problem, CHV(ode[1]); n_jumps = nj)
	println("--> norm difference = ", norm(res.time - res_a_chv[1], Inf64), "  - solver = ",ode[2])
	push!(errors,norm(res.time - res_a_chv[1],Inf64))
end


module MultRoot
using Polynomials
import PolynomialRoots
using LinearAlgebra
include("../utils.jl")
## The main function here is `MultRoot.multroot`

## Polynomial root finder for polynomials with multiple roots
##
## Based on "Computing multiple roots of inexact polynomials"
## https://doi.org/10.1090/S0025-5718-04-01692-8
## Author: Zhonggang Zeng
## Journal: Math. Comp. 74 (2005), 869-903
##
## Zeng has a MATLAB package `multroot`, from which this name is derived.
## Basic idea is
## 1) for polynomial p we do gcd decomposition p = u * v; p' = u * w. Then roots(v) are the roots without multiplicities.
## 2) can repeat with u to get multiplicities.
##
## This is from Gauss, as explained in paper. Zeng shows how to get u,v,w when the polynomials
## are inexact due to floating point approximations or even model error. This is done in his
## algorithm II.
## 3) Zeng's algorithm I (pejroot) uses the pejorative manifold of Kahan and Gauss-Newton to
## improve the root estimates from algorithm II (roots(v)). The pejorative manifold is defined by
## the multiplicities l and is operationalized in evalG and evalJ from Zeng's paper.

using ..AGCD
monic(p) = p/p[end]
rcoeffs(p) = reverse(p.a)
function proots(zs)
    rs = PolynomialRoots.roots(zs)
    if all(iszero.(imag(rs)))
        return real.(rs)
    else
        return rs
    end
end

## map monic(p) to a point in C^n
## p = 1x^n + a1x^n-1 + ... + an_1 x + an -> (a1,a2,...,an)
function p2a(p::Poly)
    p = monic(p)
    rcoeffs(p)[2:end]
end
function p2a(p::Vector{T}) where {T}
    a,pn = reverse(p[1:end-1]),p[end]
    a .* (1/p[end])
end

## get value of gl(z). From p16
function evalG(zs::Vector, ls::Vector)
    length(zs) == length(ls) || throw("Length mismatch")

    s = prod([poly([z])^l for (z,l) in zip(zs, ls)])  # \prod (x-z_i)^l_i
    p2a(s)
#    rcoeffs(s)[2:end]
end

## get jacobian J_l(z), p16

## get jacobian J_l(z), p16
function evalJ!(J, zs::Vector{T}, ls::Vector) where {T}
    length(zs) == length(ls) || throw("Length mismatch")

    n, m = sum(ls), length(zs)
    u = evalG(zs, ls .- 1)
    pushfirst!(u, one(T))

#    x = Polynomials.variable(T)
#    u = prod((x-z)^(l-1) for (z,l) in zip(zs, ls))  # \prod (x-z_i)^l_i

    for j in 1:m
        s = -ls[j] * u

        for (l, zl) in zip(1:m, zs)
            l == j && continue
            s = AGCD._polymul(s, (one(T), -zl))
        end
    J[:,j] = s#rceffs(s)
    end
    J
end
function evalJ(zs::Vector{T}, ls) where {T}
    n, m = sum(ls), length(zs)
    J = zeros(T, n, m)
    evalJ!(J, zs, ls)
    J
end

## Gauss-Newton iteration to solve weighted least squares problem
## G_l(z) = a, where a is related to monic version of polynomial p
## l is known multiplicity structure of polynomial p = (x-z1)^l1 * (x-z2)^l2 * ... * (x-zn)^ln
## Algorithm I, p17
pejroot(p::Poly, z0, ls; kwargs...) = pejroot(Polynomials.coeffs(p), z0, ls; kwargs...)
function pejroot(p::Vector{T}, z0::Vector{S}, l::Vector{Int};
                 wts::Union{Vector, Nothing}=nothing, # weight vector
                 τ = sqrt(eps(real(T))),
                 maxsteps = 100
                 ) where {T, S <: Union{T, Complex{T}}}

    a = p2a(p)
    λ = min(sqrt(eps(real(T))), norm(p,2) * eps(real(T))^(2/3))
    if wts == nothing
        wts = map(aj -> min(1, 1/abs(aj)), a)
    end

    ## Solve WJ Δz = W(Gl(z) - a) in algorithm I
    zk = copy(z0);
    J = evalJ(zk, l)

    deltak = AGCD.weighted_least_square(J, evalG(zk,l).-a, wts)
    zk .-=  deltak
    δ0 = norm(deltak, 2)

    cvg = false

    for ctr in 1:maxsteps

        AGCD.weighted_least_square!(deltak, evalJ!(J, zk,l), evalG(zk,l).-a, wts)
        zk .-= deltak
        δ1 = norm(deltak, 2)

        Δ = δ0 - δ1

        if Δ < 0 && ctr > 2
            @debug "Growing delta. Best guess is being returned."
            break
        end

        ## add extra abs(delta) < 100*eps() condition
        if δ1^2 < Δ * τ# || δ1 < λ
            cvg = true
            break
        end

        δ0 = δ1
    end

    if !cvg @info ("""The multiplicity count may be in error--the initial guess for the roots failed to improve when refined along the pejorative manifold.""")
        return(z0)
    end
    return(zk)
end

"""
        identify_z0s_ls(p; ...)

Step one of the algorithm identifies initial guesses for the roots and
identifies the pejorative manifold.

"""
function identify_z0s_ls(p::Vector{T};
                         θ::Real=sqrt(eps(real(T))),
                         ρ::Real=cbrt(eps(real(T)))*θ, # initial residual tolerance
                         ϕ::Real=100.0,          # residual tolerance growth factor
                         precondition = false
                         ) where {T}

    q = AGCD._polyder(p)
    if precondition
        p,q, phi, alpha = AGCD.precondition(p,AGCD._polyder(p))
    else
        phi = one(T)
    end

    u_j, v_j, w_j, residual = AGCD.agcd(p, q, θ=θ,  ρ=ρ)

    ## bookkeeping
    ρ = max(ρ, ϕ * norm(residual))
    rs = PolynomialRoots.roots(v_j)
    zs = proots(v_j)
    N = length(zs)
    ls = ones(Int, N)

    p0 = u_j

    while AGCD._degree(p0) > 0

        if AGCD._degree(p0) == 1
            z = proots(p0)[1]
            tmp, ind = findmin(abs.(zs .- z))
            ls[ind] = ls[ind] + 1
            break
        end

        u_j, v_j, w_j, residual= AGCD.agcd(p0, θ=θ, ρ=ρ, maxk=N+1)

        ## need to worry about residual between
        ## u0 * v0 - monic(p0) and u0 * w0 - monic(Polynomials.polyder(p0))
        ## resiudal tolerance grows with m, here it depends on
        ## initial value and previous residual times a growth tolerance, ϕ
        ρ = max(ρ, ϕ * abs(residual))
        ## update multiplicities
        for z in proots(v_j)
            tmp, ind = findmin(abs.(zs .- z))
            ls[ind] = ls[ind] + 1
        end

        ## rename
        p0 = u_j
    end

    # remove preconditioning from roots
    phi * zs, ls, ρ

end

"""
    multroot(p; [θ, ρ, ϕ, δ])

Find roots of polynomial `p`,

The `multroot` function returns the roots and their multiplicities
for `Poly` objects. It performs better than `roots` when the
polynomial has multiplicities.

Based on "Computing multiple roots of inexact polynomials"
Zhonggang Zeng
Journal: Math. Comp. 74 (2005), 869-903
https://doi.org/10.1090/S0025-5718-04-01692-8

Zeng has a MATLAB package `multroot`, from which this name is derived.

Basic idea is:
* for polynomial p we do gcd decomposition p = u * v; p' = u * w. Then roots(v) are the roots without multiplicities.
* can repeat with u to get multiplicities.

The basic idea is from Gauss, as explained in paper. Zeng shows how to get u,v,w when the polynomials
are inexact due to floating point approximations or even model error. This is done in his
algorithm II.

Zeng's algorithm I (pejroot) uses the pejorative manifold of Kahan and Gauss-Newton to
improve the root estimates from algorithm II (roots(v)). The pejorative manifold is defined by
the multiplicities l and is operationalized in `evalG` and `evalJ` from Zeng's paper.

Examples:
```
x = poly([0.0]);
p = (x-1)^4 * (x-2)^3 * (x-3)^3 * (x-4)
multroot(p) # ([4.0, 3.0, 2.0, 1.0], [1, 3, 3, 4])

## For "prettier" printing, results can be coerced to a dict
Dict(k=>v for (k, v) in zip(multroot(p)...))
## Dict{Float64,Int64} with 4 entries:
##   4.0 => 1
##   2.0 => 3
##   1.0 => 4
##   3.0 => 3

## compare to
roots(p)
# 11-element Array{Complex{Float64},1}:
#   4.000000000049711 + 0.0im
#   3.000340992482669 + 0.0005901104690775108im
#   3.000340992482669 - 0.0005901104690775108im
#  2.9993180137669726 + 0.0im
#  2.0006129094631833 + 0.0im
#  1.9996935459268157 + 0.0005303204210266187im
#  1.9996935459268157 - 0.0005303204210266187im
#  1.0006651061397798 + 0.00066713538542472im
#  1.0006651061397798 - 0.00066713538542472im
#  0.9993348938107887 + 0.0006630950464016094im
#  0.9993348938107887 - 0.0006630950464016094im

## Large order polynomials prove difficult. We can't match the claims in Zeng's paper
## as we don't get the pejorative manifold structure right.
p = poly(1.0:7.0);
multroot(p^2) ## should be 1,2,3,4,...,7 all with multplicity 2, but
## ([7.00028, 6.99972, 6.00088, 5.99912, 5.00102, 4.99898, 4.00055, 3.99945, 3.00014, 2.99986, 2.00002, 1.99998, 1.0, 0.999999], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])

## nearby roots can be an issue
delta = 0.00001  ## delta = 0.0001 works as desired.
p = (x-1 - delta)*(x-1)*(x-1 + delta)
multroot(p)
## ([1.0], [3])
```
"""
function multroot(ps::Vector{T};
                  θ::Real=1e-2 * sqrt(eps(real(T))),      # 1e-8 in paper
                  ρ::Real=(eps(real(T)))^(5/6), # 1e-10 in paper
                  ϕ::Real=100.0,             # residual tolerance growth factor
                  τ::Real= θ,       # passed to solve y sigma
                  precondition=true
                  ) where {T}

    p = float.(ps[1:findlast(!iszero,ps)])

    # simple cases
    AGCD._degree(p) == 0 && error("Degree of `p` must be atleast 1")
    if AGCD._degree(p) == 1
        return (-p[1:1]/p[end], [1])
    end

    # two steps
    zs, ls, rho = identify_z0s_ls(p, θ=θ, ρ=ρ, ϕ=ϕ, precondition=precondition)

    if maximum(ls) > 1
        zs = pejroot(p, zs, ls, τ=τ)
    end

    return (zs, ls)

end

## Different interfaces

## can pass in Poly too
multroot(p::Poly; kwargs...) = multroot(Polynomials.coeffs(p); kwargs...)
## Can pass in function
multroot(f::Function; kwargs...) = multroot(as_poly(Float64, f); kwargs...)

end


### A Pluto.jl notebook ###
# v0.16.4

using Markdown
using InteractiveUtils

# ╔═╡ 828b5fdc-2f57-11ec-2ba2-bd6c394912fa
begin
	using PlutoUI
	using LinearAlgebra
	using PrettyTables
end

# ╔═╡ 8c65af87-71e1-45ed-a635-d97a6e6b7a3e
md"""$(PlutoUI.LocalResource("./figs/Fig-ReactorFilter-F21.png"))

__Fig 1: Reactor-Filter problem schematic.__ Starting material A is converted to product B by enzyme E in a well-mixed reactor with volume V. The reactor is well insulated. The output from the reactor is fed into a filtration unit that has no moving parts and is NOT well insulated."""

# ╔═╡ b56695f3-68c1-4b76-8cc8-bb57d7e34699
md"""
Consider the reaction/separation process shown in Fig 1.
    A starting material $A$ is converted to product $B$ by enzyme $E$ in a well-mixed and well-insulated reactor.
    Mixing is achieved in the reactor using a mechanical stirring device.
    The enzyme $E$ is immobilized in the reactor (does not flow out), and is stable. Downstream of the reactor,
    a filtration device separates unreacted starting material $A$ from product $B$.
    The filtration unit has no moving parts, and is NOT well insulated.

 __Assumptions:__
* the reactor and filtration units operate at steady-state;
* neglect changes in the kinetic and potential energy in the systems and streams;
* the volume of the reactor is $V$ = 14 L;
* the density of the systems and streams is constant $\rho$ = 1.2 kg/L.

"""

# ╔═╡ 59a9a683-e2da-4690-9950-80e2bcedbb18
md"""

__Compute:__
* (a) Compute the missing values for the mol and volumetric flow rates if the specific reaction rate $\hat{r}_{1}$ = 6.0 mmol L$^{-1}$ min$^{-1}$.
* (b) Compute the missing mass flow rate values.
* (c) Compute the rate of work $\dot{W}_{sh}$ (kJ/min) required to keep the reactor well mixed.
* (d) Compute the rate of heat flow $\dot{Q}$ (kJ/min) into (or from) the filtration unit.
"""

# ╔═╡ f198f507-e5ca-4ec1-a2bd-648965f94780
md"""
##### a) Compute the missing mol and volumetric flow rates in the table.

###### Volumetric flow rates:
Let's start with the volumetric flow rates. You can compute the volumetic flow rate from the _total mass balance_ equation. At steady state, for _any process unit_ we know that:

$$\sum_{s}v_{s}\dot{m}_{s} = 0$$
However, we know that $\dot{m}_{s}$ = $\rho_{s}\dot{F}_{s}$, where $\rho_{s}$ denotes the density of stream $s$ (units: kg/L), and $\dot{F}_{s}$ denotes the volumetric flow rate for stream $s$ (units: L/min). Thus, we can re-write the total mass balance in terms of the volumetric flow rates:

$$\sum_{s}v_{s}\rho_{s}\dot{F}_{s} = 0$$
In this problem (from the assumptions) we have assumed that the density of all streams was the same (and constant) i.e., $\rho_{1}=\rho_{2}\dots\rho_{\mathcal{S}}\equiv\rho$ which means we can pull the density out of the sum, and divide by $\rho$ to give:

$$\sum_{s}v_{s}\dot{F}_{s} = 0$$

For the __reactor__ we have a single in and a single out, so $\dot{F}_{1}$ = $\dot{F}_{2}$. However, for the __filter__ we have a single in, but two exit streams, thus: $\dot{F}_{2} = \dot{F}_{3}+\dot{F}_{4}$
"""

# ╔═╡ 3e17b4fe-1025-471b-ba7e-f257d3f9ef0b
md"""
###### Mol flow rates:

To compute the missing mol flow rates, we start with the steady-state species mol balance equation (which is true for any process unit):

$$\sum_{s}v_{s}\dot{n}_{k,s}+\dot{n}_{gen,k} = 0$$

where $\dot{n}_{k,s}$ denotes the flow of mols of species $k$ in stream $s$, and
$\dot{n}_{gen,k}$ denotes the generation (reaction) terms for species $k$ that are occuring inside the process unit (system). Lastly, we known (in the absence of cells) that the generation (reaction) term(s) are given by:

$$\dot{n}_{gen,k} = \left(\sum_{r}\sigma_{kr}\hat{r}_{r}\right)V$$

where $\sigma_{kr}$ denotes the stoichiometric coefficient for species $k$ in reaction $r$, and $\hat{r}_{r}$ denotes the _specific reaction rate_ for reaction $r$ (units: mmol/min-L) and $V$ denotes volume (units: L). For this problem, we have a single reaction whose rate $\hat{r}_{1}$ which was given in the problem, the volume $V$ was given, and $\sigma_{11}$ = -1 and $\sigma_{21}$ = +1, where $A$ = 1 and $B$ = 2.

For the __reactor__, the species mol balances are given by:

$$\begin{eqnarray}
\dot{n}_{11} -\dot{n}_{12} -\hat{r}_{1}V &=& 0\\
\dot{n}_{21} -\dot{n}_{22} +\hat{r}_{1}V &=& 0\\
\end{eqnarray}$$

For the __filtration unit__, the species mol balances are given by:

$$\begin{eqnarray}
\dot{n}_{12} -\dot{n}_{13} - \dot{n}_{14} &=& 0\\
\dot{n}_{22} -\dot{n}_{23} - \dot{n}_{24} &=& 0\\
\end{eqnarray}$$
"""

# ╔═╡ 75e41abc-bf6b-42b6-a400-3e68b49e53a1
begin

	# data given in the problem setup -
	n11 = 100.0 	# units: mol/min
	n21 = 0.0 		# units: mol/min
	n24 = 0.0 		# units: mol/min
	F1_dot = 10.0 	# units: L/min
	F3_dot = 4.0 	# units: L/min

	# reaction terms -> change for γ and δ versions of prelim
	r1_hat = 4.0    # units: mmol/min-L
	V = 14.0 		# units: L
	n14 = 8.0 		# units: mol/min

	# show -
	nothing
end

# ╔═╡ 51cf0922-fa58-4335-9f34-ae033203f4df
# matrix vector around reactor and filtration -
begin

	# setup both units - unknowns: n12, n22, n13, n23, F2_dot and F4_dot -
	AT = [-1 0 0 0 0 0; 0 -1 0 0 0 0; 1 0 -1 0 0 0; 0 1 0 -1 0 0; 0 0 0 0 -1 0; 0 0 0 0 1 -1];
	bT = [-n11 + r1_hat*V ; -n21 - r1_hat*V ; n14 ; n24 ; -F1_dot ; F3_dot]
	x_dot = inv(AT)*bT
	n12 = x_dot[1]
	n22 = x_dot[2]
	n13 = x_dot[3]
	n23 = x_dot[4]
	F2_dot = x_dot[5]
	F4_dot = x_dot[6]

	# show -
	nothing
end

# ╔═╡ 99d53a10-dc5e-43ae-8095-4ef594f9c420
begin
	# put all the data into a table, so we can display the solution using the PrettyTable.j package -
	data_table = zeros(4,4)

	# row 1 -
	data_table[1,1] = 1
	data_table[1,2] = n11
	data_table[1,3] = n21
	data_table[1,4] = F1_dot

	# row 2 -
	data_table[2,1] = 2
	data_table[2,2] = n12
	data_table[2,3] = n22
	data_table[2,4] = F2_dot

	# row 3 -
	data_table[3,1] = 3
	data_table[3,2] = n13
	data_table[3,3] = n23
	data_table[3,4] = F3_dot

	# row 4 -
	data_table[4,1] = 4
	data_table[4,2] = n14
	data_table[4,3] = n24
	data_table[4,4] = F4_dot

	with_terminal() do

		# setup the table -
		header_row = (["Stream","n1,i","n2,i","Vol Flow Rate"],["","[mmol/min]","[mmol/min]","[L/min]"]);

		# write the table -
		pretty_table(data_table; header=header_row)
	end
end

# ╔═╡ ccee2491-9a2e-45d2-a607-f27b54ad66ae
md"""
###### b) Compute the missing mass flow rates in the table.

To compute the missing mass flow rates, we apply a total mass balance to each process unit. At steady-state we know that:

$$\sum_{s}v_{s}\dot{m}_{s} = 0$$

For the __reactor__ we have a single in (given) and a single out, thus: $\dot{m}_{1}=\dot{m}_{2}$. However, for the filter we have a single in $\dot{m}_{2}$ but two output streams $\dot{m}_{3}$ and $\dot{m}_{4}$, thus:

$$\dot{m}_{2} = \dot{m}_{3} + \dot{m}_{4}$$

"""

# ╔═╡ c3967197-6c0c-4f7f-ac02-20a95ba921ca
begin

	# setup problem -
	m1_dot = 12.0 	# units: kg/min
	m3_dot = 4.8 	# units: kg/min

	# reactor -
	m2_dot = m1_dot

	# filter -
	m4_dot = m2_dot - m3_dot

	# show -
	nothing
end

# ╔═╡ 230abf7b-db83-49b9-a0d0-124aa2b328e5
begin

	# initialize -
	mass_data_table = zeros(4,4)

	# row 1
	mass_data_table[1,1] = 1
	mass_data_table[1,2] = 1.2
	mass_data_table[1,3] = m1_dot
	mass_data_table[1,4] = 120.0

	# row 2
	mass_data_table[2,1] = 2
	mass_data_table[2,2] = 1.2
	mass_data_table[2,3] = m2_dot
	mass_data_table[2,4] = 200.0

	# row 3
	mass_data_table[3,1] = 3
	mass_data_table[3,2] = 1.2
	mass_data_table[3,3] = m3_dot
	mass_data_table[3,4] = 125.0

	# row 3
	mass_data_table[4,1] = 4
	mass_data_table[4,2] = 1.2
	mass_data_table[4,3] = m4_dot
	mass_data_table[4,4] = 260.0

	with_terminal() do

		# setup the table -
		mass_header_row = (["Stream","density","mdot_i","H"],["","[kg/L]","[kg/min]","[kJ/kg]"]);

		# write the table -
		pretty_table(mass_data_table; header=mass_header_row)

	end
end

# ╔═╡ 00a50f4a-838e-4f5a-b01d-0a754ca5a256
md"""
###### c) Compute the rate of work $\dot{W}_{sh}$ (kJ/min) required to keep the reactor well mixed.

To compute the rate of work, we start from the steady-state open energy balance equation:

$$\dot{Q}+\dot{W}_{sh}+\sum_{s}v_{s}\dot{m}_{s}H_{s} = 0$$

and throw out (or simplify) terms that don't apply to reactor process unit. First,
for the reactor we have a single input and a single output, thus $\dot{m}_{1}=\dot{m}_{2}$. Next, the reactor is well-insulated, thus, we know that $\dot{Q}$ = 0. Putting these ideas together gives:

$$\dot{W}_{sh} + \dot{m}_{1}\left(H_{1}-H_{2}\right) = 0$$ or (after we solve for work):

$$\dot{W}_{sh} = \dot{m}_{1}\left(H_{2}-H_{1}\right)$$.
"""

# ╔═╡ 1f41d0db-abb1-48c6-8651-655ba4f73686
begin
	# alias the H's
	H₁ = mass_data_table[1,4];
	H₂ = mass_data_table[2,4];
	H₃ = mass_data_table[3,4];
	H₄ = mass_data_table[4,4];

	# compute the ΔH -
	ΔH = (H₂ - H₁)

	# compute the work -
	W = m1_dot*ΔH
end

# ╔═╡ ddaabaaa-f1dd-4853-9412-f7972dc4dc6d
md"""
###### d) Compute the rate of heat flow $\dot{Q}$ (kJ/min) into (or from) the filtration unit.

To compute the rate of heat transfer, we start from the steady-state open energy balance equation:

$$\dot{Q}+\dot{W}_{sh}+\sum_{s}v_{s}\dot{m}_{s}H_{s} = 0$$

and throw out (or simplify) terms that don't apply to filtration process unit.
First, the filtration unit has no moving parts, thus, the rate of shaft work $\dot{W}_{sh}$. However, unlike the reactor, we have a single input but multiple outputs, thus, the energy balance for the filtration unit is given by:

$$\dot{Q} + \dot{m}_{2}H_{2} - \dot{m}_{3}H_{3} - \dot{m}_{4}H_{4} = 0$$

All the enthalpies were given in the problem, and we know the $\dot{m}_{s}$ from part b). To compute $\dot{Q}$:

$$\dot{Q} = \dot{m}_{3}H_{3} + \dot{m}_{4}H_{4} - \dot{m}_{2}H_{2}$$

"""

# ╔═╡ 8196a83d-b705-479a-b8fd-551f1ef95bd3
begin
	# compute the Qdot -
	Q_dot = m3_dot*H₃ + m4_dot*H₄ - m2_dot*H₂
end

# ╔═╡ db72171c-3964-4992-8d00-c9f8217a829d
html"""
<style>
main {
    max-width: 1200px;
    width: 95%;
    margin: auto;
    font-family: "Roboto, monospace";
}

a {
    color: blue;
    text-decoration: none;
}

.H1 {
    padding: 0px 30px;
}
</style>"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"

[compat]
PlutoUI = "~0.7.16"
PrettyTables = "~1.2.2"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[HypertextLiteral]]
git-tree-sha1 = "f6532909bf3d40b308a0f360b6a0e626c0e263a8"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.1"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "98f59ff3639b3d9485a03a72f3ab35bab9465720"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.6"

[[PlutoUI]]
deps = ["Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "4c8a7d080daca18545c56f1cac28710c362478f3"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.16"

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "69fd065725ee69950f3f58eceb6d144ce32d627d"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
"""

# ╔═╡ Cell order:
# ╟─8c65af87-71e1-45ed-a635-d97a6e6b7a3e
# ╟─b56695f3-68c1-4b76-8cc8-bb57d7e34699
# ╟─59a9a683-e2da-4690-9950-80e2bcedbb18
# ╟─f198f507-e5ca-4ec1-a2bd-648965f94780
# ╟─3e17b4fe-1025-471b-ba7e-f257d3f9ef0b
# ╠═75e41abc-bf6b-42b6-a400-3e68b49e53a1
# ╠═99d53a10-dc5e-43ae-8095-4ef594f9c420
# ╟─51cf0922-fa58-4335-9f34-ae033203f4df
# ╟─ccee2491-9a2e-45d2-a607-f27b54ad66ae
# ╠═c3967197-6c0c-4f7f-ac02-20a95ba921ca
# ╠═230abf7b-db83-49b9-a0d0-124aa2b328e5
# ╟─00a50f4a-838e-4f5a-b01d-0a754ca5a256
# ╠═1f41d0db-abb1-48c6-8651-655ba4f73686
# ╟─ddaabaaa-f1dd-4853-9412-f7972dc4dc6d
# ╠═8196a83d-b705-479a-b8fd-551f1ef95bd3
# ╠═828b5fdc-2f57-11ec-2ba2-bd6c394912fa
# ╟─db72171c-3964-4992-8d00-c9f8217a829d
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


cd(@__DIR__); include("setups/grid7x3.jl")
pyplot(dpi = 200)

## (a) eigenvectors by nondecreasing eigenvalue ordering
plot(layout = Plots.grid(3, 7))
for i in 1:N
    heatmap!(reshape(𝚽[:, i], (Nx, Ny))', c = :viridis, cbar = false,
                clims = (-0.4,0.4), frame = :none, ratio = 1, ylim = [0, Ny + 1],
                title = latexstring("\\phi_{", i-1, "}"), titlefont = 12,
                subplot = i)
end
plt = current()
savefig(plt, "../figs/grid7x3_evsp_title.png")

## (b) eigenvectors by natural frequency ordering
plot(layout = Plots.grid(3, 7))
for i in 1:N
    k = grid2eig_ind[i]
    heatmap!(reshape(𝚽[:,k], (Nx, Ny))', c = :viridis, cbar = false,
                clims = (-0.4,0.4), frame = :none, ratio = 1, ylim = [0, Ny + 1],
                title = latexstring("\\varphi_{", string(eig2dct[k,1]),
                ",", string(eig2dct[k,2]), "}"), titlefont = 12, subplot = i)
end
plt = current()
savefig(plt, "../figs/grid7x3_dct_title2.png")


@testset verbose=true "Interpolation" begin
    @testset verbose=true "Unflip" begin
        Random.seed!(1234)
        function measure_flips(q)
            flips = 0
            for i in 2:size(q, 1)
                if q[i].w*q[i-1].w+q[i].x*q[i-1].x+q[i].y*q[i-1].y+q[i].z*q[i-1].z < 0
                    flips += 1
                end
            end
            flips
        end

        @testset verbose=true "$T" for T in FloatTypes
            q = randn(Quaternion{T}, 100_000);
            flips = measure_flips(q)
            @test flips > 0
            p = unflip(q)
            flips = measure_flips(q)
            @test flips > 0
            flips = measure_flips(p)
            @test flips == 0
            unflip!(q)
            flips = measure_flips(q)
            @test flips == 0

            q = randn(Quaternion{T}, 10_000, 3);
            flips = sum(measure_flips(q[:, i]) for i in 1:3)
            @test flips > 0
            p = unflip(q)
            flips = sum(measure_flips(q[:, i]) for i in 1:3)
            @test flips > 0
            flips = sum(measure_flips(p[:, i]) for i in 1:3)
            @test flips == 0
            unflip!(q)
            flips = sum(measure_flips(q[:, i]) for i in 1:3)
            @test flips == 0

            q = randn(Quaternion{T}, 3, 1_000, 3);
            flips = sum(measure_flips(q[i, :, j]) for i in 1:3 for j in 1:3)
            @test flips > 0
            p = unflip(q, dim=2)
            flips = sum(measure_flips(q[i, :, j]) for i in 1:3 for j in 1:3)
            @test flips > 0
            flips = sum(measure_flips(p[i, :, j]) for i in 1:3 for j in 1:3)
            @test flips == 0
            unflip!(q, dim=2)
            flips = sum(measure_flips(q[i, :, j]) for i in 1:3 for j in 1:3)
            @test flips == 0
        end
    end

    @testset verbose=true "Slerp" begin
        ϵ = 20eps()
        comp = [0, 1, -1]
        Rs = [
            [Rotor(w, x, y, z) for w in comp for x in comp for y in comp for z in comp][2:end]...;
            randn(RotorF64, 20)...
        ]
        directions = [Rotor(1), Rotor(imx), Rotor(imy), Rotor(imz), Rotor(-imx), Rotor(-imy), Rotor(-imz)]

        # Check extremes
        for Q1 in directions
            @test distance(slerp(Q1, Q1, 0.0), Q1) < ϵ
            @test distance(slerp(Q1, Q1, 1.0), Q1) < ϵ
            @test distance(slerp(Q1, -Q1, 0.0), Q1) < ϵ
            @test distance(slerp(Q1, -Q1, 1.0), Q1) < ϵ
            for Q2 in directions
                @test distance(slerp(Q1, Q2, 0.0), Q1) < ϵ
                @test distance(slerp(Q1, Q2, 1.0), Q2) < ϵ
                @test distance(slerp(Q1, -Q2, 0.0), Q1) < ϵ
                @test distance(slerp(Q1, -Q2, 1.0), -Q2) < ϵ
                @test distance(slerp(Q2, Q1, 0.0), Q2) < ϵ
                @test distance(slerp(Q2, Q1, 1.0), Q1) < ϵ
            end
        end

        # Test simple rotations about each axis
        for Q2 in directions[2:end]
            for t in LinRange(0.0, 1.0, 100)
                @test distance(
                    slerp(Rotor(1), Q2, t),
                    Rotor(cos(π * t / 2) + sin(π * t / 2) * Q2)
                ) < ϵ
            end
        end

        # Test that (slerp of rotated rotors) is (rotated slerp of rotors)
        for R in Rs
            for Q2 in directions[1:end]
                for t in LinRange(0.0, 1.0, 100)
                    @test distance(
                        R * slerp(Rotor(1), Q2, t),
                        slerp(R, R*Q2, t)
                    ) < ϵ
                end
            end
        end

        # Check that unflipping works
        for Q in directions
            for t in LinRange(0.0, 1.0, 10)
                @test distance(slerp(Q, -Q, t, unflip=true), Q) < ϵ
            end
        end

    end

    @testset verbose=true "Squad" begin
        ϵ = 20eps()
        comp = [0, 1, -1]
        Rs = [
            [Rotor(w, x, y, z) for w in comp for x in comp for y in comp for z in comp][2:end]...;
            randn(RotorF64, 20)...
        ]
        directions = [Rotor(1), Rotor(imx), Rotor(imy), Rotor(imz), Rotor(-imx), Rotor(-imy), Rotor(-imz)]

        ω = 0.1
        tin = collect(LinRange(-10, 10, 201))
        Rin = [exp(ω*ti*imz/2) for ti in tin]
        tout = (tin[1:end-1] + tin[2:end]) / 2
        Rout = squad(Rin, tin, tout, validate=true)
        Ravg = [exp(ω*ti*imz/2) for ti in tout]
        @test maximum(distance.(Rout, Ravg)) < 2eps()
        random_signs = [1; rand([-1, 1], length(Rin)-1)...]  # First one must be 1
        Rin_flipped = [Rotor(r*R) for (r, R) in zip(random_signs, Rin)]
        @test Rout == squad(Rin_flipped, tin, tout, validate=true, unflip=true)
        Rout = squad(Rin, tin, tin, validate=true)
        @test maximum(distance.(Rout, Rin)) < 2eps()
        @test distance(Rotor(1), squad(Rin, tin, 0, validate=true)) == 0
        @test distance(Rin[1], squad(Rin, tin, tin[1], validate=true)) == 0
        @test distance(Rin[end], squad(Rin, tin, tin[end], validate=true)) == 0
        @test squad(Rin, tin, Vector{eltype(tin)}()) == Vector{eltype(Rin)}()

        t_in = LinRange(0.0, 1.0, 13)
        t_out = LinRange(0.0, 1.0, 37)
        t_out2 = sort(rand(59))
        # squad interpolated onto the inputs should be the identity
        for R1 in Rs
            for R2 in Rs
                R_in = [slerp(R1, R2, t) for t in t_in]
                @test squad(R_in, t_in, t_in) ≈ R_in atol=ϵ
            end
        end

        # squad should be the same as slerp for linear interpolation
        for R in directions
            R_in = [slerp(Rotor(1), R, t) for t in t_in]
            R_out_squad = squad(R_in, t_in, t_out)
            R_out_slerp = [slerp(Rotor(1), R, t) for t in t_out]
            @test R_out_squad ≈ R_out_slerp atol=ϵ
            R_out_squad = squad(R_in, t_in, t_out2)
            R_out_slerp = [slerp(Rotor(1), R, t) for t in t_out2]
            @test R_out_squad ≈ R_out_slerp atol=ϵ
        end
    end

    @testset verbose=true "Squad derivatives" begin
        R, ω⃗, Ṙ = precessing_nutating_example()
        tin = collect(range(0, 1, length=1000))
        for tout in [tin, tin[498]]
            Rsquad, ω⃗squad, Ṙsquad = squad(
                Rotor.(R.(tin)), tin, tout,
                compute_angular_velocity=true, compute_derivative=true
            )
            ω⃗eval = ω⃗.(tout)
            Ṙeval = Ṙ.(tout)
            @test ω⃗squad ≈ ω⃗eval rtol=1e-6
            @test Ṙsquad ≈ Ṙeval rtol=1e-6
            Rsquad2, ω⃗squad2 = squad(
                Rotor.(R.(tin)), tin, tout,
                compute_angular_velocity=true
            )
            @test Rsquad == Rsquad2
            @test ω⃗squad == ω⃗squad2
            Rsquad3, Ṙsquad3 = squad(
                Rotor.(R.(tin)), tin, tout,
                compute_derivative=true
            )
            @test Rsquad == Rsquad3
            @test Ṙsquad == Ṙsquad3
        end
    end
end


using  jInv.Mesh
using DelimitedFiles
using  jInv.Utils
using jInvSeismic.FWI
using jInvSeismic.Utils

function readModelAndGenerateMeshMrefMarmousi(readModelFolder::String,modelFilename::String,dim::Int64,pad::Int64,domain::Vector{Float64},newSize::Vector=[],velBottom::Float64=1.75,velHigh::Float64=2.9)
########################## m,mref are in Velocity here. ###################################

if dim==2
	# SEGmodel2Deasy.dat
	m = readdlm(string(readModelFolder,"/",modelFilename));
	m = m*1e-3;
	m = Matrix(m');
	mref = getSimilarLinearModel(m,velBottom,velHigh);
else
	# 3D SEG slowness model
	file = matopen(string(readModelFolder,"/",modelFilename)); DICT = read(file); close(file);
	m = DICT["VELs"];
	m = m*1e-3;
	mref = getSimilarLinearModel(m,velBottom,velHigh);
end

sea_level = 1.5;
sea = abs.(m[:] .- 1.5) .< 1e-3;
mref[sea] = m[sea];
if newSize!=[]
	m    = expandModelNearest(m,   collect(size(m)),newSize);
	mref = expandModelNearest(mref,collect(size(mref)),newSize);
end

Minv = getRegularMesh(domain,collect(size(m)));

(mPadded,MinvPadded) = addAbsorbingLayer(m,Minv,pad);
(mrefPadded,MinvPadded) = addAbsorbingLayer(mref,Minv,pad);

mPaddedNoSalt = copy(mPadded);
mPaddedNoSalt[mPaddedNoSalt .> 3.8] .= 3.8;

mrefPadded = smoothModel(mPaddedNoSalt,[],250);
sea_level = 1.5;
sea = abs.(mPadded[:] .- 1.5) .< 1e-2;
mrefPadded[sea] = mPadded[sea];

mrefRowsNum = size(mrefPadded, 1)
mrefPadded = repeat(mean(mrefPadded,dims=1), outer=(mrefRowsNum, 1))

N = prod(MinvPadded.n);
boundsLow  = minimum(mPadded);
boundsHigh = maximum(mPadded);

boundsLow  = ones(N)*boundsLow;
boundsLow = convert(Array{Float32},boundsLow);
boundsHigh = ones(N)*boundsHigh;
boundsHigh = convert(Array{Float32},boundsHigh);

return (mPadded,MinvPadded,mrefPadded,boundsHigh,boundsLow);
end

dim     = 2;
pad     = 50;
jumpSrc    = 4;
jumpRcv = 1
offset  = 1000;
domain = [0.0,9.192,0.0,2.904]; # without the pad for Marmousi 1
newSize = [550,200];
modelDir = pwd();
(m,Minv,mref,boundsHigh,boundsLow) = readModelAndGenerateMeshMrefMarmousi(modelDir,"examples/MarmousiModelWithoutPad.dat",dim,pad,domain,newSize,1.25,4.0);


function main()
    S = readline()
    N = length(S)

    cnt = 0
    for i = 1:N, j = i:N
        space_left = max(0, i - 2)
        space_right = max(0, N - j - 1)
        cnt += parse(Int, S[i:j]) * 2^(space_left + space_right)
    end

    println(cnt)
end

main()


abstract type AbstractMetric end

abstract type SingleRecommendMetric <: AbstractMetric end

abstract type TopKMetric <: SingleRecommendMetric end

struct Recall <: TopKMetric
    k::Int
    name::AbstractString

    Recall(k::Int) = new(k, "recall")
end

function (recall::Recall)(recommend_list, ground_truth)
    if recommend_list === nothing || length(recommend_list) == 0
        return 0.0
    end

    k = min(recall.k, length(recommend_list))
    truncated_recom = recommend_list[1:k]
    num_hits = length(intersect(truncated_recom, ground_truth))
    return num_hits / length(ground_truth)
end

struct Precision <: TopKMetric
    k::Int
    name::AbstractString

    Precision(k::Int) = new(k, "precision")
end

function (precision::Precision)(recommend_list, ground_truth)
    if recommend_list === nothing || length(recommend_list) == 0
        return 0.0
    end

    k = min(precision.k, length(recommend_list))
    truncated_recom = recommend_list[1:k]
    num_hits = length(intersect(truncated_recom, ground_truth))
    return num_hits / length(truncated_recom)
end

function _dcg(relevances)
    result = 0.0
    for i in eachindex(relevances)
        result += (2^relevances[i] - 1) / log2(1.0 + i)
    end
    return result
end

struct DCG <: TopKMetric
    k::Int
    name::AbstractString

    DCG(k::Int) = new(k, "dcg")
end

function (dcg::DCG)(recommend_list, ground_truth, true_relevance = nothing)
    if recommend_list === nothing || length(recommend_list) == 0
        return 0.0
    end

    k = min(dcg.k, length(recommend_list))
    truncated_recom = recommend_list[1:k]

    if true_relevance === nothing
        true_relevance = ones(length(ground_truth))
    end

    relevances = zeros(k)
    for i = 1:k
        j = findfirst(truncated_recom[i] .== ground_truth)
        if !(j === nothing)
            relevances[i] = true_relevance[j]
        end
    end
    return _dcg(relevances)
end

struct NDCG <: TopKMetric
    k::Int
    name::AbstractString

    NDCG(k::Int) = new(k, "ndcg")
end

function (ndcg::NDCG)(recommend_list, ground_truth, true_relevance = nothing)
    if recommend_list === nothing || length(recommend_list) == 0
        return 0.0
    end

    k = min(ndcg.k, length(recommend_list))
    truncated_recom = recommend_list[1:k]

    if true_relevance === nothing
        true_relevance = ones(length(ground_truth))
    end

    relevances = zeros(k)
    for i = 1:k
        j = findfirst(truncated_recom[i] .== ground_truth)
        if !(j === nothing)
            relevances[i] = true_relevance[j]
        end
    end
    dcg = _dcg(relevances)

    # Note: Table 3 of [Dacrema, Cremonesi and Jannach, 2019]("Are we really...")
    # probably adopts n = length(true_relevance) for IDCG.
    # I believe it is not suitable and use instead the definition below
    # This formulation is seen, for instance, in
    # [Rendle 2021]("Item Recommendation from Implicit Feedback")
    n = min(length(ground_truth), k)
    idcg = _dcg(sort(true_relevance, rev = true)[1:n])

    return dcg / idcg
end

#only for implicit recommendation
struct MeanMetric{T<:SingleRecommendMetric} <: AbstractMetric
    base_metric::T
end

function (metric::MeanMetric)(recommend_lists, ground_truth_list)
    result = 0.0
    for (recom, gt) in zip(recommend_lists, ground_truth_list)
        result += metric.base_metric(recom, gt)
    end
    return result / length(recommend_lists)
end

@doc raw"""
    MeanRecall(k)

Create callbale struct to compute Recall@k averaged over all predictions. Recall@k is defined by
```math
\mathrm{Recall@k} = \frac{|(\text{ground truth}) \cap (\text{top k prediction})|}{|(\text{ground truth})|}
```

# Example

```julia
recall10 = MeanRecall(10)
recall10(predictions, ground_truth)
```
"""
MeanRecall(k) = MeanMetric(Recall(k))

@doc raw"""
    MeanPrecision(k)

Create callbale struct to compute Precision@k averaged over all predictions. Precision@k is defined by
```math
\mathrm{Precision@k} = \frac{|(\text{ground truth}) \cap (\text{top k prediction})|}{k}
```

# Example

```julia
prec10 = MeanPrecision(10)
prec10(predictions, ground_truth)
```
"""
MeanPrecision(k) = MeanMetric(Precision(k))

@doc raw"""
    MeanDCG(k)

Create callbale struct to compute DCG@k averaged over all predictions. DCG@k is defined by
```math
\mathrm{DCG@k} = \sum_{i=1}^{\mathrm{min}(k, \mathrm{length}(\text{prediction}))} \frac{2^{r_i}-1}{\log(i+1)}\,,
```
where ``r_i`` is the true relevance for the i-th predicted item (binary for implicit feedback).

# Example

```julia
dcg10 = MeanDCG(10)
dcg10(predictions, ground_truth)
```
"""
MeanDCG(k) = MeanMetric(DCG(k))

@doc raw"""
    MeanNDCG(k)

Create callbale struct to compute NDCG@k averaged over all predictions. NDCG@k is defined by
```math
\mathrm{NDCG@k} = \frac{\mathrm{DCG}@k}{\mathrm{IDCG}@k}
```
where IDCG is the ideal DCG, prediction sorted by true relevance. Note that if the number of ground truth items is smaller than ``k``, the predicted item list is truncated to that length.

# Example

```julia
ndcg10 = MeanNDCG(10)
ndcg10(predictions, ground_truth)
```
"""
MeanNDCG(k) = MeanMetric(NDCG(k))

name(metric::MeanMetric) = Symbol(metric.base_metric.name, metric.base_metric.k)


"""
    random_init(
        ncellx,ncelly,
        mean_ρ,range_ρ,
        mean_θ,range_θ;
        bcond_x="periodic",bcond_y="periodic"
    )

Sample a random data `(ρ,u,v)` of size `(ncellx+2)*(ncelly+2)`. The density `ρ` is sampled
uniformly around `mean_ρ` in an interval of size `range_ρ`. The velocity component `u` and
`v` are respectively given by `cos(θ)` and `sin(θ)` where `θ` is sampled uniformly around
`mean_θ` in an interval of size `range_θ`. Boundary conditions are then applied.
"""
function random_init(
    ncellx,ncelly,
    mean_ρ,range_ρ,
    mean_θ,range_θ;
    bcond_x="periodic",bcond_y="periodic"
)
    ρ = mean_ρ .+ range_ρ .* (rand(ncellx+2,ncelly+2) .- 0.5)
    θ = mean_θ .+ range_θ .* (rand(ncellx+2,ncelly+2) .- 0.5)
    u = cos.(θ)
    v = sin.(θ)
    boundary_conditions_ρuv!(ρ,u,v,bcond_x,bcond_y)
    return ρ,u,v
end

"""
    quadratic_potential_force(ncellx,ncelly,Δx,Δy,C)

Return the components `Fx` and `Fy` of the force exerted by the quadratic radial
potential V(r) = Cr²/2 centered around the midpoint of the box `[0,Lx]*[0,Ly]`
where `Lx = ncellx * Δx` and `Ly = ncelly * Δy`. For convenience, the outputs `Fx` and `Fy` are
matrices of size `(ncellx+2)*(ncelly+2)` with zeros on the boundary.
"""
function quadratic_potential_force(ncellx,ncelly,Δx,Δy,C)
    Fx = zeros(ncellx+2,ncelly+2)
    Fy = zeros(ncellx+2,ncelly+2)
    Lx = ncellx * Δx
    Ly = ncelly * Δy
    for i in 2:ncellx+1
        for j in 2:ncelly+1
            mx = Lx/2
            my = Ly/2
            xi = (i-2)*Δx + Δx/2
            yj = (j-2)*Δy + Δy/2
            Fx[i,j] = -C * (xi - mx)
            Fy[i,j] = -C * (yj - my)
        end
    end
    return Fx, Fy
end
"""
    flat_quadratic_potential_force(ncellx,ncelly,Δx,Δy,r0,C)

Return the components `Fx` and `Fy` of the force exerted by the quadratic radial
potential defined by V(r) = Cr²/2 for r>r0 and V(r)=0 otherwise,
centered around the midpoint of the box `[0,Lx]*[0,Ly]` where `Lx = ncellx * Δx`
and `Ly = ncelly * Δy`. For convenience, the outputs `Fx` and `Fy` are
matrices of size `(ncellx+2)*(ncelly+2)` with zeros on the boundary.
"""
function flat_quadratic_potential_force(ncellx,ncelly,Δx,Δy,r0,C)
    Fx = zeros(ncellx+2,ncelly+2)
    Fy = zeros(ncellx+2,ncelly+2)
    Lx = ncellx * Δx
    Ly = ncelly * Δy
    for i in 2:ncellx+1
        for j in 2:ncelly+1
            mx = Lx/2
            my = Ly/2
            xi = (i-2)*Δx + Δx/2
            yj = (j-2)*Δy + Δy/2
            rij = sqrt((xi-mx)^2+(yj-my)^2)
            if rij > r0
                Fx[i,j] = -C * (rij - r0) * (xi - mx)/rij
                Fy[i,j] = -C * (rij - r0) * (yj - my)/rij
            end
        end
    end
    return Fx, Fy
end


# various recipes for Plots.jl

"""
   trimplot(f, a, b, c=20; kwargs...)

Plot f over [a,b] but break graph if it exceeds c in absolute value.
"""
@userplot TrimPlot
@recipe function __(a::TrimPlot)
    if length(a.args) < 4
        f, x0, x1 = a.args
        c = 20
    else
        f, x0, x1, c = a.args
    end

    xs = range(x0, x1, length=251)
    ys = f.(xs)

    Xs = Any[]
    Ys = Any[]

    us, vs = Real[], Real[]
    for (x,y) in zip(xs, ys)
        if abs(y) <= c
            push!(us, x); push!(vs, y)
        else
            if length(us) > 0
                push!(Xs, copy(us))
                push!(Ys, copy(vs))
            end
            empty!(us); empty!(vs)
        end
    end
    if length(us) > 0
        push!(Xs, copy(us))
        push!(Ys, copy(vs))
    end

    seriestype := :path
    x := Xs
    y := Ys

    Xs, Ys
end

"""
    plotif(f, g, a, b)

Plot f colored depending on g < 0 or not.
"""
@userplot PlotIf
@recipe function __(a::PlotIf)
    f, g, x0, x1 = a.args
    xs = range(x0, x1, length=251)
    ys = f.(xs)

    seriestype := :path
    x := xs
    y := ys
    color := [gx < 0 ? :red : :blue for gx in g.(xs)]
    linewidth := 5
    legend := false

    xs, ys
end

@userplot SignChart
@recipe function __(a::SignChart)
    f, x0, x1 = a.args
    xs = range(x0, x1, length=251)
    ys = f.(xs)
    zs = zeros(length(xs))
    @series begin
        seriestype := :path
        x := xs
        y := zs
        linewidth := 1
        color := :black
        xs, zs
    end

    seriestype := :path
    x := xs
    y := ys
    color := [y < 0 ? :red : :blue for y in ys]

    linewidth --> 5
    legend --> false

    xs, ys
end

#### Multidimensional

# """
#     plot_polar(r, a, b; [n=251]

# Create a polar plot of `r(theta)` for `a <= theta <= b`.

# An alternative to the three step approach:

# ```
# ts = range(a, b, length=n)
# rs = r.(ts)
# plot(ts, rs, proj=:polar, legend=false)
# ```

# """
# @userplot Plot_Polar
# @recipe function __(rt::Plot_Polar; n=251)

#     r, a, b = rt.args
#     fn = t -> r(t) * [cos(t), sin(t)]
#     ts = range(a, stop=b, length=n)
#     xs = fn.(ts)

#     xys = unzip(xs)
#     seriestype := :path
#     x := xys[1]
#     y := xys[2]
#     xys
# end

# """
#     plot_parametric_curve(r, a, b; [n=251])

# For `r` taking `R -> R^n` make a plot over [a,b] using n points.

# There are other ways to produce this plot:
# * if we have two functions, `rx` and `ry` say, then passing as function arguments should work

# ```
# plot(rx, ry, a, b)
# ```

# * if we have a vector-valued function, then either of these work:

# ```
# r(t) = [cos(t), sin(t), t]
# plot(unzip(r, a, b, n)...)  # unzip reshapes the data
# plot_parametric_curve(r, a, b)
# ```

# We see that this function is a convenience for vector-valued descriptions of parameterized curves.
# """
# @userplot Plot_Parametric_Curve
# @recipe function __(r::Plot_Parametric_Curve; n=251)

#     f, a, b = r.args
#     ts = range(a, stop=b, length=n)
#     xs = f.(ts)

#     n = length(first(xs))
#     if n == 2
#         xys = unzip(xs)
#         seriestype := :path
#         x := xys[1]
#         y := xys[2]
#         xys
#     else
#         xyzs = unzip(xs)
#         seriestype := :path3d
#         x := xyzs[1]
#         y := xyzs[2]
#         z := xyzs[3]
#         xyzs
#     end
# end

# """
#     plot_parametric_surface(F, [xlim=(-5,5)], [ylim=(-5,5)], [nx=50], [ny=50])

# Plot the paremeterized surface described by `F:R^2 -> R^3`. This works with `pyplot` and `plotly`, but not `gr`.

# A surface `(x,y,f(x,y))` can be directly plotted with `surface` as:

# ```
# surface(xs, ys, f)
# ```

# where `xs` and `ys` provide a grid to plot over.

# For parametrically described surfaces, the above doesn't work. This function provides an interface:

# ```
# F(u,v) = [u*cos(v), u*sin(v), u] # a cone
# plot_parametric_surface(F, xlim=(0, 1), ylim=(0,2pi))
# ```

# The values of `xlim` are used as the range to plot over; `nx` specifies the number of points to use. Similarly for `ylim`.
# """
# @userplot Plot_Parametric_Surface
# @recipe function __(r::Plot_Parametric_Surface; nx=50, ny=50)

#     F = first(r.args)

#     xlim = get(plotattributes,:xlims, (-5,5))
#     ylim = get(plotattributes,:ylims, (-5,5))

#     us = range(xlim[1], xlim[2], length=nx)
#     vs = range(ylim[1], ylim[2], length=ny)
#     ws = unzip(F.(us, vs'))

#     seriestype := :surface
#     x := ws[1]
#     y := ws[2]
#     z := ws[3]
#     xlims := extrema(ws[1])
#     ylims := extrema(ws[2])
#     ws

# end

"""
   arrow(p, v)

Add vector, `v`, to plot anchored at point `p`.

Example
```
Fn = parametric(t -> [2cos(t), 3sin(t)])
Fnp = t -> ForwardDiff.derivative(Fn, t)
p = plot(Fn, 0, 2pi, legend=false)
for t in 0:pi/4:pi
   arrow!(Fn(t), Fnp(t))
end
p
```
"""
@userplot Arrow
@recipe function __(a::Arrow)
    v0, dv0 = a.args
    v = [v0...]; dv = [dv0...]
    if length(v) == 2
        seriestype := :quiver
        quiver := unzip([dv])
        x,y = unzip([v])
        x := x
        y := y
        ()
    else
        seriestype := :path3d
        M = hcat(v, v + dv)'
        (M[:,1],M[:,2], M[:,3])
    end

end

"""
    vectorfieldplot(F; [xlim=(-5,5)], [ylim=(-5,5)], [nx=8], [ny=8])

Create a vector field plot using a grid described by `xlim`, `ylim` with `nx` and `ny` grid points in each direction.

```
F(x,y) = [-y, x]
vectorfieldplot(F, xlim=(-4,4), ylim=(-4,4))
```
"""
@userplot VectorFieldPlot
@recipe function __(V::VectorFieldPlot; nx=8, ny=8)

    ars = V.args
    F = ars[1]
    xlim = get(plotattributes,:xlims, (-5,5))
    ylim = get(plotattributes,:ylims, (-5,5))

    dx, dy = (xlim[2]-xlim[1])/(nx-1), (ylim[2]-ylim[1])/(ny-1)
    xs, ys = xlim[1]:dx:xlim[2], ylim[1]:dy:ylim[2]

    ps = [[x,y] for x in xs for y in ys]
    vs = (xy -> F(xy[1], xy[2])).(ps)

    λ = 0.9 * minimum([u/maximum(getindex.(vs,i)) for (i,u) in enumerate((dx,dy))])

    seriestype := :quiver
    quiver := unzip(λ * vs)
    xs, ys = unzip(ps)
        x := xs
    y := ys
    ()
end

"""
    vectorfieldplot3d(F; [xlim=(-5,5)], [ylim=(-5,5)], [nx=5], [ny=5])

Create a 3 dimensional vector field plot using a grid described by `xlim`, `ylim`, `zlim` with `nx`, `ny`, and `nz` grid points in each direction.

Note: the vectors are represented with line, not arrow due to no implementation of `:quiver3d`.

```
F(x,y,z) = [-y, x,z]
vectorfieldplot3d(F, xlims=(-4,4), ylims=(-4,4), zlims=(0,3))
```
"""
@userplot VectorFieldPlot3d
@recipe function __(V::VectorFieldPlot3d;  nx=5, ny=5, nz=5)
    ars = V.args
    F = ars[1]
    xlim = get(plotattributes,:xlims, (-5,5))
    ylim = get(plotattributes,:ylims, (-5,5))
    zlim = get(plotattributes,:zlims, (-5,5))

    dx, dy, dz = (xlim[2]-xlim[1])/(nx-1), (ylim[2]-ylim[1])/(ny-1), (zlim[2]-zlim[1])/(nz-1)
    xs, ys, zs = xlim[1]:dx:xlim[2], ylim[1]:dy:ylim[2], zlim[1]:dz:zlim[2]

    ps = [[x,y,z] for x in xs for y in ys for z in zs]
    vs = (xyz -> F(xyz[1], xyz[2], xyz[3])).(ps)

    λ = 0.9 * minimum([u/maximum(getindex.(vs,i)) for (i,u) in enumerate((dx,dy,dx))])

    seriestype := :path3d
    dxs, dys, dzs = unzip(λ * vs)
    xs, ys, zs = unzip(ps)

    legend := false
    [[x,dx] for (x,dx) in zip(xs, xs+dxs)],    [[y,dy] for (y,dy) in zip(ys, ys+dys)],    [[z,dz] for (z,dz) in zip(zs, zs+dzs)]
end


function sum_3_5(n::Int)
    s = 0
    for i = 3:3:n-1
        s += i
    end

    for i = 5:5:n-1
        if i% 3 != 0
            s += i
        end
    end
    return s
end
print(sum_3_5(1000))


# ---
# title: 643. Maximum Average Subarray I
# id: problem643
# author: Indigo
# date: 2021-02-04
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/maximum-average-subarray-i/description/>
# hidden: true
# ---
#
# Given an array consisting of `n` integers, find the contiguous subarray of
# given length `k` that has the maximum average value. And you need to output
# the maximum average value.
#
# **Example 1:**
#
#
#
#     Input: [1,12,-5,-6,50,3], k = 4
#     Output: 12.75
#     Explanation: Maximum average is (12-5-6+50)/4 = 51/4 = 12.75
#
#
#
#
# **Note:**
#
#   1. 1 <= `k` <= `n` <= 30,000.
#   2. Elements of the given array will be in the range [-10,000, 10,000].
#
#
#
#
## @lc code=start
using LeetCode

function find_max_average(nums::Vector{Int}, K::Int)
    res = sum(@view(nums[1:K]))
    tmp = res
    i, j = 1, K + 1
    while j ≤ length(nums)
        tmp += nums[j] - nums[i]
        res = max(tmp, res)
        i += 1
        j += 1
    end
    res / K
end
## @lc code=end


#!/usr/bin/env julia

data = Array{Int}(undef, 2, 3)

for i in 1:size(data, 1), j in 1:size(data, 2)
    data[i, j] = (i - 1)*size(data, 2) + j
end

println(data)


function JuliaBenchSIMD( operationMode )

    allFunctions = [MatrixAddition, MatrixMultiplication, ElementWiseOperations]; # only SIMD functions to run

    allFunctionsString = [ "Matrix Addition", "Matrix Multiplication", "Element Wise Operations"];

	if (operationMode == 1) # partial benchmark
		vMatrixSize =  dropdims(readdlm(joinpath("Inputs","vMatrixSizePartial.csv"), ',',Int64), dims=1);
		numIterations = dropdims(readdlm(joinpath("Inputs","numIterationsPartial.csv"), ',',Int64), dims=1);

	elseif (operationMode == 2) # full benchmark
		vMatrixSize = dropdims(readdlm(joinpath("Inputs","vMatrixSizeFull.csv"), ',',Int64), dims=1);
		numIterations =  dropdims(readdlm(joinpath("Inputs","numIterationsFull.csv"), ',',Int64), dims=1);

	elseif (operationMode == 0) # Test benchmark
		vMatrixSize = 2;
		numIterations =  1;

	end

    numIterations = numIterations[1]; # It is 1x1 Array -> Scalar

    mRunTime = zeros(length(vMatrixSize), length(allFunctions), numIterations);
    tRunTime= Array{Any}(undef,length(allFunctions)+1,length(vMatrixSize)+1)# a table containing all the information
	tRunTime[1,1]="FunctionName\\MatrixSize";

    for ii = 1:length(vMatrixSize)
        matrixSize = vMatrixSize[ii];
        mX = randn(matrixSize, matrixSize);
        mY = randn(matrixSize, matrixSize);
        println("Matrix Size - $matrixSize");

        jj=1;
        for fun in allFunctions
            println("Processing $(allFunctionsString[jj]) - MatrixSize= $matrixSize");

            for kk = 1:numIterations;

                benchIJK =@benchmark $fun($matrixSize, $mX, $mY)
                # t =@benchmarkable $fun($matrixSize, $mX, $mY);
                # tune!(t)
                # run(t)

                mRunTime[ii, jj, kk]=median(benchIJK).time/1e3;
                # println("$(mRunTime[ii, jj, kk])")

            end
			tRunTime[jj+1,1]="$(allFunctionsString[jj])";
			tRunTime[1,ii+1]="$matrixSize";
			tRunTime[jj+1,ii+1]=mean(mRunTime[ii, jj,:]);
            jj+=1;
        end

    end

    return tRunTime, mRunTime;
end

#=
function MatrixGeneration( matrixSize, mX, mY )

  mA = randn(matrixSize, matrixSize);
  mB = rand(matrixSize, matrixSize);

  return mA;
end
=#
function MatrixAddition( matrixSize, mX, mY )

  scalarA = rand();
  scalarB = rand();

  # mA = (scalarA .* mX) .+ (scalarB .* mY);
  mA = Array{Float64}(undef, matrixSize, matrixSize);
  @simd for ii = 1:(matrixSize * matrixSize)
    @inbounds mA[ii] = (scalarA * mX[ii]) + (scalarB * mY[ii]);
  end

  return mA;
end

function MatrixMultiplication( matrixSize, mX, mY )

  scalarA = rand();
  scalarB = rand();

  # mA = (scalarA .+ mX) * (scalarB .+ mY);
  mA = Array{Float64}(undef, matrixSize, matrixSize);
  @simd for ii = 1:(matrixSize * matrixSize)
    @inbounds mA[ii] = (scalarA + mX[ii]) * (scalarB + mY[ii]);
  end

  return mA;
end
#=
function MatrixQuadraticForm( matrixSize, mX, mY )

  vX = randn(matrixSize);
  vB = randn(matrixSize);
  sacalrC = rand();

  mA = (transpose(mX * vX) * (mX * vX)) .+ (transpose(vB) * vX) .+ sacalrC;

  return mA;
end

function MatrixReductions( matrixSize, mX, mY )

  mA = sum(mX, dims=1) .+ minimum(mY, dims=2); #Broadcasting

  return mA;
end
=#
function ElementWiseOperations( matrixSize, mX, mY )

  mA = rand(matrixSize, matrixSize);
  mB = 3 .+ rand(matrixSize, matrixSize);
  mC = rand(matrixSize, matrixSize);

  # mD = abs.(mA) .+ sin.(mA);
  mD = Array{Float64}(undef, matrixSize, matrixSize);
  @simd for ii = 1:(matrixSize * matrixSize)
    @inbounds mD[ii] = abs(mA[ii]) + sin(mA[ii]);
  end

  # mE = exp.(-(mA .^ 2));
  mE = Array{Float64}(undef, matrixSize, matrixSize);
  @simd for ii = 1:(matrixSize * matrixSize)
    @inbounds mE[ii] = exp(- (mA[ii] * mA[ii]));
  end

  # mF = (-mB .+ sqrt.((mB .^ 2) .- (4 .* mA .* mC))) ./ (2 .* mA);
  mF = Array{Float64}(undef, matrixSize, matrixSize);
  @simd for ii = 1:(matrixSize * matrixSize)
    @inbounds mF[ii] = (-mB[ii] + sqrt( (mB[ii] * mB[ii]) - (4 * mA[ii] * mC[ii]) )) ./ (2 * mA[ii]);
  end

  mA = mD .+ mE .+ mF;

  return mA;
end

#=
function MatrixExp( matrixSize, mX, mY )

    mA = exp(mX);

    return mA;
end

function MatrixSqrt( matrixSize, mX, mY )

    mY = transpose(mX) * mX;

    mA = sqrt(mY);

  return mA;
end

function Svd( matrixSize, mX, mY )

    F = svd(mX, full = false); # F is SVD object
    mU, mS, mV = F;

    return mA=0;
end

function Eig( matrixSize, mX, mY )

    F  = eigen(mX); # F is eigen object
    mD, mV = F;

    return mA=0;
end

function CholDec( matrixSize, mX, mY )

  mY = transpose(mX) * mX;

  mA = cholesky(mY);

  return mA;
end

function MatInv( matrixSize, mX, mY )

  mY = transpose(mX) * mX;

  mA = inv(mY);
  mB = pinv(mX);

  mA = mA .+ mB;

  return mA;
end

function LinearSystem( matrixSize, mX, mY )

  mB = randn(matrixSize, matrixSize);
  vB = randn(matrixSize);

  vA = mX \ vB;
  mA = mX \ mB;

  mA = mA .+ vA;

  return mA;
end

function LeastSquares( matrixSize, mX, mY )

  mB = randn(matrixSize, matrixSize);
  vB = randn(matrixSize);

  vA = (transpose(mX) * mX) \ (transpose(mX) * vB);
  mA = (transpose(mX) * mX) \ (transpose(mX) * mB);

  mA = mA .+ vA;

  return mA;
end

function CalcDistanceMatrix( matrixSize, mX, mY )

  mY = randn(matrixSize, matrixSize);

  mA = transpose(sum(mX .^ 2, dims=1)) .- (2 .* transpose(mX) * mY) .+ sum(mY .^ 2, dims=1);

  return mA;
end

function KMeans( matrixSize, mX, mY )

    # Assuming Samples are slong Columns (Rows are features)
    numClusters  = Int64( max( round(matrixSize / 100), 1 ) ); # % max between 1 and round(...)
    numIterations = 10;

    # http://stackoverflow.com/questions/36047516/julia-generating-unique-random-integer-array
    mA = mX[:, randperm(matrixSize)[1:numClusters]]; #<! Cluster Centroids

    for ii = 1:numIterations
        vMinDist, mClusterId = findmin( transpose(sum(mA .^ 2, dims=1)) .- (2 .* transpose(mA)* mX), dims=1); #<! Is there a `~` equivalent in Julia?
        vClusterId = LinearIndices( dropdims(mClusterId, dims=1) ); # to be able to access it later

        for jj = 1:numClusters
            mA[:, jj] = mean( mX[:, vClusterId .== jj ], dims=2 );
        end
    end

    mA = mA[:, 1] .+ transpose(mA[:, end]);

  return mA;
end
=#


module BrmlFunctions
# General helper functions:

#VERSION < v"0.4-" && using Docile
#@docstrings

import Base: vec, findmax

# Type Unions:
export IntOrIntArray
IntOrIntArray=Union{Integer,Array{Integer},Array{}} # include also the ranges and vectors

export vec
@doc """
 Creates a vector with 1 element of value v
""" ->
function vec(v::Integer)
    return vec([v])
end

export maxarray
@doc """
 Finds the largest non-zero value of matrix **`A`**
""" ->
function maxarray(A)
    p,q,r=findnz(A)
    return maximum(r)
end

export SparseIntMatrix
@doc """
 Initialises a new matrix of size **M x N**

* `M`:     number of rows
* `N`:     number of columns
""" ->
function SparseIntMatrix(M,N)
    #return Int(spzeros(M,N))
    return round.(Int64,spzeros(M,N))
end

export fillmatrix!
@doc """
 Initialises a subarray of **`A`** with value **`val`**

* `A`:         array to fill
* `indsi`:     vector/range of row indeces
* `indsj`:     vector/range of column indeces
* `val`:       value to fill with the elements
""" ->
function  fillmatrix!(A,indsi,indsj,val)
    for i=indsi
        for j=indsj
            A[i,j]=val
        end
    end
    return A
end

export states
@doc """
 Enumerates all states given the number of states of each variable from a given set similar to MATLAB _ind2subv_

* `ns`:    vector with the total number of states for each variable
""" ->
function states(ns)
    # enumerate all states eg states([2 2 3]) (like ind2subv.m)
    # first index changes the most (like matlab)
    #ns=fliplr(ns)
    ns=flipdim(ns,2)
    n=length(ns)
    nstates=prod(vec(ns))
    c=cumprod(vec(ns[end:-1:1]))
    c=vcat(1,c[1:end-1])
    s=Array(Int64,nstates,n)
    for i=1:nstates
        tmp=i-1
        for j=n:-1:1
            s[i,n+1-j]=floor(tmp/c[j])
            tmp=tmp-s[i,n+1-j]*c[j]
        end
    end
    #return fliplr(s.+1)
    return flipdim(s.+1,2)
end

export standardise
@doc """
 Transforms an one-dimensional array **`A`** of numerical values into a column vector,
  otherwise it leaves the input unchanged
""" ->
function standardise{T<:Number}(A::Array{T,})
    # If the array is a vector, make this a column vector, otherwise leave unchanged
    if isavector(A)
        A=vec(A)
    end
    return A
end

export mysize
@doc """
 Returns the size for each dimension of a vector or matrix

* `a`:     vector or matrix to analyse
""" ->
function mysize(a)
    a=standardise(a)
    s=size(a)
    if isavector(a)
        sz=s[1]
    else
        sz=s[1:end]
    end
    return sz
end

export memberinds
@doc """
 Returns a vector mapping the indices of **`x`** and **`y`** for which the corresponding elements have the same value

* `x`:     item to compare
* `y`:     item to compare
""" ->
function memberinds(x,y)
    ind=zeros(Int64,length(x),1)
    for i=1:length(x)
        for j=1:length(y)
            if y[j]==x[i]
                ind[i]=j;
            end
        end
    end
    return ind
end

export myvcat
@doc """
 Wrapper for standard **`vcat`** used for
 backward compatibility from porting the MATLAB version of BRML code
""" ->
function myvcat(x,y)
    if length(x)==0
        return y
    end

    if length(y)==0
        return x
    end

    return vcat(x,y)
end

export isavector
@doc """
 Verifies if matrix **`A`** has one of the dimensions of size **1**
""" ->
function isavector(A)

    if length(size(A))>2
        return false
    elseif  (size(A,1)==1 && size(A,2)>1) || (size(A,2)==1 && size(A,1)>1)
        return true
    else
        return false
    end

end

export numstates
@doc """
 Returns the number of elements of matrix **`A`**
""" ->
function numstates(A)
    if isavector(A)
        n=prod(size(A));
    else
        n=size(A);
    end
    return n
end

export replace!
@doc """
 Finds the elements having value **`f`** in matrix **`A`** and replaces their value with **`r`**
""" ->
function replace!(A,f,r)
    A[find(A.==f)]=r
end

export findone
@doc """
 Returns the indices of the first element in matrix **`A`** that has the value **`c`**
""" ->
function findone(A,c)
    A, B=findn(A.==c)
    A=A[1]; B=B[1]
    return A,B
end

# Basic graph routines

export neighboursize
@doc """
 Returns the number of neighbours in an graph

     nsize = neighboursize(A,<node>)

* `A`: adjacency matrix of the graph
* `node`: (_optional_) the node for which to find the number of neighbours

If **`node`** is missing, return the neighbour sizes (including self) of each node.
If **`A`** is directed, returns the number of parents of the specified node.
""" ->
function neighboursize(A,nodes=[])
    if isempty(nodes)
        nsize=sum(A,1);
    else
        nsize=sum(A[:,vec(nodes)],1);
    end
    return nsize
end

export istree
@doc """
 Checks if the input graph is singly-connected (a polytree)

    tree, elimseq, schedule = istree(A, <root>=[]; <ReturnElimSeq>=false)

##### Input :
* `A`: graph's adjacency matrix (zeros on diagonal)
* `root`: (_optional_) root node of the graph

##### Outputs:
* `tree`: _true_ if graph is singly connected, otherwise _false_
* `elimseq`: a variable elimination sequence in which simplical nodes of the tree are listed,
 as each simplical node is removed from the tree.
* `schedule`: the sequence of messages from node to node corresponding to elimseq

If **`A`** is a directed graph, the elimination schedule begins with the nodes with no children.
 If root is specified, the last node eliminated is root.

If the graph is connected and the number of edges is less than the number of nodes,
 it must be a tree. However, to deal with the general case in which it is unknown if the graph
 is connected w check using elimination.

A tree/singly-connected graph must admit a recursive simplical node elimination. That is at
 any stage in the elimination there must be a node with either 0 or 1 neighbours in the remaining graph.
""" ->
function istree(A,root=[];ReturnElimSeq=false)
    C = size(A,1); # number of nodes in the graph
    schedule=zeros(Int,C,2)
    tree=true; # assume A is singly connected
    AA=copy(A); # adjacency of the eliminated graph
    elimseq=[]; # set of variables eliminated (in sequence)

    for node=1:C
        # now find the number of neighbours:
        nn=(C+1)*ones(1,C)  # ensures that we don't pick eliminated nodes
        s=1:C; r=zeros(1,C)
        r[elimseq]=1; # need to check this
        s=s[find(r.==0)];
        nn[s]=neighboursize(AA',s)
        if !isempty(root)
            nn[root]=C+1 # ensures we don't pick root
        end

        val, elim=findmin(nn) # find node with least number of neighbours
        neigh = find(AA[:,vec(elim)]) # find the non zero elements

        if length(neigh)>1; # if least has more than 1 neighbour, cannot be a tree
            tree=false
            break;
        end

        AA[vec(elim),:]=0;  AA[:,vec(elim)]=0; # eliminate node from graph
        elimseq=[elimseq... elim]; # add eliminated node to elimination set

        if isempty(neigh);  schedule[node,:]=[elim elim];
        else
            schedule[node,:]=[elim neigh];
        end

    end
    if !tree;
        if ReturnElimSeq
            return false, [],[]
        else
            return false
        end
    end

    c=[]
    for i=1:size(schedule,1)
        if schedule[i,1]!=schedule[i,2] # remove self elimination
            c=[c... i]
        end
    end
     if ReturnElimSeq
         return tree, elimseq, schedule[vec(c),:]
     else
         return tree
     end
end

export findmax
@doc """
 Maximises a multi-dimensional array over a set of dimensions

    [maxval maxstate] = findmax(A, variables)

* `A`:         array to fill
* `variables`: <???>

Finds the values and states that maximize the multi-dimensional
 array **`A`** over the dimensions in <???>maxover
""" ->
function findmax(A,variables;Ind2Sub=false)

    maxval,maxind=findmax(A,variables)
    if !Ind2Sub
        return maxval, maxind
    end

    s=ind2sub(size(A),maxind[:]) # compatibilty with matlab BRML
    maxstate=zeros(Int64,length(s[1]),length(s))
    for i=1:length(s)
        maxstate[:,i]=s[i]
    end
    return maxval, maxstate

end

export randgen
@doc """
 Returns a random value from distribution **`p`**
""" ->
function randgen(p)
    p=p./sum(p)
    f=find(rand().<cumsum(vec(p)))
    return(Int(minimum(f)))
end

export myipermutedims
@doc """
 Wrapper for **`ipermutedims`** used for backward compatibility
 from porting the MATLAB version of BRML code
""" ->
function myipermutedims{T<:AbstractArray}(A::T,d)
    if length(d)==1 && d[1]==1
        return A
    else
        #ipermutedims(A,d)
        permutedims(A,invperm(d[:]))
    end
end

export DictToArray
@doc """
 Copies the dictionary **D** values into a new vector
 with the size equal to the number of keys in the dictionary
""" ->
function DictToArray(D)
    if isa(D,Dict)
        L=length(collect(keys(D)))
        pot=Array(Any,L)
        ky=collect(keys(D))
        for k=1:length(keys(D))
            pot[k]=D[ky[k]]
        end
        return pot
    else
        return D
    end
end

#export iskey
#function iskey(k::Any,D::Dict)
#    allkeys=collect(keys(D))
#    return any(k.==allkeys)
#end

export condp
@doc """
 Creates a conditional distribution from an array

    pnew = condp(pin, varargin)

##### Input:
* `pin`:      a positive matrix
* `varargin`: optional input specifying which indices form the distribution variables

##### Output:
* `pnew`:    a new matrix with **`sum(pnew, 1) = ones(1, size(p, 2))`**

##### Example:
    r = rand(4, 2, 3);
    p = condp(r, DistributionIndices=[3, 1]);

**`p`** is now an array of the same size as **`r`**, but with **`sum(sum(p,3),1) = 1`** for each of the dimensions of the 2nd index.
""" ->
function condp(p; DistributionIndices::IntOrIntArray=[]) ## FIXME! This doesn't work when p is more than an 2D array
    p=p+realmin(); # in case all unnormalised probabilities are zero

    if isempty(DistributionIndices)
        p=p./repmat(sum(p,1),size(p,1),1)
        return p
    else
    if DistributionIndices==0
        #p=p./sum(p[:])
        p=p./sum(p)
        return p
    end
        allvars=1:length(size(p))
        sizevars=size(p)
        condvars=setdiff(allvars,DistributionIndices)
        newp=deepcopy(permutedims(p,vcat(DistributionIndices,condvars)))
        newp=reshape(newp,prod(sizevars[DistributionIndices]),prod(sizevars[condvars]))
        newp=newp./repmat(sum(newp,1),size(newp,1),1)
        pnew=reshape(newp,sizevars[vcat(DistributionIndices,condvars)])
        #pnew=ipermutedims(pnew,vcat(DistributionIndices,condvars))
        pnew=ipermutedims(pnew,invperm(vcat(DistributionIndices,condvars)[:]))
        return pnew
    end
end

export normp
@doc """
 Creates a normalised array from an array

    pnew = normp(pin)

##### Input:
* `pin`:      an array

##### Output:
* `pnew`:    a new array with **`sum(pnew) =1`**

##### Example:
    r = rand(4, 2, 3);
    p = normp(r);

**`p`** is now an array of the same size as **`r`**, but with **`sum(p) = 1`**
""" ->
function normp(p) ## FIXME! This doesn't work when p is more than an 2D array
    p=p+realmin(); # in case all unnormalised probabilities are zero
    p=p./sum(p)
    return p
end

#function condp(p, DistributionIndices=[]) ## FIXME! This doesn't work when p is more than an 2D array

#    if isempty(DistributionIndices)
#        p=p./repmat(sum(p,1),size(p,1),1)
#        return p
#    else
#    if DistributionIndices==0
#        p=p./sum(p[:])
#        return p
#    end
#        p=p+realmin(); # in case all unnormalised probabilities are zero
#        allvars=1:length(size(p))
#        sizevars=size(p)
#        condvars=setdiff(allvars,DistributionIndices)
#        newp=deepcopy(permutedims(p,vcat(DistributionIndices,condvars)))
#        newp=reshape(newp,prod(sizevars[DistributionIndices]),prod(sizevars[condvars]))
#        newp=newp./repmat(sum(newp,1),size(newp,1),1)
#        pnew=reshape(newp,sizevars[vcat(DistributionIndices,condvars)])
#        pnew=ipermutedims(pnew,vcat(DistributionIndices,condvars))
#        return pnew
#    end
#end

export condexp
@doc """
 Computes **`p`** proportional to **`exp(log p)`**
""" ->
function condexp(logp)
    pmax=maximum(logp,1)
    P =size(logp,1)
    return condp(exp.(logp-repmat(pmax,P,1)))
end

export validgridposition
@doc """
 Returns `true` if point (x, y) is on a defined grid (1:Gx, 1:Gy)

    v = validgridposition(x, y, Gx, Gy)
""" ->
function validgridposition(x, y, Gx, Gy)
    if x > Gx || x < 1
        return false
    end
    if y > Gy || y < 1
        return false
    end
    return true
end

end #module


# ---
# title: 826. Most Profit Assigning Work
# id: problem826
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Two Pointers
# link: <https://leetcode.com/problems/most-profit-assigning-work/description/>
# hidden: true
# ---
#
# We have jobs: `difficulty[i]` is the difficulty of the `i`th job, and
# `profit[i]` is the profit of the `i`th job.
#
# Now we have some workers. `worker[i]` is the ability of the `i`th worker,
# which means that this worker can only complete a job with difficulty at most
# `worker[i]`.
#
# Every worker can be assigned at most one job, but one job can be completed
# multiple times.
#
# For example, if 3 people attempt the same job that pays /$/1, then the total
# profit will be /$/3.  If a worker cannot complete any job, his profit is /$/0.
#
# What is the most profit we can make?
#
# **Example 1:**
#
#
#
#     Input: difficulty = [2,4,6,8,10], profit = [10,20,30,40,50], worker = [4,5,6,7]
#     Output: 100
#     **Explanation: W** orkers are assigned jobs of difficulty [4,4,6,6] and they get profit of [20,20,30,30] seperately.
#
# **Notes:**
#
#   * `1 <= difficulty.length = profit.length <= 10000`
#   * `1 <= worker.length <= 10000`
#   * `difficulty[i], profit[i], worker[i]`  are in range `[1, 10^5]`
#
#
## @lc code=start
using LeetCode

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


module AnimatedDistributions

using Random, Distributions, Plots, GR

export animate_beta,
    animate_normal,
    animate_cauchy,
    animate_weibull,
    animate_gamma,
    animate_frechet,
    animate_rayleigh,
    animate_invgamma,
    animate_exponential,
    animate_pareto,
    animate_semicircle,
    animate_tstudent,
    animate_triweight

include("animate_beta.jl")
include("animate_normal.jl")
include("animate_gamma.jl")
include("animate_invgamma.jl")
include("animate_frechet.jl")
include("animate_weibull.jl")
include("animate_rayleigh.jl")
include("animate_cauchy.jl")
include("animate_exponential.jl")
include("animate_pareto.jl")
include("animate_tstudent.jl")
include("animate_triweight.jl")
include("animate_semicircle.jl")
include("save_animation.jl")

end # module

"""
A Julia package for animating probability distributions implemented in Distributions.jl.
API overview (major features):
- `animate_*(x, ...)` creates an animation for a given range of values of the sampling space and one vector of values for each of its parameters.
- `save_animation(animation, path, fps)` saves an animation generated by one of the `animate_*` functions and saves it as a .gif file in the specified path.

Users are encouraged to refer to the full documentation at
https://gongcastro.github.io/AnimatedDistributions.jl/ and https://JuliaStats.github.io/Distributions.jl/stable/ for further for further information.

"""


using ConvDiffMIPDECO
using Test
using jInv.Mesh

domain = [0. 3. 0 1.]
n      = [60 20].-1
M      = getRegularMesh(domain,n)
@time Mass, Mass_const, SM = getFEMMatrices2D(M)

f = getFEMsource2D(M)
v = Mass_const*f
u = SM\v


"""Lateral flow in a HF135 nitrocellulose membrane."""
module ExampleHF135Richards

using Fronts
using Fronts.PorousModels
using Plots

# Wetting of an HF135 membrane, Van Genuchten model
# Data from Buser (PhD thesis, 2016)
# http://hdl.handle.net/1773/38064
k = 5.50e-13  # m^2
α = 0.2555  # 1/m
n = 2.3521

θr = 0.0473
θs = 0.945

hi = -30.591486389337
hb = 0

model = VanGenuchten(n=n, α=α, k=k, θr=θr, θs=θs)

prob = DirichletProblem(RichardsEquation(model), i=hi, b=hb)

h = solve(prob)

r = range(0, 0.05, length=500)

plt = plot(r, r -> h(r,60), label="t = 60 s", xguide="r [m]", yguide="h [m]")
display(plt)

plt = plot(r, r -> θh(model, h(r,60)), label="t = 60 s", xguide="r [m]", yguide="θ")
display(plt)

plt = plot(r[2:end], r -> flux(h, r, 60), label="t = 60 s", xguide="r [m]", yguide="U [m/s]")
display(plt)

end