########################
#### Energy balance ####
########################

#' Biochemical Energy
#'
#' Radiant energy absorbed in photosynthesis or heat release by respiration calculated
#'              from net ecosystem exchange of CO2 (NEE).
#'
#' - NEE     Net ecosystem exchange (umol CO2 m-2 s-1)
#' - alpha   Energy taken up/released by photosynthesis/respiration per mol CO2 fixed/respired (J umol-1)
#'
#' # Details
 The following sign convention is employed: NEE is negative when carbon is taken up by the ecosystem.
#'          Positive values of the resulting biochemical energy mean that energy (heat) is taken up by the ecosystem,
#'          negative ones that heat is released.
#'          The value of alpha is taken from Nobel 1974 (see Meyers & Hollinger 2004), but other values
#'          have been used (e.g. Blanken et al., 1997)
#'
#' # Value
 - Sp -: biochemical energy (W m-2)
#'
#' #References
#' Meyers, T_P., Hollinger, S_E. 2004: An assessment of storage terms in the surface energy
#'             balance of maize and soybean. Agricultural and Forest Meteorology 125, 105-115.
#'
#'             Nobel, P_S., 1974: Introduction to Biophysical Plant Physiology.
#'             Freeman, New York.
#'
#'             Blanken, P_D. et al., 1997: Energy balance and canopy conductance of a boreal aspen
#'             forest: Partitioning overstory and understory components.
#'             Journal of Geophysical Research 102, 28915-28927.
#'
#' ```@example; output = false
#' ```
#' # Calculate biochemical energy taken up by the ecosystem with
#' # a measured NEE of -30umol CO2 m-2 s-1
#' biochemical_energy(NEE=-30)
#'
#' @export
function biochemical_energy(NEE,alpha=0.422)
  Sp = alpha*-NEE
  return(Sp)
end

#' Energy-Use Efficiency (EUE)
#'
#' Fraction of net radiation fixed by primary productivity.
#'
#' - GPP     Gross primary productivity exchange (umol CO2 m-2 s-1)
#' - alpha   Energy taken up/released by photosynthesis/respiration (J umol-1)
#' - Rn      Net radiation (W m-2)
#'
#' # Details
 Energy use efficiency is calculated as:
#'
#'            ``EUE = sum(GPP)/sum(Rn)``
#'
#'          where the sums are calculated for complete cases of GPP and Rn over
#'          the entire time period.
#'
#' # Value
 - EUE -: Energy use efficiency (-)
#'
#' #See also
#' [`light_use_efficiency`](@ref)
#'
#' ```@example; output = false
#' ```
#' energy_use_efficiency(GPP=20,Rn=500)
#'
#' @export
function energy_use_efficiency(GPP,alpha=0.422,Rn)

  Sp = biochemical_energy(-GPP,alpha)

  comp  = complete_cases(Sp,Rn)

  Sp_sum = sum(Sp[comp],na_rm=T)
  Rn_sum = sum(Rn[comp],na_rm=T)

  EUE = Sp_sum/Rn_sum

  return(c("EUE"=EUE))
end

#' Energy Balance Closure
#'
#' Calculates the degree of the energy balance non-closure for the entire time span
#'              based on the ratio of two sums (energy balance ratio), and ordinary least squares (OLS).
#'
#' - data  DataFrame or matrix containing all required variables.
#' - Rn    Net radiation (W m-2)
#' - G     Ground heat flux (W m-2); optional
#' - S     Sum of all storage fluxes (W m-2); optional
#' - LE    Latent heat flux (W m-2)
#' - H     Sensible heat flux (W m-2)
#' - instantaneous    should the energy balance be calculated at the time step
#'                         of the observations (`true`), or over the entire time period
#'                         provided as input (`false`)
#' - missing_G_as_NA  if `true`, missing G are treated as `missing`s ,otherwise set to 0.
#' - missing_S_as_NA  if `true`, missing S are treated as `missing`s, otherwise set to 0.
#'
#'
#' # Details
 The energy balance ratio (EBR) is calculated as:
#'
#'            ``EBR = sum(LE + H)/sum(Rn - G - S)``
#'
#'          the sum is taken for all time steps with complete observations (i.e. where
#'          all energy balance terms are available).
#'
#' # Value
 a named vector containing:
#'         - n: number of complete (all energy balance terms available) observations
#'         - intercept: intercept of the OLS regression
#'         - slope: slope of the OLS regression
#'         - r_squared: r^2 of the OLS regression
#'         - EBR: energy balance ratio
#'
#'         if `instantaneous = true`, only `EBR` is returned.
#'
#' #References
#' Wilson K., et al. 2002: Energy balance closure at FLUXNET sites.
#'             Agricultural and Forest Meteorology 113, 223-243.
#'
#' ```@example; output = false
#' ```
#' ## characterize energy balance closure for DE-Tha in June 2014
#' energy_closure(DE_Tha_Jun_2014,instantaneous=false)
#'
#' ## look at half-hourly closure
#' EBR_inst = energy_closure(DE_Tha_Jun_2014,instantaneous=true)
#' summary(EBR_inst)
#'
#' @importFrom stats complete_cases lm
"""
"""
function energy_closure(data,Rn="Rn",G=nothing,S=nothing,LE="LE",H="H",instantaneous=false,
                           missing_G_as_NA=false,missing_S_as_NA=false)

  check_input(data,list(Rn,LE,H,G,S))

  if(!isnothing(G))
    if (!missing_G_as_NA){G[ismissing(G)] = 0}
else
    cat("Ground heat flux G is not provided and set to 0.",fill=true)
    G = rep(0,nrow(data))
end

  if(!isnothing(S))
    if(!missing_S_as_NA){S[ismissing(S)] = 0 }
else
    cat("Energy storage fluxes S are not provided and set to 0.",fill=true)
    S = rep(0,nrow(data))
end

  if (!instantaneous)
    comp = complete_cases(Rn,G,S,LE,H)
    n    = sum(comp)

    EBR = sum(LE[comp] + H[comp]) / sum(Rn[comp] - G[comp] - S[comp])

    emod = lm(c(LE + H) ~ c(Rn - G - S))
    intercept = summary(emod)$coef[1,1]
    slope     = summary(emod)$coef[2,1]
    r_squared = summary(emod)$r_squared

    return(c("n"=n,"intercept"=round(intercept,3),"slope"=round(slope,3),"r^2"=round(r_squared,3),"EBR"=round(EBR,3)))

else

    EBR = (LE + H) /(Rn - G - S)

    return(EBR)
end
end

#' Isothermal Net Radiation
#'
#' Calculates the isothermal net radiation, i.e. the net radiation
#'              that the surface would receive if it had the same temperature than
#'              the air.
#'
#' - data       DataFrame or matrix containing all required variables
#' - Rn         Net radiation (W m-2)
#' - Tair       Air temperature (degC)
#' - Tsurf      Surface temperature (degC)
#' - emissivity Emissivity of the surface (-)
#' - constants  sigma - Stefan-Boltzmann constant (W m-2 K-4)
#'                   Kelvin - conversion degree Celsius to Kelvin
#'
#' # Details
 The isothermal net radiation (Rni) is given by:
#'
#'            ``Rni = Rn + \epsilon * \sigma * (Tsurf^4 - Tair^4)``
#'
#'          where ``\epsilon`` is the emissivity of the surface. Tsurf and Tair
#'          are in Kelvin.
#'
#' # Value
 - Rni -: isothermal net radiation (W m-2)
#'
#' #References
#' Jones, H. 2014: Plants and Microclimate. 3rd edition, Cambridge
#'             University Press.
#'
#' ```@example; output = false
#' ```
#' # calculate isothermal net radiation of a surface that is 2degC warmer than the air.
#' isothermal_Rn(Rn=400,Tair=25,Tsurf=27,emissivity=0.98)
#'
"""
"""
function isothermal_Rn(data,Rn="Rn",Tair="Tair",Tsurf="Tsurf",emissivity,
                          constants=BigleafConstants())

  check_input(data,list(Rn,Tair,Tsurf))

  Tair  = Tair + constants.Kelvin
  Tsurf = Tsurf + constants.Kelvin

  Rni = Rn + emissivity * constants.sigma * (Tsurf^4 - Tair^4)

  return(Rni)

end


module meanfield

export ProductState, densityoperator

import ..integrate

using QuantumOpticsBase, LinearAlgebra
using ..interaction, ..CollectiveSpins

