@testset "Is zero" begin
    @test is_zero([0])
    @test is_zero([0, 0])
    @test is_zero([0, 0, 0])

    @test !is_zero([1])
    @test !is_zero([1, 0])
    @test !is_zero([1, 0, 0])

    @test !is_zero([1e-2, 0, 0])
    @test is_zero([1e-2, 0, 0]; atol=1e-4)
    @test !is_zero([1e-1, 0, 0]; atol=1e-4)
end

@testset "Is Parallel" begin
    @test are_parallel([1, 0], [1, 0])
    @test are_parallel([1, 0], [-1, 0])
    @test are_parallel([1, 0], [2, 0])

    @test are_parallel([1, 5], [1, 5])
    @test are_parallel([1, 5], [3, 15])
    @test are_parallel([1, 5], [-5, -25])

    @test are_parallel([1, 2, 3], [3, 6, 9])

    @test !are_parallel([1, 0], [1, 1])
    @test !are_parallel([1, 0], [1, 1e-2])
    @test are_parallel([1, 0], [1, 1e-2]; atol=1e-2)

    # The zero vector is parallel to all vectors.
    @test are_parallel([0, 0], [1, 0])
    @test are_parallel([0, 0], [5, 3])
    @test are_parallel([3, 4], [0, 0])
    @test are_parallel([3, 4, -21], [0, 0, 0])

    @test !are_parallel([0, 1e-2], [1, 0])
    @test are_parallel([0, 1e-2], [1, 0]; atol=1e-2)
    @test !are_parallel([1, 0], [0, 1e-2])
    @test are_parallel([1, 0], [0, 1e-2]; atol=1e-2)
end

@testset "Is Perpendicular" begin
    @test are_perpendicular([1, 0], [0, 1])
    @test are_perpendicular([0, 1], [-1, 0])
    @test !are_perpendicular([1, 0], [1, 0])
    @test are_perpendicular([50, 0], [0, 2])

    @test are_perpendicular([5, 2], [2, -5])
    @test are_perpendicular([1, 1, 0], [0, 0, 1])

    @test !are_perpendicular([1, 0], [1e-2, 1])
    @test are_perpendicular([1, 0], [1e-2, 1]; atol=1e-2)
end

@testset "Are coplanar" begin

    # Any three or fewer points are coplanar.

    points = hcat([0; 0; 1])
    @test are_coplanar(points)

    points = [
        0 1;
        0 1;
        1 2;
    ]
    @test are_coplanar(points)

    points = [
        0 1 5;
        0 1 2;
        1 2 7;
    ]
    @test are_coplanar(points)

    # points = [
    #     0 0 1;
    #     1 1 0;
    #     5 2 0;
    #     6 -2 0;
    # ]
    points = [
        0 1 5 6;
        0 1 2 -2;
        1 0 0 0;
    ]
    @test !are_coplanar(points)

    points = [
        0 1 2 3;
        0 1 2 3;
        0 1 2 3;
    ]
    @test are_coplanar(points)

    points = [
        5 1 5 6;
        7 1 2 -2;
        4 4 4 4;
    ]
    @test are_coplanar(points)

    # Duplicates do not matter.
    points = [
        0 0 0;
        1 1 1;
        2 2 2;
        3 3 3;
        3 3 3;
    ]
    points = [
        0 1 2 3;
        0 1 2 3;
        0 1 2 3;
    ]
    @test are_coplanar(points)
end

@testset "Point on surface of line" begin
    for (point, line, bool_expected) in [
        ([0, 0], Line([0, 0], [1, 0]), true)
        ([2, 0], Line([0, 0], [5, 0]), true)
        ([2, 0], Line([0, 0], [-5, 0]), true)
        ([0, 0], Line([0, 0], [1, 1]), true)
        ([1, 0], Line([0, 0], [1, 1]), false)
        ([1, 0], Line([0, 0], [1, 1]), false)
    ]
        @test on_surface(point, line) == bool_expected
    end

    for (point, line, atol, bool_expected) in [
        ([0, 1e-2], Line([0, 0], [1, 0]), 1e-2, true)
        ([0, 1e-2], Line([0, 0], [1, 0]), 1e-3, false)
    ]
        @test on_surface(point, line; atol=atol) == bool_expected
    end
end

@testset "Lines are approximately equal" begin
    for (line_a, line_b, bool_expected) in [
        (Line([0, 0], [1, 0]), Line([0, 0], [1, 0]), true),
        (Line([0, 0], [1, 0]), Line([0, 0], [5, 0]), true),
        (Line([0, 0], [1, 0]), Line([0, 0], [-1, 0]), true),
        (Line([0, 0], [1, 0]), Line([0, 0], [-5, 0]), true),
        (Line([3, 0], [1, 0]), Line([0, 0], [-5, 0]), true),
        (Line([-3, 0], [1, 0]), Line([0, 0], [-5, 0]), true),
        (Line([0, 1], [1, 0]), Line([0, 0], [1, 0]), false),
        (Line([0, -1], [1, 0]), Line([0, 0], [1, 0]), false),
        (Line([0, 0], [1, 0]), Line([0, 0], [1, 1]), false),
    ]
        @test isapprox(line_a, line_b) == bool_expected
    end

    for (line_a, line_b, atol, bool_expected) in [
        (Line([0, 1e-2], [1, 0]), Line([0, 0], [1, 0]), 1e-2, true),
        (Line([0, 1e-2], [1, 0]), Line([0, 0], [1, 0]), 1e-3, false),
        (Line([0, 0], [1, 1]), Line([0, 0], [1, 1.01]), 1e-2, true),
        (Line([0, 0], [1, 1]), Line([0, 0], [1, 1.01]), 1e-5, false),
    ]
        @test isapprox(line_a, line_b; atol=atol) == bool_expected
    end
end


"""
For an initial guess from the index of a Hopf bifurcation point located in ContResult.bifpoint, returns a point which will be refined using `newtonHopf`.
"""
function HopfPoint(br::ContResult, index::Int64)
	@assert br.bifpoint[index].type == :hopf "The provided index does not refer to a Hopf point"
	bifpoint = br.bifpoint[index]							# Hopf point
	eigRes   = br.eig										# eigenvector at the Hopf point
	p = bifpoint.param										# parameter value at the Hopf point
	ω = (imag(eigRes[bifpoint.idx].eigenvals[bifpoint.ind_bif]))	# frequency at the Hopf point
	return BorderedArray(bifpoint.x, [p, ω] )
end

@with_kw struct HopfProblemMinimallyAugmented{TF, TJ, TJa, Td2f, Tl <: Lens, vectype, S <: AbstractLinearSolver, Sbd <: AbstractBorderedLinearSolver, Sbda <: AbstractBorderedLinearSolver}
	F::TF 								# Function F(x, p) = 0
	J::TJ 								# Jacobian of F wrt x
	Jadjoint::TJa						# Adjoint of the Jacobian of F
	d2F::Td2f = nothing					# Hessian of F
	lens::Tl							# parameter axis for the Hopf point
	a::vectype							# close to null vector of (J - iω I)^*
	b::vectype							# close to null vector of J - iω I
	linsolver::S						# linear solver
	linbdsolver::Sbd					# linear bordered solver
	linbdsolverAdjoint::Sbda			# linear bordered solver for the jacobian adjoint
end

hasHessian(pb::HopfProblemMinimallyAugmented{TF, TJ, TJa, Td2f, Tl, vectype, S, Sbd, Sbda}) where {TF, TJ, TJa, Td2f, Tl, vectype, S, Sbd, Sbda} = Td2f != Nothing

hasAdjoint(pb::HopfProblemMinimallyAugmented{TF, TJ, TJa, Td2f, Tl, vectype, S, Sbd, Sbda}) where {TF, TJ, TJa, Td2f, Tl, vectype, S, Sbd, Sbda} = TJa != Nothing

HopfProblemMinimallyAugmented(F, J, Ja, d2F, lens::Lens, a, b, linsolve) = HopfProblemMinimallyAugmented(F, J, Ja, d2F, lens, a, b, linsolve, BorderingBLS(linsolve), BorderingBLS(linsolve))

HopfProblemMinimallyAugmented(F, J, Ja, lens::Lens, a, b, linsolve) = HopfProblemMinimallyAugmented(F, J, Ja, nothing, lens, a, b, linsolve)

function (hp::HopfProblemMinimallyAugmented)(x, p::T, ω::T, par) where {T}
	# These are the equations of the minimally augmented (MA) formulation of the Hopf bifurcation point
	# input:
	# - x guess for the point at which the jacobian has a purely imaginary eigenvalue
	# - p guess for the parameter for which the jacobian has a purely imaginary eigenvalue
	# The jacobian of the MA problem is solved with a bordering method
	a = hp.a
	b = hp.b

	# we solve (J+iω)v + a σ1 = 0 with <b, v> = n
	n = T(1)
	v, σ1, flag, it = hp.linbdsolver(hp.J(x, set(par, hp.lens, p)),
							a, b,
							T(0), zero(x), n; shift = Complex{T}(0, ω))

	# we solve (J+iω)'w + b σ2 = 0 with <a, w> = n
	# we find sigma2 = conj(sigma1)
	# w, σ2, _ = fp.linbdsolver(fp.Jadjoint(x, p) - Complex(0, ω) * I, b, a, 0., zeros(N), n)

	# the constraint is σ = <w, Jv> / n
	# σ = -dot(w, apply(fp.J(x, p) + Complex(0, ω) * I, v)) / n
	# we should have σ = σ1

	return hp.F(x, set(par, hp.lens, p)), real(σ1), imag(σ1)
end

function (hopfpb::HopfProblemMinimallyAugmented)(x::BorderedArray, params)
	res = hopfpb(x.u, x.p[1], x.p[2], params)
	return BorderedArray(res[1], [res[2], res[3]])
end

# Struct to invert the jacobian of the Hopf MA problem.
struct HopfLinearSolverMinAug <: AbstractLinearSolver; end

"""
The function solve the linear problem associated with a linearization of the minimally augmented formulation of the Hopf bifurcation point. The keyword `debug_` is used to debug the routine by returning several key quantities.
"""
function hopfMALinearSolver(x, p::T, ω::T, pbMA::HopfProblemMinimallyAugmented, par,
	 						duu, dup, duω;
							debug_ = false) where T
	# N = length(du) - 2
	# The jacobian should be passed as a tuple as Jac_hopf_MA(u0, pb::HopfProblemMinimallyAugmented) = (return (u0, pb, d2F::Bool))
	# The Jacobian J of the vector field is expressed at (x, p)
	# the jacobian expression of the hopf problem Jhopf is
	#					[ J dpF   0
	#					 σx  σp  σω]
	########## Resolution of the bordered linear system ########
	# J * dX	  + dpF * dp		   = du => dX = x1 - dp * x2
	# The second equation
	#	<σx, dX> +  σp * dp + σω * dω = du[end-1:end]
	# thus becomes
	#   (σp - <σx, x2>) * dp + σω * dω = du[end-1:end] - <σx, x1>
	# This 2x2 system is then solved to get (dp, dω)
	############### Extraction of function names #################
	Fhandle = pbMA.F
	J = pbMA.J

	d2F = pbMA.d2F
	a = pbMA.a
	b = pbMA.b

	# parameter axis
	lens = pbMA.lens

	# we define the following jacobian. It is used at least 3 times below. This avoid doing 3 times the possibly costly building of J(x, p)
	J_at_xp = J(x, set(par, lens, p))

	# we do the following to avoid computing J_at_xp twice in case pbMA.Jadjoint is not provided
	if hasAdjoint(pbMA)
		JAd_at_xp = pbMA.Jadjoint(x, set(par, lens, p))
	else
		JAd_at_xp = transpose(J_at_xp)
	end

	δ = T(1e-9)
	ϵ1, ϵ2, ϵ3 = T(δ), T(δ), T(δ)

	# we solve Jv + a σ1 = 0 with <b, v> = n
	n = T(1)
	v, σ1, _, _ = pbMA.linbdsolver(J_at_xp, a, b, T(0), zero(x), n; shift = Complex{T}(0, ω))
	w, σ2, _, _ = pbMA.linbdsolverAdjoint(JAd_at_xp, b, a, T(0), zero(x), n; shift = -Complex{T}(0, ω))

	################### computation of σx σp ####################
	dpF   = (Fhandle(x, set(par, lens, p + ϵ1))	 - Fhandle(x, set(par, lens, p - ϵ1))) / T(2ϵ1)
	dJvdp = (apply(J(x, set(par, lens, p + ϵ3)), v) - apply(J(x, set(par, lens, p - ϵ3)), v)) / T(2ϵ3)
	σp = -dot(w, dJvdp) / n

	# case of sigma_omega
	σω = -dot(w, Complex{T}(0, 1) * v) / n

	x1, x2, _, (it1, it2) = pbMA.linsolver(J_at_xp, duu, dpF)

	# the case of ∂_xσ is a bit more involved
	# we first need to compute the value of ∂_xσ written σx
	# σx = zeros(Complex{T}, length(x))
	σx = similar(x, Complex{T})

	if hasHessian(pbMA) == false
		# We invert the jacobian of the Hopf problem when the Hessian of x -> F(x, p) is not known analytically. We thus rely on finite differences which can be slow for large dimensions
		prod(size(x)) > 1e4 && @warn "You might want to pass the Hessian, finite differences with $(prod(size(x))) unknowns"
		e = zero(x)
		for ii in CartesianIndices(x)
			e[ii] = T(1)
			d2Fve = (apply(J(x + ϵ2 * e, set(par, lens, p)), v) - apply(J(x - ϵ2 * e, set(par, lens, p)), v)) / T(2ϵ2)
			σx[ii] = -dot(w, d2Fve) / n
			e[ii] = T(0)
		end
		σxx1 = dot(σx, x1)
		σxx2 = dot(σx, x2)
	else
		d2Fv = d2F(x, p, v, x1)
		σxx1 = -dot(w, d2Fv) / n
		d2Fv = d2F(x, p, v, x2)
		σxx2 = -dot(w, d2Fv) / n
	end
	# we need to be carefull here because the dot produce conjugates. Hence the + dot(σx, x2) and + imag(dot(σx, x1) and not the opposite
	dp, dω = [real(σp - σxx2) real(σω);
			  imag(σp + σxx2) imag(σω) ] \
			  [dup - real(σxx1), duω + imag(σxx1)]

	if debug_
		return x1 - dp * x2, dp, dω, true, it1 + it2, (σx, σp, σω, dpF)
	else
		return x1 - dp * x2, dp, dω, true, it1 + it2
	end
