function execute(problem::PotentialFlowProblem{:analytical};kwargs...)

  # Parameters
  L = 2*π
  H = 1.0
  n = 20
  order = 2
  g = 9.81
  ξ = 0.1
  λ = L/2
  k = 2*π/L
  h = L/n
  ω = √(g*k*tanh(k*H))
  t₀ = 0.0
  tf = 8*π
  Δt = h/(10*λ*ω)
  θ = 0.5

  # Exact solution
  ϕₑ(x,t) = ω/k * ξ * (cosh(k*(x[2]))) / sinh(k*H) * sin(k*x[1] - ω*t)
  ηₑ(x,t) = ξ * cos(k*x[1] - ω*t)
  ϕₑ(t::Real) = x -> ϕₑ(x,t)
  ηₑ(t::Real) = x -> ηₑ(x,t)

  # Domain
  domain = (0,L,0,H)
  partition = (n,n)
  model = CartesianDiscreteModel(domain,partition;isperiodic=(true,false))

  # Boundaries
  labels = get_face_labeling(model)
  add_tag_from_tags!(labels,"bottom",[1,2,5])
  add_tag_from_tags!(labels,"free_surface",[3,4,6])
  bgface_to_mask = get_face_mask(labels,[3,4,6],1)
  model_Γ =BoundaryDiscreteModel(Polytope{1},model,bgface_to_mask)

  # Triangulation
  Ω = Triangulation(model)
  Γ = Triangulation(model_Γ)
  dΩ = Measure(Ω,2*order)
  dΓ = Measure(Γ,2*order)

  # FE spaces
  reffe = ReferenceFE(lagrangian,Float64,order)
  V = TestFESpace(model,reffe,conformity=:H1)
  V_Γ = TestFESpace(model_Γ,reffe,conformity=:H1)
  U = TransientTrialFESpace(V)
  U_Γ = TransientTrialFESpace(V_Γ)
  X = TransientMultiFieldFESpace([U,U_Γ])
  Y = MultiFieldFESpace([V,V_Γ])

  # Weak form
  α = 2/Δt
  m((ϕt,ηt),(w,v)) = ∫( 0.5*(α/g*(w*ϕt) + v*ϕt) - (w*ηt) )dΓ
  a((ϕ,η),(w,v)) = ∫( ∇(ϕ)⋅∇(w) )dΩ + ∫( 0.5*(α*(w*η) + g*v*η) )dΓ
  b((w,v)) = ∫( 0.0*w )dΓ
  op = TransientConstantFEOperator(m,a,b,X,Y)

  # Solver
  ls = LUSolver()
  odes = ThetaMethod(ls,Δt,θ)
  solver = TransientFESolver(odes)

  # Initial solution
  x₀ = interpolate_everywhere([ϕₑ(0.0),ηₑ(0.0)],X(0.0))

  # Solution
  sol_t = solve(solver,op,x₀,t₀,tf)

  # Post-process
  l2_Ω(w) = √(∑(∫(w*w)dΩ))
  l2_Γ(v) = √(∑(∫(v*v)dΓ))
  E_kin(w) = 0.5*∑( ∫(∇(w)⋅∇(w))dΩ )
  E_pot(v) = g*0.5*∑( ∫(v*v)dΓ )
  Eₑ = 0.5*g*ξ^2*L

  folderName = "ϕFlow-results"
  if !isdir(folderName)
    mkdir(folderName)
  end
  filePath_Ω = folderName * "/fields_Ω"
  filePath_Γ = folderName * "/fields_Γ"
  pvd_Ω = paraview_collection(filePath_Ω, append=false)
  pvd_Γ = paraview_collection(filePath_Γ, append=false)
  for ((ϕn,ηn),tn) in sol_t
    E = E_kin(ϕn) + E_pot(ηn)
    error_ϕ = l2_Ω(ϕn-ϕₑ(tn))
    error_η = l2_Γ(ηn-ηₑ(tn))
    #println(E/Eₑ," ", error_ϕ," ",error_η)

    pvd_Ω[tn] = createvtk(Ω,filePath_Ω * "_$tn.vtu",cellfields = ["phi" => ϕn])
    pvd_Γ[tn] = createvtk(Γ,filePath_Γ * "_$tn.vtu",cellfields = ["eta" => ηn])
  end
  vtk_save(pvd_Ω)
  vtk_save(pvd_Γ)
end

include("PotentialFlowBeam.jl")


println("Testing Unscented Kalman Filter...")

Δt = 0.1 # Set the time step

#Set the process function
function processFunction(x::Vector, Δt::Float64=0.1, μ::Float64=0.3)
    x1 = zeros(x)
    x1[1] = x[1] + Δt * -x[2]
    x1[2] = x[2] + Δt * (-μ * (1 - x[1]^2) * x[2] + x[1])
    x1
end

#Set the observation function.
observationFunction(x::Vector) = x

#Set process noise covariance matrix
processCovariance = 1e-2*[0.1 0;
    0 1e-3]

#Set the observation noise covariance
observationCovariance = 1e-1*eye(2)

#Create additive noise UKF model
ukfStateModel = NonlinearGaussianSSM(processFunction, processCovariance,
observationFunction, observationCovariance)
#End Section: Set the Unscented Kalman Filter Parameters
################################################################################

################################################################################
#Section: Generate noisy observations
#-------------------------------------------------------------------------------
#Set the true initial state of the oscillator
trueInitialState = [1.4, 0.0]

#Set the number of observations and generate the true values and the noisy
#observations.
numObs = 200
trueState = zeros(2,numObs)
noisyObs = zeros(2,numObs)
trueState[:,1] = trueInitialState
noisyObs[:,1] = trueInitialState + sqrt(observationCovariance)*randn(2)
for i in 2:numObs
    trueState[:,i] = processFunction(trueState[:,i-1])
    noisyObs[:,i] = trueState[:,i] + sqrt(observationCovariance)*randn(2)
end
#End Section: Generate noisy observations
################################################################################

################################################################################
#Section: Set guess of initial state
#-------------------------------------------------------------------------------
initial_guess = MvNormal([0.0,5.0], 5.0*eye(2))
#End Section: Set guess of initial state
################################################################################

################################################################################
#Section: Execute the Additive noise Unscented Kalman Filter
#-------------------------------------------------------------------------------
filtered_state = filter(ukfStateModel, noisyObs, initial_guess)
#End Section: Execute the Additive noise Unscented Kalman Filter
################################################################################

println("Unscented Kalman Filter passed.\n")


using SchumakerSpline

x = [1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6]
y = log(x) + sqrt(x)

Spline, DerivSpline, SecondDerivSpline = schumaker(x,y)

xArray = linspace(1, 6, 100)
Spp = SecondDerivSpline(xArray)

maximum(Spp) < 2*eps()


## tests of PhyloNetworklm

@testset "phyloNetworklm on small network" begin

tree_str= "(A:2.5,((B:1,#H1:0.5::0.1):1,(C:1,(D:0.5)#H1:0.5::0.9):1):0.5);"
net = readTopology(tree_str)
preorder!(net)
# printEdges(net)
# plot(net, useEdgeLength = true,  showEdgeLength=true, showGamma=true)

# Rk: Is there a way to check that the branch length are coherent with
# one another (Especialy for hybrids) ?
# Not yet (CA, 2016-12-01).
# Would be great to add functions to calculate distance node - root.
# several such distances depending on path: 2 parent choices at each hybrid

