function frqtr!(wc::AbstractVector, c::AbstractVector, α,
                prev::Vector=Array{eltype(wc),1}(length(wc)))
    fill!(wc, zero(eltype(wc)))
    dst_order = length(wc) - 1

    m1 = length(c)-1
    for i in -m1:0
        copy!(prev, wc)
        if dst_order >= 0
            @inbounds wc[1] = c[-i+1]
        end
        for m=2:dst_order+1
            @inbounds wc[m] = prev[m-1] + α*(prev[m] - wc[m-1])
        end
    end

    wc
end

function frqtr(c::AbstractVector, order=25, α=0.35)
    frqtr!(Array{eltype(c),1}(order+1), c, α)
end


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

using Markdown
using InteractiveUtils

# ╔═╡ 822b4836-fe75-11ea-161f-35d589ab833f
using Pkg, DrWatson

# ╔═╡ 822b886e-fe75-11ea-3fd5-313bd9474e67
begin
	#@quickactivate "StatisticalRethinkingStan"
	using StanQuap
	using StatisticalRethinking
end

# ╔═╡ 416556d6-fe6e-11ea-338a-fb39b7c0f00f
md"## Clip-06-08s.jl"

# ╔═╡ 822c0d84-fe75-11ea-27b2-cb1e8fa46fb1
begin
	df = CSV.read(sr_datadir("milk.csv"), DataFrame; delim=';');
	scale!(df, [:kcal_per_g, :perc_fat, :perc_lactose])
end;

# ╔═╡ 823966f0-fe75-11ea-3c37-25437bd882cd
stan6_3 = "
data{
  int <lower=1> N;              // Sample size
  vector[N] K;
  vector[N] F;
}
parameters{
  real a;
  real bF;
  real<lower=0> sigma;
}
model{
  vector[N] mu;
  sigma ~ exponential( 1 );
  a ~ normal( 0 , 0.2 );
  bF ~ normal( 0 , 0.5 );
  mu = a + bF * F;
  K ~ normal( mu , sigma );
}
";

# ╔═╡ 82435d7e-fe75-11ea-3f52-7daa1879898d
begin
	data = (N = size(df, 1), F = df.perc_fat_s, K = df.kcal_per_g_s)
	init = (a=0.0, bF=1.0, sigma=1.0)
	q6_3s, m6_3s, o6_3s = stan_quap("m6.3s", stan6_3; data, init);
	if !isnothing(m6_3s)
		post6_3s_df = read_samples(m6_3s, :dataframe)
		PRECIS(post6_3s_df)
	end
end

# ╔═╡ 82456bc6-fe75-11ea-3e9a-256e1fb9844f
!isnothing(m6_3s) && (part6_3s = Particles(post6_3s_df))

# ╔═╡ 82509064-fe75-11ea-16cb-e506f49efd72
if !isnothing(q6_3s)
	quap6_3s_df = sample(q6_3s)
	PRECIS(quap6_3s_df)
end

# ╔═╡ 8251194e-fe75-11ea-1403-d743608a5169
hpdi(part6_3s.bF.particles, alpha=0.11)

# ╔═╡ 825cb330-fe75-11ea-2df5-a92dca779683
md"## End of clip-06-08s.jl"

# ╔═╡ Cell order:
# ╟─416556d6-fe6e-11ea-338a-fb39b7c0f00f
# ╠═822b4836-fe75-11ea-161f-35d589ab833f
# ╠═822b886e-fe75-11ea-3fd5-313bd9474e67
# ╠═822c0d84-fe75-11ea-27b2-cb1e8fa46fb1
# ╠═823966f0-fe75-11ea-3c37-25437bd882cd
# ╠═82435d7e-fe75-11ea-3f52-7daa1879898d
# ╠═82456bc6-fe75-11ea-3e9a-256e1fb9844f
# ╠═82509064-fe75-11ea-16cb-e506f49efd72
# ╠═8251194e-fe75-11ea-1403-d743608a5169
# ╟─825cb330-fe75-11ea-2df5-a92dca779683


# Copyright (c) 2019 Yury Dvorkin, Miles Lubin, and contributors                #src
#                                                                               #src
# This work is licensed under a Creative Commons Attribution-ShareAlike 4.0     #src
# International License. URL: http://creativecommons.org/licenses/by-sa/4.0.    #src

# # Power Systems

# **Originally Contributed by**: Yury Dvorkin and Miles Lubin

# This tutorial demonstrates how to formulate basic power systems engineering
# models in JuMP using a 3 bus example.

# We will consider basic "economic dispatch" and "unit commitment" models
# without taking into account transmission constraints.

# ## Illustrative example

# In the following notes for the sake of simplicity, we are going to use a three
# bus example mirroring the interface between Western and Eastern Texas. This
# example is taken from R. Baldick, "[Wind and Energy Markets: A Case Study of Texas](http://dx.doi.org/10.1109/JSYST.2011.2162798),"
# IEEE Systems Journal, vol. 6, pp. 27-34, 2012.

# ![Power Systems](../../assets/power_systems.png)

# For this example, we set the following characteristics of generators,
# transmission lines, wind farms and demands:

# | Quantity         | Generator 1 | Generator 2 |
# |:-----------------|:-----------:|------------:|
# | $g_{min}$, MW    | 0           | 300         |
# | $g_{max}$, MW    | 1000        | 1000        |
# | $c^g$, \$/MWh    | 50          | 100         |
# | $c^{g0}$, \$/MWh | 1000        | 0           |

# | Quantity      | Line 1 | Line 2 |
# |:--------------|:------:|-------:|
# | $f^{max}$, MW | 100    | 1000   |
# | $x$, p.u.     | 0.001  | 0.001  |

# | Quantity        | Wind farm 1 | Wind farm 2 |
# |:----------------|:-----------:|------------:|
# | $w^{f}$, MW     | 150         | 50          |
# | $c^{w}$, \$/MWh | 50          | 50          |

# | Quantity | Bus 1 | Bus 2 | Bus 3 |
# |:---------|:-----:|:-----:|------:|
# | $d$, MW  | 0     | 0     | 15000 |

# ## Economic dispatch

# Economic dispatch (ED) is an optimization problem that minimizes the cost of
# supplying energy demand subject to operational constraints on power system
# assets. In its simplest modification, ED is an LP problem solved for an
# aggregated load and wind forecast and for a single infinitesimal moment.

# Mathematically, the ED problem can be written as follows:

# ```math
# \min \sum_{i \in I} c^g_{i} \cdot g_{i} + c^w \cdot w,
# ```

# where $c_{i}$ and $g_{i}$ are the incremental cost (\$/MWh) and power output
# (MW) of the $i^{th}$ generator, respectively, and $c^w$ and $w$ are the
# incremental cost (\$/MWh) and wind power injection (MW), respectively.

# Subject to the constraints:

# * Minimum ($g^{\min}$) and maximum ($g^{\max}$) limits on power outputs of
#   generators:
#   $g^{\min}_{i} \leq g_{i} \leq g^{\max}_{i}.$
# * Constraint on the wind power injection:
#   $0 \leq w \leq w^f,$
#   where $w$ and $w^f$ are the wind power injection and wind power forecast,
#   respectively.
# * Power balance constraint:
#   $\sum_{i \in I} g_{i} + w = d^f,$
#   where $d^f$ is the demand forecast.

# Further reading on ED models can be found in A. J. Wood, B. F. Wollenberg, and
# G. B. Sheblé, "Power Generation, Operation and Control", Wiley, 2013.

# ## JuMP Implementation of Economic Dispatch

using JuMP
import DataFrames
import GLPK

# Define some input data about the test system.

## Maximum power output of generators
g_max = [1000, 1000]
## Minimum power output of generators
g_min = [0, 300]
## Incremental cost of generators
c_g = [50, 100]
## Fixed cost of generators
c_g0 = [1000, 0]
## Incremental cost of wind generators
c_w = 50
## Total demand
d = 1500
## Wind forecast
w_f = 200
nothing #hide

