# related to
# https://github.com/JuliaDiffEq/DiffEqParamEstim.jl/blob/72114707e95e6a19dd264c8fdbb476e9fda6ee31/src/multiple_shooting_objective.jl
using KrylovKit, DiffEqBase

abstract type AbstractShootingProblem end

####################################################################################################
function flow(x, tspan, pb::ODEProblem; alg = Euler(), kwargs...)
	_prob = DiffEqBase.remake(pb; u0 = x, tspan = tspan)
	# the use of concrete_solve makes it compatible with Zygote
	sol = DiffEqBase.concrete_solve(_prob, alg, save_everystep = false; kwargs...)[end]
	return sol
end

# this function takes into accound a parameter passed to the vector field
# Putting the options `save_start=false` seems to bug with Sundials
function flow(x, p, tspan, pb::ODEProblem; alg = Euler(), kwargs...)
	_prob = DiffEqBase.remake(pb; u0 = x, tspan = tspan, p = p)
	# the use of concrete_solve makes it compatible with Zygote
	sol = DiffEqBase.concrete_solve(_prob, alg, save_everystep = false; kwargs...)[end]
	return sol
end
################
# this flow is used for computing the derivative of the flow, so pb encode the variational equation
function dflow(dx, x::AbstractVector, tspan, pb::ODEProblem; alg = Euler(), kwargs...)
	n = length(x)
	_prob = DiffEqBase.remake(pb; u0 = vcat(x, dx), tspan = tspan)
	# the use of concrete_solve makes it compatible with Zygote
	sol = DiffEqBase.concrete_solve(_prob, alg, save_everystep = false; kwargs...)[end]
	return sol[1:n], sol[n+1:end]
end

function dflow(dx, x::AbstractVector, p, tspan, pb::ODEProblem; alg = Euler(), kwargs...)
	n = length(x)
	_prob = DiffEqBase.remake(pb; u0 = vcat(x, dx), tspan = tspan, p = p)
	# the use of concrete_solve makes it compatible with Zygote
	sol = DiffEqBase.concrete_solve(_prob, alg, save_everystep = false; kwargs...)[end]
	return sol[1:n], sol[n+1:end]
end
################
function dflow_fd(x, dx, tspan, pb::ODEProblem; alg = Euler(), δ = 1e-9, kwargs...)
	sol1 = flow(x .+ δ .* dx, tspan, pb; alg = alg, kwargs...)
	sol2 = flow(x 			, tspan, pb; alg = alg, kwargs...)
	return sol2, (sol1 .- sol2) ./ δ
end

# this function takes into accound a parameter passed to the vector field
function dflow_fd(x, dx, p, tspan, pb::ODEProblem; alg = Euler(), δ = 1e-9, kwargs...)
	sol1 = flow(x .+ δ .* dx, p, tspan, pb; alg = alg, kwargs...)
	sol2 = flow(x 			, p, tspan, pb; alg = alg, kwargs...)
	return sol2, (sol1 .- sol2) ./ δ
end
################
# this gives access to the full solution, convenient for Poincaré shooting
function flowFull(x, tspan, pb::ODEProblem; alg = Euler(), kwargs...)
	_prob = DiffEqBase.remake(pb; u0 = x, tspan = tspan)
	sol = DiffEqBase.solve(_prob, alg; kwargs...)
end

# this function takes into accound a parameter passed to the vector field and returns the full solution from the ODE solver. This is useful in Poincare Shooting to extract the period.
function flowFull(x, p, tspan, pb::ODEProblem; alg = Euler(), kwargs...)
	_prob = DiffEqBase.remake(pb; u0 = x, tspan = tspan, p = p)
	sol = DiffEqBase.solve(_prob, alg; kwargs...)
end

####################################################################################################
# Structures related to computing ODE/PDE Flows
"""
	fl = Flow(F, flow, dflow)
This composite type encapsulates:
- the vector field `x -> F(x)` associated to a Cauchy problem,
- the flow (or semigroup) associated to the Cauchy problem `(x, t) -> flow(x, t)`. Only the last time point must be returned.
- the flow (or semigroup) associated to the Cauchy problem `(x, t) -> flow(x, t)`. The whole solution on the time interval (0,t) must be returned. This is not strictly necessary to provide this.
- the differential `dflow` of the flow w.r.t. `x`, `(x, dx, t) -> dflow(x, dx, t)`. One important thing is that we require `dflow(x, dx, t)` to return 2 vectors: the first is `flow(x, t)` and the second is the value of the derivative of the flow as the second vector.

There are some simple constructors for which you only have to pass a `prob::ODEProblem` from `DifferentialEquations.jl` and an ODE time stepper like `Tsit5()`. Hence, you can do for example

	fl = Flow(F, prob, Tsit5(); kwargs...)

If your vector field depends on parameters `p`, you can define a `Flow` using

	fl = Flow(F, p, prob, Tsit5(); kwargs...)

Finally, you can pass two `ODEProblem` where the second one is used to compute the variational equation:

	fl = Flow(F, p, prob1::ODEProblem, alg1, prob2::ODEProblem, alg2; kwargs...)

"""
struct Flow{TF, Tf, Tff, Td}
	F::TF				# vector field F(x)
	flow::Tf			# flow(x,t)
	flowFull::Tff		# flow(x,t) returns full solution
	dflow::Td			# dflow(x,dx,t) returns (flow(x,t), dflow(x,t)⋅dx)

	function Flow(F::TF, fl::Tf, flf::Tff, df::Td) where {TF, Tf, Tff, Td}
		new{TF, Tf, Tff, Td}(F, fl, flf, df)
	end

	function Flow(F::TF, fl::Tf, df::Td) where {TF, Tf, Td}
		new{TF, Tf, Nothing, Td}(F, fl, nothing, df)
	end
end

(fl::Flow)(x, tspan)     = fl.flow(x, tspan)
(fl::Flow)(x, dx, tspan) = fl.dflow(x, dx, tspan)

"""
Creates a Flow variable based on a `prob::ODEProblem` and ODE solver `alg`. The vector field `F` has to be passed, this will be resolved in the future as it can be recovered from `prob`. Also, the derivative of the flow is estimated with finite differences.
"""
function Flow(F, prob::ODEProblem, alg; kwargs...)
	return Flow(F,
		(x, t) -> 				flow(x, (zero(t), t), prob; alg = alg, kwargs...),
		(x, t) ->			flowFull(x, (zero(t), t), prob; alg = alg, kwargs...),
		(x, dx, t) -> dflow_fd((x, dx), (zero(t), t), prob; alg = alg, kwargs...)
		)
end

# this constructor takes into accound a parameter passed to the vector field
function Flow(F, p, prob::ODEProblem, alg; kwargs...)
	return Flow(F,
		(x, t) ->			 	flow(x, p, (zero(t), t), prob; alg = alg, kwargs...),
		(x, t) -> 		 	flowFull(x, p, (zero(t), t), prob; alg = alg, kwargs...),
		(x, dx, t) -> dflow_fd((x, dx), p, (zero(t), t), prob; alg = alg, kwargs...)
		)
end

function Flow(F, p, prob1::ODEProblem, alg1, prob2::ODEProblem, alg2; kwargs...)
	return Flow(F,
		(x, t) -> 			flow(x, p, (zero(t), t), prob1, alg = alg1; kwargs...),
		(x, t) -> 		flowFull(x, p, (zero(t), t), prob1, alg = alg1; kwargs...),
		(x, dx, t) ->  dflow(dx, x, p, (zero(t), t), prob2, alg = alg2; kwargs...)
		)
end

"""
Creates a Flow variable based on a `prob1::ODEProblem` and ODE solver `alg`. The derivative of the flow is imlemented in using `prob2::ODEProblem`. The vector field `F` has to be passed, this will be resolved in the future as it can be recovered from `prob`.
"""
function Flow(F, prob::ODEProblem, alg; kwargs...)
	return Flow(F,
		(x, t) ->			  flow(x, (zero(t), t), prob; alg = alg, kwargs...),
		(x, t) ->		  flowFull(x, (zero(t), t), prob; alg = alg, kwargs...),
		(x, dx, t) -> dflow_fd(x, dx, (zero(t), t), prob; alg = alg, kwargs...)
		)
end

# this constructor takes into accound a parameter passed to the vector field
function Flow(F, p, prob::ODEProblem, alg; kwargs...)
	return Flow(F,
		(x, t) ->			  flow(x, p, (zero(t), t), prob; alg = alg, kwargs...),
		(x, t) -> 		  flowFull(x, p, (zero(t), t), prob; alg = alg, kwargs...),
		(x, dx, t) -> dflow_fd(x, dx, p, (zero(t), t), prob; alg = alg, kwargs...)
		)
end
####################################################################################################
# Standard Shooting functional
"""
	pb = ShootingProblem(flow::Flow, ds, section)

This composite type implements the Standard Simple / Multiple Standard Shooting  method to locate periodic orbits. The arguments are as follows
- `flow::Flow`: implements the flow of the Cauchy problem though the structure `Flow`.
- `ds`: vector of time differences for each shooting. Its length is written `M`. If `M==1`, then the simple shooting is implemented and the multiple one otherwise.
- `section`: implements a phase condition. The evaluation `section(x)` must return a scalar number where `x` is a guess for the periodic orbit. Note that the period `T` of the guess `x` is always included either as the last component of `T = x[end]` or as `T = x.p`. The type of `x` depends on what is passed to the newton solver.

You can then call `pb(orbitguess)` to apply the functional to a guess. Note that `orbitguess` must be of size M * N + 1 where N is the number of unknowns of the state space and `orbitguess[M * N + 1]` is an estimate of the period `T` of the limit cycle. This form of guess is convenient for the use of the linear solvers in `IterativeSolvers.jl` (for example) which accepts only `AbstractVector`s. Another accepted guess is of the form `BorderedArray(guess, T)` where `guess[i]` is the state of the orbit at the `i`th time slice. This last form allows for non-vector state space which can be convenient for 2d problems for example.

A functional, hereby called `G`, encodes the shooting problem. For example, the following methods are available:
- `pb(orbitguess)` evaluates the functional G on `orbitguess`
- `pb(orbitguess, du)` evaluates the jacobian `dG(orbitguess).du` functional at `orbitguess` on `du`

## Simplified constructors
- A simpler way to build a functional is to use
	pb = ShootingProblem(F, p, prob::ODEProblem, alg, M::Int, section; kwargs...)
or

	pb = ShootingProblem(F, p, prob::ODEProblem, alg, ds, section; kwargs...)
where `F` is the vector field, `p` is a parameter (to be passed to the vector and the flow), `prob` is an `ODEProblem` which is used to create a flow using the ODE solver `alg` (for example `Tsit5()`). Finally, the arguments `kwargs` are passed to the ODE solver defining the flow. Look at `DifferentialEquations.jl` for more information. Note that, in this case, the derivative of the flow is computed internally using Finite Differences.
- The other way is an elaboration of the previous one
	pb = ShootingProblem(F, p, prob1::ODEProblem, alg1, prob2::ODEProblem, alg2, M::Int, section; kwargs...)
or

	pb = ShootingProblem(F, p, prob1::ODEProblem, alg1, prob2::ODEProblem, alg2, ds, section; kwargs...)
where we supply now two `ODEProblem`. The first one `prob1`, is used to define the flow associated to `F` while the second one is a problem associated to the derivative of the flow. Hence, `prob2` should implement the following vector field ``\\tilde F(x,\\phi) = (F(x),dF(x)\\cdot\\phi)``.
"""
@with_kw struct ShootingProblem{Tf <: Flow, Ts, Tsection} <: AbstractShootingProblem
	flow::Tf							# should be a Flow{TF, Tf, Td}
	ds::Ts = diff(LinRange(0, 1, 5))	# number of Poincaré sections
	section::Tsection					# sections for phase condition
end

ShootingProblem(F, prob::ODEProblem, alg, ds, section; kwargs...) =  ShootingProblem(Flow(F, prob, alg; kwargs...), ds, section)

ShootingProblem(F, prob::ODEProblem, alg, M::Int, section; kwargs...) = ShootingProblem(Flow(F, prob, alg; kwargs...), diff(LinRange(0, 1, M + 1)), section)

# this constructor takes into accound a parameter passed to the vector field
ShootingProblem(F, p, prob::ODEProblem, alg, ds, section; kwargs...) = ShootingProblem(Flow(F, p, prob, alg; kwargs...), ds, section)

ShootingProblem(F, p, prob::ODEProblem, alg, M::Int, section; kwargs...) = ShootingProblem(Flow(F, p, prob, alg; kwargs...), diff(LinRange(0, 1, M + 1)), section)

# idem but with an ODEproblem to define the derivative of the flow
ShootingProblem(F, p, prob1::ODEProblem, alg1, prob2::ODEProblem, alg2, ds, section; kwargs...) = ShootingProblem(Flow(F, p, prob1, alg1, prob2, alg2; kwargs...), ds, section)

ShootingProblem(F, p, prob1::ODEProblem, alg1, prob2::ODEProblem, alg2, M::Int, section; kwargs...) = ShootingProblem(Flow(F, p, prob1, alg1, prob2, alg2; kwargs...), diff(LinRange(0, 1, M + 1)), section)

# this function extracts the last component of the periodic orbit
extractPeriodShooting(x::AbstractVector) = x[end]
extractPeriodShooting(x::BorderedArray)  = x.p

# Standard shooting functional
function (sh::ShootingProblem)(x::AbstractVector)
	# period of the cycle
	# Sundials does not like @views :(
	T = extractPeriodShooting(x)
	M = length(sh.ds)
	N = div(length(x) - 1, M)

	# extract the orbit guess and reshape it into a matrix as it's more convenient to handle it
	xv = @view x[1:end-1]
	xc = reshape(xv, N, M)

	# variable to hold the computed result
	out = similar(x)
	outv = @view out[1:end-1]
	outc = reshape(outv, N, M)

	for ii in 1:M-1
		# yi = Flow(xi, dsi⋅T) - x[i+1]
		outc[:, ii] .= sh.flow(xc[:, ii], sh.ds[ii] * T) .- xc[:, ii+1]
	end
	# ym = Flow(xm, dsm⋅T) - x1
	outc[:, M] .= sh.flow(xc[:, M], sh.ds[M] * T) .- xc[:, 1]

	# add constraint
	out[end] = sh.section(x)

	return out
end