# Ancestral state reconstruction with ready-made matrices
params = ParamsBM(10, 1)
srand(2468) # sets the seed for reproducibility, to debug potential error
sim = simulate(net, params)
Y = sim[:Tips]
X = ones(4, 1)
phynetlm = phyloNetworklm(X, Y, net)
@show phynetlm
# Naive version (GLS)
ntaxa = length(Y)
Vy = phynetlm.Vy
Vyinv = inv(Vy)
XtVyinv = X' * Vyinv
logdetVy = logdet(Vy)
betahat = inv(XtVyinv * X) * XtVyinv * Y
fittedValues =  X * betahat
resids = Y - fittedValues
sigma2hat = 1/ntaxa * (resids' * Vyinv * resids)
# log likelihood
loglik = - 1 / 2 * (ntaxa + ntaxa * log(2 * pi) + ntaxa * log(sigma2hat) + logdetVy)
# null version
nullX = ones(ntaxa, 1)
nullXtVyinv = nullX' * Vyinv
nullresids = Y - nullX * inv(nullXtVyinv * nullX) * nullXtVyinv * Y
nullsigma2hat = 1/ntaxa * (nullresids' * Vyinv * nullresids)
nullloglik = - 1 / 2 * (ntaxa + ntaxa * log(2 * pi) + ntaxa * log(nullsigma2hat) + logdetVy)

@test coef(phynetlm) ≈ betahat
@test nobs(phynetlm) ≈ ntaxa
@test residuals(phynetlm) ≈ resids
@test model_response(phynetlm) ≈ Y
@test predict(phynetlm) ≈ fittedValues
@test dof_residual(phynetlm) ≈ ntaxa-length(betahat)
@test sigma2_estim(phynetlm) ≈ sigma2hat
@test loglikelihood(phynetlm) ≈ loglik
@test vcov(phynetlm) ≈ sigma2hat*ntaxa/(ntaxa-length(betahat))*inv(XtVyinv * X)
@test stderr(phynetlm) ≈ sqrt.(diag(sigma2hat*ntaxa/(ntaxa-length(betahat))*inv(XtVyinv * X)))
@test dof(phynetlm) ≈ length(betahat)+1
@test deviance(phynetlm) ≈ sigma2hat * ntaxa
@test nulldeviance(phynetlm) ≈ nullsigma2hat * ntaxa
@test nullloglikelihood(phynetlm) ≈ nullloglik
@test loglikelihood(phynetlm) ≈ nullloglikelihood(phynetlm)
@test deviance(phynetlm) ≈ nulldeviance(phynetlm)
@test r2(phynetlm) ≈ 1-sigma2hat / nullsigma2hat atol=1e-15
@test adjr2(phynetlm) ≈ 1 - (1 - (1-sigma2hat/nullsigma2hat))*(ntaxa-1)/(ntaxa-length(betahat)) atol=1e-15
@test aic(phynetlm) ≈ -2*loglik+2*(length(betahat)+1)
@test aicc(phynetlm) ≈ -2*loglik+2*(length(betahat)+1)+2(length(betahat)+1)*((length(betahat)+1)+1)/(ntaxa-(length(betahat)+1)-1)
@test bic(phynetlm) ≈ -2*loglik+(length(betahat)+1)*log(ntaxa)

# with data frames
dfr = DataFrame(trait = Y, tipNames = sim.M.tipNames)
fitbis = phyloNetworklm(@formula(trait ~ 1), dfr, net)
#@show fitbis

@test coef(phynetlm) ≈ coef(fitbis)
@test vcov(phynetlm) ≈ vcov(fitbis)
@test nobs(phynetlm) ≈ nobs(fitbis)
@test residuals(phynetlm)[fitbis.model.ind] ≈ residuals(fitbis)
@test model_response(phynetlm)[fitbis.model.ind] ≈ model_response(fitbis)
@test predict(phynetlm)[fitbis.model.ind] ≈ predict(fitbis)
@test dof_residual(phynetlm) ≈ dof_residual(fitbis)
@test sigma2_estim(phynetlm) ≈ sigma2_estim(fitbis)
@test stderr(phynetlm) ≈ stderr(fitbis)
@test confint(phynetlm) ≈ confint(fitbis)
@test loglikelihood(phynetlm) ≈ loglikelihood(fitbis)
@test dof(phynetlm) ≈ dof(fitbis)
@test deviance(phynetlm) ≈ deviance(fitbis)
@test nulldeviance(phynetlm) ≈ nulldeviance(fitbis)
@test nullloglikelihood(phynetlm) ≈ nullloglikelihood(fitbis)
@test r2(phynetlm) ≈ r2(fitbis) atol=1e-15
@test adjr2(phynetlm) ≈ adjr2(fitbis) atol=1e-15
@test aic(phynetlm) ≈ aic(fitbis)
@test aicc(phynetlm) ≈ aicc(fitbis)
@test bic(phynetlm) ≈ bic(fitbis)
tmp = (@test_warn "You fitted the data against a custom matrix" mu_estim(phynetlm))
@test tmp ≈ mu_estim(fitbis)

## fixed values parameters
fitlam = phyloNetworklm(@formula(trait ~ 1), dfr, net, model = "lambda", fixedValue=1.0)
@show fitlam

@test lambda_estim(fitlam) ≈ 1.0
@test coef(fitlam) ≈ coef(fitbis)
@test vcov(fitlam) ≈ vcov(fitbis)
@test nobs(fitlam) ≈ nobs(fitbis)
@test residuals(fitlam)[fitbis.model.ind] ≈ residuals(fitbis)
@test model_response(fitlam)[fitbis.model.ind] ≈ model_response(fitbis)
@test predict(fitlam)[fitbis.model.ind] ≈ predict(fitbis)
@test dof_residual(fitlam) ≈ dof_residual(fitbis)
@test sigma2_estim(fitlam) ≈ sigma2_estim(fitbis)
@test stderr(fitlam) ≈ stderr(fitbis)
@test confint(fitlam) ≈ confint(fitbis)
@test loglikelihood(fitlam) ≈ loglikelihood(fitbis)
@test dof(fitlam) ≈ dof(fitbis) + 1
@test deviance(fitlam) ≈ deviance(fitbis)
@test nulldeviance(fitlam) ≈ nulldeviance(fitbis)
@test nullloglikelihood(fitlam) ≈ nullloglikelihood(fitbis)
@test r2(fitlam) ≈ r2(fitbis) atol=1e-15
@test adjr2(fitlam) ≈ adjr2(fitbis) - 0.5 atol=1e-15
@test aic(fitlam) ≈ aic(fitbis) + 2
#@test aicc(fitlam) ≈ aicc(fitbis)
@test bic(fitlam) ≈ bic(fitbis) + log(nobs(fitbis))
@test mu_estim(fitlam) ≈ mu_estim(fitbis)

fitSH = phyloNetworklm(@formula(trait ~ 1), dfr, net, model = "scalingHybrid", fixedValue = 1.0)
@test loglikelihood(fitlam) ≈ loglikelihood(fitSH)
@test aic(fitlam) ≈ aic(fitSH)

## Pagel's Lambda
fitlam = (@test_warn "Maximum lambda value" phyloNetworklm(@formula(trait ~ 1), dfr, net, model = "lambda"))
@test lambda_estim(fitlam) ≈ 1.24875

## Scaling Hybrid
fitSH = phyloNetworklm(@formula(trait ~ 1), dfr, net, model = "scalingHybrid")
@test lambda_estim(fitSH) ≈ 4.057891910001937 atol=1e-8

end

###############################################################################
### With shifts
###############################################################################
@testset "Shifts and Transgressive Evolution" begin

net = readTopology("(((Ag:5,(#H1:1::0.056,((Ak:2,(E:1,#H2:1::0.004):1):1,(M:2)#H2:1::0.996):1):1):1,(((((Az:1,Ag2:1):1,As:2):1)#H1:1::0.944,Ap:4):1,Ar:5):1):1,(P:4,20:4):3,165:7);");
preorder!(net)

## Simulate
params = ParamsBM(10, 0.1, shiftHybrid([3.0, -3.0],  net))
srand(2468) # sets the seed for reproducibility, to debug potential error
sim = simulate(net, params)
Y = sim[:Tips]

## Construct regression matrix
dfr_shift = regressorShift(net.edge[[8,17]], net)
dfr_shift[:sum] = vec(sum(Array(dfr_shift[:,find(names(dfr_shift) .!= :tipNames)]), 2))
dfr_hybrid = regressorHybrid(net)

@test dfr_shift[:shift_8] ≈ dfr_hybrid[:shift_8]
@test dfr_shift[:shift_17] ≈ dfr_hybrid[:shift_17]
@test dfr_shift[:sum] ≈ dfr_hybrid[:sum]

## Data
dfr = DataFrame(trait = Y, tipNames = sim.M.tipNames)
dfr = join(dfr, dfr_hybrid, on=:tipNames)

## Simple BM
fitShift = phyloNetworklm(@formula(trait ~ shift_8 + shift_17), dfr, net)
@show fitShift

## Test against fixed values lambda models
fitlam = phyloNetworklm(@formula(trait ~ shift_8 + shift_17), dfr, net, model = "lambda", fixedValue = 1.0)

@test lambda_estim(fitlam) ≈ 1.0
@test coef(fitlam) ≈ coef(fitShift)
@test vcov(fitlam) ≈ vcov(fitShift)
@test nobs(fitlam) ≈ nobs(fitShift)
@test residuals(fitlam) ≈ residuals(fitShift)
@test model_response(fitlam) ≈ model_response(fitShift)
@test predict(fitlam) ≈ predict(fitShift)
@test dof_residual(fitlam) ≈ dof_residual(fitShift)
@test sigma2_estim(fitlam) ≈ sigma2_estim(fitShift)
@test stderr(fitlam) ≈ stderr(fitShift)
@test confint(fitlam) ≈ confint(fitShift)
@test loglikelihood(fitlam) ≈ loglikelihood(fitShift)
@test dof(fitlam) ≈ dof(fitShift) + 1
@test deviance(fitlam)  ≈ deviance(fitShift)
@test nulldeviance(fitlam)  ≈ nulldeviance(fitShift)
@test nullloglikelihood(fitlam)  ≈ nullloglikelihood(fitShift)
@test r2(fitlam) ≈ r2(fitShift) atol=1e-15
#@test adjr2(fitlam) ≈ adjr2(fitShift) - 0.5 atol=1e-15
@test aic(fitlam) ≈ aic(fitShift) + 2
#@test aicc(fitlam)  ≈ aicc(fitShift)
@test bic(fitlam) ≈ bic(fitShift) + log(nobs(fitShift))
@test mu_estim(fitlam)  ≈ mu_estim(fitShift)

fitSH = phyloNetworklm(@formula(trait ~ shift_8 + shift_17), dfr, net, model = "scalingHybrid", fixedValue = 1.0)
@test loglikelihood(fitlam) ≈ loglikelihood(fitSH)
@test aic(fitlam) ≈ aic(fitSH)

## ftest against own naive implementation
modnull = phyloNetworklm(@formula(trait ~ 1), dfr, net)
modhom = phyloNetworklm(@formula(trait ~ sum), dfr, net)
modhet = phyloNetworklm(@formula(trait ~ sum + shift_8), dfr, net)

table1 = ftest(modhet, modhom, modnull)
table2 = PhyloNetworks.anova(modnull, modhom, modhet)

@test table1.fstat[1] ≈ table2[:F][2]
@test table1.fstat[2] ≈ table2[:F][1]
@test table1.pval[1].v ≈ table2[Symbol("Pr(>F)")][2]
@test table1.pval[2].v ≈ table2[Symbol("Pr(>F)")][1]
# ## Replace next 4 lines with previous ones when GLM.ftest available
# @test table1[:F][2] ≈ table2[:F][2]
# @test table1[:F][1] ≈ table2[:F][1]
# @test table1[Symbol("Pr(>F)")][1] ≈ table2[Symbol("Pr(>F)")][1]
# @test table1[Symbol("Pr(>F)")][2] ≈ table2[Symbol("Pr(>F)")][2]

# Check that it is the same as doing shift_8 + shift_17
modhetbis = phyloNetworklm(@formula(trait ~ shift_8 + shift_17), dfr, net)

table2bis = PhyloNetworks.anova(modnull, modhom, modhetbis)

@test table2[:F] ≈ table2bis[:F]
@test table2[Symbol("Pr(>F)")] ≈ table2bis[Symbol("Pr(>F)")]
@test table2[:dof_res] ≈ table2bis[:dof_res]
@test table2[:RSS] ≈ table2bis[:RSS]
@test table2[:dof] ≈ table2bis[:dof]
@test table2[:SS] ≈ table2bis[:SS]

end

###############################################################################
#### Other Network
###############################################################################
@testset "phyloNetworklm and ancestralStateReconstruction" begin

# originally: "(((Ag,(#H1:7.159::0.056,((Ak,(E:0.08,#H2:0.0::0.004):0.023):0.078,(M:0.0)#H2:::0.996):2.49):2.214):0.026,(((((Az:0.002,Ag2:0.023):2.11,As:2.027):1.697)#H1:0.0::0.944,Ap):0.187,Ar):0.723):5.943,(P,20):1.863,165);"
# followed by changes in net.edge[?].length values to make the network ultrametric
net = readTopology("(((Ag:5,(#H1:1::0.056,((Ak:2,(E:1,#H2:1::0.004):1):1,(M:2)#H2:1::0.996):1):1):1,(((((Az:1,Ag2:1):1,As:2):1)#H1:1::0.944,Ap:4):1,Ar:5):1):1,(P:4,20:4):3,165:7);");
# plot(net, useEdgeLength = true,  showEdgeNumber=true)

#### Simulate correlated data in data frames ####
b0 = 1
b1 = 10
srand(5678)
sim = simulate(net, ParamsBM(1, 1))
A = sim[:Tips]
B = b0 + b1 * A + simulate(net,  ParamsBM(0, 0.1))[:Tips]

# With Matrices
X = hcat(ones(12), A)
fit_mat = phyloNetworklm(X, B, net)
#@show fit_mat

# Naive version (GLS)
ntaxa = length(B)
Vy = fit_mat.Vy
Vyinv = inv(Vy)
XtVyinv = X' * Vyinv
logdetVy = logdet(Vy)
betahat = inv(XtVyinv * X) * XtVyinv * B
fittedValues =  X * betahat
resids = B - fittedValues
sigma2hat = 1/ntaxa * (resids' * Vyinv * resids)
# log likelihood
loglik = - 1 / 2 * (ntaxa + ntaxa * log(2 * pi) + ntaxa * log(sigma2hat) + logdetVy)
# null version
nullX = ones(ntaxa, 1)
nullXtVyinv = nullX' * Vyinv
nullresids = B - nullX * inv(nullXtVyinv * nullX) * nullXtVyinv * B
nullsigma2hat = 1/ntaxa * (nullresids' * Vyinv * nullresids)
nullloglik = - 1 / 2 * (ntaxa + ntaxa * log(2 * pi) + ntaxa * log(nullsigma2hat) + logdetVy)
@test coef(fit_mat) ≈ betahat
@test nobs(fit_mat) ≈ ntaxa
@test residuals(fit_mat) ≈ resids
@test model_response(fit_mat) ≈ B
@test predict(fit_mat) ≈ fittedValues
@test dof_residual(fit_mat) ≈ ntaxa-length(betahat)
@test sigma2_estim(fit_mat) ≈ sigma2hat
@test loglikelihood(fit_mat) ≈ loglik
@test vcov(fit_mat) ≈ sigma2hat*ntaxa/(ntaxa-length(betahat)).*inv(XtVyinv * X)
@test stderr(fit_mat) ≈ sqrt.(diag(sigma2hat*ntaxa/(ntaxa-length(betahat)).*inv(XtVyinv * X)))
@test dof(fit_mat) ≈ length(betahat)+1
@test deviance(fit_mat) ≈ sigma2hat * ntaxa
@test nulldeviance(fit_mat) ≈ nullsigma2hat * ntaxa
@test nullloglikelihood(fit_mat) ≈ nullloglik
@test r2(fit_mat) ≈ 1-sigma2hat / nullsigma2hat atol=1e-15
@test adjr2(fit_mat) ≈ 1 - (1 - (1-sigma2hat/nullsigma2hat))*(ntaxa-1)/(ntaxa-length(betahat)) atol=1e-15
@test aic(fit_mat) ≈ -2*loglik+2*(length(betahat)+1)
@test aicc(fit_mat) ≈ -2*loglik+2*(length(betahat)+1)+2(length(betahat)+1)*((length(betahat)+1)+1)/(ntaxa-(length(betahat)+1)-1)
@test bic(fit_mat) ≈ -2*loglik+(length(betahat)+1)*log(ntaxa)

## perfect user using right format and formula
dfr = DataFrame(trait = B, pred = A, tipNames = sim.M.tipNames)
phynetlm = phyloNetworklm(@formula(trait ~ pred), dfr, net)
#@show phynetlm

@test coef(phynetlm) ≈ coef(fit_mat)
@test vcov(phynetlm) ≈ vcov(fit_mat)
@test nobs(phynetlm) ≈ nobs(fit_mat)
@test residuals(phynetlm) ≈ residuals(fit_mat)
@test model_response(phynetlm) ≈ model_response(fit_mat)
@test predict(phynetlm) ≈ predict(fit_mat)
@test dof_residual(phynetlm) ≈ dof_residual(fit_mat)
@test sigma2_estim(phynetlm) ≈ sigma2_estim(fit_mat)
@test stderr(phynetlm) ≈ stderr(fit_mat)
@test confint(phynetlm) ≈ confint(fit_mat)
@test loglikelihood(phynetlm) ≈ loglikelihood(fit_mat)
@test dof(phynetlm) ≈ dof(fit_mat)
@test deviance(phynetlm) ≈ deviance(fit_mat)
@test nulldeviance(phynetlm) ≈ nulldeviance(fit_mat)
@test nullloglikelihood(phynetlm) ≈ nullloglikelihood(fit_mat)
@test r2(phynetlm) ≈ r2(fit_mat)
@test adjr2(phynetlm) ≈ adjr2(fit_mat)
@test aic(phynetlm) ≈ aic(fit_mat)
@test aicc(phynetlm) ≈ aicc(fit_mat)
@test bic(phynetlm) ≈ bic(fit_mat)

# unordered data
srand(1234)
dfr = dfr[sample(1:12, 12, replace=false), :]
fitbis = phyloNetworklm(@formula(trait ~ pred), dfr, net)

@test coef(phynetlm) ≈ coef(fitbis)
@test vcov(phynetlm) ≈ vcov(fitbis)
@test nobs(phynetlm) ≈ nobs(fitbis)
@test residuals(phynetlm)[fitbis.model.ind] ≈ residuals(fitbis)
@test model_response(phynetlm)[fitbis.model.ind] ≈ model_response(fitbis)
@test predict(phynetlm)[fitbis.model.ind] ≈ predict(fitbis)
@test dof_residual(phynetlm) ≈ dof_residual(fitbis)
@test sigma2_estim(phynetlm) ≈ sigma2_estim(fitbis)
@test stderr(phynetlm) ≈ stderr(fitbis)
@test confint(phynetlm) ≈ confint(fitbis)
@test loglikelihood(phynetlm) ≈ loglikelihood(fitbis)
@test dof(phynetlm) ≈ dof(fitbis)
@test deviance(phynetlm) ≈ deviance(fitbis)
@test nulldeviance(phynetlm) ≈ nulldeviance(fitbis)
@test nullloglikelihood(phynetlm) ≈ nullloglikelihood(fitbis)
@test r2(phynetlm) ≈ r2(fitbis)
@test adjr2(phynetlm) ≈ adjr2(fitbis)
@test aic(phynetlm) ≈ aic(fitbis)
@test aicc(phynetlm) ≈ aicc(fitbis)
@test bic(phynetlm) ≈ bic(fitbis)
@test mu_estim(phynetlm) ≈ mu_estim(fitbis)

# unnamed ordered data
dfr = DataFrame(trait = B, pred = A)
fitter = (@test_warn "As requested (no_names=true)" phyloNetworklm(@formula(trait ~ pred), dfr, net, no_names=true))

@test coef(phynetlm) ≈ coef(fitter)
@test vcov(phynetlm) ≈ vcov(fitter)
@test nobs(phynetlm) ≈ nobs(fitter)
@test residuals(phynetlm) ≈ residuals(fitter)
@test model_response(phynetlm) ≈ model_response(fitter)
@test predict(phynetlm) ≈ predict(fitter)
@test dof_residual(phynetlm) ≈ dof_residual(fitter)
@test sigma2_estim(phynetlm) ≈ sigma2_estim(fitter)
@test stderr(phynetlm) ≈ stderr(fitter)
@test confint(phynetlm) ≈ confint(fitter)
@test loglikelihood(phynetlm) ≈ loglikelihood(fitter)
@test dof(phynetlm) ≈ dof(fitter)
@test deviance(phynetlm) ≈ deviance(fitter)
@test nulldeviance(phynetlm) ≈ nulldeviance(fitter)
@test nullloglikelihood(phynetlm) ≈ nullloglikelihood(fitter)
@test r2(phynetlm) ≈ r2(fitter)
@test adjr2(phynetlm) ≈ adjr2(fitter)
@test aic(phynetlm) ≈ aic(fitter)
@test aicc(phynetlm) ≈ aicc(fitter)
@test bic(phynetlm) ≈ bic(fitter)

# unnamed un-ordered data
dfr = dfr[sample(1:12, 12, replace=false), :]
@test_throws ErrorException phyloNetworklm(@formula(trait ~ pred), dfr, net) # Wrong pred

### Add NAs
dfr = DataFrame(trait = B, pred = A, tipNames = tipLabels(sim))
dfr[:pred] = allowmissing(dfr[:pred])
dfr[[2, 8, 11], :pred] = missing
fitna = phyloNetworklm(@formula(trait ~ pred), dfr, net)
#@show fitna

dfr = dfr[sample(1:12, 12, replace=false), :]
fitnabis = phyloNetworklm(@formula(trait ~ pred), dfr, net)

@test coef(fitna) ≈ coef(fitnabis)
@test vcov(fitna) ≈ vcov(fitnabis)
@test nobs(fitna) ≈ nobs(fitnabis)
@test sort(residuals(fitna)) ≈ sort(residuals(fitnabis))
@test sort(model_response(fitna)) ≈ sort(model_response(fitnabis))
@test sort(predict(fitna)) ≈ sort(predict(fitnabis))
@test dof_residual(fitna) ≈ dof_residual(fitnabis)
@test sigma2_estim(fitna) ≈ sigma2_estim(fitnabis)
@test stderr(fitna) ≈ stderr(fitnabis)
@test confint(fitna) ≈ confint(fitnabis)
@test loglikelihood(fitna) ≈ loglikelihood(fitnabis)
@test dof(fitna) ≈ dof(fitnabis)
@test deviance(fitna) ≈ deviance(fitnabis)
@test nulldeviance(fitna) ≈ nulldeviance(fitnabis)
@test nullloglikelihood(fitna) ≈ nullloglikelihood(fitnabis)
@test r2(fitna) ≈ r2(fitnabis)
@test adjr2(fitna) ≈ adjr2(fitnabis)
@test aic(fitna) ≈ aic(fitnabis)
@test aicc(fitna) ≈ aicc(fitnabis)
@test bic(fitna) ≈ bic(fitnabis)

## Tests against fixed values parameters
fitlam = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "lambda", fixedValue = 1.0)
#@show fitlam

@test lambda_estim(fitlam) ≈ 1.0
@test coef(fitlam) ≈ coef(fitnabis)
@test vcov(fitlam) ≈ vcov(fitnabis)
@test nobs(fitlam) ≈ nobs(fitnabis)
@test residuals(fitlam) ≈ residuals(fitnabis)
@test model_response(fitlam) ≈ model_response(fitnabis)
@test predict(fitlam) ≈ predict(fitnabis)
@test dof_residual(fitlam) ≈ dof_residual(fitnabis)
@test sigma2_estim(fitlam) ≈ sigma2_estim(fitnabis)
@test stderr(fitlam) ≈ stderr(fitnabis)
@test confint(fitlam) ≈ confint(fitnabis)
@test loglikelihood(fitlam) ≈ loglikelihood(fitnabis)
@test dof(fitlam) ≈ dof(fitnabis) + 1
@test deviance(fitlam) ≈ deviance(fitnabis)
@test nulldeviance(fitlam) ≈ nulldeviance(fitnabis)
@test nullloglikelihood(fitlam) ≈ nullloglikelihood(fitnabis)
@test r2(fitlam) ≈ r2(fitnabis) atol=1e-15
@test adjr2(fitlam)-1 ≈ (adjr2(fitnabis)-1)*(nobs(fitnabis)-dof(fitnabis)+1)/(nobs(fitnabis)-dof(fitlam)+1) atol=1e-15
@test aic(fitlam) ≈ aic(fitnabis) + 2
#@test aicc(fitlam) ≈ aicc(fitnabis)
@test bic(fitlam) ≈ bic(fitnabis) + log(nobs(fitnabis))
@test mu_estim(fitlam) ≈ mu_estim(fitnabis)

fitSH = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "scalingHybrid", fixedValue = 1.0)
@test loglikelihood(fitlam) ≈ loglikelihood(fitSH)
@test aic(fitlam) ≈ aic(fitSH)

## Pagel's Lambda
fitlam = (@test_warn "Maximum lambda value" phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "lambda"))
#@show fitlam
@test lambda_estim(fitlam) ≈ 1.1135518305 atol=1e-10

## scaling Hybrid
fitSH = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "scalingHybrid")
@show fitSH
@test lambda_estim(fitSH) ≈ -52.81305448333567 atol=1e-8