# Create a function solve_ed, which solves the economic dispatch problem for a
# given set of input parameters.
function solve_ed(g_max, g_min, c_g, c_w, d, w_f)
    ## Define the economic dispatch (ED) model
    ed = Model(GLPK.Optimizer)
    ## Define decision variables
    @variable(ed, 0 <= g[i = 1:2] <= g_max[i]) ## power output of generators
    @variable(ed, 0 <= w <= w_f) ## wind power injection
    ## Define the objective function
    @objective(ed, Min, c_g' * g + c_w * w)
    ## Define the constraint on the maximum and minimum power output of each
    ## generator
    @constraint(ed, [i = 1:2], g[i] <= g_max[i]) ## maximum
    @constraint(ed, [i = 1:2], g[i] >= g_min[i]) ## minimum
    ## Define the constraint on the wind power injection
    @constraint(ed, w <= w_f)
    ## Define the power balance constraint
    @constraint(ed, sum(g) + w == d)
    ## Solve statement
    optimize!(ed)
    ## return the optimal value of the objective function and its minimizers
    return value.(g), value(w), w_f - value(w), objective_value(ed)
end

# Solve the economic dispatch problem

(g_opt, w_opt, ws_opt, obj) = solve_ed(g_max, g_min, c_g, c_w, d, w_f);

println("Dispatch of Generators: ", g_opt, " MW")
println("Dispatch of Wind: ", w_opt, " MW")
println("Wind spillage: ", w_f - w_opt, " MW")
println("\n")
println("Total cost: ", obj, "\$")

# ### Economic dispatch with adjustable incremental costs

# In the following exercise we adjust the incremental cost of generator G1 and
# observe its impact on the total cost.

c_g_scale_df = DataFrames.DataFrame(
    Symbol("Dispatch of Generator 1(MW)") => Float64[],
    Symbol("Dispatch of Generator 2(MW)") => Float64[],
    Symbol("Dispatch of Wind(MW)") => Float64[],
    Symbol("Spillage of Wind(MW)") => Float64[],
    Symbol("Total cost(\$)") => Float64[],
)
for c_g1_scale = 0.5:0.1:3.0
    ## Update the incremental cost of the first generator at every iteration.
    c_g_scale = [c_g[1] * c_g1_scale, c_g[2]]
    ## Solve the ed problem with the updated incremental cost
    g_opt, w_opt, ws_opt, obj = solve_ed(g_max, g_min, c_g_scale, c_w, d, w_f)
    push!(c_g_scale_df, (g_opt[1], g_opt[2], w_opt, ws_opt, obj))
end

#-

ENV["COLUMNS"]=250 #hide

c_g_scale_df

# ## Modifying the JuMP model in place

# Note that in the previous exercise we entirely rebuilt the optimization model
# at every iteration of the internal loop, which incurs an additional
# computational burden. This burden can be alleviated if instead of re-building
# the entire model, we modify a specific constraint(s) or the objective
# function, as it shown in the example below.

# Compare the computing time in case of the above and below models.

function solve_ed_inplace(c_w_scale)
    start = time()
    obj_out = Float64[]
    w_out = Float64[]
    g1_out = Float64[]
    g2_out = Float64[]
    ed = Model(GLPK.Optimizer)
    ## Define decision variables
    @variable(ed, 0 <= g[i = 1:2] <= g_max[i]) ## power output of generators
    @variable(ed, 0 <= w <= w_f ) ## wind power injection
    ## Define the objective function
    @objective(ed, Min, c_g' * g + c_w * w)
    ## Define the constraint on the maximum and minimum power output of each generator
    @constraint(ed, [i = 1:2], g[i] <= g_max[i]) ## maximum
    @constraint(ed, [i = 1:2], g[i] >= g_min[i]) ## minimum
    ## Define the constraint on the wind power injection
    @constraint(ed, w <= w_f)
    ## Define the power balance constraint
    @constraint(ed, sum(g) + w == d)
    optimize!(ed)
    for c_g1_scale = 0.5:0.01:3.0
        @objective(
            ed,
            Min,
            c_g1_scale * c_g[1] * g[1] + c_g[2] * g[2] + c_w_scale * c_w * w,
        )
        optimize!(ed)
        push!(obj_out, objective_value(ed))
        push!(w_out, value(w))
        push!(g1_out, value(g[1]))
        push!(g2_out, value(g[2]))
    end
    elapsed = time() - start
    print(string("elapsed time: ", elapsed, " seconds"))
    return obj_out, w_out, g1_out, g2_out
end

solve_ed_inplace(2.0)

# Adjusting specific constraints and/or the objective function is faster than
# re-building the entire model.

# ## A few practical limitations of the economic dispatch model

# ### Inefficient usage of wind generators

# The economic dispatch problem does not perform commitment decisions and, thus,
# assumes that all generators must be dispatched at least at their minimum power
# output limit. This approach is not cost efficient and may lead to absurd
# decisions. For example, if $ d = \sum_{i \in I} g^{\min}_{i}$, the wind power
# injection must be zero, i.e. all available wind generation is spilled, to meet
# the minimum power output constraints on generators.

# In the following example, we adjust the total demand and observed how it
# affects wind spillage.

demandscale_df = DataFrames.DataFrame(
    Symbol("Dispatch of Generators(MW)") => Float64[],
    Symbol("Dispatch of Generator 2(MW)") => Float64[],
    Symbol("Dispatch of Wind(MW)") => Float64[],
    Symbol("Spillage of Wind(MW)") => Float64[],
    Symbol("Total cost(\$)") => Float64[],
)

for demandscale = 0.2:0.1:1.5
    g_opt,w_opt,ws_opt,obj = solve_ed(g_max, g_min, c_g, c_w, demandscale*d, w_f)
    push!(demandscale_df, (g_opt[1], g_opt[2], w_opt, ws_opt, obj))
end

demandscale_df

# This particular drawback can be overcome by introducing binary decisions on
# the "on/off" status of generators. This model is called unit commitment and
# considered later in these notes.

# For further reading on the interplay between wind generation and the minimum
# power output constraints of generators, we refer interested readers to R.
# Baldick, "Wind and Energy Markets: A Case Study of Texas," IEEE Systems
# Journal, vol. 6, pp. 27-34, 2012.

# ### Transmission-infeasible solution

# The ED solution is entirely market-based and disrespects limitations of the
# transmission network. Indeed, the flows in transmission lines would attain the
# following values:

# ```math
# f_{1-2} = 150 MW \leq f_{1-2}^{\max} = 100 MW
# ```

# ```math
# f_{2-3} = 1200 MW \leq f_{2-3}^{\max} = 1000 MW
# ```

# Thus, if this ED solution was enforced in practice, the power flow limits on
# both lines would be violated. Therefore, in the following section we consider
# the optimal power flow model, which amends the ED model with network
# constraints.

# The importance of the transmission-aware decisions is emphasized in E.
# Lannoye, D. Flynn, and M. O'Malley, "Transmission, Variable Generation, and
# Power System Flexibility," IEEE Transactions on Power Systems, vol. 30, pp.
# 57-66, 2015.

# ## Unit Commitment model

# The Unit Commitment (UC) model can be obtained from ED model by introducing
# binary variable associated with each generator. This binary variable can
# attain two values: if it is "1", the generator is synchronized and, thus, can
# be dispatched, otherwise, i.e. if the binary variable is "0", that generator
# is not synchronized and its power output is set to 0.

# To obtain the mathematical formulation of the UC model, we will modify the
# constraints of the ED model as follows:

# ```math
# g^{\min}_{i} \cdot u_{t,i} \leq g_{i} \leq g^{\max}_{i} \cdot u_{t,i},
# ```
# where $u_{i} \in \{0,1\}.$ In this constraint, if $u_{i} = 0$, then
# $g_{i}  = 0$. On the other hand, if $u_{i} = 1$, then
# $g^{max}_{i} \leq g_{i} \leq g^{min}_{i}$.

# For further reading on the UC problem we refer interested readers to G.
# Morales-Espana, J. M. Latorre, and A. Ramos, "Tight and Compact MILP
# Formulation for the Thermal Unit Commitment Problem," IEEE Transactions on
# Power Systems, vol. 28, pp. 4897-4908, 2013.

# In the following example we convert the ED model explained above to the UC
# model.

function solve_uc(g_max, g_min, c_g, c_w, d, w_f)
    ## Define the unit commitment (UC) model
    uc = Model(GLPK.Optimizer)
    ## Define decision variables
    @variable(uc, 0 <= g[i=1:2] <= g_max[i]) ## power output of generators
    @variable(uc, u[i = 1:2], Bin) ## Binary status of generators
    @variable(uc, 0 <= w <= w_f) ## wind power injection
    ## Define the objective function
    @objective(uc, Min, c_g' * g + c_w * w)
    ## Define the constraint on the maximum and minimum power output of each
    ## generator.
    @constraint(uc, [i = 1:2], g[i] <= g_max[i] * u[i]) ## maximum
    @constraint(uc, [i = 1:2], g[i] >= g_min[i] * u[i]) ## minimum
    ## Define the constraint on the wind power injection
    @constraint(uc, w <= w_f)
    ## Define the power balance constraint
    @constraint(uc, sum(g) + w == d)
    ## Solve statement
    optimize!(uc)
    status = termination_status(uc)
    if status != MOI.OPTIMAL
        return status, zeros(length(g)), 0.0, 0.0, zeros(length(u)), Inf
    end
    return status, value.(g), value(w), w_f - value(w), value.(u), objective_value(uc)
end

# Solve the economic dispatch problem
status, g_opt, w_opt, ws_opt, u_opt, obj =
    solve_uc(g_max, g_min, c_g, c_w, d, w_f)

println("Dispatch of Generators: ", g_opt[:], " MW")
println("Commitments of Generators: ", u_opt[:])
println("Dispatch of Wind: ", w_opt, " MW")
println("Wind spillage: ", w_f - w_opt, " MW")
println("\n")
println("Total cost: ", obj, "\$")

# ### Unit Commitment as a function of demand

# After implementing the UC model, we can now assess the interplay between the
# minimum power output constraints on generators and wind generation.

uc_df = DataFrames.DataFrame(
    Symbol("Commitment of Generator 1(MW)") => Float64[],
    Symbol("Commitment of Generator 2(MW)") => Float64[],
    Symbol("Dispatch of Generator 1(MW)") => Float64[],
    Symbol("Dispatch of Generator 2(MW)") => Float64[],
    Symbol("Dispatch of Wind(MW)") => Float64[],
    Symbol("Spillage of Wind(MW)") => Float64[],
    Symbol("Total cost(\$)") => Float64[],
)

for demandscale = 0.2:0.1:1.5
    status, g_opt, w_opt, ws_opt, u_opt, obj =
        solve_uc(g_max, g_min, c_g, c_w, demandscale*d, w_f)
    if status == MOI.OPTIMAL
        push!(uc_df, (u_opt[1], u_opt[2], g_opt[1], g_opt[2], w_opt, ws_opt, obj))
    else
        println("Status: $status for demandscale = $demandscale \n")
    end
end

#-

uc_df


path = "docs/src/assets/"

using ISA, Plots
𝐶₀ = AMFMtriplet(t->1,t->100,0.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(ψ₀; timeaxis=-1.0:0.001:1.0)
png(path*"IS_exSHC1.png")

using ISA, Plots
𝐶₀ = AMFMtriplet(t->1,t->100,0.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(𝐶₀; timeaxis=-1.0:0.001:1.0)
png(path*"IS_exSHC2.png")

using ISA, Plots
𝐶₀ = AMFMtriplet(t->1,t->100,0.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(𝐶₀; timeaxis=-1.0:0.001:1.0, view="TF",
     left_margin=15Plots.mm, margin=5Plots.mm)
png(path*"IS_exSHC3.png")

#------------------------------------
using ISA, Plots
𝐶₀ = AMFMtriplet(t->1,t->10π,0.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(ψ₀; timeaxis=-1.0:0.001:1.0)
png(path*"IS_exSHC4.png")

using ISA, Plots
𝐶₀ = AMFMtriplet(t->1,t->10π,0.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(𝐶₀; timeaxis=-1.0:0.001:1.0)
png(path*"IS_exSHC5.png")

using ISA, Plots
𝐶₀ = AMFMtriplet(t->1,t->10π,0.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(𝐶₀; timeaxis=-1.0:0.001:1.0, view="TF",
     left_margin=15Plots.mm, margin=5Plots.mm)
png(path*"IS_exSHC6.png")

#-----------------------------------
using ISA, Plots
𝐶₀ = AMFMtriplet(t->10,t->exp(π),1.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(ψ₀; timeaxis=-1.0:0.001:1.0)
png(path*"IS_exSHC7.png")

using ISA, Plots
𝐶₀ = AMFMtriplet(t->10,t->exp(π),1.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(𝐶₀; timeaxis=-1.0:0.001:1.0)
png(path*"IS_exSHC8.png")

using ISA, Plots
𝐶₀ = AMFMtriplet(t->10,t->exp(π),1.0)
ψ₀ = AMFMcomp(𝐶₀)
plot(𝐶₀; timeaxis=-1.0:0.001:1.0, view="TF",
     left_margin=15Plots.mm, margin=5Plots.mm)
png(path*"IS_exSHC9.png")


"""
    struct EpsilonGreedyPolicy <: AbstractPolicy
        table::Vector{Int}
        nactions::Int
        ϵ::Float64
    end

Just like the [`DeterministicPolicy`](@ref), the best actions are stored in the `table`.
However the best action will only be taken at a portion of 1 - ϵ.

See also: [`EpsilonGreedySelector`](@ref)
"""
struct EpsilonGreedyPolicy <: AbstractPolicy
    table::Vector{Int}
    nactions::Int
    ϵ::Float64
end

function (p::EpsilonGreedyPolicy)(s::Int)
    if rand() > p.ϵ
        p.table[s]
    else
        rand(1:p.nactions)
    end
end

function (p::EpsilonGreedyPolicy)(s::Int, ::Val{:dist})
    prob = fill(p.ϵ/p.nactions, p.nactions)
    prob[p.table[s]] += 1 - p.ϵ
    prob
end

(p::EpsilonGreedyPolicy)(s::Int, a::Int) = p.table[s] == a ? (1 - p.ϵ) + p.ϵ / p.nactions : p.ϵ / p.nactions

update!(p::EpsilonGreedyPolicy, s::Int, a::Int) = p.table[s] = a


using OrdinaryDiffEq, DiffEqSensitivity, Zygote

tspan = (0., 1.)
X = randn(3, 4)
p = randn(3, 4)
f(u,p,t) = u .* p
f(du,u,p,t) = (du .= u .* p)
prob_ube = ODEProblem{false}(f, X, tspan, p)
Zygote.gradient(p->sum(solve(prob_ube, Midpoint(), u0 = X, p = p)),p)

prob_ube = ODEProblem{true}(f, X, tspan, p)
Zygote.gradient(p->sum(solve(prob_ube, Midpoint(), u0 = X, p = p)),p)

function aug_dynamics!(dz, z, K, t)
    x = @view z[2:end]
    u = -K * x
    dz[1] = x' * x + u' * u
    dz[2:end] = x + u
end

policy_params = ones(2, 2)
z0 = zeros(3)
fwd_sol = solve(
    ODEProblem(aug_dynamics!, z0, (0.0, 1.0), policy_params),
    Tsit5(),
    u0 = z0,
    p = policy_params)

solve(
    ODEAdjointProblem(
        fwd_sol,
        InterpolatingAdjoint(),
        (out, x, p, t, i) -> (out .= 1),
        [1.0],
    ),Tsit5()
)

A = ones(2, 2)
B = ones(2, 2)
Q = ones(2, 2)
R = ones(2, 2)

function aug_dynamics!(dz, z, K, t)
    x = @view z[2:end]
    u = -K * x
    dz[1] = x' * Q * x + u' * R * u
    dz[2:end] = A * x + B * u # or just `x + u`
end

policy_params = ones(2, 2)
z0 = zeros(3)
fwd_sol = solve(
    ODEProblem(aug_dynamics!, z0, (0.0, 1.0), policy_params),
    u0 = z0,
    p = policy_params,
)

solve(
    ODEAdjointProblem(
        fwd_sol,
        InterpolatingAdjoint(),
        (out, x, p, t, i) -> (out .= 1),
        [1.0],
    ),
)


using Digits
@static if VERSION < v"0.7-dev"
    @eval using Base.Test
else
    @eval using Test
end

# test data
n = 1234567890
p = 1234567890987654321
l = digits(n)
lp = digits(p)

# reversedigits and reversedigits!
@test reversedigits(n) == 987654321
@test reversedigits(p) == p
@test reversedigits(l) == reverse(l)
@test reversedigits(lp) == lp
@test reversedigits!(reversedigits!(l)) == digits(n)

# undigit
@test undigit(l) == n
@test undigit(n) == n
@test undigit(Int[]) == 0

# digithist
@test length(l) == sum(digithist(l))
@test ndigits(n) == sum(digithist(n))
@test digithist(l) == [1,1,1,1,1,1,1,1,1,1]
@test digithist(n) == [1,1,1,1,1,1,1,1,1,1]
@test digithist(p) == [1,2,2,2,2,2,2,2,2,2]

# isanagram
@test isanagram(0,0) == true
@test isanagram(Int[],Int[]) == true
@test isanagram(122334,431232) == true
@test isanagram(digits(122334),digits(431232)) == true
@test isanagram(122334,433232) == false
@test isanagram(135,61436) == false
@test isanagram(-135,153) == false
@test isanagram(-532,-253) == true
@test isanagram(135, digits(513)) == true
@test isanagram(digits(3613),1633) == true

# ispalindrom
@test ispalindrome(p) == true
@test ispalindrome(lp) == true
@test ispalindrome(n) == false

# contains
@test Digits.contains(n,n) == true
@test Digits.contains(n,3456) == true
@test Digits.contains(56,n) == true
@test Digits.contains(l,567) == true
@test Digits.contains(n,digits(123)) == true
@test Digits.contains(l,digits(789)) == true
@test Digits.contains(n,457) == false

# startswith
@test Digits.startswith(n,n) == true
@test Digits.startswith(n,123) == true
@test Digits.startswith(12,n) == true
@test Digits.startswith(l,1234) == true
@test Digits.startswith(l,digits(12)) == true
@test Digits.startswith(n,digits(1)) == true
@test Digits.startswith(n,2345) == false

# endswith
@test Digits.endswith(n,n) == true
@test Digits.endswith(n,7890) == true
@test Digits.endswith(90,n) == true
@test Digits.endswith(l,90) == true
@test Digits.endswith(l,digits(90)) == true
@test Digits.endswith(n,digits(567890)) == true
@test Digits.endswith(n,45678) == false

# crop
@test crop(n,3) == 4567890
@test crop(n,-4) == 123456
@test crop(l,5) == digits(67890)
@test crop(l,-8) == digits(12)
@test crop(n,10) == 0
@test crop(l,10) == [0]
@test crop(n,0) == n
@test crop(l,0) == l
@test_throws ErrorException crop(n,11)

# combine
@test combine(0,23) == 23
@test combine(11,0) == 110
@test combine(976,23) == 97623
@test combine(digits(234),digits(46)) == digits(23446)
@test combine(l,[0]) == digits(12345678900)
@test combine([0],l) == [l;0]
@test combine(Int[],l) == l
@test combine(l,Int[]) == l
@test combine(13,digits(35)) == 1335
@test combine(digits(753),12) == digits(75312)

# crosssum
@test crosssum(n) == 45
@test crosssum(lp) == 90
@test crosssum(0) == 0
@test crosssum(Int[]) == 0
@test crosssum([0]) == 0

# select
@test Digits.select(n,1:3) == 123
@test Digits.select(l,1:3) == [0,9,8]
@test Digits.select(n,[2,4]) == 24
@test Digits.select(l,[6,9]) == [5,2]

# replace and replace!
@test Digits.replace(n,[1],[9]) == 9234567890
@test Digits.replace(n,[3,7],[8,4]) == 1284564890
@test Digits.replace(n,5,1) == 1234167890
@test Digits.replace(n,collect(5:7),[1,2,3]) == 1234123890
@test Digits.replace(l,[3],[0]) == digits(1234567090)
@test Digits.replace(l,[9,1],[3,2]) == digits(1334567892)
@test Digits.replace(l,0,1) == digits(1234567891)
@test Digits.replace(l,collect(1:10),digits(9977553311)) == digits(9977553311)
ll = deepcopy(l)
Digits.replace!(ll,collect(2:3),[0,0])
@test ll == digits(1234567000)
Digits.replace!(ll,[9,8],[3,0])
@test ll == digits(1304567000)
Digits.replace!(ll,0,9)
@test ll == digits(1394567999)

# digitroot
@test digitroot(n) == 9
@test digitroot(l) == 9
@test digitroot(0) == 0


# multiDimAdditiveSDEExample()
using DiffEqProblemLibrary.SDEProblemLibrary: importsdeproblems; importsdeproblems()
import DiffEqProblemLibrary.SDEProblemLibrary: prob_sde_lorenz, oval2ModelExample

oval2ModelExample()

oval2ModelExample(useBigs=true,largeFluctuations=true)

true


"""Theorems that we can prove with this package"""
module Theorems

include("Napoleon.jl")
include("Centroid.jl")
include("Ceva.jl")

end


using Base.Test

#-------------------------------------------------------------------------------------------------#
include("../src/pccf.jl")
@test Pccf.pccfWithConfirmation([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],2,[3,7,10]) == [1,2, 1,2,3,4,5,6,7, 1,2,3,4,5,6] # confirmed=[3,10]

#-------------------------------------------------------------------------------------------------#
include("../src/probs.jl")
@test Probs.ifDetected(5, [3, 6, 11], 3) == true
@test Probs.ifDetected(8, [3, 6, 11], 2) == true
@test Probs.ifDetected(9, [3, 6, 12], 2) == false

@test Probs.extractFps([1, 6, 19, 25], [5, 10, 15, 20], 3) == [1, 25]
@test Probs.extractTps([1, 6, 19, 25], [5, 10, 15, 20], 3) == [6, 19]

@test Probs.ifPredictedTp(2, [0.1, 0.9, 0.4], 0.41) == true
@test Probs.ifPredictedTp(3, [0.1, 0.9, 0.4], 0.41) == false

@test Probs.belowH(0.7, [0.1, 0.8, 0.4, 10.0, 11.0, 0.7], 4) == [1, 3]
@test Probs.aboveH(0.7, [0.1, 0.2, 0.71, 0.8, 0.5], 6      ) == [3, 4]

# @test Probs.whichBin(7 , [1,2,7,9]) == 3
# @test Probs.whichBin(-7, [1,2,7,9]) == 1
# @test Probs.whichBin(10, [1,2,7,9]) == 5
# @test Probs.whichBin(9 , [1,2,7,9]) == 4

#-------------------------------------------------------------------------------------------------#
include("../src/sim2.jl")

# fracOfRemovedFa(h, probs, delayConfirm, allErrors)
@test Sim2.fracOfRemovedFa(0.1, [0.01, 0.12, 0.03, 0.04, 0.11], 5, [1, 3, 4]) == 1.0

@test Sim2.fracOfRemovedFa(0.1, [0.01, 0.12, 0.03, 0.04, 0.11], 5, [1, 3, 5]) == 2/3
@test Sim2.fracOfRemovedFa(0.1, [0.01, 0.12, 0.03, 0.04, 0.11], 3, [1, 3, 5]) == 1.0

@test Sim2.fracOfRemovedFa(0.1, [0.01, 0.12, 0.03, 0.04, 0.11], 5, [2]      ) == 0.0

@test Sim2.fracOfRemovedFa(0.1, [0.01, 0.12, 0.03, 0.04, 0.11], 3, [1, 3]   ) == 1.0
@test Sim2.fracOfRemovedFa(0.1, [0.01, 0.12, 0.03, 0.04, 0.11], 3, [1, 2, 5]) == 0.5

# function fracOfPredictedTp (h, probs, delayConfirm, allChanges)
@test Sim2.fracOfPredictedTp(0.1, [0.11, 0.01, 0.11, 0.01, 0.11, 0.01], 6, [1, 3, 5]) == 1.0
@test Sim2.fracOfPredictedTp(0.1, [0.11, 0.01, 0.01, 0.01, 0.11, 0.01], 6, [1, 3, 5]) == 2/3
@test Sim2.fracOfPredictedTp(0.1, [0.11, 0.01, 0.01, 0.01, 0.11, 0.01], 3, [1, 3, 5]) == 0.5
@test Sim2.fracOfPredictedTp(0.1, [0.11, 0.01, 0.01, 0.01, 0.11, 0.01], 2, [1, 3, 5]) == 1.0

#-------------------------------------------------------------------------------------------------#
include("../src/sim3.jl")
@test Sim3.updVec([10,20,30], [2], [0]) == [10,0,30]

#-------------------------------------------------------------------------------------------------#
include("../src/detector.jl")
@test Detector.partSig(3, [1:5;]) == collect(([1,2,3],[2,3,4],[3,4,5]))
@test Detector.naiveDetector([1,2,5,2,10],4) == [3,5]

@test Detector.naiveDetectorDynamic([1.0, 2.0, 3.0, 3.5, 0.4],
                                    [0.5, 2.1, 2.9, 3.4, 0.4]) == [1,3,4]

#-------------------------------------------------------------------------------------------------#
include("../src/roc.jl")
@test Roc.tpNum([1,11]      , [10,20,30,40], 3) == 1
@test Roc.tpNum([1,11,13]   , [10,20,30,40], 3) == 2
@test Roc.tpNum([1,11,14]   , [10,20,30,40], 3) == 1
@test Roc.tpNum([1,11,14,30], [10,20,30,40], 3) == 2

@test Roc.tpRate([1,31   ], [10,20,30,40], 3) == 1/4
@test Roc.tpRate([1,31,44], [10,20,30,40], 3) == 1/4
@test Roc.tpRate([1,31,43], [10,20,30,40], 3) == 2/4

@test Roc.faRate([1,31   ], [10,20,30,40], 3, 100) == 1/(1+(100-4*3))


using Revise
	using DiffEqOperators, Setfield, Parameters
	using BifurcationKit, LinearAlgebra, Plots, SparseArrays
	const BK = BifurcationKit

plotsol(x, Nx=Nx, Ny=Ny) = heatmap(reshape(Array(x), Nx, Ny)', color=:viridis)
plotsol!(x, Nx=Nx, Ny=Ny; kwargs...) = heatmap!(reshape(Array(x), Nx, Ny)'; color=:viridis, kwargs...)

Nx = 151
	Ny = 100
	lx = 8pi
	ly = 2*2pi/sqrt(3)

function Laplacian2D(Nx, Ny, lx, ly, bc = :Neumann)
	hx = 2lx/Nx
	hy = 2ly/Ny
	D2x = CenteredDifference(2, 2, hx, Nx)
	D2y = CenteredDifference(2, 2, hy, Ny)
	if bc == :Dirichlet
		Qx = Dirichlet0BC(typeof(hx))
		Qy = Dirichlet0BC(typeof(hy))
	elseif bc == :Neumann
		Qx = Neumann0BC(hx)
		Qy = Neumann0BC(hy)
	elseif bc == :Periodic
		Qx = PeriodicBC(hx)
		Qy = PeriodicBC(hy)
	end
	# @show norm(D2x - Circulant(D2x[1,:]))
	A = kron(sparse(I, Ny, Ny), sparse(D2x * Qx)[1]) + kron(sparse(D2y * Qy)[1], sparse(I, Nx, Nx))
	return A, D2x
end

function F_sh(u, p)
	@unpack l, ν, L1 = p
	return -L1 * u .+ (l .* u .+ ν .* u.^2 .- u.^3)
end

function dF_sh(u, p)
	@unpack l, ν, L1 = p
	return -L1 .+ spdiagm(0 => l .+ 2 .* ν .* u .- 3 .* u.^2)
end

d2F_sh(u, p, dx1, dx2) = (2 .* p.ν .* dx2 .- 6 .* dx2 .* u) .* dx1
d3F_sh(u, p, dx1, dx2, dx3) = (-6 .* dx2 .* dx3) .* dx1
jet = (F_sh, dF_sh, d2F_sh, d3F_sh)

X = -lx .+ 2lx/(Nx) * collect(0:Nx-1)
Y = -ly .+ 2ly/(Ny) * collect(0:Ny-1)

sol0 = [(cos(x) .+ cos(x/2) * cos(sqrt(3) * y/2) ) for x in X, y in Y]
	sol0 .= sol0 .- minimum(vec(sol0))
	sol0 ./= maximum(vec(sol0))
	sol0 = sol0 .- 0.25
	sol0 .*= 1.7
	heatmap(sol0', color=:viridis)

Δ, D2x = Laplacian2D(Nx, Ny, lx, ly, :Neumann)
L1 = (I + Δ)^2
par = (l = -0.1, ν = 1.3, L1 = L1)

optnew = NewtonPar(verbose = true, tol = 1e-8, maxIter = 20)
# optnew = NewtonPar(verbose = true, tol = 1e-8, maxIter = 20, eigsolver = EigArpack(0.5, :LM))
	sol_hexa, hist, flag = @time newton(F_sh, dF_sh, vec(sol0), par, optnew)
	println("--> norm(sol) = ", norm(sol_hexa, Inf64))
	plotsol(sol_hexa)

heatmapsol(0.2vec(sol_hexa) .* vec([exp(-(x+0lx)^2/25) for x in X, y in Y]))

heatmapsol(0.2vec(sol_hexa) .* vec([exp(-(x+0lx)^2/25) for x in X, y in Y]))
###################################################################################################
# recherche de solutions
deflationOp = DeflationOperator(2, 1.0, [sol_hexa])

optnew = @set optnew.maxIter = 250
optnewd = @set optnew.maxIter = 250
outdef, _, flag, _ = @time newton(F_sh, dF_sh,
		0.4vec(sol_hexa) .* vec([exp(-1(x+lx)^2/25) for x in X, y in Y]),
		# 0.4vec(sol_hexa) .* vec([1 .- exp(-1(x+lx)^2/55) for x in X, y in Y]),
		par, optnewd, deflationOp)
	println("--> norm(sol) = ", norm(outdef))
	plotsol(outdef) |> display
	flag && push!(deflationOp, outdef)

plotsol(deflationOp[end])

plotsol(0.4vec(sol_hexa) .* vec([1 .- exp(-1(x+0lx)^2/55) for x in X, y in Y]))
###################################################################################################
optcont = ContinuationPar(dsmin = 0.0001, dsmax = 0.005, ds= -0.001, pMax = -0.0, pMin = -1.0, newtonOptions = setproperties(optnew; tol = 1e-9, maxIter = 15, verbose = false), maxSteps = 146, detectBifurcation = 3, nev = 40, dsminBisection = 1e-9, nInversion = 6, tolBisectionEigenvalue= 1e-19)
	optcont = @set optcont.newtonOptions.eigsolver = EigArpack(0.1, :LM)

	br, = @time continuation(
		F_sh, dF_sh,
		deflationOp[1], par, (@lens _.l), optcont;
		plot = true, verbosity = 3,
		# tangentAlgo = BorderedPred(),
		# linearAlgo = MatrixBLS(),
		plotSolution = (x, p; kwargs...) -> (plotsol!(x; label="", kwargs...)),
		recordFromSolution = (x, p) -> (n2 = norm(x), n8 = norm(x, 8)),
		# finaliseSolution = (z, tau, step, contResult; k...) -> 	(Base.display(contResult.eig[end].eigenvals) ;true),
		# callbackN = cb,
		normC = x -> norm(x, Inf))
###################################################################################################
# codim2 Fold continuation
optfold = @set optcont.detectBifurcation = 0
@set! optfold.newtonOptions.verbose = true
optfold = setproperties(optfold; pMin = -2., pMax= 2., dsmax = 0.1)
brfold = continuation(F_sh, dF_sh, br, 1, (@lens _.ν), optfold;
	verbosity = 3, plot = true, issymmetric = true,
	# bdlinsolver = MatrixBLS(),
	updateMinAugEveryStep = 1,
	)

###################################################################################################
using IncompleteLU
prec = ilu(L1 + I,τ = 0.15)
prec = lu(L1 + I)
ls = GMRESIterativeSolvers(reltol = 1e-5, N = Nx*Ny, Pl = prec)

function dF_sh2(du, u, p)
	@unpack l, ν, L1 = p
	return -L1 * du .+ (l .+ 2 .* ν .* u .- 3 .* u.^2) .* du
end

sol_hexa, _, flag = @time newton(
		F_sh,
		(u, p) -> (du -> dF_sh2(du, u, p)),
		vec(sol0), par,
		@set optnew.linsolver = ls)
	println("--> norm(sol) = ", norm(sol_hexa, Inf64))
	plotsol(sol_hexa)
###################################################################################################
# Automatic branch switching

br2, = continuation(jet..., br, 2, setproperties(optcont; ds = -0.001, detectBifurcation = 3, plotEveryStep = 5, maxSteps = 170);  nev = 30,
			plot = true, verbosity = 2,
			plotSolution = (x, p; kwargs...) -> (plotsol!(x; label="", kwargs...);plot!(br; subplot=1,plotfold=false)),
			recordFromSolution = (x, p) -> norm(x),
			normC = x -> norm(x, Inf))

plot(br, br2...)
###################################################################################################
# Manual branch switching
bp2d = computeNormalForm(jet..., br, 11; verbose = true, nev = 80)
BK.nf(bp2d)

using ProgressMeter, LaTeXStrings
Nd = 140; L = 0.9
# sampling grid
X = LinRange(-L,L, Nd); Y = LinRange(-L,L, Nd); P = LinRange(-0.0014,0.0014, Nd+1)

# sample reduced equation on the grid for the first component
V1a = @showprogress [bp2d(Val(:reducedForm),[x1,y1], p1)[1] for p1 in P, x1 in X, y1 in Y]
Ind1 = findall( abs.(V1a) .<= 9e-4 * maximum(abs.(V1a)))
# intersect with second component
V2a = @showprogress [bp2d(Val(:reducedForm),[X[ii[2]],Y[ii[3]]], P[ii[1]])[2] for ii in Ind1]
Ind2 = findall( abs.(V2a) .<= 3e-3 * maximum(abs.(V2a)))

# get solutions
resp = Float64[]; resx = Vector{Float64}[]; resnrm = Float64[]
	@showprogress for k in Ind2
		ii = Ind1[k]
		push!(resp, P[ii[1]])
		push!(resnrm, sqrt(X[ii[2]]^2+Y[ii[3]]^2))
		push!(resx, [X[ii[2]], Y[ii[3]]])
	end

plot(
	scatter(1e4resp, map(x->x[1], resx), map(x->x[2], resx); label = "", markerstrokewidth=0, xlabel = L"10^4 \cdot \lambda", ylabel = L"x_1", zlabel = L"x_2", zcolor = resnrm, color = :viridis,colorbar=false),
	scatter(1e4resp, resnrm; label = "", markersize =2, markerstrokewidth=0, xlabel = L"10^4 \cdot \lambda", ylabel = L"\|x\|"))
###################################################################################################
function optionsCont(x,p,l; opt = optcont)
	if l <= 1
		return opt
	elseif l==2
		return setproperties(opt ;detectBifurcation = 0,ds = 0.001, a = 0.75)
	else
		return setproperties(opt ;detectBifurcation = 0,ds = 0.00051, dsmax = 0.01)
	end
end

diagram = bifurcationdiagram(jet..., br, 2, optionsCont;
	plot = true, verbosity = 0,
	# usedeflation = true,
	# δp = 0.005,
	# tangentAlgo = BorderedPred(),
	callbackN = cb,
	# linearAlgo = MatrixBLS(),
	plotSolution = (x, p; kwargs...) -> (plotsol!(x; label="", kwargs...)),
	recordFromSolution = (x, p) -> norm(x),
	finaliseSolution = (z, tau, step, contResult) -> 	(Base.display(contResult.eig[end].eigenvals) ;true),
	normC = x -> norm(x, Inf)
	)

plot(diagram; code = (), legend = false, plotfold = false)
	plot!(br)

# BK.add!(diagram, br2, 2)

###################################################################################################
deflationOp = DeflationOperator(2, 1.0, [sol_hexa])
optcontdf = @set optcont.newtonOptions.verbose = true
brdf,  = continuation(F_sh, dF_sh, par, (@lens _.l), setproperties(optcontdf; detectBifurcation = 0, plotEveryStep = 1),
	deflationOp;
	plot = true, verbosity = 2,
	# tangentAlgo = BorderedPred(),
	# linearAlgo = MatrixBLS(),
	# plotSolution = (x, p; kwargs...) -> (plotsol!(x; label="", kwargs...)),
	maxIterDefOp = 50,
	maxBranches = 40,
	seekEveryStep = 5,
	recordFromSolution = (x, p) -> norm(x),
	perturbSolution = (x,p,id) -> (x  .+ 0.1 .* rand(length(x))),
	# finaliseSolution = (z, tau, step, contResult) -> 	(Base.display(contResult.eig[end].eigenvals) ;true),
	# callbackN = cb,
	normN = x -> norm(x, Inf))

plot(brdf...)


@testset "Evolutionary Objective" begin

    v = Evolutionary.default_values(ones(3))
    @test eltype(v) == Float64
    @test all(isnan.(v))

    v = Evolutionary.default_values(fill(true, 3))
    @test eltype(v) == Bool
    @test iszero(v)

    v = Evolutionary.default_values(BitVector(ones(Bool, 3)))
    @test eltype(v) == Bool
    @test iszero(v)

    v = Evolutionary.default_values(ones(Int,2, 3))
    @test eltype(v) == Int
    @test iszero(v)

    f1(x) = sum(x)
    objfun = EvolutionaryObjective(f1, [1; 2; 3; 4])
    @test typeof(objfun.F) == Int
    @test eltype(objfun.x_f) == Int
    f2(x) = sum(x)/length(x)
    objfun = EvolutionaryObjective(f2, [1; 2; 3; 4])
    @test typeof(objfun.F) == Float64
    @test eltype(objfun.x_f) == Int
    objfun = EvolutionaryObjective(f1, BitVector(ones(10)))
    @test typeof(objfun.F) == Int
    @test eltype(objfun.x_f) == Bool
    objfun = EvolutionaryObjective(f2, BitVector(ones(10)))
    @test typeof(objfun.F) == Float64
    @test eltype(objfun.x_f) == Bool
    f3(expr::Expr) = Evolutionary.Expression(expr).([1; 2; 3; 4]) |> sum
    objfun = EvolutionaryObjective(f3, Expr(:call, *, :x, :x))
    @test typeof(objfun.F) == Int
    @test typeof(objfun.x_f) == Expr

    f4(x) = x[1]+x[2]
    x = zeros(2)
    objfun = EvolutionaryObjective(f4, x)
    @test objfun.F isa Float64
    @test objfun.x_f isa Vector{Float64}
    @test !ismultiobjective(objfun)
    @test objfun.F === 0.0
    @test all(isnan.(objfun.x_f))
    v = value(objfun, ones(2))
    @test v == 2.0
    @test v isa Float64
    @test f_calls(objfun) == 1
    @test objfun.F === 0.0
    @test all(isnan.(objfun.x_f))
    @test_throws MethodError value(objfun, ones(Int, 2))
    v = value!(objfun, ones(2))
    @test v == 2.0
    @test v isa Float64
    @test f_calls(objfun) == 2
    @test objfun.F === 2.0
    @test all(isnan.(objfun.x_f))
    v = value!!(objfun, zeros(2))
    @test v === 0.0
    @test v isa Float64
    @test f_calls(objfun) == 3
    @test objfun.F === 0.0
    @test all(iszero.(objfun.x_f))

    function f5(F, x)
        F[1] = x[1]+1
        F[2] = x[2]+2
        F
    end
    objfun = EvolutionaryObjective(f5, x, copy(x))
    @test objfun.F   isa Vector{Float64}
    @test objfun.x_f isa Vector{Float64}
    @test ismultiobjective(objfun)
    @test_throws MethodError value(objfun, ones(Int, 2))
    @test iszero(objfun.F)
    @test all(isnan.(objfun.x_f))
    v = value(objfun, ones(2))
    @test eltype(v) == Float64
    @test v == [2.0, 3.0]
    @test f_calls(objfun) == 1
    @test iszero(objfun.F)
    @test all(isnan.(objfun.x_f))
    y = zeros(2)
    v = value(objfun, y, ones(2))
    @test v == [2.0, 3.0]
    @test v == y
    @test f_calls(objfun) == 2
    @test iszero(objfun.F)
    @test all(isnan.(objfun.x_f))
    y = zeros(2)
    v = value!(objfun, y, ones(2))
    @test v == [2.0, 3.0]
    @test v == y
    @test f_calls(objfun) == 3
    @test objfun.F == v
    @test all(isnan.(objfun.x_f))
    y = zeros(2)
    v = value!!(objfun, y, zeros(2))
    @test v == [1.0, 2.0]
    @test v == y
    @test f_calls(objfun) == 4
    @test objfun.F == v
    @test iszero(objfun.x_f)

    f6(x) = [x[1], x[2]+1]
    x = zeros(2)
    objfun = EvolutionaryObjective(f6, x)
    v = value(objfun, ones(2))
    @test v == [1.0, 2.0]
    @test iszero(objfun.F)
    @test all(isnan.(objfun.x_f))
    v = value!(objfun, ones(2))
    @test v == [1.0, 2.0]
    @test objfun.F == v
    @test all(isnan.(objfun.x_f))
    v = value!!(objfun, zeros(2))
    @test objfun.F == v
    @test iszero(objfun.x_f)

    X = [ones(2) for i in 1:10]
    F = zeros(10)
    objfun = EvolutionaryObjective(f4, x)
    V = value!(objfun, F, X);
    @test F == 2ones(10)

    objfun = EvolutionaryObjective(f4, x; eval=:thread)
    V = value!(objfun, F, X);
    @test F == 2ones(10)

    objfun = EvolutionaryObjective(f5, x, zeros(2))
    F = zeros(2,10)
    V = value!(objfun, F, X)
    @test F == [2ones(10) 3ones(10)]'

    objfun = EvolutionaryObjective(f5, x, zeros(2); eval=:thread)
    F = zeros(2,10)
    V = value!(objfun, F, X)
    @test F == [2ones(10) 3ones(10)]'

end


using LinearAlgebra: norm, normalize

set(obj, ::typeof(last), val) = @set obj[lastindex(obj)] = val
set(obj, ::typeof(first), val) = @set obj[firstindex(obj)] = val
delete(obj, ::typeof(last)) = delete(obj, IndexLens((lastindex(obj),)))
delete(obj, ::typeof(first)) = delete(obj, IndexLens((firstindex(obj),)))
insert(obj, ::typeof(last), val) = insert(obj, IndexLens((lastindex(obj) + 1,)), val)
insert(obj, ::typeof(first), val) = insert(obj, IndexLens((firstindex(obj),)), val)

set(obj, ::typeof(identity), val) = val
set(obj, ::typeof(inv), new_inv) = inv(new_inv)

################################################################################
##### eltype
################################################################################
set(obj::Array, ::typeof(eltype), T::Type) = collect(T, obj)
set(obj::Number, ::typeof(eltype), T::Type) = T(obj)
set(obj::Type{<:Number}, ::typeof(eltype), ::Type{T}) where {T} = T
set(obj::Type{<:Array{<:Any,N}}, ::typeof(eltype), ::Type{T}) where {N,T} = Array{T,N}
set(obj::Type{<:Dict}, ::typeof(eltype), ::Type{Pair{K,V}}) where {K,V} = Dict{K,V}
set(obj::Dict, ::typeof(eltype), ::Type{T}) where {T} = set(typeof(obj), eltype, T)(obj)

set(obj::Dict, lens::Union{typeof(keytype),typeof(valtype)}, T::Type) =
    set(typeof(obj), lens, T)(obj)
set(obj::Type{<:Dict{<:Any,V}}, lens::typeof(keytype), ::Type{K}) where {K,V} = Dict{K,V}
set(obj::Type{<:Dict{K}}, lens::typeof(valtype), ::Type{V}) where {K,V} = Dict{K,V}

################################################################################
##### os
################################################################################
set(path, ::typeof(splitext), (stem, ext))     = string(stem, ext)
set(path, ::typeof(splitdir), (dir, last))     = joinpath(dir, last)
set(path, ::typeof(splitdrive), (drive, rest)) = joinpath(drive, rest)
set(path, ::typeof(splitpath), pieces)         = joinpath(pieces...)
set(path, ::typeof(dirname), new_name)         = @set splitdir(path)[1] = new_name
set(path, ::typeof(basename), new_name)        = @set splitdir(path)[2] = new_name
delete(path, ::typeof(basename)) = dirname(path)
delete(path, ::typeof(dirname)) = basename(path)

################################################################################
##### math
################################################################################
set(x::Real, ::typeof(real), y) = y
set(x,       ::typeof(real), y) = y + im*imag(x)
set(x,       ::typeof(imag), y) = real(x) + im*y

set(arr, ::typeof(normalize), val) = norm(arr) * val
set(arr, ::typeof(norm), val)      = val/norm(arr) * arr # should we check val is positive?


"""
FunctionTransform
```
    f(x) = abs.(x)
    tr = FunctionTransform(f)
```
Take a function or object `f` as an argument which is going to act on each vector individually.
Make sure that `f` is supposed to act on a vector by eventually using broadcasting
For example `f(x)=sin(x)` -> `f(x)=sin.(x)`
"""
struct FunctionTransform{F} <: Transform
    f::F
end

apply(t::FunctionTransform, X::T; obsdim::Int = defaultobs) where {T} = mapslices(t.f, X, dims = feature_dim(obsdim))

duplicate(t::FunctionTransform,f) = FunctionTransform(f)


# functions related to noncentral chi-square distribution

# R implementations
using .RFunctions:
    nchisqpdf,
    nchisqlogpdf,
    nchisqcdf,
    nchisqccdf,
    nchisqlogcdf,
    nchisqlogccdf,
    nchisqinvcdf,
    nchisqinvccdf,
    nchisqinvlogcdf,
    nchisqinvlogccdf


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 310ce685-2661-4f32-bf14-91a4f4e569ce
# Explicit use of own environment instead of a local one for each notebook
begin
	using Pkg
	cd(joinpath(dirname(@__FILE__),".."))
    Pkg.activate(pwd())
    using NativeSVG
end

# ╔═╡ bbf4ab90-eb95-11ea-174e-c3624a0d92cd
using Random

# ╔═╡ 43627847-ce0d-4969-b6e1-2d59a54d1fd2
using PlutoUI

# ╔═╡ 56f41ca0-eb93-11ea-1ea6-11b0e8bb9a7d
md"""# Cellular Automaton

Port of [Think Complexity chapter 5](http://greenteapress.com/complexity2/html/index.html) by Allen Downey."""

# ╔═╡ dab709d0-eb93-11ea-050e-3bfa6a5e1836
md"Cellular Automaton = discrete space (cells) as input for a calculation in discrete time"

# ╔═╡ f4b601b0-eb93-11ea-0969-7967a1e85c8b
md"""## A Trivial Example

0 dimensional CA, inverting its cell at each timestep (2 state values only)

rule:"""

# ╔═╡ 14a02c30-eb94-11ea-2114-5102409c8ae5
function rule0dim(x::Bool)
    !x
end

# ╔═╡ 1a915240-eb94-11ea-087f-231ade62690d
md"time evolution:"

# ╔═╡ 2393fe0e-eb94-11ea-2858-b50503395d4a
function step0dim(x₀::Bool, steps::Int64)
    xs = [x₀]
    for i in 1:steps
        push!(xs, rule0dim(xs[end]))
    end
    xs
end

# ╔═╡ 2dde4510-eb94-11ea-212a-5da1e7733bf6
md"visualisation:"

# ╔═╡ 35cbd760-eb94-11ea-201a-6b58802154a1
let
	res = step0dim(false, 10)
	Drawing(width = 50, height = 300) do
		for (i, val) in enumerate(res)
			fill = if val "grey" else "lightgrey" end
			rect(x = 20, y = 5+i*20, width = 20, height = 20, fill = fill)
		end
	end
end

# ╔═╡ 758a0c50-eb94-11ea-11a2-e3e007b089a9
md"""## Wolfram's Experiment

1 dimensional CA with 2 state values, new value of a cell depends only on state of neighbouring cells.

rule can be expressed as an integer

|prev  |111|110|101|100|011|010|001|000|
|------|---|---|---|---|---|---|---|---|
|next  |b7 |b6 |b5 |b4 |b3 |b2 |b1 |b0 |
|rule50|0  |0  |1  |1  |0  |0  |1  |0  |

b can be converted to an integer

get a rule from an integer:"""

# ╔═╡ d8481030-eb94-11ea-1af4-db838adc37ed
function inttorule1dim(val::UInt8)
    digs = BitArray(digits(val, base=2))
    for i in length(digs):7
        push!(digs, false)
    end
    digs
end

# ╔═╡ e393e8b2-eb94-11ea-3d70-d31f7ee89420
md"Apply rule to a cell knowing its own previous state and the previous state of his left and right neighbour"

# ╔═╡ eed0a600-eb94-11ea-1863-0f33980508ba
function applyrule1dim(rule::BitArray{1}, bits::BitArray{1})
    val = 1 + bits[3] + 2*bits[2] + 4*bits[1]
    rule[val]
end

# ╔═╡ 0002a810-eb95-11ea-2ba8-bb2849bdec17
md"time evolution:"

# ╔═╡ 04139c70-eb95-11ea-1759-298193ce97b0
function step1dim(x₀::BitArray{1}, rule::BitArray{1}, steps::Int64)
    xs = [x₀]
    len = length(x₀)
    for i in 1:steps
        x = copy(x₀)
        for j in 2:len-1
            x[j] = applyrule1dim(rule, xs[end][j-1:j+1])
        end
        push!(xs, x)
    end
    xs
end

# ╔═╡ 10100040-eb95-11ea-3a6d-271e63301b17
md"initialisation:"

# ╔═╡ 1a508340-eb95-11ea-08f9-6f085748d7ff
res = let
	x₀ = falses(21)
	x₀[11] = true
	step1dim(x₀, inttorule1dim(UInt8(50)), 9)
end;

# ╔═╡ 5d2a01f0-eb95-11ea-3dcd-b794e1b0d566
md"visualisation:"

# ╔═╡ 60e8deb0-eb95-11ea-2bde-d9c259432318
function visualize1dim(res, dim)
    width = dim * (length(res[1]) + 1)
    height = dim * (length(res) + 1)
    Drawing(width = width, height = height) do
        for (i, arr) in enumerate(res)
            for (j, val) in enumerate(arr)
                fill = if val "grey" else "lightgrey" end
                rect(x = j*dim, y = i*dim, width = dim, height = dim, fill = fill)
            end
        end
    end
end

# ╔═╡ 6a08d680-eb95-11ea-17b5-d199cc14b12a
visualize1dim(res, 30)

# ╔═╡ 9db5c010-eb95-11ea-3257-652ddbd3939d
md"""## Classifying CAs

### Class 1

Evolution from any starting condition to the same uniform pattern, eg. rule0"""

# ╔═╡ adfe9aa0-eb95-11ea-1ce9-e5370aeec375
let
	x₀ = bitrand(21)
	x₀[1] = false; x₀[end] = false;
	res = step1dim(x₀, inttorule1dim(UInt8(0)), 1)
	visualize1dim(res, 30)
end

# ╔═╡ 1c6b5780-eb96-11ea-2bbd-755d35c27938
md"""### Class 2

Generation of a simple pattern with nested structure, i.e. a pattern that contains many smaller versions of itself, eg. rule50.

Example that looks like a Sierpinsi triangle (fractal): rule18."""

# ╔═╡ 2bcfc580-eb96-11ea-1837-c703fac6cd69
let
	x₀ = falses(129)
	x₀[65] = true
	res = step1dim(x₀, inttorule1dim(UInt8(18)), 63);
	visualize1dim(res, 6)
end

# ╔═╡ 4d3dd0e0-eb96-11ea-0f6a-8f6fb24e8f9d
md"""### Class 3

CAs that generate randomness, eg. rule30."""

# ╔═╡ 4d2f51f0-eb96-11ea-1c16-b7b7b81a3ca3
let
	x₀ = falses(201)
	x₀[101] = true
	res = step1dim(x₀, inttorule1dim(UInt8(30)), 99);
	visualize1dim(res, 4)
end

# ╔═╡ 8eb8d100-eb96-11ea-12f5-751c018540fc
md"""Center column as a sequence of bits, is hard to distinguish from a truly random sequence: pseudo-random number generators (PRNGs).

- regularities can be detected statistically
- a PRNG with finite amount of state will eventually repeat itself (period)
- underlying process is fundamentally deterministic (unlike some physical processes: thermodynamics or quantum mechanics)

This complex behavior is surprising (chaos is often associated with non-linear behavior of continuous time and space processes)."""

# ╔═╡ a075adee-eb96-11ea-3ddc-572f2795d6b5
md"""### Class 4

CAs that are Turing complete or universal, which means that they can compute any computable function, eg. rule110."""

# ╔═╡ aed6b5b0-eb96-11ea-2a3d-7f82f61ea518
let
	x₀ = bitrand(600)
	res = step1dim(x₀, inttorule1dim(UInt8(110)), 599);
	visualize1dim(res, 1)
end

# ╔═╡ c56df180-eb96-11ea-1a57-2d0571da6ac3
md"""- After about 100 steps, simple repeating patterns emerge, but there are a number of persistent structures that appear as disturbances. Some are vertical, other are diagonal and are called spaceships.

- Collisions between spaceships yields different results depending on their type and their phase. Some collisions annihilate both ships; other leaves one ship unchanged; still other yield one or more ships of different types.

- The collisions are the basis of computation in a rule110 CA. You can think of spaceships as signales that propagate through space, and collisions as gate that compute logical operations like AND and OR."""

# ╔═╡ f2a45860-eb96-11ea-2e5f-b1850621020b
md"""## Turing State-Machines

Based on [wikipedia: Turing Machine](https://en.wikipedia.org/wiki/Turing_machine).

A Turing machine is a mathematical model of computation that defines an abstract machine, which manipulates symbols on a tape according to a table of rules. Despite the model's simplicity, given any computer algorithm, a Turing machine capable of simulating that algorithm's logic can be constructed.

- A tape divided into cells, one next to the other. Each cell contains a symbol from some finite alphabet. The alphabet contains a special blank symbol (here written as '0') and one or more other symbols. The tape is assumed to be arbitrarily extendable to the left and to the right, i.e., the Turing machine is always supplied with as much tape as it needs for its computation. Cells that have not been written before are assumed to be filled with the blank symbol. In some models the tape has a left end marked with a special symbol; the tape extends or is indefinitely extensible to the right.
- A head that can read and write symbols on the tape and move the tape left and right one (and only one) cell at a time. In some models the head moves and the tape is stationary.
- A state register that stores the state of the Turing machine, one of finitely many. Among these is the special start state with which the state register is initialized. These states, writes Turing, replace the "state of mind" a person performing computations would ordinarily be in.
- A finite table of instructions that, given the state the machine is currently in and the symbol it is reading on the tape (symbol currently under the head), tells the machine to do the following in sequence:
    - Erase or write a symbol.
    - Move the head ( 'L' for one step left or 'R' for one step right or 'N' for staying in the same place).
    - Assume the same or a new state as prescribed.

Table of rules:

| Tape Symbol | State A   | State B   | State C   |
|:-----------:|-----------|-----------|-----------|
| 0           | 1 - R - B | 1 - L - A | 1 - L - B |
| 1           | 1 - L - C | 1 - R - B | 1 - R - H |"""

# ╔═╡ fa9e94e0-eb96-11ea-061c-a36a28f5e7b1
function applyrulebusybeaver(state, read)
    if state == 'A' && read == 0
        return 1, 'R', 'B'
    elseif state == 'A' && read == 1
        return 1, 'L', 'C'
    elseif state == 'B' && read == 0
        return 1, 'L', 'A'
    elseif state == 'B' && read == 1
        return 1, 'R', 'B'
    elseif state == 'C' && read == 0
        return 1, 'L', 'B'
    elseif state == 'C' && read == 1
        return 1, 'R', 'H'
    end
end

# ╔═╡ 11fa5930-eb97-11ea-274e-3f2c45958666
md"struct to represent the Turing State-Machine:"

# ╔═╡ 1a07f6a0-eb97-11ea-0f51-a3d742fcf260
mutable struct Turing
    tape :: Array{Int64}
    position :: Int64
    state :: Char
end

# ╔═╡ 7bfdef90-eb97-11ea-03ce-4fb081079815
function Base.show(io::IO, turing::Turing)
    print(io, turing.position, " - ", turing.state, ": ", turing.tape)
end

# ╔═╡ 9b61fa20-eb97-11ea-0f1c-c922c04a796f
md"implementation of a step:"

# ╔═╡ a68bf2c0-eb97-11ea-30ea-87d20ea07175
function stepturing!(turing, applyrule)
    if turing.state == 'H'
        error("Machine has stopped!")
    end
    read = turing.tape[turing.position]
    (write, dir, turing.state) = applyrule(turing.state, read)
    turing.tape[turing.position] = write
    if dir == 'L'
        if turing.position == length(turing.tape)
            push!(turing.tape, false)
        end
        turing.position += 1
    else
        if turing.position == 1
            pushfirst!(turing.tape, false)
        else
            turing.position -= 1
        end
    end
    nothing
end

# ╔═╡ a3263280-eb97-11ea-2e91-532bfa433f6c
md"output:"

# ╔═╡ be984390-ebb1-11ea-0729-39795172949b
turing = Turing(zeros(Int64, 11), 6, 'A')

# ╔═╡ ccda0380-ebb1-11ea-1677-11d9d171897c
begin
	stepturing!(turing, applyrulebusybeaver)
	turing
end

# ╔═╡ 74832baf-5779-47f6-907e-5288420bb1b9
with_terminal() do
	turing = Turing(zeros(Int64, 11), 6, 'A')
	println("- ", turing)
	try
		while true
			stepturing!(turing, applyrulebusybeaver)
			println("- ", turing)
		end
	catch
	end
end

# ╔═╡ Cell order:
# ╟─56f41ca0-eb93-11ea-1ea6-11b0e8bb9a7d
# ╠═310ce685-2661-4f32-bf14-91a4f4e569ce
# ╟─dab709d0-eb93-11ea-050e-3bfa6a5e1836
# ╟─f4b601b0-eb93-11ea-0969-7967a1e85c8b
# ╠═14a02c30-eb94-11ea-2114-5102409c8ae5
# ╟─1a915240-eb94-11ea-087f-231ade62690d
# ╠═2393fe0e-eb94-11ea-2858-b50503395d4a
# ╟─2dde4510-eb94-11ea-212a-5da1e7733bf6
# ╠═35cbd760-eb94-11ea-201a-6b58802154a1
# ╟─758a0c50-eb94-11ea-11a2-e3e007b089a9
# ╠═d8481030-eb94-11ea-1af4-db838adc37ed
# ╟─e393e8b2-eb94-11ea-3d70-d31f7ee89420
# ╠═eed0a600-eb94-11ea-1863-0f33980508ba
# ╟─0002a810-eb95-11ea-2ba8-bb2849bdec17
# ╠═04139c70-eb95-11ea-1759-298193ce97b0
# ╟─10100040-eb95-11ea-3a6d-271e63301b17
# ╠═1a508340-eb95-11ea-08f9-6f085748d7ff
# ╟─5d2a01f0-eb95-11ea-3dcd-b794e1b0d566
# ╠═60e8deb0-eb95-11ea-2bde-d9c259432318
# ╠═6a08d680-eb95-11ea-17b5-d199cc14b12a
# ╟─9db5c010-eb95-11ea-3257-652ddbd3939d
# ╠═bbf4ab90-eb95-11ea-174e-c3624a0d92cd
# ╠═adfe9aa0-eb95-11ea-1ce9-e5370aeec375
# ╟─1c6b5780-eb96-11ea-2bbd-755d35c27938
# ╠═2bcfc580-eb96-11ea-1837-c703fac6cd69
# ╟─4d3dd0e0-eb96-11ea-0f6a-8f6fb24e8f9d
# ╠═4d2f51f0-eb96-11ea-1c16-b7b7b81a3ca3
# ╟─8eb8d100-eb96-11ea-12f5-751c018540fc
# ╟─a075adee-eb96-11ea-3ddc-572f2795d6b5
# ╠═aed6b5b0-eb96-11ea-2a3d-7f82f61ea518
# ╟─c56df180-eb96-11ea-1a57-2d0571da6ac3
# ╟─f2a45860-eb96-11ea-2e5f-b1850621020b
# ╠═fa9e94e0-eb96-11ea-061c-a36a28f5e7b1
# ╟─11fa5930-eb97-11ea-274e-3f2c45958666
# ╠═1a07f6a0-eb97-11ea-0f51-a3d742fcf260
# ╠═7bfdef90-eb97-11ea-03ce-4fb081079815
# ╟─9b61fa20-eb97-11ea-0f1c-c922c04a796f
# ╠═a68bf2c0-eb97-11ea-30ea-87d20ea07175
# ╟─a3263280-eb97-11ea-2e91-532bfa433f6c
# ╠═be984390-ebb1-11ea-0729-39795172949b
# ╠═ccda0380-ebb1-11ea-1677-11d9d171897c
# ╠═43627847-ce0d-4969-b6e1-2d59a54d1fd2
# ╠═74832baf-5779-47f6-907e-5288420bb1b9


using Test
using HyperbolicDiffEq

@elapsed begin

  @time @testset "Riemann Problems" begin include("riemann_problems_test.jl") end
  @time @testset "Finite Volumes" begin include("finite_volumes_test.jl") end
  @time @testset "Monomial Fluxes" begin include("monomial_fluxes_test.jl") end
  @time @testset "Flux Difference" begin include("flux_difference_test.jl") end
  @time @testset "ENO Reconstruction" begin include("modified_eno_test.jl") end
  @time @testset "TeCNO" begin include("tecno_test.jl") end
  @time @testset "Quartic Nonconvex Flux" begin include("balance_laws/quartic_nonconvex_test.jl") end
  @time @testset "Keyfitz-Kranzer System" begin include("balance_laws/keyfitz_kranzer_test.jl") end

end


for N in [Float64, Rational{Int}]

    # ====================================
    # Concrete convex hull for point sets
    # ====================================

    # corner cases in dimension 1
    @test convex_hull([Vector{N}(undef, 0)]) == [Vector{N}(undef, 0)]
    @test convex_hull([[N(0)]]) == [[N(0)]]
    @test convex_hull([[N(2)], [N(1)]]) == [[N(1)], [N(2)]]
    @test convex_hull([[N(2)], [N(2)]]) == [[N(2)]]

    # corner cases in dimension 2
    @test convex_hull([[N(0), N(0)]]) == [[N(0), N(0)]]
    @test convex_hull([[N(1), N(0)], [N(0), N(1)]]) == [[N(1), N(0)], [N(0), N(1)]]
    @test convex_hull([[N(1), N(0)], [N(1), N(0)]]) == [[N(1), N(0)]]

    # corner cases in higher dimension
    @test convex_hull([[N(0), N(0), N(0)]]) == [[N(0), N(0), N(0)]]
    # there is no sorting in higher dim
    @test convex_hull([[N(1), N(0), N(0)], [N(0), N(1), N(0)]]) == [[N(1), N(0), N(0)], [N(0), N(1), N(0)]]

    # dimension 1
    points_1D = [[N(2)], [N(1)], [N(1/2)], [N(-5)]]
    @test convex_hull(points_1D) == [[N(-5)], [N(2)]]
    convex_hull!(points_1D) # check in-place version
    @test points_1D == [[N(-5)], [N(2)]]

    # dimension 2
    points_2D = [[N(1), N(0)], [N(1), N(1)], [N(0), N(1)], [N(-1), N(-1)], [N(1/2), N(1/2)]]
    @test convex_hull(points_2D) == [[N(-1), N(-1)], [N(1), N(0)], [N(1), N(1)], [N(0), N(1)]]
    convex_hull!(points_2D) # check in-place version
    @test points_2D == [[N(-1), N(-1)], [N(1), N(0)], [N(1), N(1)], [N(0), N(1)]]

    # higher dimension
    if test_suite_polyhedra && N != Float32 # no backend supporting Float32
        points_3D = [[N(1), N(0), N(4)], [N(1), N(1), N(5)], [N(0), N(1), N(6)],
                     [N(-1), N(-1), N(-7)], [N(1/2), N(1/2), N(-8)], [N(0), N(0), N(0)],
                     [N(1), N(2), N(3)]]
        @test convex_hull(points_3D) == ispermutation([[N(1), N(0), N(4)], [N(1), N(1), N(5)],
                                         [N(0), N(1), N(6)], [N(-1), N(-1), N(-7)],
                                         [N(1/2), N(1/2), N(-8)], [N(1), N(2), N(3)]])
        convex_hull!(points_3D) # check in-place version
        @test points_3D == ispermutation([[N(1), N(0), N(4)], [N(1), N(1), N(5)],
                            [N(0), N(1), N(6)], [N(-1), N(-1), N(-7)],
                            [N(1/2), N(1/2), N(-8)], [N(1), N(2), N(3)]])
    end

    # ============================
    # Binary concrete convex hull
    # ============================
    V1 = VPolygon([[N(1), N(0)], [N(1), N(1)], [N(0), N(1)]])
    V2 = VPolygon([[N(-1), N(-1)], [N(1/2), N(1/2)]])
    ch = convex_hull(V1, V2)
    @test vertices_list(ch) == ispermutation([[N(-1), N(-1)], [N(1), N(0)], [N(1), N(1)], [N(0), N(1)]])

    if test_suite_polyhedra && N != Float32 # no backend supporting Float32
        V1 = VPolytope([[N(1), N(0), N(4)], [N(1), N(1), N(5)], [N(0), N(1), N(6)]])
        V2 = VPolytope([[N(-1), N(-1), N(-7)], [N(1/2), N(1/2), N(-8)], [N(0), N(0), N(0)],
                       [N(1), N(2), N(3)]])
        ch = convex_hull(V1, V2)
        @test ch isa VPolytope
        @test vertices_list(ch) == ispermutation([[N(1), N(0), N(4)], [N(1), N(1), N(5)],
                                    [N(0), N(1), N(6)], [N(-1), N(-1), N(-7)],
                                    [N(1/2), N(1/2), N(-8)], [N(1), N(2), N(3)]])
    end
end


using Test, BenchmarkTools, LinearAlgebra
import YAAD

function bench_tr_mul_base(x1, x2)
    z1 = x1 * x2
    z2 = tr(z1)

    grad_z1 = Matrix{eltype(z1)}(I, size(z1))
    grad_z1 * transpose(x2), transpose(x1) * grad_z1
end
#
# function bench_tr_mul_base(x1, x2)
#     z1 = x1 * x2
#     z2 = tr(z1)
#
#     grads = YAAD.gradient(tr, one(eltype(x1)), z2, z1)
#     YAAD.gradient(*, first(grads), z1, x1, x2)
# end

function bench_tr_mul_yaad(x1, x2)
    z = tr(x1 * x2)
    YAAD.backward(z)
    x1.grad, x2.grad
end

function bench_tr_mul_yaad_static(z, x1, x2)
    YAAD.forward(z)
    YAAD.backward(z)
    x1.grad, x2.grad
end

xv, yv = rand(30, 30), rand(30, 30)

yaad_x = YAAD.Variable(xv)
yaad_y = YAAD.Variable(yv)

@benchmark bench_tr_mul_yaad(yaad_x, yaad_y)

z = tr(yaad_x * yaad_y)
YAAD.forward(z)

@benchmark bench_tr_mul_yaad_static(z, yaad_x, yaad_y)

@benchmark bench_tr_mul_base(xv, yv)

println("----------------------------------------------------------------------")
println("YAAD:")
display(@benchmark bench_tr_mul_yaad(yaad_x, yaad_y))
println()

function bench_tr_mul_yaad(x1, x2)
    forward(z)
    YAAD.backward(z)
    x1.grad, x2.grad
end

x = Variable(rand(100, 100))
y = Variable(rand(100, 100))

z = tr(x * y)
@profiler for i in 1:100000
    bench_tr_mul_yaad(z, x, y)
end


##### Generic methods #####

## sampling

function rand!(d::MultivariateDistribution, x::AbstractVector)
    length(x) == length(d) ||
        throw(DimensionMismatch("Output size inconsistent with sample length."))
    _rand!(d, x)
end

function rand!(d::MultivariateDistribution, A::AbstractMatrix)
    size(A,1) == length(d) ||
        throw(DimensionMismatch("Output size inconsistent with sample length."))
    _rand!(sampler(d), A)
end

rand(d::MultivariateDistribution) = _rand!(d, Array(eltype(d), length(d)))
rand(d::MultivariateDistribution, n::Int) = _rand!(sampler(d), Array(eltype(d), length(d), n))

## domain

function insupport!{D<:MultivariateDistribution}(r::AbstractArray, d::Union{D,Type{D}}, X::AbstractMatrix)
    n = length(r)
    size(X) == (length(d),n) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    for i in 1:n
        @inbounds r[i] = insupport(d, view(X, :, i))
    end
    return r
end

insupport{D<:MultivariateDistribution}(d::Union{D,Type{D}}, X::AbstractMatrix) =
    insupport!(BitArray(size(X,2)), d, X)

## statistics

entropy(d::MultivariateDistribution, b::Real) = entropy(d) / log(b)

function cor(d::MultivariateDistribution)
    C = cov(d)
    n = size(C, 1)
    @assert size(C, 2) == n
    R = Array(eltype(C), n, n)

    for j = 1:n
        for i = 1:j-1
            @inbounds R[i, j] = R[j, i]
        end
        R[j, j] = 1.0
        for i = j+1:n
            @inbounds R[i, j] = C[i, j] / sqrt(C[i, i] * C[j, j])
        end
    end

    return R
end

# pdf and logpdf

_pdf(d::MultivariateDistribution, X::AbstractVector) = exp(_logpdf(d, X))

function logpdf(d::MultivariateDistribution, X::AbstractVector)
    length(X) == length(d) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    _logpdf(d, X)
end

function pdf(d::MultivariateDistribution, X::AbstractVector)
    length(X) == length(d) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    _pdf(d, X)
end

function _logpdf!(r::AbstractArray, d::MultivariateDistribution, X::AbstractMatrix)
    for i in 1 : size(X,2)
        @inbounds r[i] = logpdf(d, view(X,:,i))
    end
    return r
end

function _pdf!(r::AbstractArray, d::MultivariateDistribution, X::AbstractMatrix)
    for i in 1 : size(X,2)
        @inbounds r[i] = pdf(d, view(X,:,i))
    end
    return r
end

function logpdf!(r::AbstractArray, d::MultivariateDistribution, X::AbstractMatrix)
    size(X) == (length(d), length(r)) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    _logpdf!(r, d, X)
end

function pdf!(r::AbstractArray, d::MultivariateDistribution, X::AbstractMatrix)
    size(X) == (length(d), length(r)) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    _pdf!(r, d, X)
end

function logpdf(d::MultivariateDistribution, X::AbstractMatrix)
    size(X, 1) == length(d) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    T = promote_type(partype(d), eltype(X))
    _logpdf!(Array(T, size(X,2)), d, X)
end

function pdf(d::MultivariateDistribution, X::AbstractMatrix)
    size(X, 1) == length(d) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    T = promote_type(partype(d), eltype(X))
    _pdf!(Array(T, size(X,2)), d, X)
end

## log likelihood

function _loglikelihood(d::MultivariateDistribution, X::AbstractMatrix)
    ll = 0.0
    for i in 1:size(X, 2)
        ll += _logpdf(d, view(X,:,i))
    end
    return ll
end

function loglikelihood(d::MultivariateDistribution, X::AbstractMatrix)
    size(X,1) == length(d) ||
        throw(DimensionMismatch("Inconsistent array dimensions."))
    _loglikelihood(d, X)
end

##### Specific distributions #####

for fname in ["dirichlet.jl",
              "multinomial.jl",
              "mvnormal.jl",
              "mvnormalcanon.jl",
              "mvlognormal.jl",
              "mvtdist.jl",
              "vonmisesfisher.jl"]
    include(joinpath("multivariate", fname))
end


# utils_TaylorN.jl: N-variables Taylor expansions
#
# Last modification: 2014.06.07
#
# Luis Benet & David P. Sanders
# UNAM
#

## Default values for the maximum degree of polynomials (MAXORDER) and
##  number of variables considered (NUMVARS)
const MAXORDER = [4]
const NUMVARS = [2]

## Hash tables
"""
`generateIndicesTable`: generates the dictionary `indicesTable`.
NUMVARS: number of variables for the polynomial expansions <--> lNiv
MAXORDER: maximum degree of polynomials <--> nPart
`generatePosTable`: generates the dictionary `posTable`; is the "inverse" of `indicesTable`
"""
function generateIndicesTable()
    numVars::Int = NUMVARS[end]
    maxDeg::Int = MAXORDER[end]
    DDic = Dict{Int, Array{Int,1}}()
    #
    if numVars==1
        info( string("Using `Taylor` constructor rather than `TaylorN` is\n",
            "MUCH faster for 1-variable expansions.\n") )
        for k = 0:maxDeg
            DDic[k+1] = [k]
        end
        return DDic
    end
    #
    nCoefTot = binomial( numVars+maxDeg, maxDeg)
    iindices = zeros(Int, numVars)
    pos = 0
    for kDeg = 0:maxDeg
        iV = numVars
        for iz = 0:kDeg
            iindices[end] = iz
            @inbounds pos, DDic[pos] = pos2indices!( iV, kDeg, iindices, pos, DDic)
        end
    end
    return DDic
end
function pos2indices!(iV::Int, kDeg::Int, iIndices::Array{Int,1}, pos::Int, dict::Dict{Int,Array{Int,1}})
    #
    jVar = iV-1
    kDegNext = kDeg - iIndices[iV]
    if jVar > 1
        for jDeg=0:kDegNext
            iIndices[jVar] = jDeg
            @inbounds pos, dict[pos] = pos2indices!( jVar, kDegNext, iIndices, pos, dict)
        end
    else
        iIndices[1] = kDegNext
        pos += 1
        @inbounds dict[pos] = iIndices[1:end]
    end
    return pos, iIndices[1:end]
end

const indicesTable = [ generateIndicesTable() ]
const sizeTable = [ length( indicesTable[end]) ]

function generatePosTable()
    DDic = Dict{Array{Int,1}, Int}()
    for i = 1:sizeTable[end]
        v = indicesTable[end][i]
        DDic[ v ] = i
    end
    return DDic
end
const posTable = [ generatePosTable() ]

## Functions to obtain the number of homogenous coefficients of given degree
# """Returns the number of homogeneous coefficients of degree k for NUMVARS:
#     binomial(k+NUMVARS-1,k)"""
# function numHomogCoefK(k::Int)
#     k == 0 && return 1
#     return binomial( k + NUMVARS[end] - 1, k )
# end
"""Returns the position of the first homogeneous coefficient of degree k for NUMVARS:
    binomial(k+NUMVARS-1,k-1)+1"""
function posHomogCoefK(k::Int)
    k == 0 && return 1
    return binomial( k + NUMVARS[end]-1, k-1 ) + 1
end
"""Generates the auxiliary table of the initial and final position for the coefficients of
homogeneous polynomial of given degree (`posAuxTable`)"""
function generateAuxTable()
    DDic = Dict{Int,(Int, Int)}()
    for k=0:MAXORDER[end]
        posI = posHomogCoefK(k)
        posF = posHomogCoefK(k+1)-1
        DDic[k] = ( posI, posF )
    end
    return DDic
end
const posAuxTable = [ generateAuxTable() ]

## Utilities to get/set MAXORDER and NUMVARS; they reset the indicesTable
get_maxOrder() = MAXORDER[end]
function set_maxOrder(n::Int)
    @assert n >= 0
    MAXORDER[end] = n
    info(string("MAXORDER is now ", n, "; hash tables regenerated.\n"))
    indicesTable[end] = generateIndicesTable()
    sizeTable[end] = length( indicesTable[end] )
    posTable[end] = generatePosTable()
    posAuxTable[end] = generateAuxTable()
    return n
end
#
get_numVars() = NUMVARS[end]
function set_numVars(n::Int)
    @assert n > 0
    NUMVARS[end] = n
    info(string("NUMVARS is now ", n, "; hash tables regenerated.\n"))
    indicesTable[end] = generateIndicesTable()
    sizeTable[end] = length( indicesTable[end] )
    posTable[end] = generatePosTable()
    posAuxTable[end] = generateAuxTable()
    return n
end

## TaylorN Constructors ##
immutable TaylorN{T<:Number} <: AbstractSeries{T,NUMVARS[end]}
   coeffs :: Array{T,1}
   order :: Int
   numVars :: Int
   function TaylorN(coeffs::Array{T,1}, order::Int, numVars::Int)
        @assert order <= MAXORDER[end]
        lencoef = length(coeffs)
        nCoefTot = sizeTable[end]
        @assert lencoef <= nCoefTot
        v = zeros(T, nCoefTot)
        #@inbounds v[1:lencoef] = coeffs[1:lencoef]
        for i = 1:lencoef
            @inbounds v[i] = coeffs[i]
        end
        new(v, order, NUMVARS[end])
   end
end
TaylorN{T<:Number}(x::TaylorN{T}, order::Int) = TaylorN{T}(x.coeffs, order, NUMVARS[end])
TaylorN{T<:Number}(x::TaylorN{T}) = TaylorN{T}(x.coeffs, x.order, NUMVARS[end])
#
TaylorN{T<:Number}(coeffs::Array{T,1}, order::Int) = TaylorN{T}(coeffs, order, NUMVARS[end])
TaylorN{T<:Number}(coeffs::Array{T,1}) = TaylorN{T}(coeffs, MAXORDER[end], NUMVARS[end])
#
TaylorN{T<:Number}(x::T, order::Int) = TaylorN{T}([x], order, NUMVARS[end])
TaylorN{T<:Number}(x::T) = TaylorN{T}([x], 0, NUMVARS[end])

## Type, length ##
eltype{T<:Number}(::TaylorN{T}) = T
length(a::TaylorN) = length( a.coeffs )
get_numVars(x::TaylorN) = x.numVars
get_maxOrder(x::TaylorN) = x.order

## Conversion and promotion rules ##
convert{T<:Number}(::Type{TaylorN{T}}, a::TaylorN) = TaylorN(convert(Array{T,1}, a.coeffs), a.order)
convert{T<:Number, S<:Number}(::Type{TaylorN{T}}, b::Array{S,1}) = TaylorN(convert(Array{T,1},b))
convert{T<:Number}(::Type{TaylorN{T}}, b::Number) = TaylorN([convert(T,b)], 0)
promote_rule{T<:Number, S<:Number}(::Type{TaylorN{T}}, ::Type{TaylorN{S}}) = TaylorN{promote_type(T, S)}
promote_rule{T<:Number, S<:Number}(::Type{TaylorN{T}}, ::Type{Array{S,1}}) = TaylorN{promote_type(T, S)}
promote_rule{T<:Number, S<:Number}(::Type{TaylorN{T}}, ::Type{S}) = TaylorN{promote_type(T, S)}

## Auxiliary functions ##
# function firstnonzero{T<:Number}(a::Taylor{T})
#     order = a.order
#     nonzero::Int = order+1
#     z = zero(T)
#     for i = 1:order+1
#         if a.coeffs[i] != z
#             nonzero = i-1
#             break
#         end
#     end
#     nonzero
# end
function fixshape{T<:Number, S<:Number}(a::TaylorN{T}, b::TaylorN{S})
    @assert a.numVars == b.numVars
    order = max(a.order, b.order)
    a1, b1 = promote(a, b)
    return TaylorN(a1, order), TaylorN(b1, order), order
end

## real, imag, conj and ctranspose ##
for f in (:real, :imag, :conj)
    @eval ($f){T<:Number}(a::TaylorN{T}) = TaylorN(($f)(a.coeffs), a.order)
end
ctranspose{T<:Number}(a::TaylorN{T}) = conj(a)

## zero and one ##
zero{T<:Number}(a::TaylorN{T}) = TaylorN(zero(T), a.order)
one{T<:Number}(a::TaylorN{T}) = TaylorN(one(T), a.order)

## Equality ##
function ==(a::TaylorN, b::TaylorN)
    a1, b1, order = fixshape(a, b)
    return a1.coeffs == b1.coeffs
end

## Addition and substraction ##
for f in (:+, :-)
    @eval begin
        function ($f)(a::TaylorN, b::TaylorN)
            a1, b1, order = fixshape(a, b)
            v = ($f)(a1.coeffs, b1.coeffs)
            return TaylorN(v, order)
        end
        ($f)(a::TaylorN) = TaylorN(($f)(a.coeffs), a.order)
    end
end

## Multiplication ##
function *(a::TaylorN, b::TaylorN)
    a1, b1, order = fixshape(a, b)
    T = eltype(a1)
    nCoefTot = sizeTable[end]
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = a1.coeffs[1] * b1.coeffs[1]   ## 0th-order
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = mulHomogCoefN( k, a1.coeffs, b1.coeffs )
    end
    TaylorN(coeffs, order)
end
function mulHomogCoefN{T<:Number}( k::Int, ac::Array{T,1}, bc::Array{T,1} )
    k==0 && return ac[1] * bc[1]
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    coeffs = zeros(T, numCoefk)
    for ka = 0:k
        kb = k-ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds coeffs[pos] += ac[ja] * bc[jb]
            end
        end
    end
    return coeffs
end

## Division ##
function /(a::TaylorN, b::TaylorN)
    a1, b1, order = fixshape(a, b)
    #!?orddivfact, cdivfact = divfactorization(a1, b1) # order and coefficient of first factorized term
    @assert b1.coeffs[1] != zero(b1.coeffs[1])
    cLHopital = a1.coeffs[1] / b1.coeffs[1]
    T = typeof(cLHopital)
    v1 = convert(Array{T,1}, a1.coeffs)
    v2 = convert(Array{T,1}, b1.coeffs)
    nCoefTot = sizeTable[end]
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = cLHopital
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = divHomogCoefN( k, v1, v2, coeffs, 0 )
    end
    TaylorN(coeffs, order)
end
# function divfactorization(a1::Taylor, b1::Taylor)
#     # order of first factorized term; a1 and b1 are assumed to be of the same order (length)
#     a1nz = firstnonzero(a1)
#     b1nz = firstnonzero(b1)
#     orddivfact = min(a1nz, b1nz)
#     if orddivfact > a1.order
#         orddivfact = a1.order
#     end
#     cdivfact = a1.coeffs[orddivfact+1] / b1.coeffs[orddivfact+1]
#     aux = abs2(cdivfact)
#     # Is the polynomial factorizable?
#     if isinf(aux) || isnan(aux)
#         info("Order k=$(orddivfact) => coeff[$(orddivfact+1)]=$(cdivfact)")
#         error("Division does not define a Taylor polynomial\n",
#             " or its first non-zero coefficient is Inf/NaN.\n")
#     ##else orddivfact>0
#     ##    warn("Factorizing the polynomial.\n",
#     ##        "The last k=$(orddivfact) Taylor coefficients ARE SET to 0.\n")
#     end
#     return orddivfact, cdivfact
# end
# Homogeneous coefficient for the division
function divHomogCoefN{T<:Number}( k::Int, ac::Array{T,1}, bc::Array{T,1},
    coeffs::Array{T,1}, ordLHopital::Int)
    #
    k == ordLHopital && return ac[ordLHopital+1] / bc[ordLHopital+1]
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    cc = zeros(T, numCoefk)
    @inbounds cc = mulHomogCoefN(k, coeffs, bc)
    @inbounds cc[1:end] = (ac[posI:posF]-cc[1:end]) / bc[ordLHopital+1]
    cc
end

## Division functions: rem and mod
for op in (:mod, :rem)
    @eval begin
        function ($op){T<:Real}(a::TaylorN{T}, x::Real)
            coeffs = a.coeffs
            coeffs[1] = ($op)(a.coeffs[1], x)
            return TaylorN( coeffs, a.order )
        end
    end
end
function mod2pi{T<:Real}(a::TaylorN{T})
    coeffs = a.coeffs
    coeffs[1] = mod2pi( a.coeffs[1] )
    return TaylorN( coeffs, a.order )
end

## Int power ##
function ^(a::TaylorN, n::Integer)
    uno = one(a)
    n < 0 && return uno / a^(-n)
    n == 0 && return uno
    if n%2 == 0     # even power
        n == 2 && return square(a)
        pow = div(n, 2)
        return square( a^pow )
    else            # odd power
        n == 1 && return a
        pow = div(n-1, 2)
        return a*square( a^pow )
    end
end
## Rational power ##
^(a::TaylorN,x::Rational) = a^(x.num/x.den)
## Real power ##
function ^(a::TaylorN, x::Real)
    uno = one(a)
    x == zero(x) && return uno
    x == 0.5 && return sqrt(a)
    order = a.order
    # First non-zero coefficient
    # ...
    @assert a.coeffs[1] != zero(a.coeffs[1])
    aux = ( a.coeffs[1] )^x
    T = typeof(aux)
    v = convert(Array{T,1}, a.coeffs)
    nCoefTot = sizeTable[end]
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = aux
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = powHomogCoefN(k, v, x, coeffs, 0)
    end
    TaylorN(coeffs,order)
end
function powHomogCoefN{T<:Number}(k::Int, ac::Array{T,1}, x::Real,
    coeffs::Array{T,1}, knull::Int)
    #
    k == knull && return (ac[ordLHopital+1])^x
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    cc = zeros(T, numCoefk)
    for ka = 0:k-1
        kb = k-ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds cc[pos] += ( x*kb-ka ) * ac[jb] * coeffs[ja]
            end
        end
    end
    @inbounds cc[1:end] = cc[1:end] / ( k * ac[1] )
    cc
end
^{T<:Number,S<:Number}(a::TaylorN{T}, x::Complex{S}) = exp( x*log(a) )
^(a::TaylorN, b::TaylorN) = exp( b*log(a) )

## Square ##
function square{T<:Number}(a::TaylorN{T})
    order = a.order
    nCoefTot = sizeTable[end]
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = a.coeffs[1]^2
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = squareHomogCoefN( k, a.coeffs )
    end
    TaylorN(coeffs,order)
end
# Homogeneous coefficients for square
function squareHomogCoefN{T<:Number}(k::Int, ac::Array{T,1})
    k == 0 && return (ac[1])^2
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    coeffs = zeros(T, numCoefk)
    kodd = k%2
    kend = div(k - 2 + kodd, 2)
    for ka = 0:kend
        kb = k - ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds coeffs[pos] += ac[ja] * ac[jb]
            end
        end
    end
    @inbounds coeffs[1:end] = 2coeffs[1:end]
    #
    if kodd == 0
        ka = div(k,2)
        @inbounds posaI, posaF = posAuxTable[end][ka]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posaI:posaF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds coeffs[pos] += ac[ja] * ac[jb]
            end
        end
    end
    coeffs
end

## sqrt ##
function sqrt(a::TaylorN)
    order = a.order
    nCoefTot = sizeTable[end]
    aux = sqrt(a.coeffs[1])
    T = typeof(aux)
    v = convert(Array{T,1}, a.coeffs)
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = aux
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = sqrtHomogCoefN( k, v, coeffs, 0 )
    end
    TaylorN(coeffs,order)
end
# Homogeneous coefficients for sqrt
function sqrtHomogCoefN{T<:Number}(k::Int, ac::Array{T,1}, coeffs::Array{T,1}, knull::Int)
    k == 0 && return sqrt( ac[1] )
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    cc = zeros(T, numCoefk)
    kodd = k%2
    kend = div(k - 2 + kodd, 2)
    for ka = 1:kend
        kb = k - ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds cc[pos] += coeffs[ja] * coeffs[jb]
            end
        end
    end
    @inbounds cc[1:end] = 2cc[1:end]
    #
    if kodd == 0
        ka = div(k,2)
        @inbounds posaI, posaF = posAuxTable[end][ka]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posaI:posaF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds cc[pos] += coeffs[ja] * coeffs[jb]
            end
        end
    end
    @inbounds cc[1:end] = (ac[posI:posF] - cc[1:end]) / (2coeffs[1])
    cc
end

## exp ##
function exp(a::TaylorN)
    order = a.order
    nCoefTot = sizeTable[end]
    aux = exp(a.coeffs[1])
    T = typeof(aux)
    v = convert(Array{T,1}, a.coeffs)
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = aux
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = expHomogCoefN( k, v, coeffs )
    end
    TaylorN(coeffs,order)
end
# Homogeneous coefficients for exp
function expHomogCoefN{T<:Number}(k::Int, ac::Array{T,1}, coeffs::Array{T,1})
    k == 0 && return exp( ac[1] )
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    cc = zeros(T, numCoefk)
    for ka = 0:k-1
        kb = k - ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds cc[pos] += kb * coeffs[ja] * ac[jb]
            end
        end
    end
    @inbounds cc[1:end] = cc[1:end] / k
    cc
end

## log ##
function log(a::TaylorN)
    order = a.order
    nCoefTot = sizeTable[end]
    aux = log(a.coeffs[1])
    T = typeof(aux)
    v = convert(Array{T,1}, a.coeffs)
    coeffs = zeros(T, nCoefTot)
    coeffs[1] = aux
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds coeffs[posI:posF] = logHomogCoefN( k, v, coeffs )
    end
    TaylorN(coeffs, order)
end
# Homogeneous coefficients for square
function logHomogCoefN{T<:Number}(k::Int, ac::Array{T,1}, coeffs::Array{T,1})
    k == 0 && return log( ac[1] )
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    cc = zeros(T, numCoefk)
    for ka = 1:k-1
        kb = k - ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds cc[pos] += ka * coeffs[ja] * ac[jb]
            end
        end
    end
    @inbounds cc[1:end] = ( ac[posI:posF] - cc[1:end] / k ) / ac[1]
    cc
end

## sin and cos ##
sin(a::TaylorN) = sincos(a)[1]
cos(a::TaylorN) = sincos(a)[2]
function sincos(a::TaylorN)
    order = a.order
    nCoefTot = sizeTable[end]
    aux = sin(a.coeffs[1])
    T = typeof(aux)
    v = convert(Array{T,1}, a.coeffs)
    sincoeffs = zeros(T,nCoefTot)
    coscoeffs = zeros(T,nCoefTot)
    sincoeffs[1] = aux
    coscoeffs[1] = cos( a.coeffs[1] )
    for k = 1:order
        @inbounds posI, posF = posAuxTable[end][k]
        @inbounds sincoeffs[posI:posF], coscoeffs[posI:posF] =
            sincosHomogCoefN(k, v, sincoeffs, coscoeffs)
    end
    return TaylorN( sincoeffs, order ), TaylorN( coscoeffs, order )
end
# Homogeneous coefficients for sin and cos
function sincosHomogCoefN{T<:Number}(k::Int, ac::Array{T,1}, scoeffs::Array{T,1}, ccoeffs::Array{T,1})
    k == 0 && return sin( ac[1] ), cos( ac[1] )
    @inbounds posI, posF = posAuxTable[end][k]
    numCoefk = posF - posI + 1
    sv = zeros(T, numCoefk)
    cv = zeros(T, numCoefk)
    for ka = 0:k-1
        kb = k - ka
        @inbounds posaI, posaF = posAuxTable[end][ka]
        @inbounds posbI, posbF = posAuxTable[end][kb]
        for ja = posaI:posaF
            @inbounds inda = indicesTable[end][ja]
            for jb = posbI:posbF
                @inbounds indb = indicesTable[end][jb]
                @inbounds pos = posTable[end][ inda + indb ]
                pos = pos - posI + 1
                @inbounds begin
                    x = kb * ac[jb]
                    sv[pos] += x * ccoeffs[ja]
                    cv[pos] -= x * scoeffs[ja]
                end
            end
        end
    end
    @inbounds sv[1:end] = sv[1:end] / k
    @inbounds cv[1:end] = cv[1:end] / k
    sv, cv
end

## Differentiation ##
"""Partial differentiation of a TaylorN series with respect to the r-th variable"""
function diffTaylor{T<:Number}(a::TaylorN{T}, r::Int)
    @assert 1 <= r <= NUMVARS[end]
    order = a.order
    nCoefTot = sizeTable[end]
    coeffs = zeros(T, nCoefTot)
    iIndices = zeros(Int, NUMVARS[end])
    for pos = 1:nCoefTot
        @inbounds iIndices[1:end] = indicesTable[end][pos]
        @inbounds n = iIndices[r]
        n == 0 && continue
        @inbounds iIndices[r] -= 1
        @inbounds posI = posTable[end][ iIndices ]
        @inbounds coeffs[posI] = n * a.coeffs[pos]
    end
    return TaylorN( coeffs, order )
end
diffTaylor(a::TaylorN) = diffTaylor(a, 1)

## TO BE DONE: Integration...

## Evaluates a Taylor polynomial on a given point ##
## NEEDS REVISION since yields only approx results
function evalTaylor{T<:Number,S<:Number}(a::TaylorN{T}, vals::Array{S,1} )
    numVars = NUMVARS[end]
    @assert length(vals) == numVars
    R = promote_type(T,S)
    suma = zero(R)
    nCoefTot = sizeTable[end]
    iIndices = zeros(Int, numVars)
    for pos = nCoefTot:-1:1
        @inbounds iIndices[1:end] = indicesTable[end][pos]
        @inbounds val = a.coeffs[pos]
        val == zero(R) && continue
        for k = 1:numVars
            @inbounds val *= vals[k]^iIndices[k]
        end
        suma += val
    end
    suma
end
evalTaylor{T<:Number}(a::TaylorN{T}) = evalTaylor(a, zeros(T, a.numVars) )


module Day11
export day11

_string_iterator(str) = Iterators.take(str, length(str))

parseLine(line) = parse.(Int64, _string_iterator(line))

function readInput(inputfile="inputs/input11.txt")
    return hcat([parseLine(line) for line in eachline(inputfile)]...)
end

const ADJACENT = let temp = vec(CartesianIndex.(-1:1, (-1:1)'))
    deleteat!(temp, 5)
    temp
end # delete (0,0)

function step!(grid)
    grid .+= 1

    flashed = 0
    for I in CartesianIndices(grid)
        if grid[I] > 9
            flashed += _octopus_flash!(grid, I)
        end
    end
    return flashed
end

function _octopus_flash!(grid, loc)
    grid[loc] = 0
    flashed = 1
    for adj in ADJACENT
        neighbour = loc + adj
        checkbounds(Bool, grid, neighbour) || continue
        if grid[neighbour] > 0 # exclude already flashed
            grid[neighbour] += 1
        end
        if grid[neighbour] > 9
            flashed += _octopus_flash!(grid, neighbour)
        end
    end
    return flashed
end

function day11Part1!(grid, steps=100)
    #return sum(step!(grid) for _ in 1:steps) # less allocs but not really faster
    flashes = [step!(grid) for _ in 1:steps]
    @assert !any(flashes .== length(grid))
    return sum(flashes)
end

function day11Part2!(grid)
    flashed = 0
    round = 0 # from part 1
    while flashed != length(grid)
        flashed = step!(grid)
        round += 1
    end
    return round
end

function day11(inputdata = readInput("inputs/input11.txt"))
    return (day11Part1!(inputdata, 100), 100+day11Part2!(inputdata))
end

end #module


@testset "Testing logic using strings" begin
    Base.one{T<:AbstractString}(::Type{T}) = "one "
    z = ["x " "y " "z "]

    want_1 = ["one " "x " "y " "z "]
    @test complete_polynomial(z, 1) == want_1

    want_2 = ["one " "x " "y " "z " "one x x " "one x y " "one x z " "one y y " "one y z " "one z z "]
    @test complete_polynomial(z, 2) == want_2
end

@testset "testing numerical routintes" begin
    z = rand(100, 10)
    the_ones = ones(size(z, 1))
    for d in 1:5
        n_comp = n_complete(size(z, 2), d)
        buff_d = Array(Float64, size(z, 1), n_comp)
        out_d = complete_polynomial(z, d)
        complete_polynomial!(z, d, buff_d)
        @test size(out_d, 1) == size(z, 1)
        @test size(out_d, 2) == n_comp
        @test out_d[:, 1] == the_ones
        @test out_d[:, 2:size(z, 2)+1] == z
        @test out_d == buff_d
    end

    z = rand(10, 3)
    out_2 = complete_polynomial(z, 2)
    # need to test columns size(z, 2) + 2:end
    @test out_2[:, 5] == z[:, 1].^2
    @test out_2[:, 6] == z[:, 1].*z[:, 2]
    @test out_2[:, 7] == z[:, 1].*z[:, 3]
    @test out_2[:, 8] == z[:, 2].*z[:, 2]
    @test out_2[:, 9] == z[:, 2].*z[:, 3]
    @test out_2[:, 10] == z[:, 3].*z[:, 3]
end


struct GTRrel <: GTR
  α::Float64
  β::Float64
  γ::Float64
  δ::Float64
  ϵ::Float64
  πA::Float64
  πC::Float64
  πG::Float64
  πT::Float64
  function GTRrel(α::Float64, β::Float64, γ::Float64,
                  δ::Float64, ϵ::Float64,
                  πA::Float64, πC::Float64, πG::Float64, πT::Float64)
    if α <= 0.
      error("GTR parameter α must be positive")
    elseif β <= 0.
      error("GTR parameter β must be positive")
    elseif γ <= 0.
      error("GTR parameter γ must be positive")
    elseif δ <= 0.
      error("GTR parameter δ must be positive")
    elseif ϵ <= 0.
      error("GTR parameter ϵ must be positive")
    elseif sum([πA,πC,πG,πT]) != 1.0
      error("GTR frequencies must sum to 1.0")
    elseif any([πA,πC,πG,πT] .<= 0.0)
      error("GTR frequencies must be positive")
    end
    new(α, β, γ, δ, ϵ, πA, πC, πG, πT)
  end
end

function show(io::IO, object::GTRrel)
  print(io, "\r\e[0m\e[1mG\e[0meneralised \e[1mT\e[0mime \e[1mR\e[0meversible model (relative rate form)
α = $(object.α), β = $(object.β), γ = $(object.γ), δ = $(object.δ), ϵ = $(object.ϵ), π = [$(object.πA), $(object.πC), $(object.πG), $(object.πT)]")
end

GTR(α, β, γ, δ, ϵ, πA, πC, πG, πT) = GTRrel(α, β, γ, δ, ϵ, πA, πC, πG, πT)

const _α(mod::GTRrel) = mod.α
const _β(mod::GTRrel) = mod.β
const _γ(mod::GTRrel) = mod.γ
const _δ(mod::GTRrel) = mod.δ
const _ϵ(mod::GTRrel) = mod.ϵ
const _η(mod::GTRrel) = 1.0


@testset "Utilities" begin
    @testset "Crossover/Crossunder" begin
        Random.seed!(SEED)
        x = cumsum(randn(N)) .+ X0
        y = x + randn(N)
        cxo = crossover(x, y)
        cxu = crossunder(x, y)
        @test any(cxo)
        @test any(cxu)
        @test !any(cxo .* cxu)  # ensure crossovers and crossunders never coincide
    end
end