# jacobian of the shooting functional
function (sh::ShootingProblem)(x::AbstractVector, dx::AbstractVector; δ = 1e-8)
	# period of the cycle
	# Sundials does not like @views :(
	dT = extractPeriodShooting(dx)
	T  = extractPeriodShooting(x)
	M = length(sh.ds)
	N = div(length(x) - 1, M)

	xv = @view x[1:end-1]
	xc = reshape(xv, N, M)

	dxv = @view dx[1:end-1]
	dxc = reshape(dxv, N, M)

	# variable to hold the computed result
	out = similar(x)
	outv = @view out[1:end-1]
	outc = reshape(outv, N, M)

	for ii = 1:M-1
		# call jacobian of the flow
		tmp = sh.flow(xc[:, ii], dxc[:, ii], sh.ds[ii] * T)
		outc[:, ii] .= tmp[2] .+ sh.flow.F(tmp[1]) .* sh.ds[ii] * dT .- dxc[:, ii+1]
	end

	# ym = Flow(xm, dsm⋅T) - x1
	tmp = sh.flow(xc[:, M], dxc[:, M], sh.ds[M] * T)
	outc[:, M] .= tmp[2] .+ sh.flow.F(tmp[1]) .* sh.ds[M] * dT .- dxc[:, 1]

	# add constraint
	out[end] = (sh.section(x .+ δ .* dx) - sh.section(x)) / δ

	return out
end

# shooting functional, this allows for Array state space
function (sh::ShootingProblem)(x::BorderedArray)
	# period of the cycle
	T = extractPeriodShooting(x)
	M = length(x.u)

	# variable to hold the computed result
	out = BorderedArray{typeof(x.u), typeof(x.p)}(similar(x.u), typeof(x.p)(0))

	for ii in 1:M-1
		# yi = Flow(xi, dsi⋅T) - x[i+1]
		out.u[ii] .= sh.flow(x.u[ii], sh.ds[ii] * T) .- x.u[ii+1]
	end
	# ym = Flow(xm, sm T) - x1
	out.u[M] .= sh.flow(x.u[M], sh.ds[M] * T) .- x.u[1]

	# add constraint
	out.p = sh.section(x)

	return out
end

# jacobian of the shooting functional, this allows for Array state space
function (sh::ShootingProblem)(x::BorderedArray, dx::BorderedArray; δ = 1e-8)
	# period of the cycle
	dT = extractPeriodShooting(dx)
	T  = extractPeriodShooting(x)
	M = length(x.u)

	# variable to hold the computed result
	out = BorderedArray{typeof(x.u), typeof(x.p)}(similar(x.u), typeof(x.p)(0))

	for ii = 1:M-1
		# call jacobian of the flow
		tmp = sh.flow(x.u[ii], dx.u[ii], sh.ds[ii] * T)
		out.u[ii] .= tmp[2] .+ sh.flow.F(tmp[1]) .* sh.ds[ii] * dT .- dx.u[ii+1]
	end

	# ym = Flow(xm, sm T) - x1
	tmp = sh.flow(x.u[M], dx.u[M], sh.ds[M] * T)
	out.u[M] .= tmp[2] .+ sh.flow.F(tmp[1]) .* sh.ds[M] * dT .- dx.u[1]

	# add constraint
	x_tmp = similar(x.u); copyto!(x_tmp, x.u)
	axpy!(δ , dx.u, x_tmp)
	out.p = (sh.section(BorderedArray(x_tmp, T + δ * dT)) - sh.section(x)) / δ

	return out
end

function _getMax(prob::ShootingProblem, x::AbstractVector; ratio = 1)
	# this function extracts the amplitude of the cycle
	T = extractPeriodShooting(x)
	M = length(prob.ds)
	N = div(length(x) - 1, M)
	xv = @view x[1:end-1]
	xc = reshape(xv, N, M)

	# we could use @views but then Sundials might complain
	sol = prob.flow.flowFull((xc[:, 1]), T)
	mx = @views maximum(sol[1:div(N, ratio), :], dims = 1)
end

function getAmplitude(prob::ShootingProblem, x::AbstractVector; ratio = 1)
	mx = _getMax(prob, x; ratio = ratio)
	return maximum(mx) - minimum(mx)
end

function getMaximum(prob::ShootingProblem, x::AbstractVector; ratio = 1)
	mx = _getMax(prob, x; ratio = ratio)
	return maximum(mx)
end

function sectionShooting(x::AbstractVector, po::AbstractMatrix, p, F)
	res = 1.0
	N, M = size(po)
	xv = @view x[1:end-1]
	xc = reshape(xv, N, M)

	for ii = 1:size(x, 2)
		res *= @views dot(xc[:, ii], F(po[:, ii], p)) .- dot(po[:, ii], F(po[:, ii], p))
	end
	res
end

####################################################################################################
"""
pb = PoincareShootingProblem(flow::Flow, M, section)

This composite type implements the Poincaré Shooting method to locate periodic orbits, basically using Poincaré return maps. The arguments are as follows
- `flow::Flow`: implements the flow of the Cauchy problem though the structure `Flow`.
- `M`: the number of return maps. If `M==1`, then the simple shooting is implemented and the multiple one otherwise.
- `section`: implements a Poincaré section condition. The evaluation `section(x)` must return a scalar number where `x` is a guess for the periodic orbit when `M=1`. Otherwise, one must implement a function `section(out, x)` which populates `out` with the `M` hyperplanes.

## Simplified constructors
A simpler way is to create a functional is `pb = PoincareShootingProblem(F, p, prob::ODEProblem, alg, section; kwargs...)` for simple shooting or `pb = PoincareShootingProblem(F, p, prob::ODEProblem, alg, M::Int, section; kwargs...)` for multiple shooting . Here `F` is the vector field, `p` is a parameter (to be passed to the vector and the flow), `prob` is an `ODEProblem` which is used to create a flow using the ODE solver `alg` (for example `Tsit5()`). Finally, the arguments `kwargs` are passed to the ODE solver defining the flow. Look at `DifferentialEquations.jl` for more information.

Another convenient call is `pb = PoincareShootingProblem(F, p, prob::ODEProblem, alg, normals::AbstractVector, centers::AbstractVector; kwargs...)` where `normals` (resp. `centers`) is a list of normals (resp. centers) which define a list of hyperplanes `\\Sigma_i`. These hyperplanes are used to define partial Poincaré return maps. See docs for more information.

## Computing the functionals
You can then call `pb(orbitguess)` to apply the functional to a guess. Note that `orbitguess` must be of size M * N where N is the number of unknowns in the state space and `M` is the number of Poincaré maps. Another accepted `guess` is such that `guess[i]` is the state of the orbit on the `i`th section. This last form allows for non-vector state space which can be convenient for 2d problems for example.

A functional, hereby called `G` encodes this shooting problem. For example, the following methods are available:
- `pb(orbitguess)` evaluates the functional G on `orbitguess`
- `pb(orbitguess, du)` evaluates the jacobian `dG(orbitguess).du` functional at `orbitguess` on `du`

!!! tip "Tip"
    You can use the function `getPeriod(pb::PoincareShootingProblem, sol)` to get the period of the solution `sol`
"""
@with_kw struct PoincareShootingProblem{Tf <: Flow, Tsection} <: AbstractShootingProblem
	flow::Tf					# should be a Flow{TF, Tf, Td}
	M::Int64 = 1				# number of Poincaré sections
	section::Tsection			# Poincaré sections
end

# simple Poincaré shooting
function PoincareShootingProblem(F, prob::ODEProblem, alg, section; interp_points = 50, kwargs...)
	pSection(u, t, integrator) = section(u) .* (integrator.iter > 1)
	# we put the nothing option to have an upcrossing
	cb = ContinuousCallback(pSection, terminate!, nothing, interp_points = interp_points)
	return PoincareShootingProblem(Flow(F, prob, alg, callback=cb; kwargs...), 1, section)
end

# this function takes into account a parameter passed to the vector field
# simple shooting
function PoincareShootingProblem(F, p, prob::ODEProblem, alg, section; interp_points = 50, kwargs...)
	pSection(u, t, integrator) = section(u) * (integrator.iter > 1)
	# we put nothing option to have an upcrossing
	cb = ContinuousCallback(pSection, terminate!, nothing, interp_points = interp_points)
	return PoincareShootingProblem(Flow(F, p, prob, alg; callback = cb, kwargs...), 1, section)
end

# this function takes into account a parameter passed to the vector field
# multiple shooting
function PoincareShootingProblem(F, p, prob::ODEProblem, alg, M, section; interp_points = 50, kwargs...)
	if M==1
		return PoincareShootingProblem(F, p, prob::ODEProblem, alg, section; kwargs...)
	end

	pSection(out, u, t, integrator) = section(out, u) .* (integrator.iter > 1)
	affect!(integrator, idx) = terminate!(integrator)
	# we put the nothing option to have an upcrossing
	cb = VectorContinuousCallback(pSection, affect!, M;interp_points = interp_points, affect_neg! = nothing)
	return PoincareShootingProblem(Flow(F, p, prob, alg; callback = cb, kwargs...), M, section)
end

function getPeriod(prob::PoincareShootingProblem, x)
	# this function extracts the period of the cycle
	@warn "Only done for M=1"
	prob.flow.flowFull(x, Inf64).t[end]
end

# Poincaré shooting functional
function (sh::PoincareShootingProblem)(x::AbstractVector)
	# period of the cycle
	M = sh.M
	N = div(length(x), M)

	# reshape the period orbit guess
	xc = reshape(x, N, M)

	# variable to hold the computed result
	out = similar(x)
	outc = reshape(out, N, M)

	for ii in 1:M-1
		# yi = Flow(xi) - x[i+1]
		# here we compute the flow up to an infinite time hoping we intersect a Poincare section before :D
		@views outc[:, ii] .= sh.flow(xc[:, ii], Inf64) .- xc[:, ii+1]
	end
	# ym = Flow(xm) - x1
	@views outc[:, M] .= sh.flow(xc[:, M], Inf64) .- xc[:, 1]

	return out
end

# jacobian of the shooting functional
function (sh::PoincareShootingProblem)(x::AbstractVector, dx::AbstractVector; δ = 1e-8)
	return (sh(x .+ δ .* dx) .- sh(x)) ./ δ
end
####################################################################################################
# Poincare shooting based on Sánchez, J., M. Net, B. Garcı́a-Archilla, and C. Simó. “Newton–Krylov Continuation of Periodic Orbits for Navier–Stokes Flows.” Journal of Computational Physics 201, no. 1 (November 20, 2004): 13–33. https://doi.org/10.1016/j.jcp.2004.04.018.

function sectionHyp(out, x, normals, centers)
	for ii = 1:length(normals)
		out[ii] = dot(normals[ii], x .- centers[ii])
	end
	out
end

# this composite type encodes a set of hyperplanes which are used as Poincaré sections
struct HyperplaneSections
	M				# number of hyperplanes
	normals 		# normals to define hyperplanes
	centers 		# representative point on each hyperplane
	indices 		# indices to be removed in the operator Ek

	function HyperplaneSections(normals, centers)
		M = length(normals)
		indices = zeros(Int64, M)
		for ii=1:M
			indices[ii] = argmax(abs.(normals[ii]))
		end
		return new(M, normals, centers, indices)
	end
end

function (hyp::HyperplaneSections)(out, u)
	sectionHyp(out, u, hyp.normals, hyp.centers)
end

# Operateur Rk from the paper above
function R!(hyp::HyperplaneSections, out, x::AbstractVector, ii::Int)
	k = hyp.indices[ii]
	@views out[1:k-1] .= x[1:k-1]
	@views out[k:end] .= x[k+1:end]
	return out
end

function R(hyp::HyperplaneSections, x::AbstractVector, ii::Int)
	out = similar(x, length(x) - 1)
	R!(hyp, out, x, ii)
end

# Operateur Ek from the paper above
function E!(hyp::HyperplaneSections, out, xbar::AbstractVector, ii::Int)
	@assert length(xbar) == length(hyp.normals[1]) - 1 "Wrong size for the projector / expansion operators, length(xbar) = $(length(xbar)) and length(normal) = $(length(hyp.normals[1]))"
	k = hyp.indices[ii]
	nbar = R(hyp, hyp.normals[ii], ii)
	xcbar = R(hyp, hyp.centers[ii], ii)
	coord_k = hyp.centers[ii][k] - dot(nbar, xbar .- xcbar) / hyp.normals[ii][k]

	@views out[1:k-1] .= xbar[1:k-1]
	@views out[k+1:end] .= xbar[k:end]
	out[k] = coord_k

	return out
end

function E(hyp::HyperplaneSections, xbar::AbstractVector, ii::Int)
	out = similar(xbar, length(xbar) + 1)
	E!(hyp, out, xbar, ii)
end

struct HyperplanePoincareShootingProblem <: AbstractShootingProblem
	psh::PoincareShootingProblem
end

getPeriod(hpsh::HyperplanePoincareShootingProblem, x) = getPeriod(hpsh.psh)

function PoincareShootingProblem(F, p, prob::ODEProblem, alg, normals::AbstractVector, centers::AbstractVector; interp_points = 50, kwargs...)
	hyp = HyperplaneSections(normals, centers)
	pSection(out, u, t, integrator) = hyp(out, u) * (integrator.iter > 1)
	affect!(integrator, idx) = terminate!(integrator)
	# we put nothing option to have an upcrossing
	cb = VectorContinuousCallback(pSection, affect!, hyp.M; interp_points = interp_points, affect_neg! = nothing)
	return HyperplanePoincareShootingProblem(PoincareShootingProblem(Flow(F, p, prob, alg; callback = cb, kwargs...), hyp.M, hyp))
end

# Poincaré (multiple) shooting with hyperplanes parametrization
function (hpsh::HyperplanePoincareShootingProblem)(x_bar::AbstractVector; verbose = false)
	sh = hpsh.psh
	M = sh.M
	Nm1 = div(length(x_bar), M)

	# reshape the period orbit guess
	x_barc = reshape(x_bar, Nm1, M)

	# variable to hold the computed result
	xc = similar(x_bar, Nm1 + 1, M)
	outc = similar(xc)

	# we extend the state space to be able to call the flow, so we fill xc
	#TODO create the projections on the fly
	for ii=1:M
		E!(sh.section, view(xc, :, ii), view(x_barc, :, ii), ii)
	end

	for ii in 1:M-1
		# yi = Flow(xi) - x[i+1]
		@views outc[:, ii] .= xc[:, ii+1].- sh.flow(xc[:, ii], Inf64)
	end
	# ym = Flow(xm) - x1
	@views outc[:, M] .= xc[:, 1] .- sh.flow(xc[:, M], Inf64)

	# build the array to be returned
	out_bar = similar(x_bar)
	out_barc = reshape(out_bar, Nm1, M)
	for ii=1:M-1
		R!(hpsh.psh.section, view(out_barc, :, ii), view(outc, :, ii), ii+1)
	end
	R!(hpsh.psh.section, view(out_barc, :, M), view(outc, :, M), 1)

	return out_bar
end

# jacobian of the shooting functional
function (sh::HyperplanePoincareShootingProblem)(x::AbstractVector, dx::AbstractVector; δ = 1e-8)
	return (sh(x .+ δ .* dx) .- sh(x)) ./ δ
end

####################################################################################################
# if we use the same code as for newton (see below) in continuation, it is difficult to tell the eigensolver not to use the jacobian but instead the monodromy matrix. So we have to use a dedicated composite type for the jacobian to handle this case.