### Ancestral State Reconstruction
params = ParamsBM(3, 1)
sim = simulate(net, params)
Y = sim[:Tips]
# From known parameters
ancestral_traits = ancestralStateReconstruction(net, Y, params)
# BLUP
dfr = DataFrame(trait = Y, tipNames = tipLabels(sim))
phynetlm = phyloNetworklm(@formula(trait~1), dfr, net)
blup = (@test_warn "These prediction intervals show uncertainty in ancestral values" ancestralStateReconstruction(phynetlm));
# plot(net, blup)
@show blup

# BLUP same, using the function dirrectly
blup_bis = (@test_warn "These prediction intervals show uncertainty in ancestral values" ancestralStateReconstruction(dfr, net));

@test expectations(blup)[:condExpectation] ≈ expectations(blup_bis)[:condExpectation]
@test expectations(blup)[:nodeNumber] ≈ expectations(blup_bis)[:nodeNumber]
@test blup.traits_tips ≈ blup_bis.traits_tips
@test blup.TipNumbers ≈ blup_bis.TipNumbers
@test predint(blup) ≈ predint(blup_bis)
@test predintPlot(blup)[:PredInt] == predintPlot(blup_bis)[:PredInt]
@test predintPlot(blup, withExp=true)[:PredInt] == predintPlot(blup_bis, withExp=true)[:PredInt]
@test expectationsPlot(blup)[:PredInt] == expectationsPlot(blup_bis)[:PredInt]

dfr = DataFrame(trait = Y, tipNames = tipLabels(sim), reg = Y)
@test_throws ErrorException ancestralStateReconstruction(dfr, net) # cannot handle a predictor

# Unordered
dfr2 = dfr[sample(1:12, 12, replace=false), :]
phynetlm = phyloNetworklm(@formula(trait~1), dfr2, net)
blup2 = (@test_warn "These prediction intervals show uncertainty in ancestral values" ancestralStateReconstruction(phynetlm))

@test expectations(blup)[:condExpectation][1:length(blup.NodeNumbers)] ≈ expectations(blup2)[:condExpectation][1:length(blup.NodeNumbers)]
@test blup.traits_tips[phynetlm.model.ind] ≈ blup2.traits_tips
@test blup.TipNumbers[phynetlm.model.ind] ≈ blup2.TipNumbers
@test predint(blup)[1:length(blup.NodeNumbers), :] ≈ predint(blup2)[1:length(blup.NodeNumbers), :]

# With unknown tips
dfr[:trait] = allowmissing(dfr[:trait])
dfr[[2, 4], :trait] = missing
phynetlm = phyloNetworklm(@formula(trait~1), dfr, net)
blup = (@test_warn "These prediction intervals show uncertainty in ancestral values" ancestralStateReconstruction(phynetlm))
# plot(net, blup)

# Unordered
dfr2 = dfr[[1, 2, 5, 3, 4, 6, 7, 8, 9, 10, 11, 12], :]
phynetlm = phyloNetworklm(@formula(trait~1), dfr, net)
blup2 = (@test_warn "These prediction intervals show uncertainty in ancestral values" ancestralStateReconstruction(phynetlm))

@test expectations(blup)[:condExpectation] ≈ expectations(blup2)[:condExpectation]
@test predint(blup) ≈ predint(blup2)
@test predintPlot(blup)[:PredInt] == predintPlot(blup2)[:PredInt]
@test predintPlot(blup, withExp=true)[:PredInt] == predintPlot(blup2, withExp=true)[:PredInt]

# Test mark on missing
ee = expectationsPlot(blup)
predMiss = ee[indexin([n.number for n in net.leaf][[2,4]], ee[:nodeNumber]),:PredInt]
for pp = predMiss
    @test pp[end] == '*'
end

end

#################
## Data with no phylogenetic signal
#################
@testset "lambda when no signal" begin

net = readTopology("(((Ag:5,(#H1:1::0.056,((Ak:2,(E:1,#H2:1::0.004):1):1,(M:2)#H2:1::0.996):1):1):1,(((((Az:1,Ag2:1):1,As:2):1)#H1:1::0.944,Ap:4):1,Ar:5):1):1,(P:4,20:4):3,165:7);");
# plot(net, useEdgeLength = true,  showEdgeNumber=true)

#### Simulate correlated data in data frames ####
b0 = 1
b1 = 10
srand(5678)
A = randn(size(tipLabels(net), 1))
B = b0 + b1 * A + randn(size(tipLabels(net), 1))
dfr = DataFrame(trait = B, pred = A, tipNames = tipLabels(net))

## Network
phynetlm = (@test_warn "Maximum lambda value" phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "lambda"))

@test lambda_estim(phynetlm) ≈ 0.5894200143 atol=1e-8

## Major Tree
tree = majorTree(net)
phynetlm = (@test_warn "Maximum lambda value" phyloNetworklm(@formula(trait ~ pred), dfr, tree, model = "lambda"))

@test lambda_estim(phynetlm) ≈ 0.5903394415 atol=1e-6

## scaling Hybrid
lmtree = phyloNetworklm(@formula(trait ~ pred), dfr, tree, model = "BM")
lmnet = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "BM")
lmSHzero = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "scalingHybrid", fixedValue = 0.0)
lmSHone = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "scalingHybrid", fixedValue = 1.0)

@test loglikelihood(lmtree) ≈ loglikelihood(lmSHzero)
@test loglikelihood(lmnet) ≈ loglikelihood(lmSHone)

lmSH = phyloNetworklm(@formula(trait ~ pred), dfr, net, model = "scalingHybrid")

@test lambda_estim(lmSH) ≈ 23.46668204551696 atol=1e-5

end

############################
## Against no regressor
###########################
@testset "phloNetworklm with no regressor" begin

net = readTopology("(((Ag:5,(#H1:1::0.056,((Ak:2,(E:1,#H2:1::0.004):1):1,(M:2)#H2:1::0.996):1):1):1,(((((Az:1,Ag2:1):1,As:2):1)#H1:1::0.944,Ap:4):1,Ar:5):1):1,(P:4,20:4):3,165:7);");

