module sNMF_MC

include("../Misc.jl")
include("BackwardKernel.jl")
using .Misc, .BackwardKernel

using Distributions, SpecialFunctions

export Particle, smc_weight, particle_mcmc

mutable struct Particle{ℜ <: Real}
    S_R::Array{ℜ,1}
    S_IR::Array{ℜ,2}
    function Particle(I::Int, R::Int; γ::ℜ=0.1) where {ℜ<:Real}
        a::ℜ = I*I*γ
        return new{ℜ}(fill(a/R,R), fill(2.0 * a/(I*R),I,R))
    end
    function Particle(S_R::Array{ℜ,1}, S_IR::Array{ℜ,2}) where {ℜ<:Real}
        return new{ℜ}(S_R, S_IR)
    end
end

function smc_weight(X::Array{ℜ,2}, R::Ƶ, N::Ƶ=1; μ::ℜ=1.0, γ::ℜ=0.1, adaptive::Bool=true) where {ℜ<:Real, Ƶ<:Int}
    I::Ƶ = size(X,1)
    T::Ƶ = Ƶ(sum(X))

    ESS::Array{ℜ} = zeros(ℜ,T)
    ESS_min::Array{ℜ} = adaptive ? fill(N/2.0,T) : fill(N+1.0,T)

    P = [Particle(I,R; γ=γ) for n=1:N]
    P_temp = [Particle(I,R; γ=γ) for n=1:N]

    a::ℜ, b::ℜ = I*I*γ, γ/μ
    log_Zᵗ::ℜ = a*log(b) - (a+T)*log(b + 1.0) - sum(lgamma.(X .+ 1.0))

    log_wᵗ::Array{ℜ}, log_Wᵗ::Array{ℜ} = fill(log_Zᵗ,N), fill(-log(N),N)
    Wᵗ::Array{ℜ}, cum_Wᵗ::Array{ℜ} = fill(1.0/N,N), zeros(ℜ,N)

    log_νᵗ::ℜ, log_q_r::Array{ℜ}, q_r::Array{ℜ} = 0.0, zeros(ℜ,R), zeros(ℜ,R)
    rᵗ::Ƶ, i₁ᵗ::Ƶ, i₂ᵗ::Ƶ, uᵗ::ℜ = 0, 0, 0, 0.0

    for (t,(i₁ᵗ, i₂ᵗ)) in enumerate(EventQueue(X))
        for (n,p) in enumerate(P)

            log_q_r .= log.(p.S_IR[i₁ᵗ,:]) .+ log.(p.S_IR[i₂ᵗ,:] .+ (i₁ᵗ == i₂ᵗ)) .- log.(4.0 .* p.S_R .+ 2)
            log_νᵗ = logsumexp(log_q_r)
            log_q_r .-= log_νᵗ

            q_r .= exp.(log_q_r)
            rᵗ = rand(Categorical(q_r))

            p.S_R[rᵗ] += 1.0
            p.S_IR[i₁ᵗ,rᵗ] += 1.0
            p.S_IR[i₂ᵗ,rᵗ] += 1.0

            log_wᵗ[n] += log_νᵗ
        end

        log_Zᵗ = logsumexp(log_wᵗ)
        log_Wᵗ .= log_wᵗ .- log_Zᵗ
        log_Zᵗ -= log(N)
        Wᵗ .= exp.(log_Wᵗ)

        ESS[t] = 1.0/sum(Wᵗ .* Wᵗ)

        if ESS[t] < ESS_min[t]
            for n=1:N
                P_temp[n].S_R .= P[n].S_R
                P_temp[n].S_IR .= P[n].S_IR
            end

            cum_Wᵗ .= cumsum(Wᵗ)
            uᵗ = rand()/N
            p_id = 1

            for n=1:N # systematic resampling
                while cum_Wᵗ[p_id] < uᵗ
                    p_id += 1
                end
                P[n].S_R .= P_temp[p_id].S_R
                P[n].S_IR .= P_temp[p_id].S_IR

                uᵗ += 1.0/N
            end
            log_wᵗ .= log_Zᵗ
        end
    end
    p_id = rand(Categorical(Wᵗ))
    P[p_id].S_R .-= a/R
    P[p_id].S_IR .-= 2.0 .* a/I*R
    return log_Zᵗ, ESS, P[p_id]
end

end


# Truncated normal distribution
"""
    TruncatedNormal(mu, sigma, l, u)

The *truncated normal distribution* is a particularly important one in the family of truncated distributions.
We provide additional support for this type with `TruncatedNormal` which calls `Truncated(Normal(mu, sigma), l, u)`.
Unlike the general case, truncated normal distributions support `mean`, `mode`, `modes`, `var`, `std`, and `entropy`.
"""
TruncatedNormal

@deprecate TruncatedNormal(mu::Real, sigma::Real, a::Real, b::Real) truncated(Normal(mu, sigma), a, b)

### statistics

minimum(d::Truncated{Normal{T},Continuous}) where {T <: Real} = d.lower
maximum(d::Truncated{Normal{T},Continuous}) where {T <: Real} = d.upper

function mode(d::Truncated{Normal{T},Continuous}) where T <: Real
    μ = mean(d.untruncated)
    d.upper < μ ? d.upper :
    d.lower > μ ? d.lower : μ
end

modes(d::Truncated{Normal{T},Continuous}) where {T <: Real} = [mode(d)]

# do not export. Used in mean
# computes mean of standard normal distribution truncated to [a, b]
function _tnmom1(a, b)
    if !(a ≤ b)
        return oftype(middle(a, b), NaN)
    elseif a == b
        return middle(a, b)
    elseif abs(a) > abs(b)
        return -_tnmom1(-b, -a)
    elseif isinf(a) && isinf(b)
        return zero(middle(a, b))
    end
    Δ = (b - a) * middle(a, b)
    if a ≤ 0 ≤ b
        m = √(2/π) * expm1(-Δ) * exp(-a^2 / 2) / (erf(a/√2) - erf(b/√2))
    elseif 0 < a < b
        z = exp(-Δ) * erfcx(b/√2) - erfcx(a/√2)
        iszero(z) && return middle(a, b)
        m = √(2/π) * expm1(-Δ) / z
    end
    return clamp(m, a, b)
end

# do not export. Used in var
# computes 2nd moment of standard normal distribution truncated to [a, b]
function _tnmom2(a::Real, b::Real)
    if !(a ≤ b)
        return oftype(middle(a, b), NaN)
    elseif a == b
        return middle(a, b)^2
    elseif abs(a) > abs(b)
        return _tnmom2(-b, -a)
    elseif isinf(a) && isinf(b)
        return one(middle(a, b))
    elseif isinf(b)
        return 1 + √(2 / π) * a / erfcx(a / √2)
    end

    if a ≤ 0 ≤ b
        ea = √(π/2) * erf(a / √2)
        eb = √(π/2) * erf(b / √2)
        fa = ea - a * exp(-a^2 / 2)
        fb = eb - b * exp(-b^2 / 2)
        m2 = (fb - fa) / (eb - ea)
        return m2
    else # 0 ≤ a ≤ b
        exΔ = exp((a - b)middle(a, b))
        ea = √(π/2) * erfcx(a / √2)
        eb = √(π/2) * erfcx(b / √2)
        fa = ea + a
        fb = eb + b
        m2 = (fa - fb * exΔ) / (ea - eb * exΔ)
        return m2
    end
end

# do not export. Used in var
function _tnvar(a::Real, b::Real)
    if a == b
        return zero(middle(a, b))
    elseif a < b
        m1 = _tnmom1(a, b)
        m2 = √_tnmom2(a, b)
        return (m2 - m1) * (m2 + m1)
    else
        return oftype(middle(a, b), NaN)
    end
end

function mean(d::Truncated{Normal{T},Continuous}) where T <: Real
    d0 = d.untruncated
    μ = mean(d0)
    σ = std(d0)
    if iszero(σ)
        return mode(d)
    else
        a = (d.lower - μ) / σ
        b = (d.upper - μ) / σ
        return μ + _tnmom1(a, b) * σ
    end
end

function var(d::Truncated{Normal{T},Continuous}) where T <: Real
    d0 = d.untruncated
    μ = mean(d0)
    σ = std(d0)
    if iszero(σ)
        return σ
    else
        a = (d.lower - μ) / σ
        b = (d.upper - μ) / σ
        return _tnvar(a, b) * σ^2
    end
end

function entropy(d::Truncated{Normal{T},Continuous}) where T <: Real
    d0 = d.untruncated
    z = d.tp
    μ = mean(d0)
    σ = std(d0)
    a = (d.lower - μ) / σ
    b = (d.upper - μ) / σ
    aφa = isinf(a) ? 0.0 : a * normpdf(a)
    bφb = isinf(b) ? 0.0 : b * normpdf(b)
    0.5 * (log2π + 1.) + log(σ * z) + (aφa - bφb) / (2.0 * z)
end

### sampling

## Use specialized sampler, as quantile-based method is inaccurate in
## tail regions of the Normal, issue #343

function rand(rng::AbstractRNG, d::Truncated{Normal{T},Continuous}) where T <: Real
    d0 = d.untruncated
    μ = mean(d0)
    σ = std(d0)
    if isfinite(μ)
        a = (d.lower - μ) / σ
        b = (d.upper - μ) / σ
        z = randnt(rng, a, b, d.tp)
        return μ + σ * z
    else
        return clamp(μ, d.lower, d.upper)
    end
end

# Rejection sampler based on algorithm from Robert (1995)
#
#  - Available at http://arxiv.org/abs/0907.4010

function randnt(rng::AbstractRNG, lb::AbstractFloat, ub::AbstractFloat, tp::AbstractFloat)
    local r::AbstractFloat
    if tp > 0.3   # has considerable chance of falling in [lb, ub]
        r = randn(rng)
        while r < lb || r > ub
            r = randn(rng)
        end
        return r

    else
        span = ub - lb
        if lb > 0 && span > 2.0 / (lb + sqrt(lb^2 + 4.0)) * exp((lb^2 - lb * sqrt(lb^2 + 4.0)) / 4.0)
            a = (lb + sqrt(lb^2 + 4.0))/2.0
            while true
                r = rand(rng, Exponential(1.0 / a)) + lb
                u = rand(rng)
                if u < exp(-0.5 * (r - a)^2) && r < ub
                    return r
                end
            end
        elseif ub < 0 && ub - lb > 2.0 / (-ub + sqrt(ub^2 + 4.0)) * exp((ub^2 + ub * sqrt(ub^2 + 4.0)) / 4.0)
            a = (-ub + sqrt(ub^2 + 4.0)) / 2.0
            while true
                r = rand(rng, Exponential(1.0 / a)) - ub
                u = rand(rng)
                if u < exp(-0.5 * (r - a)^2) && r < -lb
                    return -r
                end
            end
        else
            while true
                r = lb + rand(rng) * (ub - lb)
                u = rand(rng)
                if lb > 0
                    rho = exp((lb^2 - r^2) * 0.5)
                elseif ub < 0
                    rho = exp((ub^2 - r^2) * 0.5)
                else
                    rho = exp(-r^2 * 0.5)
                end
                if u < rho
                    return r
                end
            end
        end
    end
end


function run_train(M,net_param,train_param,time_param,dt,utarg)

N        = length(M[1,:]);
w        = zeros(N,N);
extinput = zeros(N);
u        = zeros(N);

x, r, b = net_param[1:N], net_param[N+1:2*N], net_param[2*N+1];
stim, lambda, learn_every, nloop, target_type = train_param[1:N], train_param[N+1], train_param[N+2], train_param[N+3], train_param[N+4];
stim_on, stim_off, train_time = time_param[1], time_param[2], time_param[3];

# RHS of ODEs
dtheta(x_var,I_var) = 1 - cos(x_var) + I_var.*(1 + cos(x_var));
dr(r_var) = -b*r_var;

# set up correlation matrix
P = Dict{Int64,Array{Float64,2}}();
Px = Dict{Int64,BitArray{2}}();
for ni=1:Int(N)
    ni_prenum = sum(M[ni,:].!=0);
    ni_preind = M[ni,:].!=0;
    P[ni] = (1.0/lambda)*eye(ni_prenum);
    Px[ni] = ni_preind;
end