struct ShootingJacobian{Tpb <: AbstractShootingProblem, Torbitguess}
	pb::Tpb
	x::Torbitguess
end

(shjac::ShootingJacobian)(dx) = shjac.pb(shjac.x, dx)

####################################################################################################
# newton wrapper
"""
	newton(prob::T, orbitguess, options::NewtonPar; kwargs...) where {T <: AbstractShootingProblem}

This is the Newton Solver for computing a periodic orbit using Shooting method.

# Output:
- solution
- history of residuals
- flag of convergence
- number of iterations
"""
function newton(prob::T, orbitguess, options::NewtonPar; kwargs...) where {T <: AbstractShootingProblem}
	return newton(x -> prob(x),
			x -> (dx -> prob(x, dx)),
			orbitguess,
			options; kwargs...)
end

"""
	newton(prob::T, orbitguess, options::NewtonPar, defOp::DeflationOperator; kwargs...) where {T <: AbstractShootingProblem}

This is the deflated Newton Solver for computing a periodic orbit using Shooting method.

# Output:
- solution
- history of residuals
- flag of convergence
- number of iterations
"""
function newton(prob::Tpb, orbitguess, options::NewtonPar, defOp::DeflationOperator{T, Tf, vectype}; kwargs...) where {Tpb <: AbstractShootingProblem, T, Tf, vectype}
	return newton(x -> prob(x),
			x -> (dx -> prob(x, dx)),
			orbitguess,
			options, defOp; kwargs...)
end

####################################################################################################
# Continuation

function continuationPOShooting(prob, orbitguess, p0::Real, _contParams::ContinuationPar, linearAlgo::AbstractBorderedLinearSolver; printPeriod = true, kwargs...)

	contParams = check(_contParams)

	options = contParams.newtonOptions

	if contParams.computeEigenValues
		contParams = @set contParams.newtonOptions.eigsolver = FloquetQaDShooting(contParams.newtonOptions.eigsolver)
	end

	if (prob(p0) isa PoincareShootingProblem) || (prob(p0) isa HyperplanePoincareShootingProblem)
		if printPeriod
			printSolutionPS = (x, p) -> getPeriod(prob(p), x)
			return continuation(
				(x, p) -> prob(p)(x),
				# (x, p) -> (dx -> prob(p)(x, dx)),
				(x, p) -> ShootingJacobian(prob(p), x),
				orbitguess, p0,
				contParams, linearAlgo;
				printSolution = printSolutionPS,
				kwargs...)
		end
	end

	return continuation(
		(x, p) -> prob(p)(x),
		(x, p) -> ShootingJacobian(prob(p), x),
		orbitguess, p0,
		contParams, linearAlgo;
		kwargs...)
end

"""
	continuationPOShooting(prob, orbitguess, p0::Real, contParams::ContinuationPar; printPeriod = true, kwargs...)

This is the continuation routine for computing a periodic orbit using a functional G based on a Shooting method.

# Arguments
- `p -> prob(p)` is a function or family such that `prob(p)::AbstractShootingProblem` encodes the functional G
- `orbitguess` a guess for the periodic orbit. For the type of `orbitguess`, please see the information concerning `ShootingProblem` and `PoincareShootingProblem`.
- `p0` initial parameter, must be a real number
- `contParams` same as for the regular `continuation` method
- `printPeriod` in the case of Poincaré Shooting, plot the period of the cycle.
"""
function continuationPOShooting(prob, orbitguess, p0::Real, contParams::ContinuationPar; linearAlgo = BorderingBLS(), printPeriod = true, kwargs...)
	_linearAlgo = @set linearAlgo.solver = contParams.newtonOptions.linsolver
	return continuationPOShooting(prob, orbitguess, p0, contParams, _linearAlgo; printPeriod = printPeriod, kwargs...)
end


module RateDistortionDecisionMaking

#using #dependencies

using DataFrames, Colors, Gadfly,
      Patchwork,  #there seems to be an issue with Patchwork and firefox right now
      Distances.kl_divergence,
      Distributions,
      Reactive, Interact

export boltzmanndist, BAiterations, setuputilityarrays,
       compute_marginals, marginalizeo, threevarBAiterations,
       mutualinformation, conditional_mutualinformation, expectedutility, entropybits,
       log_bits, kl_divergence_bits,
       RDobjective, ThreeVArRDobjective, analyzeBAsolution, analyze_three_var_BAsolution,
       BAtheme, BAcontinuouscolorscale, BAmatrixvisscale, BAprobabilityvisscale, BAdiscretecolorscale,
       visualizeBAmarginal, visualizeBAconditional, visualizeBAsolution, visualizeMatrix,
       visualizeBA_double_conditional, visualize_three_var_BAsolution,
       plotperformancemeasures, plot_three_var_BA_convergence, plot_three_var_performancemeasures,
       boltzmannresult2DataFrame, BAmarginal2DataFrame, BAconditional2DataFrame,
       BAresult2DataFrame, performancemeasures2DataFrame,
       visualize_three_var_BAsolution, plot_three_var_performancemeasures

#include helper functions for mutual information, expected utiliy
include("InformationTheoryFunctions.jl")

#include BlahutArimoto iterations
include("BlahutArimoto.jl")

#include three-variable iteration schemes
include("ThreeVariableBlahutArimoto.jl")

#include helper functions to aggregate results (as vectors/matrices) into DataFrames
include("ConversionFunctions.jl")

#include helper functions for visualization
include("VisualizationFunctions.jl")

end


# =========================================================================================
"""
    legendre{T <: AbstractFloat}(
        maxn::Int,
        x   ::T
    )

Precomputes the Legendre polynomials ``Pₙ(x)`` for ``n = 0, 1, ..., \\texttt{maxn}-1`` and
returns a generic function to access the values.

# Return type
`(generic function)`
```julia
(n::Int) -> Pₙ(x)   # return type: T
```

# Example

```jldoctest; setup = :(using NESSie.TestModel: legendre)
julia> p = legendre(3, 0.5);

julia> [p(n) for n in 0:2]    # [P₀(0.5), P₁(0.5), P₂(0.5)]
3-element Array{Float64,1}:
  1.0
  0.5
 -0.125
```
"""
function legendre(maxn::Int, x::T) where T <: AbstractFloat
    maxn == 0 && return n::Int -> T[][n+1]
    maxn == 1 && return n::Int -> [one(T)][n+1]

    P = Array{T}(undef, maxn)
    P[1] = one(T) # P₀(x) = 0
    P[2] = x      # P₁(x) = x

    # Use Bonnet's recursion formula for remaining values:
    # Pₙ₊₁(x) = 1/(n+1) ⋅ [(2n + 1)⋅x⋅Pₙ(x) - n⋅Pₙ₋₁}(x)]
    @inbounds for n in 1:maxn-2
        P[n + 2] = (n + 1) \ ((2n + 1) * x * P[n + 1] - n * P[n])
    end
    n::Int -> P[n+1]
end

# =========================================================================================
"""
    spherical_besseli{T <: AbstractFloat}(
        maxn::Int,
        r   ::T
    )

Precomputes the modified spherical Bessel function of the first kind ``iₙ(r)`` for
``n=-1, 0, ..., \\texttt{maxn}`` and returns a generic function to access the values.
``iₙ(r)`` is defined as

```math
iₙ(r) = \\sqrt{\\frac{π}{2r}} I_{n+0.5}(r),
```

where ``I_ν`` is the modified Bessel function of the first kind
[[Xie16]](@ref Bibliography).

# Return type
`(generic function)`
```julia
(n::Int) -> iₙ(r)   # return type: T
```
"""
function spherical_besseli(maxn::Int, r::T) where T <: AbstractFloat
    i = √(π / 2r) * [besseli(n + one(T)/2, r) for n in -1:maxn]
    n::Int -> i[n+2]
end

# =========================================================================================
"""
    spherical_besselk{T <: AbstractFloat}(
        maxn::Int,
        r   ::T
    )

Precomputes the modified spherical Bessel function of the second kind ``kₙ(r)`` for
``n=-1, 0, ..., \\texttt{maxn}`` and returns a generic function to access the values.
``kₙ(r)`` is defined as

```math
kₙ(r) = \\sqrt{\\frac{π}{2r}} K_{n+0.5}(r),
```

where ``K_ν`` is the modified Bessel function of the second kind
[[Xie16]](@ref Bibliography).

# Return type
`(generic function)`
```julia
(n::Int) -> kₙ(r)   # return type: T
```
"""
function spherical_besselk(maxn::Int, r::T) where T <: AbstractFloat
    k = √(π / 2r) * [besselk(n + one(T)/2, r) for n in -1:maxn]
    n::Int -> k[n+2]
end


module LazyArraysTests

using Test
using Gridap.Arrays
using FillArrays
using Gridap.Helpers

a = rand(3,2,4)
test_array(a,a)

a = rand(3,2,4)
a = CartesianIndices(a)
test_array(a,a)

a = rand(16,32)
a = CartesianIndices(a)
c = lazy_map(-,a)
test_array(c,-a)

a = rand(12)
c = lazy_map(-,a)
test_array(c,-a)

a = rand(12)
b = rand(12)
c = lazy_map(-,a,b)
test_array(c,a.-b)

c = lazy_map(-,Float64,a,b)
test_array(c,a.-b)

a = rand(0)
b = rand(0)
c = lazy_map(-,a,b)
test_array(c,a.-b)

a = fill(rand(Int,2,3),12)
b = fill(rand(Int,1,3),12)
c = map(array_cache,(a,b))
i = 1
v = map((ci,ai) -> getindex!(ci,ai,i),c,(a,b))
# v = getitems!(c,(a,b),i)
@test c == (nothing,nothing)
@test v == (a[i],b[i])

a = fill(rand(Int,2,3),12)
b = fill(rand(Int,1,3),12)
ai = testitem(a)
@test ai == a[1]
ai, bi = map(testitem,(a,b))
@test ai == a[1]
@test bi == b[1]

a = fill(rand(Int,2,3),0)
b = fill(1,0)
ai = testitem(a)
@test ai == Array{Int,2}(undef,0,0)
ai, bi = map(testitem,(a,b))
@test ai == Array{Int,2}(undef,0,0)
@test bi == zero(Int)

a = fill(+,10)
x = rand(10)
y = rand(10)
v = lazy_map(+,x,y)
r = [(xi+yi) for (xi,yi) in zip(x,y)]
test_array(v,r)
v = lazy_map(+,Float64,x,y)
test_array(v,r)

p = 4
p1 = [1,2]
p2 = [2,1]
p3 = [4,3]
p4 = [6,1]

x = [p1,p2,p3,p4]

fa(x) = 2*x
fb(x) = sqrt.(x)

aa = Fill(fa,4)
r = lazy_map(fa,x)
@test all([ r[i] ≈ 2*x[i] for i in 1:4])

bb = Fill(fb,4)
r = lazy_map(fb,x)
@test all([ r[i] ≈ sqrt.(x[i]) for i in 1:4])

aaop = lazy_map(Operation,aa)
cm = lazy_map(evaluate,aaop,bb)
r = lazy_map(evaluate,cm,x)
@test all([ r[i] ≈ 2*(sqrt.(x[i])) for i in 1:4])

kk = cm[1]
ckk = return_cache(kk,p)
evaluate!(ckk,kk,p)

aop = lazy_map(Operation(+),aa,bb)
lazy_map(evaluate,aa,x)+lazy_map(evaluate,bb,x)
lazy_map(evaluate,aop,x)
@test lazy_map(evaluate,aop,x) == lazy_map(evaluate,aa,x)+lazy_map(evaluate,bb,x)

# Broadcasting

a = fill(rand(2,3),12)
b = rand(12)
c = lazy_map(Broadcasting(-),a,b)
test_array(c,[ai.-bi for (ai,bi) in zip(a,b)])

a = fill(rand(2,3),0)
b = rand(0)
c = lazy_map(Broadcasting(-),a,b)
test_array(c,[ai.-bi for (ai,bi) in zip(a,b)])

a = fill(rand(2,3),12)
b = rand(12)
c = lazy_map(Broadcasting(-),a,b)
d = lazy_map(Broadcasting(+),a,c)
e = lazy_map(Broadcasting(*),d,c)
test_array(e,[((ai.-bi).+ai).*(ai.-bi) for (ai,bi) in zip(a,b)])

a = Fill(Broadcasting(+),10)
x = [rand(2,3) for i in 1:10]
y = [rand(1,3) for i in 1:10]
v = lazy_map(evaluate,a,x,y)
r = [(xi.+yi) for (xi,yi) in zip(x,y)]
test_array(v,r)

a = Fill(Broadcasting(+),10)
x = [rand(mod(i-1,3)+1,3) for i in 1:10]
y = [rand(1,3) for i in 1:10]
v = lazy_map(evaluate,a,x,y)
r = [(xi.+yi) for (xi,yi) in zip(x,y)]
test_array(v,r)

# Operations

x = fill(4,3,3)

ax = Fill(x,4)
aa = Fill(fa,4)
bb = Fill(fb,4)

aop = lazy_map(Operation(Broadcasting(+)),aa,bb)
aax = lazy_map(evaluate,aa,ax)
bbx = lazy_map(evaluate,bb,ax)
aopx = lazy_map(evaluate,aop,ax)
@test aopx == aax+bbx

aop = lazy_map(Operation(Broadcasting(*)),aa,bb)
aopx = lazy_map(evaluate,aop,ax)
@test aopx[1] == aax[1].*bbx[1]

# Allocations

x = fill(4,3,3)

ax = Fill(x,4)
aa = Fill(Operation(fa),4)
bb = Fill(fb,4)
cm = lazy_map(evaluate,aa,bb)
r = lazy_map(evaluate,cm,ax)
@test all([ r[i] ≈ 2*(sqrt.(ax[i])) for i in 1:4])

nn = 2
an = Fill(nn,4)
ap = Fill(Broadcasting(*),4)
cm = lazy_map(evaluate,ap,ax,an)
@test all([cm[i] == nn*ax[i] for i in 1:4])

c_cm = array_cache(cm)
@allocated getindex!(c_cm,cm,1)
nalloc = 0
for i in length(cm)
  global nalloc
  nalloc += @allocated getindex!(c_cm,cm,i)
end
# @test nalloc == 0

as = Fill(Broadcasting(sqrt),4)
cs = lazy_map(evaluate,as,ax)
@test all([cs[i] == sqrt.(ax[i]) for i in 1:4])

c_cs = array_cache(cs)
@allocated getindex!(c_cs,cs,1)
nalloc = 0
for i in length(cs)
  global nalloc
  nalloc += @allocated getindex!(c_cs,cs,i)
end
# @test nalloc == 0

asm = lazy_map(Operation,as)
ah = lazy_map(evaluate,asm,ap)
ch = lazy_map(evaluate,ah,ax,an)
@test all([ ch[i] ≈ sqrt.(nn*ax[i]) for i in 1:4])
c_ch = array_cache(ch)
@allocated getindex!(c_ch,ch,1)
nalloc = 0
for i in length(ch)
  global nalloc
  nalloc += @allocated getindex!(c_ch,ch,i)