params = ParamsBM(10, 1)
srand(2468) # sets the seed for reproducibility, to debug potential error
sim = simulate(net, params)
Y = sim[:Tips]
phynetlm = phyloNetworklm(zeros(length(Y),0), Y, net)
#@show phynetlm
# Naive version (GLS)
ntaxa = length(Y)
Vy = phynetlm.Vy
Vyinv = inv(Vy)
logdetVy = logdet(Vy)
fittedValues =  zeros(length(Y))
resids = Y - fittedValues
sigma2hat = 1/ntaxa * (resids' * Vyinv * resids)
# log likelihood
loglik = - 1 / 2 * (ntaxa + ntaxa * log(2 * pi) + ntaxa * log(sigma2hat) + logdetVy)
# null version
nullX = ones(ntaxa, 1)
nullXtVyinv = nullX' * Vyinv
nullresids = Y - nullX * inv(nullXtVyinv * nullX) * nullXtVyinv * Y
nullsigma2hat = 1/ntaxa * (nullresids' * Vyinv * nullresids)
nullloglik = - 1 / 2 * (ntaxa + ntaxa * log(2 * pi) + ntaxa * log(nullsigma2hat) + logdetVy)

@test nobs(phynetlm) ≈ ntaxa
@test residuals(phynetlm) ≈ resids
@test model_response(phynetlm) ≈ Y
@test predict(phynetlm) ≈ fittedValues
@test dof_residual(phynetlm) ≈ ntaxa
@test sigma2_estim(phynetlm) ≈ sigma2hat
@test loglikelihood(phynetlm) ≈ loglik
@test deviance(phynetlm) ≈ sigma2hat * ntaxa
@test nulldeviance(phynetlm) ≈ nullsigma2hat * ntaxa
@test nullloglikelihood(phynetlm) ≈ nullloglik
@test r2(phynetlm) ≈ 1-sigma2hat / nullsigma2hat atol=1e-14
@test adjr2(phynetlm) ≈ 1 - (1 - (1-sigma2hat/nullsigma2hat))*(ntaxa-1)/(ntaxa) atol=1e-14
@test aic(phynetlm) ≈ -2*loglik+2*(1)
@test aicc(phynetlm) ≈ -2*loglik+2*(1)+2(1)*((1)+1)/(ntaxa-(1)-1)
@test bic(phynetlm) ≈ -2*loglik+(1)*log(ntaxa)

# with data frames
dfr = DataFrame(trait = Y, tipNames = sim.M.tipNames)
fitbis = phyloNetworklm(@formula(trait ~ -1), dfr, net)
@show fitbis
#@test coef(phynetlm) ≈ coef(fitbis)
#@test vcov(phynetlm) ≈ vcov(fitbis)
@test nobs(phynetlm) ≈ nobs(fitbis)
@test residuals(phynetlm)[fitbis.model.ind] ≈ residuals(fitbis)
@test model_response(phynetlm)[fitbis.model.ind] ≈ model_response(fitbis)
@test predict(phynetlm)[fitbis.model.ind] ≈ predict(fitbis)
@test dof_residual(phynetlm) ≈ dof_residual(fitbis)
@test sigma2_estim(phynetlm) ≈ sigma2_estim(fitbis)
#@test stderr(phynetlm) ≈ stderr(fitbis)
#@test confint(phynetlm) ≈ confint(fitbis)
@test loglikelihood(phynetlm) ≈ loglikelihood(fitbis)
#@test dof(phynetlm) ≈ dof(fitbis)
@test deviance(phynetlm) ≈ deviance(fitbis)
@test nulldeviance(phynetlm) ≈ nulldeviance(fitbis)
@test nullloglikelihood(phynetlm) ≈ nullloglikelihood(fitbis)
@test r2(phynetlm) ≈ r2(fitbis) atol=1e-15
@test adjr2(phynetlm) ≈ adjr2(fitbis) atol=1e-15
@test aic(phynetlm) ≈ aic(fitbis)
@test aicc(phynetlm) ≈ aicc(fitbis)
@test bic(phynetlm) ≈ bic(fitbis)
#@test mu_estim(phynetlm)  mu_estim(fitbis)

end


module FourierGPE

using Reexport
# using SpecialFunctions
# using LoopVectorization
# using Tullio
using LazyArrays
using FillArrays
using OrdinaryDiffEq
using RecursiveArrayTools
using PaddedViews
using FFTW
import DiffEqCallbacks.FunctionCallingCallback
import OrdinaryDiffEq: ODEProblem, solve

# using LaTeXStrings
# using ColorSchemes
# using Plots
# const c1 = cgrad(ColorSchemes.inferno.colors)
# const c2 = cgrad(ColorSchemes.RdBu_11.colors)
# const c3 =:mediumseagreen

@reexport using Parameters
@reexport using JLD2
@reexport using FileIO

include("types.jl")
include("arrays.jl")
include("transforms.jl")
include("evolution.jl")

# simulation
export Simulation, TransformLibrary, UserParams
export xvec, kvec, xvecs, kvecs, dfft, dfftall
export crandn_array, crandnpartition
export maketransarrays, makearrays, xspace, xspace!, kspace, kspace!
export nlin, nlin!, Lgp, Lgp!, V, Params
export initsim!, runsim, internalnorm
export Transforms, @pack_Transforms!, @unpack_Transforms
export Sim, @pack_Sim!, @unpack_Sim, testsim
export Params, @pack_Params!, @unpack_Params
export k2, @pack!, @unpack, makeT, definetransforms #makeTMixed,
export Field, XField, KField
# export showpsi, c1, c2, c3

end


N = 64; G = path_graph(N)
L = Matrix(laplacian_matrix(G))
𝛌, 𝚽 = eigen(L); 𝚽 = 𝚽 .* sign.(𝚽[1,:])'
Q = incidence_matrix(G; oriented = true)
∇𝚽 = Q' * 𝚽


"""
Maps v to every possible coalition P(G)
"""
function valuemapping(G::Game)
    ((S, G.v(S)) for S in subsets(G.N))
end

"""
Breadth first test of convexity of a game,

For all S, T ∈ P(G):
v(S ∪ T) + v(S ∩ T) ≥ v(S) + v(T)
"""
function isconvex(G::Game)
    v = G.v
    coalitions = subsets(G.N)

    for (i, T) in enumerate(coalitions)
        for S in drop(coalitions, i)
            if v(S ∪ T) + v(S ∩ T) < v(S) + v(T)
                return false
        end
    end
    end

    return true
end


module fock

export FockBasis, number, destroy, create, displace, fockstate, coherentstate, coherentstate!

import Base: ==

using ..bases, ..states, ..operators, ..operators_dense, ..operators_sparse
using SparseArrays

"""
    FockBasis(N)

Basis for a Fock space where `N` specifies a cutoff, i.e. what the highest
included fock state is. Note that the dimension of this basis then is N+1.
"""
mutable struct FockBasis <: Basis
    shape::Vector{Int}
    N::Int
    function FockBasis(N::Int)
        if N < 0
            throw(DimensionMismatch())
        end
        new([N+1], N)
    end
end

==(b1::FockBasis, b2::FockBasis) = b1.N==b2.N

"""
    number(b::FockBasis)

Number operator for the specified Fock space.
"""
function number(b::FockBasis)
    diag = complex.(0.:b.N)
    data = spdiagm(0 => diag)
    SparseOperator(b, data)
end

"""
    destroy(b::FockBasis)

Annihilation operator for the specified Fock space.
"""
function destroy(b::FockBasis)
    diag = complex.(sqrt.(1.:b.N))
    data = spdiagm(1 => diag)
    SparseOperator(b, data)
end

"""
    create(b::FockBasis)

Creation operator for the specified Fock space.
"""
function create(b::FockBasis)
    diag = complex.(sqrt.(1.:b.N))
    data = spdiagm(-1 => diag)
    SparseOperator(b, data)
end

"""
    displace(b::FockBasis, alpha)

Displacement operator ``D(α)`` for the specified Fock space.
"""
displace(b::FockBasis, alpha::Number) = exp(dense(alpha*create(b) - conj(alpha)*destroy(b)))

"""
    fockstate(b::FockBasis, n)

Fock state ``|n⟩`` for the specified Fock space.
"""
function fockstate(b::FockBasis, n::Int)
    @assert n <= b.N
    basisstate(b, n+1)
end

"""
    coherentstate(b::FockBasis, alpha)

Coherent state ``|α⟩`` for the specified Fock space.
"""
function coherentstate(b::FockBasis, alpha::Number)
    result = Ket(b, Vector{ComplexF64}(undef, length(b)))
    coherentstate!(result, b, alpha)
    return result
end

"""
    coherentstate!(ket::Ket, b::FockBasis, alpha)

Inplace creation of coherent state ``|α⟩`` for the specified Fock space.
"""
function coherentstate!(ket::Ket, b::FockBasis, alpha::Number)
    alpha = complex(alpha)
    data = ket.data
    data[1] = exp(-abs2(alpha)/2)
    @inbounds for n=1:b.N
        data[n+1] = data[n]*alpha/sqrt(n)
    end
end

end # module


# Coefficients taken from Table B1 of Rančić et al., (1996): Quarterly Journal of the Royal Meteorological Society,
#   A global shallow-water model using an expanded spherical cube - Gnomonic versus conformal coordinates

A_Rancic = [
    +0.00000000000000,
    +1.47713062600964,
    -0.38183510510174,
    -0.05573058001191,
    -0.00895883606818,
    -0.00791315785221,
    -0.00486625437708,
    -0.00329251751279,
    -0.00235481488325,
    -0.00175870527475,
    -0.00135681133278,
    -0.00107459847699,
    -0.00086944475948,
    -0.00071607115121,
    -0.00059867100093,
    -0.00050699063239,
    -0.00043415191279,
    -0.00037541003286,
    -0.00032741060100,
    -0.00028773091482,
    -0.00025458777519,
    -0.00022664642371,
    -0.00020289261022,
    -0.00018254510830,
    -0.00016499474461,
    -0.00014976117168,
    -0.00013646173946,
    -0.00012478875823,
    -0.00011449267279,
    -0.00010536946150,
    -0.00009725109376
]

A_series = Taylor1(A_Rancic)
B_series = inverse(A_series) # This is the inverse Taylor series.
B_Rancic = B_series.coeffs


"""
Provides a collection of projection operators and error functionals.
"""

###### Projections ######

"Fourier modulus projection"
function pM(g, sqrtI)
    ang = angle.(fft(g))
    res = real(ifft(sqrtI .* exp.(im .* ang)))
end

"""
Incomplete (Elser) modulus projection

A variant of the modulus projection that leaves 0'th frequency
invariant or sets it to 0 if it is negative. This projection is
used, e.g., in
> "Benchmark problems for phase retrieval", V. Elser, T.-Y. Lan & T. Bendory
"""
function pM0(g, sqrtI)
    goalAbs = sqrtI
    goalAbs[1,1] = maximum([sum(g), 0.0])
    return real(ifft(goalAbs .* exp.(im .* angle.(fft(g)))))
end

"Positivity projection"
function pP(g)
    res = real(g .* (g .>= 0))
end

"Soft sparsity projection"
function pTa(g, alpha=0)
    res = real(g .* (g .>= alpha))
end

"""
Hard sparsity projection

Set all but suppSize largest elements of g to zero.
"""
function pTn(g, suppSize)
    whereLargest = partialsortperm(g[:], 1:suppSize, rev=true)
    res = zeros(size(g))
    res[whereLargest] = g[:][whereLargest]
    return res
end

###### Error functionals ######
"Modulus energy functional"
function eM(g, sqrtI)
    res = 0.5 * sum((abs.(fft(g)) - sqrtI).^2) / length(g)
end

"Incomplete modulus energy functional"
function eM0(g, sqrtI)
        res = 0.5 * sum((abs.(fft(g)[2:end]) - sqrtI[2:end]).^2) / length(g)
end

"Positive energy functional"
function eP(g)
    res = 0.5 * sum(real(g .* (g .< 0)).^2)
end

"Soft sparsity energy functional"
function eTa(g, alpha=0)
    res = 0.5 * sum((g - pTa(g, alpha)).^2)
end

"Hard sparsity energy functional"
function eTn(g, suppSize)
    res = 0.5 * sum((g - pTn(g, suppSize)).^2)
end


include("prop_logic_theory.jl")
include("prover.jl")

using Test

# Metatheory.options.printiter = true
# Metatheory.options.verbose = true

ex = rewrite(:(((p => q) ∧ (r => s) ∧ (p ∨ r)) => (q ∨ s)), impl)
@test prove(t, ex, 3, 10, 5000)

@test @areequal t true ((¬p == p) == false)
@test @areequal t true ((¬p == ¬p) == true)
@test @areequal t true ((¬p ∨ ¬p) == ¬p) (¬p ∨ p) ¬(¬p ∧ p)
@test @areequal t true ((p => (p ∨ p)))
@test @areequal t true ((p => (p ∨ p)) == ((¬(p) ∧ q) => q)) == true

# Frege's theorem
@test @areequal t true (p => (q => r)) => ((p => q) => (p => r))

# Demorgan's
@test @areequal t true (¬(p ∨ q) == (¬p ∧ ¬q))

# Consensus theorem
@test @areequal t ((x ∧ y) ∨ (¬x ∧ z) ∨ (y ∧ z))   ((x ∧ y) ∨ (¬x ∧ z))


import Pkg
Pkg.add("LowRankApprox")
using LowRankApprox

m=128
n=64
M = matrixlib(:fourier, rand(m), rand(n))
A=real(M)
F = psvdfact(A)
@time U, S, V = psvd(A)

using LinearAlgebra
@time U1, S1, V1 = svd(A)


# # 220: 2D Nonlinear Poisson with boundary reaction and boundary species
# ([source code](SOURCE_URL))

module Example220_NonlinearPoisson2D_BoundarySpecies

using Printf
using VoronoiFVM

function main(;n=10,Plotter=nothing,verbose=false,unknown_storage=:sparse)

    h=1.0/convert(Float64,n)
    X=collect(0.0:h:1.0)
    Y=collect(0.0:h:1.0)

    grid=VoronoiFVM.Grid(X,Y)

    k=1.0
    eps=1.0
    physics=VoronoiFVM.Physics(
    num_species=3,
    breaction=function(f,u,node)
        if  node.region==2
            f[1]=k*(u[1]-u[3])
            f[3]=k*(u[3]-u[1])+ k*(u[3]-u[2])
            f[2]=k*(u[2]-u[3])
        end
    end,

    bstorage=function(f,u,node)
        if  node.region==2
            f[3]=u[3]
        end
    end,

    flux=function(f,u,edge)
        f[1]=eps*(u[1,1]-u[1,2])
        f[2]=eps*(u[2,1]-u[2,2])
    end,

    source=function(f,node)
        x1=node[1]-0.5
        x2=node[2]-0.5
        f[1]=exp(-20.0*(x1^2+x2^2))
    end,

    storage=function(f,u,node)
        f[1]=u[1]
        f[2]=u[2]
    end
    )

    sys=VoronoiFVM.System(grid,physics,unknown_storage=unknown_storage)

    enable_species!(sys,1,[1])
    enable_species!(sys,2,[1])
    enable_boundary_species!(sys,3,[2])

    function tran32!(a,b)
        a[1]=b[2]
    end

    bgrid2=subgrid(grid,[2],boundary=true,transform=tran32!)

    inival=unknowns(sys)
    inival.=0.0
    U=unknowns(sys)

    eps=1.0e-2

    control=VoronoiFVM.NewtonControl()
    control.verbose=verbose
    control.tol_linear=1.0e-5
    control.tol_relative=1.0e-5
    control.max_lureuse=0
    tstep=0.01
    time=0.0
    istep=0
    u5=0
    while time<1
        time=time+tstep
        solve!(U,inival,sys,control=control,tstep=tstep)
        inival.=U
        if verbose
            @printf("time=%g\n",time)
        end
        tstep*=1.0
        istep=istep+1
        U_bound=view(U[3,:],bgrid2)
        u5=U_bound[5]
        if isplots(Plotter)
            p1=Plotter.contourf(X,Y,reshape(U[1,:],length(X),length(Y)),levels=collect(0:0.1:0.6),clim=(0,0.6),colorbar=:right,color=:viridis,title=@sprintf("max1=%g max2=%g maxb=%g\n",maximum(U[1,:]),maximum(U[2,:]),maximum(U_bound[3,:])))
            p2=Plotter.contourf(X,Y,reshape(U[2,:],length(X),length(Y)),levels=collect(0:0.0001:0.002),clim=(0,0.002), colorbar=:right,color=:viridis)
            p3=Plotter.plot(grid=true,ylims=(0,0.0025))
            plot(bgrid2,U_bound,p=p3,show=false,Plotter=Plotter)
            p=Plotter.plot(p1,p2,p3,layout=(3,1) )
            Plotter.gui(p)
        end
    end
    return u5
end

function test()
    main(unknown_storage=:sparse) ≈ 0.0020781361856598
    main(unknown_storage=:dense) ≈ 0.0020781361856598
end
end


using GroupsCore
using Test
import AbstractAlgebra
include("symmetric.jl")

include("conformance_test.jl")

include("cyclic.jl")
include("infinite_cyclic.jl")

@testset "GroupsCore.jl" begin

    include("test_notsatisfied.jl")

    @testset "Cyclic(12)" begin
        G = CyclicGroup(12)
        test_Group_interface(G)
        test_GroupElement_interface(rand(G, 2)...)
    end

    @testset "InfCyclic" begin
        G = InfCyclicGroup()
        test_Group_interface(G)
        test_GroupElement_interface(rand(G, 2)...)
    end

    @testset "Symmetric(5)" begin
        G = AbstractAlgebra.SymmetricGroup(5)
        test_Group_interface(G)
        test_GroupElement_interface(rand(G, 2)...)
    end

    include("extensions.jl")
end

@testset "GroupConstructions" begin

    @testset "DirectProduct" begin
        GH =
            let G = AbstractAlgebra.SymmetricGroup(3),
                H = AbstractAlgebra.SymmetricGroup(4)

                GroupsCore.Constructions.DirectProduct(G, H)
            end
        test_Group_interface(GH)
        test_GroupElement_interface(rand(GH, 2)...)
    end

    @testset "DirectPower" begin
        GGG = GroupsCore.Constructions.DirectPower{3}(
            AbstractAlgebra.SymmetricGroup(3),
        )
        test_Group_interface(GGG)
        test_GroupElement_interface(rand(GGG, 2)...)
    end
    @testset "WreathProduct" begin
        W = let G = AbstractAlgebra.SymmetricGroup(2),
                P = AbstractAlgebra.SymmetricGroup(4)

                GroupsCore.Constructions.WreathProduct(G, P)
            end
        test_Group_interface(W)
        test_GroupElement_interface(rand(W, 2)...)
    end
end


function dft(n, fx, fsample)
    if n <= 0
        throw(DomainError(n, "sample points must be > 0.\r\n"))
    end

    if fsample <= 0
        throw(DomainError(fsample, "sample must be > 0.\r\n"))
    end

    X = zeros(complex(Float64),n)

    for i = 0:n-1
        for j = 0:n-1
            X[i+1] += fx[j+1] * exp(-im * 2 * pi * j * i / n)
        end
    end

    return X
end


using Gadfly, Distributions

plot(y=rand(Normal(), 100), Geom.violin)


using StatsBase
import StatsBase.kldivergence
using LinearAlgebra

"""
    kldivergence(P::Histogram, Q::Function)

Kullback-Leibler divergence between an empirical distribution (measured) and a reference distribution (analytic)

So far this is defined only for 1-dimensional distributions of type StatsBase.Histogram
"""
function kldivergence(P::Histogram, Q::Function)
    ##KL divergence sum P(args)logP(args)/Q(args)
    ## P=P_meas, Q=P_true s.t P(args)=0 simply ignored
    kld = 0.0
    for (i, x) in enumerate(P.edges[1])
        if i <= length(P.weights)
            @inbounds p = P.weights[i]
            if p > 0
                q = Q(x)
                kld += p * log(p / q)
            end
        end
    end
    return kld
end


using Dates, DelimitedFiles, Plots, Random, ScoreDrivenModels

# Define dates and load historical Affluent Natural Energy data
dates = collect(Date(1961):Month(1):Date(2000, 12))
y = vec(readdlm("../test/data/ane_northeastern.csv"))
y_train = y[1:400]
y_test = y[401:460]

# Set RNG seed to guarantee consistent results
Random.seed!(123)

# Specify GAS model: a lognormal model with time-varying μ, constant σ, and lags 1 and 12
gas = Model([1, 2, 11, 12], [1, 2, 11, 12], LogNormal, 0.0; time_varying_params=[1])

# Obtain initial parameters to start the GAS recursion
initial_params = dynamic_initial_params(y_train, gas)

# Fit specified model to historical data using initial parameters
f = ScoreDrivenModels.fit!(gas, y_train; initial_params=initial_params)

# Print estimation statistics
fit_stats(f)

# Simulate 1000 future scenarios and obtain the 5% and 95% quantiles in each time period
forec = forecast(y_train, gas, 60; S=1000, initial_params=initial_params)

# Plot results
plot(dates[401:460], forec.observation_scenarios, color="grey", width=0.05, label="", ylims=(0, 70))
plot!(dates[360:460], y[360:460], label="ANE", color="black", xlabel="Months", ylabel="GWmed", legend=:topright)
plot!(dates[401:460], forec.observation_quantiles, label=["Quantiles" "" ""], color="red", line=:dash)


# Postcompose with scaling and sum

export Postcompose

"""
**Postcomposition with an affine transformation**

    Postcompose(f, a=1.0, b=0.0)

Returns the function
```math
g(x) = a\\cdot f(x) + b.
```
"""
struct Postcompose{T <: ProximableFunction, R <: Real} <: ProximableFunction
    f::T
    a::R
    b::R
    function Postcompose{T,R}(f::T, a::R, b::R) where {T <: ProximableFunction, R <: Real}
        if a <= 0.0
            error("parameter `a` must be positive")
        else
            new(f, a, b)
        end
    end
end

is_prox_accurate(f::Postcompose) = is_prox_accurate(f.f)
is_separable(f::Postcompose) = is_separable(f.f)
is_convex(f::Postcompose) = is_convex(f.f)
is_set(f::Postcompose) = is_set(f.f)
is_singleton(f::Postcompose) = is_singleton(f.f)
is_cone(f::Postcompose) = is_cone(f.f)
is_affine(f::Postcompose) = is_affine(f.f)
is_smooth(f::Postcompose) = is_smooth(f.f)
is_quadratic(f::Postcompose) = is_quadratic(f.f)
is_generalized_quadratic(f::Postcompose) = is_generalized_quadratic(f.f)
is_strongly_convex(f::Postcompose) = is_strongly_convex(f.f)

Postcompose(f::T, a::R=R(1), b::R=R(0)) where {T <: ProximableFunction, R <: Real} = Postcompose{T, R}(f, a, b)

Postcompose(f::Postcompose{T, R}, a::R=R(1), b::R=R(0)) where {T <: ProximableFunction, R <: Real} = Postcompose{T, R}(f.f, a*f.a, b+a*f.b)

function (g::Postcompose)(x::AbstractArray{T}) where T <: RealOrComplex
    return g.a*g.f(x) + g.b
end

function gradient!(y::AbstractArray{T}, g::Postcompose, x::AbstractArray{T}) where T <: RealOrComplex
    v = gradient!(y, g.f, x)
    y .*= g.a
    return g.a*v + g.b
end

function prox!(y::AbstractArray{T}, g::Postcompose, x::AbstractArray{T}, gamma=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    v = prox!(y, g.f, x, g.a*gamma)
    return g.a*v + g.b
end

function prox_naive(g::Postcompose, x::AbstractArray{T}, gamma=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    y, v = prox_naive(g.f, x, g.a*gamma)
    return y, g.a*v + g.b
end

fun_name(f::Postcompose) = string("Postcomposition of ", fun_name(f.f))
fun_dom(f::Postcompose) = fun_dom(f.f)
fun_expr(f::Postcompose) = "x ↦ a*f(x)+b"
fun_params(f::Postcompose) = string("f(x) = ", fun_expr(f.f), ", a = $(f.a), b = $(f.b)")


export RationalFunctions,
    RationalBasisFunction

"A basis of functions of the form `1/(z-z_j)` where `z_j` are the poles."
struct RationalFunctions{T} <: Dictionary{T,T}
    poles   :: Vector{T}
end

RationalFunctions(n::Int) = RationalFunctions{Float64}(n)
RationalFunctions{T}(n::Int) where {T} = RationalFunctions(rand(T, n))

size(dict::RationalFunctions) = (length(dict.poles),)

pole(dict::RationalFunctions, idx) = dict.poles[idx]

unsafe_eval_element(dict::RationalFunctions, idx::Int, x) = 1/(x-dict.poles[idx])

support(dict::RationalFunctions{T}) where {T} = DomainSets.FullSpace{T}()

function unsafe_eval_element_derivative(dict::RationalFunctions, idx, x, order)
    @assert order == 1
    -1/(x-dict.poles[idx])^2
end

function similar(dict::RationalFunctions, ::Type{T}, n::Int) where {T}
    @assert n == length(dict)
    RationalFunctions{T}(dict.poles)
end

struct RationalBasisFunction{T} <: Polynomial{T}
    pole    ::  T
end

pole(r::RationalBasisFunction) = r.pole

RationalBasisFunction{T}(r::RationalBasisFunction) where {T} = RationalBasisFunction{T}(r.pole)

show(io::IO, d::RationalBasisFunction{T}) where {T<:Real} =
    print(io, "1/(x-$(pole(r)))  (rational function)")
show(io::IO, d::RationalBasisFunction{T}) where {T<:Complex} =
    print(io, "1/(z-$(pole(r)))  (rational function)")

convert(::Type{TypedFunction{T,T}}, r::RationalBasisFunction) where {T} = RationalBasisFunction{T}(r.pole)

(r::RationalBasisFunction)(x) = 1/(x-pole(r))

basisfunction(dict::RationalFunctions{T}, idx) where {T} = RationalBasisFunction{T}(pole(dict, idx))


function solve()
    n = parse(Int, readline())
    a = [parse(Int, x) for x in split(readline())]

    dp = zeros(Int, n)
    dp[2] = max(a[1], a[2])
    oddsum = a[1]
    for i in 3:n
        if i % 2 == 0
            dp[i] = max(oddsum, dp[i-2] + a[i])
        else
            dp[i] = max(dp[i-1], dp[i-2] + a[i])
            oddsum += a[i]
        end
    end
    dp[n]
end

println(solve())


@testset "Checks" begin
    X = randn(5, 3)
    y = randn(5)
    @test R.check_nrows(X, y) === nothing
    y = randn(4)
    @test_throws DimensionMismatch R.check_nrows(X, y)
    @test R.check_pos(1)
    @test_throws ArgumentError R.check_pos(-1)
end

@testset "Augment" begin
    X = randn(5, 3)
    X1 = R.augment_X(X, false)
    @test X1 === X
    X1 = R.augment_X(X, true)
    @test X1 == hcat(X, ones(5, 1))
end

@testset "Apply" begin
    n, p = 5, 3
    X = randn(n, p)
    X1 = R.augment_X(X, true)
    θ = randn(p)
    θ1 = randn(p+1)
    @test R.apply_X(X, θ) == X*θ
    @test R.apply_X(X, θ1) ≈ X1*θ1
    # multiclass
    c = 3
    θa  = randn(p)
    θb  = randn(p)
    θc  = randn(p)
    θ   = vcat(θa, θb, θc)
    θa1 = randn(p+1)
    θb1 = randn(p+1)
    θc1 = randn(p+1)
    θ1  = vcat(θa1, θb1, θc1)
    @test R.apply_X(X, θ, c) ≈  X * hcat(θa, θb, θc)
    @test R.apply_X(X, θ1, c) ≈ X1 * hcat(θa1, θb1, θc1)

    θ1 = randn(p+1)
    Xθ = zeros(n)
    R.apply_X!(Xθ, X, θ1)
    @test Xθ ≈ X1 * θ1

    z = randn(n)
    Xtz = zeros(p)
    R.apply_Xt!(Xtz, X, z)
    @test Xtz ≈ X'z

    Xtz2 = zeros(p+1)
    R.apply_Xt!(Xtz2, X, z)
    @test Xtz2 ≈ X1'z

    v = view(Xtz2, 1:p)
    R.apply_Xt!(v, X, z)
    @test Xtz2 ≈ X1'z
end

@testset "Sigmoid" begin
    @test R.sigmoid(zero(Float32)) == 0.5f0
    @test R.sigmoid(zero(Float64)) == 0.5
    @test R.logsigmoid(zero(Float32)) == log(0.5f0)
    @test R.logsigmoid(zero(Float64)) == log(0.5)

    @test R.sigmoid(50) == 1.0
    @test R.sigmoid(50f0) == 1.0f0
    @test R.sigmoid(-50) == 0.0
    @test R.sigmoid(-50f0) == 0.0f0

    @test R.logsigmoid(50) == 0.0
    @test R.logsigmoid(50f0) == 0.0f0
    @test R.logsigmoid(-50) == -50.0
    @test R.logsigmoid(-50f0) == -50.0f0

    x = randn()
    @test -R.σ(-x) ≈ (R.σ(x) - 1.0)
end

@testset "Hat matrix" begin
    λ = 3
    X = randn(5, 3)
    X1 = R.augment_X(X, true)
    @test R.form_XtX(X, true, λ) ≈ X1'X1 + λ*I
end

@testset "Soft-thresh" begin
    x = randn(50)
    η = 0.5
    y = R.soft_thresh.(x, η)

    z = copy(x)
    m1 = x .> η
    m2 = abs.(x) .<= η
    m3 = x .< -η

    z[m1] .= x[m1] .- η
    z[m2] .= 0.0
    z[m3] .= x[m3] .+ η

    @test z == y
end

@testset "Clip" begin
    x = randn(10)
    x[[1, 4, 7]] .= 1e-7 .* rand(3)
    tau = 1e-5
    z = R.clip.(x, tau)
    mask = abs.(x) .>= tau
    @test z[mask] == x[mask]
    @test all(zi == tau for zi in z[.!mask])
end


using SortingNetworks
using Test

function bittuple_impl(::Val{N}) where {N}
    args = [:(ithbit(x, $i)) for i in 0:N-1]
   :(tuple($(args...)))
end
ithbit(x, i) = Bool((x >> i) & true)
@generated bittuple(x, N) = bittuple_impl(N())

function bittuples(vN::Val{N}) where {N}
    @assert N <= 25
    xs = UInt64(1):UInt64(2)^N
    [bittuple(x, vN) for x in xs]
end

@noinline function test_swapsort(vN::Val)
    for cba ∈ bittuples(vN)
        abc = swapsort(cba)
        @test issorted(abc)
    end
end

test_swapsort(n) = test_swapsort(Val{n}())

@testset "test 0-1 principle implementation" begin
    N = 8
    tups = @inferred bittuples(Val{N}())
    @test length(tups) == 2^N
    @test length(unique(tups)) == length(tups)
end

@testset "0-1 principle" for n ∈ 1:25
    test_swapsort(n)
end


#   Unit "hilbert" of the FourierAnalysis Package for julia language
#
#   MIT License
#   Copyright (c) 2019, Marco Congedo, CNRS, Grenobe, France:
#   https://sites.google.com/site/marcocongedo/home

# ? CONTENTS :
#   This unit implements the Hilbert transform and analytic signal
#   estimations for signals of arbitrary length, using FFTW.

"""
```
   (1)
   function analyticsignal( X  :: Union{Vector{T}, Matrix{T}},
                            wl :: Int     = size(X, 1);
                     nonlinear :: Bool    =  false,
                     planner   :: Planner =  getplanner,
                     ⏩       :: Bool    =  true) where T<:Real

   (2)
   function analyticsignal( 𝐗      :: Vector{Matrix{T}},
                            wl     :: Int;
                        nonlinear  ::  Bool      =  false,
                        planner    ::  Planner   =  getplanner,
                        ⏩        ::  Bool      =  true) where T<:Real
```

(1)

Compute the analytic signal(AS) of vector `X` or of
all column vectors of matrix `X` via the FFT and iFFT procedure,
as explained in Marple(1999).
If `wl`=size(X, 1) (default), use the standard method passing to the FFT
and iFFT all samples in `X` altogether, whereas if `wl`<size(X, 1) a Welch-like
method is used (see below).

Return the analytic signal `𝑌`, a complex vector if `X` is a vector
or a complex matrix holding in its columns the analytic signal of the
columns of `X` if `X` is a matrix. `𝑌` has the same number of samples (rows) as `X`.
Contrarely to what is done in the [DSP](https://github.com/JuliaDSP/DSP.jl)
package, the DC level of the signal is removed, therefore,
if the input signal features a non-zero DC level,
the real part of the AS will be equal to the input signal with the
DC level removed. The imaginary part of `𝑌` is the Hilbert transform
of such no-DC `X`.

The Welch-like AS allows an efficient estimation of the AS for vectors and
matrices of any length, that is, even if they are very large; it proceeds
computing the AS on 50% sliding overlapping windows and forming the AS
by retaining the central half of each window. The number of points effectively
used to obtain the final estimation is ``wl÷2`` (integer division)
if `wl` is even, ``wl÷2+1`` otherwise. Using the central half of the
AS computed at each window eliminates edge effects in the range ``wl÷2:end-wl÷2``.
Before running the procedure, ``wl÷2`` zeros are padded at the beginning
and at the end of `X` and trimmed on exiting the function,
in order to reduce edge effects at the beginning
and end of `X`. This procedure does not eliminate these edge effects
completely, thus the first and last ``wl÷2`` samples of the AS estimation
should be discarded. In practice, one requires the AS of a larger data
segment and trims at least ``wl÷2`` samples at the beginning and end of
the estimation. This is done automatically by the [`TFanalyticsignal`](@ref)
function.

!!! note "Nota Bene"
    In order to avoid FFT computation of very long epochs,
    if `wl` > 2^14, then `wl` is set to 2^10.

    See [window length in FFTW](@ref) for setting efficiently argument `wl`.

    The input signal should be previously band-pass or high-pass filtered
    so as not to contain frequency components below the first discrete
    Fourier frequency obtained with windows of `wl` samples, that is,
    below sr/wl Hz, where sr is the sampling rate.

**Optional Keyword Arguments**:

`nonlinear`, if true, the analytic signal is normalized so that its amplitude
is ``1.0`` at all points. This allow non-linear univariate and bivariate estimations
(see [timefrequencyuni.jl](@ref) and [timefrequencybi.jl](@ref)).

`planner` is an instance of the [`Planner`](@ref) object, holding the forward
and backward FFTW plans used to compute the FFTs and the iFFTs.
By default the planner is computed, but it can be passed as an
argumet here if it is pre-computed. This is interesting if the
`analyticsignal` function is to be invoked repeatedly.

if `⏩` is true, the method is run in multi-threaded mode across the series in `X`
if the number of series is at least twice the number of threads Julia
is instructed to use. See [Threads](@ref).

(2)

Compute the analytic signal for all ``k``
multivariate data matrices given as a vector of matrices `𝐗`.
Return a vector of matrices hodling the corresponding analytic signals
as in method (1). The FFT and iFFT plans are computed only once.
The ``k`` matrices in `𝐗` may have different number of columns (i.e., different
number of series) and different number of rows (samples).
However, the number of rows must be larger than `wl` for all of them.
if `⏩` is true, this method run in multi-threaded mode across the
matrices in `𝐗` if the number of matrices is at least twice
the number of threads Julia is instructed to use, otherwise it
tries to run each analytic signal estimation in multi-threaded mode
as per method (1). See [Threads](@ref).

This function is called by the following functions operating
on time-frequency reprsentations: [`TFanalyticsignal`](@ref),
[`TFamplitude`](@ref), [`TFphase`](@ref), [`meanAmplitude`](@ref),
[`concentration`](@ref), [`meanDirection`](@ref), [`comodulation`](@ref),
[`coherence`](@ref).

**References**
Marple S.L. (1999)
Computing the Discrete-Time Analytic Signal via FFT.
IEEE Transactions on Signal Processing 47(9), 2600-3.

**Examples**:
```
using FourierAnalysis, FFTW, LinearAlgebra, Statistics, Plots, DSP
t=128; lab=["x", "real(y)", "imag(y)"]

# Analytic signal of one vector
x=sinusoidal(10, 2, 128, t, π/2; DC=10) # cosine
y=analyticsignal(x)
# note that real(y) is x without the DC level, i.e., x=real(y)+DC
plot([x, real(y), imag(y)]; labels=lab)

# make a check
s=sinusoidal(10, 2, 128, t, 0) # sine
norm(s-imag(y)) # should be zero

# Analytic Signal by DSP.jl
y2=hilbert(x)
norm(s-imag(y2)) # should be zero
# DSP.jl does not remove the DC level
# thus x=real(y2) in this case
plot([x, real(y2), imag(y2)]; labels=lab)

# Analytic signal of multiple vectors
x=hcat(x, sinusoidal(10, 3, 128, t, π/2; DC=10))
y=analyticsignal(x)

# welch-like analytic signal of one vector
# (note edge effects)
x=sinusoidal(10, 2, 128, t*4, π/2; DC=0)
y=analyticsignal(x, t)
plot([x, real(y), imag(y)]; labels=lab)

# Welch-like analytic signal of multiple vectors
x=hcat(x, sinusoidal(10, 3, 128, t*4, π/2; DC=0))
y=analyticsignal(x, t)
```
"""
function analyticsignal( X  :: Union{Vector{T}, Matrix{T}},
                         wl :: Int     = size(X, 1);
                  nonlinear :: Bool    =  false,
                  planner   :: Planner =  getplanner,
                  ⏩       :: Bool    =  true) where T<:Real

   # get all needed paramters (see below the _paramHT! function)
   X_, 𝚙, i𝚙, e, tₐₗₗ, n, wl½, two_wl⁻¹, cT, f, g, ζ = _paramHT!(X, wl, planner)

   𝑌=zeros(cT, tₐₗₗ, n) # Preallocate memory for the Analytic Signal of X

   # Add to `𝑌` the analytic signal of a vector `X_` or of the column vectors of
   # matrix `X_`, computed on the epoch(s) of duration `t` samples starting at
   # sample `at`. It does not return anything.
   # ARGUMENTS:
   # `n` (Int) is the number of columns in `X_`; it must be 1 if `X_` is a vector.
   # `wl½` = wl÷2
   # `two_wl⁻¹` = 2/wl
   # `e` (Int) the number of sliding windows
   # `𝚙` is the forward FFTW plan performing the FFT.
   # `i𝚙` is the backward FFTW plan performing the iFFT (Hilber transform).
   # `ζ`, =zeros(cT, wl-wl½_), a zero-vector append to the iFFT vectors, since the second half of the FFT is not computed
   # `f`, the lower limit (in samples) of the central half of each Hilbert transform to cumulate with respect to the FFT window
   # `g`, the upper limit (in samples) of the central half of each Hilbert transform to cumulate with respect to the FFT window
   function _analyticsignal!(at, 𝚗)
      y = 𝚙*X_[at:at+wl-1, 𝚗]        # FFT
      @inbounds begin                # transform
         y[1]=0.
         for i=2:wl½ y[i] *= two_wl⁻¹ end
      end
      e==1 ? 𝑌[:, 𝚗]+=i𝚙*vcat(y, ζ) : 𝑌[at+f-1:at+g-1, 𝚗]+=(i𝚙*vcat(y, ζ))[f:g]  # iFFT, cumulate
   end

   # add the Analytic Signal
   as!(𝚎, 𝚗)=_analyticsignal!((𝚎*wl½)+1, 𝚗)
   _thread(⏩, n) ? (for 𝚎=0:e-1 @threads for 𝚗=1:n as!(𝚎, 𝚗) end end) : (for 𝚗=1:n, 𝚎=0:e-1 as!(𝚎, 𝚗) end)

   # return a vector if `X` is a vector, a matrix if `X` is a matrix.
   # for the high-resolution version eliminate the first and last t½ samples that have been previously padded.
   𝑌_ = n==1 ? (e>1 ? 𝑌[:][wl½+1:end-wl½] : 𝑌[:]) : (e>1 ? 𝑌[wl½+1:end-wl½, 1:n] : 𝑌)
   if nonlinear @inbounds for i in eachindex(𝑌_) 𝑌_[i]/=abs(𝑌_[i]) end end
   return 𝑌_
end

function analyticsignal( 𝐗      :: Vector{Matrix{T}},
                         wl     :: Int;
                     nonlinear  ::  Bool      =  false,
                     planner    ::  Planner   =  getplanner,
                     ⏩        ::  Bool      =  true) where T<:Real

   cT, k=typeof(Complex(T(0))), length(𝐗)

   planner ≠ getplanner ? plan=planner : plan=Planner(plan_estimate, -1.0, wl, T, true)

   𝒀=Vector{Matrix{cT}}(undef, k)

   # function to add the analytic signal
   as(i, ⏩)=analyticsignal(𝐗[i], wl; nonlinear=nonlinear, planner=plan, ⏩=⏩)
   _thread(⏩, k) ? (@threads for i=1:k 𝒀[i]=as(i, false) end) : (for i=1:k 𝒀[i]=as(i, ⏩) end)

   return 𝒀
end

# Internal function: prepare all parameters that are needed for
# Analytic Signal and High-Resolution Analytic Signal estimations
#
# ARGUMENTS:
# `X` is the input data vector or matrix (n time-series)
# `inrange` is the range (UnitRange{Int}, e.g., 128:1280-1) of the vectors
#      or of rows of `X` on which the analysis is to be performed
# `wl` (Int) is the epoch length for FFT
# `plan` a FFTW.rFFTWPlan forward plan for computing the FFT
# `plan` a FFTW.cFFTWPlan backward plan for computing the FFT
# `planflag` flags for the computation of the plan by FFYW. See doc of `spectra`
# `plantime` (Int) is the maximum time allowed (in seconds) to optimize the FFTW plan
# `⏩` if true multi-threaded computations are requested
#
# RETURN
# `X_`: if a standard anaytic signal is requested (t==size(X, 1)) then X_=X
#       else X_ is the data in `X` with `t`-1 zeros prepended and appended
# `𝚙` return the forward FFTW plan. It is computed if argument `plan`=`computeplan`
# `i𝚙` return the backward FFTW plan. It is computed if argument `iplan`=`computeiplan`
# `e` (Int) the number of sliding windows for the validated `inrange`, always with 1-sample step
# `n` (Int) 1 if `X` is a Vector, the number of columns of `X` otherwise
# `from` the first sample in the validated `inrange`
# `wl½` `t`÷2 computed by the right aritmetic bit-wise operation `wl`>>1
# `wl½_`, the length of FFT vectors:  t½+1 if DC=true, t½ is DC=false
# `two_wl⁻¹` = 2/wl
# `cT` the complex type corresponding to type `T` in function definition (e.g., ComplexF64 if T=Float64)
# `thrn`: true if ⏩=true and # of threads is >1 and is >= 2*`n`, false otherwise
# `navg`, the number of analytic signal estimations for each sample. This is
#       different from `e` because only the central half of each Hilbert transform is used for each window
# `f`, the lower limit (in samples) of the central half of each Hilbert transform to cumulate with respect to the FFT window
# `g`, the upper limit (in samples) of the central half of each Hilbert transform to cumulate with respect to the FFT window
# `ζ`, =zeros(cT, wl-wl½_), a zero-vector append to the iFFT vectors, since the second half of the FFT is not computed
# NB if wl > 2^14 then t is set to 2^10. This affects the default behavior of all AS functions
function _paramHT!( X       :: AbstractArray{T},
                    wl      :: Int,
                    planner :: Planner) where T<:Real

    size(X, 1) < wl && @error 📌*", the number of samples in input matrix is smaller than the desired window length."
    wl > 2^14 ? wl = 2^10 : nothing
    n, wl½, two_wl⁻¹ = size(X, 2), (wl>>1), T(2/wl)
    if size(X, 1)>wl
        X_=[zeros(T, wl½, n); X; zeros(T, wl½, n)] else X_=X
        isodd(wl) && @error 📌*", for Welch-like Analytic Signal estimation `wl` must be even."
    end
    tₐₗₗ = size(X_, 1) # for high-resolution anaytic signal, this is not size(X, 1)
    cT = typeof(Complex(T(0)))
    if planner ≠ getplanner
       𝚙=planner.p
       i𝚙=planner.ip
    else
       𝚙=plan_rfft(zeros(T, wl), flags=plan_estimate, timelimit=-1.0)
       i𝚙=plan_bfft(zeros(cT, wl), flags=plan_estimate, timelimit=-1.0)
    end
    f=wl½÷2+1 # lower limit of central region to copy
    g=f+wl½-1 # upper limit of central region to copy
    e = (tₐₗₗ-wl)÷wl½+1 # number of 50% overlapping epochs
    ζ=zeros(cT, wl-wl½-1)

    return X_, 𝚙, i𝚙, e, tₐₗₗ, n, wl½, two_wl⁻¹, cT, f, g, ζ
end


"""
This module contains functiosn for generating datasets suitable for testing density estimation
"""
module GenerateDatasets

using Distributions
using StatsBase
using StaticArrays
import DensityEstimationML: sample_from_cdf

export approximate_support, original_sample

struct RectangularInterval{T}
    lb::T
    ub::T
end

Base.minimum(iv::RectangularInterval) = iv.lb
Base.maximum(iv::RectangularInterval) = iv.ub

"""
    approximate_support(d)

Returns the effective support for a distribution,
This can be overloaded to provide a restricted support for distribitions that have infinitely large supports,
but can be approximated by bounding them.
Falls back to `support(d)` + 10% to either side
"""
function approximate_support(d)
    sp = support(d)
    a=minimum(sp)
    b=maximum(sp)
    len = b-a
    typeof(sp)(a-0.1*len, b+0.1*len)
end

"""
    original_sample(d)
Returns a sample of a distribution of the size that was used in the corresponding paper.
Falls back to a default of 5000 elements
"""
original_sample(d) = rand(d, 5000)

"""
The distribution used by
Magdon-Ismail and Atiya, in their 1998 NIPS paper "Neural Networks for Denity Estimation."
This is a 1D GMM with two peaks, without any conditioning on any properties.
for their SIC and SLC method they tested with  n=100,
for their SIC they additionally tested with n=200

This dataset is not conditional.
"""
struct MagdonIsmailAndAtiya <: UnivariateDistribution{Continuous}
end

function backing_distribition(::MagdonIsmailAndAtiya)
    comp1 = Normal(-30, 3)
    comp2 = Normal(9, 9)
    MixtureModel([comp1, comp2], [0.3, 0.7])
end

approximate_support(::MagdonIsmailAndAtiya) = RealInterval(-50.0, 50.0)
original_sample(d::MagdonIsmailAndAtiya) = rand(backing_distribition(d), 200)

"""
The distribution used by example 1 of
Likas A, 2001, "Probability density estimation using artificial neural networks"

They only estimate it between -12 and 12

This dataset is not conditional.
"""
struct Likas1 <: UnivariateDistribution{Continuous}
end

function backing_distribition(::Likas1)
    MixtureModel([Normal(-7,0.5), Uniform(-3,-1), Uniform(1,3), Normal(7,0.5)])
end
approximate_support(::Likas1) = RealInterval(-12.0,12.0)
original_sample(d::Likas1) = rand(backing_distribition(d), 5000)

for wrapper in [:MagdonIsmailAndAtiya, :Likas1]
    for (mod,funs) in [(:Distributions, [:sampler, :mode, :modes]),
                       (:Base, [:minimum, :maximum, :mean, :var]),
                       (:StatsBase, [:skewness])
                      ]
        for ff in funs
            @eval $mod.$ff(d::$wrapper)=$mod.$ff(backing_distribition(d))
        end
    end
    for (mod,funs) in [(:Distributions, [:pdf, :logpdf, :cdf, :insupport]),
                       (:Base, [:quantile]),
                       (:StatsBase, [:entropy])
                      ]
        for ff in funs
            @eval $mod.$ff(d::$wrapper, x::Real)=$mod.$ff(backing_distribition(d), x)
        end
    end

    for (mod,funs) in [(:Distributions, [:mgf, :cf])]
        for ff in funs
            @eval $mod.$ff(d::$wrapper, x)=$mod.$ff(backing_distribition(d), x)
        end
    end

    @eval StatsBase.kurtosis(d::$wrapper, x::Bool)=StatsBase.kurtosis(backing_distribition(d),x)
end

"""
Generates a dataset with the same parameters as used in example 2 of tests by
Likas A, 2001, "Probability density estimation using artificial neural networks"
and also in
Modha, D. S. & Fainman, 1994 Y. A learning law for density estimation

The PDF is given by:

\[f(x)=\begin{cases}
    \frac{2-\frac{x}{2}}{6.5523}    & 0\le x<2\\
    \frac{2-(x-3)^{2}}{6.5523}      & 0\le x<3+\sqrt{2}\\
    0                   & otherwise
\end{cases}
\]

Thus the CDF is given by
\[
F(x)=\dfrac{1}{6.5523}\begin{cases}
    2x-\frac{x^{2}}{4} & 0\le x<2\\
    \frac{-x^{3}}{3}+3x^{2}-7x+\frac{23}{3} & 2\le x<3+\sqrt{2}\\
    0 & x<0
    1 & x>3+\sqrt{2}
\end{cases}
\]

Both works use the same 5000 samples
This dataset is not conditional.
"""
struct Likas2<:UnivariateDistribution{Continuous}
end

function Distributions.pdf(::Likas2, x::Real)
    1/6.5523 * if (x ≤ 0)
    0.0
    elseif (0<x ≤ 2)
    2.0-x/2
    elseif (2<x≤3+√2)
    2.0-(x-3.0)^2
    else
    @assert(3+√2 < x)
    0.0
    end
end

function Distributions.cdf(::Likas2, x::Real)
    1/6.5523 * if (x ≤ 0)
    0.0
    elseif (0<x ≤ 2)
    2x-x^2/4
    elseif (2<x≤3+√2)
    -x^3/3 + 3x^2 -7x + 23/3
    else
    @assert(3+√2 < x)
    6.5523
    end
end

function Base.rand(::Likas2)
    likas_2_cdf(x) = cdf(Likas2(), x[1])
    likas_2_pdf(x) = pdf(Likas2(), x[1])
    sample_from_cdf(likas_2_cdf, likas_2_pdf, [2.5])[1]
end

original_sample(d::Likas2) = rand(d, 5000)

Base.minimum(::Likas2) = -1.0
Base.maximum(::Likas2) = 6.0

const ModhaAndFainman=Likas2

"""
Generates a dataset with the same parameters as used in example 3 of tests by
Likas A, 2001, "Probability density estimation using artificial neural networks"

It is a 2D uniform retangle between 0 and 0.2 on both axis

Likas used 5000 samples.

This dataset is not conditional.
"""
struct Likas3 <: ContinuousMultivariateDistribution
end

Distributions.support(::Likas3)=RectangularInterval(@SVector([0., 0.]), @SVector([0.2, 0.2]))
approximate_support(::Likas3)=RectangularInterval(@SVector([-0.1, -0.1]), @SVector([0.3, 0.3]))
Base.rand(::Likas3) = 0.2 .* rand(2)
Base.length(::Likas3) = 2
original_sample(::Likas3, n=5000) = 0.2 .* rand((2, n))

function Distributions._pdf(d::Likas3, X::AbstractVector)
    length(X)==2 || ArgumentError("Likas3 only supports 2D inputs")
    if all(0.<X.<0.2)
        1/(0.2^2)
    else
        0.0
    end
end

Distributions._logpdf(d::Likas3, X::AbstractVector) = log(Distributions._pdf(d,X))

end #module


struct ConvexPolygon{D, N, T}
    pts::SVector{N, SVector{D, T}}
end

function ConvexPolygon(pts::Array{Array{T, 1}}, ::Val{D}, ::Val{N}) where {D, N, T}
    ConvexPolygon(SVector{N, SVector{D, T}}(pts...))
end

function support(poly::ConvexPolygon{D, N, T}, dir::SVector{D, T}) where {D, N, T}
    @inbounds poly.pts[argmax(Ref(dir').*poly.pts)]
end

function point(pt::SVector{D, T}) where {D, T}
    ConvexPolygon(SVector{1, SVector{D, T}}(pt))
end

macro point(ex)
    :(point(@SVector $ex))
end

function line(ptA::SVector{D, T}, ptB::SVector{D, T}) where {D, T}
    ConvexPolygon(SVector{2, SVector{D, T}}(ptA, ptB))
end

macro line(ex)
    :(line(@SVector($(ex.args[1])), @SVector($(ex.args[2]))))
end

function rect(center::SVector{2, T}, widths::SVector{2, T}) where {T}
    pt1 = center + SVector{2}( 0.5,  0.5).*widths
    pt2 = center + SVector{2}(-0.5,  0.5).*widths
    pt3 = center + SVector{2}(-0.5, -0.5).*widths
    pt4 = center + SVector{2}( 0.5, -0.5).*widths
    ConvexPolygon(SVector{4, SVector{2, T}}(pt1, pt2, pt3, pt4))
end

function rect(center::SVector{3, T}, widths::SVector{3, T}) where {T}
    pt1 = center + SVector{3}( 0.5,  0.5,  0.5).*widths
    pt2 = center + SVector{3}(-0.5,  0.5,  0.5).*widths
    pt3 = center + SVector{3}(-0.5, -0.5,  0.5).*widths
    pt4 = center + SVector{3}( 0.5, -0.5,  0.5).*widths
    pt5 = center + SVector{3}( 0.5,  0.5, -0.5).*widths
    pt6 = center + SVector{3}(-0.5,  0.5, -0.5).*widths
    pt7 = center + SVector{3}(-0.5, -0.5, -0.5).*widths
    pt8 = center + SVector{3}( 0.5, -0.5, -0.5).*widths
    ConvexPolygon(SVector{8, SVector{3, T}}(pt1, pt2, pt3, pt4,
                                            pt5, pt6, pt7, pt8))
end

macro rect(ex)
    :(rect(@SVector($(ex.args[1])), @SVector($(ex.args[2]))))
end

function square(center::SVector{D, T}, width::Real) where {D, T}
    widths = width*@SVector(ones(D))
    rect(center, widths)
end

macro square(ex)
    :(square(@SVector($(ex.args[1])), $(ex.args[2])))
end

function randpoly(center=[0., 0.], θ=0.0; n::Int=10, scale=1.0)
    # from http://cglab.ca/~sander/misc/ConvexGeneration/convex.html
    xs = sort(rand(n))
    ys = sort(rand(n))
    minx = xs[1]; maxx = xs[end]
    miny = ys[1]; maxy = ys[end]
    xv = similar(xs)
    yv = similar(ys)
    ltop = minx; lbot = minx;
    llef = miny; lrig = miny;
    for i = 2:n-1
        if rand(Bool)
            xv[i-1] = xs[i] - ltop
            ltop = xs[i]
        else
            xv[i-1] = lbot - xs[i]
            lbot = xs[i]
        end
        if rand(Bool)
            yv[i-1] = ys[i] - llef
            llef = ys[i]
        else
            yv[i-1] = lrig - ys[i]
            lrig = ys[i]
        end
    end
    xv[n-1] = maxx - ltop
    xv[n] = lbot - maxx
    yv[n-1] = maxy - llef
    yv[n] = lrig - maxy
    shuffle!(xv)
    shuffle!(yv)
    vec = [[xv[i], yv[i]] for i = 1:n]
    sort!(vec, by = x -> atan(x[2], x[1]))
    v = [0.0, 0.0]
    minv = [0.0, 0.0]
    pts = similar(vec)
    for i = 1:n
        pts[i] = v
        v += vec[i]
        minv = min.(minv, v)
    end
    shift = [minx-0.5, miny-0.5] - minv
    pts .+= Ref(shift)
    rotate = [cos(θ) -sin(θ); sin(θ) cos(θ)]
    pts = Ref(rotate).*pts
    pts *= scale
    pts .+= Ref(center)
    ConvexPolygon(pts, Val(2), Val(n))
end


##=================####==============##
## TEST SIMULATION ABM NETWORK-EPIDEMICS
##=================####==============##

##=================####==============##

using StatsBase     # For sample function
using SparseArrays  # For Adyacency Matrix
using Distributions # For probability distributions
using DelimitedFiles # For reading/writing files
using LightGraphs

using Plots         # plotting
using LaTeXStrings  # latex labels in plots

##=================####==============##

include("NetABM.jl") # Include file with functions and definitions

##=================####==============##
# PATH TO SAVE IMGS (MODIFY WITH YOUR OWN!)

img_out_path = joinpath(homedir(), "Code", "Collaborations", "networkEpidemiology_MX", "julia_ABM", "figures")

##=================####==============##
## SYSTEM'S PARAMETERS

params = Params()

params.num_agents    = 2^16 # Number of agents (32768)
params.p_infected_t0 = 0.01 # Initial fraction of infected agents
params.attack_rate   = 0.25 # Virus attack rate
params.repetition    = 1 # Repetition ID for ensemble statistics

params.μ_cop_agents  = 0.15 # Mean of cooperation probability
params.σ_cop_agents  = 0.1 # Std deviation of cooperation probability
params.μ_recovT_agents = 7 # Mean recovery time

# Link probability (Erdos-Renyi)
# params.p_link = ((1+5exp10(-2))*log(params.num_agents))/params.num_agents

##=================####==============##
# INITIALIZE ADJACENCY MATRIX (LFR Model)

the_root = joinpath(repo_path, "src", "binary_networks")

println("BUILDING NETWORK...")

the_net, true_com = get_network(
    the_root,
    "julia_ABM",
    N    = params.num_agents,
    t2   = 0.25,
    minc = 100,
    maxc = 1000);

g, Nodes = lectura_uw(the_net)

adj_mat = adjacency_matrix(g)
rows = rowvals(adj_mat)

println("NETWORK DONE")

##=================####==============##
# INITIALIZE MEETS, COOPERATION AND RECOVERY TIMES DISTRIBUTION

rt_μ = params.μ_recovT_agents # MEAN RECOVERY TIME
pc_μ = params.μ_cop_agents # MEAN COOPERATION PROBABILITY
pc_σ = params.σ_cop_agents # STD_DEV COOPERATION PROBABILITY

# meets_dist = Exponential()
meets_dist  = Geometric()
coop_dist   = truncated(Normal(pc_μ, pc_σ), 0.0, 1.0)
recovt_dist = truncated(Poisson(rt_μ), 7, 35)

##=================####==============##

# DEFINE AGENTS POPULATION (DEFAULT VALUES)
agents = [Agent(id) for id in 1:params.num_agents]

# MODIFY AGENT'S CHARACTERISTICS ACCORDING TO ITS DEMOGRAPHICS
initialize_demographics!(agents, params, coop_dist, meets_dist, recovt_dist)

# ASSGINS CONTACTS TO agent FROM adj_mat
for ag in agents
    assign_contacts!(ag, agents, adj_mat, rows)
end

##=================####==============##

time_lockdown = 15

tau = []
num_S = []
num_I = []
num_R = []

# INITIALIZE TIME AND POPULATIONS
params.now_t = 0
populations = get_populations(agents, params)

while populations[2] > 0.0 # WHILE THERE ARE INFECTED AGENTS

    # RECORD DATA
    push!(tau, params.now_t)
    push!(num_S, populations[1])
    push!(num_I, populations[2])
    push!(num_R, populations[3])

    # EACH AGENTS DECIDES TO STAY AT HOME/ NOT TO STAY AT HOME
    if params.now_t >= time_lockdown
        set_coop_agents!(agents, params)
    end

    # FINDS NEXT STATE AND UPDATES ALL AGENTS
    update_all_agents!(agents, params)

    # GET CURRENT POPULATIONS AND UPDATE TIME
    global populations = get_populations(agents, params);
    params.now_t += 1
end

println("END")

##=================####==============##
# POPULATIONS GRAPH
#
plt = plot(
        xlabel = L"t",
        ylabel = L"N(s)/N",
        guidefontsize = 16,
        tickfontsize = 10
    )

plot!(plt, tau, num_S, color = :blue,  lab = "S", lw = 1.25)
plot!(plt, tau, num_I, color = :red,   lab = "I", lw = 1.25)
plot!(plt, tau, num_R, color = :green, lab = "R", lw = 1.25)
# plot!(plt, tau, 1.0 .- num_R, color = :green, lab = "R", lw = 1.25)

vline!(plt, [time_lockdown], ls = :dash, color = :red, lab = "")

display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "SIR_N_$(params.num_agents)_AR_$(params.attack_rate).png"))
##=================####==============##
# Distribución de Números de Encuentros

plt = plot(
        xlabel = L"N_m",
        ylabel = L"N (N_m)",
        title = "Encounters per agent per time step distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

vals = [ag.num_meets for ag in agents]
histogram!(plt, vals, lab = false, bar_width = 0.85)

xticks!(plt, 1:maximum(vals))
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "meetsDist_N_$(params.num_agents).png"))
##=================####==============##
# Distribución de Tiempo de Recuperación

plt = plot(
        xlabel = L"R_t",
        ylabel = L"N(R_t)",
        title = "Agent's recovery times distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

vals = [ag.recovery_t for ag in agents]
histogram!(plt, vals, lab = false)

# histogram!(plt, vals, normalized = true, lab = false)

# xticks!(plt, unique(vals))
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "recovtDist_N_$(params.num_agents).png"))
##=================####==============##
# Distribución de Probabilidad de Coperación

plt = plot(
        xlabel = L"p_C",
        ylabel = L"N(p_C)",
        title = "Agent's cooperation probability distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

vals = [ag.p_cop for ag in agents]
histogram!(plt, vals, lab = false)

display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "cooProb_N_$(params.num_agents).png"))
##=================####==============##
# Tiempo de Infección

plt = plot(
        xlabel = L"t_I",
        ylabel = L"p(t_I)",
        title = "Time of infection distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

histogram!(plt, [ag.infection_t for ag in agents], normalized = false, lab = false)
vline!(plt, [time_lockdown], color = :red, ls = :dash, lw = 1.25, lab = "lockdown")
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "tiempoI_N_$(params.num_agents)_AR_$(params.attack_rate).png"))
##=================####==============##
# Distribución de Grado

plt = plot(
        xlabel = L"\kappa_i",
        ylabel = L"p\;(\kappa_i)",
        title = "Network's degree distribution ",
        guidefontsize = 16,
        tickfontsize = 10
    )

histogram!(plt, [ag.degree_t for ag in agents], normalized = true, lab = false)
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "degDist_N_$(params.num_agents)_ER.png"))
##=================####==============##