end

function (hopfl::HopfLinearSolverMinAug)(Jhopf, du::BorderedArray{vectype, T}; debug_ = false)  where {vectype, T}
	out = hopfMALinearSolver((Jhopf.x).u,
				(Jhopf.x).p[1],
				(Jhopf.x).p[2],
				Jhopf.hopfpb,
				Jhopf.param,
				du.u, du.p[1], du.p[2];
				debug_ = debug_)
	if debug_ == false
		return BorderedArray(out[1], [out[2], out[3]]), out[4], out[5]
	else
		return BorderedArray(out[1], [out[2], out[3]]), out[4], out[5], out[6]
	end
end

################################################################################################### Newton / Continuation functions
"""
	newtonHopf(F, J, hopfpointguess::BorderedArray{vectypeR, T}, par, lens::Lens, eigenvec, eigenvec_ad, options::NewtonPar; Jt = nothing, d2F = nothing, normN = norm) where {vectypeR, T}

This function turns an initial guess for a Hopf point into a solution to the Hopf problem based on a Minimally Augmented formulation. The arguments are as follows
- `F   = (x, p) -> F(x, p)` where `p` is a set of parameters.
- `dF  = (x, p) -> d_xF(x, p)` associated jacobian
- `hopfpointguess` initial guess (x_0, p_0) for the Hopf point. It should a `BorderedArray` as returned by the function `HopfPoint`.
- `par` parameters used for the vector field
- `lens` parameter axis used to locate the Hopf point.
- `eigenvec` guess for the  iω eigenvector
- `eigenvec_ad` guess for the -iω eigenvector
- `options::NewtonPar` options for the Newton-Krylov algorithm, see [`NewtonPar`](@ref).

# Optional arguments:
- `Jt = (x, p) -> transpose(d_xF(x, p))` jacobian adjoint, it should be implemented in an efficient manner. For matrix-free methods, `transpose` is not readily available and the user must provide a dedicated method. In the case of sparse based jacobian, `Jt` should not be passed as it is computed internally more efficiently, i.e. it avoid recomputing the jacobian as it would be if you pass `Jt = (x, p) -> transpose(dF(x, p))`
- `d2F = (x, p, v1, v2) ->  d2F(x, p, v1, v2)` a bilinear operator representing the hessian of `F`. It has to provide an expression for `d2F(x,p)[v1,v2]`.
- `normN = norm`
- `kwargs` keywords arguments to be passed to the regular Newton-Krylov solver

# Simplified call:
Simplified call to refine an initial guess for a Hopf point. More precisely, the call is as follows

	newtonHopf(F, J, br::ContResult, ind_hopf::Int64, par, lens::Lens; Jt = nothing, d2F = nothing, normN = norm, options = br.contparams.newtonOptions, kwargs...)

where the optional argument `Jt` is the jacobian transpose and the Hessian is `d2F`. The parameters / options are as usual except that you have to pass the branch `br` from the result of a call to `continuation` with detection of bifurcations enabled and `index` is the index of bifurcation point in `br` you want to refine. You can pass newton parameters different from the ones stored in `br` by using the argument `options`.

!!! tip "Jacobian tranpose"
    The adjoint of the jacobian `J` is computed internally when `Jt = nothing` by using `tranpose(J)` which works fine when `J` is an `AbstractArray`. In this case, do not pass the jacobian adjoint like `Jt = (x, p) -> transpose(d_xF(x, p))` otherwise the jacobian will be computed twice!

!!! warning "Hessian"
    The hessian of `F`, when `d2F` is not passed, is computed with Finite differences. This can be slow for many variables, e.g. ~1e6
"""
function newtonHopf(F, J, hopfpointguess::BorderedArray{vectypeR, T}, par, lens::Lens, eigenvec, eigenvec_ad, options::NewtonPar; Jt = nothing, d2F = nothing, normN = norm, kwargs...) where {vectypeR, T}
	hopfproblem = HopfProblemMinimallyAugmented(
		F, J, Jt, d2F, lens,
		_copy(eigenvec),
		_copy(eigenvec_ad),
		options.linsolver)

	# Jacobian for the Hopf problem
	Jac_hopf_MA = (x, param) -> (x = x, param = param, hopfpb = hopfproblem)

	# options for the Newton Solver
	opt_hopf = @set options.linsolver = HopfLinearSolverMinAug()

	# solve the hopf equations
	return newton(hopfproblem, Jac_hopf_MA, hopfpointguess, par, opt_hopf, normN = normN, kwargs...)
end

function newtonHopf(F, J, br::ContResult, ind_hopf::Int64, par, lens::Lens; Jt = nothing, d2F = nothing, normN = norm, options = br.contparams.newtonOptions, kwargs...)
	hopfpointguess = HopfPoint(br, ind_hopf)
	bifpt = br.bifpoint[ind_hopf]
	options.verbose && println("--> Newton Hopf, the eigenvalue considered here is ", br.eig[bifpt.idx].eigenvals[bifpt.ind_bif])
	@assert bifpt.idx == bifpt.step + 1 "Error, the bifurcation index does not refer to the correct step"
	eigenvec = geteigenvector(options.eigsolver ,br.eig[bifpt.idx].eigenvec, bifpt.ind_bif)
	eigenvec_ad = conj.(eigenvec)

	# solve the hopf equations
	return newtonHopf(F, J, hopfpointguess, par, lens, eigenvec_ad, eigenvec, options; Jt = Jt, d2F = d2F, normN = normN, kwargs...)
end

"""
$(SIGNATURES)

codim 2 continuation of Hopf points. This function turns an initial guess for a Hopf point into a curve of Hopf points based on a Minimally Augmented formulation. The arguments are as follows
- `F = (x, p) ->	F(x, p)` where `p` is a set of parameters
- `J = (x, p) -> d_xF(x, p)` associated jacobian
- `hopfpointguess` initial guess (x_0, p1_0) for the Hopf point. It should be a `Vector` or a `BorderedArray`
- `par` set of parameters
- `lens1` parameter axis for parameter 1
- `lens2` parameter axis for parameter 2
- `eigenvec` guess for the iω eigenvector at p1_0
- `eigenvec_ad` guess for the -iω eigenvector at p1_0
- `options_cont` keywords arguments to be passed to the regular [`continuation`](@ref)

# Optional arguments:

- `Jt = (x, p) -> adjoint(d_xF(x, p))` associated jacobian adjoint
- `d2F = p -> ((x, p, v1, v2) -> d2F(x, p, v1, v2))` this is the hessian of `F` computed at `(x, p)` and evaluated at `(v1, v2)`.
- `kwargs` keywords arguments to be passed to the regular [`continuation`](@ref)

# Simplified call:
The call is as follows

	continuationHopf(F, J, br::ContResult, ind_hopf::Int64, par, lens1::Lens, lens2::Lens, options_cont::ContinuationPar ;  Jt = nothing, d2F = nothing, kwargs...)

where the parameters are as above except that you have to pass the branch `br` from the result of a call to `continuation` with detection of bifurcations enabled and `index` is the index of Hopf point in `br` you want to refine.

!!! warning "Hessian"
    The hessian of `F`, when `d2F` is not passed, is computed with Finite differences. This can be slow for many variables, e.g. ~1e6

!!! tip "Jacobian tranpose"
    The adjoint of the jacobian `J` is computed internally when `Jt = nothing` by using `tranpose(J)` which works fine when `J` is an `AbstractArray`. In this case, do not pass the jacobian adjoint like `Jt = (x, p) -> transpose(d_xF(x, p))` otherwise the jacobian would be computed twice!

!!! warning "Hessian"
    The hessian of `F`, when `d2F` is not passed, is computed with Finite differences. This can be slow for many variables, e.g. ~1e6
"""
function continuationHopf(F, J, hopfpointguess::BorderedArray{vectype, Tb}, par, lens1::Lens, lens2::Lens, eigenvec, eigenvec_ad, options_cont::ContinuationPar ; Jt = nothing, d2F = p2 -> nothing, kwargs...) where {T,Tb,vectype}

	# options for the Newton Solver inheritated from the ones the user provided
	options_newton = options_cont.newtonOptions

	hopfPb = HopfProblemMinimallyAugmented(
		F, J, Jt, d2F,
		lens1,
		_copy(eigenvec), #copy(eigenvec),
		_copy(eigenvec_ad),
		options_newton.linsolver)

	# Jacobian for the Hopf problem
	Jac_hopf_MA = (x, param) -> (x = x, param = param, hopfpb = hopfPb)

	opt_hopf_cont = @set options_cont.newtonOptions.linsolver = HopfLinearSolverMinAug()

	# solve the hopf equations
	branch, u, tau = continuation(
		hopfPb, Jac_hopf_MA,
		hopfpointguess, par, lens2,
		opt_hopf_cont;
		printSolution = (u, p) -> u.p[1],
		plotSolution = (x, p; kwargs...) -> (xlabel!("p2", subplot=1); ylabel!("p1", subplot=1)), kwargs...)

	return setproperties(branch; type = :HopfCodim2, functional = hopfPb), u, tau
end

function continuationHopf(F, J, br::ContResult, ind_hopf::Int64, par, lens1::Lens, lens2::Lens, options_cont::ContinuationPar ;  Jt = nothing, d2F = nothing, kwargs...)
	hopfpointguess = HopfPoint(br, ind_hopf)
	bifpt = br.bifpoint[ind_hopf]
	eigenvec = geteigenvector(options_cont.newtonOptions.eigsolver ,br.eig[bifpt.idx].eigenvec, bifpt.ind_bif)
	eigenvec_ad = conj.(eigenvec)
	return continuationHopf(F, J, hopfpointguess, par, lens1, lens2, eigenvec_ad, eigenvec, options_cont ; Jt = Jt, d2F = d2F, kwargs...)
end


# segment types
"""
Finite set {0, .., k-1}
"""
struct Segment{T} <: AbstractSegment
    itr :: UnitRange{T}
end

"""
Finite set {c, .., c + k-1}
"""
struct ShiftedSegment{T} <: AbstractSegment
    itr :: UnitRange{T}