end
# @test nalloc == 0

# Empty arrays

g(x) = 1.0*x
a = lazy_map(g,Int[])
@test test_array(a,Float64[])

myf(x) = sqrt(x-1)
Arrays.testargs(::typeof(myf),x) = one(x)
@test test_array(lazy_map(myf,Int[]),Float64[])

# Fill optimizations

l = 10
v = 3
a = lazy_map(g,Fill(v,l))
test_array(a,Fill(g(v),l))
@test isa(a,Fill)

a = lazy_map(g,Float64,Fill(v,l))
test_array(a,Fill(g(v),l))
@test isa(a,Fill)

# array_cache

a = [rand(3) for i in 1:l]
b = [rand(3) for i in 1:l]
c = lazy_map(Broadcasting(+),a,b)
d = lazy_map(Broadcasting(*),a,c)

cache = array_cache(d)
@test getindex!(cache,d,1) === getindex!(cache,d,2)

i = 1
cache = array_cache(d)
@test getindex!(cache,d,i) === getindex!(cache,d,i)

ci = CartesianIndex((1))
cache = array_cache(d)
@test getindex!(cache,d,ci) === getindex!(cache,d,ci)

# Shapes and indices

s = (3,4)
a = rand(s...)
b = rand(s...)
c = lazy_map(+,a,b)
@test size(c) == s
d = a.+b
test_array(c,d)
@test c[1] == d[1]
@test c[2,3] == d[2,3]
@test c[CartesianIndex(2,3)] == d[CartesianIndex(2,3)]
@test c[2,3,1,1,1] == d[2,3,1,1,1]
@test c[CartesianIndex(2,3),1,1] == d[CartesianIndex(2,3),1,1]

s = (3,4)
a = rand(s...)
b = rand(prod(s))
c = lazy_map(+,a,b)
d = map(+,a,b)
@test size(c) == size(d)
@test  c == d

a = fill(x->2*x,3,3)
b = rand(9)
c = lazy_map(evaluate,a,b)
@test size(c) == (9,)
@test ndims(c) == 1
test_array(c,2*b)

# Test the intermediate results caching mechanism

a = Arrays.ArrayWithCounter(fill(rand(2,3),12))
b = Arrays.ArrayWithCounter(rand(12))
c = lazy_map(Broadcasting(-),a,b)
d = lazy_map(Broadcasting(+),a,c)
e = lazy_map(Broadcasting(*),d,c)
r = [ (ai.-bi).*(ai.+(ai.-bi)) for (ai,bi) in zip(a,b)]
test_array(e,r)
cache = array_cache(e)
Arrays.resetcounter!(a)
Arrays.resetcounter!(b)
for i in 1:length(e)
  ei = getindex!(cache,e,i)
  ei = getindex!(cache,e,i)
  ei = getindex!(cache,e,i)
end
@test all(a.counter .== 2)
@test all(b.counter .== 1)

# using lazy_map and test_map at the same time

n = 4
k  = Fill(+,n)
a = fill(2,n)
b = fill(4,n)
c = a + b
@test all(lazy_map(test_map,c,k,a,b))
@test all(lazy_map((x...)->test_map(x...,cmp=≈),c,k,a,b))

end # module


function getmetaboliteintervals(tag::String,
    molecule_names,
    ΩS_ppm::Vector{Vector{Vector{T}}},
    Δsys_border::Vector{Vector{T}};
    min_dist::T = 0.15) where T <: Real

    n_select = findfirst(xx->(xx==tag), molecule_names)
    C0 = ΩS_ppm[n_select]
    N_groups = length(C0)

    Δcompound_border = Δsys_border[n_select]
    @assert N_groups == length(Δcompound_border)

    st_c = Vector{Vector{T}}(undef, N_groups)
    fin_c = Vector{Vector{T}}(undef, N_groups)

    for i = 1:N_groups
        st_c[i], fin_c[i] = segmentmoleculefreqs2(C0[i],
            Δcompound_border[i], min_dist = min_dist)
    end

    return MetaboliteRegionType(st_c, fin_c)
end

# A region needs to contain all interval C0[l] +/- r such that
#   all intervals are within min_dist from each other.
# r and min_dst are in ppm.
function segmentmoleculefreqs2(C0::Vector{T}, border::T;
    min_dist::T = 0.15) where T <: Real

    sort_inds = sortperm(C0)
    C = C0[sort_inds]

    st_c = Vector{T}(undef, 0)
    fin_c = Vector{T}(undef, 0)

    push!(st_c, C[1])

    for l = 2:length(C)
        left = C[l-1] + border
        right = C[l] -border

        if (right-left) > min_dist
            push!(fin_c, C[l-1])
            push!(st_c, C[l])
        end
    end
    push!(fin_c, C[end])

    return st_c, fin_c
end

function constructgraph(sts::Vector{T}, fins::Vector{T}) where T

    # construct graph.
    N_intervals = length(sts)
    g = LightGraphs.SimpleGraph(N_intervals)

    for i = 1:N_intervals
        flags = isthereoverlap(sts[i], fins[i], sts, fins)

        for j = 1:length(flags)
            if flags[j]
                #add edge i to j, j to i.
                LightGraphs.add_edge!(g, i, j)
                LightGraphs.add_edge!(g, j, i)
            end
        end
    end

    return g
end

function isthereoverlap(st::T, fin::T, sts, fins) where T

    flags = collect( isoverlapped(st, fin, sts[i], fins[i]) for i = 1:length(fins))

    return flags
end

# assumes st_a < fin_a, st_b < fin_b.
function isoverlapped(st_a::T, fin_a::T, st_b::T, fin_b::T) where T

    if st_a < st_b && st_b < fin_a < fin_b
        return true

    elseif st_b < st_a && st_a < fin_b < fin_a
        return true

    elseif st_b < st_a < fin_b
        return true

    elseif st_a < st_b < fin_a
        return true

    end

    return false
end


using Test, BitBasis

@test bsizeof(ind) == sizeof(Int) * 8
@test onehot(ComplexF64, 2, 2) == [0, 0, 1, 0]
@test bdistance(1, 7) == 2
@test onehot(ComplexF64, bit"01", 2) == transpose(ComplexF64[0 1 0 0; 0 1 0 0])
@test log2dim1(rand(4, 4)) == log2i(4)

@testset "log2i" begin
    for itype in [Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128]
        @test log2i(itype(2^5)) == 5
        @test typeof(log2i(itype(2^5))) == Int
    end
end

@testset "bit length" begin
    @test bit_length(8) == 4
    @test bit_length(Int32(8)) == 4
end

@testset "indices with" begin
    # indices_with
    nbit = 5
    poss, vals = [4, 2, 3], [1, 0, 1]
    @test indices_with(nbit, poss, vals) == filter(x -> readbit.(x, poss) == vals, basis(nbit))
end


"""
    non_zero_elements(t)

Return an iterator over non-zero elements of `t` as tuples whose first element
is a tuple of indices and whose second element is the value of `t` at those indices.
"""
function non_zero_elements(t)
    ((ind, t[ind...]) for ind in Iterators.product(axes(t)...) if !iszero(t[ind...]))
end

"""
    non_zero_elements_python(t)

Like `non_zero_elements`, but use Python's order for product of iterators. Also use zero-based indexing in
reported indices.
"""
function non_zero_elements_python(t)
    ((reverse(ind .- 1), t[reverse(ind)...]) for ind in Iterators.product(axes(t)...) if !iszero(t[reverse(ind)...]))
end

"""
    phys_to_chem(two_body)

Convert the rank-four tensor `two_body` representing two-body integrals from physicist
index order to chemist index order: i,j,k,l -> i,l,j,k

See `chem_to_phys`, `check_two_body_symmetries`.
"""
function phys_to_chem(two_body)
    @tullio two_body_out[i,l,j,k] := two_body[i,j,k,l]
    return two_body_out
end

"""
    chem_to_phys(two_body)

Convert the rank-four tensor `two_body` representing two-body integrals from chemist
index order to physicist index order: i,j,k,l -> i,k,l,j

See `phys_to_chem`, `check_two_body_symmetries`.

# Note
Either `chem_to_phys` or `phys_to_chem` generates the cyclic group of order three.
Denote `chem_to_phys` by `g` and `phys_to_chem` by `h`. The elements `g`, `h`, `I` form
a group with `gh = hg = I`, `g^2=h`, and `h^2=g`.
"""
function chem_to_phys(two_body)
    @tullio two_body_out[i,k,l,j] := two_body[i,j,k,l]
    ## Following is tranform used in qiskit_nature. It is the same as
    ## above, because of symmetry.
    #  @tullio two_body_out[l,j,i,k] := two_body[i,j,k,l]
    return two_body_out
end

"""
    to_chem(two_body_tensor)

Permute `two_body_tensor` to chemists' index order, if it is not already
in this order and return the new tensor.

See `phys_to_chem`, `chem_to_phys`.
"""
function to_chem(two_body_tensor)
    if check_two_body_symmetries(two_body_tensor)
        return two_body_tensor
    end
    transformed_tensor = phys_to_chem(two_body_tensor)
    if check_two_body_symmetries(transformed_tensor)
        return transformed_tensor
    end
    transformed_tensor = phys_to_chem(transformed_tensor)
    if check_two_body_symmetries(transformed_tensor)
        return transformed_tensor
    end
    throw(ArgumentError("Unable to permute `two_body_tensor` to chemists' index order"))
end

"""
    to_phys(two_body_tensor)

Permute `two_body_tensor` to physicists' index order, if it is not already
in this order and return the new tensor.

# Throws
- `ArgumentError`: If the algorithm fails to permute `two_body_tensor` to physicists' order.
"""
function to_phys(two_body_tensor)
    index_order = find_index_order(two_body_tensor)
    if index_order == :physicist
        return two_body_tensor
    end
    if index_order == :chemist
        return chem_to_phys(two_body_tensor)
    end
    if index_order == :intermediate
        return phys_to_chem(two_body_tensor)
    end
    throw(ArgumentError("Unable to permute `two_body_tensor` to physicists' index order"))
end

## This routine (and many others here) are quite inefficient. But, they are probably
## more clear as written, and furthermore they are unlikely to be perf bottlenecks.
function to_intermediate(two_body_tensor)
    index_order = find_index_order(two_body_tensor)
    if index_order == :intermediate
        return two_body_tensor
    end
    if index_order == :chemist  # doing the "wrong" permutation takes chem to intermediate.
        return phys_to_chem(two_body_tensor)
    end
    if index_order == :physicist  # doing the "wrong" permutation takes phys to intermediate.
        return chem_to_phys(two_body_tensor)
    end
    throw(ArgumentError("Unable to permute `two_body_tensor` to intermediate' index order"))
end

"""
    to_index_order(two_body_tensor, index_order)

Permute the rank-four tensor `two_body_tensor` so that the indices
are in the order specified by `index_order`, which must be one of
`:chemist`, `:physicist`, or `:intermediate`.

`two_body_tensor` must be in one of the three index orders in order that
the permutation algorithm be successful. This function is relatively inefficient.
It first performs sequences of symmetry tests to detemine the index order of the
input tensor. It then performs the correct sequence of permutations to bring the
tensor to the desired input order. If performance is important, and you know the
index order of the input tensor, it is better to apply just the single required
permutation.

# Throws:
- `ArgumentError` if the algorithm is unable to obtain the desired index order.
"""
function to_index_order(two_body_tensor, index_order)
    if index_order == :chemist
        return to_chem(two_body_tensor)
    elseif index_order == :physicist
        return to_phys(two_body_tensor)
    elseif index_order == :intermediate
        return to_intermediate(two_body_tensor)
    else
        throw(ArgumentError("`index_order` must be one of `:chemist`, `:physicist`, or `:intermediate`"))
    end
end

"""
    check_two_body_symmetries(two_body_tensor; chemist=true)

Return `true` if the rank-4 tensor `two_body_tensor` has the required symmetries for coefficents of the
two-electron terms.  If `chemist` is `true`, assume the input is in chemists' order,
otherwise in physicists' order.

If `two_body_tensor` is a correct tensor of indices, with the correct index order, it must pass the
tests. If `two_body_tensor` is a correct tensor of indicies, but the flag `chemist` is incorrect, it will
fail the tests, unless the tensor has accidental symmetries.
This test may be used with care to discriminiate between the orderings.

References: HJO Molecular Electronic-Structure Theory (1.4.17), (1.4.38)

See `phys_to_chem`, `chem_to_phys`, `find_index_order`.
"""
function check_two_body_symmetries(two_body_tensor_in; chemist=true)
    two_body_tensor = ZChop.zchop(two_body_tensor_in)
    if ! chemist
        ## Safest is to use property that if a tensor satisfies phys ordered symmetries,
        ## then it satisfies chem ordered symmetries after transformation
        two_body_tensor = phys_to_chem(two_body_tensor)
    end
    t1 = similar(two_body_tensor) # `t1` will hold the transformed tensor
    for test! in _chem_tests
        test!(t1, two_body_tensor)  # write transformed tensor to `t1`
        if ! (t1 ≈ two_body_tensor)
            return false
        end
    end
    return true
end

"""
    find_index_order(two_body_tensor)

Return the index convention of rank-four `two_body_tensor`.

The index convention is determined by checking symmetries of the tensor.
If the indexing convention can be determined, then one of `:chemist`,
`:physicist`, or `:intermediate` is returned. The `:intermediate` indexing
may be obtained by applying `chem_to_phys` to the physicists' convention or
`phys_to_chem` to the chemists' convention. If the tests for each of these
conventions fail, then `:unknown` is returned.

See also: `chem_to_phys`, `phys_to_chem`, `check_two_body_symmetries`.

# Note
The first of `:chemist`, `:physicist`, and `:intermediate`, in that order, to pass the tests
is returned. If `two_body_tensor` has accidental symmetries, it may in fact satisfy more
than one set of symmetry tests. For example, if all elements have the same value, then the
symmetries for all three index orders are satisfied.
"""
function find_index_order(two_body_tensor)
    if check_two_body_symmetries(two_body_tensor)
        return :chemist
    else
        transformed_tensor = phys_to_chem(two_body_tensor)
        if check_two_body_symmetries(transformed_tensor)
            return :physicist
        end
    end
    transformed_tensor = phys_to_chem(transformed_tensor)
    if check_two_body_symmetries(transformed_tensor)
        return :intermediate
    end
    return :unknown
end

## Tests for two-electron coefficient symmetries in chemists' order.
## These anonymous functions each take two arguments. `t` is the tensor
## to be tested and `t1` is a tensor of the same shape that will be overwritten
## with the transformed tensor. The calling code should then compare `t` and
## `t1` for approximate equality.
const _chem_tests = [
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[q, p, r, s]),  # (1.4.38) in HJO book
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[p, q, s, r]),  # (1.4.38)
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[q, p, s, r]),  # (1.4.38)
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[r, s, p, q]),  # (1.4.17)
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[r, s, q, p]),  # 1 and 4
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[s, r, p, q]),  # 2 and 4
        ((t1, t) ->  @tullio t1[p, q, r, s] = t[s, r, q, p]),  # 3 and 4
    ]