#
module MultTest

export X
export ptest43, ptest51, ptest52, ptest53

import Polynomials
using Polynomials

# Standard polynomial
X = poly([0.0])
#some test polynomials used by Zeng

# polynomial with roots 1:4 to the power of k
# k = 1..8
function ptest46(k::Int)
    ((X-1)^4 * (X-2)^3 * (X-3)^2 * (X-4) ) ^k
end

# polynomial with non-integer roots
function ptest51(dig::Int)
   p = (X - 10/11)^5 * (X - 20/11)^5 * (X - 30/11)^5
   Poly(map(i->round(p.a[i], dig), 1:length(p.a)))
end

# polynomial with adjacent roots close to 1 with given multiplicity
# epsilon = 0.1, 0.01, 0.001 ...
function ptest52(epsilon::Number)
    (X - 1 + epsilon)^20 * (X - 1)^20 * (X + 0.5)^5
end

# polynomial with given roots (including all conjugate roots) to the power of 2^k
function ptest53(k::Int)
	c = [0.5+im, -1.0+0.2im, -0.1+im, +0.8+0.6im, -0.7+0.7im,
		 1.4, -0.4+0.9im, 0.9, -0.8+0.3im, 0.3+0.8im, 0.6+0.4im]

    fX = Poly([1.0])
    for i = 1:length(c)
        a = c[i]
        if isreal(a)
            fX = fX * (X - a)
        else
            fX = fX * ((X-real(a))^2 + imag(a)^2)
        end
    end

	for i = 1:k
        fX = fX * fX
    end
    fX