end

# Segment initializers

"""
Construct a Segment [0..k-1]
"""
Segment(k :: Int) = Segment(0 : (k-1))

"""
Construct a Segment [start..finish]
"""
Segment(start, finish) = ShiftedSegment(start : finish)

# INTERFACE FUNCTIONS
iterator(s :: Segment) = s.itr


include("schema.jl")

function estimate_aide(gold_standard_sampler::Sampler, target_sampler::Sampler, num_replicates::Int)
    aide_estimates = aide(gold_standard_sampler, target_sampler, 1, 1, num_replicates, num_replicates)
    estimate = mean(aide_estimates)
    stderr = std(aide_estimates) / sqrt(num_replicates)
    return (estimate, stderr)
end

function estimate_expected_num_clusters(target_sampler::Sampler, num_replicates::Int)
    target_samples = pmap((i) -> simulate(target_sampler)[1], 1:num_replicates)
    num_clusters = map((s) -> length(clusters(s.crp)), target_samples)
    num_clusters = convert(Vector{Float64}, num_clusters)
    estimate = mean(num_clusters)
    stderr = std(num_clusters) / sqrt(length(num_clusters))
    return (estimate, stderr)
end

function generate_sampler_data(data::Vector{Float64}, hypers::Hyperparameters,
                               num_aide_replicates::Int, num_diagnostic_replicates::Int,
                               gold_standard_sampler::Sampler)
    proposal_name_column = String[]
    num_sweeps_column = Int[]
    num_particles_column = Int[]
    aide_estimate_column = Float64[]
    aide_stderr_column = Float64[]
    num_clusters_estimate_column = Float64[]
    num_clusters_stderr_column = Float64[]

    for num_sweeps in num_sweeps_list
        for num_particles in num_particles_list
            prior_target_sampler = make_prior_proposal_smc_sampler(data, hypers, num_sweeps, num_particles)
            optimal_target_sampler = make_optimal_proposal_smc_sampler(data, hypers, num_sweeps, num_particles)

            target_samplers = [(prior_target_sampler, PRIOR_PROPOSAL_NAME),
                               (optimal_target_sampler, OPTIMAL_PROPOSAL_NAME)]

            for (target_sampler, proposal_name) in target_samplers
                println("$proposal_name, num_sweeps=$num_sweeps, num_particles=$num_particles")
                push!(proposal_name_column, proposal_name)
                push!(num_sweeps_column, num_sweeps)
                push!(num_particles_column, num_particles)

                # AIDE estimate
                # NOTE: it is possible to reuse the gold-standard samplers when evaluating different target samplers
                # We do not do this here.
                (aide_estimate, aide_stderr) = estimate_aide(gold_standard_sampler, target_sampler, num_aide_replicates)
                push!(aide_estimate_column, aide_estimate)
                push!(aide_stderr_column, aide_stderr)

                # estimate the expected number of clusters
                (num_clusters_estimate, num_clusters_stderr) = estimate_expected_num_clusters(target_sampler, num_diagnostic_replicates)
                push!(num_clusters_estimate_column, num_clusters_estimate)
                push!(num_clusters_stderr_column, num_clusters_stderr)
            end
        end
    end

    data = DataFrame()
    data[COL_PROPOSAL_NAME] = proposal_name_column
    data[COL_NUM_PARTICLES] = num_particles_column
    data[COL_NUM_SWEEPS] = num_sweeps_column
    data[COL_AIDE_ESTIMATE] = aide_estimate_column
    data[COL_AIDE_STDERR] = aide_stderr_column
    data[COL_NUM_CLUSTERS_ESTIMATE] = num_clusters_estimate_column
    data[COL_NUM_CLUSTERS_STDERR] = num_clusters_stderr_column
    return data
end


@testset "Test LAStopping" begin
  ###############################################################################
  #
  # The Stopping structure eases the implementation of algorithms and the
  # stopping criterion.
  #
  # The following examples illustrate a specialized Stopping for the
  # LinearOperators type.
  # https://github.com/JuliaSmoothOptimizers/LinearOperators.jl
  #
  using LinearAlgebra, SparseArrays, LinearOperators #v.1.0.1

  ##############################################################
  #TEST

  n = 5
  A = rand(n, n)
  xref = 100 * rand(n)
  x0 = zeros(n)
  b = A * xref
  sA = sparse(A)
  opA = LinearOperator(A)

  mLO = LinearSystem(A, b)
  sLO = LLSModel(sA, b)
  opLO = LinearSystem(opA, b)
  meta = StoppingMeta(max_cntrs = init_max_counters_linear_operators())
  mLOstp_meta = LAStopping(mLO, meta, GenericState(x0)) #this is different because of optimality_check
  mLOstp = LAStopping(mLO, GenericState(x0), max_cntrs = init_max_counters_linear_operators())
  sLOstp = LAStopping(sLO, GenericState(x0))
  short_stop = LAStopping(A, b, sparse = true) #note that sparse is true by default
  maxcn = init_max_counters_linear_operators(nprod = 1)
  opLOstp = LAStopping(opLO, GenericState(x0), max_cntrs = maxcn)

  opLOstp2 = LAStopping(opLO, GenericState(x0), optimality_check = linear_system_check)
  @test opLOstp2.meta.max_cntrs == init_max_counters_linear_operators()
  @test opLOstp2.meta.optimality_check == opLOstp.meta.optimality_check

  mLOstp_src = LAStopping(mLO, meta, StopRemoteControl(), GenericState(x0))

  @test typeof(mLOstp.pb.counters) <: LACounters
  @test typeof(sLOstp.pb.counters) <: NLSCounters
  @test typeof(opLOstp.pb.counters) <: LACounters
  @test typeof(short_stop.pb.counters) <: NLSCounters
  @test typeof(short_stop.pb) <: LLSModel

  @test start!(mLOstp) == false
  @test start!(sLOstp) == false
  @test start!(opLOstp) == false

  update_and_stop!(mLOstp, x = xref, res = Float64[])
  @test status(mLOstp) == :Optimal
  @test mLOstp.meta.resources == false
  update_and_stop!(sLOstp, x = xref, res = Float64[])
  @test status(sLOstp) == :Optimal
  @test sLOstp.meta.resources == false
  update_and_stop!(opLOstp, x = xref, res = Float64[])
  @test status(opLOstp) == :Optimal
  @test opLOstp.meta.resources == true

  include("_additional_linear_operator_algebra.jl")

  """
  Randomized block Kaczmarz
  """
  function RandomizedBlockKaczmarz(stp::AbstractStopping; kwargs...)
    b = stp.pb.b
    x0 = stp.current_state.x
    m, n = length(b), length(x0)
    A = try
      stp.pb.A #LSSModel.A does not exist
    catch
      sparse(stp.pb.Arows, stp.pb.Acols, stp.pb.Avals, m, n)
    end

    xk = x0

    OK = start!(stp)

    while !OK
      i = Int(floor(rand() * m) + 1) #rand a number between 1 and m
      Ai = A[i, :]
      xk = Ai == 0 ? x0 : x0 - (dot(Ai, x0) - b[i]) / dot(Ai, Ai) * Ai
      #xk  = Ai == 0 ? x0 : x0 - (Ai' * x0-b[i])/(Ai' * Ai) * Ai

      if xtype(stp.current_state) <: SparseVector && !(typeof(xk) <: SparseVector)
        OK = update_and_stop!(stp, x = sparse(xk), res = sparse(Float64[]))
      elseif xtype(stp.current_state) <: Vector && typeof(xk) <: SparseVector
        OK = update_and_stop!(stp, x = convert(Vector, xk), res = Float64[])
      else
        OK =
          update_and_stop!(stp, x = xk, res = Stopping._init_field(typeof(stp.current_state.res)))
      end
      x0 = xk
    end

    return stp
  end

  m, n = 200, 100 #size of A: m x n
  A = 100 * rand(m, n) #It's a dense matrix :)
  xref = 100 * rand(n)
  b = A * xref

  #Our initial guess
  x0 = zeros(n)

  la_stop = LAStopping(
    A,
    b,
    GenericState(x0),
    max_iter = 150000,
    rtol = 1e-6,
    max_cntrs = init_max_counters_NLS(residual = 150000),
  )
  #Be careful using GenericState(x0) would not work here without forcing convert = true
  #in the update function. As the iterate will be a SparseVector to the contrary of initial guess.
  #Tangi: maybe start! should send a Warning for such problem !?
  sa_stop = LAStopping(
    sparse(A),
    b,
    GenericState(sparse(x0)),
    max_iter = 150000,
    rtol = 1e-6,
    max_cntrs = init_max_counters_NLS(residual = 150000),
  )
  op_stop = LAStopping(
    LinearSystem(LinearOperator(A), b),
    GenericState(x0),
    max_iter = 150000,
    rtol = 1e-6,
    max_cntrs = init_max_counters_linear_operators(nprod = 150000),
  )
  opbis_stop = LAStopping(LinearOperator(A), b)

  try
    @timed RandomizedBlockKaczmarz(la_stop)
    @test status(la_stop) == :Optimal
    @timed RandomizedBlockKaczmarz(sa_stop)
    @test status(sa_stop) == :Optimal
  catch
    @warn "If LSSModel.A does not exist consider [la_stop.pb.Avals[i,j] for (i) in la_stop.pb.Arows, j in la_stop.pb.Acols]"
    #https://github.com/JuliaSmoothOptimizers/NLPModels.jl/blob/master/src/lls_model.jl
  end

  @timed RandomizedBlockKaczmarz(op_stop)
  @test status(op_stop) == :Optimal

  update!(la_stop.current_state, x = xref)
  @test normal_equation_check(la_stop.pb, la_stop.current_state) <= la_stop.meta.atol
  update!(op_stop.current_state, x = xref)
  @test normal_equation_check(op_stop.pb, op_stop.current_state) <= la_stop.meta.atol
end


function Emma()
        println("Willkommen Emma zum Rechentrainer")
        println("============================")
        println("Welche Rechenart möchtest du trainiren?")
        println("a für Addition")
        println("s für Subtraktion")
        println("m für Multiplikation")
        println("d für Divison")
        println("f für Finde den Platzhalter")
        println("r für Division mit Rest")

        v = chomp(readline())
        n = 10
        zeitLimit = 5 * n
        result = zeros(Int, n)
        result2 = zeros(Int, n)
        resultComp = zeros(Int, n)
        #flush(stdin)

        if v=="a"

                print("Addition:\n")
                a = rand(1:500, n)
                b = rand(1:500, n)

                startTime = Dates.Time(now())

                for i in (1:n)
                        print(string(a[i], " + ", b[i], " = "))
                        result[i] = parse(Int, chomp(readline()))
                        resultComp[i] = a[i] + b[i]
                end

        elseif v=="s"
                println("Subtraktion\n")

                a = rand(1:50, n)
                b = rand(1:50, n)

                startTime = Dates.Time(now())

                for i in (1:n)
                        if a[i] > b[i]
                                resultComp[i] = a[i] - b[i]
                                print(string(a[i], " - ", b[i], " = "))

                        else
                                resultComp[i] = b[i] - a[i]
                                print(string(b[i], " - ", a[i], " = "))

                        end

                        result[i] = parse(Int, chomp(readline()))

                end

        elseif v=="m"
                println("Multiplikation\n")
                a = rand(1:10, n)
                b = rand(1:10, n)

                startTime = Dates.Time(now())

                for i in (1:n)
                        print(string(a[i], " * ", b[i], " = "))
                        result[i] = parse(Int, chomp(readline()))
                        resultComp[i] = a[i] * b[i]
                end

        elseif v=="d"
                println("Divison")
                a = rand(1:10, n)
                b = rand(1:10, n)

                startTime = Dates.Time(now())

                for i in (1:n)
                        resultComp[i] = a[i] * b[i]
                        print(string(resultComp[i], " : ", a[i], " = "))
                        result[i] = parse(Int, chomp(readline()))
                end

        elseif v=="f"
                println("Finde den Platzhalter:")
                a = rand(1:10, n)
                b = rand(1:10, n)

                startTime = Dates.Time(now())

                for i in (1:n)
                        resultComp[i] = a[i] * b[i]
                        print(string(a[i], " * x = ", resultComp[i], "\t x = "))
                        result[i] = parse(Int, chomp(readline()))
                end

        elseif v=="r"
                println("Bestimme das Ergebnis der Division mit Rest:")
                a = rand(10:100, n)
                b = rand(1:9, n)
                c = zeros(Int64, n)

                startTime = Dates.Time(now())

                for i in (1:n)
                        c[i] = a[i] % b[i]
                        resultComp[i] = (a[i] - c[i]) / b[i]
                        print(string(a[i], " : ", b[i], " =  ?\t"))
                        result[i] = parse(Int, chomp(readline()))
                        print("Rest?\t")
                        result2[i] = parse(Int, chomp(readline()))
                end

        else
                println("Abbruch!!")
        end

    endTime = Dates.Time(now())
    #timeElapsed = Int64(round(Float64(endTime-startTime)/1000000000))
    timeElapsed = convert(Int64, round(Dates.value(endTime - startTime)/1000000000))

    if timeElapsed < zeitLimit

        zeitAnsage = string("Sehr gut, du warst um $(zeitLimit - timeElapsed) Sekunden schneller als erlaubt!")

    else

        zeitAnsage = string("Schade!! Du warst um $(timeElapsed - zeitLimit) Sekunden zu langsam.")

    end

        if v=="a"||v=="s"||v=="m"||v=="t"

                text2 = string("Du hast $(sum(result .== resultComp)) von $n Rechnungaufgaben richtig gelöst!")
                println(text2)

        elseif v=="d"||v=="f"
                text2 = string("Du hast $(sum(result .== b)) von $n Rechnungaufgaben richtig gelöst!")
                #text2 = @sprintf "Du hast %i von %i Rechnungaufgaben richtig gelöst!" sum(result .== resultComp) n;
                println(text2)

        elseif v=="r"

                text2 = string("Du hast $(sum(result .== resultComp)) von $n Quotienten gefunden!")
                text3 = string("Du hast $(sum(result2 .== c)) von $n Resten der Division gefunden!")
                #text2 = @sprintf "Du hast %i von %i Quotienten gefunden!" sum(result .== resultComp) n;
                #text3 = @sprintf "Du hast %i von %i Resten der Division gefunden!" sum(result2 .== c) n;

                println(text2)
                println(text3)

        end

        println(zeitAnsage)