# import PkgVersion
# import Dates
# function data_header_dict()
#     d = Dict{String, Any}()
#     d["version"] = PkgVersion.Version(ElectronicStructure)
#     d["date"] = Dates.now()
#     return d
# end


const SpinHalfSite = Union{TagType"S=1/2", TagType"SpinHalf"}

function siteinds(::SpinHalfSite,
                  N::Int; kwargs...)
  conserve_qns = get(kwargs,:conserve_qns,false)
  conserve_sz = get(kwargs,:conserve_sz,conserve_qns)
  if conserve_sz
    return [Index(QN("Sz",+1)=>1,QN("Sz",-1)=>1;tags="Site,S=1/2,n=$n") for n=1:N]
  end
  return [Index(2,"Site,S=1/2,n=$n") for n=1:N]
end

function state(::SpinHalfSite,
               st::AbstractString)
  if st == "Up" || st == "↑"
    return 1
  elseif st == "Dn" || st == "↓"
    return 2
  end
  throw(ArgumentError("State string \"$st\" not recognized for SpinHalf site"))
  return 0
end

function op(::SpinHalfSite,
            s::Index,
            opname::AbstractString;kwargs...)::ITensor
  Up = s(1)
  UpP = s'(1)
  Dn = s(2)
  DnP = s'(2)

  Op = emptyITensor(s',dag(s))

  if opname == "S⁺" || opname == "Splus" || opname == "S+"
    Op[UpP, Dn] = 1.
  elseif opname == "S⁻" || opname == "Sminus" || opname == "S-"
    Op[DnP, Up] = 1.
  elseif opname == "Sˣ" || opname == "Sx"
    Op[UpP, Dn] = 0.5
    Op[DnP, Up] = 0.5
  elseif opname == "iSʸ" || opname == "iSy"
     Op[UpP, Dn] = 0.5
     Op[DnP, Up] = -0.5
  elseif opname == "Sʸ" || opname == "Sy"
     Op = complex(Op)
     Op[UpP, Dn] = -0.5*im
     Op[DnP, Up] = 0.5*im
  elseif opname == "Sᶻ" || opname == "Sz"
     Op[UpP, Up] = 0.5
     Op[DnP, Dn] = -0.5
  elseif opname == "projUp"
     Op[UpP, Up] = 1.
  elseif opname == "projDn"
    Op[DnP, Dn] = 1.
  elseif opname == "Up" || opname == "↑"
    pU = emptyITensor(s)
    pU[Up] = 1.
    return pU
  elseif opname == "Dn" || opname == "↓"
    pD = emptyITensor(s)
    pD[Dn] = 1.
    return pD
  else
    throw(ArgumentError("Operator name '$opname' not recognized for SpinHalfSite"))
  end
  return Op
end


u = 2.0collect(1:10)
t = 1.0collect(1:10)
A = LinearInterpolation(u,t)

@test length(A) == 20
for i in 1:10
  @test u[i] == A[i]
end

for i in 11:20
  @test t[i-10] == A[i]
end

A = LinearInterpolation{false}(u,t)
@test length(A) == 10
for i in 1:10
  @test u[i] == A[i]
end

using Symbolics
u = 2.0collect(1:10)
t = 1.0collect(1:10)
A = LinearInterpolation(u,t)

@variables t x(t)
substitute(A(t), Dict(t => x))


using Arpack, SparseArrays, LinearAlgebra

#include("file.jl")

# defining vectors
n = 10
a = Matrix(I, n, n)

b = [1 0 0; 0 1 0; 0 0 1]
c = zeros(10,1)
d = ones(10,1)

e = sparse(a)
eigs(e)

# defining functions
function f(x)
    return x + 1
end

# g(1) will fail, but g(1.0) will work
function g(x::Float64)
    return x+1
end

# h(1) will work, but h("1") will not
function h(x::T) where {T <: Number}
    return x+1
end

function main(x)
    f(x)
    #g(x)
    h(x)

    return nothing
end

main(1)

@time main(1)

using BenchmarkTools

@benchmark main(1)

using Plots

a = rand(10,10)

heatmap(a)


@testset "tensorproduct" begin
    rng = MersenneTwister(123456)
    u1 = rand(rng, 10)
    u2 = rand(rng, 10)
    v1 = rand(rng, 5)
    v2 = rand(rng, 5)

    # kernels
    k1 = SqExponentialKernel()
    k2 = ExponentialKernel()
    kernel1 = TensorProduct(k1, k2)
    kernel2 = TensorProduct([k1, k2])

    @test kernel1.kernels === (k1, k2) === TensorProduct((k1, k2)).kernels
    @test length(kernel1) == length(kernel2) == 2
    @test_throws DimensionMismatch kernel1(rand(3), rand(3))

    @testset "val" begin
        for (x, y) in (((v1, u1), (v2, u2)), ([v1, u1], [v2, u2]))
            val = k1(x[1], y[1]) * k2(x[2], y[2])

            @test kernel1(x, y) == kernel2(x, y) == val
        end
    end

    @testset "kernelmatrix and kerneldiagmatrix" begin
        X = rand(rng, 2, 10)
        x_cols = ColVecs(X)
        x_rows = RowVecs(X')
        Y = rand(rng, 2, 10)
        y_cols = ColVecs(Y)
        y_rows = RowVecs(Y')

        trueX = kernelmatrix(k1, X[1, :]) .* kernelmatrix(k2, X[2, :])
        trueXY = kernelmatrix(k1, X[1, :], Y[1, :]) .* kernelmatrix(k2, X[2, :], Y[2, :])
        tmp = Matrix{Float64}(undef, 10, 10)
        tmp_diag = Vector{Float64}(undef, 10)

        for kernel in (kernel1, kernel2), (x, y) in ((x_cols, y_cols), (x_rows, y_rows))
            @test kernelmatrix(kernel, x) ≈ trueX

            @test kernelmatrix(kernel, x, y) ≈ trueXY

            fill!(tmp, 0)
            kernelmatrix!(tmp, kernel, x)
            @test tmp ≈ trueX

            fill!(tmp, 0)
            kernelmatrix!(tmp, kernel, x, y)
            @test tmp ≈ trueXY

            @test kerneldiagmatrix(kernel, x) ≈ diag(kernelmatrix(kernel, x))

            fill!(tmp_diag, 0)
            kerneldiagmatrix!(tmp_diag, kernel, x)
            @test tmp_diag ≈ diag(kernelmatrix(kernel, x))
        end
    end

    @testset "single kernel" begin
        kernel = TensorProduct(k1)
        @test length(kernel) == 1

        @testset "eval" begin
            for (x, y) in (((v1,), (v2,)), ([v1], [v2]))
                val = k1(x[1], y[1])

                @test kernel(x, y) == val
            end
        end

        @testset "kernelmatrix" begin
            N = 10

            x = randn(rng, N)
            y = randn(rng, N)
            vectors = (x, y)

            X = reshape(x, 1, :)
            x_cols = ColVecs(X)
            x_rows = RowVecs(X')
            Y = reshape(y, 1, :)
            y_cols = ColVecs(Y)
            y_rows = RowVecs(Y')

            trueX = kernelmatrix(k1, x)
            trueXY = kernelmatrix(k1, x, y)
            tmp = Matrix{Float64}(undef, N, N)
            tmp_diag = Vector{Float64}(undef, N)

            for (x, y) in ((x, y), (x_cols, y_cols), (x_rows, y_rows))

                @test kernelmatrix(kernel, x) ≈ trueX

                @test kernelmatrix(kernel, x, y) ≈ trueXY

                fill!(tmp, 0)
                kernelmatrix!(tmp, kernel, x)
                @test tmp ≈ trueX

                fill!(tmp, 0)
                kernelmatrix!(tmp, kernel, x, y)
                @test tmp ≈ trueXY

                @test kerneldiagmatrix(kernel, x) ≈ diag(kernelmatrix(kernel, x))

                fill!(tmp_diag, 0)
                kerneldiagmatrix!(tmp_diag, kernel, x)
                @test tmp_diag ≈ diag(kernelmatrix(kernel, x))
            end
        end
    end
    test_ADs(()->TensorProduct(SqExponentialKernel(), LinearKernel()), dims = [2, 2]) # ADs = [:ForwardDiff, :ReverseDiff])
end


######################
# 1: Attributes of ToricVarieties
######################

@doc Markdown.doc"""
    dim( v::AbstractNormalToricVariety )

Computes the dimension of the normal toric variety `v`.
"""
function dim( v::AbstractNormalToricVariety )
    return v.polymakeNTV.FAN_DIM::Int
end
export dim

@doc Markdown.doc"""
    dim_of_torusfactor( v::AbstractNormalToricVariety )

Computes the dimension of the torus factor of the normal toric variety `v`.
"""
function dim_of_torusfactor( v::AbstractNormalToricVariety )

    if has_torusfactor( v ) == false
        return 0
    end

    dimension_of_fan = v.polymakeNTV.FAN_DIM::Int
    ambient_dimension = v.polymakeNTV.FAN_AMBIENT_DIM::Int
    return ambient_dimension - dimension_of_fan
end
export dim_of_torusfactor

@doc Markdown.doc"""
    euler_characteristic( v::AbstractNormalToricVariety )

Computes the Euler characteristic of the normal toric variety `v`.
"""
function euler_characteristic( v::AbstractNormalToricVariety )
    f_vector = Vector{Int}(v.polymakeNTV.F_VECTOR)
    return f_vector[ dim( v ) ]
end
export euler_characteristic

"""
    nef_cone( v::NormalToricVariety )

Computes the nef cone of the normal toric variety `v`.

# Examples
```jldoctest
julia> pp = projective_space(2)
A normal toric variety corresponding to a polyhedral fan in ambient dimension 2

julia> nef = nef_cone(pp)
A polyhedral cone in ambient dimension 1

julia> dim(nef)
1
```
"""
nef_cone( v::NormalToricVariety ) = Cone( v.polymakeNTV.NEF_CONE )
export nef_cone

"""
    mori_cone( v::NormalToricVariety )

Computes the mori cone of the normal toric variety `v`.

# Examples
```jldoctest
julia> pp = projective_space(2)
A normal toric variety corresponding to a polyhedral fan in ambient dimension 2

julia> mori = mori_cone(pp)
A polyhedral cone in ambient dimension 1

julia> dim(mori)
1
```
"""
mori_cone( v::NormalToricVariety ) = Cone( v.polymakeNTV.MORI_CONE )
export mori_cone

######################
# 2: Methods of ToricVarieties
######################

@doc Markdown.doc"""
    ith_betti_number( v::AbstractNormalToricVariety, i::Int )

Compute the i-th Betti number of the normal toric variety `v`.
"""
function ith_betti_number( v::AbstractNormalToricVariety, i::Int )
    if isodd(i)
        return 0
    end
    k = div(i, 2)
    f_vector = Vector{Int}(v.polymakeNTV.F_VECTOR)
    pushfirst!(f_vector, 1)
    betti_number = sum( (-1)^(i-k) * binomial(i,k) * f_vector[ dim( v ) - i + 1 ] for i=k:dim( v ))
    return betti_number

end
export ith_betti_number

@doc Markdown.doc"""
    toric_ideal_binomial_generators(antv::AffineNormalToricVariety)

Get the exponent vectors corresponding to the generators of the toric ideal
associated to the affine normal toric variety `antv`.

# Examples
Take the cyclic quotient singularity corresponding to the pair of integers
`(2,5)`.
```jldoctest
julia> C = Oscar.positive_hull([-2 5; 1 0])
A polyhedral cone in ambient dimension 2

julia> antv = AffineNormalToricVariety(C)
A normal toric variety corresponding to a polyhedral fan in ambient dimension 2

julia> toric_ideal_binomial_generators(antv)
pm::Matrix<long>
-1 -1 2 1
-1 0 3 -1
0 -1 -1 2
```
"""
function toric_ideal_binomial_generators(antv::AffineNormalToricVariety)
    pmntv = pm_ntv(antv)
    result = pmntv.TORIC_IDEAL.BINOMIAL_GENERATORS
    return result
end
export toric_ideal_binomial_generators


# methods/inplace.jl
#
# Method-based access to tensor operations using inplace definitions.
tensorcopy!(A, IA, C, IC) = tensorcopy!(A, tuple(IA...), C, tuple(IC...))
tensoradd!(α, A, IA, β, C, IC) = tensoradd!(α, A, tuple(IA...), β, C, tuple(IC...))
tensortrace!(α, A, IA, β, C, IC) = tensortrace!(α, A, tuple(IA...), β, C, tuple(IC...))
tensorcontract!(α, A, IA, conjA, B, IB, conjB, β, C, IC) =
    tensorcontract!(α, A, tuple(IA...), conjA, B, tuple(IB...), conjB, β, C, tuple(IC...))
tensorproduct!(α, A, IA, B, IB, β, C, IC) = tensorproduct!(α, A, tuple(IA...), B, tuple(IB...), β, C, tuple(IC...))

"""
    tensorcopy!(A, IA, C, IC)

Copies `A` into `C` by permuting the dimensions according to the pattern specified by
`IA` and `IC`. Both iterables should contain the same elements in a different order.
The result of this method is equivalent to `permutedims!(C, A, p)` where `p` is the
permutation such that `IC=IA[p]`. The implementation of tensorcopy! is however more
efficient on average, especially if `Threads.nthreads() > 1`.
"""
tensorcopy!(A, IA::Tuple, C, IC::Tuple) = tensoradd!(1, A, IA, 0, C, IC)

"""
    tensoradd!(α, A, IA, β, C, IC)

Updates `C` to `β*C + α * tensorcopy(A,IA,IC)`, but without creating the temporary
permuted array.

See also: [`tensorcopy`](@ref)
"""
function tensoradd!(α, A, IA::Tuple, β, C, IC::Tuple)
    indCinA = add_indices(IA, IC)
    add!(α, A, :N, β, C, indCinA)
end

"""
    tensortrace!(α, A, IA, β, C, IC)

Updates `C` to `β*C + α tensortrace(A,IA,IC)`, but without creating the temporary
traced array.

See also: [`tensortrace`](@ref)
"""
function tensortrace!(α, A, IA::Tuple, β, C, IC::Tuple)
    indCinA, cindA1, cindA2 = trace_indices(IA, IC)
    trace!(α, A, :N, β, C, indCinA, cindA1, cindA2)
    return C
end

"""
    tensorcontract!(α, A, labelsA, conjA, B, labelsB, conjB, β, C, labelsC)

Replaces `C` with `β C + α A * B`, where some indices of array `A` are contracted with corresponding
indices in array `B` by assigning them identical labels in the iterables `labelsA` and `labelsB`.
The arguments `conjA` and `conjB` should be of type `Char` and indicate whether the data of
arrays `A` and `B`, respectively, need to be conjugated (value `'C'`) or not (value `'N'`).
Every label should appear exactly twice in the union of `labelsA`, `labelsB` and `labelsC`,
either in the intersection of `labelsA` and `labelsB` (for indices that need to be contracted)
or in the interaction of either `labelsA` or `labelsB` with `labelsC`, for indicating the order
in which the open indices should be match to the indices of the output array `C`.
"""
function tensorcontract!(α, A, IA::Tuple, conjA, B, IB::Tuple, conjB, β, C, IC::Tuple)

    conjA == 'N' || conjA == 'C' || throw(ArgumentError("Value of conjA should be 'N' or 'C' instead of $conjA"))
    conjB == 'N' || conjB == 'C' || throw(ArgumentError("Value of conjB should be 'N' or 'C' instead of $conjB"))
    CA = conjA == 'N' ? :N : :C
    CB = conjB == 'N' ? :N : :C

    oindA, cindA, oindB, cindB, indCinoAB = contract_indices(IA, IB, IC)
    contract!(α, A, CA, B, CB, β, C, oindA, cindA, oindB, cindB, indCinoAB)
    return C
end

"""
    tensorproduct!(α, A, labelsA, B, labelsB, β, C, labelsC)

Replaces C with `β C + α A * B` without any indices being contracted.
"""
function tensorproduct!(α, A, IA::Tuple, B, IB::Tuple, β, C, IC::Tuple)
    isempty(intersect(IA, IB)) || throw(LabelError("not a valid tensor product"))
    tensorcontract!(α, A, IA, 'N', B, IB, 'N', β, C, IC)
end


# ## Getting started

using MLJ, RDatasets, ScientificTypes, PrettyPrinting

@load LinearRegressor pkg=MLJLinearModels
@load RidgeRegressor pkg=MLJLinearModels
@load LassoRegressor pkg=MLJLinearModels

hitters = dataset("ISLR", "Hitters")
@show size(hitters)
names(hitters) |> pprint

# The target is `Salary`

y, X = unpack(hitters, ==(:Salary), col->true);

# It has missing values which we will just ignore:

no_miss = .!ismissing.(y)
y = collect(skipmissing(y))
X = X[no_miss, :]
train, test = partition(eachindex(y), 0.5, shuffle=true, rng=424);

# Most features are currently encoded as integers but we will consider them as continuous

Xc = coerce(X, autotype(X, rules=(:discrete_to_continuous,)))
scitype(Xc)

# There're a few features that are categorical which we'll one-hot-encode.

# ## Ridge pipeline
# ### Baseline
#
# Let's first fit a simple pipeline with a standardizer, a one-hot-encoder and a basic linear regression:

@pipeline RegPipe(std = Standardizer(),
                  hot = OneHotEncoder(),
                  reg = LinearRegressor())

model = RegPipe()
pipe  = machine(model, Xc, y)
fit!(pipe, rows=train)
ŷ = predict(pipe, rows=test)
round(rms(ŷ, y[test])^2, sigdigits=4)

# ### Basic ridge
#
# Let's now swap the linear regressor for a ridge one without specifying the penalty (`1` by default):

pipe.model.reg = RidgeRegressor()
fit!(pipe, rows=train)
ŷ = predict(pipe, rows=test)
round(rms(ŷ, y[test])^2, sigdigits=4)

# Ok that's a bit better but surely we can do better with an appropriate selection of the hyperparameter.

# ### Cross validating

# What penalty should you use? Let's do a simple CV to try  to find out:

r  = range(model, :(reg.lambda), lower=1e-2, upper=100_000, scale=:log10)
tm = TunedModel(model=model, ranges=r, tuning=Grid(resolution=50),
                resampling=CV(nfolds=3, rng=4141), measure=rms)
mtm = machine(tm, Xc, y)
fit!(mtm, rows=train)

best_mdl = fitted_params(mtm).best_model
round(best_mdl.reg.lambda, sigdigits=4)

# right, and  with that we get:

ŷ = predict(mtm, rows=test)
round(rms(ŷ, y[test])^2, sigdigits=4)

# ## Lasso pipeline
#
# Let's do the same as above but using a Lasso model and adjusting the range a bit:

mtm.model.model.reg = LassoRegressor()
mtm.model.ranges = range(model, :(reg.lambda), lower=500, upper=100_000, scale=:log10)
fit!(mtm, rows=train)

best_mdl = fitted_params(mtm).best_model
round(best_mdl.reg.lambda, sigdigits=4)

# Ok and let's see how that does:

ŷ = predict(mtm, rows=test)
round(rms(ŷ, y[test])^2, sigdigits=4)

# Pretty good! and the parameters are reasonably sparse as expected:

coefs = mtm.fitresult.fitresult.machine.fitresult
round.(coefs, sigdigits=2)

# with around 50% sparsity:

sum(coefs .≈ 0) / length(coefs)

# ## Elastic net pipeline

@load ElasticNetRegressor pkg=MLJLinearModels

mtm.model.model.reg = ElasticNetRegressor()
mtm.model.ranges = [range(model, :(reg.lambda), lower=0.1, upper=100, scale=:log10),
                    range(model, :(reg.gamma),  lower=500, upper=10_000, scale=:log10)]
mtm.model.tuning = Grid(resolution=10)
fit!(mtm, rows=train)

best_mdl = fitted_params(mtm).best_model
@show round(best_mdl.reg.lambda, sigdigits=4)
@show round(best_mdl.reg.gamma, sigdigits=4)

# And it's not too bad in terms of accuracy either

ŷ = predict(mtm, rows=test)
round(rms(ŷ, y[test])^2, sigdigits=4)

# But the simple ridge regression seems to work best here.


using Test

using Distributions, LinearAlgebra, Statistics
using EnKF

@testset "Additive Inflation" begin

T = AdditiveInflation(10)

@test size(T)==(10,)

@test length(T)==10

@test mean(T)== zeros(10)

A = MvNormal(zeros(10), 1.05*ones(10))

T.α = A

@test var(T) == var(A)

@test var(T) == 1.05^2*ones(10)

end

@testset "Multiplicative Inflation" begin

T = MultiplicativeInflation(10)

@test length(T)==10

@test T.β == 1.0

T.β = 1.05

@test T.β == 1.05

end

@testset "MultiAdditive Inflation" begin

T = MultiAdditiveInflation(10)

@test size(T)== (10,)

@test length(T)==10

@test T.β == 1.0

# @test T.α == MvNormal(zeros(10), I)

@test mean(T) == zeros(10)

@test var(T) ==  ones(10)

A = MvNormal(zeros(10), 1.05*ones(10))

T.β = 1.05

T.α = A

@test T.β == 1.05

@test mean(T) == zeros(10)

@test var(T) == 1.05^2*ones(10)

A = MvNormal(zeros(10), 1.01*ones(10))
T = MultiAdditiveInflation(10, 1.01, A)

@test T.β == 1.01

@test mean(T) == zeros(10)

@test var(T) == 1.01^2*ones(10)

end

@testset "State" begin

end


using ApproxFun, SpecialFunctions, LinearAlgebra, Test

@testset "Readme" begin
    @testset "Calculus and algebra" begin
        x = Fun(identity,0..10)
        f = sin(x^2)
        g = cos(x)

        @test ≈(f(.1),sin(.1^2);atol=1000eps())

        h = f + g^2
        r = roots(h)
        rp = roots(differentiate(h))

        @test norm(h.(r))<1000eps()
        @test norm(h'.(rp))<100000eps()
    end

    @testset "Differentiation and Integration" begin
        f = Fun(x->exp(x),-1..1)
        @test norm(f-f')<1000eps()

        g = cumsum(f)
        g = g + f(-1)
        @test norm(f-g)<100eps()

        x = Fun(identity)
        f = exp(x)
        g = f/sqrt(1-x^2)

        space(f),domain(f)
        space(g),domain(g)

        f = Fun(x->cospi(5x))
        g = abs(f)
        space(f)
        space(g)

        x = Fun()
        f = erf(x)
        g = besselj(3,exp(f))
        h = airyai(10asin(f)+2g)
    end

    @testset "ODE" begin
        a,b = -1000,200
        x = Fun(identity, a..b)
        d = domain(x)
        D = Derivative(d)
        B = Dirichlet(d)
        L = D^2 - x
        u = [B;L] \ [[airyai(a),airyai(b)],0]

        @test ≈(u(0.),airyai(0.);atol=10000eps())

        ##) Nonlinear BVPs
        x=Fun()
        u0=0.0x

        N=u->[u(-1.)-1.,u(1.)+0.5,0.001u''+6*(1-x^2)*u'+u^2-1.]
        u=newton(N,u0)

        @test norm(N(u)[end]) ≤ 1000eps()
    end

    @testset "Periodic" begin
        f = Fun(cos,Fourier(-π..π))
        @test norm(differentiate(f) + Fun(sin,Fourier(-π..π))) < 100eps()

        s = Chebyshev(-π..π)
        a = Fun(t-> 1+sin(cos(2t)),s)
        L = Derivative() + a
        f = Fun(t->exp(sin(10t)),s)
        B = periodic(s,0)
        uChebyshev = [B;L]\[0.,f]

        s = Fourier(-π..π)
        a = Fun(t-> 1+sin(cos(2t)),s)
        L = Derivative() + a
        f = Fun(t->exp(sin(10t)),s)
        uFourier = L\f

        @test uChebyshev(0.) ≈ uFourier(0.)
    end

    @testset "Sampling" begin
        ## Sampling
        f = abs(Fun(sin,-5..5))
        x = ApproxFun.sample(f,10)
    end

    @testset "PDE" begin
        d = ChebyshevInterval()^2                            # Defines a rectangle

        # @time u = \([Dirichlet(d);Laplacian(d)+100I],
        #                     [ones(∂(d));0.];tolerance=1E-10)      # First four entries of rhs are
        #

        QR = qr([Dirichlet(d);Laplacian()+100I])
                @time ApproxFun.resizedata!(QR,:,4000)
                @time u = \(QR,
                                [ones(∂(d));0.];tolerance=1E-7)

        @test u(0.1,1.) ≈ 1.0
        @test ≈(u(0.1,0.2),-0.02768276827514463;atol=1E-8)
    end

    @testset "BigFloat" begin
        setprecision(1000) do
            d=BigFloat(0)..BigFloat(1)
            D=Derivative(d)
            u=[ldirichlet();D-I]\[1;0]
            @test u(1) ≈ exp(BigFloat(1))
        end
    end
end


import Composites
using Test

@testset "Textbook ABD" begin
    e1 = fill(148e9, 2)
    e2 = fill(9.65e9, 2)
    g12 = fill(4.55e9, 2)
    nu12 = fill(0.3, 2)
    theta = [0.0, 45.0]

    # Single value getQ
    qtest = [148.87e9  2.91e9  0.0;
               2.91e9  9.71e9  0.0;
               0.0     0.0     4.55e9]
    q = Composites.getQ(e1[1], e2[1], g12[1], nu12[1], theta[1])
    for i = 1:size(qtest, 1)
        for j = 1:size(qtest, 2)
            @test round(q[i,j]/1e9, digits=2) == qtest[i,j]/1e9
        end
    end

    # Single value getQ with angle
    qbartest = [45.65e9 36.55e9 34.79e9;
                36.55e9 45.65e9 34.79e9;
                34.79e9 34.79e9 38.19e9]
    qbar = Composites.getQ(e1[2], e2[2], g12[2], nu12[2], theta[2])
    for i = 1:size(qbartest, 1)
        for j = 1:size(qbartest, 2)
            @test round(qbar[i,j]/1e9, digits=2) == qbartest[i,j]/1e9
        end
    end

    # getABD test
    nply = [10, 10]
    tply = [0.0001, 0.0001]
    matid = [1, 1]
    theta = [0.0, 45.0]
    Atest = [194.52 39.46 34.79;
              39.46 55.36 34.79;
              34.79 34.79 42.74]
    Btest = [-51.61 16.82 17.40;
              16.82 17.97 17.40;
              17.40 17.40 16.82]
    Dtest = [64.84 13.15 11.60;
             13.15 18.45 11.60;
             11.60 11.60 14.25]

    q = Composites.getQ.(e1, e2, g12, nu12)
    A, B, D = Composites.getABD(matid, nply, tply, theta, q)
    for i = 1:size(qbartest, 1)
        for j = 1:size(qbartest, 2)
            @test round(A[i,j]/1e6, digits=2) == Atest[i,j]
            @test round(B[i,j]/1e3, digits=2) == Btest[i,j]
            @test round(D[i,j], digits=2) == Dtest[i,j]
        end
    end

    alfatest = [13.44  -4.85  -7.14;
                -4.85  41.81 -21.23;
                -7.14 -21.23  64.95]
    betatest = [17.07  -6.01 -11.06;
                -6.01  -5.04 -11.06;
               -11.06 -11.06 -24.05]
    deltatest = [40.32 -14.56 -21.41;
                -14.56 125.42 -63.68;
                -21.41 -63.68 194.86]

    alfa, beta, delta = Composites.compliance(A, B, D)
    for i = 1:size(qbartest, 1)
        for j = 1:size(qbartest, 2)
            @test round(alfa[i,j]*1e9, digits=2) == alfatest[i,j]
            @test round(beta[i,j]*1e6, digits=2) == betatest[i,j]
            @test round(delta[i,j]*1e3, digits=2) == deltatest[i,j]
        end
    end
end

### Classical Laminate Theory ###

# materials
e1 = [181e9, 203e9, 38.6e9, 76e9]
e2 = [10.3e9, 11.2e9, 8.27e9, 5.5e9]
g12 = [7.17e9, 8.4e9, 4.14e9, 2.3e9]
nu12 = [0.28, 0.32, 0.26, 0.34]
rho = zeros(Float64, 4)
xt = [1500.0, 3500.0, 1062.0, 1400.0]*1e6
xc = [1500.0, 1540.0, 610.0, 235.0]*1e6
yt = [40.0, 56.0, 31.0, 12.0]*1e6
yc = [246.0, 150.0, 118.0, 53.0]*1e6
s = [68.0, 98.0, 72.0, 34.0]*1e6
t = fill(0.0001, 4)
mat = Composites.Material.(e1, e2, g12, nu12, rho, xt, xc, yt, yc, s, t)

# Laminate
matid = [4, 4, 4, 4, 4, 4, 4]
nply = [5, 5, 5, 10, 5, 5, 5]
tply = [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001]
theta = [0.0, 45.0, -45.0, 90.0, -45.0, 45.0, 0.0]
lam = Composites.Laminate{Float64}(matid, nply, tply, theta)

# getz(tply::Array{Float64,1},nply::Array{Int64,1))
z = Composites.getz(tply, nply)
@testset "getz" begin
    ztest = [-2.0, -1.5, -1.0, -0.5, 0.5, 1.0, 1.5, 2.0]*1e-3
    for i = 1:length(z)
        @test isapprox(z[i], ztest[i])
    end
    # getz(lam::Laminate)
    @test Composites.getz(lam) == z
end

# getQ(e1::Array{Float64,1},e2::Array{Float64,1},g12::Array{Float64,1},
#   nu12::Array{Float64,1},theta::Array{Float64,1}=zeros(Float64,length(e1)))
q = Composites.getQ.(e1, e2, g12, nu12)
@testset "getQ" begin
    qtest1 = [181.811 2.897 0.0; 2.897 10.346 0.0; 0.0 0.0 7.17] #GPa
    for i = 1:3
        for j = 1:3
            @test round.(q[1][i,j]/1e9, digits=3) == qtest1[i,j]
        end
    end
    qtest3 = [39.167 2.182 0.0; 2.182  8.392 0.0; 0.0 0.0 4.14] #GPa
    for i = 1:3
        for j = 1:3
            @test round.(q[3][i,j]/1e9, digits=3) == qtest3[i,j]
        end
    end
    for i = 1:length(mat)
        # getQ(e1::Float64,e2::Float64,g12::Float64,nu12::Float64,theta::Float64=0.0)
        @test Composites.getQ(e1[i], e2[i], g12[i], nu12[i]) == q[i][:,:]
        # getQ(mat::material,theta::Float64)
        @test Composites.getQ(mat[i]) == q[i][:,:]
    end
    # getQ(mat::Array{material,1},theta::Array{Float64,1}
    # getQ(mat::Array{material,1},lam::Laminate)
    @test Composites.getQ.(mat) == q
end

# rotQ(q::Array{Float64,2},theta::Float64)
qbar = [Composites.rotQ(q[matid[i]], theta[i]) for i in 1:length(theta)]
@testset "rotQ" begin
    qbartest1 = [76.641 1.886 0.0; 1.886 5.546 0.0; 0.0 0.0 2.3] #GPa
    qbartest2 = [23.79 19.19 17.774; 19.19 23.79 17.774; 17.774 17.774 19.604] #GPa
    qbartest3 = [23.79 19.19 -17.774; 19.19 23.79 -17.774; -17.774 -17.774 19.604] #GPa
    qbartest4 = [5.546 1.886 0.0; 1.886 76.641 0.0; 0.0 0.0 2.3] #GPa
    for i = 1:3
        for j = 1:3
            @test round.(qbar[1][i,j]/1e9, digits=3) == qbartest1[i,j]
            @test round.(qbar[2][i,j]/1e9, digits=3) == qbartest2[i,j]
            @test round.(qbar[3][i,j]/1e9, digits=3) == qbartest3[i,j]
            @test round.(qbar[4][i,j]/1e9, digits=3) == qbartest4[i,j]
            @test round.(qbar[5][i,j]/1e9, digits=3) == qbartest3[i,j]
            @test round.(qbar[6][i,j]/1e9, digits=3) == qbartest2[i,j]
            @test round.(qbar[7][i,j]/1e9, digits=3) == qbartest1[i,j]
        end
    end
    # rotQ(q11::Float64,q12::Float64,q22::Float64,q66::Float64,theta::Float64)
    for i = 1:size(qbar, 3)
        @test Composites.rotQ(q[matid[i]][1,1],q[matid[i]][1,2],q[matid[i]][2,2],
            q[matid[i]][3,3], theta[i]) == qbar[i][:,:]
    end
end

# getABD(matid::Array{Int64,1},nply::Array{Int64,1},tply::Array{Float64,1},
#   theta::Array{Float64,1},q::Array{Float64,3})
A,B,D = Composites.getABD(matid, nply, tply, theta, q)
@testset "getABD" begin
    Atest = [1.298e8 4.215e7 0.0; 4.215e7 1.298e8 0.0; 0.0 0.0 4.381e7] #N
    Btest = zeros(3, 3)
    Dtest = [288.317 47.549 17.774; 47.549 75.033 17.774; 17.774 17.774 49.759] #N*m
    for i = 1:3
        for j = 1:3
            @test isapprox(round(A[i,j], sigdigits=4),Atest[i,j])
            @test round(B[i,j], digits=3) == Btest[i,j]
            @test round(D[i,j], digits=3) == Dtest[i,j]
        end
    end
    # getABD(lam::Laminate,q::Array{Float64,3})
    @test Composites.getABD(lam, q) == (A,B,D)
end

# compliance(A::Array{Float64,2},B::Array{Float64,2},D::Array{Float64,2})
a, b, d = Composites.compliance(A, B, D)
@testset "compliance" begin
    atest = [8.615e-9 -2.798e-9 0.0; -2.798e-9  8.615e-9 0.0;0.0 0.0 2.283e-8] #N
    btest = zeros(3,3)
    dtest = [3.887e-3 -2.332e-3 -5.556e-4; -2.332e-3  1.596e-2 -4.867e-3;
        -5.556e-4 -4.867e-3  2.203e-2] #N*m
    for i = 1:3
        for j = 1:3
            @test isapprox(round(a[i,j],sigdigits=4), atest[i,j], atol = eps(Float64))
            @test round(b[i,j], digits=3) == btest[i,j]
            @test isapprox(round(d[i,j],sigdigits=4), dtest[i,j])
        end
    end
end

@testset "rotstress" begin
    # rotstress(stress::Array{Float64,1},theta::Float64)
    stress = rand(3)
    tsigmatest = [0.5 0.5 1.0;0.5 0.5 -1.0;-0.5 0.5 0.0]*stress
    tsigma = Composites.rotstress(stress, 45.0)
    for i = 1:size(tsigma, 1)
        for j = 1:size(tsigma, 2)
            @test isapprox(tsigma[i,j], tsigmatest[i,j])
        end
    end
end
@testset "rotstrain" begin
    # rotstrain(strain::Array{Float64,1},theta::Float64)
    strain = rand(3)
    tepstest = [0.5 0.5 0.5;0.5 0.5 -0.5;-1.0 1.0 0.0]*strain
    teps = Composites.rotstrain(strain, 45.0)
    for i = 1:size(teps, 1)
        for j = 1:size(teps, 2)
            @test isapprox(teps[i,j], tepstest[i,j])
        end
    end
end

force = [25000.0, 0.0, 0.0]
moment = [1000.0, 0.0, 0.0]
resultantstress = vcat(force, moment)
S = vcat(hcat(A, B), hcat(B', D))
resultantstrain = S\resultantstress

lowerplystrain,upperplystrain = Composites.getplystrain(lam, resultantstrain::Array{Float64,1})
lowerplystress = Composites.getplystress(lowerplystrain, q, matid)
upperplystress = Composites.getplystress(upperplystrain, q, matid)
@testset "getplystrain/getplystress" begin
    # getplystrain(lam::Laminate,resultantstrain::Array{Float64,1})
    # getplystrain(nply::Array{Int64,1},tply::Array{Float64,1},theta::Array{Float64,1},
    #   resultantstrain::Array{Float64,1})
    @test (lowerplystrain, upperplystrain) == Composites.getplystrain(nply, tply,
        theta, resultantstrain)
    # getplystress(plystrain::Array{Float64,2},q::Array{Float64,3},matid::Array{Int64,1})
    # getplystress(plystrain::Array{Float64,2},q::Array{Float64,3},lam::Laminate) = getplystress(plystrain,q,matid)
    @test Composites.getplystress(lowerplystrain, q, lam) == lowerplystress
    lowerplystresstest = [-570.672 -54.747 -76.129 80.735 46.629 46.014 456.66;
                          11.223  -9.655  -4.223 -7.519  2.922  7.337 -8.386;
                          2.556   20.799 -13.647 -0.639 7.808 -14.96  -1.917] #MPa
    for i = 1:3
        for j = 1:length(lowerplystress)
            @test round.(lowerplystress[j][i]/1e6, digits = 3) == lowerplystresstest[i,j]
        end
    end
    # getplystress(plystrain::Array{Float64,2},q::Array{Float64,3},lam::Laminate) = getplystress(plystrain,q,matid)
    @test Composites.getplystress(upperplystrain, q, lam) == upperplystress
    upperplystresstest = [-423.91 -34.595 -35.21  -90.646 87.548  66.167 603.422;
                           8.422  -6.257  -1.841  9.644  5.304  10.736 -11.187;
                           1.917  13.647  -6.496  0.639  14.96 -22.112 -2.556] #MPa
    for i = 1:3
        for j = 1:length(upperplystress)
            @test round(upperplystress[j][i]/1e6, digits=3) == upperplystresstest[i,j]
        end
    end
end

@testset "maxstress" begin
    failmaxstresstest = [
        -0.302793   -0.0247108  -0.0251497  -0.0647469   0.0625344   0.0472619   0.431016;
        1.80387     0.147213    0.149828    0.385726   -0.372545   -0.28156    -2.56775  ;
        0.701835   -0.521387   -0.153435    0.803671    0.441966    0.894661   -0.932254 ;
        -0.158906    0.11805     0.0347399  -0.181963   -0.100068   -0.202565    0.211076 ;
        0.0563767   0.401395   -0.191046    0.0187922   0.439999   -0.650348   -0.0751689;
        -0.0563767  -0.401395    0.191046   -0.0187922  -0.439999    0.650348    0.0751689]
    sfmaxstresstest = [
        -3.30258   -40.4682   -39.7618   -15.4447   15.9912   21.1587     2.3201;
        0.554362    6.79287    6.67431    2.59251  -2.68424  -3.55164   -0.389446;
        1.42484    -1.91796   -6.51743    1.24429   2.26262   1.11774   -1.07267;
        -6.29303     8.47099   28.7853    -5.49561  -9.99323  -4.93669    4.73762;
        17.7378      2.49131   -5.23433   53.2135    2.27273  -1.53764  -13.3034;
        -17.7378     -2.49131    5.23433  -53.2135   -2.27273   1.53764   13.3034]
    failmaxstress,sfmaxstress = Composites.getmatfail(upperplystress, mat,
        lam, "maxstress")
    for i = 1:6
        for j = 1:7
            @test round(failmaxstress[j][i], sigdigits=6) == failmaxstresstest[i,j]
            @test round(sfmaxstress[j][i], sigdigits=6) == sfmaxstresstest[i,j]
        end
    end
end

@testset "tsaiwu" begin
    failtsaiwutest = [2.95173,-0.069493,0.0470993,1.17469,0.260911,1.02618,-1.08208]
    sftsaiwutest = [0.413356,2.93886, 4.85947, 0.873163, 2.02113, 0.983487, 1.90494]
    failtsaiwu, sftsaiwu = Composites.getmatfail(upperplystress, mat, lam, "tsaiwu")
    @test isapprox(failtsaiwu, failtsaiwutest, atol=1e-5)
    @test isapprox(sftsaiwu, sftsaiwutest, atol=1e-5)
end

@testset "hashinrotem" begin
    failhashinrotemtest = [
    -0.407623  -0.0391053  -0.0543778   0.0576675   0.0333063   0.0328674   0.326186;
    2.42839    0.232968    0.323953   -0.343551   -0.198421   -0.195806   -1.94323;
    0.880392  -1.02161    -0.284952   -0.392936    0.112031    0.567472   -0.491518;
    -0.050493   0.407417    0.167466    0.0204784  -0.0557789  -0.212765    0.0282125]
    sfhashinrotemtest = [
    -2.45325   -25.572     -18.3899   17.3408   30.0243   30.4253    3.06574 ;
    0.411795    4.29244     3.08687  -2.91077  -5.0398   -5.10711  -0.514606;
    1.06577     0.989369    1.87333   1.59529   2.98766   1.32748   1.42636 ;
    -4.45025     1.56668     2.44364   6.98798  -4.23414  -2.16795   5.9536  ]
    failhashinrotem, sfhashinrotem = Composites.getmatfail(lowerplystress, mat,
        lam, "hashinrotem")
    for i = 1:4
        for j = 1:7
            @test round(failhashinrotem[j][i], sigdigits=6) == failhashinrotemtest[i,j]
            @test round(sfhashinrotem[j][i], sigdigits=6) == sfhashinrotemtest[i,j]
        end
    end
end


"""
    FOLyapunov()

Computing fractional order Lyapunov exponent of a fractionl order system.

```tex
@article{Danca2018MatlabCF,
  title={Matlab Code for Lyapunov Exponents of Fractional-Order Systems},
  author={Marius-F. Danca and Nikolay V. Kuznetsov},
  journal={Int. J. Bifurc. Chaos},
  year={2018},
  volume={28},
  pages={1850067:1-1850067:14}
}
```
"""
function FOLyapunov(fun, order, t_start, h_norm, t_end, u0, h, out)# TODO: Generate the Lyapunov exponent plot
    ne::Int = length(u0) # System dimension

    Jfdefun(t, u) = jacobian_of_fdefun(fun, t, u)

    tspan = Float64[]
    LE = Float64[]

    # Generate extend system with jacobian
    function extend_fun(t, temp)
        temp=reshape(temp, ne, ne+1)
        result = zeros(ne)
        fun(result, temp[:, 1], nothing, t)
        for i=2:ne+1
            result = [result; Jfdefun(t, temp[:, 1])*temp[:, i]]
        end
        return result
    end
    x = zeros(Float64, ne*(ne+1))
    x0 = zeros(Float64, ne*(ne+1))
    c = zeros(Float64, ne)
    gsc = zeros(Float64, ne)
    zn = zeros(Float64, ne)
    n_it = round(Int, (t_end-t_start)/h_norm)
    x[1:ne] = u0
    q = order*ones(ne*(ne+1))# fractional order of the extend system
    for i=1:ne
        x[(ne+1)*i]=1.0
    end
    t = t_start
    LExp = zeros(ne)
    for it=1:n_it
        (_, Y) = pc(q, extend_fun, t, t+h_norm, x, h) # Solve the extend system
        t = t+h_norm
        Y = Y'

        x = Y[size(Y, 1), :]
        for i=1:ne
            for j=1:ne
                x0[ne*i+j]=x[ne*j+i]
            end
        end
        zn[1] = 0.0
        for j=1:ne
            zn[1] = zn[1]+x0[ne*j+1]^2
        end
        zn[1] = sqrt(zn[1])
        for j=1:ne
            x0[ne*j+1] = x0[ne*j+1]/zn[1]
        end
        for j=2:ne
            for k=1:(j-1)
                gsc[k] = 0.0
                for l=1:ne
                    gsc[k] = gsc[k]+x0[ne*l+j]*x0[ne*l+k]
                end
            end
            for k=1:ne
                for l=1:j-1
                    x0[ne*k+j]=x0[ne*k+j]-gsc[l]*x0[ne*k+l]
                end
            end
            zn[j]=0.0
            for k=1:ne
                zn[j]=zn[j]+x0[ne*k+j]^2
            end
            zn[j]=sqrt(zn[j])
            for k=1:ne
                x0[ne*k+j]=x0[ne*k+j]/zn[j]
            end
        end
        for k=1:ne
            c[k]=c[k]+log(zn[k])
            LExp[k]=c[k]/(t-t_start)
        end

        mod(it, out)==0 ? println(LExp) : nothing

        for i=1:ne
            for j=1:ne
                x[ne*j+i]=x0[ne*i+j]
            end
        end
        LE = [LE; LExp]
        tspan = [tspan; t]
    end
    LE = reshape(LE, ne, :)
    return LE, tspan
end

FOLyapunov(sys::FODESystem, h_norm, h, out) = FOLyapunov(sys.f, sys.α, tspan[1], h_norm, tspan[2], sys.x0, h, out)

mutable struct M
    an
    bn
    a0
    halpha1
    halpha2
    mu
    mu_tol
    r
    index_fft
    an_fft
    bn_fft
end
#TODO: Decouple ABM methods for FODESystems from FractionalSystems.jl to FractionalDiffEq.jl
function pc(alpha, f_fun, t0, T, y0, h)
    METH = M(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    mu_tol = 1.0e-6
    mu = 1
    alpha = alpha[:]

    # Check compatibility size of the problem with number of fractional orders
    alpha_length = length(alpha)
    problem_size = size(y0, 1)

    m_alpha = ceil.(Int, alpha)
    m_alpha_factorial = zeros(alpha_length, maximum(m_alpha))
    for i = 1 : alpha_length
        for j = 0 : m_alpha[i]-1
            m_alpha_factorial[i, j+1] = factorial(j)
        end
    end

    f_temp = f_vectorfield(t0, y0[:, 1], f_fun)

    r = 16
    N = ceil(Int64, (T-t0)/h)
    Nr = ceil(Int64, (N+1)/r)*r
    Qr::Int = ceil(Int64, log2(Nr/r)) - 1
    NNr = Int64(2^(Qr+1)*r)

    # Preallocation of some variables
    y = zeros(problem_size, N+1)
    fy = zeros(problem_size, N+1)
    zn_pred = zeros(problem_size, NNr+1)
    if mu > 0
        zn_corr = zeros(problem_size, NNr+1)
    else
        zn_corr = 0
    end

    # Evaluation of coefficients of the PECE method
    nvett = 0 : NNr+1
    bn = zeros(alpha_length, NNr+1) ; an = copy(bn); a0 = copy(bn)
    for i_alpha = 1:alpha_length
        #find_alpha = find(alpha[i_alpha]==alpha[1:i_alpha-1])
        find_alpha=[]
        if alpha[i_alpha] == alpha[1:i_alpha-1]
            push!(find_alpha, i_alpha)
        end

        if isempty(find_alpha) == false
            bn[i_alpha, :] = bn[find_alpha[1], :]
            an[i_alpha, :] = an[find_alpha[1], :]
            a0[i_alpha, :] = a0[find_alpha[1], :]
        else
            nalpha = nvett.^alpha[i_alpha]
            nalpha1 = nalpha.*nvett
            bn[i_alpha, :] = nalpha[2:end] - nalpha[1:end-1]
            an[i_alpha, :] = [1; (nalpha1[1:end-2] - 2*nalpha1[2:end-1] + nalpha1[3:end]) ]
            a0[i_alpha, :] = [0; nalpha1[1:end-2]-nalpha[2:end-1].*(nvett[2:end-1].-alpha[i_alpha].-1)]
        end
    end
    METH.bn = bn ; METH.an = an ; METH.a0 = a0
    METH.halpha1 = h.^alpha./gamma.(alpha.+1)
    METH.halpha2 = h.^alpha./gamma.(alpha.+2)
    METH.mu = mu ; METH.mu_tol = mu_tol

    # Evaluation of FFT of coefficients of the PECE method
    METH.r = r
    if Qr >= 0
        index_fft = zeros(Int, 2, Qr+1) ;
        for l = 1 : Qr+1
            if l == 1
                index_fft[1, l] = 1 ; index_fft[2, l] = r*2
            else
                index_fft[1, l] = index_fft[2, l-1]+1 ; index_fft[2, l] = index_fft[2, l-1]+2^l*r
            end
        end

        bn_fft = zeros(Complex, alpha_length, index_fft[2, Qr+1]) ; an_fft = copy(bn_fft)
        for l = 1 : Qr+1
            coef_end = 2^l*r
            for i_alpha = 1 : alpha_length
                #find_alpha = find(alpha(i_alpha)==alpha(1:i_alpha-1)) ;
                find_alpha = []
                if alpha[i_alpha] == alpha[1:i_alpha-1]
                    push!(find_alpha, i_alpha)
                end
                if isempty(find_alpha) == false
                    bn_fft[i_alpha, index_fft[1, l]:index_fft[2, l]] = bn_fft(find_alpha(1),index_fft(1,l):index_fft(2,l)) ;
                    an_fft[i_alpha, index_fft[1, l]:index_fft[2, l]] = an_fft(find_alpha(1),index_fft(1,l):index_fft(2,l)) ;
                else
                    bn_fft[i_alpha, index_fft[1, l]:index_fft[2, l]] = ourfft(METH.bn[i_alpha, 1:coef_end], coef_end)
                    an_fft[i_alpha, index_fft[1, l]:index_fft[2, l]] = ourfft(METH.an[i_alpha, 1:coef_end], coef_end)
                end
            end
        end
        METH.bn_fft = bn_fft ; METH.an_fft = an_fft ; METH.index_fft = index_fft ;
    end

    # Initializing solution and proces of computation
    t = t0 .+ collect(0:N)*h
    y[:, 1] = y0[:, 1]
    fy[:, 1] = f_temp
    (y, fy) = Triangolo(1, r-1, t, y, fy, zn_pred, zn_corr, N, METH, problem_size, alpha_length, m_alpha, m_alpha_factorial, y0, t0, f_fun) ;

    # Main process of computation by means of the FFT algorithm
    ff = zeros(1, 2^(Qr+2)) ; ff[1:2] = [0; 2] ; card_ff = 2
    nx0 = 0 ; ny0 = 0 ;
    for qr = 0 : Qr
        L = 2^qr
        (y, fy) = DisegnaBlocchi(L, ff, r, Nr, nx0+L*r, ny0, t, y, fy, zn_pred, zn_corr, N, METH, problem_size, alpha_length, m_alpha, m_alpha_factorial, y0, t0, f_fun) ;
        ff[1:2*card_ff] = [ff[1:card_ff]; ff[1:card_ff]]
        card_ff = 2*card_ff
        ff[card_ff] = 4*L
    end

    # Evaluation solution in T when T is not in the mesh
    if T < t[N+1]
        c = (T - t[N])/h
        t[N+1] = T
        y[:, N+1] = (1-c)*y[:, N] + c*y[:, N+1]
    end

    t = t[1:N+1]; y = y[:, 1:N+1]
    return t, y

end

function DisegnaBlocchi(L, ff, r, Nr, nx0, ny0, t, y, fy, zn_pred, zn_corr, N , METH, problem_size, alpha_length, m_alpha, m_alpha_factorial, y0, t0, f_fun)

    nxi = nx0 ; nxf = nx0 + L*r - 1
    nyi = ny0 ; nyf = ny0 + L*r - 1
    is = 1
    s_nxi = zeros(N)
    s_nxf = zeros(N)
    s_nyi = zeros(N)
    s_nyf = zeros(N)
    s_nxi[is] = nxi ; s_nxf[is] = nxf ; s_nyi[is] = nyi ; s_nyf[is] = nyf ;

    i_triangolo = 0 ; stop = false
    while stop == false

        stop = (nxi+r-1 == nx0+L*r-1) || (nxi+r-1>=Nr-1) # Ci si ferma quando il triangolo attuale finisce alla fine del quadrato

        (zn_pred, zn_corr) = Quadrato(nxi, nxf, nyi, nyf, fy, zn_pred, zn_corr, N, METH, problem_size, alpha_length) ;

        (y, fy) = Triangolo(nxi, nxi+r-1, t, y, fy, zn_pred, zn_corr, N, METH, problem_size, alpha_length, m_alpha, m_alpha_factorial, y0, t0, f_fun) ;
        i_triangolo = i_triangolo + 1 ;

        if stop == false
            if nxi+r-1 == nxf   # Il triangolo finisce dove finisce il quadrato -> si scende di livello
                i_Delta = ff[i_triangolo]
                Delta = i_Delta*r ;
                nxi = s_nxf[is]+1 ; nxf = s_nxf[is]  + Delta ;
                nyi = s_nxf[is] - Delta +1; nyf = s_nxf[is]  ;
                s_nxi[is] = nxi ; s_nxf[is] = nxf ; s_nyi[is] = nyi ; s_nyf[is] = nyf ;
            else # Il triangolo finisce prima del quadrato -> si fa un quadrato accanto
                nxi = nxi + r ; nxf = nxi + r - 1 ; nyi = nyf + 1 ; nyf = nyf + r  ;
                is = is + 1 ;
                s_nxi[is] = nxi ; s_nxf[is] = nxf ; s_nyi[is] = nyi ; s_nyf[is] = nyf ;
            end
        end

    end
    return y, fy
end

function Quadrato(nxi, nxf, nyi, nyf, fy, zn_pred, zn_corr, N, METH, problem_size, alpha_length)

    #coef_beg = nxi-nyf ;
    coef_end = Int64(nxf-nyi+1)
    i_fft = log2(coef_end/METH.r)
    funz_beg = Int64(nyi+1) ; funz_end = Int64(nyf+1)
    Nnxf = min(N, nxf)

    # Evaluation convolution segment for the predictor
    vett_funz = fy[:, funz_beg:funz_end]
    vett_funz_fft = rowfft(vett_funz, coef_end)
    zzn_pred = zeros(problem_size,coef_end) ;
    for i = 1 : problem_size
        i_alpha = min(alpha_length,i) ;
        Z = METH.bn_fft[i_alpha, METH.index_fft[1, Int64(i_fft)]:METH.index_fft[2, Int64(i_fft)]].*vett_funz_fft[i, :]
        zzn_pred[i, :] = real.(ourifft(Z, coef_end))
    end
    zzn_pred = zzn_pred[:, Int64(nxf-nyf+1-1):end-1]
    zn_pred[:, Int64(nxi+1):Int64(Nnxf+1)] = zn_pred[:, Int64(nxi+1):Int64(Nnxf+1)] + zzn_pred[:, 1:Int64(Nnxf-nxi+1)]

    # Evaluation convolution segment for the corrector
    if METH.mu > 0
        if nyi == 0 # Evaluation of the lowest square
            vett_funz = [zeros(problem_size, 1) fy[:, funz_beg+1:funz_end]]
            vett_funz_fft = rowfft(vett_funz, coef_end)
        end
        zzn_corr = zeros(problem_size, coef_end) ;
        for i = 1 : problem_size
            i_alpha = min(alpha_length,i) ;
            Z = METH.an_fft[i_alpha,METH.index_fft[1, Int64(i_fft)]:METH.index_fft[2, Int64(i_fft)]].*vett_funz_fft[i, :]
            zzn_corr[i, :] = real.(ourifft(Z, coef_end))
        end
        zzn_corr = zzn_corr[:, Int64(nxf-nyf+1):end]
        zn_corr[:, Int64(nxi+1):Int64(Nnxf+1)] = zn_corr[:, Int64(nxi+1):Int64(Nnxf+1)] + zzn_corr[:, 1:Int64(Nnxf-nxi+1)]
    else
        zn_corr = 0
    end
    return zn_pred, zn_corr
end

function Triangolo(nxi, nxf, t, y, fy, zn_pred, zn_corr, N, METH, problem_size, alpha_length, m_alpha, m_alpha_factorial, y0, t0, f_fun)

    for n = Int64(nxi) : Int64(min(N, nxf))

        # Evaluation of the predictor
        Phi = zeros(problem_size, 1)
        if nxi == 1 # Case of the first triangle
            j_beg = 0
        else # Case of any triangle but not the first
            j_beg = nxi
        end
        for j = Int64(j_beg) : Int64(n-1)
            Phi = Phi + METH.bn[1:alpha_length,n-j].*fy[:, j+1]
        end
        St = StartingTerm(t[n+1], y0, m_alpha, t0, m_alpha_factorial)
        y_pred = St + METH.halpha1.*(zn_pred[:, n+1] + Phi)
        f_pred = f_vectorfield(t[n+1], y_pred, f_fun)

        # Evaluation of the corrector
        if METH.mu == 0
            y[:, n+1] = y_pred
            fy[:, n+1] = f_pred
        else
            j_beg = Int64(nxi)
            Phi = zeros(problem_size, 1)
            for j = j_beg : n-1
                Phi = Phi + METH.an[1:alpha_length, n-j+1].*fy[:, j+1]
            end
            Phi_n = St + METH.halpha2.*(METH.a0[1:alpha_length, n+1].*fy[:, 1] + zn_corr[:, n+1] + Phi)
            yn0 = y_pred ; fn0 = f_pred ;
            stop = 0 ; mu_it = 0 ;
            while stop == false
                global yn1 = Phi_n + METH.halpha2.*fn0 ;
                mu_it = mu_it + 1 ;
                if METH.mu == Inf
                    stop = norm(yn1-yn0,inf) < METH.mu_tol ;
                    if mu_it > 100 && ~stop
                        stop = 1 ;
                    end
                else
                    stop = mu_it == METH.mu ;
                end
                global fn1 = f_vectorfield(t[n+1], yn1, f_fun)
                yn0 = yn1 ; fn0 = fn1 ;
            end
            y[:, n+1] = yn1
            fy[:, n+1] = fn1
        end
    end
    return y, fy
end

function f_vectorfield(t, y, f_fun!)
    f = f_fun!(t, y)
    return f
end

function  StartingTerm(t, y0, m_alpha, t0, m_alpha_factorial)
    ys = zeros(size(y0, 1),1)
    for k = 1 : maximum(m_alpha)
        if length(m_alpha) == 1
            ys = ys + (t-t0)^(k-1)/m_alpha_factorial[k]*y0[:, k]
        else
            i_alpha = findall(x -> x>=k, m_alpha)
            ys[i_alpha, 1] = ys[i_alpha, 1] + (t-t0)^(k-1)*y0[i_alpha, k]./m_alpha_factorial[i_alpha, k]
        end
    end
    return ys
end

function ourfft(x::Vector, n)
    s=length(x)
    x=x[:]
    if s > n
        return fft(x[1:n])
    elseif s < n
        return fft([x; zeros(n-s)])
    else
        return fft(x)
    end
end

function ourifft(x, n)
    s=length(x)
    x=x[:]
    if s > n
        return ifft(x[1:n])
    elseif s < n
        return ifft([x; zeros(n-s)])
    else
        return ifft(x)
    end
end

function rowfft(x::AbstractMatrix, n)
    result = zeros(Complex, size(x)[1], n)
    for i=1:size(x)[1]
        result[i, :] = ourfft(x[i, :], n)
    end
    return result
end

function jacobian_of_fdefun(f, t, y)
    ForwardDiff.jacobian(y) do y
    du = similar(y)
    f(du, y, nothing, t)
    du
    end
end