end

end

import MultTest
using MultTest


"""
    DualComplex{T <: Real} = Nilplexification{Cmplex{T}}

A dual-complex number is a nilplexification of the complex numbers.
"""
const DualComplex{T <: Real} = Nilplexification{Cmplex{T}}

function DualComplex(z::Cmplex{T}) where T <: Real
    DualComplex{T}(z, zero(Cmplex{T}))
end

function DualComplex(a::T, b::T, c::T, d::T) where T <: Real
    DualComplex{T}(Cmplex{T}(a, b), Cmplex{T}(c, d))
end

function DualComplex(a::T, b::T, c::T) where T <: Real
    DualComplex{T}(Cmplex{T}(a, b), Cmplex{T}(c, zero(T)))
end

function DualComplex(a::T, b::T) where T <: Real
    DualComplex{T}(Cmplex{T}(a, b), zero(Cmplex{T}))
end

function DualComplex(a::T) where T <: Real
    DualComplex{T}(Cmplex{T}(a, zero(T)), zero(Cmplex{T}))
end

function DualComplex(a::Real, b::Real)
    DualComplex(promote(a, b)...)
end

function DualComplex(a::Real, b::Real, c::Real)
    DualComplex(promote(a, b, c)...)
end

function DualComplex(a::Real, b::Real, c::Real, d::Real)
    DualComplex(promote(a, b, c, d)...)