end


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:3] <= 1)

                     @variable(m, -8.232182974914569 <= q <= 6.070970235703473)

                     add_NL_constraint(m, :(gelu(-0.17705543456567296 + 0.5634748900178774*gelu(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])) + 0.6106029608527352*gelu(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])) + 0.5384388086098779*gelu(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))) + gelu(0.08053767290922664 + 0.911477397877237*gelu(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])) + 0.7134526118957916*gelu(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])) + -0.34097520379571256*gelu(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))) + gelu(0.5992621446321471 + 0.15304126986204158*gelu(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])) + -0.37134199071135*gelu(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])) + 0.8747239793986465*gelu(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


module ImageTools
# Copyright © 2014-2015 Vardan Andriasyan, Yauhen Yakimovich, Artur Yakimovich.
#
#  MIT license.
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including without
# limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

export Morph
export graythresh,
im2bw,
imshow,
imread,
bwlabel,
jet,
hsv,
label2rgb

include("imagetools/morph.jl")

# import Tk
import FileIO
import Images
import Images: Gray, properties, data, label_components
# import FixedPointNumbers
import ImageView # view images for users of the Julia command-line interface (non Juno or IJulia) https://juliaimages.org/latest/install/#Displaying-images-1
import Colors
import BinDeps

################################################################
function graythresh(img)
  # graythresh - Based on the original Paper: N. Otsu, "A Threshold Selection
  # Method from Gray-Level Histograms" 1979. Calculates a threshold of a grayscale
  # image, which can be used downstream to convert a grayscale image to binary
  # image. One input argument is required - image (expected image format obtained
  # by Images.imread, see Images package  for more information). E.g. to convert
  # an array to image use grayim(array) or colorim(array).

  #Check whether the input image is empty
  if isempty(img)
    error("Image is empty");
  end
  #Check whether the input image is gray

  if !(eltype(img) <: Gray)
    error("Input Image should be grayscale")
  end

  # raw --> rawview in Images # https://github.com/JuliaImages/ImageCore.jl/issues/83

  if isa(Images.rawview(img),Array{UInt16,2})
    # Convert image to 8bit and return it's raw values to compute the histogram
    # https://juliaimages.org/latest/conversions_views/#Views-for-%22converting%22-between-fixed-point-and-raw-representations-1
    image_array = map(Images.BitShift(Uint8,8),Images.rawview(img));
  elseif isa(Images.rawview(img),Array{Uint8,2})
    image_array = Images.rawview(img);
  else
    @warn("Input Image is neither Uint8 or Uint16");
    image_array = Images.rawview(img);
  end
  #image_array = rawview(img)

  expected_number_of_bins = 2^8;

  (range, counts) = hist(reshape(image_array,length(image_array)),expected_number_of_bins);

  #get real number of bins to be used below
  number_of_bins = size(counts)[1];
  #Next the algorithm implementation according to the original paper
  p = counts / sum(counts);
  ω = cumsum(p);
  μ = cumsum(p .* (1:number_of_bins));
  μ_t = μ[end];

  sigma_b_squared = (μ_t * ω - μ).^2 ./ (ω .* (1 - ω));

  # Find the location of the maximum value of sigma_b_squared.
  # The maximum may extend over several bins, so average together the
  # locations.  If maxval is NaN, meaning that sigma_b_squared is all NaN,
  # then return 0.
  maxvalue = maximum(sigma_b_squared);
  if isfinite(maxvalue)
    index = findfirst(sigma_b_squared, maxvalue);

    # Normalize the threshold to the range [0, 1].
    threshold = (index - 1) / (expected_number_of_bins - 1);
  else
    threshold = 0.0;
  end

  return threshold
end
################################################################
function im2bw(img, threshold)
  # im2bw - converts image into a binary image. Input: image (expected image
  # format obtained by Images.imread,  see Images package  for more information)
  # and threshold (floating point number). E.g. to convert an array to image use
  # grayim(array) or colorim(array).

  if !(eltype(img) <: Gray)
    error("Input Image should be grayscale")
  end

  if typeof(threshold) != Float64 && typeof(threshold) != Float32 && typeof(threshold) != Float16
    error("Threshold must be of types Float64, Float32 or Float16");
  end
  if threshold > 1 || threshold < 0
    error("Threshold must be between 0 and 1");
  end

  m,n = size(img);
  blacknWhite = falses(n,m)
  imageData = data(img);

  for i = 1:n
    for j = 1:m
      if imageData[j,i] > threshold
        blacknWhite[i,j] = true;
      end
    end
  end

  return Images.Image(blacknWhite, colorspace = "Binary")
end
################################################################
function imshow(image)
  # wrapper for ImageView.view, need to add convertion of image to array and back
  ImageView.view(image)
end
################################################################
function imread(path)
  # wrapper for FileIO.load with added image retrieval from url functionality
  if (occursin(r"http://.*", path) || occursin(r"https://.*", path) || occursin(r"ftp://.*", path)|| occursin(r"smb://.*", path))
    imageContainer = nothing
    # By default, do silent download.
    url = path
    path = tempname()
    cmd = BinDeps.download_cmd(url, path)
    run(pipe(cmd, devnull, devnull));
    imageContainer = path

  else
    imageContainer = path;
  end
  image = FileIO.load(imageContainer);
  return image
end
################################################################
function bwlabel(inputImage,connectivity)
  # wrapper for Images.label_components

  #define pixel connectivity matrices for appropriate inputs
  if connectivity == 4
    connectivityMatrix = bool([0 1 0;1 1 1; 0 1 0])
  elseif connectivity == 8
    connectivityMatrix = bool([1 1 1;1 1 1; 1 1 1]);
  else
    error("Invalid pixel connectivity. Pixel connectivity can be either 4 or 8");
  end

  labeledImage = label_components(inputImage,connectivityMatrix);

  return labeledImage;
end

################################################################
function jet(numberOfColors::Int64)
  #Generate matlab-like jet colormap with specified amount of colors
  return jetColorMap = Colors.RGB{Float64}[
  Colors.RGB(
  clamp(min(4*ellement - 1.5, -4*ellement + 4.5) ,0.0,1.0),
  clamp(min(4*ellement - 0.5, -4*ellement + 3.5) ,0.0,1.0),
  clamp(min(4*ellement + 0.5, -4*ellement + 2.5) ,0.0,1.0))
  for ellement in range(0.0, stop=1.0, length=numberOfColors)];

  end

  ################################################################
  function hsv(numberOfColors::Int64)
    #Generate matlab-like hsv colormap with specified amount of colors
    return hsvColorMap = range(Colors.HSV(0,1,1), stop=Colors.HSV(330,1,1), length=numberOfColors);

  end
  ################################################################
  function label2rgb(labeledMatrix, inputColorMap = "jet",backgroundColor = [1 1 1], isShuffled = "noshuffle")

    #check if the labeledMatrix contains only integers
    if !isinteger(labeledMatrix)
      error("labeledMatrix is not an Integer matrix");
    end

    #change indexing of the labeled Matrix to start from 1
    labeledMatrix = labeledMatrix +1;
    numberOfEllements =  maximum(labeledMatrix); # number of connected ellements in the Matrix

    if inputColorMap == "jet"
      currentColormap =  jet(numberOfEllements);
    elseif inputColorMap == "hsv"
      currentColormap =  hsv(numberOfEllements);
      #check if custom colormap is loaded
    elseif is(typeof(inputColorMap),Array{Colors.RGB,1})

      if length(inputColorMap) == maximum(labeledMatrix)
        currentColormap = inputColorMap;
      else
        error("Input Colormap size is inconsistent with the Labeled Image");
      end
    else
      error("Invalid Input Colormap");
    end

    #convert input backgroundColor to RGB type
    backgroundColor = Colors.RGB(backgroundColor[1],backgroundColor[2],backgroundColor[3]);

    if  isShuffled == "shuffle"
      #shuffle the colormap
      currentColormap  = shuffle(currentColormap);
    elseif isShuffled == "noshuffle"
      #do nothing
    else
      error("isShuffled can be either shuffle or noshuffle ");
    end

    if (currentColormap[1] != backgroundColor)
      currentColormap = [backgroundColor,currentColormap[1:end-1]];
    end

    indexedImage = Images.ImageCmap(labeledMatrix, currentColormap);
    outputRGB = convert(Images.Image, indexedImage);

    return outputRGB;

  end

  ################################################################
end #End of ImageTools


""" Sets parameters within Dict. """
function set_param!(d::Dict, key::Symbol, val, checks = true)
    if haskey(d, key) && val isa typeof(d[key])
        d[key] = val
        return
    elseif checks
        throw(ErrorException("Parameter with key " * string(key) * " is invalid."))
    else
        return
    end
end

function set_params!(d::Dict; kwargs...)
    for (k,v) in kwargs
        set_param!(d, k, v)
    end
    return
end

""" Gets parameters within Dict. """
function get_param(gm::Dict, key::Symbol)
    if haskey(gm, key)
        return gm[key]
    else
        throw(ErrorException("Parameter with key " * string(key) * " is invalid."))
    end
end

"""
    $(TYPEDSIGNATURES)

Normalizes a given chunk of data by column.
"""
function normalize(X::Matrix)
    bounds = [(minimum(X[:,i]), maximum(X[:,i])) for i = 1:size(X, 2)]
    X_norm = zeros(size(X))
    for i = 1:size(X,2)
        X_norm[:,i] = (X[:,i] .- bounds[i][1]) ./ (bounds[i][2] - bounds[i][1])
    end
    return X_norm, bounds
end

function normalize(X::Vector)
    bounds = (minimum(X), maximum(X))
    X_norm = (X .- bounds[1]) ./ (bounds[2] - bounds[1])
    return X_norm, bounds
end

"""
    $(TYPEDSIGNATURES)

Returns the distance matrix of X. Should almost always be done on a normalized X matrix.
"""
function pairwise_distances(X_norm::Matrix)
    dists = zeros(size(X_norm, 1), size(X_norm, 1))
    for i = 1:size(X_norm, 1)
        for j = i:size(X_norm, 1)
            dists[i,j] = sum((X_norm[i,:] .- X_norm[j,:]).^2)
            dists[j, i] = dists[i,j]
        end
    end
    return dists
end

""" Computes the mode, i.e. the most common element of an array. """
function mode(X::Vector)
    count_dict = Dict()
    for i = 1:length(X)
        if !(X[i] in keys(count_dict))
            count_dict[X[i]] = 1
        else
            count_dict[X[i]] += 1
        end
    end
    maxval = maximum(values(count_dict))
    return [k for (k,v) in count_dict if v == maxval]
end

"""
    $(TYPEDSIGNATURES)

Splits data according to the kwargs. Potential kwargs:
- bins::Int: The number of bins that you would like the data to fit into.
- sample_proportion::Union{Array, Real}: Proportion of samples in each bin. If Real, means only two bins.
- sample_count::Union{Array}: Number of samples in each bin.
"""
function split_data(X, Y;
    bins::Union{Int, Nothing} = nothing,
    sample_proportion::Union{Array, Real, Nothing} = nothing,
    sample_count::Union{Array, Nothing} = nothing)

    (sum(!isnothing(bins) + !isnothing(sample_proportion) + !isnothing(sample_count)) <= 1) || throw(ErrorException("split_data only takes one or zero kwargs."))

    subset_idxs = [0]
    if sum(!isnothing(bins) + !isnothing(sample_proportion) + !isnothing(sample_count)) == 0
        sample_proportion = [0.5, 0.5]
    elseif !isnothing(bins)
        sample_proportion = [1/bins for i = 1:bins]
    end

    if !isnothing(sample_proportion)
        isapprox(sum(sample_proportion), 1, atol = 1e-7) || throw(ErrorException("Sum of sample-proportion in split_data must be equal to the number of samples. "))
        for i = 1:length(sample_proportion)
            append!(subset_idxs, minimum([Int(ceil(length(Y)*sum(sample_proportion[1:i]))), length(Y)])) # Minimum to make sure we get no bugs with atol = 1e-7
        end
    elseif !isnothing(sample_count)
        sum(sample_count) == size(X, 1) || throw(ErrorException("Sum of sample-count in split_data must be equal to the number of samples. "))
        for i = 1:length(sample_count)
            push!(subset_idxs, subset_idxs[end] + sample_count[i])
        end
    end
    return [(X[subset_idxs[i]+1:subset_idxs[i+1], :],
    Y[subset_idxs[i]+1:subset_idxs[i+1]]) for i = 1:length(subset_idxs)-1]
end


# ===== VSB Verifications: Vortex Sheet Calculations =====

println("=== BEGINNING Verifications5.jl ===")

# === Imports ===
println("   IMPORTING PACKAGES...")
using VSB
using SimpleVPM
using LinearAlgebra
using Plots
pyplot()
using LaTeXStrings
#using Printf

cd("/Users/Damyn/Documents/BYU/FLOW Lab/VSB/verifications")

# === Boundary Geometry and Plotting Grids ===
println("   GENERATING GEOMETRY...")
NPTS = 50                              # Number of Points on Boundary
del_theta = (2*pi)/(NPTS - 1)           # Step in theta around body
R = 1.0                                 # Cylinder Radius
X_coor = zeros(NPTS)                    # X-coordinates
Y_coor = zeros(NPTS)                    # Y-coordinates
X_tHat = zeros(NPTS)                    # X Tangent Vector
Y_tHat = zeros(NPTS)                    # X Tangent Vector
X_nHat = zeros(NPTS)                    # Y Normal Vector
Y_nHat = zeros(NPTS)                    # Y Normal Vector
theta = zeros(NPTS)                     # Angle from positive X-axis
for i=1:NPTS
    X_coor[i] = R*cos((i-1)*del_theta)
    Y_coor[i] = R*sin((i-1)*del_theta)

    X_tHat[i] = sin((i-1)*del_theta)
    Y_tHat[i] = -cos((i-1)*del_theta)

    X_nHat[i] = cos((i-1)*del_theta)
    Y_nHat[i] = sin((i-1)*del_theta)

    theta[i] = (i-1)*del_theta
end
body_pts = [[X_coor[i], Y_coor[i], 0.0] for i in 1:NPTS]
t_hats = [[X_tHat[i], Y_tHat[i], 0.0] for i in 1:NPTS]
n_hats = [[X_nHat[i], Y_nHat[i], 0.0] for i in 1:NPTS]
body = VSB.Boundary(body_pts,t_hats,n_hats)

# === Problem parameters ===
println("   CALCULATING PROBLEM PARAMETERS...")
magU_inf = 1.0
U_inf(X) = magU_inf .* [1,0,0]
params = VSB.Parameters(body, U_inf, verbose = true)
alphas = params.alphas

# === Analytical solution for gamma ===
println("   CALCULATING GAMMA ANALYTICAL...")
function gamma_analytical_i(i)
    point = body.bodyPTS[i]
    t_hat = body.tHats[i]
    gamma = -dot(U_inf(point), t_hat)
    return gamma
end
gammaA = [gamma_analytical_i(i) for i in 1:NPTS]

# === Numerical solution for gamma ===
println("   CALCULATING GAMMA NUMERICAL...")
function gamma_numerical_i(i)
    point = body.bodyPTS[i]
    gamma = VSB.CalcVS(body, alphas, point)
    return gamma
end
gammaN = [gamma_numerical_i(i) for i in 1:NPTS]

# === Plotting ===
println("   PLOTTING...")
plt1 = plot(theta, gammaA, line = (:red), label = "Analytical")
plot!(theta, gammaN, line = (:blue, :dash), label = "Numerical")
plot!(xlims = (0.0, 2*pi),
      xticks = ([0.0,pi/2,pi,3*pi/2,2*pi],["0",L"\frac{\pi}{2}",L"\pi",L"\frac{3\pi}{2}",L"2\pi"]),
      xlabel = L"\theta",
      ylims = (-1.05, 1.05),
      ylabel = L"\gamma(\theta)",
      grid = false)
# annotate!( [ (1,0.1,text("Analytical",14,:red)) , (1,-0.1,text("Numerical",14,:blue))])
savefig(plt1, "VSB_Verification_VS2.pdf")

println("=== END OF Verifications5.jl ===")


using Roots
using Test
import SpecialFunctions.erf

include("./test_find_zero.jl")
include("./test_bracketing.jl")
include("./test_derivative_free.jl")
include("./test_simple.jl")
include("./test_find_zeros.jl")
include("./test_fzero.jl")
include("./test_newton.jl")
include("./test_simple.jl")

#include("./test_composable.jl")
#include("./runbenchmarks.jl")
#include("./test_derivative_free_interactive.jl")


using LinearAlgebra: Symmetric, dot, diagind

@doc raw"""
```julia
restrict_to_inactive!(Q::Symmetric,
                      q::AbstractVector,
                      Z::AbstractVector{BoundConstraint_Enum},
                      lb::AbstractVector{<:Real},
                      ub::AbstractVector{<:Real})
```

```julia
function restrict_to_inactive!(Q::Symmetric,
                               q::AbstractVector,
                               Z::AbstractVector{BoundConstraint_Enum},
                               bc::BoundConstraints{<:Real,1})
```

In-place modification of ``(Q,q)`` that produces
``(\tilde{Q},\tilde{q})`` such that the initial optimization problem:

```math
\tilde{x} = \arg\min \frac{1}{2} x^t Q x + q^tx
```
under these constraints:
```math
x[i] = \left\{\begin{array}{ll}
l[i], & \text{if } Z[i] = -1 \\
u[i], & \text{if } Z[i] = +1
\end{array}\right.
```
is transformed into this linear system:
```math
\tilde{Q}\tilde{x}+\tilde{q}=0
```
"""
function restrict_to_inactive!(Q::Symmetric,
                               q::AbstractVector,
                               Z::AbstractVector{BoundConstraint_Enum},
                               lb::AbstractVector{<:Real},
                               ub::AbstractVector{<:Real})
    Q_ET = eltype(Q)
    n = length(q)

    @assert (n,n)==size(Q)
    @assert n==length(Z)
    @assert n==length(lb)
    @assert n==length(ub)
    @assert Q.uplo=='L' || Q.uplo=='U'

    if Q.uplo=='L'

        for k = 1:n

            if Z[k]!=INACTIVE_BC

                constrained_x_k = ifelse(Z[k]==ACTIVE_LB,
                                         lb[k],
                                         ub[k])

                q[1:(k-1)] .+= constrained_x_k*@view(Q.data[k,1:(k-1)])
                Q.data[k,1:(k-1)] .= zero(Q_ET)

                Q_kk = max(1,abs(Q.data[k,k]))
                q[k] = -constrained_x_k*Q_kk
                Q.data[k,k] = Q_kk

                q[(k+1):n] .+= constrained_x_k*@view(Q.data[(k+1):n,k])
                Q.data[(k+1):n,k] .= zero(Q_ET)

            end # Z[k]!=inactive
        end # k = 1:n
    else
        @assert (Q.uplo=='U') ""

        for k = 1:n

            if Z[k]!=INACTIVE_BC

                constrained_x_k = ifelse(Z[k]==ACTIVE_LB,
                                         lb[k],
                                         ub[k])

                q[1:(k-1)] .+= constrained_x_k*@view(Q.data[1:(k-1),k])
                Q.data[1:(k-1),k] .= zero(Q_ET)

                Q_kk = max(1,abs(Q.data[k,k]))
                q[k] = -constrained_x_k*Q_kk
                Q.data[k,k] = Q_kk

                q[k+1:n] .+= constrained_x_k*@view(Q.data[k,(k+1):n])
                Q.data[k,k+1:n] .= zero(Q_ET)

            end # Z[k]!=inactive
        end # k = 1:n
    end
end
function restrict_to_inactive!(Q::Symmetric,
                               q::AbstractVector,
                               Z::AbstractVector{BoundConstraint_Enum},
                               bc::BoundConstraints{<:Real,1})
    restrict_to_inactive!(Q,q,Z,lower_bound(bc),upper_bound(bc))
end

@doc raw"""
```julia
update_Z!(x::AbstractVector,
          τ::AbstractVector,
          Z::AbstractVector{BoundConstraint_Enum},
          lb::AbstractVector,
          ub::AbstractVector)
```
```julia
update_Z!(x::AbstractVector,
          τ::AbstractVector,
          Z::AbstractVector{BoundConstraint_Enum},
          bc::BoundConstraints{<:Real,1})
```
This function updates `Z` according to `x`, `τ` and bounds `lb`, `ub`
values.

It also count how many changes have be done during this update.

No change means that the algorithm has converged.

**Note:** this function only modifies `Z` and return the number of bad
hypothesis.
"""
function update_Z!(x::AbstractVector,
                   τ::AbstractVector,
                   Z::AbstractVector{BoundConstraint_Enum},
                   lb::AbstractVector,
                   ub::AbstractVector)
    n = length(x)

    @assert n == length(τ)
    @assert n == length(Z)
    @assert n == length(lb)
    @assert n == length(ub)

    count_bad_hypothesis::UInt = 0

    for i in 1:n

        if Z[i]==INACTIVE_BC

            if x[i]<=lb[i]

                count_bad_hypothesis+=1
                Z[i]=ACTIVE_LB

            elseif x[i]>=ub[i]

                count_bad_hypothesis+=1
                Z[i]=ACTIVE_UB

            end

        elseif Z[i]==ACTIVE_LB

            @assert x[i]==lb[i] "Internal error $(x[i]) != $(lb[i])"

            if τ[i]>0
                count_bad_hypothesis+=1
                Z[i]=INACTIVE_BC
            end

        else

            @assert x[i]==ub[i] "Internal error $(x[i]) != $(ub[i])"

            if τ[i]<0
                count_bad_hypothesis+=1
                Z[i]=INACTIVE_BC
            end

        end

    end #  for i in 1:n

    return count_bad_hypothesis
end
function update_Z!(x::AbstractVector,
                   τ::AbstractVector,
                   Z::AbstractVector{BoundConstraint_Enum},
                   bc::BoundConstraints{<:Real,1})
    update_Z!(x,τ,Z,lower_bound(bc),upper_bound(bc))
end

@doc raw"""
```julia
initialize_x_Z(x_init::AbstractArray,
               bc::BoundConstraints)
```

Create (x,Z) from initial guess `x_init` and bound constraints `bc`

`Z` is created by recording how `x_init` fulfils the bound constraints `bc`.

`x` is the projection of `x_init` on the bounded domain [l,b].
"""
function initialize_x_Z(x_init::AbstractArray,
                        bc::BoundConstraints)

    @assert size(x_init) == size(bc)

    x = similar(x_init)
    Z = similar(Array{BoundConstraint_Enum},axes(bc))
    lb = lower_bound(bc)
    ub = upper_bound(bc)

    for (i,(lb_i,x_init_i,ub_i)) in enumerate(zip(lb,x_init,ub))

        if x_init_i<lb_i
            x[i]=lb_i
            Z[i]=ACTIVE_LB
            continue
        end

        if x_init_i>ub_i
            x[i]=ub_i
            Z[i]=ACTIVE_UB
            continue
        end

        x[i]=x_init_i
        Z[i]=INACTIVE_BC
    end

    x, Z
end

@doc raw"""
```julia
update_x!(x::AbstractArray,
          Z::AbstractArray{BoundConstraint_Enum},
          bc::BoundConstraints)
```

Update x value such that:
```math
x[i] = \left\{\begin{array}{ll}
l[i], & \text{if } Z[i] = -1 \\
u[i], & \text{if } Z[i] = +1
\end{array}\right.
```
When ``Z[i]=0`` the ``x[i]`` value is unaffected.
"""
function update_x!(x::AbstractArray,
                   Z::AbstractArray{BoundConstraint_Enum},
                   bc::BoundConstraints)

    @assert size(x) == size(bc) == size(Z)

    for (i,(Z_i,lb_i,ub_i)) in enumerate(zip(Z,lower_bound(bc),upper_bound(bc)))
        if Z_i==ACTIVE_LB
            x[i]=lb_i
            continue
        end

        if Z_i==ACTIVE_UB
            x[i]=ub_i
            continue
        end
    end

    x
end

@doc raw"""
```julia
clean_τ!(τ::AbstractArray{<:Real},
         Z::AbstractArray{BoundConstraint_Enum})
```

By definition τ=-Qx-q. If the algorithm converged, then one must have
τ[i]=0 when the constraint is inactive.

This function updates τ by overwriting τ[i]=0 when Z[i]=inactive.
"""
function clean_τ!(τ::AbstractArray{<:Real},
                  Z::AbstractArray{BoundConstraint_Enum})

    @assert size(τ)==size(Z)

    τ_ELT = eltype(τ)

    for (i,Z_i) in enumerate(Z)
        if Z_i == INACTIVE_BC
            τ[i]=zero(τ_ELT)
        end
    end
    τ
end

# ****************************************************************

# Config stucture
struct Kunisch_Rendl_Conf <: Abstract_BC_QuadSolver_Conf
    _max_iter::Int

    function Kunisch_Rendl_Conf(;
                                max_iter::Int=50)

        @assert max_iter>0

        new(max_iter)
    end
end

max_iter(conf::Kunisch_Rendl_Conf) = conf._max_iter

# ****************************************************************

# Result structure
struct Kunisch_Rendl_Result <: Abstract_BC_QuadSolver_Result
    _cv::Bool
    _iter_count::Int
    _fobj::Real
    _x::AbstractVector{<:Real}
    _τ::AbstractVector{<:Real}

    # For a reason I ignore, using Base.@kwdef leads to an error with
    # Julia 1 By consequence I do not use this macro and write the
    # constructor manually
    function  Kunisch_Rendl_Result(;
                                   _cv,
                                   _iter_count,
                                   _fobj,
                                   _x,
                                   _τ)
        new(_cv,_iter_count,_fobj,_x,_τ)
    end

end
converged(r::Kunisch_Rendl_Result) = r._cv
iteration_count(r::Kunisch_Rendl_Result) = r._iter_count
objective_value(r::Kunisch_Rendl_Result) = r._fobj
multiplier_τ(r::Kunisch_Rendl_Result) = ReadOnlyArray(r._τ)
solution(r::Kunisch_Rendl_Result) = ReadOnlyArray(r._x)

# ****************************************************************

# Kunisch-Rendl algorithm to solve a bound constrained quadratic problem
#
#
# CAVEAT: positive definiteness of Q is no enough to guarantee
# convergence
#
function Kunisch_Rendl(Q::Symmetric{<:Real},
                       q::AbstractVector{<:Real},
                       x_init::AbstractVector{<:Real},
                       bc::BoundConstraints{<:Real,1},
                       conf::Kunisch_Rendl_Conf)
    n = length(q)

    @assert (n,n) == size(Q) "(n,n) == size(Q) $((n,n)) == $(size(Q))"
    @assert n == length(bc)

    (x, Z) = initialize_x_Z(x_init,bc)

    τ = similar(q)
    Q_tilde = similar(Q)
    q_tilde = similar(q)

    has_CV::Bool = false
    local iter_count
    for iter in 1:max_iter(conf)
        iter_count = iter
        Q_tilde .= Q
        q_tilde .= q

        # modify Q,q according to constraints
        restrict_to_inactive!(Q_tilde,q_tilde,Z,bc)

        # x = -Q^{-1}.q
        # TODO: add exception is Q_tilde is too badly conditioned
        try
            x = -Q_tilde\q_tilde
        catch
            @warn "Singular system... Abort..."
            break
        end

        # update x and compute a posteriori multipliers
        update_x!(x,Z,bc)
        τ .= -Q*x-q # for perfs use mul! instead

        # update Z and count bad hypothesis
        count_bad_choice = update_Z!(x,τ,Z,bc)

        # @info "KR: iter=$(_fmt(iter)), wrong_hypothesis=$(_fmt(count_bad_choice))"

        # CV check
        if count_bad_choice==0
            has_CV=true
            break
        end
    end
    # prepare quantities export

    # as τ=-Q*x-q, fobj is 1/2( -τ.x + q.x)
    #
    fobj = (-dot(τ,x) + dot(q,x))/2
    # "true" 0 for inactive constraints
    multiplier_τ = clean_τ!(τ,Z)

    result = Kunisch_Rendl_Result(
        _cv=has_CV,
        _iter_count=iter_count,
        _fobj=fobj,
        _x=x, # no risk as x is local
        _τ=τ # no risk as τ is local
    )

    # @info "Leaving Kunisch-Rendl" result = result

    result
end

# Specialize the generic solve method
#
solve(Q::Symmetric{<:Real},
      q::AbstractVector{<:Real},
      x_init::AbstractVector{<:Real},
      bc::BoundConstraints{<:Real,1},
      conf::Kunisch_Rendl_Conf) = Kunisch_Rendl(Q,q,x_init,bc,conf)


module Percolation

export dfspercolate, dfspercolate!, colorpercolate, hkpercolate

using DataStructures

"""
Find out whether or not percolation is possible in a 2D grid using depth-first search

Percolation is assumed to occur from top to bottom.

# Argument

`grid::Matrix{Bool}`: Represents the grid cells; True if porous (part of the path;
    can pass through) and False if blocked (can't pass through; no path passes through)

# Returns

boolean indicating whether percolation is possible
"""
function dfspercolate(grid::BitMatrix)
    height, width = size(grid)

    undiscovered = BitMatrix(undef, height + 2, width + 2)
    # "pad" the array to avoid bound checking for neigboring
    undiscovered[:, 1] .= false
    undiscovered[1, :] .= false
    undiscovered[end, :] .= false
    undiscovered[:, end] .= false
    # insert grid into undiscovered array
    undiscovered[2:end-1, 2:end-1] = grid

    stack = Stack{Tuple{Integer, Integer}}()
    for root in 2:width+1
        if undiscovered[2, root] # top row
            # search this path
            push!(stack, (2, root))
            if dfs!(height + 1, stack, undiscovered)
                return true
            end
        end
    end
    return false
end

"""
Depth-first search algorithm adopted for the dfspercolate function
"""
@inline function dfs!(destination::Integer, stack::Stack, undiscovered::BitMatrix)
    while !isempty(stack)
        vertex = pop!(stack)
        undiscovered[vertex...] = false

        # check if destination is reached
        if vertex[1] == destination
            return true
        end

        # check neighboring vertices
        for step in ((-1, 0), (0, -1), (0, 1), (1, 0))
            neighbor = vertex .+ step
            if undiscovered[neighbor...]
                push!(stack, neighbor)
            end
        end
    end
    return false
end

"""
Find out whether or not percolation is possible in a 2D grid using depth-first search

Percolation is assumed to occur from top to bottom. this version marks points visited by
the dfs in the visited

# Arguments

- `grid::Matrix{Bool}`: Represents the grid cells; True if porous (part of the path;
    can pass through) and False if blocked (can't pass through; no path passes through)
- `visited::BitMatrix`: whether each point is visited by the dfs

# Returns

boolean indicating whether percolation is possible
"""
function dfspercolate!(grid::BitMatrix, visited::BitMatrix)
    height, width = size(grid)

    undiscovered = BitMatrix(undef, height + 2, width + 2)
    # "pad" the array to avoid bound checking for neigboring
    undiscovered[:, 1] .= false
    undiscovered[1, :] .= false
    undiscovered[end, :] .= false
    undiscovered[:, end] .= false
    # insert grid into undiscovered array
    undiscovered[2:end-1, 2:end-1] = grid

    stack = Stack{Tuple{Integer, Integer}}()
    for root in 2:width+1
        if undiscovered[2, root] # top row
            # search this path
            push!(stack, (2, root))
            if dfs!(height + 1, stack, undiscovered, visited)
                return true
            end
        end
    end
    return false
end

"""
Depth-first search algorithm adopted for the dfspercolate function

this version modifies the `visited` matrix
"""
@inline function dfs!(destination::Integer, stack::Stack, undiscovered::BitMatrix,
        visited::BitMatrix)
    while !isempty(stack)
        vertex = pop!(stack)
        undiscovered[vertex...] = false
        visited[(vertex .- (1, 1))...] = true

        # check if destination is reached
        if vertex[1] == destination
            return true
        end

        # check neighboring vertices
        for step in ((-1, 0), (0, -1), (0, 1), (1, 0))
            neighbor = vertex .+ step
            if undiscovered[neighbor...]
                push!(stack, neighbor)
            end
        end
    end
    return false
end

"""
labels the clusters in a 2D grid for solving the percolation problem

Uses and inefficient O((length * width)^2) coloring algorithm.
Percolation is assumed to occur from top to bottom.

# Argument

`grid::Matrix{Bool}`: Represents the grid cells; True if porous (part of the path;
    can pass through) and False if blocked (can't pass through; no path passes through)

# Returns

a boolean indicating whether percolation is possible and a matrix "colored" with integers.
Points with value 1 are connected to the starting row.
"""
function colorpercolate(grid::BitMatrix)
    height, width = size(grid)
    colors = zeros(Integer, height + 1, width)
    colors[1, :] .= 1
    counter = 2

    for row in 2:height+1
        for col in 1:width
            if grid[row - 1, col]
                neighbors = neighborhood(colors, row, col)
                nonzero = filter(n -> colors[n...] != 0, neighbors)
                if length(nonzero) == 0
                    colors[row, col] = counter
                    counter += 1
                elseif length(nonzero) == 1
                    colors[row, col] = colors[nonzero[1]...]
                else
                    minneighbor = minimum([colors[n...] for n in nonzero])
                    colors[row, col] = minneighbor
                    for neighbor in nonzero
                        if colors[neighbor...] != minneighbor
                            colors[colors .== colors[neighbor...]] .= minneighbor
                        end
                    end
                end
            end
        end
    end
    return any(x -> x == 1, colors[end, :]), colors[2:end, :]
end

"""
Retruns a vector of tuples of the neighbors of the point at [row, col]
"""
@inline function neighborhood(grid::Matrix, row::Integer, col::Integer)
    neighbors = [(row + 1, col), (row - 1, col), (row, col + 1), (row, col - 1)]
    return filter(n -> checkbounds(Bool, grid, n...), neighbors)
end

"""
Uses the Hoshen-Kopelman algorithm to label the clusters in a 2D grid for percolation

Percolation is assumed to occur from top to bottom.

# Arguments

- `grid::Matrix{Bool}`: Represents the grid cells; True if porous (part of the path;
    can pass through) and False if blocked (can't pass through; no path passes through)
- `color::Bool` (default = `false`): whether to color the grid after finishing the algorithm

# Returns

[a boolean indicating whether percolation is possible or, if qinfty is true,
the sum of the sizes of the open connecting clusters (integer)],
an integer matrix where clusters, are marked by unique integers, and the cluster lengths.
"""
function hkpercolate(grid::BitMatrix; color::Bool=false, qinfty::Bool=false)
    height, width = size(grid)
    labels = zeros(Integer, height, width)
    list = Vector{Integer}()
    clusters = Vector{Integer}()

    # first row, special case because of top boundary
    hkfirstrow!(grid, labels, list, clusters)

    for row in 2:height
        # first column, special case because of left boundary
        if grid[row, 1]
            hkfirstcol!(labels, list, clusters, row)
        end

        # "middle" points, with appropriate neighbors
        for col in 2:width
            if grid[row, col]
                hkiter!(labels, list, clusters, row, col)
            end
        end
    end

    if color
        hkcolor!(labels, list)
    end
    if qinfty
        return hkresult(labels, list, clusters), labels, clusters
    else
        return hkbool(labels, list), labels, clusters
    end
end

"""
Helper function for `hkpercolate`; Adds new label.
"""
@inline function hknew!(labels::Matrix{<:Integer}, list::Vector{<:Integer},
        clusters::Vector{<:Integer}, row::Integer, col::Integer)
    labels[row, col] = length(list) + 1
    push!(list, labels[row, col])
    push!(clusters, 0)
end

"""
Helper function for `hkpercolate`; Applies the Hoshen-Kopelman algorithm to the first row.
"""
@inline function hkfirstrow!(grid::BitMatrix, labels::Matrix{<:Integer},
        list::Vector{<:Integer}, clusters::Vector{<:Integer})
    width = size(labels)[2]
    if grid[1, 1]
        hknew!(labels, list, clusters, 1, 1)
        clusters[labels[1, 1]] += 1
    end
    for col in 2:width
        if grid[1, col]
            if labels[1, col - 1] != 0
                labels[1, col] = hkfind!(list, labels[1, col - 1])
            else
                hknew!(labels, list, clusters, 1, col)
            end
            clusters[labels[1, col]] += 1
        end
    end
end

"""
Helper function for `hkpercolate`; Applies the Hoshen-Kopelman algorithm to the first column.
"""
@inline function hkfirstcol!(labels::Matrix{<:Integer}, list::Vector{<:Integer},
        clusters::Vector{<:Integer}, row::Integer)
    if labels[row - 1, 1] != 0
        labels[row, 1] = hkfind!(list, labels[row - 1, 1])
    else
        hknew!(labels, list, clusters, row, 1)
    end
    clusters[labels[row, 1]] += 1
end

"""
Helper function for `hkpercolate`; Applies one iteration of the Hoshen-Kopelman algorithm.
"""
@inline function hkiter!(labels::Matrix{<:Integer}, list::Vector{<:Integer},
        clusters::Vector{<:Integer}, row::Integer, col::Integer)
    above, left = labels[row - 1, col], labels[row, col - 1]
    if above == 0
        if left == 0
            hknew!(labels, list, clusters, row, col)
        else
            labels[row, col] = hkfind!(list, left)
        end
    elseif left == 0
        labels[row, col] = hkfind!(list, above)

    else
        labels[row, col] = hkunion!(list, clusters, above, left)
    end
    clusters[labels[row, col]] += 1
end

"""
Union function of the union-find algorithm used by the Hoshen-Kopelman algorithm
"""
@inline function hkunion!(list::Vector{<:Integer}, clusters::Vector{<:Integer},
        x::Integer, y::Integer)
    root = hkfind!(list, x)
    dest = hkfind!(list, y)
    if dest != root
        list[dest] = root
        clusters[root] += clusters[dest]
        clusters[dest] = 0
    end
    return root
end

"""
Find function of the union-find algorithm used by the Hoshen-Kopelman algorithm
"""
@inline function hkfind!(list::Vector{<:Integer}, x::Integer)
    y = x;
    while list[y] != y
        y = list[y];
    end
    while list[x] != x
        z = list[x];
        list[x] = y;
        x = z;
    end
    return y;
end

"""
Same as hkfind!, but doesn't update labels
"""
@inline function hkfind(list::Vector{<:Integer}, x::Integer)
    while x != list[x]
        x = list[x];
    end
    return x
end

"""
Color resultant grid of the Hoshen-Kopelman algorithm appropriately
"""
@inline function hkcolor!(labels::Matrix{<:Integer}, list::Vector{<:Integer})
    width, height = size(labels)
    for row in 1:height
        for col in 1:width
            if labels[row, col] != 0
                labels[row, col] = hkfind(list, labels[row, col])
            end
        end
    end
end

"""
Find the sum of the sizes of the open connecting clusters (integer)
"""
@inline function hkresult(labels::Matrix{<:Integer}, list::Vector{<:Integer},
        clusters::Vector{<:Integer})
    sumsize = 0
    # to avoid over-counting the clusters, save the label of each cluster that is counted
    counted = []

    for root in labels[1, :]
        if root > 0
            for dest in labels[end, :]
                if dest > 0
                    toplabel = hkfind(list, root)
                    bottomlabel = hkfind(list, dest)
                    if toplabel == bottomlabel && !(toplabel in counted)
                        sumsize += clusters[toplabel]
                        append!(counted, toplabel)
                    end
                end
            end
        end
    end
    return sumsize
end

"""
Find out whether percolation is possible or not in hkpercolate output
"""
@inline function hkbool(labels::Matrix{<:Integer}, list::Vector{<:Integer})
    for root in labels[1, :]
        if root > 0
            for dest in labels[end, :]
                if dest > 0 && hkfind(list, dest) == hkfind(list, root)
                    return true
                end
            end
        end
    end
    return false
end

end


include_string("""
    x = sum([1, 2, 3])
    @show x
    """)

@show typeof(x) # Int64


# ---
# title: 213. House Robber II
# id: problem213
# author: zhwang
# date: 2022-01-19
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/house-robber-ii/description/>
# hidden: true
# ---
#
# You are a professional robber planning to rob houses along a street. Each
# house has a certain amount of money stashed. All houses at this place are
# **arranged in a circle.** That means the first house is the neighbor of the
# last one. Meanwhile, adjacent houses have a security system connected, and
# **it will automatically contact the police if two adjacent houses were broken
# into on the same night**.
#
# Given a list of non-negative integers `nums` representing the amount of money
# of each house, return _the maximum amount of money you can rob tonight
# **without alerting the police**_.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [2,3,2]
#     Output: 3
#     Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [1,2,3,1]
#     Output: 4
#     Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
#     Total amount you can rob = 1 + 3 = 4.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [0]
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 100`
#   * `0 <= nums[i] <= 1000`
#
#
## @lc code=start
using LeetCode

function rob_213(nums::Vector{Int})::Int
    length(nums) <= 3 && return max(nums...)
    ## start from house 1
    a, b1 = nums[1], nums[1] + nums[3]
    for num in @view nums[4:(end - 1)]
        a, b1 = b1, max(b1, a + num)
    end
    ## start from house 2
    a, b2 = nums[2], max(nums[2], nums[3])
    for num in @view nums[4:end]
        a, b2 = b2, max(b2, a + num)
    end
    return max(b1, b2)
end

## @lc code=end


#=
Test adapted from https://github.com/LLNL/sundials/blob/master/examples/arkode/C_serial/ark_analytic_nonlin.c
/*-----------------------------------------------------------------
 * Programmer(s): Daniel R. Reynolds @ SMU
 *---------------------------------------------------------------
 * SUNDIALS Copyright Start
 * Copyright (c) 2002-2020, Lawrence Livermore National Security
 * and Southern Methodist University.
 * All rights reserved.
 *
 * See the top-level LICENSE and NOTICE files for details.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 * SUNDIALS Copyright End
 *---------------------------------------------------------------
 * Example problem:
 *
 * The following is a simple example problem with analytical
 * solution,
 *     dy/dt = (t+1)*exp(-y)
 * for t in the interval [0.0, 10.0], with initial condition: y=0.
 * This has analytical solution
 *      y(t) = log(0.5*t^2 + t + 1)
 *
 * This program solves the problem with the ERK method.
 * Output is printed every 1.0 units of time (10 total).
 * Run statistics (optional outputs) are printed at the end.
 *-----------------------------------------------------------------*/
=#

using Sundials, Test

t0 = 0.0
tf = 10.0
dTout = 1.0
neq = 1
reltol = 1e-6
abstol = 1e-10
y0 = [0.0]

function f(t, y, ydot, user_data)
    y = convert(Vector, y)
    ydot = convert(Vector, ydot)
    ydot[1] = (t + 1.0) * exp(-1 * y[1])
    return Sundials.ARK_SUCCESS
end

mem_ptr = Sundials.ERKStepCreate(f, t0, y0)
erkStep_mem = Sundials.Handle(mem_ptr)
Sundials.@checkflag Sundials.ERKStepSStolerances(erkStep_mem, reltol, abstol)

res = [0.0]
t = [t0]
tout = t0 + dTout
while (tf - t[1] > 1e-15)
    y = similar(y0)
    Sundials.@checkflag Sundials.ERKStepEvolve(erkStep_mem, tout, y, t, Sundials.ARK_NORMAL)
    push!(res, y[1])
    global tout += dTout
    global tout = (tout > tf) ? tf : tout
end

t = 0.0:1:10
y_analytic = log.((0.5 * t .^ 2 .+ t .+ 1))
for i in 1:length(t)
    @test isapprox(y_analytic[1], res[1]; atol = 1e-3)
end
y = nothing
temp = Ref(Clong(-1))
Sundials.@checkflag Sundials.ERKStepGetNumSteps(erkStep_mem, temp)
Sundials.@checkflag Sundials.ERKStepGetNumStepAttempts(erkStep_mem, temp)
Sundials.@checkflag Sundials.ERKStepGetNumRhsEvals(erkStep_mem, temp)
Sundials.@checkflag Sundials.ERKStepGetNumErrTestFails(erkStep_mem, temp)


@testset "Find Center" begin

	P = [
		0.0 1.0 0.0 0.0
		0.0 0.0 1.0 0.0
		0.0 0.0 0.0 1.0
	]

	@testset "Points Find Center" begin
		@test AlphaStructures.findCenter(P[1:1, 2:2]) == [1.0]
		@test AlphaStructures.findCenter(P[1:2, 2:2]) == [1.0, 0.0]
		@test AlphaStructures.findCenter(P[1:3, 2:2]) == [1., 0.0, 0.0]
	end

	@testset "Edges Find Center" begin
		@test AlphaStructures.findCenter(P[1:1, 1:2]) == [0.5]
		@test AlphaStructures.findCenter(P[1:2, 1:2]) == [0.5, 0.0]
		@test AlphaStructures.findCenter(P[1:3, 1:2]) == [0.5, 0.0, 0.0]
	end

	@testset "Triangles Find Center" begin
		@test AlphaStructures.findCenter(P[1:2, 1:3]) == [0.5, 0.5]
		@test AlphaStructures.findCenter(P[1:3, 1:3]) == [0.5, 0.5, 0.0]
	end

	@testset "Tetrahedrons Find Center" begin
		@test AlphaStructures.findCenter(P[1:3, 1:4]) == [0.5, 0.5, 0.5]
	end

end

#–------------------------------------------------------------------------------

@testset "Find Closest Point" begin
	P = [
		0.0 1.0 2.0 3.0 4.0
		0.0 3.0 2.0 2.0 1.0
		0.0 5.0 6.0 2.0 2.0
	]

	@testset "1D closest point" begin
		idxs = [2; 3; 4; 5]
		@test AlphaStructures.findClosestPoint(P[[1], [1]], P[[1], idxs]) == 1
		@test AlphaStructures.findClosestPoint(P[[2], [1]], P[[2], idxs]) == 4
		@test AlphaStructures.findClosestPoint(P[[3], [1]], P[[3], idxs]) == 3
	end

	@testset "2D closest point" begin
		idxs = [2; 3; 4; 5]
		σ = [1]
		while length(σ) <= 2
			newidx = idxs[
					AlphaStructures.findClosestPoint(P[1:2, σ], P[1:2, idxs])
			]
			idxs = [i for i in idxs if i ≠ newidx]
			σ = [σ; newidx]
		end
		@test σ == [1; 3; 2]
	end

	@testset "3D closest point" begin
		idxs = [2; 3; 4; 5]
		σ = [1]
		while length(σ) <= 3
			newidx = idxs[
					AlphaStructures.findClosestPoint(P[1:3, σ], P[1:3, idxs])
			]
			idxs = [i for i in idxs if i ≠ newidx]
			σ = [σ; newidx]
		end
		@test σ == [1; 4; 5; 3]
	end

	@testset "Keyword Argument Exception" begin
		@test_throws AssertionError AlphaStructures.findClosestPoint(
			P, P, metric = "Euclidean"
		)
		@test_throws AssertionError AlphaStructures.findClosestPoint(
			P, P, metric = "DD"
		)
		@test_throws AssertionError AlphaStructures.findClosestPoint(
			P, P, metric = "abc"
		)
	end

	@testset "Cannot add more points" begin
		@test_throws AssertionError AlphaStructures.findClosestPoint(P, P)
	end

end

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

@testset "FindMedian" begin

	P = [
		0.0 1.0 0.0 0.0
		0.0 0.0 2.0 0.0
		0.0 0.0 0.0 3.0
	]

	@test AlphaStructures.findMedian(P, 1) == 0.5
	@test AlphaStructures.findMedian(P, 2) == 1.0
	@test AlphaStructures.findMedian(P, 3) == 1.5

end

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

@testset "Find Radius" begin

	P = [
		0.0 1.0 0.0 0.0 0.0
		0.0 0.0 1.0 0.0 0.0
		0.0 0.0 0.0 1.0 1.0
	]

	@testset "Points Find Radius" begin
		@test AlphaStructures.findRadius(P[1:1, 2:2]) == 0.0
		@test AlphaStructures.findRadius(P[1:2, 2:2]) == 0.0
		@test AlphaStructures.findRadius(P[1:3, 2:2]) == 0.0
	end

	@testset "Edges Find Radius" begin
		@test AlphaStructures.findRadius(P[1:1, 1:2]) == 0.5
		@test AlphaStructures.findRadius(P[1:2, 1:2]) == 0.5
		@test AlphaStructures.findRadius(P[1:3, 1:2]) == 0.5

		@test AlphaStructures.findRadius(P[1:2, 4:5]) == 0.0
	end

	@testset "Triangles Find Radius" begin
		r = 0.707106781
		@test isapprox(AlphaStructures.findRadius(P[1:2, 1:3]), r, atol=1e-9)
		@test isapprox(AlphaStructures.findRadius(P[1:3, 1:3]), r, atol=1e-9)

		@test AlphaStructures.findRadius(P[1:2, 3:5]) == Inf
		@test AlphaStructures.findRadius(P[1:3, 3:5]) == Inf
	end

	@testset "Tetrahedrons Find Radius" begin
		r = 0.866025403
		@test isapprox(AlphaStructures.findRadius(P[1:3, 1:4]), r, atol=1e-9)

		@test AlphaStructures.findRadius(P[1:3, 2:5]) == Inf
	end

end

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

@testset "Matrix Perturbation" begin
	P = rand(Float64, 3, 100000)

	Pperturbated = AlphaStructures.matrixPerturbation(P, atol = 1e-2)
	P1 = AlphaStructures.matrixPerturbation(P, row = [1], atol = 1e-3)
	P2 = AlphaStructures.matrixPerturbation(P, col = [2], atol = 10)
	P13 = AlphaStructures.matrixPerturbation(P, row = [1; 3], atol = 1e-1)
	P11 = AlphaStructures.matrixPerturbation(P, row = [1], col = [1], atol=100)
	noPt = AlphaStructures.matrixPerturbation(P, atol = 0.0)

	@test sum(abs.(P - Pperturbated) .> 1e-2) == 0
	@test sum(abs.(P[1, :] - P1[1, :]) .> 1e-3) == 0
	@test P[[2;3], :] == P1[[2;3], :]
	@test sum(abs.(P[:, 2] - P2[:, 2]) .> 10) == 0
	@test P[:, [1;3]] == P2[:, [1;3]]
	@test sum(abs.(P[[1;3], :] - P13[[1;3], :]) .> 1e-1) == 0
	@test P[2, :] == P13[2, :]
	@test abs.(P[1, 1] - P11[1, 1]) .< 100
	@test P[2:end] == P11[2:end]
	@test P != P1 != P2 != P13 != P11 != Pperturbated
	@test P == noPt

end

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

@testset "Opposit Half Space" begin
	V = [
		0.0 1.0 0.0 0.0 4.0 -1. 1.0
		0.0 0.0 1.0 0.0 1.0 0.0 1.0
		0.0 0.0 0.0 1.0 2.0 0.0 1.0
	]

	@testset "1D Opposite Half Space" begin
		V1D = V[[1], :]
		@test AlphaStructures.oppositeHalfSpacePoints(
			V1D, V1D[:, [1]], V1D[:, 2]
		) == [6]
		@test AlphaStructures.oppositeHalfSpacePoints(
			V1D, V1D[:, [6]], V1D[:, 1]
		) == []
		@test AlphaStructures.oppositeHalfSpacePoints(
			V1D, V1D[:, [1]], V1D[:, 6]
		) == [2; 5; 7]
	end

	@testset "2D Opposite Half Space" begin
		V2D = V[1:2, :]
		@test AlphaStructures.oppositeHalfSpacePoints(
			V2D, V2D[:, [2; 3]], V2D[:, 1]
		) == [5; 7]
		@test AlphaStructures.oppositeHalfSpacePoints(
			V2D, V2D[:, [1; 2]], V2D[:, 3]
		) == []
		@test AlphaStructures.oppositeHalfSpacePoints(
			V2D, V2D[:, [1; 3]], V2D[:, 2]
		) == [6]
	end

	@testset "3D Opposite Half Space" begin
		@test AlphaStructures.oppositeHalfSpacePoints(
			V, V[:, [2; 3; 4]], V[:, 1]
		) == [5; 7]
		@test AlphaStructures.oppositeHalfSpacePoints(
			V, V[:, [1; 2; 3]], V[:, 4]
		) == []
		@test AlphaStructures.oppositeHalfSpacePoints(
			V, V[:, [1; 3; 4]], V[:, 2]
		) == [6]
	end

end

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

@testset "planarIntersection" begin

	P = [
		0.0 1.0 -1. 1.0 2.0
		1.0 2.0 -1. 1.0 3.0
		1.0 0.0 -1. -1. -5.
	]

	@testset "1D Planar Intersection" begin
		@test AlphaStructures.planarIntersection(P, [1], 1, 0.0) == 0
		@test AlphaStructures.planarIntersection(P, [2], 1, 0.0) == 1
		@test AlphaStructures.planarIntersection(P, [3], 1, 0.0) == -1
	end

	@testset "2D Planar Intersection" begin
		@test AlphaStructures.planarIntersection(P, [2; 3], 1, 0.0) == 0
		@test AlphaStructures.planarIntersection(P, [1; 2], 2, 0.0) == 1
		@test AlphaStructures.planarIntersection(P, [2; 3], 3, 0.0) == -1
	end

	@testset "3D Planar Intersection" begin
		@test AlphaStructures.planarIntersection(P, [1; 2; 3], 1, 0.0) == 0
		@test AlphaStructures.planarIntersection(P, [1; 2; 4], 2, 0.0) == 1
		@test AlphaStructures.planarIntersection(P, [2; 3; 4], 3, 0.0) == -1
	end

	@testset "4D Planar Intersection" begin
		@test AlphaStructures.planarIntersection(P, [2; 3; 4; 5], 1, 0.0) == 0
		@test AlphaStructures.planarIntersection(P, [1; 2; 4; 5], 2, 0.0) == 1
		@test AlphaStructures.planarIntersection(P, [2; 3; 4; 5], 3, 0.0) == -1
	end
end

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

@testset "Simplex Faces" begin
	@test AlphaStructures.simplexFaces([1; 2]) == [ [1], [2] ]
	@test AlphaStructures.simplexFaces([1; 2; 3]) ==
		[ [1, 2], [1, 3], [2, 3] ]
	@test AlphaStructures.simplexFaces([1; 2; 3; 4]) ==
		[ [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4] ]
	@test AlphaStructures.simplexFaces([4; 3; 2; 1; 5]) ==
		[ [1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5] ]
end

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

 @testset "Vertex in Circumball" begin

 	@testset "2D Vertex in Circumball" begin
 		V = [
 			0. 1. 0.;
 			0. 0. 1.
 		]
 		simplex = [2, 3]
 		up_simplex = [1, 2, 3]
 		point = V[:, setdiff(up_simplex, simplex)]
 		T= V[:, simplex]
 		@test AlphaStructures.vertexInCircumball(
 			T, AlphaStructures.findRadius(T), point
 		)
 	end

 	@testset "3D Vertex in Circumball" begin

 		@testset "edge and triangle" begin
 			V=[
 				0. 1. 0.;
 				0. 0. 1.;
 				0. 0. 0.
 			]
 			simplex = [2, 3]
 			up_simplex = [1, 2, 3]
 			point = V[:, setdiff(up_simplex, simplex)]
 			T= V[:, simplex]
			@test AlphaStructures.vertexInCircumball(
 				T, AlphaStructures.findRadius(T), point
 			)
 		end

 		@testset "triangle and tetrahedron" begin
 			V=[
 				0. 1. 0. 0.;
 				0. 0. 1. 0.;
 				0. 0. 0. 1.
 			]
 			simplex = [2, 3, 4]
 			up_simplex = [1, 2, 3, 4]
 			point = V[:, setdiff(up_simplex, simplex)]
 			T= V[:, simplex]
 			@test AlphaStructures.vertexInCircumball(
 				T, AlphaStructures.findRadius(T), point
 			)
 		end

 	end

 end


# ---
# title: 881. Boats to Save People
# id: problem881
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Two Pointers, Greedy
# link: <https://leetcode.com/problems/boats-to-save-people/description/>
# hidden: true
# ---
#
# The `i`-th person has weight `people[i]`, and each boat can carry a maximum
# weight of `limit`.
#
# Each boat carries at most 2 people at the same time, provided the sum of the
# weight of those people is at most `limit`.
#
# Return the minimum number of boats to carry every given person.  (It is
# guaranteed each person can be carried by a boat.)
#
#
#
# **Example 1:**
#
#
#
#     Input: people = [1,2], limit = 3
#     Output: 1
#     Explanation: 1 boat (1, 2)
#
#
# **Example 2:**
#
#
#
#     Input: people = [3,2,2,1], limit = 3
#     Output: 3
#     **Explanation** : 3 boats (1, 2), (2) and (3)
#
#
# **Example 3:**
#
#
#
#     Input: people = [3,5,3,4], limit = 5
#     Output: 4
#     **Explanation** : 4 boats (3), (3), (4), (5)
#
# **Note** :
#
#   * `1 <= people.length <= 50000`
#   * `1 <= people[i] <= limit <= 30000`
#
#
## @lc code=start
using LeetCode

function num_rescue_boats(people::Vector{Int}, limit::Int)
    sort!(people)
    a, b = 1, length(people)
    res = 0
    while a <= b
        res += 1
        a == b && break
        b -= 1
        (people[a] + people[b] <= limit) && (a += 1)
    end
    res
end
## @lc code=end


function ispossemidef(A)
	eigs = eigvals(A)
	if any(real(eigs) .< 0)
		return false
	else
		return true
	end
end

struct NotImplemented <: Exception
	fun::Symbol
	type::Symbol
end

@inline NotImplemented(fun::Symbol, type::DataType) = NotImplemented(fun, Symbol(DataType))
@inline NotImplemented(fun::Symbol, type) = NotImplemented(fun, Symbol(typeof(type)))

Base.showerror(io::IO, ex::NotImplemented) =
	print(io, "Not Implemented Error: ", ex.fun, " not implemented for type ", ex.type)

function gen_zinds(n::Int, m::Int, N::Int, isequal::Bool=false)
	Nu = isequal ? N : N-1
	zinds = [(k-1)*(n+m) .+ (1:n+m) for k = 1:Nu]
	if !isequal
		push!(zinds, (N-1)*(n+m) .+ (1:n))
	end
	return zinds
end