t = 0.;
for iloop = 1:Int(nloop)
    print("Loop $(iloop)...",'\n')
    for i=1:Int(train_time/dt)
          # External stimulus
          if i > Int(stim_on/dt) && i <= Int(stim_off/dt)
                extinput[:] = stim[:];
          else
                extinput[:] = zeros(N)[:];
          end

          # Update neuron phase
          k1 = dt*dtheta(x,      u+extinput);
          k2 = dt*dtheta(x+k1/2, u+extinput);
          k3 = dt*dtheta(x+k2/2, u+extinput);
          k4 = dt*dtheta(x+k3,   u+extinput);
          xnext  = x + (k1+2*k2+2*k3+k4)/6;

          # Update filtered spikes
          l1 = dt*dr(r);
          l2 = dt*dr(r+l1/2);
          l3 = dt*dr(r+l2/2);
          l4 = dt*dr(r+l3);

          # Spike detection.
          idx1 = xnext - x .> 0.;
          idx2 = xnext - x .> mod(pi - mod(x,2*pi),2*pi); # distance to next pi.
          idx  = idx1.*idx2;
          ind  = collect(1:N)[idx[:]]';

          # Update each neuron's spike
          if !isempty(ind)
              r[idx[:]] = r[idx[:]] + b;
          end

          # Update x, r, z, t.
          x = x + (k1+2*k2+2*k3+k4)/6;
          r = r + (l1+2*l2+2*l3+l4)/6;
          u = M*r;
          t = t + dt;

          # Train w
          if i > Int(stim_off/dt)+1 && i <= Int(train_time/dt) && mod(i, learn_every/dt) == 0
              for ni=1:Int(N)
                	# update inverse correlation matrix
                	k     = P[ni]*r[Px[ni][:]];
                	vPv   = r[Px[ni][:]].'*k;
                	den   = 1.0/(1.0 + vPv[1]);
                	P[ni] = P[ni] - k*(k.'*den);

                	# update recurrent weights
                  e  = M[ni,Px[ni][:]]*r[Px[ni][:]] - utarg[i,ni];
                	dw = -e[1]*k*den;
                	M[ni,Px[ni][:]] = M[ni,Px[ni][:]] + dw';
              end
          end
    end
end

return M

end


""" Algebra of subobjects in a category.

This module defines the interface for subobjects as well as some generic
algorithm for computing subobjects using limits and colimits. Concrete instances
such as subsets (subobjects in Set or FinSet) and sub-C-sets (subobjects in
C-Set) are defined elsewhere.
"""
module Subobjects
export Subobject, ob, hom, SubOpAlgorithm, SubOpWithLimits,
  meet, ∧, join, ∨, top, ⊤, bottom, ⊥,
  implies, ⟹, subtract, \, negate, ¬, non, ~

import Base: \, ~
using AutoHashEquals
using StaticArrays: SVector

using ...GAT, ...Theories, ..Limits
import ...Theories: ob, hom, meet, ∧, join, ∨, top, ⊤, bottom, ⊥

# Theories
##########

""" Theory of lattice of subobjects in a coherent category, such as a pretopos.

The axioms are omitted since this theory is the same as the theory
[`Catlab.Theories.AlgebraicLattice`](@ref) except that the lattice elements are
dependent on another type. TODO: It should be possible to define a projection
morphism of GATs from `SubobjectLattice` to `AlgebraicLattice` that sends `Ob`
to the unit type.
"""
@signature SubobjectLattice{Ob,Sub} begin
  Ob::TYPE
  Sub(ob::X)::TYPE

  @op begin
    (∧) := meet
    (∨) := join
    (⊤) := top
    (⊥) := bottom
  end

  meet(A::Sub(X), B::Sub(X))::Sub(X) ⊣ (X::Ob)
  join(A::Sub(X), B::Sub(X))::Sub(X) ⊣ (X::Ob)
  top(X::Ob)::Sub(X)
  bottom(X::Ob)::Sub(X)
end

""" Theory of Heyting algebra of subobjects in a Heyting category, such as a
topos.
"""
@signature SubobjectHeytingAlgebra{Ob,Sub} <: SubobjectLattice{Ob,Sub} begin
  @op begin
    (⟹) := implies
    (¬) := negate
  end

  implies(A::Sub(X), B::Sub(X))::Sub(X) ⊣ (X::Ob)
  negate(A::Sub(X))::Sub(X) ⊣ (X::Ob)
end

""" Theory of bi-Heyting algebra of subobjects in a bi-Heyting topos, such as a
presheaf topos.
"""
@signature SubobjectBiHeytingAlgebra{Ob,Sub} <: SubobjectHeytingAlgebra{Ob,Sub} begin
  @op begin
    (\) := subtract
    (~) := non
  end

  subtract(A::Sub(X), B::Sub(X))::Sub(X) ⊣ (X::Ob)
  non(A::Sub(X))::Sub(X) ⊣ (X::Ob)
end

# Data types
############

""" Subobject in a category.

A subobject of an object ``X`` is a monomorphism into ``X``.
"""
@auto_hash_equals struct Subobject{Ob,Hom}
  hom::Hom
  Subobject(hom::Hom) where Hom = new{typeof(codom(hom)),Hom}(hom)
end

hom(sub::Subobject) = sub.hom
ob(sub::Subobject) = codom(hom(sub))

# Algorithms
############

""" Abstract type for algorithm to compute operation(s) on subobjects.
"""
abstract type SubOpAlgorithm end

""" Algorithm to compute subobject operations using limits and/or colimits.
"""
struct SubOpWithLimits <: SubOpAlgorithm end

""" Meet (intersection) of subobjects.
"""
function meet(A::Subobject, B::Subobject, ::SubOpWithLimits)
  meet(SVector(A,B), SubOpWithLimits())
end
function meet(As::AbstractVector{<:Subobject}, ::SubOpWithLimits)
  fs = map(hom, As)
  lim = pullback(fs)
  Subobject(compose(first(legs(lim)), first(fs))) # Arbitrarily use first leg.
end

""" Join (union) of subobjects.
"""
function join(A::Subobject, B::Subobject, ::SubOpWithLimits)
  join(SVector(A,B), SubOpWithLimits())
end
function join(As::AbstractVector{<:Subobject}, ::SubOpWithLimits)
  fs = map(hom, As)
  lim = pullback(fs)
  colim = pushout(legs(lim))
  Subobject(copair(colim, fs))
end

""" Top (full) subobject.
"""
top(X, ::SubOpWithLimits) = Subobject(id(X))

""" Bottom (empty) subobject.
"""
bottom(X::T, ::SubOpWithLimits) where T = Subobject(create(initial(T), X))

end


using ReservoirComputing
using MLJLinearModels
#model parameters
const approx_res_size = 30
const radius = 1.2
const activation = tanh
const degree = 6
const sigma = 0.1
const beta = 0.0
const alpha = 1.0
const nla_type = NLADefault()
const in_size = 3
const out_size = 3
const extended_states = false
const delta = 0.5

const train_len = 50
const predict_len = 12
data = ones(Float64, in_size, 100)
train = data[:, 1:1+train_len-1]
test = data[:, train_len:train_len+predict_len-1]

#constructor 1
esn = ESN(approx_res_size,
    train,
    degree,
    radius,
    activation = activation,
    sigma = sigma,
    alpha = alpha,
    nla_type = nla_type,
    extended_states = extended_states)

W_out = ESNtrain(esn, beta)

fit1 = ESNfitted(esn, W_out; autonomous=false)
@test size(fit1) == size(train)

fit2 = ESNfitted(esn, W_out; autonomous=true)
@test size(fit1) == size(train)


module UnitaryDecomposition

using LinearAlgebra
using Optim

include("Pauli.jl")
include("BlochRotation.jl")
include("BasicGates.jl")

export UDecompose, Pauli, BlochRotation

#This code will decompose an unitary into blocks of single qubit rotations
#and cnots between adjacent qubits

function U(n,p,nq)
Ut=1
for i=1:nq
 Ut=kron(Ut,BlochRotation(n[i,:],p[i]))
end
for j=0:nq-2
  Ut=Ut*kron(Diagonal(ones(2^j)),CNOT12,Diagonal(ones(2^(nq-j-2))))
end
return Ut
end

function Utestlist(U)
# U will have to have the form [n[,] a[] p[] nq]
# Only leave U[i] on the non fixed parameters, and fix the ones I want
  n=[U[1] U[2] U[3]; U[4] U[5] U[6]] # This depends on nq
  p=[U[7] U[8]]
  nq=2 #usually fixed, never optimize over nq
  return U(n,p,nq)
end

function UDecompose(U,N)
#U is the unitary we want to decompose
#N is the number of blocks we want
f(x)=norm(U-Ublock(N)(x))
nq=2
x0=rand(N,4*nq)
res=optimize(f,x0)
return res.minimizer, res.minimum
end

function Ublock(N)
# N is the number of layers
if N==1
return Utestlist
end
#if N==2
#Ublock2(U)=Utestlist(U[1,:])*Utestlist(U[2,:])
#return Ublock2
#end
if N>1
Ub(U)=Utestlist(U[1,:])*Ublock(N-1)(U[2:end,:])
end
end

end


function plot_circle!(plt, x, y, r; points=100, color=:black)
    rng = range(0; stop=2pi, length=points)
    plot!(plt, θ -> x + r*cos(θ), θ -> y + r*sin(θ), rng; color=color)
end

function plot_winding(winding, awg, maxturns, fill=1.0)

    coil=winding.coil

    width = (coil.od-coil.id)/2 |> ustrip
    h = ustrip(coil.len)
    plt=plot([0, h,h, 0, 0], [0, 0, width, width, 0]; aspect_ratio=:equal,legend=:none)

    d = awg.d_insul |>  unit(winding.coil.id) |> ustrip
    r = d/2
    d /= sqrt(fill)
    turns = 0
    foreach(enumerate(range(d/2;step=sqrt(3)/2*d,length=winding.winding.layers))) do (layer,y)
        x_offset = isodd(layer) ? d/2 : d
        layerturns = isodd(layer) ? winding.winding.odd_layer : winding.winding.even_layer
        foreach(range(x_offset;step=d,length=layerturns)) do x
            if turns < maxturns
                plot_circle!(plt,x,y,r)
                turns += 1
            else

            end
        end
    end
    plt
end


using SpikingNetworks
@unit
parseParameters"""
τ = 20*ms
N= 100
a= [Float64(0.05+0.7*i/N) for i=1:N]
b = 1.2
freq = 10*Hz"""
parseEquations"""
dv/dt=(-v+Iₑ(t))/τ
Iₑ(t)=a*sin(2π*freq*t)+b"""
net=init"""
N=N
v=rand(N)"""
spike"v.>1."
reset"v[i]=0."
stateMonitor"[50]"
net.dt=0.1
run!(net,1000/net.dt)

using MatlabPlot
mplot(net.vRecord)
t,neuron=rasterPlot(net)
mplot(t,neuron,".k")


function snow17(forcings::DataFrame, elev::Float64, par::AbstractArray; prcpCol::Symbol=:precip, tavgCol::Symbol=:tavg, dateCol::Symbol=:datetime, initstate::AbstractArray=[0.,0.,0.,0.])
    # Define constants
    dtt = 24  # time interval of temperature data
    dtp = 24  # time interval of prcipitation data

    # SET PARAMETERS
    SCF    = par[1]
    PXTEMP = par[2]
    MFMAX  = par[3]
    MFMIN  = par[4]
    UADJ   = par[5]
    MBASE  = par[6]
    TIPM   = par[7]
    PLWHC  = par[8]
    NMF    = par[9]
    DAYGM  = par[10]

    dts = forcings[!,dateCol]
    tavg = forcings[!,tavgCol]
    prcp = forcings[!,prcpCol]
    len = length(tavg)

    doy = map(dayofyear, dts)

    meltNrain = Array{Float64}(undef,len)

    # Set initial states
    W_i, ATI, W_q, Deficit = initstate

    # LOOP THROUGH EACH PERIOD
    for i in 1:len

        # Set current temperature and precipitation
        Ta = tavg[i]  # Air temperature at this time step [deg C]
        Pr = prcp[i]  # Precipitation at this time step [mm]

        # FORM OF PRECIPITATION
        if Ta <= PXTEMP
            # Air temperature is cold enough for snow to occur
            SNOW = Pr
            RAIN = 0.0
        else
            # Air temperature is warm enough for rain
            SNOW = 0.0
            RAIN =  Pr
        end

        # ACCUMULATION OF THE SNOW COVER
        Pn = SNOW * SCF  # Water equivalent of new snowfall [mm]
        W_i = W_i + Pn  # Water equivalent of the ice portion of the snow cover [mm]
        E = 0  # Excess liquid water in the snow cover

        # ENERGY EXCHANGE AT SNOW/AIR SURFACE DURING NON-MELT PERIODS

        # Seasonal variation in the non-rain melt factor (Assume a year has 365 days)
        N_Mar21 = doy[i] - 80

        Sv = (0.5 * sin((N_Mar21 * 2 * pi)/365)) + 0.5  # Seasonal variation
        Av = 1  # Seasonal variation adjustment, Av<-1.0 when lat < 54N
        Mf = dtt/6 * ((Sv * Av * (MFMAX - MFMIN)) + MFMIN)  # Seasonally varying non-rain melt factor

        # New snow temperature and heat deficit from new snow
        if (Ta < 0)
            T_snow_new = Ta
        else
            T_snow_new = 0
        end

        # Change in the heat deficit due to new snowfall [mm], 80 cal/g: latent heat of fusion, 0.5 cal/g/C:
        # specific heat of ice
        delta_HD_snow = -(T_snow_new * Pn)/(80/0.5)

        # Heat Exchange due to a temperature gradient change in heat deficit due to a temperature gradient
        # [mm]
        delta_HD_T = NMF * dtp/6 * Mf/MFMAX * (ATI - T_snow_new)

        # Update ATI[Antecedent Temperature Index]
        if (Pn > 1.5 * dtp)
            ATI = T_snow_new  #Antecedent temperature index
        else
            TIPM_dtt = 1 - ((1 - TIPM)^(dtt/6))
            ATI = ATI + TIPM_dtt * (Ta - ATI)
        end

        ATI = min(ATI, 0)

        # SNOW MELT
        T_rain = max(Ta, 0)  # Temperature of rain (deg C), Ta or 0C, whichever greater

        if (RAIN > 0.25 * dtp)
            # Rain-on-Snow Melt
            stefan = 6.12 * (10^(-10))  # Stefan-Boltzman constant (mm/K/hr)
            e_sat = 2.7489 * (10^8) * exp((-4278.63/(Ta + 242.792)))  # Saturated vapor pressure at Ta (mb)
            P_atm = 33.86 * (29.9 - (0.335 * (elev/100)) + (0.00022 * ((elev/100)^2.4)))  # Atmospheric pressure (mb) where elevation is in HUNDREDS of meters (this is incorrectly stated in the manual)
            term1 = stefan * dtp * (((Ta + 273)^4) - (273^4))
            term2 = 0.0125 * RAIN * T_rain
            term3 = 8.5 * UADJ * (dtp/6) * ((0.9 * e_sat - 6.11) + (0.00057 * P_atm * Ta))
            Melt = term1 + term2 + term3
            Melt = max(Melt, 0)

        elseif ((RAIN <= 0.25 * dtp) && (Ta > MBASE))
            # Non-Rain Melt
            Melt = (Mf * (Ta - MBASE) * (dtp/dtt)) + (0.0125 * RAIN * T_rain)
            Melt = max(Melt, 0)

        else
            Melt = 0
        end

        # Ripeness of the snow cover W_i : water equivalent of the ice portion of the snow cover W_q :
        # liquide water held by the snow W_qx: liquid water storage capacity Qw : Amount of available water
        # due to melt and rain

        Deficit = max(Deficit + delta_HD_snow + delta_HD_T, 0)  # Deficit <- heat deficit [mm]

        if (Deficit > (0.33 * W_i))
            # limits of heat deficit
            Deficit = 0.33 * W_i
        end

        if (Melt < W_i)
            W_i = W_i - Melt
            Qw = Melt + RAIN
            W_qx = PLWHC * W_i

            if ((Qw + W_q) > (Deficit + Deficit * PLWHC + W_qx))
                # THEN the snow is RIPE

                E = Qw + W_q - W_qx - Deficit - (Deficit * PLWHC)  # Excess liquid water [mm]
                W_i = W_i + Deficit  # W_i increases because water refreezes as heat deficit is decreased
                W_q = W_qx + PLWHC * Deficit  # fills liquid water capacity
                Deficit = 0

            elseif ((Qw + W_q) >= Deficit)

                # & [[Qw + W_q] <= [[Deficit*[1+PLWHC]] + W_qx]] THEN the snow is NOT yet ripe, but ice is being
                # melted

                E = 0
                W_i = W_i + Deficit  # W_i increases because water refreezes as heat deficit is decreased
                W_q = W_q + Qw - Deficit
                Deficit = 0

            elseif ((Qw + W_q) < Deficit)
                # elseif [[Qw + W_q] < Deficit]

                # THEN the snow is NOT yet ripe
                E = 0
                W_i = W_i + Qw + W_q  # W_i increases because water refreezes as heat deficit is decreased
                Deficit = Deficit - Qw - W_q
            end

        else
            Melt = W_i + W_q  # Melt >= W_i
            W_i = 0
            W_q = 0
            Qw = Melt + RAIN
            E = Qw
            # SWE = 0

        end

        if (Deficit == 0)
            ATI = 0
        end

        # Constant daily amount of melt which takes place at the snow-soil interface whenever there is a
        # snow cover
        if (W_i > DAYGM)

            gmwlos = (DAYGM/W_i) * W_q
            gmslos = DAYGM
            gmro = gmwlos + gmslos
            W_i = W_i - gmslos
            W_q = W_q - gmwlos

            E = E + gmro
            SWE = W_i + W_q

        else
          gmro = W_i + W_q
          W_i = 0
          W_q = 0
          E = E + gmro
          SWE = 0
        end

        meltNrain[i] = E
    end

    return meltNrain
end


#---------------------------------------------------------------
function temperatureVI(posterior, σ2, T; maxiter = 1, S = 10)
#---------------------------------------------------------------

    D = round(Int, length(posterior)/2)

    gT = observedunitgalaxyvector(T)

    function unpack(param)

        @assert(length(param) == 2D+2)

        local a     = param[1:D]
        local b     = param[1+D:2*D]
        local t     = param[end-1]
        local scale = exp(param[end])

        return a, b, t, scale

    end

    function logp(a, b, t, scale)

        local x = scale * gT

        logpdf(MvNormal(a*t + b, Diagonal(σ2)), x) + logpdf(posterior,[a;b])

    end

    objective(param) = logp(unpack(param)...)

    optimoptions = Optim.Options(show_trace = false, iterations = maxiter, g_tol=1e-6)

    VI(objective, [5*randn(2D+2) for i=1:100]; maxiter = maxiter, S = S, optimiser = LBFGS(), optimoptions = optimoptions)

end

#---------------------------------------------------------------
function temperatureVI2(posterior, σ2, T; maxiter = 1, S = 10)
#---------------------------------------------------------------

    D = round(Int, length(posterior)/2)

    gT = observedunitgalaxyvector(T)

    function unpack(param)

        @assert(length(param) == 2D+1)

        local a     = param[1:D]
        local b     = param[1+D:2*D]
        local scale = exp(param[end])

        return a, b, scale

    end

    function logp(a, b, scale)

        local x = scale * gT

        logpdf(MvNormal(a*(dot(a,x-b)) + b, Diagonal(σ2)), x) + logpdf(posterior,[a;b])

    end

    objective(param) = logp(unpack(param)...)

    optimoptions = Optim.Options(show_trace = false, iterations = maxiter, g_tol=1e-6)

    VI(objective, [5*randn(2D+1) for i=1:100]; maxiter = maxiter, S = S, optimiser = LBFGS(), optimoptions = optimoptions)

end

#---------------------------------------------------------------
function temperatureVI3(posterior, σ2, T; S = S, maxiter = maxiter)
#---------------------------------------------------------------

    # dimension of problem is D

    D = round(Int, length(posterior)/2)

    # create galaxy vector

    g = observedunitgalaxyvector(T)

    # marginals, conditionals

    linemeansigma = marginaline(posterior, σ2)

    function logp(p)

        local s, t = exp(p[1]), p[2]

        local mu, Sigma = linemeansigma(t)

        # local logprior = logpdf(Normal(0.0, 1000.0), s) + logpdf(Normal(0.0, 1000.0), p[2])

        logpdf(MvNormal(mu, Sigma), s*g)# + logprior

    end

    initoptions  = Optim.Options(iterations = 5, g_tol = 1e-6)

    optimoptions = Optim.Options(show_trace = false, show_every = 1, iterations = maxiter, g_tol = 1e-6)

    initsol      = estimatetemperature4(posterior, σ2, T)[1]

    VI(logp, push!([randn(2) for i=1:5], initsol); S = S, optimiser = LBFGS(), optimoptions = optimoptions, initoptions = initoptions)

end


const domainXY = range(-maxR, stop = maxR, length = 333)/w₀
const domainR  = range(0., stop = maxR, length = 800)/w₀
const time = range(- n * τ, stop = n * τ, length = 1200)/T₀

# useful function definitions

function w_inst(x, y, z, t, type)
    @unpack ε₀, c = fundamental_constants(type)
    return (ε₀/2)*(E([x,y,z], t, type)⋅E([x,y,z], t, type) +
               c^2*B([x,y,z], t, type)⋅B([x,y,z], t, type))
end

# dot takes the conjugate of the first vector automatically!
function w_cycle(x, y, z, type)
    @unpack ε₀, c = fundamental_constants(type)
    return (ε₀/4)*real(E([x, y, z], type)⋅E([x, y, z], type) +
                 (c^2)*B([x, y, z], type)⋅B([x, y, z], type))
end

function j_inst(x, y, z, t, type)
    ε₀ = fundamental_constants(type, :ε₀)
    return ε₀*([x,y,z]×(E([x,y,z], t, type)×B([x,y,z], t, type)))
end

function jz_cycle(x, y, z, type)
    ε₀ = fundamental_constants(type, :ε₀)

    return (ε₀/4)*real((x*(
                            (E([x, y, z], type)×conj(B([x, y, z], type)))[2] +
                            (conj(E([x, y, z], type))×B([x, y, z], type))[2]
                            )
                            -
                        y*(
                            (E([x, y, z], type)×conj(B([x, y, z], type)))[1] +
                            (conj(E([x, y, z], type))×B([x, y, z], type))[1]
                            ))
                            )
end

function Mz(x, y, z, t, type) # normed to the q*E₀*w₀
    q = type.constants.q
    E₀ = type.derived.E₀
    w₀ = type.w₀
    sign(q)*(x*E([x, y, z], t, type)[2] - y*E([x, y, z], t, type)[1])/(E₀*w₀)
end

E_ρAvg(ρ, z, t, type) = quadgk(ϕ -> E([ρ*cos(ϕ), ρ*sin(ϕ), z], t, type)/(2*π), 0, 2*π, rtol = 1e-8, atol = 1e-10)[1]

wi_ρAvg(ρ, z, t, type) = quadgk(ϕ -> w_inst(ρ*cos(ϕ), ρ*sin(ϕ), z, t, type)/(2*π), 0, 2*π, rtol = 1e-8)[1]
wc_ρAvg(ρ, z, type) = quadgk(ϕ -> w_cycle(ρ*cos(ϕ), ρ*sin(ϕ), z, type)/(2*π), 0, 2*π, rtol = 1e-8)[1]

ji_ρAvg(ρ, z, t, type) = quadgk(ϕ -> j_inst(ρ*cos(ϕ), ρ*sin(ϕ), z, t, type)/(2*π), 0, 2*π, rtol = 1e-8, atol = 1e-10)[1]
jc_ρAvg(ρ, z, type) = quadgk(ϕ -> jz_cycle(ρ*cos(ϕ), ρ*sin(ϕ), z, type)/(2*π), 0, 2*π, rtol = 1e-8, atol = 1e-10)[1]

# end of definitions

common_settings = (fontfamily = "computer modern", dpi = 360, label = false)
plot_settings   = (color_palette = :Set1_4, seriescolor = 4, linewidth = 2.5)

for type in lasers
    pol = polarization(type)

    folder_setup("$(pol)_p$(type.p)_m$(type.m)") # change folder for each simulation
    folder_setup("fields") # separate fields from particles

    # w₀ = type.w₀
    p = type.p
    m = type.m
    ξy = type.polarization.ξy
    # T₀ = type.derived.T₀
    roots = rootsLUT[(p = p, m = abs(m))]

    file_ext = "$(pol)_p$(p)_m$(m).$(fmt)"

    # 1. Heatmaps

    w_00 = heatmap(domainXY, domainXY, (x,y) -> w_inst(w₀*x, w₀*y, 0, 0, type);
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :linear_kryw_5_100_c67_n256,
                clims = x -> (0, maximum(x)),
                aspect_ratio = 1, common_settings...)
    add_info!(w_00, type, true)
    savefig(w_00, "xy00Energy_$file_ext")
    println("Energy density for time = 0, z = 0, in the xy-plane")

    w_0T₀ = heatmap(domainXY, domainXY, (x,y) -> w_cycle(w₀*x, w₀*y, 0, type);
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :linear_kryw_5_100_c67_n256,
                clims = x -> (0, maximum(x)),
                aspect_ratio = 1, common_settings...)
    add_info!(w_0T₀, type, true)
    savefig(w_0T₀, "xy0T₀Energy_$file_ext")
    println("Energy density cycle averaged, z = 0, in the xy-plane")

    Ex_00 = heatmap(domainXY, domainXY, (x,y) -> E([w₀*x, w₀*y, 0], 0, type)[1];
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :vik,
                clims = x -> maximum(abs.(extrema(x))).*(-1,1),
                aspect_ratio = 1, common_settings...)
    add_info!(Ex_00, type, true)
    savefig(Ex_00, "xy00Ex_$file_ext")
    println("Electric field on the x-axis for time = 0, z = 0, in the xy-plane")

    Ez_00 = heatmap(domainXY, domainXY, (x,y) -> E([w₀*x, w₀*y, 0], 0, type)[3];
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :vik,
                clims = x -> maximum(abs.(extrema(x))).*(-1,1),
                aspect_ratio = 1, common_settings...)
    add_info!(Ez_00, type, true)
    savefig(Ez_00, "xy00Ez_$file_ext")
    println("Electric field on the z-axis for time = 0, z = 0, in the xy-plane")

    jz_00 = heatmap(domainXY, domainXY, (x,y) -> j_inst(w₀*x, w₀*y, 0, 0, type)[1];
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :vik,
                clims = x -> maximum(abs.(extrema(x))).*(-1,1),
                aspect_ratio = 1, common_settings...)
    add_info!(jz_00, type, true)
    savefig(jz_00, "xy00jz_$file_ext")
    println("z-axis field angular momentum density for time = 0, z = 0, in the xy-plane")

    jz_0T₀ = heatmap(domainXY, domainXY, (x,y) -> jz_cycle(w₀*x, w₀*y, 0, type);
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :vik,
                clims = x -> maximum(abs.(extrema(x))).*(-1,1),
                aspect_ratio = 1, common_settings...)
    add_info!(jz_0T₀, type, true)
    savefig(jz_0T₀, "xy0T₀jz_$file_ext")
    println("z-axis field angular momentum density cycle averaged, z = 0, in the xy-plane")

    jz_w = heatmap(domainXY, domainXY, (x,y) -> ω*jz_cycle(w₀*x, w₀*y, 0, type)/w_cycle(w₀*x, w₀*y, 0, type);
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :lajolla,
                aspect_ratio = 1, common_settings...)
    add_info!(jz_w, type, true)
    savefig(jz_w, "xy0T₀jz_w_$file_ext")
    println("z-axis field angular momentum density divided by energy density, cycle averaged, z = 0, in the xy-plane")

    Lz′ = heatmap(domainXY, domainXY, (x,y) -> Mz(w₀*x, w₀*y, 0, 0, type);
                xlabel = L"x/w_0", ylabel = L"y/w_0",
                seriescolor = :vik,
                clims = x -> maximum(abs.(extrema(x))).*(-1,1),
                aspect_ratio = 1, common_settings...)
    add_info!(Lz′, type, true)
    savefig(Lz′, "xy00Lz_$file_ext")
    println("Instantaneous reduced torque for time t = 0, z = 0, in the xy-plane")

    @time begin
    Lz′_avg = heatmap(domainXY, domainXY, (x,y) -> quadgk(t -> Mz(w₀*x, w₀*y, 0, t, type), -Inf, Inf, atol = 1e-2)[1];
    xlabel = L"x/w_0", ylabel = L"y/w_0",
    seriescolor = :vik,
    clims = x -> maximum(abs.(extrema(x))).*(-1,1),
    aspect_ratio = 1, common_settings...)
    add_info!(Lz′_avg, type, true)
    savefig(Lz′_avg, "xy0∞Lz_$file_ext")
    println("Pulse averaged reduced torque for, z = 0, in the xy-plane")
    end
    # 2. Azimuthally averaged radial plots

    jz_ρAvg = plot(domainR, r -> jc_ρAvg(w₀*r, 0, type);
                label = false)
    jz_ρAvg = add_rect!(jz_ρAvg, domainR, r -> jc_ρAvg(w₀*r, 0, type), roots;
                xlabel = L"\rho/w_0", ylabel = L"\overline{j_z^{xy}}(\rho, t = 0)\quad\mathrm{[a.u.]}",
                common_settings..., plot_settings...)
    add_info!(jz_ρAvg, type)
    savefig(jz_ρAvg, "jz_ρAvg_$file_ext")
    println("Azimuthally averaged angular momentum on the z-axis for time = 0, z = 0, in the xy")

    w_rAvg = plot(domainR, r -> wc_ρAvg(w₀*r, 0, type);
                label = false)
    w_rAvg = add_rect!(w_rAvg, domainR, r -> wc_ρAvg(w₀*r, 0, type), roots;
        xlabel = L"\rho/w_0", ylabel = L"\mathrm{Intensity}(\rho)\quad\mathrm{[a.u.]}",
        common_settings..., plot_settings...)
    add_info!(w_rAvg, type)
    savefig(w_rAvg, "w_ρAvg_$file_ext")
    println("Azimuthally averaged energy density for time = 0, z = 0, in the xy")

    # 3. Time axis plots.

    Ex_t = plot(time, t -> E([w₀/√2/2, w₀/√2/2, 0], t*T₀, type)[1];
         xlabel = L"t/T_0", ylabel = L"E_x({w_0}/{\sqrt{8}}, {w_0}/{\sqrt{8}}, 0, t)\quad\mathrm{[a.u.]}",
         common_settings..., plot_settings...)
    add_info!(Ex_t, type)
    savefig(Ex_t,"Ex_t_$file_ext")
    println("Electric field on the x-axis in time for x = w₀/√2/2, y = w₀/√2/2, z = 0")

    # 3.1 Animations

    animLz = Animation()
    for (i,t) in enumerate(range(-n*T₀, n*T₀, length = 299)) # +1 = 300 / 30fps = 10s
        Lz′_anim = heatmap(domainXY, domainXY, (x,y) -> Mz(w₀*x, w₀*y, 0,  t, type);
                    xlabel = L"x/w_0", ylabel = L"y/w_0",
                    seriescolor = :vik,
                    clims = x -> (-0.5,0.5), # maximum(abs.(extrema(x))).*(-1,1),
                    cbar = :none,
                    aspect_ratio = 1,
                    fontfamily = "computer modern",
                    dpi = 120,
                    label = false)
        add_info!(Lz′_anim, type, true)
        frame(animLz)
        pritnln("Animatin ... Frame $i/300.")
    end
    gif(animLz, "Lz′.gif", fps = 30)
    println("Instantaneous reduced torque for time t = 0, z = 0, in the xy-plane")

    cd("../..") # return to output_* base folder
    break
end


module SphericalFunctions

using FastTransforms, LinearAlgebra, ProgressMeter
import Base.Threads: @threads, nthreads

export complex_powers, complex_powers!
export theta_phi, phi_theta
export WignerMatrixCalculator, H!, d!, D!, Y!
export ALFRecursionCoefficients, ALFrecurse!, ALFcompute!
export fejer1, fejer2, clenshaw_curtis
export map2salm, map2salm!, plan_map2salm

const MachineFloat = Union{Float16, Float32, Float64}

include("utils.jl")

include("complex_powers.jl")

include("indexing.jl")

include("wigner_matrices/evaluate.jl")

include("associated_legendre/calculator.jl")
using .AssociatedLegendreFunction: ALFRecursionCoefficients, ALFrecurse!, ALFcompute!

include("weights.jl")

include("map2salm.jl")

end # module


lesser(x::TimeOrderedGreenFunction) = x.L

function lesser(x::TimeOrderedConvolution{TimeOrderedGreenFunction, TimeOrderedGreenFunction})
    a, b = x.A, x.B
    a.R * b.L + a.L * b.A
end

function lesser(x::TimeOrderedConvolution{TimeOrderedGreenFunction, TimeOrderedConvolution{TimeOrderedGreenFunction,TimeOrderedGreenFunction}})
    a, b, c = x.A, x.B.A, x.B.B
    lesser3(a, b, c)
end

function lesser(x::TimeOrderedConvolution{TimeOrderedConvolution{TimeOrderedGreenFunction,TimeOrderedGreenFunction}, TimeOrderedGreenFunction})
    a, b, c = x.A.A, x.A.B, x.B
    lesser3(a, b, c)
end

function lesser3(a, b, c)
    x = a.L * b.A * c.A
    x - x' + a.R * b.L * c.A
end

greater(x::TimeOrderedGreenFunction) = x.G

function greater(x::TimeOrderedConvolution{TimeOrderedGreenFunction, TimeOrderedGreenFunction})
    a, b = x.A, x.B
    a.R * b.G + a.G * b.A
end

function greater(x::TimeOrderedConvolution{TimeOrderedGreenFunction, TimeOrderedConvolution{TimeOrderedGreenFunction,TimeOrderedGreenFunction}})
    a, b, c = x.A, x.B.A, x.B.B
    greater3(a, b, c)
end

function greater(x::TimeOrderedConvolution{TimeOrderedConvolution{TimeOrderedGreenFunction,TimeOrderedGreenFunction}, TimeOrderedGreenFunction})
    a, b, c = x.A.A, x.A.B, x.B
    greater3(a, b, c)
end

function greater3(a, b, c)
    x = a.G * b.A * c.A
    x - x' + a.R * b.G * c.A
end


# Uses the same optimiser as the base instance. Only works if that solver
# understands lazy constraints and SOCP.
mutable struct OSSBExactSmart <: OSSBOptimisationAlgorithm
  silent_solver::Bool
  instance

  # Separation subproblem (built once, when need be, hence mutability of this struct).
  sub_m
  sub_vars
  sub_r
  sub_z
end

OSSBExactSmart() = OSSBExactSmart(true, nothing, nothing, nothing, nothing, nothing)
OSSBExactSmart(silent_solver::Bool) = OSSBExactSmart(silent_solver, nothing, nothing, nothing, nothing, nothing)

function _ossb_exact_cg_separation(instance::CombinatorialInstance{T},
                                   algo::OSSBExactSmart,
                                   t::Dict{T, Float64},
                                   θ::Dict{T, Float64},
                                   r_max::Float64) where T
  if algo.instance === nothing
    algo.instance = instance
  elseif algo.instance != instance
    error("The OSSBExactSmart has already been used with another combinatorial instance.")
  end

  # If the separation subproblem has not yet been created, do it right now.
  if algo.sub_m === nothing
    # For a given t, solve the following (quadratic) separation problem:
    #    max_{x ∈ X} ∑_{i \in I} x_i / t_i - Δ_x^2
    # Return a solution x such that the objective function is positive (i.e. a
    # constraint that is violated), or nothing.
    m, _, vars = get_lp_formulation(copy(instance), t) # t is ignored, as it's only used
    # in the objective. It has all required arms, so that's enough.

    # Make a conic formulation for z == Δ_x^2 where Δ_x = r_max - θ^T x:
    #    z >= (θ^T x)^2    ⟺    1 >= (θ^T x)^2 / z
    # This is equivalent to:
    #    Δx == θ^T x
    #    (z + 1, z - 1, 2 * Δx) ∈ SOC
    @variable(m, Δx, lower_bound=0.0)
    @variable(m, z, lower_bound=0.0)
    @constraint(m, Δx == r_max - sum(θ[i] * vars[i] for i in keys(t)))
    @constraint(m, [1 + z, 1 - z, 2 * Δx] in SecondOrderCone())

    # Store the model in the OSSB object.
    algo.sub_m = m
    algo.sub_vars = vars
    algo.sub_r = Δx
    algo.sub_z = z
  end

  I = _ossb_confusing_arms(instance, θ)
  # I = all_arm_indices(instance)

  # Create the objective function.
  obj_linear = sum(algo.sub_vars[arm] / t[arm] for arm in keys(t))# if arm in I)
  obj_Δx² = algo.sub_z
  # obj_Δx² = r_max^2 - 2 * r_max * algo.sub_r + algo.sub_z # Δx² = (θ^T x^\star - θ^T x)^2
  # #                                                                \_ r_max _/   \ r /, with z ≥ r²
  @objective(algo.sub_m, Max, obj_linear - obj_Δx²)

  # println(algo.sub_m)
  # @show r_max

  optimize!(algo.sub_m)

  if termination_status(algo.sub_m) != MOI.OPTIMAL
    @show termination_status(algo.sub_m)
    println(algo.sub_m)
    error(termination_status(algo.sub_m))
  end

  # # println(algo.sub_m)
  # @show value(algo.sub_r)
  # @show value(algo.sub_z)
  # @show objective_value(algo.sub_m)
  # @show result_count(algo.sub_m)

  status = termination_status(algo.sub_m)
  return if status == MOI.OPTIMAL && objective_value(algo.sub_m) > 1.0e-5 # TODO: \Delta_min?
    Vector{T}[T[arm for arm in keys(t) if value(algo.sub_vars[arm], result=i) > 0.5] for i in 1:result_count(algo.sub_m)]
    # T[arm for arm in keys(t) if value(algo.sub_vars[arm]) > 0.5]
  else
    Vector{T}[]
  end
end

function optimise_graves_lai(instance::CombinatorialInstance{T},
                             algo::OSSBExactSmart,
                             θ::Dict{T, Float64}; with_trace::Bool=true) where T
  # Must be able to get a LP formulation.
  if ! has_lp_formulation(instance)
    # TODO: with the inexact algorithm, the instance's algorithm will no more be forced to be LP-based! But still, the convex decomposition step requires an LP formulation.
    error("The exact formulation for OSSB relies on a LP formulation, " *
          "which the solver associated to this instance cannot provide.")
  end

  # This function deals with copying the instance as many times as necessary.
  # Called functions should not deal with these details.
  # TODO: rework the interface so that get_lp_formulation always returns a *new* formulation? Currently, these functions always return the same model...
  t0 = time_ns()

  # Precompute useful results.
  d = dimension(instance)
  x_max = solve_linear(copy(instance), θ)
  r_max = sum(θ[arm] for arm in x_max)

  arm_to_idx = Dict(arm => idx for (idx, arm) in enumerate(all_arm_indices(instance)))
  idx_to_arm = Dict(idx => arm for (idx, arm) in enumerate(all_arm_indices(instance)))

  I = _ossb_confusing_arms(instance, θ)

  # Reduced formulation:
  #   min   v (θ^T x^\star) - θ^T t
  #   s.t.  A t ≤ v b
  #         ∑ x_i / t_i ≤ Δ_x^2     ∀ x ∈ X    (constraint generation)
  # v could be removed if the polytope is described as A t = b, but that would require fiddling
  # with the formulation quite a lot if that is not the case.

  instance = copy(instance)
  m, _, vars = get_lp_formulation(instance, θ) # A t ≤ b
  @variable(m, v >= 0)
  @variable(m, 0 <= t[arm in keys(θ)] <= 100) # TODO: param for this upper bound, if it is really needed.
  @variable(m, 0 <= inv_t[arm in keys(θ)] <= 100) # TODO: param for this upper bound, if it is really needed.

  # From "A t ≤ b" to "A t - v b ≤ 0".
  # Multiply the right-hand side by v, for all constraints.
  for (F, S) in JuMP.list_of_constraint_types(m)
    # Remove integrality constraints. (In this case, F is a single variable.)
    # This could be replaced by relax_integrality() of newer JuMP versions, but it would be slower
    # (relax_integrality is reversible, which is not needed here).
    if S in [MOI.ZeroOne, S == MOI.Integer]
      for c in all_constraints(m, F, S)
        JuMP.delete(m, c)
      end
    end
    # Ignore the case where this is a simple bounds.
    if F == JuMP.VariableRef || F == MOI.SingleVariable
      continue
    end

    # If there is a RHS (JuMP normalises everything so that the RHS has only
    # constants), multiply by v. Due to normalisation, this term goes to the
    # LHS, and must see its sign reversed.
    for c in all_constraints(m, F, S)
      if ! iszero(normalized_rhs(c))
        coeff = normalized_rhs(c)
        set_normalized_rhs(c, 0.0)
        set_normalized_coefficient(c, v, -coeff)
      end
    end
  end

  # Define the inv_t variable (only used for the added constraints).
  for arm in keys(θ) # I
    @constraint(m, [vars[arm], inv_t[arm], sqrt(2)] in RotatedSecondOrderCone())
  end

  # Set the objective function.
  obj_best = v * r_max
  obj_actual = sum(θ[arm] * vars[arm] for arm in keys(θ))
  @objective(m, Min, obj_best - obj_actual)
  @constraint(m, obj_best - obj_actual >= 0) # Avoid numerical problems with some polytopes.

  if algo.silent_solver
    set_silent(m)
  else
    unset_silent(m)
  end

  # And go for constraint generation!
  prev_sols = Set{Vector{T}}() # Safety net. Should not be triggered (otherwise, the test on the objective value
  # in _ossb_exact_cg_separation may need to be loosened).
  println("=> Starting constraint generation")
  while true
    optimize!(m)

    if termination_status(m) != MOI.OPTIMAL
      @show termination_status(m)
      println(m)
      error(termination_status(m))
    end

    # Use the separation oracle. Only care for values in I, and not all arms.
    # t_val = Dict{T, Float64}(arm => value(vars[arm]) for arm in I)
    t_val = Dict{T, Float64}(arm => value(vars[arm]) for arm in keys(θ))
    x_vecs = _ossb_exact_cg_separation(instance, algo, t_val, θ, r_max)

    if length(x_vecs) == 0 # No more constraint to add!
      break
    end

    added_constraint = false
    for x_vec in x_vecs
      if x_vec in prev_sols # || all(!(arm in I) for arm in x_vec)
        continue
      end
      push!(prev_sols, x_vec)

      # Found a violated constraint!
      Δx² = (r_max - sum(θ[arm] for arm in x_vec))^2
      if Δx² >= 1.0e-5 # TODO: rather use \Delta_min?
        lhs = sum(inv_t[arm] for arm in x_vec if arm in I)
        # lhs = sum(inv_t[arm] for arm in x_vec)
        c = @constraint(m, lhs <= Δx²)
        added_constraint = true
      end
    end

    if ! added_constraint
      println(":: No constraint to add")
      break
    end

    println(":: Did one iteration of constraint generation")
  end
  println("=> Done with constraint generation")

  # Retrieve the solution and normalise with respect to v.
  # value. doesn't broadcast on dictionaries.
  v_val = value(v)
  t_val = Dict{T, Float64}(arm => value(vars[arm]) / v_val for arm in keys(θ))

  # If the solution t_val is zero, there is not much to do. This should never happen, unless there is a serious
  # bug in the rest of the function.
  if iszero(collect(values(t_val)))
    println("Uh ho...")
    return solve_linear(instance, θ) # Perform pure exploitation.
  end

  # Return the solution that has been found.
  res = GravesLaiResults(instance, t_val, objective_value(m))
  if with_trace
    return res, GravesLaiDetails((time_ns() - t0) / 1_000_000, objective_value(m))
  else
    return res
  end
end


using Pyramids
using Base.Test, Images, Colors

function end_to_end(T; convert_to_arr=true)
    test_im = rand(128, 128)

    if !convert_to_arr
        test_im = convert(Image{Gray}, test_im)
    end

    if typeof(T) <: ComplexSteerablePyramid
        scale = 0.5^(1/4);
        pyramid = ImagePyramid(test_im, T, scale=scale, num_orientations=8, max_levels=23, min_size=15, twidth=1.0)
    else
        pyramid = ImagePyramid(test_im, T, max_levels=23, min_size=15)
    end

    test_im_recon = toimage(pyramid)

    return all((test_im_recon .- test_im) .< 0.0002)
end

println("Running end-to-end image comparison test for Complex Steerable Pyramid.")
@test end_to_end(ComplexSteerablePyramid())
println("\twithout array conversion")
@test end_to_end(ComplexSteerablePyramid(), convert_to_arr=false)

println("Running end-to-end image comparison test for Gaussian Pyramid.")
@test end_to_end(GaussianPyramid())
println("\twithout array conversion")
@test end_to_end(GaussianPyramid(), convert_to_arr=false)

println("Running end-to-end image comparison test for Laplacian Pyramid.")
@test end_to_end(LaplacianPyramid())

# TODO:
#    Test pyramid copying
#       Test that pyramid levels don't unexpectedly change
#    Test subband editing
#    Test another scale factor

println("Tests passing.")


using DifferentialEquations
using Printf
using Plots
using LaTeXStrings
gr()
# define the set of odes
function stellar_structure!(du, u, params, m)
    a, b, n, s, nu, lambda, A = params
    p, x, t, l = u
    du[1] = - (m / x^4)
    du[2] = (t^b / (x^2 * p^a))
    du[3] = - (p^(a * n) * l) / (x^4 * t^(3 + s + b * n))
    du[4] = A * p^(a * lambda) * t^(nu - (b * lambda))
end

# We define the condition to find the event where p becomes less than some tolerace set by user.
condition(u, m, integrator) =  u[1] - 1e-4
affect!(integrator) = terminate!(integrator)
cb = ContinuousCallback(condition, affect!)

# Initial conditions, at m = 0, as per eq 1.2
p0 = 1.0
x0 = 1e-4
t0 = 1.0
l0 = 0.0

u0 = [p0, x0, t0, l0]

# parameters
a = 1.0
b = 1.0
n = 0.0
s = 0.0
nu = 4.0
lambda = 1.0
A = 0.535

params = [a, b, n, s, nu, lambda, A]
mspan = (0, 20.0)
problem1 = ODEProblem(stellar_structure!, u0, mspan, params)
solution1 = solve(problem1, alg_hints=[:stiff], callback=cb)
println(solution1.t[end], "\t", solution1.u[end][1])
plot(solution1, vars=(0, 1), lw = 2, xlabel = L"m", label = L"p")
plot!(solution1, vars=(0, 2), lw = 2, xlabel = L"m", label = L"x")
plot!(solution1, vars=(0, 3), lw = 2, xlabel = L"m", label = L"t")
plot!(solution1, vars=(0, 4), lw = 2, xlabel = L"m", label = L"l")
plot!(title = L"A=%$A", titlefontsize = 10, xlims = [0, 12], ylims = [0, 5])
savefig("p_vs_m_A_$A.pdf")


using PanelMatrices
using BenchmarkTools
using StaticArrays
using StaticNumbers
using LinearAlgebra
using Statistics

# Make sure we're benchmarking BLAS single-threaded
LinearAlgebra.BLAS.set_num_threads(1)

# In case it affects benchmarking
set_zero_subnormals(true)

const y = reshape(collect(1:100), 10, 10)
const x = PanelMatrix(y)
const v = @SMatrix [1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]

println("Benchmarking using Julia", VERSION)

let
    println("Setting each individual element")
    function f(x, s)
        for i ∈ eachindex(x)
            @inbounds x[i] = s
        end
    end
    @btime $f($x, 0)
end

let
    println("Setting each panel")
    function f(x, v)
        for i ∈ 1:3, j ∈ 1:3
            @inbounds PanelMatrices.set_full_panel!(x, v, i, j)
        end
    end
    @btime $f($x, $v)
end

for T in (Float32, Float64), M = 7:5:32, N = 7:5:32, K = 7:5:32
    if M*N*K <= 25*25*25
        println("\n$T $M x $N x $K mul!")

        A0 = reshape(collect(T(1):T(M*K)), M, K)
        B0 = reshape(collect(T(1):T(K*N)), K, N)
        C0 = A0*B0

        A = PanelMatrix(A0)
        B = PanelMatrix(B0)
        C = PanelMatrix{T}(undef, (M,N))

        mul!(C, A, B)
        @assert C ≈ C0

        tb = @benchmark mul!($C, $A, $B) samples=10 evals=1000
        println(2*M*N*K/minimum(tb.times), " Gflops")

        println("Julia arrays using $(LinearAlgebra.BLAS.vendor()) (single thread), for comparison")
        tbr = @benchmark mul!($C0, $A0, $B0) samples=100 evals=100
        println(2*M*N*K/minimum(tbr.times), " Gflops")
    end
end


struct Poisson <: Distribution{Int} end

"""
    poisson(lambda::Real)

Sample an `Int` from the Poisson distribution with rate `lambda`.
"""
const poisson = Poisson()

function logpdf(::Poisson, x::Integer, lambda::Real)
    x * log(lambda) - lambda - lgamma(x+1)
end

function logpdf_grad(::Poisson, x::Integer, lambda::Real)
    error("Not implemented")
    (nothing, nothing)
end

function random(::Poisson, lambda::Real)
    rand(Distributions.Poisson(lambda))
end

(::Poisson)(lambda) = random(Poisson(), lambda)
is_discrete(::Poisson) = true

has_output_grad(::Poisson) = false
has_argument_grads(::Poisson) = (false,)

export poisson


"""
  `SingerAlg.PrimesGAP.jl` provides a few functions based on GAP
  that are faster than the corresponding functions from the Julia package
  `Primes.jl`.
  Since one of the aims of GAP's `SingerAlg` package is
  to compare implementations in GAP with Julia implementations,
  we use GAP's factorization code in both situations.
"""
module PrimesGAP

import GAP

isprime(n::T) where {T<:Integer} = GAP.Globals.IsPrimeInt(GAP.julia_to_gap(n))

nextprime(n::T) where {T<:Integer} = GAP.Globals.NextPrimeInt(GAP.julia_to_gap(n-1))

factors(n::T) where {T<:Integer} = Vector{Int}(GAP.Globals.Factors(GAP.julia_to_gap(n)))
#T We would better provide a function `factor` as in `Primes.jl`,
#T to whose result one can apply `sort(collect(Set, .))`.

end


"""
    fftPositiveFreq(ft,f)

Returns (coefficients, frequencies) for positive frequencies of the fourier transform
"""
function fftPositiveFreq(fft,f)
	@assert length(fft) == length(f) "Fourier transform and frequency vectors much have matching length"
	fftRange = f .>= 0
	return (fft[fftRange],f[fftRange])
end

export fftPositiveFreq

"""
    meanFFT(fft::Vector{S},cuts::Tuple{T,T}) where {S<:Real, T<:Integer}

Takes mean before taking fourier transform
"""
function meanFFT(timeSeries::Vector{S},cuts::Tuple{T,T}) where {S<:Real, T<:Integer}
	tsPrep = timeSeries[cuts[1]:cuts[2]]
	tsPrepMean = mean(tsPrep)
	tsPrep = tsPrep .- tsPrepMean
	return fft(tsPrep)
end

export meanFFT

"""
    fftFreq(tres::Real,τ::Vector,cuts::Tuple{T,T}) where {T<:Integer}

Returns frequencies associated with Fourier transform
"""
function fftFreq(tres::Real,τ::Vector,cuts::Tuple{T,T}) where {T<:Integer}
	τlen = cuts[2]-cuts[1]+1
	freqFFT = fftfreq(τlen,1/tres)
end

export fftFreq


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    BlockWeighting(sides)
    BlockWeighting(side₁, side₂, ...)

A weighting method that assigns weights to points in spatial object
based on blocks of given `sides`. The number `n` of points inside a
block determines the weights `1/n` of these points.
"""
struct BlockWeighting{Dim,T} <: WeightingMethod
  sides::SVector{Dim,T}
end

BlockWeighting(sides::NTuple) = BlockWeighting(SVector(sides))
BlockWeighting(sides::Vararg) = BlockWeighting(SVector(sides))

weight(object, method::BlockWeighting) =
  weight(domain(object), method)

function weight(domain::Domain, method::BlockWeighting)
  p = partition(domain, BlockPartition(method.sides))

  weights = Vector{Float64}(undef, nelements(domain))
  for s in subsets(p)
    weights[s] .= 1 / length(s)
  end

  GeoWeights(domain, weights)
end


using Plots, LaTeXStrings, Pkg, Revise
gr(titlefontsize=12,size=(500,300),transpose=true,colorbar=false)

#pkg"activate ."
using FourierGPE

# ==== Units: ========================
# this example works in oscillator units

# ==== set simulation parameters ====
L = (20.0,20.0)
N = (128,128)
μ = 25.0

# ========= Initialize simulation ======
sim = Sim(L,N)
@pack! sim = μ
@unpack_Sim sim

# declare the potential function
import FourierGPE.V
V(x,y,t) = 0.5*(x^2 + y^2)

# useful TF state
using VortexDistributions
ψ0(x,y,μ,g) = sqrt(μ/g)*sqrt(max(1.0-V(x,y,0.0)/μ,0.0)+im*0.0)

#make initial state
x,y = X
ψi = ψ0.(x,y',μ,g)
makevortex!(ψi,[0. 0. 1],x,y)
showpsi(x,y,ψi)
ϕi = kspace(ψi,sim)
@pack! sim = ϕi
sim
@unpack T = sim

T = Transforms(T,psi = ArrayPartition(ϕi,copy(ϕi),copy(ϕi)))

sim = Sim(sim,T=T)
ϕj = copy(ϕi)

function Lz(ϕ,sim)
    @unpack T,X,K = sim
    @unpack psi = T
    xky,ykx,L = psi.x[1],psi.x[2],psi.x[3]
    kx,ky = K
    x,y = X
    xky .= ϕ.*ky'
    ykx .= kx.*ϕ
    T.Tkx!*xky
    T.Tkx!*ykx
    @. L = x*xky - ykx*y'
    T.Txk!*L
    return L
end

@time Ltest = Lz(ϕj,sim)
Ltest == ϕj
ψjL = xspace(Ltest,sim)
sum(conj(ψi).*ψjL)/sum(abs2.(ψi))

# Ok, we have allocation free evaluation!!!

# ====== Evolve in k space ==========
sol = runsim(sim)
# ===================================

# pull out the ground state:
ϕg = sol[end]
ψg = xspace(ϕg,sim)
showpsi(x,y,ψg)

# Add a vortex off-axis
using VortexDistributions

# initial state: imprint a vortex inside Thomas-Fermi radius
healing(x,y,μ,g) = 1/sqrt(g*abs2(ψ0(x,y,μ,g)))
Rtf = sqrt(2*μ)
rv = 0.5*Rtf
xv,yv,cv = rv, 0.0, 1
initialvortex = [xv yv cv]
ξv = healing(xv,yv,μ,g)
ψv = copy(ψg)
makeallvortices!(ψv,initialvortex,x,y,ξv)
showpsi(x,y,ψv)

# In TF regim precession frequency is given analytically by:
# (see Fetter JLTP 2010)
ξ = 1/sqrt(μ)
Ωm = 3*log(Rtf/ξ/sqrt(2))/2/Rtf^2
Ωv = Ωm/(1-rv^2/Rtf^2)

#or a period of
Tv = 2*π/Ωv

# ==== set simulation parameters ====
γ = 0.0
tf = Tv
t = LinRange(ti,tf,Nt)
ϕi = kspace(ψv,sim)
reltol = 1e-7
alg = DP5()
@pack! sim = tf,t,γ,ϕi,reltol,alg
# initsim!(sim)

@unpack_Sim sim

# ====== Evolve in k space ==========
solv = runsim(sim)
# ===================================

ϕf = solv[100]
ψf = xspace(ϕf,sim)
showpsi(x,y,ψf)

#trim last few frames to show one orbit
# analytical result is within 10%
anim = @animate for i=1:Nt-6
    ψ = xspace(solv[i],sim)
    showpsi(x,y,ψ)
end

gif(anim,"./examples/vortex.gif",fps=30)


@testset "Generate PSF method" begin
    # large aperture is delta peak
    out = zeros((5, 5))
    out[1,1] = 1

    @test out ≈ simple_psf((5, 5), 100)
    out[1,1] = 0
    out[3,3] = 1
    @test out ≈ simple_psf((5, 5), 100, shift=true)

    # pinhole aperture
    out = ones((10, 10))
    out ./= sum(out)
    @test out ≈ simple_psf((10, 10), 0.01)

    # normalized
    @test 1 ≈ sum(simple_psf((100, 100), 10))
end

@testset "Generate jinc PSF method" begin
    # large aperture is delta peak
    out = zeros((5, 5))
    out[1,1] = 1

    # large aperture is roughly a delta
    out[1,1] = 0
    out[3,3] = 1
    @test out ≈ jinc_psf((5, 5), 1, 1, shift=true)

    # pinhole aperture
    out = ones((10, 10))
    out ./= sum(out)
    @test out ≈ jinc_psf((10, 10), 1, 1e-12)

    # normalized
    @test 1 ≈ sum(jinc_psf((100, 100), 10f0, 2f0, 1; λ=1f0, shift=true))
    @test 1 ≈ sum(jinc_psf((100, 100), 10f0, 2f0, 1e-20; λ=1f0, shift=true))

    # check consistency with Rayleigh resolution
    d = 1
    f = 1
    r = rayleigh_criterion(f, d)

    psf = jinc_psf((12, 12), 2 * r, d, f)
    @test psf[1, 6] > psf[1, 7] && psf[1, 7] < psf[1, 8]

    psf = jinc_psf((1024, 1024), 2 * r, d, f)
    @test 1 .+ psf[513, 513] ≈ 1
end


b = SpinBasis(1//2)
up = spinup(b)
down = spindown(b)
# id = identityoperator(b)

@test qubit("010") ≈ qubit("010")
@test qubit("010")' ≈ qubit("010")'
@test (qubit("010")')' ≈ qubit("010")
@test sigmax() ≈ sigmax()
@test sigmax() ≈ full(sigmax())
@test full(sigmax()) ≈ sigmax()
@test full(sigmax()) ≈ full(sigmax())

@test sigmax()' == dagger(sigmax())
@test full(sigmax())' == dagger(full(sigmax()))

@test outer(spinup(b), spinup(b)) == spinup(b) ⊗ dagger(spinup(b))
@test inner(spinup(b), spinup(b)) == dagger(spinup(b)) * spinup(b)
n = 5; @test ghz(n) == (basisstate(b^n, 1) + basisstate(b^n, 2^n)) / sqrt(2)

srand(2018)
for i in 1:10
    tmpr = rand(1:5)
    @test sparse_spinallup_dm(tmpr) |> full == dm(basisstate(b^tmpr, 1))
end
for i in 1:10
    tmpr = rand(1:5)
    @test sparse_spinalldown_dm(tmpr) |> full == dm(basisstate(b^tmpr, 2^tmpr))
end

srand(2018)
@test begin
    outcome, state = measure(hadamard() * up, 1)
    outcome == 1 && state ≈ down
end
begin
    @show outcome, state = measure(hadamard() * up, 1)
    outcome == 0 && state ≈ up
end

@test qubit("010") == up ⊗ down ⊗ up
@test qubit("010")' == dagger(qubit("010"))
@test qudit("012", 3) == basisstate(NLevelBasis(3), 1) ⊗ basisstate(NLevelBasis(3), 2) ⊗ basisstate(NLevelBasis(3), 3)
@test quxit("012", [2,2,3]) == basisstate(NLevelBasis(2), 1) ⊗ basisstate(NLevelBasis(2), 2) ⊗ basisstate(NLevelBasis(3), 3)


println("Calculating summary statistics")
for m in [boon,lti,volterra,kmm]
    err = StatsBase.msd(TDM.validate(quantity(Q2),1:N2,m.M),TDM.evalmodel(m,p2,Q2,1:N2))
    ns = nash_sutcliffe(m,p2,Q2)
    sf = flatness(m,p2,Q2)
    println(typeof(m),":: ",err,"\t",ns,"\t",sf)
end


using Expokit
using Test
using LinearAlgebra
using SparseArrays

# reference matrix exponential
expm_higham(A) = exp(A)

function ev1(n)

    v = zeros(n)
    v[1] = 1
    return v
end

function test_expmv(n::Int)

    A = sprand(n,n,0.4)
    v = ev1(n)

    t1 = @elapsed w1 = expmv(1.0, A, v)

    t2 = @elapsed w2 = expm_higham(Matrix(A))*v

    return norm(w1-w2)/norm(w2), t1, t2
end

function test_expmv2(n::Int)

    A = sprand(n,n,0.2) + 1im*sprand(n,n,0.2)
    v = ev1(n) + 0im*ev1(n)

    t1 = @elapsed w1 = expmv(1.0, A, v)

    t2 = @elapsed w2 = expm_higham(Matrix(A))*v

    return norm(w1-w2)/norm(w2), t1, t2
end

function test_expmv3()
    e1 = norm(expm_higham(pi/4*[0 1; 1 0])*[1.; 0.] - expmv(pi/4,[0 1; 1 0]|>sparse, [1.; 0.]))
    e2 = norm(expm_higham(-pi/4*1im*[0 1; 1 0])*[1.; 0.] - expmv(pi/4,-1im*[0 1; 1 0]|>sparse, [1.0+0im; 0.]))

    return e1, e2
end

# Testing custom type for A
struct LinearOp
    m
end

LinearAlgebra.mul!(y, lo::LinearOp, x) = mul!(y, lo.m, x)
Base.size(lo::LinearOp, i::Int) = size(lo.m, i)
Base.eltype(lo::LinearOp) = eltype(lo.m)

function test_expmv_linop(n::Int)
    A = LinearOp(sprand(n,n,0.2) + 1im*sprand(n,n,0.2))
    v = ev1(n) + 0im*ev1(n)

    t1 = @elapsed w1 = expmv(1.0, A, v, anorm=norm(A.m, Inf))

    t2 = @elapsed w2 = expmv(1.0, A, v)

    t3 = @elapsed w3 = expm_higham(Matrix(A.m))*v

    return max(norm(w1-w2)/norm(w2), norm(w1-w3)/norm(w3)), t1, t2, t3
end

println("testing real n=100 (first expmv, then expm)")
res, t1, t2 = test_expmv(100)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing complex n=100 (first expmv, then expm)")
res, t1, t2 = test_expmv2(100)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing real n=1000 (first expmv, then expm)")
res, t1, t2 = test_expmv(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing complex n=1000 (first expmv, then expm)")
res, t1, t2 = test_expmv2(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing linear operator n=1000 (first expmv, then expm)")
res, t1, t2, t3 = test_expmv_linop(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing 2x2 cases")
e1, e2 = test_expmv3()
println("residua: $e1, $e2\n")
@test e1 < 1e-10
@test e2 < 1e-10

function test_padm(n::Int)

    A = sprand(n,n,0.001)

    t1 = @elapsed w1 = padm(A)

    t2 = @elapsed w2 = expm_higham(Matrix(A))

    return norm(w1-w2)/norm(w2), t1, t2
end

function test_padm2(n::Int)

    A = sprand(n,n,0.0005) + 1im*sprand(n,n,0.0005)

    t1 = @elapsed w1 = padm(A)

    t2 = @elapsed w2 = expm_higham(Matrix(A))

    return norm(w1-w2)/norm(w2), t1, t2
end

println("testing real n=1000 (first padm, then expm)")
res, t1, t2 = test_padm(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing complex n=100 (first padm, then expm)")
res, t1, t2 = test_padm2(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

function test_chbv(n::Int)

    p = 0.1
    D = Matrix(Diagonal(-rand(n)))
    T = sprandn(n, n, p)
    H = T * D * transpose(T)  # random negative semidefinite symmetric matrix

    vec = randn(n)
    w1 = chbv(H, vec)
    t1 = @elapsed w1 = chbv(H, vec)

    w2 = expm_higham(Matrix(H)) * vec
    t2 = @elapsed  w2 = expm_higham(Matrix(H)) * vec

    return norm(w1-w2)/norm(w2), t1, t2
end

function test_chbv2(n::Int)

    p = 0.1
    D = Matrix(Diagonal(-rand(n)))
    T = sprandn(n, n, p) + sprandn(n, n, p)*im
    H = T * D * adjoint(T)  # random negative semidefinite hermitian matrix

    vec = randn(n) + randn(n)*im
    w1 = chbv(H, vec)
    t1 = @elapsed w1 = chbv(H, vec)

    w2 = expm_higham(Matrix(H)) * vec
    t2 = @elapsed w2 = expm_higham(Matrix(H)) * vec

    return norm(w1-w2)/norm(w2), t1, t2
end

println("testing real n=100 (first chbv, then expm)")
res, t1, t2 = test_chbv(100)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing complex n=100 (first chbv, then expm)")
res, t1, t2 = test_chbv2(100)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing real n=1000 (first chbv, then expm)")
res, t1, t2 = test_chbv(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

#= commented since this test takes sometime difs very long
println("testing complex n=1000 (first chbv, then expm)")
res, t1, t2 = test_chbv2(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6
=#

function test_phimv(n::Int)

    p = 0.1
    found = false
    A = sprand(n, n, p)
    u = rand(n)
    x = similar(u)
    while !found
        try
            copyto!(x, A\u)
            found = true
        catch
            A = sprand(n, n, p)
        end
    end
    vec = ev1(n)

    w1 = phimv(1.0, A, u, vec) # warmup
    t1 = @elapsed w1 = phimv(1.0, A, u, vec)

    w2 = expm_higham(Matrix(A))*(vec+x)-x # warmup
    t2 = @elapsed w2 = expm_higham(Matrix(A))*(vec+x)-x

    return norm(w1-w2)/norm(w2), t1, t2
end

function test_phimv2(n::Int)

    p = 0.1
    found = false
    A = sprand(n, n, p) + 1im*sprand(n, n, p)
    u = rand(n) + 1im*rand(n)
    x = similar(u)
    while !found
        try
            copyto!(x, A\u)
            found = true
        catch
            A = sprand(n, n, p) + 1im*sprand(n, n, p)
        end
    end
    vec = ev1(n) + 0im*ev1(n)

    w1 = phimv(1.0, A, u, vec) # warmup
    t1 = @elapsed w1 = phimv(1.0, A, u, vec)

    w2 = expm_higham(Matrix(A))*(vec+x)-x # warmup
    t2 = @elapsed w2 = expm_higham(Matrix(A))*(vec+x)-x

    return norm(w1-w2)/norm(w2), t1, t2
end

println("testing real n=100 (first phimv, then expm)")
res, t1, t2 = test_phimv(100)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing complex n=100 (first phimv, then expm)")
res, t1, t2 = test_phimv2(100)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing real n=1000 (first phimv, then expm)")
res, t1, t2 = test_phimv(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

println("testing complex n=1000 (first phimv, then expm)")
res, t1, t2 = test_phimv2(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6

function test_phimv_linop(n::Int)
    p = 0.1
    found = false
    A = LinearOp(sprand(n,n,p))
    u = rand(n)
    x = similar(u)
    while !found
        try
            copyto!(x, A.m\u)
            found = true
        catch
            A = LinearOp(sprand(n,n,p))
        end
    end
    vec = ev1(n)

    w1 = phimv(1.0, A, u, vec) # warmup
    t1 = @elapsed w1 = phimv(1.0, A, u, vec)

    w2 = expm_higham(Matrix(A.m))*(vec+x)-x # warmup
    t2 = @elapsed w2 = expm_higham(Matrix(A.m))*(vec+x)-x

    return norm(w1-w2)/norm(w2), t1, t2
end

println("testing real n=1000 (first phimv, then expm), the linear operator version.")
res, t1, t2 = test_phimv_linop(1000)
println("residuum: $res")
println("time dif: $(t1-t2)\n")
@test res < 1e-6


using .Hidato       # Note that the . here means to look locally for the module rather than in the libraries

const holyknight = """
 . 0 0 0 . . . .
 . 0 . 0 0 . . .
 . 0 0 0 0 0 0 0
 0 0 0 . . 0 . 0
 0 . 0 . . 0 0 0
 1 0 0 0 0 0 0 .
 . . 0 0 . 0 . .
 . . . 0 0 0 . . """

const knightmoves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]

board, maxmoves, fixed, starts = hidatoconfigure(holyknight)
printboard(board, " 0", "  ")
hidatosolve(board, maxmoves, knightmoves, fixed, starts[1][1], starts[1][2], 1)
printboard(board)


using Test
using Example2018

# Polynomial form a ring
#1. addition is associative
#2. additive identity
#3. additive inverse
#4. addition commutes
#5. multiplication is ...

@testset "Polynomials form a ring" begin
    z = Polynomial([0])
    p = Polynomial([1,2,3])
    for n in 1:100
        deg = rand(0:100)
        p = Polynomial(rand(-100000:100000, deg+1))

        @test p + z == p
        @test z + p == p
        @test p + (-p) == z
   end

end


@enum Action UP=1 DOWN LEFT RIGHT

const actionspace = [UP, DOWN, LEFT, RIGHT]
const actions = ["↑", "↓", "←", "→"] # alias

const MOVEMENTS = Dict(
    UP    => State(0, 1),
    DOWN  => State(0, -1),
    LEFT  => State(-1, 0),
    RIGHT => State(1, 0)
)

# State Transition Function
@inline Base.:+(s1::State, s2::State)::State = State(s1.x + s2.x, s1.y + s2.y);

# Reward Function
function reward(g::Grid, s::State)::Real
    return inbounds(g, s) ? g[s.x, s.y] : 0;
end

# Transition Function
function transition(g::Grid, s::State, a::Action)
    if reward(g, s) != 0 # reached a terminal state
        return Deterministic(g.null_state);
    end

    N = length(actionspace)
    next_states = Vector{State}(undef, N+1)
    probabilities = zeros(N+1)
    p_transition = g.p_transition

    for (i, a′) in enumerate(actionspace)
        prob = (a′ == a) ? p_transition : (1 - p_transition) / (N - 1)
        destination = s + MOVEMENTS[a′]
        next_states[i+1] = destination
        if inbounds(g, destination)
            probabilities[i+1] += prob
        end
    end

    # handle out of bounds transitions
    next_states[1] = s
    probabilities[1] = 1 - sum(probabilities)

    return SparseCat(next_states, probabilities);
end

# Terminal State: Goal, Error, Out of Bounds
@inline terminal(g::Grid, s::State)::Bool = s == g.null_state;


module Integers

##############################################################################
##
## TODO
##
##############################################################################

# - Should remove module and rely on package-level module as per convention
# - Methods e.g., promote, convert, macro +, -, *, /

##############################################################################
##
## Exports
##
##############################################################################

export IntegerMod, makeModular, extendedEuclideanAlgorithm,
       gcd

##############################################################################
##
## Implementation
##
##############################################################################

import Base.show,
       Base.length,
       Base.convert,
       Base.divrem,
       Base.promote_rule,
       Base.promote,
       Base.abs,
       Base.div,
       Base.rem,
       Base.zero,
       Base.zeros

type IntegerMod{P} <: Number
    n::Integer
    p::Integer

    function IntegerMod()
      new(0, P)
    end
    function IntegerMod(N::Integer)
        new(mod(N,P), P)
    end
end

function makeModular(n::Integer, p::Integer)
    return IntegerMod{p}(n)
end

convert{P}(::Type{IntegerMod{P}}, n::Integer) = IntegerMod{P}(n)
convert{P}(::Type{IntegerMod{P}}, a::Array) = [IntegerMod{P}(n) for n in a]

promote_rule{P}(::Type{IntegerMod{P}}, ::Type{Integer}) = IntegerMod{P}

function show{P}(io::IO, a::IntegerMod{P})
    print(io, string(a.n, " (mod ", P, ")"))
end

function =={P}(a::IntegerMod{P}, b::IntegerMod{P})
  return a.n == b.n
end

function =={P}(a::IntegerMod{P}, b::Integer)
  return a.n == mod(b, P)
end

function =={P}(a::Integer, b::IntegerMod{P})
  return mod(a, P) == b.n
end

function +{P}(a::IntegerMod{P}, b::IntegerMod{P})
  return IntegerMod{P}(a.n + b.n)
end

function +{P}(a::IntegerMod{P}, b::Integer)
  return IntegerMod{P}(a.n + b)
end

function +{P}(a::Integer, b::IntegerMod{P})
  return IntegerMod{P}(a + b.n)
end

function -{P}(a::IntegerMod{P})
  return IntegerMod{P}(-a.n)
end

function -{P}(a::IntegerMod{P}, b::IntegerMod{P})
  return IntegerMod{P}(a.n - b.n)
end

function -{P}(a::IntegerMod{P}, b::Integer)
  return IntegerMod{P}(a.n - b)
end

function -{P}(a::IntegerMod{P}, b::IntegerMod{P})
  return return IntegerMod{P}(a - b.n)
end

function *{P}(a::IntegerMod{P}, b::IntegerMod{P})
  return IntegerMod{P}(a.n * b.n)
end

function *{P}(a::IntegerMod{P}, b::Bool)
  return
end

function *{P}(a::Bool, b::IntegerMod{P})
  return
end

function *{P}(a::IntegerMod{P}, b::Integer)
  return IntegerMod{P}(a.n * b)
end

function *{P}(a::Integer, b::IntegerMod{P})
  return IntegerMod{P}(a * b.n)
end

function zero{P}(a::IntegerMod{P})
  return IntegerMod{P}(0)
end

function zero{P}(a::Type{IntegerMod{P}})
  return IntegerMod{P}(0)
end

function zeros{P}(T::Type{IntegerMod{P}}, n = 0)
  result = Array(IntegerMod{P}, n)
  for i in 1:n
    result[i] = 0
  end
  return result
end

function divrem{P}(a::IntegerMod{P}, b::IntegerMod{P})
    q, r = divrem(a.n, b.n)
  return (IntegerMod{P}(q), IntegerMod{P}(r))
end

function /{P}(a::IntegerMod{P}, b::IntegerMod{P})
    return a * inverse(b)
end

function abs{P}(a::IntegerMod{P})
  return abs(a.n)
end

function inverse{P}(a::IntegerMod{P})
    x, y, d = extendedEuclideanAlgorithm(a.n, P)
    return IntegerMod{P}(x)
end

function gcd(a, b)
  if abs(b) > abs(a)
    return gcd(b, a)
  end

  while abs(b) > 0
    _, r = divrem(a,b)
    a, b = b, r
  end

  return a
end

function extendedEuclideanAlgorithm{T<:Number}(a::T, b::T)
    if abs(b) > abs(a)
        x, y, d = extendedEuclideanAlgorithm(b,a)
        return (y,x,d)
    end

    if abs(b) == 0
        return (1, 0, a)
    end

    x1, x2, y1, y2 = convert(T, 0), convert(T, 1), convert(T, 1), convert(T, 0)
    while abs(b) > 0
        q, r = divrem(a, b)
        x = x2 - q*x1
        y = y2 - q*y1
        a, b, x2, x1, y2, y1 = b, r, x1, x, y1, y
    end

    return (x2, y2, a)
end

end # module Integer


pointstr, foldstr = split(read("input.txt", String), "\n\n")
points = map(x -> parse.(Int, split(x, ',')), split.(pointstr, '\n', keepempty=false))
folds = map(x -> split(x, "="), split.(foldstr, '\n', keepempty=false))

function fold!(points, directions)
    dim = directions[1] == "fold along x" ? 1 : 2
    mag = parse(Int, directions[2])
    for p in points
        Δ = p[dim] - mag
        Δ > 0 && (p[dim] -= 2*Δ)
    end
    return points
end

function display_code(points)
    points |> sort! |> unique!
    X, Y = maximum(reduce(hcat, points), dims=2)
    for y in 0:Y
        println()
        for x in 0:X
            print([x, y] in points ? repeat(Char(0x2588), 2) : "  ")
        end
    end
end

for d in folds
    fold!(points, d)
    d == first(folds) && @info "Part 1: $(length(unique(points)))"
end
display_code(points)


# Tests the GeoUnits
using Test
using GeoParams

@testset "Units" begin

# test creating structures
CharUnits_GEO   =   GEO_units(viscosity=1e19, length=1000km);
@test CharUnits_GEO.length  ==  1000km
@test CharUnits_GEO.Pa      ==  10000000Pa
@test CharUnits_GEO.Mass    ==  1.0e37kg
@test CharUnits_GEO.Time    ==  1.0e12s
@test CharUnits_GEO.Length  ==  1000000m

CharUnits_SI   =   SI_units();
@test CharUnits_SI.length   ==  1000m
@test CharUnits_SI.Pa       ==  10Pa

CharUnits_NO   =   NO_units();
@test CharUnits_NO.length   ==  1
@test CharUnits_NO.Pa       ==  1

# test nondimensionization of various parameters
@test Nondimensionalize(10cm/yr,CharUnits_GEO) ≈ 0.0031688087814028945   rtol=1e-10
@test Nondimensionalize(10cm/yr,CharUnits_SI) ≈ 3.168808781402895e7   rtol=1e-10

A = 10MPa*s^(-1)   # should give 1e12 in ND units
@test Nondimensionalize(A,CharUnits_GEO)  ≈ 1e12

A = 10Pa^1.2*s^(-1)
@test Nondimensionalize(A,CharUnits_GEO)  ≈ 39810.71705534975

CharUnits   =   GEO_units(viscosity=1e23, length=100km, stress=0.00371MPa);
@test Nondimensionalize(A,CharUnits)  ≈ 1.40403327e16

A = (1.58*10^(-25))*Pa^(-4.2)*s^(-1)        # calcite
@test Nondimensionalize(A,CharUnits_GEO)  ≈ 3.968780561785161e16

R=8.314u"J/mol/K"
@test Nondimensionalize(R,CharUnits_SI)  ≈ 8.314e-7

# test Dimensionalize
v_ND      =   Nondimensionalize(3cm/yr, CharUnits_GEO);
@test Dimensionalize(v_ND, cm/yr, CharUnits_GEO) == 3.0cm/yr

# Test the GeoUnit struct
x=GeoUnit(8.1cm/yr)
@test  x.val==8.1cm/yr
Nondimensionalize!(x,CharUnits_GEO)
@test  x.val≈ 0.002566735112936345 rtol=1e-8        # Nondimensionalize a single value
Dimensionalize!(x,CharUnits_GEO)
@test  x.val==8.1cm/yr                              # Dimensionalize again

y   =   x+2cm/yr
@test  y.val==10.1cm/yr                             # errors

xx  =GeoUnit([8.1cm/yr; 10cm/yr]);
@test  xx.val==[8.1cm/yr; 10cm/yr]
yy=xx/(1cm/yr);                                         # transfer to no-unt

z=GeoUnit([100km 1000km 11km; 10km 2km 1km], km);       # array
@test z/1km*1.0 == [100.0 1000.0 11.0; 10.0 2.0 1.0]    # The division by 1km transfer it to a GeoUnit structure with no units; the multiplying with a float creates a float array

zz = GeoUnit([1:10]km)

# Test non-dimensionalisation if z is an array
@test Nondimensionalize!(z,CharUnits_GEO)==[0.1   1.0    0.011; 0.01  0.002  0.001]

Dimensionalize!(z,CharUnits_GEO)
@test z.val==[100km 1000km 11km; 10km 2km 1km]          # transform back

# test extracting a value from a GeoUnit array
@test z[2,2] == 2.0km

# test setting a new value
z[2,1]=3km
@test z[2,1] == 3.0km
end


"""
    Integrate1D(F::Function, Cube::HyperCube; tol::Real=1e-14, FullSol::Bool=false, meth=nothing)
Integrates `F` over a one-dimensional domain specified via a `HyperCube` by rephrasing the integral as an ODE and using `DifferentialEquations.jl`.
"""
function Integrate1D(F::Function, Cube::HyperCube; tol::Real=1e-14, FullSol::Bool=false, meth=nothing)
    length(Cube) != 1 && throw(ArgumentError("Cube dim = $(length(Cube)) instead of 1"))
    Integrate1D(F,(Cube.L[1],Cube.U[1]); tol=tol,FullSol=FullSol,meth=meth)
end
Integrate1D(F::Function, Interval::AbstractVector{<:Number}; tol::Real=1e-14, FullSol::Bool=false, meth=nothing) = Integrate1D(F, Tuple(Interval); tol=tol, FullSol=FullSol, meth=meth)
function Integrate1D(F::Function, Interval::Tuple{<:Number,<:Number}; tol::Real=1e-14, FullSol::Bool=false, meth=nothing)
    Interval = floatify(Interval)
    !(0. < tol < 1.) && throw("Integrate1D: tol unsuitable")
    Interval[1] > Interval[2] && throw(ArgumentError("Interval orientation wrong."))
    f(u,p,t) = F(t);    u0 = 0.
    if tol < 1e-15
        u0 = BigFloat(u0);        Interval = BigFloat.(Interval)
        meth = isnothing(meth) ? Vern9() : meth
    else
        meth = isnothing(meth) ? Tsit5() : meth
    end
    if FullSol
        return solve(ODEProblem(f,u0,Interval),meth; reltol=tol,abstol=tol)
    else
        return solve(ODEProblem(f,u0,Interval),meth; reltol=tol,abstol=tol,save_everystep=false,save_start=false,save_end=true).u[end]
    end
end

"""
    IntegrateND(F::Function,Cube::HyperCube; tol::Real=1e-12, WE::Bool=false, kwargs...)
Integrates the function `F` over `Cube` with the help of **HCubature.jl** to a tolerance of `tol`.
If `WE=true`, the result is returned as a `Measurement` which also contains the estimated error in the result.
"""
function IntegrateND(F::Function, Cube::HyperCube; tol::Real=1e-12, WE::Bool=false, kwargs...)
    if length(Cube) == 1
        val, uncert = hquadrature(F, Cube.L[1], Cube.U[1]; rtol=tol, atol=tol, kwargs...)
    else
        val, uncert = hcubature(F, Cube.L, Cube.U; rtol=tol, atol=tol, kwargs...)
    end
    if length(val) == 1
        return WE ? measurement(val[1],uncert[1]) : val[1]
    else
        return WE ? measurement.(val,uncert) : val
    end
end
IntegrateND(F::Function, L::AbstractVector{<:Number}, U::AbstractVector{<:Number}; tol::Real=1e-12, WE::Bool=false, kwargs...) = IntegrateND(F,HyperCube(L,U); tol=tol, WE=WE, kwargs...)
IntegrateND(F::Function, Interval::Union{AbstractVector{<:Number},Tuple{<:Number,<:Number}}; tol::Real=1e-12, WE::Bool=false, kwargs...) = IntegrateND(F,HyperCube(Interval); tol=tol, WE=WE, kwargs...)

"""
    IntegrateOverConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, Confnum::Real, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
Integrates a function `F` over the intersection of `Domain` and the confidence region of level `Confnum`.
"""
function IntegrateOverConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, Confnum::Real, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
    @assert length(Domain) == pdim(DM)
    # Multiply F with characteristic function for confidence region
    Threshold = LogLikeMLE(DM) - 0.5InvChisqCDF(pdim(DM), ConfVol(Confnum))
    InsideRegion(X::AbstractVector{<:Number}) = loglikelihood(DM, X; kwargs...) > Threshold
    Integrand(X::AbstractVector{<:Number}) = InsideRegion(X) ? F(X) : zero(suff(X))
    # Use HCubature instead of MonteCarlo
    MonteCarloArea(Integrand, Domain, N; WE=WE)
end

"""
    IntegrateOverApproxConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, sol::AbstractODESolution, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
    IntegrateOverApproxConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, Planes::AbstractVector{<:Plane}, sols::AbstractVector{<:AbstractODESolution}, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
Integrates a function `F` over the intersection of `Domain` and the polygon defined by `sol`.
"""
function IntegrateOverApproxConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, sol::AbstractODESolution, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
    @assert length(Domain) == pdim(DM) == length(sol.u[1]) == 2
    Integrand(X::AbstractVector{<:Number}) = ApproxInRegion(sol, X) ? F(X) : zero(suff(X))
    # Use HCubature instead of MonteCarlo
    MonteCarloArea(Integrand, Domain, N; WE=WE)
end

function IntegrateOverApproxConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, Tup::Tuple{<:AbstractVector{<:Plane},<:AbstractVector{<:AbstractODESolution}}, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
    IntegrateOverApproxConfidenceRegion(DM, Domain, Tup[1], Tup[2], F; N=N, WE=WE, kwargs...)
end
function IntegrateOverApproxConfidenceRegion(DM::AbstractDataModel, Domain::HyperCube, Planes::AbstractVector{<:Plane}, sols::AbstractVector{<:AbstractODESolution}, F::Function; N::Int=Int(1e5), WE::Bool=true, kwargs...)
    @assert length(Domain) == pdim(DM) == ConsistentElDims(Planes)
    @assert length(Planes) == length(sols)
    Integrand(X::AbstractVector{<:Number}) = ApproxInRegion(Planes, sols, X) ? F(X) : zero(suff(X))
    # Use HCubature instead of MonteCarlo
    MonteCarloArea(Integrand, Domain, N; WE=WE)
end

function MonteCarloArea(Test::Function,Cube::HyperCube,N::Int=Int(1e7); WE::Bool=false)
    if WE
        return CubeVol(Cube) * MonteCarloRatioWE(Test,Cube,N)
    else
        return CubeVol(Cube) * MonteCarloRatio(Test,Cube,N)
    end
end
function MonteCarloRatio(Test::Function,Cube::HyperCube,N::Int=Int(1e7))
    (1/N)* @distributed (+) for i in 1:N
        Test(rand.(Uniform.(Cube.L,Cube.U)))
    end
end

function MonteCarloRatioWE(Test::Function,LU::HyperCube,N::Int=Int(1e7); chunksize::Int=Int(N/20))
    chunksize > N && error("chunksize > N")
    if N%chunksize != 0
        N += Int(N%chunksize + 1)
    end
    chunks = Int(N/chunksize)
    # Output not normalized by chunksize
    function CarloLoop(Test::Function,LU::HyperCube,chunksize::Int)
        tot = [rand.(Uniform.(LU.L,LU.U)) for i in 1:chunksize] .|> Test
        res = sum(tot)
        [res, sum(abs2, (tot .- (res/chunksize)))]
    end
    Tot = @distributed (+) for i in 1:chunks
        CarloLoop(Test,LU,chunksize)
    end
    measurement(Tot[1]/N, sqrt(1/((N-1)*N) * Tot[2]))
end

# # From Cuba.jl docs
# function CompactDomainTransform(F::ModelOrFunction, Cube::HyperCube)
#     (!all(x->isfinite(x),Cube.L) || !all(x->isfinite(x),Cube.U)) && throw("Not applicable.")
#     if length(Cube) == 1
#         W = Cube.U[1] - Cube.L[1]
#         return x -> W * F(Cube.L[1] + W * x)
#         # Use mul! or something like that?
#     else
#         W = CubeWidths(Cube);   V = prod(CubeWidths)
#         return x -> V * F(Cube.L + W * x)
#     end
# end
#
# function HalfInfiniteTransform(F::ModelOrFunction, Cube::HyperCube)
#     # integral over [a,∞]
#     if Cube.U[1] == Inf && isfinite(Cube.L[1])
#         return x -> (1-x)^-2 * F(Cube.L[1] + x/(1-x))
#     end
# end
#
# function InfiniteDomainTransform(F::ModelOrFunction, Cube::HyperCube)
#     if Cube.L[1] == -Inf && Cube.L[1] == Inf
#         return x -> F((2x - 1.)/((1 - x)*x)) * (2x^2 - 2y + 1) / ((1-x^2)*x^2)
#     end
# end


function leap_it()

    # Prompt the user for input and convert it to an int from a string
    year = parse(Int64, (Base.prompt("Which year would you like to check if it's a leap year?")))

    # If the remainder of dividing the year by 4 is zero (which is the basic math to check a leap year)
    if (year % 4) == 0
        println("$year is a leap year...")
    else

        # Calculate how long till the next leap year, if the entered year is not one
        years_away = 4 - (year % 4)
        println("$year is not a leap year, you have to wait $years_away year...")
    end
end


#=
conjugateUpdates.jl

The function naming convention for this file is:
post_likelihood_unknownParams
=#

export post_norm_mean, post_norm_var, rpost_normlm_beta1, rpost_normlmGenCov_beta1,
       post_alphaDP,
       rpost_MvN_knownPrec, rpost_MvNprec_knownMean;

"""
    post_norm_mean(ȳ, n, σ2, μ0, v0)

  Returns conjugate normal posterior under independent normal likelihood
  with known variance and normal prior on the mean.

  Accepts the sample mean and returns a normal distribution.
"""
function post_norm_mean(ȳ::T, n::Int, σ2::T, μ0::T, v0::T) where T <: Real
  σ2 > 0.0 || throw(ArgumentError("σ must be positive."))
  v0 > 0.0 || throw(ArgumentError("v0 must be positive."))
  n > 0.0 || throw(ArgumentError("n must be positive."))

  # n = convert(Float, n)

  v1 = 1.0 / (1.0/v0 + n/σ2)
  sd1 = sqrt(v1)
  μ1 = v1 * (μ0/v0 + n*ȳ/σ2)

  Normal(μ1, sd1)
end

"""
    post_norm_var(ss, n, a0, b0)

  Returns conjugate inverse-gamma posterior for variance under
  independent normal likelihood with known mean and
  inverse-gamma prior on the variance.

  Accepts `ss```= ∑(y_i - μ_i)^2`` and sample size `n`.

  `a0` is the shape parameter of the inverse-gamma prior.
  `b0` is the scale parameter of the inverse-gamma prior.
"""
function post_norm_var(ss::T, n::T, a0::T, b0::T) where T <: Real
  a0 > 0.0 || throw(ArgumentError("a0 must be positive."))
  b0 > 0.0 || throw(ArgumentError("b0 must be positive."))

  # n = convert(Float, n)
  a1 = a0 + 0.5*n
  b1 = b0 + 0.5*ss

  InverseGamma(a1, b1)
end

"""
    rpost_normlm_beta1(y, X, σ2, τ2, β0=0.0)

  Returns draw from conjugate normal posterior for linear model
  coefficients under independent normal likelihood with
  (optionally) known intercept `β0` and observation variance `σ2`.

  Assumes independent normals prior with mean `0.0` and variance `τ2`.
"""
function rpost_normlm_beta1(y::Vector{T},
  X::Matrix{T},
  σ2::T, τ2::T, β0::T=0.0) where T <: Real
  # n > 1 and p > 1 case

  σ2 > 0.0 || throw(ArgumentError("σ2 must be positive."))
  τ2 > 0.0 || throw(ArgumentError("τ2 must be positive."))

  n,p = size(X) # assumes X was a matrix
  length(y) == n || throw(ArgumentError("y and X dimension mismatch"))
  ystar = y .- β0

  A = Matrix(1.0I, p, p) / τ2 + X'X / σ2
  U = (cholesky(A)).U
  Ut = transpose(U)

  # μ_a = At_ldiv_B(U, (X'ystar/σ2))
  μ_a = Ut \ (X'ystar/σ2)
  μ = U \ μ_a

  z = randn(p)
  β = U \ z + μ
end
function rpost_normlm_beta1(y::T, X::Vector{T},
  σ2::T, τ2::T, β0::T=0.0) where T <: Real
  # n = 1 and p > 1 case

  σ2 > 0.0 || throw(ArgumentError("σ2 must be positive."))
  τ2 > 0.0 || throw(ArgumentError("τ2 must be positive."))

  p = length(X)
  ystar = y .- β0

  A = Matrix(1.0I, p, p) / τ2 + (X * transpose(X)) / σ2
  U = (cholesky(A)).U
  Ut = transpose(U)

  # μ_a = At_ldiv_B(U, (ystar*X/σ2))
  μ_a = Ut \ (ystar*X/σ2)
  μ = U \ μ_a

  z = randn(p)
  β = U \ z + μ
end
function rpost_normlm_beta1(y::Vector{T},
  X::Vector{T},
  σ2::T, τ2::T, β0::T=0.0) where T <: Real
  # n > 1 and p = 1 case

  σ2 > 0.0 || throw(ArgumentError("σ2 must be positive."))
  τ2 > 0.0 || throw(ArgumentError("τ2 must be positive."))
  length(y) == length(X) || throw(ArgumentError("y and X dimension mismatch"))

  ystar = y .- β0

  A = 1.0 / τ2 + dot(X,X) / σ2
  μ = (X'ystar/σ2) / A

  z = randn(1)
  β = z / sqrt(A) .+ μ
end
function rpost_normlm_beta1(y::T, X::T,
  σ2::T, τ2::T, β0::T=0.0) where T <: Real
  # n = 1 and p = 1 case

  σ2 > 0.0 || throw(ArgumentError("σ2 must be positive."))
  τ2 > 0.0 || throw(ArgumentError("τ2 must be positive."))

  ystar = y .- β0

  A = 1.0 / τ2 + dot(X,X) / σ2
  μ = (X'ystar/σ2) / A

  z = randn(1)
  β = z / sqrt(A) .+ μ
end

function rpost_normlmGenCov_beta1(y::Vector{T}, X::Array{T,2}, Σ::TT,
                                  mβ::Vector{T}, # prior mean vector
                                  Vβdiag::Vector{T}) where {T <: Real, TT <: PDMat}

    all(Vβdiag .> 0.0) || thow("All varaince components must be positive.")

    Vβinv = Diagonal(1.0 ./ Vβdiag)
    Prec = Vβinv + Xt_invA_X(Σ, X)
    pot = Vβinv*mβ + whiten(Σ, X)'whiten(Σ, y)

    return rand( MvNormalCanon( pot, Prec) )
end
function rpost_normlmGenCov_beta1(y::Vector{T}, X::Array{T,2}, Σ::TT,
                                  Vβdiag::Vector{T}) where {T <: Real, TT <: PDMat}

    ## assumes zero vector for prior mean
    all(Vβdiag .> 0.0) || thow("All varaince components must be positive.")

    Vβinv = Diagonal(1.0 ./ Vβdiag)
    Prec = Vβinv + Xt_invA_X(Σ, X)
    pot =  whiten(Σ, X)'whiten(Σ, y)

    return rand( MvNormalCanon( pot, Prec) )
end

"""
    post_alphaDP(H::Int, lω_last::T, a_α::T, b_α::T)

    Returns posterior full conditional gamma distribution
    for the Dirichlet Process mass parameter `α`
    when using the truncated stick breaking representation.

    `H` is the the DP truncation level.
    `lω_last` is the Hth and last log weight.
    `a_α` is the shape of the gamma prior.
    `b_α` is the rate of the gamma prior.
"""
function post_alphaDP(H::Int, lω_last::T, a_α::T, b_α::T) where T <: Real
    a1 = a_α + H - 1.0
    b1 = b_α - lω_last # rate parameter
    Gamma(a1, 1.0/b1)
end

function rpost_MvN_knownPrec(Y::Array{T,2}, Λ::TT,
                             μ0::Array{T,1}, Λ0::TT) where {T <: Real, TT <: PDMat}
    n = float(T)(size(Y,1))
    ybar = vec(mean(Y, dims=1))
    return rpost_MvN_knownPrec(n, ybar, Λ, μ0, Λ0)
end
function rpost_MvN_knownPrec(n::T, ybar::Array{T,1}, Λ::TT,
                             μ0::Array{T,1}, Λ0::TT) where {T <: Real, TT <: PDMat}
    d = length(ybar)
    Λ1 = Λ0 + (n * Λ) # result is of type PDMat
    a = Λ0*μ0 + (n * Λ * ybar)
    μ1 = Λ1 \ a
    return μ1 + Λ.chol.U \ randn(d)
end

function rpost_MvNprec_knownMean(Y::Array{T,2}, μ::Array{T,1},
                                 df::T, invSc::TT) where {T <: Real, TT <: PDMat}
    n = size(Y,1)
    K = length(μ)
    SS = zeros(T, K, K)
    for i = 1:n
        dev = Y[i,:] - μ
        SS +=  dev * dev' # not necessarily pos. def.
    end
    return rpost_MvNprec_knownMean(float(T)(n), SS, df, invSc)
end
function rpost_MvNprec_knownMean(n::T, SS::Array{T,2}, df::T, invSc::TT) where {T <: Real, TT <: PDMat}
    df1 = df + n
    invSc1 = PDMat(invSc + SS) # result is of type PDMat
    Sc1 = inv(invSc1) # is there some way around this?
    return rand(Distributions.Wishart(df1, Sc1))
end


@testset "Interval tests" begin

    # Closed interval
    h = HalfSpace([-1], 3.) ∩ HalfSpace([1.], 8)
    v = convexhull([-3.], [8.])

    v0 = [[1.], [8], [-2], [4], [-1], [-3], [5]]
    pp = polyhedron(vrep(v0), SimplePolyhedraLibrary{Float64}())
    @test !hrepiscomputed(pp)
    @test vrepiscomputed(pp)
    for p in (Interval{Float64, SVector{1, Float64}}(pp),
              Interval{Float64, SVector{1, Float64}}(Polyhedra.vreps(pp)...))
        @test similar_library(pp, FullDim{1}()) == IntervalLibrary{Float64}()
        @test getlibrary(p) == IntervalLibrary{Float64}()
        @test getlibraryfor(p, FullDim{2}()) == SimplePolyhedraLibrary{Float64}()
        @test hrepiscomputed(p)
        @test vrepiscomputed(p)
        @test p isa Interval{Float64}
        @test !isempty(p)
        @test volume(p) == 11
        @test dim(p) == 1
        inequality_fulltest(p, h)
        generator_fulltest(p, v)
    end

    pp = polyhedron(h, SimplePolyhedraLibrary{Float64}())
    @test hrepiscomputed(pp)
    @test !vrepiscomputed(pp)
    for p in (Interval{Float64, SVector{1, Float64}}(pp),
              Interval{Float64, SVector{1, Float64}}(Polyhedra.hreps(pp)...))
        @test hrepiscomputed(p)
        @test vrepiscomputed(p)
        @test p isa Interval{Float64}
        @test !isempty(p)
        @test iszero(surface(p))
        @test volume(p) == 11
        @test dim(p) == 1
        generator_fulltest(p, v)
        inequality_fulltest(p, h)
    end

    # Singleton
    h = intersect(HyperPlane([1.], 2))
    v = convexhull([2.])

    v0 = reshape([2., 2., 2.], 3, 1)
    p = polyhedron(vrep(v0))
    @test p isa Interval{Float64}
    @test !isempty(p)
    @test iszero(surface(p))
    @test iszero(volume(p))
    @test dim(p) == 0
    inequality_fulltest(p, h)
    generator_fulltest(p, v)

    p = polyhedron(h)
    @test p isa Interval{Float64}
    @test !isempty(p)
    @test iszero(surface(p))
    @test iszero(volume(p))
    @test dim(p) == 0
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    # Left-open interval
    h = intersect(HalfSpace([1.], 2))
    v = v + conichull([-1.])

    r0 = reshape([-1., -1], 2, 1)
    p = polyhedron(vrep(v0, r0))
    @test p isa Interval{Float64}
    @test !isempty(p)
    @test iszero(surface(p))
    @test dim(p) == 1
    inequality_fulltest(p, h)
    generator_fulltest(p, v)

    p = polyhedron(HalfSpace([1.], 3) ∩ HalfSpace([5.], 10) ∩ HalfSpace([2.], 6))
    @test p isa Interval{Float64}
    @test !isempty(p)
    @test iszero(surface(p))
    @test dim(p) == 1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    # Right-open interval
    h = intersect(HalfSpace([-1.], -2))
    v = convexhull([2.]) + conichull([1.])

    p = polyhedron(convexhull([2.], [3.]) + conichull([1.], [3.]))
    @test p isa Interval{Float64}
    @test !isempty(p)
    @test iszero(surface(p))
    @test dim(p) == 1
    inequality_fulltest(p, h)
    generator_fulltest(p, v)

    p = polyhedron(HalfSpace([-3.], -6) ∩ HalfSpace([-2.], -2))
    p = polyhedron(h)
    @test p isa Interval{Float64}
    @test !isempty(p)
    @test iszero(surface(p))
    @test dim(p) == 1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    # Empty
    h = HyperPlane([0], 1) ∩ HyperPlane([1], 0)
    v = vrep(Line{1, Int, Vector{Int}}[])

    p = polyhedron(v)
    @test p isa Interval{Int}
    @test isempty(p)
    @test iszero(surface(p))
    @test iszero(volume(p))
    @test dim(p) == -1
    inequality_fulltest(p, h)
    generator_fulltest(p, v)

    p = polyhedron(HalfSpace([1], 1) ∩ HalfSpace([-1], -2))
    @test p isa Interval{Int}
    @test isempty(p)
    @test iszero(surface(p))
    @test iszero(volume(p))
    @test dim(p) == -1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    p = polyhedron(intersect(HyperPlane([0], 1)))
    @test p isa Interval{Int}
    @test isempty(p)
    @test iszero(surface(p))
    @test iszero(volume(p))
    @test dim(p) == -1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    p = polyhedron(intersect(HalfSpace([0], -1)))
    @test p isa Interval{Int}
    @test isempty(p)
    @test iszero(surface(p))
    @test iszero(volume(p))
    @test dim(p) == -1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    # Line
    h = hrep(HyperPlane{1, Int, Vector{Int}}[])
    v = conichull(Line([1]))

    p = polyhedron(v)
    @test p isa Interval{Int}
    @test !isempty(p)
    @test iszero(surface(p))
    @test dim(p) == 1
    inequality_fulltest(p, h)
    generator_fulltest(p, v)

    p = polyhedron(h)
    @test p isa Interval{Int}
    @test !isempty(p)
    @test iszero(surface(p))
    @test dim(p) == 1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)

    # Symmetric interval
    h = HalfSpace([1], 1) ∩ HalfSpace([-1], 1)
    v = convexhull([1], [-1])

    p = polyhedron(convexhull([-1], [1], [0]))
    @test p isa Interval{Int}
    @test !isempty(p)
    @test iszero(surface(p))
    @test volume(p) == 2
    @test dim(p) == 1
    inequality_fulltest(p, h)
    generator_fulltest(p, v)

    p = polyhedron(h)
    @test p isa Interval{Int}
    @test !isempty(p)
    @test iszero(surface(p))
    @test volume(p) == 2
    @test dim(p) == 1
    generator_fulltest(p, v)
    inequality_fulltest(p, h)
end