end

"""
    unreal(z::DualComplex)

The unreal part of a dual-complex number is a 3-vector.
"""
function unreal(z::DualComplex)
    vcat(unreal(z.l), array(z.r))
end

function show(io::IO, z::DualComplex)
    print(io, "[1: ")
    print(io, z.l.l)
    print(io, ", H: ")
    print(io, z.l.r)
    print(io, ", A: ")
    print(io, z.r.l)
    print(io, ", HA: ")
    print(io, z.r.r)
    print(io, "]")
end

function random(::Type{DualComplex{T}}) where T <: Real
    DualComplex{T}(random(Cmplex{T}), random(Cmplex{T}))
end

function barH(z::DualComplex{T}) where T <: Real
    DualComplex{T}(barH(z.l), barH(z.r))
end

function barA(z::DualComplex{T}) where T <: Real
    DualComplex{T}(z.l, -(z.r))
end

function quadrance(z::DualComplex)
    (z * barA(z)).l
end

function norm(z::DualComplex)
    abs2(quadrance(z))
end

function iszerodivisor(z::DualComplex)
    iszero(quadrance(z))
end

function inv(z::DualComplex)
    if iszerodivisor(z)
        error(ZeroDivisorInverse)
    end

    q = quadrance(z)
    barA(z) * barH(q) / abs2(q)