# Define Spin 1/2 operators
const spinbasis = SpinBasis(1//2)
const id = dense(identityoperator(spinbasis))
const sigmax_ = dense(sigmax(spinbasis))
const sigmay_ = dense(sigmay(spinbasis))
const sigmaz_ = dense(sigmaz(spinbasis))
const sigmap_ = dense(sigmap(spinbasis))
const sigmam_ = dense(sigmam(spinbasis))

"""
Class describing a Meanfield state (Product state).

The data layout is [sx1 sx2 ... sy1 sy2 ... sz1 sz2 ...]

# Arguments
* `N`: Number of spins.
* `data`: Vector of length 3*N.
"""
mutable struct ProductState{T1<:Int,T2<:Real}
    N::T1
    data::Vector{T2}
end

"""
    meanfield.ProductState(N)

Meanfield state with all Pauli expectation values equal to zero.
"""
ProductState(N::T) where T<:Int = ProductState(N, zeros(float(T), 3*N))

"""
    meanfield.ProductState(data)

Meanfield state created from real valued vector of length 3*spinnumber.
"""
ProductState(data::Vector{<:Real}) = ProductState(dim(data), data)

"""
    meafield.ProductState(rho)

Meanfield state from density operator.
"""
function ProductState(rho::DenseOpType)
    N = quantum.dim(rho)
    basis = quantum.basis(N)
    state = ProductState(N)
    sx, sy, sz = splitstate(s)
    f(ind, op) = real(expect(embed(basis, ind, op), rho))
    for k=1:N
        sx[k] = f(k, sigmax_)
        sy[k] = f(k, sigmay_)
        sz[k] = f(k, sigmaz_)
    end
    return state
end

"""
    meanfield.blochstate(phi, theta[, N=1])

Product state of `N` single spin Bloch states.

All spins have the same azimuthal angle `phi` and polar angle `theta`.
"""
function blochstate(phi::Vector{T1}, theta::Vector{T2}) where {T1<:Real, T2<:Real}
    N = length(phi)
    @assert length(theta)==N
    state = ProductState(N)
    sx, sy, sz = splitstate(state)
    for k=1:N
        sx[k] = cos(phi[k])*sin(theta[k])
        sy[k] = sin(phi[k])*sin(theta[k])
        sz[k] = cos(theta[k])
    end
    return state
end

function blochstate(phi::Real, theta::Real, N::Int=1)
    state = ProductState(N)
    sx, sy, sz = splitstate(state)
    for k=1:N
        sx[k] = cos(phi)*sin(theta)
        sy[k] = sin(phi)*sin(theta)
        sz[k] = cos(theta)
    end
    return state
end

"""
    meanfield.dim(state)

Number of spins described by this state.
"""
function dim(state::Vector{T}) where T<:Real
    N, rem = divrem(length(state), 3)
    @assert rem==0
    return N
end

"""
    meanfield.splitstate(N, data)
    meanfield.splitstate(state)

Split state into sx, sy and sz parts.
"""
splitstate(N::Int, data::Vector{<:Real}) = view(data, 1:1*N), view(data, 1*N+1:2*N), view(data, 2*N+1:3*N)
splitstate(state::ProductState) = splitstate(state.N, state.data)

"""
    meanfield.densityoperator(sx, sy, sz)
    meanfield.densityoperator(state)

Create density operator from independent sigma expectation values.
"""
function densityoperator(sx::Real, sy::Real, sz::Real)
    return 0.5*(id + sx*sigmax_ + sy*sigmay_ + sz*sigmaz_)
end
function densityoperator(state::ProductState)
    sx, sy, sz = splitstate(state)
    rho = densityoperator(sx[1], sy[1], sz[1])
    for i=2:state.N
        rho = tensor(rho, densityoperator(sx[i], sy[i], sz[i]))
    end
    return rho
end

"""
    meanfield.sx(state)

Sigma x expectation values of state.
"""
sx(x::ProductState) = view(x.data, 1:x.N)

"""
    meanfield.sy(state)

Sigma y expectation values of state.
"""
sy(x::ProductState) = view(x.data, x.N+1:2*x.N)

"""
    meanfield.sz(state)

Sigma z expectation values of state.
"""
sz(x::ProductState) = view(x.data, 2*x.N+1:3*x.N)

"""
    meanfield.timeevolution(T, S::SpinCollection, state0[; fout])

Meanfield time evolution.

# Arguments
* `T`: Points of time for which output will be generated.
* `S`: [`SpinCollection`](@ref) describing the system.
* `state0`: Initial ProductState.
* `fout` (optional): Function with signature `fout(t, state)` that is called whenever output
    should be generated.
"""
function timeevolution(T, S::SpinCollection, state0::ProductState; fout=nothing, kwargs...)
    N = length(S.spins)
    @assert N==state0.N
    Ω = interaction.OmegaMatrix(S)
    Γ = interaction.GammaMatrix(S)

    function f(dy, y, p, t)
        sx, sy, sz = splitstate(N, y)
        dsx, dsy, dsz = splitstate(N, dy)
        @inbounds for k=1:N
            dsx[k] = -S.spins[k].delta*sy[k] - 0.5*S.gammas[k]*sx[k]
            dsy[k] = S.spins[k].delta*sx[k] - 0.5*S.gammas[k]*sy[k]
            dsz[k] = -S.gammas[k]*(1+sz[k])
            for j=1:N
                if j==k
                    continue
                end
                dsx[k] += Ω[k,j]*sy[j]*sz[k] + 0.5*Γ[k,j]*sx[j]*sz[k]
                dsy[k] += -Ω[k,j]*sx[j]*sz[k] + 0.5*Γ[k,j]*sy[j]*sz[k]
                dsz[k] += Ω[k,j]*(sx[j]*sy[k] - sy[j]*sx[k]) - 0.5*Γ[k,j]*(sx[j]*sx[k] + sy[j]*sy[k])
            end
        end
    end

    if isa(fout, Nothing)
        fout_(t, state::ProductState) = deepcopy(state)
    else
        fout_ = fout
    end

    return integrate(T, f, state0, fout_; kwargs...)
end

"""
    meanfield.timeevolution_symmetric(T, state0, Ωeff, Γeff[; γ, δ0, fout])

Symmetric meanfield time evolution.

# Arguments
* `T`: Points of time for which output will be generated.
* `state0`: Initial ProductState.
* `Ωeff`: Effective dipole-dipole interaction.
* `Γeff`: Effective collective decay rate.
* `γ=1`: Single spin decay rate.
* `δ0=0`: Phase shift for rotated symmetric meanfield time evolution.
* `fout` (optional): Function with signature `fout(t, state)` that is called whenever output
    should be generated.
"""
function timeevolution_symmetric(T, state0::ProductState, Ωeff::Real, Γeff::Real; γ::Real=1.0, δ0::Real=0., fout=nothing, kwargs...)
    N = 1
    @assert state0.N==N
    function f(dy, y, p, t)
        sx, sy, sz = splitstate(N, y)
        dsx, dsy, dsz = splitstate(N, dy)
        dsx[1] = -δ0*sy[1] + Ωeff*sy[1]*sz[1] - 0.5*γ*sx[1] + 0.5*Γeff*sx[1]*sz[1]
        dsy[1] = δ0*sx[1] - Ωeff*sx[1]*sz[1] - 0.5*γ*sy[1] + 0.5*Γeff*sy[1]*sz[1]
        dsz[1] = -γ*(1+sz[1]) - 0.5*Γeff*(sx[1]^2+sy[1]^2)
    end

    if isa(fout, Nothing)
        fout_ = (t, state) -> deepcopy(state)
    else
        fout_ = fout
    end

    return integrate(T, f, state0, fout_; kwargs...)

end

"""
    meanfield.rotate(axis, angles, state)

Rotations on the Bloch sphere for the given [`ProductState`](@ref).

# Arguments
* `axis`: Rotation axis.
* `angles`: Rotation angle(s).
* `state`: [`ProductState`](@ref) that should be rotated.
"""
function rotate(axis::Vector{T1}, angles::Vector{T2}, state::ProductState) where {T1<:Real, T2<:Real}
    @assert length(axis)==3
    @assert length(angles)==state.N
    w = axis/norm(axis)
    sx, sy, sz = splitstate(state)
    state_rot = ProductState(state.N)
    sx_rot, sy_rot, sz_rot = splitstate(state_rot)
    v = zeros(T1, 3)
    for i=1:state.N
        v[1], v[2], v[3] = sx[i], sy[i], sz[i]
        θ = angles[i]
        sx_rot[i], sy_rot[i], sz_rot[i] = cos(θ)*v + sin(θ)*(w × v) + (1-cos(θ))*(w ⋅ v)*w
    end
    return state_rot
end

rotate(axis::Vector{T}, angle::Real, state::ProductState) where {T<:Real} = rotate(axis, ones(T, state.N)*angle, state)

end # module


using ProbNumDiffEq
using Test

du0 = [0.0]
u0 = [2.0]
tspan = (0.0, 6.3)
p = [1e1]

function vanderpol!(ddu, du, u, p, t)
    μ = p[1]
    @. ddu = μ * ((1 - u^2) * du - u)
end
prob_iip = SecondOrderODEProblem(vanderpol!, du0, u0, tspan, p)

function vanderpol(du, u, p, t)
    μ = p[1]
    ddu = μ .* ((1 .- u .^ 2) .* du .- u)
    return ddu
end
prob_oop = SecondOrderODEProblem(vanderpol, du0, u0, tspan, p)

appxsol = solve(prob_iip, Tsit5(), abstol=1e-7, reltol=1e-7)

@testset "IIP" begin
    for alg in (EK0(), EK1())
        @testset "$alg" begin
            @test solve(prob_iip, alg) isa ProbNumDiffEq.ProbODESolution
            @test solve(prob_iip, alg).u[end] ≈ appxsol.u[end] rtol = 1e-3
        end
    end
end

@testset "OOP" begin
    for alg in (EK0(), EK1())
        @testset "$alg" begin
            @test solve(prob_oop, alg) isa ProbNumDiffEq.ProbODESolution
            @test solve(prob_oop, alg).u[end] ≈ appxsol.u[end] rtol = 1e-3
        end
    end
end

@testset "RungeKuttaInit for SecondOrderODEProblems" begin
    @test_broken solve(prob_iip, EK1(initialization=RungeKuttaInit())) isa
                 ProbNumDiffEq.ProbODESolution
end

@testset "Fixed Diffusion" begin
    @test solve(prob_iip, EK0(diffusionmodel=:fixed)) isa ProbNumDiffEq.ProbODESolution
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

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

# ╔═╡ 867b290c-6bd9-11eb-1afb-47757ee13936
begin
	using PlutoUI
	using NativeSVG
end

# ╔═╡ 66ddc134-6bea-11eb-3bde-0fea30c863af
let
	t = Turtle()
	include("../src/chap05.jl")
	penup(t)
	forward(t, -200)
	turn(t, 90)
	forward(t, 60)
	turn(t,-90)
	pendown(t)
	koch(t, 400)
	Drawing(t, 720, 130)
end

# ╔═╡ 4cc6b8ac-6bd9-11eb-1d23-f5d9caf02cd3
md"""# Conditionals and Recursion

The main topic of this chapter is the `if` statement, which executes different code depending on the state of the program. But first I want to introduce two new operators: floor division and modulus."""

# ╔═╡ 81ea78c0-6bd9-11eb-1d3b-5f67fdf090f4
md"""## Floor Division and Modulus

The *floor division* operator, `÷` (**`\div TAB`**), divides two numbers and rounds down to an integer. For example, suppose the running time of a movie is 105 minutes. You might want to know how long that is in hours. Conventional division returns a floating-point number:

```julia
julia> minutes = 105
105
julia> minutes / 60
1.75
```

But we don’t normally write hours with decimal points. Floor division returns the integer number of hours, rounding down:

```julia
julia> hours = minutes ÷ 60
1
```

To get the remainder, you could subtract one hour in minutes:

```julia
julia> remainder = minutes - hours * 60
45
```

An alternative is to use the *modulus operator*, `%`, which divides two numbers and returns the remainder:

```julia
julia> remainder = minutes % 60
45
```

!!! tip
    The modulus operator is more useful than it seems. For example, you can check whether one number is divisible by another—if `x % y` is `0`, then `x` is divisible by `y`.

    Also, you can extract the rightmost digit or digits from a number. For example, `x % 10` yields the rightmost digit of an integer `x` (in base 10). Similarly, `x % 100` yields the last two digits.
"""

# ╔═╡ fa7e737c-6bd9-11eb-3cd0-d5b1d2ae7af1
md"""## Boolean Expressions

A *Boolean expression* is an expression that is either true or false. The following examples use the operator `==`, which compares two operands and produces `true` if they are equal and `false` otherwise:

```julia
julia> 5 == 5
true
julia> 5 == 6
false
```

`true` and `false` are special values that belong to the type `Bool`; they are not strings:

```julia
julia> typeof(true)
Bool
julia> typeof(false)
Bool
```

The `==` operator is one of the relational operators (operators that compare their operands). The others are:

```julia
x!=y # x is not equal to y
x≠y  # (\ne TAB)
x>y  # x is greater than y
x<y  # x is less than y
x>=y # x is greater than or equal to y x≥y # (\ge TAB)
x<=y # x is less than or equal to y
x≤y  # (\le TAB)
```

!!! danger
    Although these operations are probably familiar to you, the Julia symbols are different from the mathematical symbols. A common error is to use a single equals sign (`=`) instead of a double equals sign (`==`). Remember that `=` is an assignment operator and `==` is a relational operator. There is no such thing as `=<` or `=>`.
"""

# ╔═╡ 01a13616-6bdb-11eb-1b8b-8bdda36e623f
md"""## Logical Operators

There are three *logical operators*: `&&` (and), `||` (or), and `!` (not). The semantics (meaning) of these operators is similar to their meaning in English. For example, `x > 0 && x < 10` is true only if `x` is greater than `0` *and* less than `10`.

`n % 2 == 0 || n % 3 == 0` is true if *either or both* of the conditions is true; that is, if the number is divisible by 2 or 3.

Both `&&` and `||` associate to the right (i.e., grouped from the right), but `&&` has higher precedence than `||` does.

Finally, the `!` operator negates a Boolean expression, so `!(x > y)` is true if `x > y` is false; that is, if `x` is less than or equal to `y`.
"""

# ╔═╡ 7478967a-6bdb-11eb-03f7-81f894751f6d
md"""## Conditional Execution

In order to write useful programs, we almost always need the ability to check conditions and change the behavior of the program accordingly. *Conditional statements* give us this ability. The simplest form is the `if` statement:

```julia
if x > 0
	println("x is positive")
end
```

The Boolean expression after `if` is called the *condition*. If it is true, the indented statement runs. If not, nothing happens.

`if` statements have the same structure as function definitions: a header followed by a body terminated with the keyword `end`. Statements like this are called *compound statements*.

There is no limit on the number of statements that can appear in the body. Occasionally, it is useful to have a body with no statements (usually as a placeholder for code you haven’t written yet):

```julia
if x < 0
	# TODO: need to handle negative values!
end
```
"""

# ╔═╡ e75638d2-6bdb-11eb-0a7e-93446ea41c2d
md"""## Alternative Execution

A second form of the `if` statement is “alternative execution,” in which there are two possibilities and the condition determines which one runs. The syntax looks like this:

```julia
if x % 2 == 0
	println("x is even")
else
	println("x is odd")
end
```

If the remainder when `x `is divided by 2 is 0, then we know that `x` is even, and the program displays an appropriate message. If the condition is false, the second set of statements runs. Since the condition must be true or false, exactly one of the alternatives will run. The alternatives are called *branches*, because they are branches in the flow of execution.
"""

# ╔═╡ 482d8390-6bdc-11eb-3e3c-33b9f4f244b9
md"""## Chained Conditionals

Sometimes there are more than two possibilities and we need more than two branches. One way to express a computation like that is using a *chained conditional*:

```julia
if x < y
	println("x is less than y")
elseif x > y
	println("x is greater than y")
else
	println("x and y are equal")
end
```

Again, exactly one branch will run. There is no limit on the number of `elseif` statements. If there is an `else` clause, it has to be at the end, but there doesn’t have to be one:

```julia
if choice == "a"
	draw_a()
elseif choice == "b"
	draw_b()
elseif choice == "c"
	draw_c()
end
```

Each condition is checked in order. If the first is false, the next is checked, and so on. If one of them is true, the corresponding branch runs and the statement ends. Even if more than one condition is true, only the first true branch runs.
"""

# ╔═╡ 9a3080ec-6be4-11eb-32eb-f162987e0fd6
md"""
!!! languages
    In Python, `if` statements have the same structure as function definitions: a header followed by an indented body.

    ```python
    if x < y:
        print('x is less than y')
    elif x > y:
        print('x is greater than y')
    else:
        print('x and y are equal')

    ```

    In the C programming language, we find a similar `if` statement:

    ```c
    if (x < y) {
        printf("x is less than y\n");
    }
    else if (x > y) {
        printf("x is greater than y\n");
    }
    else {
        printf("x and y are equal\n");
    }
    ```
"""

# ╔═╡ c0f31858-6bdc-11eb-00f8-9b4fb5bebb3e
md"""## Nested Conditionals

One conditional can also be nested within another. We could have written the example in the previous section like this:

```julia
if x == y
	println("x and y are equal")
else
	if x < y
		println("x is less than y")
	else
		println("x is greater than y")
	end
end
```

The outer conditional contains two branches. The first branch contains a simple statement. The second branch contains another `if` statement, which has two branches of its own. Those two branches are both simple statements, although they could have been conditional statements as well.

Although the (noncompulsory) indentation of the statements makes the structure apparent, *nested conditionals* become difficult to read very quickly. It is a good idea to avoid them when you can.

Logical operators often provide a way to simplify nested conditional statements. For example, we can rewrite the following code using a single conditional:

```julia
if 0 < x
	if x < 10
		println("x is a positive single-digit number.")
	end
end
```

The print statement runs only if we make it past both conditionals, so we can get the same effect with the `&&` operator:

```julia
if 0 < x && x < 10
	println("x is a positive single-digit number.")
end
```

For this kind of condition, Julia provides a more concise syntax:

```julia
if 0 < x < 10
	println("x is a positive single-digit number.")
end
```
"""

# ╔═╡ 81410cd0-6bdd-11eb-3ac7-21c132df1a56
md"""## Recursion

It is legal for one function to call another; it is also legal for a function to call itself. It may not be obvious why that is a good thing, but it turns out to be one of the most magical things a program can do. For example, look at the following function:

```julia
function countdown(n)
	if n ≤ 0
		println("Blastoff!")
	else
		print(n, " ")
		countdown(n-1)
	end
end
```

If `n` is `0` or negative, it outputs the word `"Blastoff!"`. Otherwise, it outputs `n` and then calls a function named `countdown`—itself—passing `n-1` as an argument.

What happens if we call this function like this?

```julia
julia> countdown(3)
3 2 1 Blastoff!
```

* The execution of `countdown` begins with `n = 3`, and since `n` is greater than `0`, it outputs the value `3`, and then calls itself...
  * The execution of `countdown` begins with `n = 2`, and since `n` is greater than `0`, it outputs the value `2`, and then calls itself...

    * The execution of `countdown` begins with `n = 1`, and since `n` is greater than `0`, it outputs the value `1`, and then calls itself...

      * The execution of `countdown` begins with `n = 0`, and since `n` is not greater than `0`, it outputs the word `"Blastoff!"` and then returns.

    * The countdown that got `n = 1` returns.

  * The countdown that got `n = 2` returns.

* The countdown that got `n = 3` returns. And then you’re back in `Main`.

A function that calls itself is *recursive*; the process of executing it is called *recursion*.

As another example, we can write a function that prints a string ``n`` times:

```julia
function printn(s, n)
	if n ≤ 0
		return
	end
	println(s)
	printn(s, n-1)
end
```

If `n <= 0` the *`return` statement* exits the function. The flow of execution immediately returns to the caller, and the remaining lines of the function don’t run.

The rest of the function is similar to countdown: it displays s and then calls itself to display `s` `n-1` additional times. So, the number of lines of output is `1 + (n - 1)`, which adds up to `n`.

For simple examples like this, it is probably easier to use a `for` loop. But we will see examples later that are hard to write with a for loop and easy to write with recursion, so it is good to start early.
"""

# ╔═╡ 481f2708-6bde-11eb-2e2f-059722a2e288
md"""## Stack Diagrams for Recursive Functions

In “Stack Diagrams”, we used a stack diagram to represent the state of a program during a function call. The same kind of diagram can help us interpret a recursive function.

Every time a function gets called, Julia creates a frame to contain the function’s local variables and parameters. For a recursive function, there might be more than one frame on the stack at the same time.
"""

# ╔═╡ e7bc0384-6bdf-11eb-23d5-1f8454e713f2
Drawing(width=720, height=200) do
	text(x=320, y=30, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("Main")
	end
    rect(x=360, y=10, width=100, height=30, fill="rgb(242, 242, 242)", stroke="black")
	text(x=320, y=70, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("countdown")
	end
    rect(x=360, y=50, width=100, height=30, fill="rgb(242, 242, 242)", stroke="black")
	text(x=380, y=70, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	text(x=400, y=70, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("->")
	end
	text(x=430, y=70, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("3")
	end
	text(x=320, y=110, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("countdown")
	end
    rect(x=360, y=90, width=100, height=30, fill="rgb(242, 242, 242)", stroke="black")
	text(x=380, y=110, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	text(x=400, y=110, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("->")
	end
	text(x=430, y=110, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("2")
	end
	text(x=320, y=150, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("countdown")
	end
    rect(x=360, y=130, width=100, height=30, fill="rgb(242, 242, 242)", stroke="black")
	text(x=380, y=150, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	text(x=400, y=150, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("->")
	end
	text(x=430, y=150, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	text(x=320, y=190, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("countdown")
	end
    rect(x=360, y=170, width=100, height=30, fill="rgb(242, 242, 242)", stroke="black")
	text(x=380, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	text(x=400, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("->")
	end
	text(x=430, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("0")
	end
end

# ╔═╡ 85d66ec4-6be0-11eb-32c2-f7068bc06a62
md"*Figure 5-1. Stack diagram.*"

# ╔═╡ 0658bd3c-6be0-11eb-21bd-d753ae791777
md"""As usual, at the top of the stack is the frame for `Main`. It is empty because we did not create any variables in `Main` or pass any arguments to it.

The four `countdown` frames have different values for the parameter `n`. The bottom of the stack, where `n = 0`, is called the *base case*. It does not make a recursive call, so there are no more frames."""

# ╔═╡ c7a395b0-6be1-11eb-2d6c-8d15b718faa9
md"""#### Exercise 5-1

As an exercise, draw a stack diagram for printn called with `s = "Hello"` and `n = 2`. Then write a function called `do_n` that takes a function object and a number, `n`, as arguments, and that calls the given function `n` times.
"""

# ╔═╡ f94724b0-6be1-11eb-2183-1d97bab81764
md"""## Infinite Recursion

If a recursion never reaches a base case, it goes on making recursive calls forever, and the program never terminates. This is known as *infinite recursion*, and it is generally not a good idea. Here is a minimal program with an infinite recursion:

```julia
function recurse()
	recurse()
end
```

In most programming environments, a program with an infinite recursion does not really run forever. Julia reports an error message when the maximum recursion depth is reached:

```julia
julia> recurse()
ERROR: StackOverflowError:
Stacktrace:
     [1] recurse() at ./REPL[1]:2 (repeats 80000 times)
```

This stacktrace is a little bigger than the one we saw in the previous chapter. When the error occurs, there are 80,000 `recurse` frames on the stack!

If you encounter an infinite recursion by accident, review your function to confirm that there is a base case that does not make a recursive call. And if there is a base case, check whether you are guaranteed to reach it.
"""

# ╔═╡ b5bbb0ac-6be2-11eb-33fd-7971aa3a4c24
md"""## Keyboard Input

The programs we have written so far accept no input from the user. They just do the same thing every time.

Julia provides a built-in function called readline that stops the program and waits for the user to type something. When the user presses Return or Enter, the program resumes and readline returns what the user typed as a string:

```julia
julia> text = readline()
What are you waiting for?
"What are you waiting for?"
```

Before getting input from the user, it is a good idea to print a prompt telling her what to type:

```julia
julia> print("What...is your name? "); readline()
What...is your name? Arthur, King of the Britons!
"Arthur, King of the Britons!"
```

A semicolon (`;`) allows you to put multiple statements on the same line. In the REPL, only the last statement returns its value. semicolon (;) allows you to put multiple statements on the same line. In the REPL, only the last statement returns its value.

If you expect the user to type an integer, you can try to convert the return value to `Int64`:

```julia
julia> println("What...is the airspeed velocity of an unladen swallow?"); speed = readline()
What...is the airspeed velocity of an unladen swallow?
42
"42"
julia> parse(Int64, speed)
42
```

But if the user types something other than a string of digits, you get an error:

```julia
julia> println("What...is the airspeed velocity of an unladen swallow? "); speed = readline();
What...is the airspeed velocity of an unladen swallow?
What do you mean, an African or a European swallow?
julia> parse(Int64, speed)
ERROR: ArgumentError: invalid base 10 digit 'W' in "What do you mean, an African or a European swallow?"
[...]
```

We will see how to handle this kind of error later.

!!! info
    `readline` does not function in a Pluto notebook. However, a variable can be bound directly to a text input field:

    ```julia
    @bind x html"<input type=text>"
    ```

    Entering text in the text field will update the `x` variable and all top level statement using `x` will be reexecuted.

    All html input type are available.
"""

# ╔═╡ 96c3d1d8-6be3-11eb-1a7a-51786b2935ee
md"""## Debugging

When a syntax or runtime error occurs, the error message contains a lot of information. This can be overwhelming. The most useful parts are usually:

* What kind of error it was
* Where it occurred

Syntax errors are usually easy to find, but there are a few gotchas. In general, error messages indicate where the problem was discovered, but the actual error might be earlier in the code, sometimes on a previous line.

The same is true of runtime errors. Suppose you are trying to compute a signal-to- noise ratio in decibels. The formula is:

```math
\textit{SNR}_\mathrm{db} = 10 \log_{10} \frac{P_\mathrm{signal}}{P_\mathrm{noise}}
```

In Julia, you might write something like this:

```julia
signal_power = 9
noise_power = 10
ratio = signal_power ÷ noise_power
decibels = 10 * log10(ratio)
print(decibels)
```

And you’d get:

```julia
-Inf
```

This is not the result you expected.

To find the error, it might be useful to print the value of `ratio`, which turns out to be `0`. The problem is in line 3, which uses floor division instead of floatingpoint division.

!!! warning
    You should take the time to read error messages carefully, but don’t assume that everything they say is correct.
"""

# ╔═╡ acd667ba-6be3-11eb-00a0-55632ca86594
md"""## Glossary

*floor division*:
An operator, denoted `÷`, that divides two numbers and rounds down (toward negative infinity) to an integer.

*modulus operator*:
An operator, denoted with a percent sign (`%`), that works on integers and returns the remainder when one number is divided by another.

*Boolean expression*:
An expression whose value is either `true` or `false`.

*relational operator*:
One of the operators that compares its operands: `==`, `≠` (`!=`), `>`, `<`, `≥` (`>=`), and `≤` (`<=`).

*logical operator*:
One of the operators that combines Boolean expressions: `&&` (and), `||` (or), and `!` (not).

*conditional statement*:
A statement that controls the flow of execution depending on some condition.

*condition*:
The Boolean expression in a conditional statement that determines which branch runs.

*compound statement*:
A statement that consists of a header and a body. The body is terminated with the keyword `end`.

*branch*:
One of the alternative sequences of statements in a conditional statement.

*chained conditional*:
A conditional statement with a series of alternative branches.

*nested conditional*:
A conditional statement that appears in one of the branches of another conditional statement.

*recursive function*:
A function that calls itself.

*recursion*:
The process of calling the function that is currently executing.

*return statement*:
A statement that causes a function to end immediately and return to the caller.

*base case*:
A conditional branch in a recursive function that does not make a recursive call.

*infinite recursion*:
A recursion that doesn’t have a base case, or never reaches it. Eventually, an infinite recursion causes a runtime error.
"""

# ╔═╡ 81e8c81e-6be7-11eb-1052-434aee7931cf
md"""## Exercises

#### Exercise 5-2

The function `time` returns the current Greenwich Mean Time in seconds since “the epoch,” which is an arbitrary time used as a reference point. On Unix systems, the epoch is 1 January 1970:

```julia
julia> time()
1.612992834821836e9
```

Write a script that reads the current time and converts it to a time of day in hours, minutes, and seconds, plus the number of days since the epoch.
"""

# ╔═╡ bdd9c6c0-6be7-11eb-1cf0-65fbaf11abd0
md"""#### Exercise 5-3

Fermat’s Last Theorem says that there are no positive integers ``a``, ``b``, and ``c`` such that:

```math
a^n + b^n = c^n
```

for any value of ``n`` greater than ``2``.

1. Write a function named `checkfermat` that takes four parameters—`a`, `b`, `c`, and `n`—and checks to see if Fermat’s theorem holds. If `n` is greater than `2` and `a^n + b^n == c^n` the program should print, “Holy smokes, Fermat was wrong!” Otherwise, the program should print, “No, that doesn’t work.”

2. Write a function that prompts the user to input values for `a`, `b`, `c`, and `n`, converts them to integers, and uses `checkfermat` to check whether they violate Fermat’s theorem.
"""

# ╔═╡ 3a3ae2be-6be8-11eb-1fdc-db6aadd33a76
md"""#### Exercise 5-4

If you are given three sticks, you may or may not be able to arrange them in a triangle. For example, if one of the sticks is 12 inches long and the other two are 1 inch long, you will not be able to get the short sticks to meet in the middle. For any three lengths, there is a simple test to see if it is possible to form a triangle:

*If any of the three lengths is greater than the sum of the other two, then you cannot form a triangle. Otherwise, you can. (If the sum of two lengths equals the third, they form what is called a “degenerate” triangle.)*

1. Write a function named `istriangle` that takes three integers as arguments and prints either “Yes” or “No,” depending on whether you can or cannot form a triangle from sticks with the given lengths.

2. Write a function that prompts the user to input three stick lengths, converts them to integers, and uses `istriangle` to check whether sticks with the given lengths can form a triangle.
"""

# ╔═╡ 894fdcfe-6be8-11eb-021a-df6b152e0a2d
md"""#### Exercise 5-5

What is the output of the following program? Draw a stack diagram that shows the state of the program when it prints the result.

```julia
function recurse(n, s)
	if n == 0
		println(s)
	else
		recurse(n-1, n+s)
	end
end

recurse(3, 0)
```

1. What would happen if you called this function like this: `recurse(-1, 0)`?

2. Write a docstring that explains everything someone would need to know in order to use this function (and nothing else).
"""

# ╔═╡ 052ec49c-6bea-11eb-325f-d924ddacca6f
md"""
!!! info
    The following exercises use the `NativeSVG` module, described in Chapter 4.

#### Exercise 5-6

Read the following function and see if you can figure out what it does. Then run it and see if you got it right.

```julia
function draw(t, length, n)
	if n == 0
		return
	end
	angle = 50
	forward(t, length*n)
	turn(t, -angle)
	draw(t, length, n-1)
	turn(t, 2*angle)
	draw(t, length, n-1)
	turn(t, -angle)
	forward(t, -length*n)
end
```
"""

# ╔═╡ 5609252e-6bea-11eb-073c-0db3c1ab2da7
md"""#### Exercise 5-7

The Koch curve is a fractal that looks something like Figure 5-2.
"""

# ╔═╡ a70a425e-6beb-11eb-08e3-5d02826766e1
md"*Figure 5-2. A Koch curve.*"

# ╔═╡ b7655da0-6beb-11eb-21e4-a3372fe53c50
md"""To draw a Koch curve with length ``x``, all you have to do is:

1. Draw a Koch curve with length ``\frac{x}{3}``.
2. Turn left ``60`` degrees.
3. Draw a Koch curve with length ``\frac{x}{3}``.
4. Turn right ``120`` degrees.
5. Draw a Koch curve with length ``\frac{x}{3}``.
6. Turn left ``60`` degrees.
7. Draw a Koch curve with length ``\frac{x}{3}``.

The exception is if ``x`` is less than ``3``: in that case, you can just draw a straight line with length ``x``.

1. Write a function called `koch` that takes a turtle and a length as parameters, and that uses the turtle to draw a Koch curve with the given length.

2. Write a function called `snowflake` that draws three Koch curves to make the out‐ line of a snowflake.

3. The Koch curve can be generalized in several ways. See [https://en.wikipedia.org/ wiki/Koch_snowflake](https://en.wikipedia.org/ wiki/Koch_snowflake) for examples and implement your favorite.
"""

# ╔═╡ Cell order:
# ╟─867b290c-6bd9-11eb-1afb-47757ee13936
# ╟─4cc6b8ac-6bd9-11eb-1d23-f5d9caf02cd3
# ╟─81ea78c0-6bd9-11eb-1d3b-5f67fdf090f4
# ╟─fa7e737c-6bd9-11eb-3cd0-d5b1d2ae7af1
# ╟─01a13616-6bdb-11eb-1b8b-8bdda36e623f
# ╟─7478967a-6bdb-11eb-03f7-81f894751f6d
# ╟─e75638d2-6bdb-11eb-0a7e-93446ea41c2d
# ╟─482d8390-6bdc-11eb-3e3c-33b9f4f244b9
# ╟─9a3080ec-6be4-11eb-32eb-f162987e0fd6
# ╟─c0f31858-6bdc-11eb-00f8-9b4fb5bebb3e
# ╟─81410cd0-6bdd-11eb-3ac7-21c132df1a56
# ╟─481f2708-6bde-11eb-2e2f-059722a2e288
# ╟─e7bc0384-6bdf-11eb-23d5-1f8454e713f2
# ╟─85d66ec4-6be0-11eb-32c2-f7068bc06a62
# ╟─0658bd3c-6be0-11eb-21bd-d753ae791777
# ╟─c7a395b0-6be1-11eb-2d6c-8d15b718faa9
# ╟─f94724b0-6be1-11eb-2183-1d97bab81764
# ╟─b5bbb0ac-6be2-11eb-33fd-7971aa3a4c24
# ╟─96c3d1d8-6be3-11eb-1a7a-51786b2935ee
# ╟─acd667ba-6be3-11eb-00a0-55632ca86594
# ╟─81e8c81e-6be7-11eb-1052-434aee7931cf
# ╟─bdd9c6c0-6be7-11eb-1cf0-65fbaf11abd0
# ╟─3a3ae2be-6be8-11eb-1fdc-db6aadd33a76
# ╟─894fdcfe-6be8-11eb-021a-df6b152e0a2d
# ╟─052ec49c-6bea-11eb-325f-d924ddacca6f
# ╟─5609252e-6bea-11eb-073c-0db3c1ab2da7
# ╟─66ddc134-6bea-11eb-3bde-0fea30c863af
# ╟─a70a425e-6beb-11eb-08e3-5d02826766e1
# ╟─b7655da0-6beb-11eb-21e4-a3372fe53c50


export bracket, inverse_uk, inverse, inverse!

#Expands the range of valued searched geometrically until a root is bracketed
# Julia's adaptation of zbrac Numerical Recipes in Fortran 77 p.345
function bracket(f, a, b)
    F = 1.6
    Niter = 100
    outa = deepcopy(a)
    outb = deepcopy(b)
    counter = 0
    # Root is not bracketed
    while f(outa)*f(outb)>0 && counter<Niter
        Δ = F*deepcopy(outa-outb)
        if abs(f(outa))<abs(f(outb))
        outa += Δ
        else
        outb += Δ
        end
        counter += 1
    end
    return outa, outb
    @assert counter<=(Niter-1) "Maximal number of iterations reached"
end

function inverse_uk(u::uk, x, κ; z0::Real=0.0)
    if u.p==-1
    # Identity transformation
        return x
    elseif u.p==0
    # Solve u(z)= az + b = x for z
        return (x-u.coeffk[1])/u.coeffk[2]
    else
        zlim = (u.ξk[1]-κ*u.σk[1], u.ξk[end]+κ*u.σk[end])
        #Ensure that the zero is bracketed
        zlim = bracket(z->u(z)-x, zlim[1], zlim[2])
        # Roots.Brent()
        return find_zero(z->u(z)-x, zlim, Roots.Brent())
    end
end

# y is the observation of size ny
function inverse(x::AbstractVector{Float64}, F, S::RadialMap, ystar; apply_rescaling::Bool=true)
    @get S (Nx, p, κ)
    ny = size(ystar,1)
    x[1:ny] .= ystar
    # Recursive 1D root-finding
    # Start optimization from the a priori component

    if apply_rescaling == true
            transform!(S.L, x)
    end

    @inbounds for i=ny+1:Nx
        Ui = S.C[i]
        uk_i = component(Ui, i)
        x[i] = inverse_uk(uk_i, F[i] - off_diagonal(Ui, view(x,1:i-1)), κ)
    end

    if apply_rescaling == true
            itransform!(S.L, x)
    end
end

# y is the observation of size ny
function inverse(x::AbstractVector{Float64}, F, S::SparseRadialMap, ystar; apply_rescaling::Bool=true)
    @get S (Nx, p, κ)
    ny = size(ystar,1)
    x[1:ny] .= ystar

    if apply_rescaling == true
        transform!(S.L, x)
    end

    # Recursive 1D root-finding
    # Start optimization from the a priori component
    @inbounds for i=ny+1:Nx
        Ui = S.C[i]
        uk_i = component(Ui, i)
        x[i] = inverse_uk(uk_i, F[i] - off_diagonal(Ui, view(x,1:i-1)), κ)
    end

    if apply_rescaling == true
        transform!(S.L, x)
    end
end

function inverse!(X::Array{Float64,2}, F, S::SparseRadialMap, ystar::AbstractVector{Float64}; apply_rescaling::Bool=true, start::Int64=1)
    @get S (Nx, p, κ)
    Nx = S.Nx
    NxX, Ne = size(X)

    Ny = size(ystar,1)
    @assert NxX == Nx
    @assert 1 <= Ny < Nx
    @assert size(F) == (Nx, Ne)

    @view(X[1:Ny,:]) .= ystar

    if apply_rescaling == true
        transform!(S.L, X)
    end

    # Recursive 1D root-finding
    # Start optimization from the a priori component
    @inbounds for i=Ny+1:Nx
        Ui = S.C[i]
        if Ui.p[end] != -1
            uk_i = component(Ui, i)
            for j=1:Ne
                X[i,j] = inverse_uk(uk_i, F[i,j] - off_diagonal(Ui, view(X,1:i-1,j)), κ)
            end
        end
    end

    if apply_rescaling == true
        itransform!(S.L, X)
    end
end


# This file contains algorithms needed for the integratiorn of
# coupled Risch differential systems from chapter 8 of the book
#
#    Manuel Bronstein, Symbolic Integration I: Transcendental Functions,
#    2nd ed., Springer 2005.
#

"""
    CoupledDECancelPrim(b₁, b₂, c₁, c₂,  D[, n=∞]) -> (q₁, q₂, ρ)

Coupled Polynomial Risch differential system, cancellation - primitive case.

Given a field `k` not containing `√-1` , a derivation `D` on `k[t]` with `D(t)` in `k` , an integer `n`,
`b₁`, `b₂` in `k` with `(b₁,b₂)≠(0,0)` and `c₁`, `c₂` in `k[t]`, return either
`ρ=0`, in which case the system of equations `D(q₁)+b₁*q₁-b₂*q₂=c₁`, `D(q₂)+b₂*q₁+b₁*q₂=c₂`
has no solution `q₁`, `q₂` with both degrees at most `n` in `k[t]`,
or `ρ=1` and a solution `(q₁,q₂)` in `k[t]×k[t]` of this system with `degree(q₁)≤n`
and `degree(q₂)≤n`.

See [Bronstein's book](https://link.springer.com/book/10.1007/b138171), Section 8.2, p. 260.
"""
function CoupledDECancelPrim(b1::T, b2::T,  c1::P, c2::P, D::Derivation, n::Int=typemax(Int)) where
    {T<:FieldElement, P<:PolyElem{T}} # here typemax(Int) represents +infinity
    isprimitive(D) ||
        error("monomial of derivation D must be primitive")
    D0 = BaseDerivation(D)
    (iscompatible(b1, D0) && iscompatible(b2, D0)) ||
        error("coefficients b1 and b2 must be in the domain of the base derivation of D")
    (iscompatible(c1, D) && iscompatible(c2, D)) ||
        error("polynomials c1 and c2 must be in the domain of derivation D")
    Z = zero(c1)
    no_solution = (Z, Z, 0)
    # TODO: case (b1, b2)==(0, 0) !?
    t = gen(parent(c1))
    _, I, D0I = Complexify(parent(b1), D0)
    z, ρ = InFieldLogarithmicDerivative(b1 + b2*I, D0I)
    if ρ>0
        z1 = real(z)
        z2 = imag(z)
        p10, ρ = InFieldDerivative(z1*c1 - z2*c2, D)
        p1 = numerator(p10)
        if ρ<=0 || !isone(denominator(p10)) || degree(p1)>n
            return no_solution
        end
        p20, ρ = InFieldDerivative(z2*c1 + z1*c2, D)
        p2 = numerator(p20)
        if ρ<=0 || !isone(denominator(p20)) || degree(21)>n
            return no_solution
        end
        d = z1^2 + z1^2
        return (z1*p1 + z2*p2)//d, (z1*p2 - z2*p1)//d, 1
    end
    if iszero(c1) && iszero(c2)
        return Z, Z, 1 # zero is solution
    end
    if n<max(degree(c1), degree(c2))
        return no_solution
    end
    q1 = Z
    q2 = Z
    while !iszero(c1) || !iszero(c2)
        m = max(degree(c1), degree(c2))
        if n<m
            return no_solution
        end
        s1, s2, ρ = CoupledDESystem(b1, b2, coeff(c1, m), coeff(c2, m), D0)
        if ρ<=0
            return no_solution
        end
        q1 = q1 + s1*t^m
        q2 = q2 + s2*t^m
        n = m - 1
        c1 -= D(s1*t^m) + (b1*s1 - b2*s2)*t^m
        c2 -= D(s2*t^m) + (b2*s1 + b1*s2)*t^m
    end
    q1, q2, 1
end

"""
    CoupledDECancelExp(b₁, b₂, c₁, c₂,  D[, n=∞]) -> (q₁, q₂, ρ)

Coupled Polynomial Risch differential system, cancellation - hyperexponential case.

Given a field `k` not containing `√-1` , a derivation `D` on `k[t]` with `D(t)/t` in `k` , an integer `n`,
`b₁`, `b₂` in `k` with `(b₁,b₂)≠(0,0)` and `c₁`, `c₂` in `k[t]`, return either
`ρ=0`, in which case the system of equations `D(q₁)+b₁*q₁-b₂*q₂=c₁`, `D(q₂)+b₂*q₁+b₁*q₂=c₂`
has no solution `q₁`, `q₂` with both degrees at most `n` in `k[t]`,
or `ρ=1` and a solution `(q₁,q₂)` in `k[t]×k[t]` of this system with `degree(q₁)≤n`
and `degree(q₂)≤n`.

See [Bronstein's book](https://link.springer.com/book/10.1007/b138171), Section 8.2, p. 262.
"""
function CoupledDECancelExp(b1::T, b2::T,  c1::P, c2::P, D::Derivation, n::Int=typemax(Int)) where
    {T<:FieldElement, P<:PolyElem{T}} # here typemax(Int) represents +infinity
    ishyperexponential(D) ||
        error("monomial of derivation D must be hyperexponential")
    D0 = BaseDerivation(D)
    (iscompatible(b1, D0) && iscompatible(b2, D0)) ||
        error("coefficients b1 and b2 must be in the domain of the base derivation of D")
    (iscompatible(c1, D) && iscompatible(c2, D)) ||
        error("polynomials c1 and c2 must be in the domain of derivation D")
    Z = zero(c1)
    no_solution = (Z, Z, 0)
    # TODO: case (b1, b2)==(0, 0) !?
    t = gen(parent(c1))
    _, I, D0I = Complexify(parent(b1), D0)
    H = MonomialDerivative(D)
    w = coeff(H,1) # = Dt/t for hyperexponentialm case
    n1, m, z, ρ = ParametricLogarithmicDerivative(b1 + b2*I, w + 0*I, D0I)
    if  ρ>0 && n1==1
        z1 = real(z)
        z2 = imag(z)
        p1, ρ = InFieldDerivative((z1*c1 - z2*c2)*t^m, D)
        if ρ<=0 || !isreduced(p1, D)
            return no_solution
        end
        h = (t//1)^(-m)//(z1^2 + z2^2)
        q10 = (z1*p1 + z2*p2)*h
        q1 = numerator(q10)
        if !isone(denominator(q10)) || degree(q1)>n
            return no_solution
        end
        p2, ρ = InFieldDerivative((z2*c1 + z1*c2)*t^m, D)
        if ρ<=0 || !isreduced(p1, D)
            return no_solution
        end
        q20 = (z1*p2 - z2*p1)*h
        q2 = numerator(q20)
        if !isone(denominator(q20)) || degree(q2)>n
            return no_solution
        end
        return q1, q2, 1
    end
    if iszero(c1) && iszero(c2)
        return Z, Z, 1 # zero is solution
    end
    if n<max(degree(c1), degree(c2))
        return no_solution
    end
    q1 = Z
    q2 = Z
    while !iszero(c1) || !iszero(c2)
        m = max(degree(c1), degree(c2))
        if n<m
            return no_solution
        end
        s1, s2, ρ = CoupledDESystem(b1 + m*w, b2, coeff(c1, m), coeff(c2, m), D0)
        if ρ<=0
            return no_solution
        end
        q1 = q1 + s1*t^m
        q2 = q2 + s2*t^m
        n = m - 1
        c1 -= D(s1*t^m) + (b1*s1 - b2*s2)*t^m
        c2 -= D(s2*t^m) + (b2*s1 + b1*s2)*t^m
    end
    q1, q2, 1
end

"""
    CoupledDECancelTan(b₀, b₂, c₁, c₂,  D[, n=∞]) -> (q₁, q₂, ρ)

Coupled Polynomial Risch differential system, cancellation - hypertangent case.

Given a field `k` not containing `√-1` , a derivation `D` on `k[t]` with `D(t)/(t²+1)=η` in `k` , an integer `n`,
`b₀`, `b₂` in `k` with `(b₀,b₂)≠(0,0)` and `c₁`, `c₂` in `k[t]`, return either
`ρ=0`, in which case the system of equations `D(q₁)+(b₀-n*η*t)*q₁-b₂*q₂=c₁`, `D(q₂)+b₂*q₁+(b₀+n*η*t)*q₂=c₂`
has no solution `q₁`, `q₂` with both degrees at most `n` in `k[t]`,
or `ρ=1` and a solution `(q₁,q₂)` in `k[t]×k[t]` of this system with `degree(q₁)≤n`
and `degree(q₂)≤n`.

See [Bronstein's book](https://link.springer.com/book/10.1007/b138171), Section 8.2, p. 262.
"""
function CoupledDECancelTan(b0::T, b2::T,  c1::P, c2::P, D::Derivation, n::Int=typemax(Int)) where
    {T<:FieldElement, P<:PolyElem{T}} # here typemax(Int) represents +infinity
    ishypertangent(D) ||
        error("monomial of derivation D must be hypertangent")
    D0 = BaseDerivation(D)
    (iscompatible(b0, D0) && iscompatible(b2, D0)) ||
        error("coefficients b0 and b2 must be in the domain of the base derivation of D")
    (iscompatible(c1, D) && iscompatible(c2, D)) ||
        error("polynomials c1 and c2 must be in the domain of derivation D")
    Z = zero(c1)
    no_solution = (Z, Z, 0)
    # TODO: case (b0, b2)==(0, 0) !?
    if n==0
        if degree(c1)<=0 && degree(c2)<=0
            q1, q2, ρ = CoupledDESystem(b0, b2, constant_coefficient(c1), constant_coefficient(c2), BaseDerivation(D))
            if ρ<=0
                return no_solution
            end
            return q1 + Z, q2 + Z, 1
        end
        return no_solution
    end
    t = gen(parent(c1))
    H = MonomialDerivative(D)
    η = constant_coefficient(divexact(H, t^2 + 1))
    ktI, I, DI = Complexify(FractionField(parent(c1)), D) # k(t)(√-1)
    p = t - I
    z = c1(I) + c2(I)*I
    z1 = real(z)
    @assert isone(denominator(z1)) && degree(numerator(z1))<=0
    z1 = constant_coefficient(numerator(z1))  # real(z) in k(t), z1 in k
    z2 = imag(z)
    @assert isone(denominator(z2)) && degree(numerator(z2))<=0
    z2 = constant_coefficient(numerator(z2))  # imag(z) in k(t), z2 in k
    s1, s2, ρ = CoupledDESystem(b0, b2 - n*η, z1, z2, BaseDerivation(D))
    if ρ<=0
        return no_solution
    end
    c = (c1 - z1 + n*η*(s1*t + s2) + (c2 - z2 + n*η*(s2*t - s1))*I)//p
    d1 = real(c)
    @assert isone(denominator(d1))
    d1 = numerator(d1)
    d2 = imag(c)
    @assert isone(denominator(d2))
    d2 = numerator(d2)
    h1, h2, ρ = CoupledDECancelTan(b0, b2 + η, d1, d2, D, n - 1)
    if ρ<=0
        return no_solution
    end
    h1*t + h2 + s1, h2*t - h1 + s2, 1
end

function CoupledDESystem(f1::F, f2::F, g1::F, g2::F, D::Derivation) where
    {P<:PolyElem, F<:FracElem{P}}
    iscompatible(f1, D) && iscompatible(f2, D) && iscompatible(g1, D) && iscompatible(g2, D)||
        error("rational functions f1. f2. g1, g2 must be in the domain of derivation D")
    #if iszero(f1) && iszero(f2)
    #    return InFieldDerivative ...
    #end

    no_solution = zero(f1), zero(f1), 0

    t0 = gen(parent(numerator(f1)))
    ktI, I0, DI0 = Complexify(parent(f1), D) # k(t)(√-1)
    kIt, t, I, DI =  switch_t_i(ktI, DI0) # k(√-1)(t)

    f = transform(f1 + I0*f2, t, I)
    g = transform(g1 + I0*g2, t, I)

    Z = zero(f)
    h0 = WeakNormalizer(f, DI)

    f = f - DI(h0)//h0
    g = h0*g
    a, b, c, h1, ρ = RdeNormalDenominator(f, g, DI)
    ρ>=1 || return no_solution

    a, b, c, h2 = RdeSpecialDenominator(a, b, c, DI)

    n = RdeBoundDegree(a, b, c, DI)

    b, c, n, α, β, ρ = SPDE(a, b, c, DI, n)
    ρ>=1 || return no_solution

    z, ρ = PolyRischDE(b, c, DI, n)
    if ρ<=0
        return no_solution
    elseif ρ==1
        y = backtransform((α*z + β)//(h0*h1*h2), t0, I0)
        return real(y), imag(y), 1
    end

    @assert ρ>=100
    n = ρ - 101

    #TODO: case b1=0 and b2=0: in-field integration algorithm. see p. 259
    c = backtransform(z + Z, t0, I0)
    c1 = real(c)
    c2 = imag(c)
    @assert isone(denominator(c1)) && isone(denominator(c2))
    c1 = numerator(c1)
    c2 = numerator(c2)
    b = backtransform(b + Z, t0, I0)
    b1 = real(b)
    b2 = imag(b)
    @assert isone(denominator(b1)) && isone(denominator(b2))
    b1 = numerator(b1)
    b2 = numerator(b2)
    if isprimitive(D)
        @assert degree(b1)<=0 && degree(b2)<=0
        z1, z2, ρ = CoupledDECancelPrim(constant_coefficient(b1), constant_coefficient(b2), c1, c2,  D, n)
        ρ>=1 || return no_solution
    elseif ishyperexponential(D)
        @assert degree(b1)<=0 && degree(b2)<=0
        z1, z2, ρ = CoupledDECancelExp(constant_coefficient(b1), constant_coefficient(b2), c1, c2,  D, n)
        ρ>=1 || return no_solution
    elseif ishypertangent(D)
        η = divexact(MonomialDerivative(D), t0^2+1)
        b0 = b1 + n*t0*η
        @assert degree(b0)<=0 && degree(b2)<=0
        z1, z2, ρ = CoupledDECancelTan(constant_coefficient(b0), constant_coefficient(b2), c1, c2,  D, n)
        ρ>=1 || return no_solution
    else
        H = MonomialDerivative(D)
        throw(NotImplementedError("CoupledDESystem: cancellation case, monomial derivative $H\n@ $(@__FILE__):$(@__LINE__)"))
    end

    # Note: α, β, h0, h1, h2 are in k(√-1)(t) or k(√-1)[t]
    α = backtransform(α + Z, t0, I0)
    β = backtransform(β + Z, t0, I0)
    h = backtransform(h0*h1*h2 + Z, t0, I0)
    y = (α*(z1+z2*I0)+β)//h
    real(y), imag(y), 1
end


# boot.m
# Luca Brugnolini 2014
# Based on Kilian code
# This function gives the Kilian bootstrap inverval for IRF by VAR

function boot(A, u, y, p, H)

# Input : Parameter estimates(intercept,slope), estimated residual, data
# Output: Bias-corrected bootstrap 95% confidence interval (Kilian 1999)

(T,K) = size(y);
nrep = 100;                              # number of bootstrap replications 2000
IRFrmat = zeros(nrep, K^2*(H+1));       # IRF for each bootstrap replication
u = u*sqrt((T-p)/(T-p-K*p-1));          # rescaling residual (Stine, JASA 1987)

#  Bootstrap simulation

 for j = 1:nrep

    # Draw block of initial pre-sample values
    yr = zeros(K,T);                             # bootstrap data
    pos = itrunc((rand()*(T-p+1)))+1;            # position of initial draw
    yr[:,1:p] = y[pos:pos+p-1,:]';               # drawing pre-sample obs

    # Draw innovations
    ur = zeros(K,T);                             # bootstrap innovations
    index = itrunc((rand(T-p)*(T-p))+1);         # index for innovation draws
    ur[:, p+1:T] = u[:, index];                  # drawing innovations

    # Recursively construct sample

    for i = p+1:T
            yr[:,i] = A[:,1]+ur[:,i];

        for jj = 1:p
            yr[:,i] = yr[:,i] + A[:,(jj-1)*K+2:jj*K+1]*yr[:,i-jj];

        end

    end

    yr = yr';

    for i=1:K                                   # demean yr bootstrap data
        yr[:,i] = yr[:,i] - mean(yr[:,i]);

    end

    # pbar = 4;
    # pr = varlagorder(yr,pbar);                 # lag-order selection for bootstrap data
    pr = p;
    (A1r,SIGMA1r) = olsvar(yr,pr);
    Ar = [A1r[:,2:end]; [eye(K*(pr-1)) zeros(K*(pr-1),K)]];   # Slope parameter estimates in companion form
    SIGMAr = [SIGMA1r zeros(K,K*pr-K); zeros(K*pr-K,K*pr)];   # variance of residuals in companion form

# Bias correction: if the largest root of the companion matrix
# is less than 1, do BIAS correction

if ~ any(abs(eigvals(Ar)).>=1)
     Ar = asybc(Ar,SIGMAr,T,K,pr);

end

    Ar = real(Ar)
    IRFr = irfvar1(Ar[1:K,1:K*pr], SIGMAr[1:K,1:K], pr, H);
    IRFrmat[j,:] = vec(IRFr')';

    end;                              # end bootstrap

# Calculate 95 perccent interval endpoints
CILv = zeros(1,size(IRFrmat,2));
CIHv = zeros(1,size(IRFrmat,2));

for i = 1:size(IRFrmat,2)
    CILv[:,i] = quantile(vec(IRFrmat[:,i]),0.025);
    CIHv[:,i] = quantile(vec(IRFrmat[:,i]),0.975);

end

CI = [CILv; CIHv];

                return CI

end


export minkowski_sum

"""
    minkowski_sum(P::LazySet{N}, Q::LazySet{N};
                  [backend]=nothing,
                  [algorithm]=nothing,
                  [prune]=true) where {N<:Real}

Concrete Minkowski sum for a pair of lazy sets using their constraint representation.

### Input

- `P`         -- lazy set
- `Q`         -- another lazy set
- `backend`   -- (optional, default: `nothing`) polyhedral computations backend
- `algorithm` -- (optional, default: `nothing`) algorithm to compute the elimination
                 of variables; available options are `Polyhedra.FourierMotzkin`,
                 `Polyhedra.BlockElimination`, and `Polyhedra.ProjectGenerators`
- `prune`     -- (optional, default: `true`) if `true`, apply a post-processing algorithm
                 to remove redundant constraints

### Output

An `HPolytope` that corresponds to the Minkowski sum of `P` and `Q` if both `P`
and `Q` are bounded; otherwise an `HPolyhedron`.

### Notes

This function requires that the list of constraints of both lazy sets `P` and
`Q` can be obtained. After obtaining the respective lists of constraints, the
`minkowski_sum` fucntion for polyhedral sets is used. For details see
[`minkowski_sum(::VPolytope, ::VPolytope)`](@ref).

This method requires `Polyhedra` and `CDDLib`, so you have to do:

```julia
julia> using LazySets, Polyhedra, CDDLib

julia> ...

julia> minkowski_sum(P, Q)
```
"""
function minkowski_sum(P::LazySet{N}, Q::LazySet{N};
                       backend=nothing,
                       algorithm=nothing,
                       prune=true) where {N<:Real}
    @assert applicable(constraints_list, P) &&
        applicable(constraints_list, Q) "this function requires that the " *
        "list of constraints is available for both arguments; try " *
        "overapproximating with an `HPolytope` first"

    res = _minkowski_sum_hrep_preprocess(P, Q, backend, algorithm, prune)
    if isbounded(P) && isbounded(Q)
        return convert(HPolytope, res)
    else
        return res
    end
end

"""
    minkowski_sum(P::AbstractPolyhedron{N}, Q::AbstractPolyhedron{N};
                  [backend]=nothing,
                  [algorithm]=nothing,
                  [prune]=true) where {N<:Real}

Compute the Minkowski sum between two polyhedra in constraint representation.

### Input

- `P`         -- polyhedron in constraint representation
- `Q`         -- another polyhedron in constraint representation
- `backend`   -- (optional, default: `nothing`) polyhedral computations backend
- `algorithm` -- (optional, default: `nothing`) algorithm to compute the elimination
                 of variables; available options are `Polyhedra.FourierMotzkin`,
                 `Polyhedra.BlockElimination`, and `Polyhedra.ProjectGenerators`
- `prune`     -- (optional, default: `true`) if `true`, apply a post-processing algorithm
                 to remove redundant constraints

### Output

A polyhedron in H-representation that corresponds to the Minkowski sum of `P` and `Q`.

### Notes

This method requires `Polyhedra` and `CDDLib`, so you have to do:

```julia
julia> using LazySets, Polyhedra, CDDLib

julia> ...

julia> minkowski_sum(P, Q)
```

### Algorithm

This function implements the concrete Minkowski sum by projection and variable
elimination as detailed in [1]. The idea is that if we write ``P`` and ``Q`` in
*simple H-representation*, that is, ``P = \\{x ∈ \\mathbb{R}^n : Ax ≤ b \\}``
and ``Q = \\{x ∈ \\mathbb{R}^n : Cx ≤ d \\}``, then their Minkowski sum can be
seen as the projection onto the first ``n``-dimensional coordinates of the polyhedron

```math
    \\begin{pmatrix} 0 & A \\ C & -C \\end{pmatrix} \\binom{x}{y} ≤ \binom{b}{d}
```
This is seen by noting that ``P ⊕ Q`` corresponds to the set of points
``x ∈ \\mathbb{R}^n`` such that ``x = y + z`` with ``Ay ≤ b`` and ``Cz ≤ d``;
hence it follows that ``Ay ≤ b`` and ``C(x-y) ≤ d``, and the inequality displayed
above follows by considering the ``2n``-dimensional space ``\\binom{x}{y}``.
The reduction from ``2n`` to ``n`` variables is performed using an elimination
algorithm as described next.

The elimination of variables depends on the concrete polyhedra library `Polyhedra`,
which itself uses `CDDLib` for variable elimination. The available algorithms are:

- `Polyhedra.FourierMotzkin`   -- computation of the projection by computing the
                                  H-representation and applying the Fourier-Motzkin
                                  elimination algorithm to it

- `Polyhedra.BlockElimination` -- computation of the projection by computing the
                                  H-representation and applying the block elimination
                                  algorithm to it

- `Polyhedra.ProjectGenerators` -- computation of the projection by computing the
                                   V-representation

[1] Kvasnica, Michal. "Minkowski addition of convex polytopes." (2005): 1-10.
"""
function minkowski_sum(P::AbstractPolyhedron{N}, Q::AbstractPolyhedron{N};
                       backend=nothing,
                       algorithm=nothing,
                       prune=true) where {N<:Real}
    return _minkowski_sum_hrep_preprocess(P, Q, backend, algorithm, prune)
end

function minkowski_sum(P::HPolytope{N}, Q::HPolytope{N};
                       backend=nothing,
                       algorithm=nothing,
                       prune=true) where {N<:Real}
    res = _minkowski_sum_hrep_preprocess(P, Q, backend, algorithm, prune)
    return convert(HPolytope, res)
end

# common code before calling _minkowski_sum_hrep
function _minkowski_sum_hrep_preprocess(P, Q, backend, algorithm, prune)
    require(:Polyhedra; fun_name="minkowski_sum")
    require(:CDDLib; fun_name="minkowski_sum")

    A, b = tosimplehrep(P)
    C, d = tosimplehrep(Q)
    return _minkowski_sum_hrep(A, b, C, d, backend=backend, algorithm=algorithm,
                               prune=prune)
end

# This function computes the concrete Minkowski sum between two polyhedra in
# simple H-representation,
# P = {x : Ax <= b} and Q = {x : Cx <= d}
# using the projection methods. See the documentation of `minkowski_sum` for details.
function _minkowski_sum_hrep(A::AbstractMatrix{N}, b::AbstractVector{N},
                             C::AbstractMatrix{N}, d::AbstractVector{N};
                             backend=nothing,
                             algorithm=nothing,
                             prune=true) where {N<:Real}

    if backend == nothing
        backend = default_cddlib_backend(N)
    end

    if algorithm == nothing
        algorithm = Polyhedra.FourierMotzkin()
    elseif !(algorithm <: EliminationAlgorithm)
        error("the algorithm $algorithm is not a valid elimination algorithm;
              choose among any of $(subtypes(Polyhedra.EliminationAlgorithm))")
    end

    mP, nP = size(A)
    mQ, nQ = size(C)
    E = [zeros(N, mP, nQ) A; C -C]
    f = [b; d]
    PQ = HPolyhedron(E, f)
    PQ_cdd = polyhedron(PQ, backend=backend)
    W = HPolyhedron(Polyhedra.eliminate(PQ_cdd, nP+1:2nP, algorithm))
    if prune
        success = remove_redundant_constraints!(W)
        if !success
            error("the constraints corresponding to the minkowski sum of the given " *
                  "sets are infeasible")
        end
    end
    return W
end

"""
    minkowski_sum(H1::AbstractHyperrectangle{N}, H2::AbstractHyperrectangle{N})
        where {N<:Real}

Concrete Minkowski sum of a pair of hyperrectangular sets.

### Input

- `H1` -- hyperrectangular set
- `H2` -- hyperrectangular set

### Output

A `Hyperrectangle` corresponding to the concrete Minkowski sum of `H1` and `H2`.

### Algorithm

The resulting hyperrectangle is obtained by summing up the centers and
radiuses of `H1` and `H2`.
"""
function minkowski_sum(H1::AbstractHyperrectangle{N},
                       H2::AbstractHyperrectangle{N}) where {N<:Real}
    return Hyperrectangle(center(H1) + center(H2),
                          radius_hyperrectangle(H1) + radius_hyperrectangle(H2))
end

"""
    minkowski_sum(Z1::AbstractZonotope{N}, Z2::AbstractZonotope{N})
        where {N<:Real}

Concrete Minkowski sum of a pair of zonotopic sets.

### Input

- `Z1` -- zonotopic set
- `Z2` -- zonotopic set

### Output

A `Zonotope` corresponding to the concrete Minkowski sum of `Z1` and `Z2`.

### Algorithm

The resulting zonotope is obtained by summing up the centers and concatenating
the generators of `Z1` and `Z2`.
"""
function minkowski_sum(Z1::AbstractZonotope{N},
                       Z2::AbstractZonotope{N}) where {N<:Real}
    return Zonotope(center(Z1) + center(Z2), [genmat(Z1) genmat(Z2)])
end


using Revise
using Distributions, Random
using Flux, Flux.Tracker
using RCall

@rimport graphics as rgraphics
@rimport rcommon

compute_ke(x) = x^2/2

function hmc(curr_state::Real, log_prob::Function, L::Integer, eps::Real)
  U(q) = -log_prob(q)
  grad_U(q) = Tracker.gradient(U, q)[1]

  q_history = zeros(L + 1)
  p_history = zeros(L + 1)

  q = curr_state
  p = randn()

  q_history[1] = q
  p_history[1] = p

  curr_K = compute_ke(p)
  curr_U = U(q)

  p -= eps * Tracker.data(grad_U(q)) / 2
  for i in 1:L
    q += eps * p
    q_history[i + 1] = q
    if i < L
      p -= eps * Tracker.data(grad_U(q))
      p_history[i + 1] = p
    end
  end
  p -= eps * Tracker.data(grad_U(q)) / 2
  p_history[L + 1] = p

  cand_U = U(q)
  cand_K = compute_ke(p)

  log_accept_ratio = curr_U + curr_K - cand_U - cand_K

  if log_accept_ratio > log(rand())
    q, lp = q, -cand_U
  else
    q, lp = curr_state, -curr_U
  end

  return Dict(:q => q, :log_prob => lp,
              :qhist => q_history, :phist => p_history)
end

### MAIN ###

function simulate(; L, eps, n, sd=.5, init=0.0, plot_bound=4)
  log_prob(x) = logpdf(Normal(0, sd), x)
  rgraphics.plot(0, xlab="q", ylab="p", typ="n",
                 ylim=[-1, 1]*plot_bound, xlim=[-sd, sd]*plot_bound)
  sim = hmc(init, log_prob, L, eps)
  qs = zeros(n + 1)
  qs[1] = sim[:q]

  for i in 1:n
    sim = hmc(sim[:q], log_prob, L, eps)
    rgraphics.lines(sim[:qhist], sim[:phist], typ="l")
    rgraphics.points(sim[:qhist][end], sim[:phist][end], pch=4, lwd=2)
    qs[i + 1] = sim[:q]
  end

  return qs
end

qs = simulate(L=2^4, eps=.1, n=30, sd=.5)
rcommon.plotPost(qs, typ="l", xlab="", ylab="", main="");

function simulate_gamma(; L, eps, n, sd=.5, init=0.0, plot_bound=4)
  shape = 2
  dist = Gamma(shape, sd/sqrt(shape))
  log_prob(logx) = logpdf(dist, exp(logx)) + logx
  rgraphics.par(mfrow=[2, 1])
  x = collect(range(0, sd*plot_bound, step=.01))
  rgraphics.plot(x, pdf.(dist, x), ylab="", xlab="", main="", typ="l")
  rgraphics.plot(0, xlab="q", ylab="p", typ="n",
                 ylim=[-1, 1]*plot_bound, xlim=[0, sd]*plot_bound)
  sim = hmc(init, log_prob, L, eps)
  qs = zeros(n + 1)
  qs[1] = sim[:q]

  for i in 1:n
    sim = hmc(sim[:q], log_prob, L, eps)
    rgraphics.lines(exp.(sim[:qhist]), sim[:phist], typ="l");
    rgraphics.points(exp(sim[:qhist][end]), sim[:phist][end], pch=4, lwd=2);
    # rgraphics.abline(v=[exp(sim[:qhist][end]), sim[:phist][end]], lty=2, lwd=.5)
    # rgraphics.points(exp(sim[:qhist][1]), sim[:phist][1], pch="$i", lwd=2)
    qs[i + 1] = sim[:q]
  end
  rgraphics.abline(v=mean(dist), lty=2, col="grey")
  rgraphics.par(mfrow=[1, 1])

  return qs
end

Random.seed!(3);
qs = simulate_gamma(L=2^4, eps=.1, n=10, sd=1, plot_bound=5)
rcommon.plotPost(exp.(qs), typ="l", xlab="", ylab="", main="");
_ = nothing

# sim = hmc(0.0, logx -> logpdf(Gamma(2, 3), exp(logx)) + logx, 10, .1)
# rgraphics.plot(sim[:qhist], main="", xlab="", ylab="")


#####
##### Smolyak implementation details
#####

####
#### Block sizes and shuffling
####

"""
$(SIGNATURES)

Length of each block `b`.

!!! note
    Smolyak grids use “blocks” of polynomials, each indexed by ``b == 0, …, B`, with an
    increasing number of points in each.
"""
@inline __block_length(b::Int) = b ≤ 1 ? b + 1 : 1 << (b - 1)

"""
$(SIGNATURES)

Cumulative block length at block `b`.
"""
@inline __cumulative_block_length(b::Int) = b == 0 ? 1 : ((1 << b) + 1)

"""
$(TYPEDEF)

An iterator of indices for picking elements from a grid of length `len`, which should be a
valid cumulative block length.
"""
struct SmolyakGridShuffle
    len::Int
end

Base.length(ι::SmolyakGridShuffle) = ι.len

Base.eltype(::Type{SmolyakGridShuffle}) = Int

function Base.iterate(ι::SmolyakGridShuffle)
    @unpack len = ι
    i = (len + 1) ÷ 2
    i, (0, 0)                   # step = 0 is special-cased
end

function Base.iterate(ι::SmolyakGridShuffle, (i, step))
    @unpack len = ι
    i == 0 && return len > 1 ? (1, (1, len - 1)) : nothing
    i′ = i + step
    if i′ ≤ len
        i′, (i′, step)
    else
        step′ = step ÷ 2
        if step′ ≥ 2
            i′ = step′ ÷ 2 + 1
            i′, (i′, step′)
        else
            nothing
        end
    end
end

####
#### index traversal
####

"""
$(SIGNATURES)

Internal implementation of the Smolyak indexing iterator.

# Arguments

- `M`: upper bound on each block index, remains constant during the iteration.

- `slack`: `B - sum(blocks)`, cached

- `indices`: current indices

- `blocks`: block indexes

- `limits`: `__cumulative_block_length.(blocks)`, cached

# Return values

- `valid::Bool`: `false` iff there is no next element, in which case the following values
  should be ignored

- `Δ::Int`: change in `slack`

- `indices′`, `blocks′, `limits′`: next values for corresponding arguments above, each an
  `::NTuple{N,Int}`
"""
@inline function __inc(cumulative_block_lengths::NTuple{M,Int}, slack::Int,
                       indices::NTuple{N,Int}, blocks::NTuple{N,Int},
                       limits::NTuple{N,Int}) where {M,N}
    i1, iτ... = indices
    b1, bτ... = blocks
    l1, lτ... = limits
    if i1 < l1                  # increment i1, same block
        true, 0, (i1 + 1, iτ...), blocks, limits
    elseif b1 < M && slack > 0  # increment i1, next block
        b1′ = b1 + 1
        true, -1, (i1 + 1, iτ...), (b1′, bτ...), (cumulative_block_lengths[b1′], lτ...)
    else
        if N == 1               # end of iteration, arbitrary value since !valid
            false, 0, indices, blocks, limits
        else                    # i1 = 1, increment tail if applicable
            Δ1 = b1
            valid, Δτ, iτ′, bτ′, lτ′ = __inc(cumulative_block_lengths, slack + Δ1, iτ, bτ, lτ)
            valid, Δ1 + Δτ, (1, iτ′...), (0, bτ′...), (__cumulative_block_length(0), lτ′...)
        end
    end
end

function __inc_init(::Val{N}, ::Val{B}) where {N,B}
    indices = ntuple(_ -> 1, Val(N))
    blocks = ntuple(_ -> 0, Val(N))
    l = __cumulative_block_length(0)
    limits = ntuple(_ -> l, Val(N))
    slack = B
    slack, indices, blocks, limits
end

"""
$(SIGNATURES)

Calculate the length of a [`SmolyakIndices`](@ref) iterator. Argument as in the latter.
"""
function __smolyak_length(::Val{N}, ::Val{B}, M::Int) where {N,B}
    # implicit assumption: M ≤ B
    c = zeros(MVector{B+1,Int}) # indexed as 0, …, B
    for b in 0:M
        c[b + 1] = __block_length(b)
    end
    for n in 2:N
        for b in B:(-1):0            # blocks with indices that sum to b
            s = 0
            for a in 0:min(b, M)
                s += __block_length(a) * c[b - a + 1]
            end
            # can safely overwrite since they will not be used again for n + 1
            c[b + 1] = s
        end
    end
    sum(c)
end


export generate_psf
export generate_downsample, my_interpolate
export center_extract, center_set!, get_indices_around_center, center_pos
export conv_psf, conv_otf, conv_otf_r, plan_conv_r

"""
    conv_psf(obj, psf [, dims])

Convolve `obj` with `psf` over `dims` dimensions.
Based on FFT convolution.
"""
function conv_psf(obj, psf, dims=[1, 2])
    return real.(ifft(fft(obj, dims) .* fft(psf, dims), dims))
end

"""
    conv_otf(obj, otf [, dims])

Performs a FFT-based convolution of an `obj`
with an `otf`. `otf = fft(psf)`. The 0 frequency of the `otf` must be located
at position [1, 1, 1].
The `obj` can be of arbitrary dimension but `ndims(psf) ≥ ndims(otf)`.
The convolution happens over the `dims` array. Any further dimensions are broadcasted.
Per default `dims = [1, 2]`.
"""
function conv_otf(obj, otf, dims=[1, 2])
    return real.(ifft(fft(obj, dims) .* otf, dims))
end

"""
    conv_otf_r(obj, otf [, dims])

Performs a FFT-based convolution of an `obj`
with an `otf`.
Same arguments as `conv_otf` but with `obj` being real and `otf=rfft(psf)`.
"""
function conv_otf_r(obj, otf, dims=[1, 2])
    return real.(irfft(rfft(obj, dims) .* otf, size(obj)[dims[1]], dims))
end

"""
    plan_conv_r(psf [, dims])

Pre-plan an optimized convolution for array shaped like `psf` (based on pre-plan FFT)
along the given dimenions `dims`.
`dims = [1, 2]` per default.
The 0 frequency of `psf` must be located at [1, 1, 1].
We return first the `otf` (obtained by `rfft(psf))`.
The second return is the convolution function `conv`.
`conv` itself has two arguments. `conv(obj, otf)` where `obj` is the object and `otf` the otf.

This function achieves faster convolution than `conv_psf(obj, psf)`.
"""
function plan_conv_r(psf, rec0, dims=[1, 2])
    # do the preplanning step
    P = plan_rfft(rec0, dims)
    rec0_fft = P * rec0
    P_inv = plan_irfft(rec0_fft, size(psf)[dims[1]], dims)

    # obtain the otf by real based fft
    otf = rfft(psf, dims)
    # construct the efficient conv function
    conv(obj, otf) = real.(P_inv * ((P * obj) .* otf))

    return otf, conv
end

"""
    generate_downsample(num_dim, downsample_dims, factor)

Generate a function (based on Tullio.jl) which can be used to downsample arrays.
`num_dim` (Integer) are the dimensions of the array.
`downsample_dims` is a list of which dimensions should be downsampled.
`factor` is a downsampling factor. It needs to be an integer number.

# Examples
```jldoctest
julia> ds = generate_downsample(2, [1, 2], 2)
[...]
julia> ds([1 2; 3 4; 5 6; 7 8])
2×1 Array{Float64,2}:
 2.5
 6.5

julia> ds = generate_downsample(2, [1], 2)
[...]
julia> ds([1 2; 3 5; 5 6; 7 8])
2×2 Array{Float64,2}:
 2.0  3.5
 6.0  7.0
```
"""
function generate_downsample(num_dim, downsample_dims, factor)
    @assert num_dim ≥ length(downsample_dims)
    # create unit cell with Cartesian Index
    # dims_units containts every where a 1 where the downsampling should happen
    dims_units = zeros(Int, num_dim)
    # here we set which dimensions should be downsamples
    dims_units[downsample_dims] .= 1
    # the unit cell expressed in CartesianIndex
    one = CartesianIndex(dims_units...)

    # create a list of symbols
    # these list represents the symbols to access the arrays
    ind = :i
    inds_out = map(1:num_dim) do di
        i = Symbol(ind, di)
    end

    # output list for the add commands
    add = []
    # via CartesianIndex we can loop over all rectangular neighbours
    # we loop only over the neighbours in the downsample_dims
    for n = one:one * factor
        # for each index calculate the offset to the neighbour
        inds = map(1:num_dim) do di
                i = Symbol(ind, di)
                if n[di] == 0
                    di = i
                else
                    expr = :($factor * $i)
                    diff = -factor + n[di]
                    di = :($expr + $diff)
                end
            end
        # push this single neighbour to add list
        push!(add, :(arr[$(inds...)]))
    end
    # combine the different parts and divide for averaging
    expr = [:(@tullio res[$(inds_out...)] := (+($(add...))) / $factor ^ $(length(downsample_dims)))]
    #= return expr =#
    # evaluate to function
    @eval f = arr -> ($(expr...))
    return f
end

"""
    my_interpolate(arr, size_n, [interp_type])

Interpolates `arr` to the sizes provided in `size_n`.
Therefore it holds `ndims(arr) == length(size_n)`.
`interp_type` specifies the interpolation type.
See Interpolations.jl for all options
"""
function my_interpolate(arr, size_n, interp_type=BSpline(Linear()))

    # we construct a arr which includes the interpolation
    # type for each dimension
    interp = []
    for s in size_n
        # if the outpute size is of the s-th dimension=1,
        # do NoInterp
        if s == 1
            push!(interp, NoInterp())
        else
            push!(interp, interp_type)
        end
    end
    # prepare the interpolation
    arr_n = interpolate(arr, Tuple(interp))

    # interpolate introduces fractional indices
    # via LinRange we access these fractional indices
    inds = []
    for d = 1:ndims(arr)
        push!(inds, LinRange(1, size(arr)[d], size_n[d]))
    end

    # return the new array sampled at the positions of inds
    # this accessing actually interpolates the data
    return arr_n(inds...)
end

"""
    get_indices_around_center(i_in, i_out)
A function which provides two output indices `i1` and `i2`
where `i2 - i1 = i_out`
The indices are chosen in a way that the set `i1:i2`
cuts the interval `1:i_in` in a way that the center frequency
stays at the center position.
Works for both odd and even indices
"""
function get_indices_around_center(i_in, i_out)
    if (mod(i_in, 2) == 0 && mod(i_out, 2) == 0
     || mod(i_in, 2) == 1 && mod(i_out, 2) == 1)
        x = (i_in - i_out) ÷ 2
        return 1 + x, i_in - x
    elseif mod(i_in, 2) == 1 && mod(i_out, 2) == 0
        x = (i_in - 1 - i_out) ÷ 2
        return 1 + x, i_in - x - 1
    elseif mod(i_in, 2) == 0 && mod(i_out, 2) == 1
        x = (i_in - (i_out - 1)) ÷ 2
        return 1 + x, i_in - (x - 1)
    end
end

"""
    center_extract(arr, new_size_array)
Extracts a center of an array.
`new_size_array` must be list of sizes indicating the output
size of each dimension. Centered means that a center frequency
stays at the center position. Works for even and uneven.
If `length(new_size_array) < length(ndims(arr))` the remaining dimensions
are untouched and copied.
# Examples
```jldoctest
julia> DeconvOptim.center_extract([1 2; 3 4], [1])
1×2 Array{Int64,2}:
 3  4
julia> DeconvOptim.center_extract([1 2; 3 4], [1, 1])
1×1 Array{Int64,2}:
 4
julia> DeconvOptim.center_extract([1 2 3; 3 4 5; 6 7 8], [2 2])
2×2 Array{Int64,2}:
 1  2
 3  4
```
"""
function center_extract(arr::AbstractArray, new_size_array)
    new_size_array = collect(new_size_array)

    # we construct two lists
    # the reason is, that we don't change higher dimensions which are not
    # specified in new_size_array
    out_indices1 = [get_indices_around_center(size(arr)[x], new_size_array[x])
                    for x = 1:length(new_size_array)]

    out_indices1 = [x[1]:x[2] for x = out_indices1]

    # out_indices2 contains just ranges covering the full size of each dimension
    out_indices2 = [1:size(arr)[i] for i = (1 + length(new_size_array)):ndims(arr)]
    return arr[out_indices1..., out_indices2...]
end

"""
    center_set!(arr_large, arr_small)
Puts the `arr_small` central into `arr_large`.
The convention, where the center is, is the same as the definition
as for FFT based centered.
Function works both for even and uneven arrays.
# Examples
```jldoctest
julia> DeconvOptim.center_set!([1, 1, 1, 1, 1, 1], [5, 5, 5])
6-element Array{Int64,1}:
 1
 1
 5
 5
 5
 1
```
"""
function center_set!(arr_large, arr_small)
    out_is = []
    for i = 1:ndims(arr_large)
        a, b = get_indices_around_center(size(arr_large)[i], size(arr_small)[i])
        push!(out_is, a:b)
    end

    #rest = ones(Int, ndims(arr_large) - 3)
    arr_large[out_is...] = arr_small

    return arr_large
end

"""
    center_pos(x)
Calculate the position of the center frequency.
Size of the array is `x`
# Examples
```jldoctest
julia> DeconvOptim.center_pos(3)
2
julia> DeconvOptim.center_pos(4)
3
```
"""
function center_pos(x::Integer)
    # integer division
    return div(x, 2) + 1
end

"""
    generate_psf(psf_size, radius)

Generation of an approximate 2D PSF.
`psf_size` is the output size of the PSF. The PSF will be centered
around the point [1, 1],
`radius` indicates the pupil diameter in pixel from which the PSF is generated.

# Examples
```julia-repl
julia> generate_psf([5, 5], 2)
5×5 Array{Float64,2}:
 0.36       0.104721    0.0152786    0.0152786    0.104721
 0.104721   0.0304627   0.00444444   0.00444444   0.0304627
 0.0152786  0.00444444  0.000648436  0.000648436  0.00444444
 0.0152786  0.00444444  0.000648436  0.000648436  0.00444444
 0.104721   0.0304627   0.00444444   0.00444444   0.0304627
```
"""
function generate_psf(psf_size, radius)
    mask = rr_2D(psf_size) .<= radius
    mask_ft = fft(mask)
    psf = abs2.(mask_ft)
    return psf ./ sum(psf)
end

function rr_3D(s)
    rarr = zeros((s...))
    for k = 1:s[3]
        for j = 1:s[2]
            for i = 1:s[1]
                rarr[i, j, k] = sqrt( (i-center_pos(s[1]))^2 + (j-center_pos(s[2]))^2 + (k-center_pos(s[3]))^2)
            end
        end
    end
    return rarr
end

"""
    rr_2D(s)

Generate a image with values being the distance to the center pixel.
`s` specifies the output size of the 2D array.

# Examples
```julia-repl
julia> DeconvOptim.rr_2D((6, 6))
6×6 Array{Float64,2}:
 4.24264  3.60555  3.16228  3.0  3.16228  3.60555
 3.60555  2.82843  2.23607  2.0  2.23607  2.82843
 3.16228  2.23607  1.41421  1.0  1.41421  2.23607
 3.0      2.0      1.0      0.0  1.0      2.0
 3.16228  2.23607  1.41421  1.0  1.41421  2.23607
 3.60555  2.82843  2.23607  2.0  2.23607  2.82843
```
"""
function rr_2D(s)
    rarr = zeros((s...))
    for j = 1:s[2]
        for i = 1:s[1]
               rarr[i, j] = sqrt( (i - center_pos(s[1]))^2 + (j - center_pos(s[2]))^2)
        end
    end
    return rarr
end


using ArrayInterface, Test
import ArrayInterface: has_sparsestruct, findstructralnz
@test ArrayInterface.ismutable(rand(3))

using StaticArrays
@test ArrayInterface.ismutable(@SVector [1,2,3]) == false
@test ArrayInterface.ismutable(@MVector [1,2,3]) == true

using LinearAlgebra, SparseArrays

D=Diagonal([1,2,3,4])
@test has_sparsestruct(D)
rowind,colind=findstructralnz(D)
@test [D[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,2,3,4]
@test length(rowind)==4
@test length(rowind)==length(colind)

Bu = Bidiagonal([1,2,3,4], [7,8,9], :U)
@test has_sparsestruct(Bu)
rowind,colind=findstructralnz(Bu)
@test [Bu[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,7,2,8,3,9,4]
Bl = Bidiagonal([1,2,3,4], [7,8,9], :L)
@test has_sparsestruct(Bl)
rowind,colind=findstructralnz(Bl)
@test [Bl[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,7,2,8,3,9,4]

Tri=Tridiagonal([1,2,3],[1,2,3,4],[4,5,6])
@test has_sparsestruct(Tri)
rowind,colind=findstructralnz(Tri)
@test [Tri[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,2,3,4,4,5,6,1,2,3]
STri=SymTridiagonal([1,2,3,4],[5,6,7])
@test has_sparsestruct(STri)
rowind,colind=findstructralnz(STri)
@test [STri[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,2,3,4,5,6,7,5,6,7]

Sp=sparse([1,2,3],[1,2,3],[1,2,3])
@test has_sparsestruct(Sp)
rowind,colind=findstructralnz(Sp)
@test [Tri[rowind[i],colind[i]] for i in 1:length(rowind)]==[1,2,3]


@testset "complete substitution" begin
    @polyvar x y z

    p = x^2 * y + y^2 + x
    @test p(x=>1, y=>2) == 7
    @inferred p(x=>1, y=>2)

    subs1(p, x, y) = @allocated p(x=>1, y=>2)
    subs1(p, x, y)
    @test subs1(p, x, y) == 0

    @test (@inferred x(x=>5)) == 5
    @test (@inferred x(y=>2)) == x
    @test (@inferred x(x=>y)) == y
end

@testset "partial substitution" begin
    @polyvar x y z

    p = x^2 * y + y^2 + x
    p2 = @inferred p(x=>4)
    @test length(terms(p2)) == 3
    @test terms(p2)[1].coefficient == 4
    @test degree(terms(p2)[1].monomial) == 0
    @test terms(p2)[2].coefficient == 16
    @test degree(terms(p2)[2].monomial) == 1
    @test exponents(monomial(terms(p2)[2])) == (1,)
    @test terms(p2)[3].coefficient == 1
    @test degree(terms(p2)[3].monomial) == 2
    @test exponents(monomial(terms(p2)[3])) == (2,)
end


# Reference: Cormen, p. 18
# Notes: I ran into the problem of Julia's parametric typing system, because I
# wanted the algorithm to work on any array of real numbers. This meant I wanted
# concrete subtypes of Int and AbstractFloat to work correctly. Naively, I wrote
# the function signature:
#    insertionsort(A::Array{Real, 1})
# but this fails because, for example,
#    Array{Int64, 1} <: Array{Real, 1} => false
# I think that this is due to compiler optimization, if I remember the lecture I
# watched correctly. Regardless, for parametric type abstraction for methods,
# the correct syntax is given below:
function insertionsort{T<:Real}(A::Array{T,1})
    for j = 2:length(A)
        key = A[j]
        # Insert A[j] into the sorted sequence A[1..j-1].
        i = j - 1
        while i > 0 && A[i] > key
            A[i+1] = A[i]
            i -= 1
        end
        A[i+1] = key
    end
end

# Reference: Cormen, p. 34
function mergesort{T<:Real}(A::Array{T,1})
    # Reference: Cormen, p. 31
    # This was more straightforward to implement, but I developed a bit of a
    # "hacked" solution for introducing the sentinel '∞' that is used in the
    # textbook. I essentially set ∞ to the maximum allowable value for the
    # specified type, so it acts like infinity for every practical purpose.
    function merge{T<:Real}(A::Array{T,1}, p::Int, q::Int, r::Int)
        ∞ = typemax(T) - 1
        n₁, n₂ = q - p + 1, r - q
        L, R = zeros(T,n₁+1), zeros(T, n₂+1)
        L[1:end-1] = A[p:p+n₁-1]
        R[1:end-1] = A[q+1:q+n₂]
        L[n₁+1], R[n₂+1] = ∞, ∞
        i, j = 1, 1
        for k = p:r
            if L[i] <= R[j]
                A[k] = L[i]
                i += 1
            else
                A[k] = R[j]
                j += 1
            end
        end
    end

    function kernel{T<:Real}(A::Array{T,1}, p::Int, r::Int)
        if p < r
            q = Int(floor((p + r) / 2))
            kernel(A, p, q)
            kernel(A, q + 1, r)
            merge(A, p, q, r)
        end
    end

    kernel(A, 1, length(A))
end

# Reference: Cormen, p. 161
function heapsort{T<:Real}(A::Array{T,1})
    # Reference: Cormen, p. 152
    parent(i::Int) = Int(floor(i/2))
    leftchild(i::Int) = 2i
    rightchild(i::Int) = 2i + 1

    # Reference: Cormen, p. 154
    # The modification from the book is that maxheapify also takes an additional
    # parameter, 'size', which gives the size of the heap.
    function maxheapify{T<:Real}(A::Array{T,1}, i::Int, size::Int)
        # get the children of i
        l, r = leftchild(i), rightchild(i)

        # check if the left child exists and is greater than i
        if l <= size && A[l] > A[i]
            largest = l
        else
            largest = i
        end

        # check if the right child exists and is greater than i
        if r <= size && A[r] > A[largest]
            largest = r
        end

        # if one of the children is greater than the node A[i], swap them and
        # recurse
        if largest != i
            A[i], A[largest] = A[largest], A[i]
            maxheapify(A, largest, size)
        end
    end

    # Reference: Cormen, p. 157
    # The modification made here is that the heap size is calculated and
    # returned instead of being made an attribute of A. This is to avoid making
    # new types.
    function buildmaxheap{T<:Real}(A::Array{T, 1})
        size = length(A)
        for i = Int(floor(length(A)/2)):-1:1
            maxheapify(A, i, size) end
        size
    end

    # The actual heap sort is down here:
    size = buildmaxheap(A)
    for i = length(A):-1:2
        A[1], A[i] = A[i], A[1]
        size -= 1
        maxheapify(A, 1, size)
    end
end

function quicksort{T<:Real}(A::Array{T,1})

    # Reference: Cormen, p. 171
    function partition{T<:Real}(A::Array{T,1}, p::Int, r::Int)
        x = A[r]
        i = p - 1
        for j = p:-1:(r-1)
            if A[j] <= x
                i += 1
                A[i], A[j] = A[j], A[i]
            end
        end
        A[i+1], A[r] = A[r], A[i+1]

        i + 1
    end

    # Reference: Cormen, p. 179
    function randomizedpartition{T<:Real}(A::Array{T,1}, p::Int, r::Int)
        i = rand(p:r)
        A[r], A[i] = A[i], A[r]
        partition(A, p, r)
    end

    # Reference: Cormen, p. 171
    function kernel{T<:Real}(A::Array{T,1}, p::Int, r::Int)
        if p < r
            q = randomizedpartition(A, p, r)
            kernel(A, p, q - 1)
            kernel(A, q + 1, r)
        end
    end

    kernel(A, 1, length(A))
end

function countingsort{T<:Real}(A::Array{T,1}, B::Array{T,1}, k::Int)
    C = zeros(k+1)
    for j = 1:length(A)
        C[A[j]] += 1
    end
    for i = 2:(k+1)
        C[i] = C[i] + C[i-1]
    end
    for j = length(A):-1:1
        B[C[A[j]]] = A[j]
        C[A[j]] = C[A[j]] - 1
    end
end


using Test
using TestExtras
using Random
using TensorKit
using Combinatorics
using TensorKit: ProductSector, fusiontensor
using TensorOperations
TensorOperations.disable_cache() # avoids memory overflow during CI?
using Base.Iterators: take, product
# using SUNRepresentations: SUNIrrep
# const SU3Irrep = SUNIrrep{3}
import LinearAlgebra

include("newsectors.jl")
using .NewSectors

const TK = TensorKit

Random.seed!(1234)

smallset(::Type{I}) where {I<:Sector} = take(values(I), 5)
function smallset(::Type{ProductSector{Tuple{I1,I2}}}) where {I1,I2}
    iter = product(smallset(I1), smallset(I2))
    s = collect(i ⊠ j for (i,j) in iter if dim(i)*dim(j) <= 6)
    return length(s) > 6 ? rand(s, 6) : s
end
function smallset(::Type{ProductSector{Tuple{I1,I2,I3}}}) where {I1,I2,I3}
    iter = product(smallset(I1), smallset(I2), smallset(I3))
    s = collect(i ⊠ j ⊠ k for (i,j,k) in iter if dim(i)*dim(j)*dim(k) <= 6)
    return length(s) > 6 ? rand(s, 6) : s
end
function randsector(::Type{I}) where {I<:Sector}
    s = collect(smallset(I))
    a = rand(s)
    while a == one(a) # don't use trivial label
        a = rand(s)
    end
    return a
end
function hasfusiontensor(I::Type{<:Sector})
    try
        fusiontensor(one(I), one(I), one(I))
        return true
    catch e
        if e isa MethodError
            return false
        else
            rethrow(e)
        end
    end
end

sectorlist = (Z2Irrep, Z3Irrep, Z4Irrep, U1Irrep, CU1Irrep, SU2Irrep, NewSU2Irrep,# SU3Irrep,
              FibonacciAnyon, IsingAnyon, Z3Irrep ⊠ Z4Irrep, U1Irrep ⊠ SU2Irrep, SU2Irrep ⊠
              SU2Irrep, NewSU2Irrep ⊠ NewSU2Irrep, NewSU2Irrep ⊠ SU2Irrep, SU2Irrep ⊠
              NewSU2Irrep, Z2Irrep ⊠ FibonacciAnyon ⊠ FibonacciAnyon)

Ti = time()
include("sectors.jl")
include("fusiontrees.jl")
include("spaces.jl")
include("tensors.jl")
Tf = time()
printstyled("Finished all tests in ",
            string(round((Tf-Ti)/60; sigdigits=3)),
            " minutes."; bold = true, color = Base.info_color())
println()


# This example is used to generate plots in the paper, "Reflection from a multi-species material and its transmitted effective wavenumber." Proc. R. Soc. (2018): 20170864.

# Everything related to ploting has been commented so that this example can run with requiring the Plots package. That way, this example can be checked everytime the package is modified.

using EffectiveWaves
# using LaTeXStrings

## choose material

  # concrete
  medium = LimeStone
  inclusion1 = AirDry
  inclusion2 = Brick

  ωfactor = 50.0;
  ωs = ωfactor.*LinRange(real(medium.c/10000),real(medium.c),100) # k from 0 to 1

  volfrac = 0.16
  r1 = 0.1/ωfactor; vol1 = 0.06
  r2 = 1.0/ωfactor; vol2 = volfrac - vol1

  sp1 = Specie(ρ=inclusion1.ρ ,r=r1, c=inclusion1.c, volfrac = vol1)
  sp2 = Specie(ρ=inclusion2.ρ ,r=r2, c=inclusion2.c, volfrac = volfrac-vol1)

  # True low partilce volume fraction wavenumber
  kTs  = wavenumber_low_volumefraction(ωs, medium, [sp1,sp2]);
  # Approximate wavenumber
  kTLSs = two_species_approx_wavenumber(ωs, medium, [sp1,sp2]);
  # Approximate challis
  kTCs = wavenumber_challis(ωs, medium, [sp1,sp2]);

  kTs_arr = [kTs,kTLSs,kTCs];
  speed_arr = [ ωs./real(ks) for ks in kTs_arr];
  push!(speed_arr, 0 .*ωs .+ real(medium.c))
  atten_arr = imag.(kTs_arr)

  # styles = [:solid :dashdot :dashdot :dot]
  # labs = [L"Effective $k_{*}$" L"Approximate $k_{*LS}$" L"Approximate $k_{*C}$" "Lime Stone"]
  ys_arr = speed_arr;
  xs = r1.*(ωs./real(medium.c));
  m =5;
  y1 = min(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  y2 = max(ys_arr[1][1:m]..., ys_arr[2][1:m]...);

  # using Plots
  # height=500
  # pyplot(linewidth=3, size=(2.6*height,height), border=false)

  # Plots.scalefontsizes(1.7)
  # plot(xs, ys_arr, xlabel=L"a_S k", ylabel="sound speed (m/s)", labels=labs, line = styles, xlims = (-0.002,maximum(xs))
        # , ylims = ( min(ys_arr[1]...,ys_arr[3]...,ys_arr[4]...)*0.995,  max(ys_arr[1]...,ys_arr[3]...,ys_arr[4]...)*1.005));
  # p1 = gray_square([xs[1],xs[m]],[y1,y2],plot!);

  ys_arr = atten_arr;
  # labs = [L"Effective $k_{*}$" L"Approximate $k_{*LS}$" L"Approximate $k_{*C}$"]
  # plot(xs, ys_arr, labels=labs, xlabel=L"a_S k", ylabel="attenuation (1/m)", line=styles, xlims = (-0.002,maximum(xs))
        # , ylims = ( min(ys_arr[1]...,ys_arr[3]...)*0.995,  max(ys_arr[1]...,ys_arr[3]...)*1.005));
  y1 = min(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  y2 = max(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  # p2 = gray_square([xs[1],xs[m]],[y1,y2],plot!);

  # plot(p1,p2)
  # try mkdir("media") end
  # savefig("media/compare_concrete.png")
  # savefig("media/compare_concrete.pdf")
  # gui()

## Zoomed in version
  ωs = LinRange(ωs[1],ωs[m],250) # k from 0 to 1
  m = length(ωs);
  kTs  = wavenumber_low_volumefraction(ωs, medium, [sp1,sp2]);
  kTLSs = two_species_approx_wavenumber(ωs, medium, [sp1,sp2]);
  kTCs  = wavenumber_challis(ωs, medium, [sp1,sp2]);

  kTs_arr = [kTs,kTLSs,kTCs];
  speed_arr = [ ωs./real(ks) for ks in kTs_arr];
  atten_arr = imag.(kTs_arr)

  # labs = [L"Effective $k_{*}$" L"Approximate $k_{*LS}$" L"Approximate $k_{*C}$"]

  ys_arr = speed_arr;
  xs = r1.*(ωs./real(medium.c));
  # plot(xs, ys_arr, xlabel=L"a_S k", ylabel="sound speed (m/s)", labels=labs
  #                , border = false, line = styles, xlims = (0,maximum(xs)));
  y1 = min(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  y2 = max(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  # p1 = gray_square([xs[1],xs[m]],[y1,y2],plot!);

  ys_arr = atten_arr;
  # labs = [L"Effective $k_{*}$" L"Approximate $k_{*LS}$" L"Approximate $k_{*C}$"]
  # plot(xs, ys_arr, xlabel=L"a_S k", ylabel="attenuation (1/m)", labels=labs
  #                , border=false, line = styles, xlims = (0,maximum(xs)));
  y1 = min(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  y2 = max(ys_arr[1][1:m]..., ys_arr[2][1:m]...);
  dy = abs(y2- y1)/120.0;
  # p2 = gray_square([xs[1],xs[m]],[y1,y2],plot!);

#   plot(p1,p2)
#   savefig("media/compare_concrete_zoom.png")
#   savefig("media/compare_concrete_zoom.pdf")
#
# Plots.scalefontsizes(1/1.7)