end


# -----------------------------------------------
# Total Welfare
# -----------------------------------------------

@defcomp fund_welfare begin

    regions  = Index()                                   # 16 regions in FUND.

    ρ                 = Parameter()                      # Pure rate of time preference.
    η                 = Parameter()                      # Elasticity of marginal utility of consumption.
    welfare_year      = Parameter{Int}()                      # First year to begin counting total discounted welfare over time.
    pop               = Parameter(index=[time, regions]) # Regional population levels (millions of people).
    c                 = Parameter(index=[time, regions]) # Regional total consumption levels ($).

    UTILITY           = Variable(index=[time])           # Total economic welfare over model time horizon (using the same name as the default RICE welfare component).
    cpc               = Variable(index=[time, regions])  # Per capita consumption levels ($).

    function run_timestep(p, v, d, t)

        # Calculate regional per capita consumption levels.
        for r in d.regions
            v.cpc[t,r] = p.c[t,r] / p.pop[t,r] / 1000000.0
        end

        # Calculate total welfare for each period. If η = 1, the social welfare function becomes log(x).
        # Note: FUND starts a spin-up period in 1950 to initialize the model, so should not track welfare in early periods.
        if gettime(t) < p.welfare_year
            v.UTILITY[t] = 0.0
        else
            # Calculate total welfare over time.
            if p.η == 1.0
                v.UTILITY[t] = v.UTILITY[t-1] + sum(log.(v.cpc[t,:]) .* p.pop[t,:]) / (1.0 + p.ρ)^(gettime(t) - p.welfare_year)
            else
                v.UTILITY[t] = v.UTILITY[t-1] + sum((v.cpc[t,:] .^ (1.0 - p.η)) ./ (1.0 - p.η) .* p.pop[t,:]) / (1.0 + p.ρ)^(gettime(t) - p.welfare_year)
            end
        end
    end
end


"""
    stream_velocity(r, horseshoes, Γs, V, Ω)

Evaluate the total induced velocity at a point `r` given Horseshoes, vortex strengths `Γ`s, rotation rates `Ω`, and freestream flow vector `freestream` in the global reference frame.
"""
stream_velocity(r, horseshoes, Γs, V, Ω) = sum(x -> velocity(r, x[1], x[2], V / norm(V)), zip(horseshoes, Γs)) + V + Ω × r

"""
    streamlines(point, freestream :: Freestream, horseshoes, Γs, length, num_steps)

Compute the streamlines from a given starting point, a Freestream, Horseshoes and their associated strengths Γs with a specified length of the streamline and number of evaluation points.
"""
function streamlines(V, Ω, horseshoes, Γs, point, length, num_steps :: Integer)
    streamlines = fill(point, num_steps)
    f(x) = stream_velocity(x, horseshoes, Γs, V, Ω)
    for i ∈ 2:num_steps
        update = f(streamlines[i-1])
        streamlines[i] = streamlines[i-1] + (update / norm(update) * length / num_steps)
    end
    streamlines
end

streamlines(fs :: Freestream, refs :: References, vortices, Γs, points, length, num_steps :: Integer) = mapreduce(pt -> streamlines(refs.speed * velocity(fs), fs.omega, vortices, Γs, pt, length, num_steps), hcat, points)

streamlines(system :: VortexLatticeSystem, points, length, num_steps :: Integer) = streamlines(system.freestream, system.reference, system.vortices, system.circulations, points, length, num_steps)


"""
$(SIGNATURES)

Construct the matrix of a random 2D Ising lattice ``∑ᵢⱼJᵢⱼZᵢZⱼ``, where ``Jᵢⱼ``s are uniformly distributed between [-1, 1). Generate sparse matrix when `sp` is set to true.
"""
function random_ising(num_qubits::Integer; sp=false)
    J = Vector{Float64}()
    idx = Vector{Vector{Int64}}()
    for i ∈ 1:num_qubits
        for j ∈ i+1:num_qubits
            push!(J, 2*rand()-1)
            push!(idx, [i, j])
        end
    end
    two_local_term(J, idx, num_qubits, sp=sp)
end

"""
$(SIGNATURES)

Construct the matrix of the alternating sectors chain model described in (https://www.nature.com/articles/s41467-018-05239-9#Sec1). Generate sparse matrix when `sp` is set to true.
"""
function alt_sec_chain(w1, w2, n, num_qubits; sp=false)
    J = Vector{Float64}()
    idx = Vector{Vector{Int64}}()
    for i in 1:num_qubits-1
        push!(J, isodd(ceil(Int, i/n)) ? w1 : w2)
        push!(idx, [i, i+1])
    end
    two_local_term(J, idx, num_qubits, sp=sp)
end

function build_example_hamiltonian(num_qubits; sp=false)
    A = (s) -> (1 - s)
    B = (s) -> s
    if num_qubits == 1
        sp ? SparseHamiltonian([A, B], [spσx, spσz]) : DenseHamiltonian([A, B], [σx, σz])
    elseif num_qubits == 4
        Hd = -standard_driver(4, sp = sp)
        Hp = -q_translate("ZIII+IZII+IIZI+IIIZ", sp = sp)
        sp ? SparseHamiltonian([A, B], [Hd, Hp]) : DenseHamiltonian([A, B], [Hd, Hp])
    end
end


# Create a maze using the depth-first algorithm described at
# https://scipython.com/blog/making-a-maze/
# Christian Hill, April 2017.

using DataStructures

@enum Direction North South East West

# A cell in the maze.
# A maze "Cell" is a point in the grid which may be surrounded by walls to
# the north, east, south or west.
struct Cell
    x::Int
    y::Int
    walls::Dict

    # Initialize the cell at (x,y). At first it is surrounded by walls.
    function Cell(x, y)
        walls = Dict(North => true, South => true, East => true, West => true)
        new(x, y, walls)
    end
end

# Does this cell still have all its walls?
function has_all_walls(cell::Cell)
    return all(values(cell.walls))
end

# Knock down the wall between cells self and other.
function knock_down_wall(cell::Cell, other::Cell, wall::Direction)

    # A wall separates a pair of cells in the N-S or W-E directions.
    wall_pairs = Dict(North => South, South => North, East => West, West => East)

    cell.walls[wall] = false
    other.walls[wall_pairs[wall]] = false
end

# Return a list of unvisited neighbours to cell
function find_valid_neighbours(maze::Matrix{Cell}, cell::Cell)

    delta = OrderedDict(West => [-1 0], East => [1 0], South => [0 1], North => [0 -1])

    neighbours = []

    nx, ny = size(maze)

    for (direction, (dx, dy)) in delta

        x2, y2 = cell.x + dx, cell.y + dy

        if (1 ≤ x2 ≤ nx) && (1 ≤ y2 ≤ ny)
            neighbour = maze[x2, y2]
            if has_all_walls(neighbour)
                push!(neighbours, (direction, neighbour))
            end
        end
    end

    return neighbours
end

function make_maze(nx::Int, ny::Int, randoms=nothing)

    # Total number of cells.
    n = nx * ny

    maze = [Cell(x, y) for x = 1:nx, y = 1:ny]
    cell_stack = []

    # Total number of visited cells during maze construction.
    nv = 1

    current_cell = maze[1, 1]

    while nv < n
        neighbours = find_valid_neighbours(maze, current_cell)

        if isempty(neighbours)
            # We've reached a dead end: backtrack.
            current_cell = pop!(cell_stack)
            continue
        end

        # Choose a random neighbouring cell and move to it.
        if randoms === nothing
            k = rand(1:length(neighbours))
        else
            k = randoms[nv]
        end

        direction, next_cell = neighbours[k]
        knock_down_wall(current_cell, next_cell, direction)
        push!(cell_stack, current_cell)
        current_cell = next_cell
        nv += 1
    end

    return maze
end

function get_coordinates_maze_cell(cell_x::Int, cell_y::Int, maze_cell_size::Int)

    x_left = maze_cell_size * (cell_x - 1)
    x_right = maze_cell_size * cell_x
    y_top = maze_cell_size * (cell_y - 1)
    y_bottom = maze_cell_size * cell_y

    return x_left, x_right, y_top, y_bottom

end


# it's supposed that basis is a vector of its elements
# Each element of the basis can be a multidimensional array ()
# Internally it can be implemented differently: as one multidimensional array or as vector of arrays.
# Externally it should make no difference via the following organization.
using RecursiveArrayTools

import Base: length

"""
Abstract type representing any set of functions (`elements`) defined on some subset `ap` of a Cartesian domain.

    `elements(b::Basis)` gives vector of the basis functions.
    `aperture(b::Basis)` gives the  integer mask of non-zero elements of the array.
    `length(b::Basis)` gives the total number of elements in the basis.
"""
abstract type Basis end

abstract type OrthogonalBasis <: Basis end

abstract type OrthonormalBasis <: OrthogonalBasis end

elements(b::Basis) =  b.elements
elements(b::Basis, ind) =  b.elements[ind]

aperture(b::Basis)  = b.ap

# Todo: correct below, returns array instead of VectorOfArray
aperturedelements(b::Basis) =  (b.ap) .* b.elements
aperturedelements(b::Basis, ind) =  (b.ap) .* b.elements[ind]

norms(b::Basis) =  b.norms[:]

norms(b::Basis, ind::Vector) =  b.norms[ind]

length(b::Basis) = length(elements(b))

@doc raw"""
    compose(b, ind, coef) gives linear combination  math`\sum_{i\in ind \lambda_i f_i`

    compose(b, coef) requires `coef` be the complete vector of the coefficients.
"""
compose(b::Basis, ind::Vector, coef::Vector) = length(ind) != length(coef) ? error("Coefficients and indexes are of different length") : comb(coef, elements(b,ind))
compose(b::Basis,  coef::Vector) =  length(elements(b)) != length(coef) ? error("Coefficient vector does not match length of basis") : comb(coef, elements(b))

@doc raw"""

    decompose(a::Array, b::Basis)

Calculate coefficients of `a` in basis `b`.
"""
function decompose(a::Array, b::Basis)
    error("There are no decomposition rules of basis $b")
end

function decompose(a::Array, b::OrthonormalBasis)
    [inner(a,  f, aperture(b) ) for f in elements(b) ]
end

function decompose(a::Array, b::OrthogonalBasis)
    [inner(a,  f, aperture(b) ) / n^2 for (f,n) in zip(elements(b),norms(b)) ]
end

# Function below is for basis implemented as VectorOfArray
# Do we need the same for multidimensional array?
function comb(coef::Vector, a::VectorOfArray)
    sum = similar(a[1]) .* 0
    for i = 1: length(coef)
        sum += coef[i] * a[i]
    end
    return sum
end

# TODO rewrite so it works as tensor inner product if applied to two multidimensional arrays (or use Tensors.jl/ TensorOperations.jl?)
# a proper version of comb
# It can be realised as inner product in Mathematica
# Calculate inner product (tensor contraction) using the last index in `a` and first index in `b`
# Can be also related to scalar product in Banach space. Then inner product `(c, f)` of function `f` and
# constant `c` is defined as inner of `(f) ⋅ (c) = c f` .
"""
    inner(a, b)

Calculate inner product of functions or array of functions.
"""
inner(a::Array{T,N}, b::Array{T,N}) where {T <: Number, N} = integrate(a .* b)
inner(a::Array{T,N}, b::Array{T,N}, domain) where {T <: Number, N} = integrate(a .* b , domain)

inner(a::Number, b::Number) = dot(a,b)
# inner(c::Number, a::Array) = dot(c, sum(a))
# inner(a::Array, c::Number) = dot(sum(a), c)
inner(c::Number, a::Array) = conj(c) * a
inner(a::Array, c::Number) = conj(a) * c

inner(a::Union{Vector, VectorOfArray}, b::Union{Vector, VectorOfArray}) = sum(inner(va,vb) for (va,vb) in zip(a, b))

innermatrix(b::Basis) = [inner(elements(b,i), b.ap .* elements(b,j)) for i in eachindex(elements(b)), j in eachindex(elements(b))]

"""
    integrate(a)

calculate numerical integral of a
"""
# rectangle rule
integrate(a::Array{T,N}) where {T <: Number, N} = sum(a[:])
integrate(a::Array{T,N}, domain::Array{T,N}) where {T <: Number, N} = sum(a[:] .* domain[:])

# TODO simpson's rule on unit disk