using Distributions

struct StableMeander <: ContinuousUnivariateDistribution
  α::Float64
  β::Float64
  θ::Float64
  ρ::Float64
  StableMeander(α,β) = ( β<-1 || β>1 || (β==-1 && α<=1) || α<=0 || α>2 ) ?
    error("Parameters' requirements unmet: (α,β)∈(0,2]×[-1,1]-(0,1]×{-1}") :
    β==1 && α<=1 ? Stable(α,1) :
    new(α,β,β*(α<=1 ? 1 : (α-2)/α),(1+β*(α<=1 ? 1 : (α-2)/α))/2)
end

import Distributions.params
function params(d::StableMeander)
  return (d.α,d.β,d.θ,d.ρ)
end

import Distributions.minimum
function minimum(d::StableMeander)
  return 0
end

import Distributions.maximum
function maximum(d::StableMeander)
  return Inf
end

import Distributions.insupport
function insupport(d::StableMeander,x::Real)
  return x>=0
end

import Distributions.mean
function mean(d::StableMeander)
  return pi/(sin(pi/d.α) * gamma(d.ρ + 1/d.α) * gamma(1 - d.ρ))
end

struct PreciseStableMeander <: Sampleable{Multivariate,Continuous}
  α::Float64
  β::Float64
  θ::Float64
  ρ::Float64
  d::Float64
  η::Float64
  δ::Float64
  γ::Float64
  κ::Float64
  Δ::Int64
  mAst::Int64
  ε::Float64
  PreciseStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,mAst::Integer,ε::Float64) =
    (β < -1 || β > 1 || (β == -1 && α <= 1) || α <= 0 || α > 2) ?
    error("Parameters' requirements unmet: (α,β)∈(0,2]×[-1,1]-(0,1]×{-1}") : (0 >= γ || γ >= α) ?
    error("Parameters' requirements unmet: α>γ>0") : (0 >= δ || δ >= d || d >= 2/(α + β*(α <= 1 ? α : α-2))) ?
    error("Parameters' requirements unmet: 1/(αρ)>d>δ>0") : κ < 0 ?
    error("Parameters' requirements unmet: κ≥0") : Δ < 0 ?
    error("Parameters' requirements unmet: Δ≥0") : mAst < 0 ?
    error("Parameters' requirements unmet: m*≥0") : ε <= 0 ?
    error("Parameters' requirements unmet: ε>0") : new(α, β, β*(α <= 1 ? 1 : (α-2)/α),(1+β*(α <= 1 ? 1 : (α-2)/α))/2 , d,
      etaF(d*(α + β*(α <= 1 ? α : α-2))/2)*(α + β*(α <= 1 ? α : α-2))/2 , δ, γ, κ + Int(ceil(max(2/(α+β*(α <= 1 ? α : α-2)),
      log(2)*2/(3*etaF(d*(α+β*(α <= 1 ? α : α-2))/2)*(α+β*(α <= 1 ? α : α-2)) ) ))), Δ, mAst, ε)
  PreciseStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,mAst::Integer) = PreciseStableMeander(α,β,d,δ,γ,κ,Δ,mAst,.5^32)
  PreciseStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real,Δ::Integer) = PreciseStableMeander(α,β,d,δ,γ,κ,Δ,12)
  PreciseStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real) = PreciseStableMeander(α,β,d,δ,γ,κ,
    Int(ceil(33*log(2)/log(gamma(1+1/α+(1+β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1+β*(α <= 1 ? 1 : (α-2)/α))/2))))))
  PreciseStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real) = PreciseStableMeander(α,β,d,δ,γ,4)
  PreciseStableMeander(α::Real,β::Real) = PreciseStableMeander(α,β,(2/3)*2/(α+β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α)
  PreciseStableMeander(α::Real,β::Real,Δ::Integer,ε::Real) = PreciseStableMeander(α,β,(2/3)*2/(α+β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,4,Δ,12,ε)
  PreciseStableMeander(α::Real,β::Real,ε::Real) = PreciseStableMeander(α,β,
    Int(ceil(abs(log(ε/2))/log(gamma(1+1/α+(1+β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1+β*(α <= 1 ? 1 : (α-2)/α))/2))))),ε)
end

function precise_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,mAst::Integer,ε::Real)
  return PreciseStableMeander(d.α,d.β,dr,δ,γ,κ,Δ,mAst,ε)
end

function precise_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,mAst::Integer)
  return PreciseStableMeander(d.α,d.β,dr,δ,γ,κ,Δ,mAst)
end

function precise_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real,Δ::Integer)
  return PreciseStableMeander(d.α,d.β,dr,δ,γ,κ,Δ)
end

function precise_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real)
  return PreciseStableMeander(d.α,d.β,dr,δ,γ,κ)
end

function precise_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real)
  return PreciseStableMeander(d.α,d.β,dr,δ,γ)
end

function precise_sampler(d::StableMeander,Δ::Integer,ε::Real)
  return PreciseStableMeander(d.α,d.β,Δ,ε)
end

function precise_sampler(d::StableMeander,ε::Real)
  return PreciseStableMeander(d.α,d.β,ε)
end

function precise_sampler(d::StableMeander)
  return PreciseStableMeander(d.α,d.β)
end

import Base.length
function length(d::PreciseStableMeander)
  return 3
end

using LambertW
# @Input: normalised drift d'=αρd∈(0,1)
# @Returns: normalised η'=η/(αρ)>0
function etaF(d::Real)
  return -lambertw(-d*exp(-d),-1)/d-1
end

# @Input: drift d, η, 1/(αρ+η), and a bound M we want to know if we exceed
# @Returns: a sample of 1{R_0 <= M}
function reflectedProcess(d::Real,et::Real,et1::Real,M::Real)
  T = rand(Exponential())/et
  if T <= M
    return true
  end
  Sn = [0.]
  while Sn[end] < T
    push!(Sn,Sn[end]-rand(Exponential())*et1+d)
  end
  return maximum(Sn[1:(end-1)]) <= M
end

# @Input: drift d, η, 1/(αρ+η), a bound M that it should not exceed,
# and another bound M1 we want to know if we exceed
# @Returns: a sample of 1{R_0 <= M1} given R_0<M
function reflectedProcess(d::Real,et::Real,et1::Real,M::Real,M1::Real)
  m = exp(-M-d)
  Sn = [0.]
  while true
    T = -log(rand(Uniform(m,1)))/et # Exponential conditioned on <= M+d, since T>M+d always yields a rejection
    if T <= M1
      return true
    end
    while Sn[end] < T
      push!(Sn,Sn[end]-rand(Exponential())*et1+d)
    end
    max = maximum(Sn[1:(end-1)])
    if max <= M
      return max <= M1
    else
      Sn = [0.]
    end
  end
end

# @Input: drift d, η, 1/(αρ+η), κ and an upper bound x
# @Returns: A simulated run of the random walk up to the hitting time of the barrier
# -2κ conditioned on never exceeding x
function downRW(d::Real,rar::Real,et::Real,et1::Real,κ::Real,x::Real)
  Sn = [0.]
  INCn = [0.]
  if isfinite(x)
    while true
      while Sn[end] > -2 *κ
        push!(INCn,rand(Exponential())*rar)
        push!(Sn,Sn[end]-INCn[end]+d)
      end
      if maximum(Sn) < x && reflectedProcess(d,et,et1,x-Sn[end])
        return (Sn[2:end],INCn[2:end])
      else
        Sn = [0.]
        INCn = [0.]
      end
    end
  else
    while Sn[end] > -2 *κ
      push!(INCn,rand(Exponential())*rar)
      push!(Sn,Sn[end]-INCn[end]+d)
    end
    return (Sn[2:end],INCn[2:end])
  end
end

# @Input: drift d, η, 1/(αρ+η), κ, an upper bound x that will be attained
# @Returns: A simulated run of the random walk up to the hitting time of the barrier
# κ conditioned on never exceeding x
function upRW(d::Real,et::Real,et1::Real,κ::Real,x::Real)
  Sn = [0.]
  INCn = [0.]
  if x == Inf
    while true
      while Sn[end] < κ
        push!(INCn,rand(Exponential())*et1)
        push!(Sn,Sn[end]-INCn[end]+d)
      end
      if rand(Uniform()) <= exp(-et*Sn[end])
        return (Sn[2:end],INCn[2:end])
      else
        Sn = [0.]
        INCn = [0.]
      end
    end
  else
    while true
      while Sn[end] < κ
        push!(INCn,rand(Exponential())*et1)
        push!(Sn,Sn[end]-INCn[end]+d)
      end
      if rand(Uniform()) <= exp(-et*Sn[end]) && reflectedProcess(d,et,et1,x-Sn[end]) # The condition maximum(Sn) < x1 is trivially satisfied by construction of κ
        return (Sn[2:end],INCn[2:end])
      else
        Sn = [0.]
        INCn = [0.]
      end
    end
  end
end

# Blanchet-Sigman algorithm
# @Input: drift d, η, 1/(αρ+η), κ and an upper bound x, last value of random walk s,
# @Returns: A simulated run of the random walk up to the first time we know an upper bound for the walk,
# the increments of the RW, the new upper bound, and the last time at which we may compute D_n
function BSAlgorithm(d::Real,et::Real,et1::Real,κ::Real,x::Real,s::Real)
  Sn = [Float64(s)]
  INCn = [0.]
  x1 = x
  t = 0

  while true
    # Step 1
    (Sn1,INCn1) = downRW(d,et,et1,κ,x1-Sn[end])
    # Step 2
    append!(Sn,Sn[end] .+ Sn1)
    append!(INCn,INCn1)
    t = t+length(Sn1)
    # Step 3 and 4
    if reflectedProcess(d,et,et1,x1-Sn[end],κ)
      x1 = Sn[end]+κ
      t = t-length(Sn1)
      break
    else
      (Sn1,INCn1) = upRW(d,et,et1,κ,x1-Sn[end])
      append!(Sn,Sn[end] .+ Sn1)
      append!(INCn,INCn1)
      t = t+length(Sn1)
    end
  end

  return (Sn[2:end],INCn[2:end],x1,t)
end

# Algorithm 3 - 1 (unconditional probabilities)
# @Input: S^+(α,ρ), δ, dega = δ*γ, dega2 = -1/(1-exp(-dega)), mom = E(S_1^γ), shift = m-k >= m*-1
function chiStable1(d::PositiveStable,δ::Real,dega::Real,dega2::Real,mom::Real,shift::Integer)
  U = rand(Uniform())
  n = shift
  S = Float64[]
  while true
    n = n+1
    aux = exp(δ*n)
    p = cdf(d,aux) # 1-pn
    aux1 = exp(-(n+1)*dega)*mom
    q = p*exp(dega2*aux1/(1-aux1)) # qn
    if U > p
      push!(S,cond_rand(d,aux,Inf))
      U = (U-p)/(1-p)
    elseif U < q
      push!(S,cond_rand(d,0,aux))
      return S
    else
      push!(S,cond_rand(d,0,aux))
      U = U/p
    end
  end
end

# Algorithm 3 - 2 (conditional probabilities)
# @Input: S^+(α,ρ), δ, dega = δ*γ, dega2 = -1/(1-exp(-dega)), mom = E(S_1^γ)
function chiStable2(d::PositiveStable,δ::Real,dega::Real,dega2::Real,mom::Real,shift::Integer)
  U = rand(Uniform())
  n = shift
  S = Float64[]
  while true
    n = n+1
    aux = exp(δ*n)
    aux0 = exp(δ*(n+1))
    p = cdf(d,aux)/cdf(d,aux0) # 1-pn
    aux1 = exp(-(n+1)*dega)*mom
    q = p*exp(dega2*aux1/(1 -aux1)) # qn
    if U > p
      push!(S,cond_rand(d,aux,aux0))
      U = (U-p)/(1 -p)
    elseif U < q
      push!(S,cond_rand(d,0,aux))
      return S
    else
      push!(S,cond_rand(d,0,aux))
      U = U/p
    end
  end
end

import Distributions.rand

# Algorithm 9
# @Input: PreciseStableMeander
# @Output: A random sample from a the law d, σ
function rand(d::PreciseStableMeander)
  ar = d.α*d.ρ
  et1 = 1 /(ar+d.η)
  ra = 1 /d.α
  rar = 1 /ar
  # Conditionally positive stable distribution
  dPos = PositiveStable(d.α,d.β)
  sep = d.d - d.δ
  e2 = 1 /(1 -exp(-sep))
  mom = mellin(dPos,d.γ)
  dega = d.δ*d.γ
  dega2 = -1 /(1 -exp(-dega))
  mAst = d.mAst + Int(ceil( max(0,log(mom)/dega) + rar ))
  # θ sequence
  U = Float64[]
  # Line 1
  x = Inf
  σ = 0
  # Line 2
  lagU = rand(Beta(d.ρ,1),d.Δ)
  lagS = rand(dPos,d.Δ)
  eps = d.ε / prod(lagU)^ra

  # Line 3 (first iteration)
  # Line 4 (Algorithm 3 with unconditional probabilities)
  S = rand(dPos,mAst)
  append!(S,chiStable1(dPos,d.δ,dega,dega2,mom,mAst-1))

  R = Float64[]
  C = [0.]
  F = Float64[]
  # Last value of C
  endC = 0.
  # First iteration
  # Line 5
  while (length(R) == σ) || (length(S) >= length(C))
    t = length(C)
    # Lines 6 and 7
    (C1,F1) = downRW(d.d,rar,d.η,et1,d.κ,x-endC)
    append!(F,F1)
    append!(C,endC .+ C1)
    endC = C[end]
    # Lines 8 and 9
    if reflectedProcess(d.d,d.η,et1,d.κ,x-endC)
      # Line 10
      x = endC + d.κ
      R1 = [maximum(C[t:end])]
      for i = (t-1):-1:(length(R)+1)
        push!(R1,max(R1[end],C[i]))
      end
      append!(R,R1[end:-1:1] .- C[(length(R)+1):t])
    else # Line 11
      # Lines 12 and 13
      (C1,F1) = upRW(d.d,d.η,et1,d.κ,x-endC)
      append!(F,F1)
      append!(C,endC .+ C1)
      endC = C[end]
    end # Line 14
  end # Line 15
  # Line 16
  while length(U) < length(S)
    push!(U,exp(-d.α*F[length(U)+1]))
  end
  # Lines 17, 18 and 19
  σ = 1
  D = exp(R[σ])*( e2*exp(-sep*(length(S)-σ-1)) + sum(S[σ:end] .*
    exp.(-d.d .* (0:(length(S)-σ))) .* (1 .- U[σ:end]) .^ ra) )
  eps /= U[σ]^ra
  # Line 20
  if D <= eps # Finished!
    # Line 21
    D = (prod(lagU)*U[1])^ra * D
    X = (1-U[1])^ra * S[1]
    for i = d.Δ:-1:1
      X = lagU[i]^ra * X + (1-lagU[i])^ra * lagS[i]
    end
    return (X,X+D,σ)
  end # Line 22
  # Line 3 (second to last iterations)
  while true
    # Line 4 (Algorithm 3 with conditional probabilities)
    append!(S,chiStable2(dPos,d.δ,dega,dega2,mom,length(S)-σ-1))
    # Line 5
    while (length(R) == σ) || (length(S) >= length(C))
      t = length(C)-1
      # Lines 6 and 7
      (C1,F1) = downRW(d.d,rar,d.η,et1,d.κ,x-endC)
      append!(F,F1)
      append!(C,endC .+ C1)
      endC = C[end]
      # Lines 8 and 9
      if reflectedProcess(d.d,d.η,et1,d.κ,x-endC)
        # Line 10
        x = endC + d.κ
        R1 = [maximum(C[t:end])]
        for i = (t-1):-1:(length(R)+1)
          push!(R1,max(R1[end],C[i]))
        end
        append!(R,R1[end:-1:1] .- C[(length(R)+1):t])
      else # Line 11
        # Lines 12 and 13
        (C1,F1) = upRW(d.d,d.η,et1,d.κ,x-endC)
        append!(F,F1)
        append!(C,endC .+ C1)
        endC = C[end]
      end # Line 14
    end # Line 15
    # Line 16
    while length(U) < length(S)
      push!(U,exp(-d.α*F[length(U)+1]))
    end
    # Lines 17, 18 and 19
    σ += 1
    D = exp(R[σ])*( e2*exp(-sep*(length(S)-σ-1)) + sum(S[σ:end] .*
      exp.(-d.d .* (0:(length(S)-σ))) .* (1 .- U[σ:end]) .^ ra) )
    eps /= U[σ]^ra
    # Line 24
    if D <= eps # Finished!
      D = (prod(U[1:σ])*prod(lagU))^ra * D
      X = 0.
      for i = σ:-1:1
        X = U[i]^ra * X + (1-U[i])^ra * S[i]
      end
      for i = d.Δ:-1:1
        X = lagU[i]^ra * X + (1-lagU[i])^ra * lagS[i]
      end
      # Line 25
      return (X,X+D,σ)
    end # Line 26
  end # Line 27
end

# @Input: StableMeander
# @Output: A random sample (lower estimate) from the law d
function rand(d::StableMeander)
  return rand(precise_sampler(d))[1]
end

function rand(d::StableMeander,N::Integer)
  return [rand(d) for i =1:N]
end

function length(d::PreciseStableMeander)
  return 3
end

# Distribution created to sample ε-strongly and localise the function according
# to the partition induced by a discrete function f.
struct LocStableMeander <: Distribution{Multivariate,Continuous}
  α::Float64
  β::Float64
  θ::Float64
  ρ::Float64
  d::Float64
  η::Float64
  δ::Float64
  γ::Float64
  κ::Float64
  Δ::Int64
  mAst::Int64
  f::Function
  LocStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,mAst::Integer,f::Function) =
    (β < -1 || β > 1 || (β == -1 && α <= 1) || α <= 0 || α > 2) ?
    error("Parameters' requirements unmet: (α,β)∈(0,2]×[-1,1]-(0,1]×{-1}") : (0 >= γ || γ >= α) ?
    error("Parameters' requirements unmet: α>γ>0") : (0 >= δ || δ >= d || d >= 2/(α + β*(α <= 1 ? α : α-2))) ?
    error("Parameters' requirements unmet: 1/(αρ)>d>δ>0") : κ < 0 ?
    error("Parameters' requirements unmet: κ≥0") : Δ <= 0 ?
    error("Parameters' requirements unmet: Δ≥0") : mAst < 0 ?
    error("Parameters' requirements unmet: m*≥0") : new(α, β, β*(α <= 1 ? 1 : (α-2)/α),(1+β*(α <= 1 ? 1 : (α-2)/α))/2 , d,
      etaF(d*(α + β*(α <= 1 ? α : α-2))/2)*(α + β*(α <= 1 ? α : α-2))/2 , δ, γ, κ + Int(ceil(max(2/(α+β*(α <= 1 ? α : α-2)),
      log(2)*2/(3*etaF(d*(α+β*(α <= 1 ? α : α-2))/2)*(α+β*(α <= 1 ? α : α-2)) ) ))), Δ, mAst, f)
  LocStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,f::Function) = LocStableMeander(α,β,d,δ,γ,κ,Δ,12,f)
  LocStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,κ::Real,f::Function) = LocStableMeander(α,β,d,δ,γ,κ,
    Int(ceil(33*log(2)/log(gamma(1+1/α+(1+β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1+β*(α <= 1 ? 1 : (α-2)/α))/2))))),f)
  LocStableMeander(α::Real,β::Real,d::Real,δ::Real,γ::Real,f::Function) = LocStableMeander(α,β,d,δ,γ,4,f)
  LocStableMeander(α::Real,β::Real,Δ::Integer,f::Function) = LocStableMeander(α,β,(2/3)*2/(α+β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,4,Δ,f)
  LocStableMeander(α::Real,β::Real,f::Function) = LocStableMeander(α,β,(2/3)*2/(α+β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,f)
end

function local_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,mAst::Integer,f::Function)
  return LocStableMeander(d.α,d.β,dr,δ,γ,κ,Δ,mAst,f)
end

function local_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real,Δ::Integer,f::Function)
  return LocStableMeander(d.α,d.β,dr,δ,γ,κ,Δ,f)
end

function local_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,κ::Real,f::Function)
  return LocStableMeander(d.α,d.β,dr,δ,γ,κ,f)
end

function local_sampler(d::StableMeander,dr::Real,δ::Real,γ::Real,f::Function)
  return LocStableMeander(d.α,d.β,dr,δ,γ,f)
end

function local_sampler(d::StableMeander,Δ::Integer,f::Function)
  return LocStableMeander(d.α,d.β,Δ,f)
end

function local_sampler(d::StableMeander,f::Function)
  return LocStableMeander(d.α,d.β,f)
end

# Algorithm 9
# @Input: LocStableMeander
# @Output: A random sample from (f(StableMeander),UE,LE),
# where UE and LE are the upper and lower estimates of StableMeander.
function rand(d::LocStableMeander)
  ar = d.α*d.ρ
  et1 = 1 /(ar+d.η)
  ra = 1 /d.α
  rar = 1 /ar
  # Conditionally positive stable distribution
  dPos = PositiveStable(d.α,d.β)
  sep = d.d - d.δ
  e2 = 1 /(1 -exp(-sep))
  mom = mellin(dPos,d.γ)
  dega = d.δ*d.γ
  dega2 = -1 /(1 -exp(-dega))
  mAst = d.mAst + Int(ceil( max(0,log(mom)/dega) + rar ))
  # θ sequence
  U = Float64[]
  # Line 1
  x = Inf
  σ = 0
  # Line 2
  lagU = rand(Beta(d.ρ,1),d.Δ)
  lagS = rand(dPos,d.Δ)
  eps = 1 / prod(lagU)^ra

  # Line 3 (first iteration)
  # Line 4 (Algorithm 3 with unconditional probabilities)
  S = rand(dPos,mAst)
  append!(S,chiStable1(dPos,d.δ,dega,dega2,mom,mAst-1))

  R = Float64[]
  C = [0.]
  F = Float64[]
  # Last value of C
  endC = 0.
  # First iteration
  # Line 5
  while (length(R) == σ) || (length(S) >= length(C))
    t = length(C)
    # Lines 6 and 7
    (C1,F1) = downRW(d.d,rar,d.η,et1,d.κ,x-endC)
    append!(F,F1)
    append!(C,endC .+ C1)
    endC = C[end]
    # Lines 8 and 9
    if reflectedProcess(d.d,d.η,et1,d.κ,x-endC)
      # Line 10
      x = endC + d.κ
      R1 = [maximum(C[t:end])]
      for i = (t-1):-1:(length(R)+1)
        push!(R1,max(R1[end],C[i]))
      end
      append!(R,R1[end:-1:1] .- C[(length(R)+1):t])
    else # Line 11
      # Lines 12 and 13
      (C1,F1) = upRW(d.d,d.η,et1,d.κ,x-endC)
      append!(F,F1)
      append!(C,endC .+ C1)
      endC = C[end]
    end # Line 14
  end # Line 15
  # Line 16
  while length(U) < length(S)
    push!(U,exp(-d.α*F[length(U)+1]))
  end
  # Lines 17, 18 and 19
  σ = 1
  D = exp(R[σ])*( e2*exp(-sep*(length(S)-σ-1)) + sum(S[σ:end] .*
    exp.(-d.d .* (0:(length(S)-σ))) .* (1 .- U[σ:end]) .^ ra) )
  eps /= U[σ]^ra
  # Line 20
  # Line 21
  X = (1-U[1])^ra * S[1]
  for i = d.Δ:-1:1
    X = lagU[i]^ra * X + (1-lagU[i])^ra * lagS[i]
  end
  aux = d.f(X)
  if aux == d.f(X+D/eps)
    return (aux,X,X+D/eps,σ)
  end # Line 22
  # Line 3 (second to last iterations)
  while true
    # Line 4 (Algorithm 3 with conditional probabilities)
    append!(S,chiStable2(dPos,d.δ,dega,dega2,mom,length(S)-σ-1))
    # Line 5
    while (length(R) == σ) || (length(S) >= length(C))
      t = length(C)-1
      # Lines 6 and 7
      (C1,F1) = downRW(d.d,rar,d.η,et1,d.κ,x-endC)
      append!(F,F1)
      append!(C,endC .+ C1)
      endC = C[end]
      # Lines 8 and 9
      if reflectedProcess(d.d,d.η,et1,d.κ,x-endC)
        # Line 10
        x = endC + d.κ
        R1 = [maximum(C[t:end])]
        for i = (t-1):-1:(length(R)+1)
          push!(R1,max(R1[end],C[i]))
        end
        append!(R,R1[end:-1:1] .- C[(length(R)+1):t])
      else # Line 11
        # Lines 12 and 13
        (C1,F1) = upRW(d.d,d.η,et1,d.κ,x-endC)
        append!(F,F1)
        append!(C,endC .+ C1)
        endC = C[end]
      end # Line 14
    end # Line 15
    # Line 16
    while length(U) < length(S)
      push!(U,exp(-d.α*F[length(U)+1]))
    end
    # Lines 17, 18 and 19
    σ += 1
    D = exp(R[σ])*( e2*exp(-sep*(length(S)-σ-1)) + sum(S[σ:end] .*
      exp.(-d.d .* (0:(length(S)-σ))) .* (1 .- U[σ:end]) .^ ra) )
    X += (1-U[σ])^ra * S[σ] / eps
    eps /= U[σ]^ra
    # Line 24
    aux = d.f(X)
    if aux == d.f(X+D/eps)
      return (aux,X,X+D/eps,σ)
    end # Line 26
  end # Line 27
end

function length(d::LocStableMeander)
  return 4
end

# Finite-dimensional stable meander

struct MvStableMeander <: ContinuousMultivariateDistribution
  α::Float64
  β::Float64
  θ::Float64
  ρ::Float64
  t::Array{Float64,1}
  MvStableMeander(α,β,t) = ( β<-1 || β>1 || (β==-1 && α<=1) || α<=0 || α>2 ) ?
    error("Parameters' requirements unmet: (α,β)∈(0,2]×[-1,1]-(0,1]×{-1}") :
    !( 0 < t[1] && prod([ t[i-1] < t[i] for i = 2:length(t)]) ) ? error("Parameters' requirements unmet: 0 < t[1] < ... < t[end]") :
    new(α,β,β*(α<=1 ? 1 : (α-2)/α),(1+β*(α<=1 ? 1 : (α-2)/α))/2,t)
end

import Distributions.params
function params(d::MvStableMeander)
  return (d.α,d.β,d.θ,d.ρ,d.t)
end

import Distributions.insupport
function insupport(d::MvStableMeander,x::Array{Float64,1})
  return prod(x .>= 0)
end

import Base.length
function length(d::MvStableMeander)
  return length(d.t)
end

import Distributions.rand
function rand(d::MvStableMeander)
  if d.α <= 1 && d.β == 1
    X = rand(Stable(d.α,1),length(d.t))
    X[1] *= d.t[1]^(1/d.α)
    for i=2:length(d.t)
      X[i] *= (d.t[i]-d.t[i-1])^(1/d.α)
    end
    return cumsum(X)
  else
    return rand(precise_sampler(d))[1]
  end
end

# Does not really support α≤1 and β=1 (produces an exact MvStableMeander)
struct PreciseMvStableMeander <: Sampleable{Multivariate,Continuous}
  α::Float64
  β::Float64
  θ::Float64
  ρ::Float64
  t::Array{Float64,1}
  # For the rest of the parameters, we have left/right versions
  d_l::Float64
  d_r::Float64
  η_l::Float64
  η_r::Float64
  δ_l::Float64
  δ_r::Float64
  γ_l::Float64
  γ_r::Float64
  κ_l::Float64
  κ_r::Float64
  Δ_l::Int64
  Δ_r::Int64
  mAst_l::Int64
  mAst_r::Int64
  ε::Float64
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer,ε::Real) =
    ( β<-1 || β>1 || (β==-1 && α<=1) || α<=0 || α>2 ) ?
    error("Parameters' requirements unmet: (α,β)∈(0,2]×[-1,1]-(0,1]×{-1}") :
    (β == 1 && α <= 1) ? MvStableMeander(α,1,t) :
    (0 >= γ_l || γ_l >= α || 0 >= γ_r || γ_r >= α) ?
    error("Parameters' requirements unmet: α>γ_l>0 and α>γ_r>0") : (0 >= δ_l || δ_l >= d_l || 0 >= δ_r || δ_r >= d_r || d_l >= 2/(α - β*(α <= 1 ? α : α-2)) || d_r >= 2/(α + β*(α <= 1 ? α : α-2))) ?
    error("Parameters' requirements unmet: 1/(α(1-ρ))>d_l>δ_l>0 and 1/(αρ)>dr>δr>0") : (κ_l < 0 || κ_r < 0) ?
    error("Parameters' requirements unmet: κ_l,κ_r≥0") : (Δ_l <= 0 || Δ_r <= 0) ?
    error("Parameters' requirements unmet: Δ_l,Δr≥0") : (mAst_l < 0 || mAst_r < 0) ?
    error("Parameters' requirements unmet: m_l*,m_r*≥0") : ε <= 0 ?
    error("Parameters' requirements unmet: ε>0") : !( 0 < t[1] && prod([ t[i-1] < t[i] for i = 2:length(t)]) ) ?
    error("Parameters' requirements unmet: 0 < t[1] < ... < t[end]") :
    new(α, β, β*(α <= 1 ? 1 : (α-2)/α), (1+β*(α <= 1 ? 1 : (α-2)/α))/2, t, d_l, d_r,
      etaF(d_l*(α - β*(α <= 1 ? α : α-2))/2)*(α - β*(α <= 1 ? α : α-2))/2, etaF(d_r*(α + β*(α <= 1 ? α : α-2))/2)*(α + β*(α <= 1 ? α : α-2))/2,
      δ_l, δ_r, γ_l, γ_r,
      κ_l + Int(ceil(max(2/(α-β*(α <= 1 ? α : α-2)),log(2)*2/(3*etaF(d_l*(α-β*(α <= 1 ? α : α-2))/2)*(α-β*(α <= 1 ? α : α-2)) ) ))),
      κ_r + Int(ceil(max(2/(α+β*(α <= 1 ? α : α-2)),log(2)*2/(3*etaF(d_r*(α+β*(α <= 1 ? α : α-2))/2)*(α+β*(α <= 1 ? α : α-2)) ) ))),
      Δ_l, Δ_r, mAst_l, mAst_r, ε)
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer) = PreciseMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,mAst_l,mAst_r,.5^16)
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer) = PreciseMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,12,12)
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real) = PreciseMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,
    Int(ceil(17*log(2)/log(gamma(1+1/α+(1-β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1-β*(α <= 1 ? 1 : (α-2)/α))/2))))),
    Int(ceil(17*log(2)/log(gamma(1+1/α+(1+β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1+β*(α <= 1 ? 1 : (α-2)/α))/2))))))
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real) = PreciseMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,4,4)
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1}) = PreciseMvStableMeander(α,β,t,
    (2/3)*2/(α-β*(α <= 1 ? α : α-2)),(2/3)*2/(α+β*(α <= 1 ? α : α-2)),
    (1/3)*2/(α-β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,.95*α)
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},Δ_l::Integer,Δ_r::Integer,ε::Real) = PreciseMvStableMeander(α,β,t,
    (2/3)*2/(α-β*(α <= 1 ? α : α-2)),(2/3)*2/(α+β*(α <= 1 ? α : α-2)),
    (1/3)*2/(α-β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,.95*α,4,4,Δ_l,Δ_r,12,12,ε)
  PreciseMvStableMeander(α::Real,β::Real,t::Array{Real,1},ε::Real) = PreciseMvStableMeander(α,β,t,
    Int(ceil(abs(log(ε/2))/log(gamma(1+1/α+(1-β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1-β*(α <= 1 ? 1 : (α-2)/α))/2))))),
    Int(ceil(abs(log(ε/2))/log(gamma(1+1/α+(1+β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1+β*(α <= 1 ? 1 : (α-2)/α))/2))))),ε)
end

function precise_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer,ε::Real)
  return PreciseMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,mAst_l,mAst_r,ε)
end

function precise_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer)
  return PreciseMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,mAst_l,mAst_r)
end

function precise_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer)
  return PreciseMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r)
end

function precise_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real)
  return PreciseMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r)
end

function precise_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real)
  return PreciseMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r)
end

function precise_sampler(d::MvStableMeander,Δ_l::Integer,Δ_r::Integer,ε::Real)
  return PreciseMvStableMeander(d.α,d.β,d.t,Δ_l,Δ_r,ε)
end

function precise_sampler(d::MvStableMeander,ε::Real)
  return PreciseMvStableMeander(d.α,d.β,d.t,ε)
end

function precise_sampler(d::MvStableMeander)
  return PreciseMvStableMeander(d.α,d.β,d.t)
end

import Base.length
function length(d::PreciseMvStableMeander)
  return Int(2*length(d.t) + 1)
end

function rand(d::PreciseMvStableMeander)
  ra = 1 /d.α

  if length(d.t) == 1
    return rand(PreciseStableMeander(d.α,d.β,d.d_r,δ_r,γ_r,κ_r,Δ_r,mAst_r,ε)) * t[1]^ra
  end

  m_r = length(d.t)
  m_l = Int(m_r-1)

  while true
    τ = rand(Beta(1-d.ρ,d.ρ),m_l)
    T_l = [d.t[i]-d.t[i-1] for i = 2:length(d.t)] .* τ
    T_r = [dt[1]]
    append!(T_r, [d.t[i]-d.t[i-1] for i = 2:length(d.t)] .* (1 .- τ))

    T_l = T_l .^ ra
    T_r = T_r .^ ra

    ar_l, ar_r = d.α*(1-d.ρ), d.α*d.ρ
    et1_l, et1_r = 1/(ar_l+d.η_l), 1/(ar_r+d.η_r)
    rar_l, rar_r = 1/ar_l, 1/ar_r

    # Conditionally positive stable distribution
    dPos_l, dPos_r = PositiveStable(d.α,-d.β), PositiveStable(d.α,d.β)
    sep_l, sep_r = d.d_l - d.δ_l, d.d_r - d.δ_r
    e2_l, e2_r = 1 /(1 -exp(-sep_l)), 1 /(1 -exp(-sep_r))
    mom_l, mom_r = mellin(dPos_l,d.γ_l), mellin(dPos_r,d.γ_r)
    dega_l, dega_r = d.δ_l*d.γ_l, d.δ_r*d.γ_r
    dega2_l, dega2_r = -1 /(1 -exp(-dega_l)), -1 /(1 -exp(-dega_r))
    mAst_l, mAst_r = d.mAst_l + Int(ceil( max(0,log(mom_l)/dega_l) + rar_l )), d.mAst_r + Int(ceil( max(0,log(mom_r)/dega_r) + rar_r ))
    # θ sequence
    U_l, U_r = [Float64[] for i = 1:m_l], [Float64[] for i = 1:m_r]
    # Line 1
    x_l, x_r = [Inf for i = 1:m_l], [Inf for i = 1:m_r]
    σ = 0
    # Line 2
    lagU_l, lagU_r = [rand(Beta(1-d.ρ,1),d.Δ_l) for i = 1:m_l], [rand(Beta(d.ρ,1),d.Δ_r) for i = 1:m_r]
    lagS_l, lagS_r = [rand(dPos_l,d.Δ_l) for i = 1:m_l], [rand(dPos_r,d.Δ_r) for i = 1:m_r]
    prod_l, prod_r = [prod(lagU_l[i]) for i = 1:m_l], [prod(lagU_r[i]) for i = 1:m_r]
    # Line 3 (first iteration)
    # Line 4 (Algorithm 3 with unconditional probabilities)
    S_l, S_r = [rand(dPos_l,mAst_l) for i = 1:m_l], [rand(dPos_r,mAst_r) for i = 1:m_r]
    for i = 1:m_l
      append!(S_l[i],chiStable1(dPos_l,d.δ_l,dega_l,dega2_l,mom_l,mAst_l-1))
    end
    for i = 1:m_r
      append!(S_r[i],chiStable1(dPos_r,d.δ_r,dega_r,dega2_r,mom_r,mAst_r-1))
    end

    R_l, R_r = [Float64[] for i = 1:m_l], [Float64[] for i = 1:m_r]
    C_l, C_r = [[0.] for i = 1:m_l], [[0.] for i = 1:m_r]
    F_l, F_r = [Float64[] for i = 1:m_l], [Float64[] for i = 1:m_r]
    # Last value of C
    endC_l, endC_r = [0. for i = 1:m_l], [0. for i = 1:m_r]
    # First iteration
    # Line 5
    for i = 1:m_l
      while (length(R_l[i]) == σ) || (length(S_l[i]) >= length(C_l[i]))
        t_l = length(C_l[i])
        # Lines 6 and 7
        (C1_l[i],F1_l[i]) = downRW(d.d_l,rar_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
        append!(F_l[i],F1_l[i])
        append!(C_l[i],endC_l[i] .+ C1_l[i])
        endC_l[i] = C_l[i][end]
        # Lines 8 and 9
        if reflectedProcess(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
          # Line 10
          x_l[i] = endC_l[i] + d.κ_l
          R1_l = [maximum(C_l[i][t_l:end])]
          for j = (t_l-1):-1:(length(R_l[i])+1)
            push!(R1_l,max(R1_l[end],C_l[i][j]))
          end
          append!(R_l,R1_l[end:-1:1] .- C_l[i][(length(R_l[i])+1):t_l])
        else # Line 11
          # Lines 12 and 13
          (C1_l,F1_l) = upRW(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
          append!(F_l[i],F1_l)
          append!(C_l[i],endC_l .+ C1_l)
          endC_l[i] = C_l[i][end]
        end # Line 14
      end # Line 15 - left
    end
    for i = 1:m_r
      while (length(R_r[i]) == σ) || (length(S_r[i]) >= length(C_r[i]))
        t_r = length(C_r[i])
        # Lines 6 and 7
        (C1_r,F1_r) = downRW(d.d_r,rar_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
        append!(F_r[i],F1_r)
        append!(C_r[i],endC_r[i] .+ C1_r)
        endC_r[i] = C_r[i][end]
        # Lines 8 and 9
        if reflectedProcess(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
          # Line 10
          x_r[i] = endC_r[i] + d.κ_r
          R1_r = [maximum(C_r[i][t_r:end])]
          for j = (t_r-1):-1:(length(R_r[i])+1)
            push!(R1_r,max(R1_r[end],C_r[i][j]))
          end
          append!(R_r[i],R1_r[end:-1:1] .- C_r[i][(length(R_r[i])+1):t_r])
        else # Line 11
          # Lines 12 and 13
          (C1_r,F1_r) = upRW(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
          append!(F_r[i],F1_r)
          append!(C_r[i],endC_r[i] .+ C1_r)
          endC_r[i] = C_r[i][end]
        end # Line 14
      end # Line 15 - right
    end
    # Line 16
    for i = 1:m_l
      while length(U_l[i]) < length(S_l[i])
        push!(U_l[i],exp(-d.α*F_l[i][length(U_l[i])+1]))
      end
    end
    for i = 1:m_r
      while length(U_r[i]) < length(S_r[i])
        push!(U_r[i],exp(-d.α*F_r[i][length(U_r[i])+1]))
      end
    end
    # Lines 17, 18 and 19
    σ = 1
    D_l = [exp(R_l[i][σ])*( e2_l*exp(-sep_l*(length(S_l[i])-σ-1)) + sum(S_l[i][σ:end] .*
        exp.(-d.d_l .* (0:(length(S_l[i])-σ))) .* (1 .- U_l[i][σ:end]) .^ ra) ) for i = 1:m_l]
    D_r = [exp(R_r[i][σ])*( e2_r*exp(-sep_r*(length(S_r[i])-σ-1)) + sum(S_r[i][σ:end] .*
        exp.(-d.d_r .* (0:(length(S_r[i])-σ))) .* (1 .- U_r[i][σ:end]) .^ ra) ) for i = 1:m_r]

    for i = 1:m_l
      push!(lagS_l[i],S_l[i][σ])
      push!(lagU_l[i],U_l[i][σ])
    end
    for i = 1:m_r
      push!(lagS_r[i],S_r[i][σ])
      push!(lagU_r[i],U_r[i][σ])
    end
    # Line 20
    # Line 21
    L_l = [(prod_l[i]*U_l[i][1])^ra for i = 1:m_l]
    L_r = [(prod_r[i]*U_r[i][1])^ra for i = 1:m_r]

    X_l = [(1-U_l[i][1])^ra * S_l[i][1] for i = 1:m_l]
    X_r = [(1-U_r[i][1])^ra * S_r[i][1] for i = 1:m_r]

    for i = 1:m_l
      for j = d.Δ_l:-1:1
        X_l[i] = lagU_l[i][j]^ra * X_l[i] + (1-lagU_l[i][j])^ra * lagS_l[i][j]
      end
    end
    for i = 1:m_r
      for j = d.Δ_r:-1:1
        X_r[i] = lagU_r[i][j]^ra * X_r[i] + (1-lagU_r[i][j])^ra * lagS_r[i][j]
      end
    end

    CX_lu = [0.]
    append!(CX_lu, cumsum(X_l .* T_l))
    CX_ld = [0.]
    append!(CX_ld, cumsum((X_l .+ D_l .* L_l) .* T_l))
    CX_ru = cumsum((X_r .+ D_r .* L_r) .* T_r)
    CX_rd = cumsum(X_r .* T_r)
    X_u = CX_ru .- CX_lu
    X_d = CX_rd .- CX_ld

    if !(prod(X_u .> 0))
      continue
    elseif X_u[end] - X_d[end] < d.ε && prod(X_d .> 0)# Precision reached!
      return (X_d,X_u,σ)
    end # Line 22
    # Line 3 (second to last iterations)
    while true
      # Line 4 (Algorithm 3 with conditional probabilities)
      for i = 1:m_l
        append!(S_l[i],chiStable2(dPos_l,d.δ_l,dega_l,dega2_l,mom_l,length(S_l[i])-σ-1))
      end
      for i = 1:m_r
        append!(S_r[i],chiStable2(dPos_r,d.δ_r,dega_r,dega2_r,mom_r,length(S_r[i])-σ-1))
      end
      # Line 5
      for i = 1:m_l
        while (length(R_l[i]) == σ) || (length(S_l[i]) >= length(C_l[i]))
          t_l = length(C_l[i])-1
          # Lines 6 and 7
          (C1_l,F1_l) = downRW(d.d_l,rar_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
          append!(F_l[i],F1_l)
          append!(C_l[i],endC_l[i] .+ C1_l)
          endC_l[i] = C_l[i][end]
          # Lines 8 and 9
          if reflectedProcess(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
            # Line 10
            x_l[i] = endC_l[i] + d.κ_l
            R1_l = [maximum(C_l[i][t_l:end])]
            for j = (t_l-1):-1:(length(R_l[i])+1)
              push!(R1_l,max(R1_l[end],C_l[i][j]))
            end
            append!(R_l[i],R1_l[end:-1:1] .- C_l[i][(length(R_l[i])+1):t_l])
          else # Line 11
            # Lines 12 and 13
            (C1_l,F1_l) = upRW(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
            append!(F_l[i],F1_l)
            append!(C_l[i],endC_l .+ C1_l)
            endC_l[i] = C_l[i][end]
          end # Line 14
        end # Line 15
      end
      for i = 1:m_r
        while (length(R_r[i]) == σ) || (length(S_r[i]) >= length(C_r[i]))
          t_r = length(C_r[i])-1
          # Lines 6 and 7
          (C1_r,F1_r) = downRW(d.d_r,rar_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
          append!(F_r[i],F1_r)
          append!(C_r[i],endC_r[i] .+ C1_r)
          endC_r[i] = C_r[i][end]
          # Lines 8 and 9
          if reflectedProcess(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
            # Line 10
            x_r[i] = endC_r[i] + d.κ_r
            R1_r = [maximum(C_r[t_r:end])]
            for j = (t_r-1):-1:(length(R_r[i])+1)
              push!(R1_r,max(R1_r[end],C_r[i][j]))
            end
            append!(R_r[i],R1_r[end:-1:1] .- C_r[i][(length(R_r[i])+1):t_r])
          else # Line 11
            # Lines 12 and 13
            (C1_r,F1_r) = upRW(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
            append!(F_r[i],F1_r)
            append!(C_r[i],endC_r[i] .+ C1_r)
            endC_r[i] = C_r[i][end]
          end # Line 14
        end # Line 15
      end
      # Line 16
      for i = 1:m_l
        while length(U_l[i]) < length(S_l[i])
          push!(U_l[i],exp(-d.α*F_l[i][length(U_l[i])+1]))
        end
      end
      for i = 1:m_r
        while length(U_r[i]) < length(S_r[i])
          push!(U_r[i],exp(-d.α*F_r[i][length(U_r[i])+1]))
        end
      end
      # Lines 17, 18 and 19
      σ += 1
      D_l = [exp(R_l[i][σ])*( e2_l*exp(-sep_l*(length(S_l[i])-σ-1)) + sum(S_l[i][σ:end] .*
          exp.(-d.d_l .* (0:(length(S_l[i])-σ))) .* (1 .- U_l[i][σ:end]) .^ ra) ) for i = 1:m_l]
      D_r = [exp(R_r[σ])*( e2_r*exp(-sep_r*(length(S_r[i])-σ-1)) + sum(S_r[i][σ:end] .*
          exp.(-d.d_r .* (0:(length(S_r[i])-σ))) .* (1 .- U_r[i][σ:end]) .^ ra) ) for i = 1:m_r]
      for i = 1:m_l
        push!(lagS_l[i],S_l[i][σ])
        push!(lagU_l[i],U_l[i][σ])
      end
      for i = 1:m_r
        push!(lagS_r[i],S_r[i][σ])
        push!(lagU_r[i],U_r[i][σ])
      end
      # Line 24
      X_l .+= [L_l[i]*(1-U_l[i][σ])^ra * S_l[i][σ] for i = 1:m_l]
      X_r .+= [L_r[i]*(1-U_r[i][σ])^ra * S_r[i][σ] for i = 1:m_r]

      L_l .*= [U_l[i][σ]^ra for i = 1:m_l]
      L_r .*= [U_r[i][σ]^ra for i = 1:m_r]

      CX_lu = [0.]
      append!(CX_lu, cumsum(X_l .* T_l))
      CX_ld = [0.]
      append!(CX_ld, cumsum((X_l .+ D_l .* L_l) .* T_l))
      CX_ru = cumsum((X_r .+ D_r .* L_r) .* T_r)
      CX_rd = cumsum(X_r .* T_r)
      X_u = CX_ru .- CX_lu
      X_d = CX_rd .- CX_ld

      if !(prod(X_u .> 0))
        continue
      elseif X_u[end] - X_d[end] < d.ε && prod(X_d .> 0)# Precision reached!
        return (X_d,X_u,σ)
      end # Line 26
    end # Line 27
  end
end

struct LocMvStableMeander <: Sampleable{Multivariate,Continuous}
  α::Float64
  β::Float64
  θ::Float64
  ρ::Float64
  t::Array{Float64,1}
  # For the rest of the parameters, we have left/right versions
  d_l::Float64
  d_r::Float64
  η_l::Float64
  η_r::Float64
  δ_l::Float64
  δ_r::Float64
  γ_l::Float64
  γ_r::Float64
  κ_l::Float64
  κ_r::Float64
  Δ_l::Int64
  Δ_r::Int64
  mAst_l::Int64
  mAst_r::Int64
  f::Function
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer,f::Function) =
    ( β<-1 || β>1 || (β==-1 && α<=1) || α<=0 || α>2 ) ?
    error("Parameters' requirements unmet: (α,β)∈(0,2]×[-1,1]-(0,1]×{-1}") :
    #(β == 1 && α <= 1) ? MvStableMeander(α,1,t) :
    (0 >= γ_l || γ_l >= α || 0 >= γ_r || γ_r >= α) ?
    error("Parameters' requirements unmet: α>γ_l>0 and α>γ_r>0") : (0 >= δ_l || δ_l >= d_l || 0 >= δ_r || δ_r >= d_r || d_l >= 2/(α - β*(α <= 1 ? α : α-2)) || d_r >= 2/(α + β*(α <= 1 ? α : α-2))) ?
    error("Parameters' requirements unmet: 1/(α(1-ρ))>d_l>δ_l>0 and 1/(αρ)>dr>δr>0") : (κ_l < 0 || κ_r < 0) ?
    error("Parameters' requirements unmet: κ_l,κ_r≥0") : (Δ_l <= 0 || Δ_r <= 0) ?
    error("Parameters' requirements unmet: Δ_l,Δr≥0") : (mAst_l < 0 || mAst_r < 0) ?
    error("Parameters' requirements unmet: m_l*,m_r*≥0") : !( 0 < t[1] && prod([ t[i-1] < t[i] for i = 2:length(t)]) ) ?
    error("Parameters' requirements unmet: 0 < t[1] < ... < t[end]") :
    new(α, β, β*(α <= 1 ? 1 : (α-2)/α), (1+β*(α <= 1 ? 1 : (α-2)/α))/2, t, d_l, d_r,
      etaF(d_l*(α - β*(α <= 1 ? α : α-2))/2)*(α - β*(α <= 1 ? α : α-2))/2, etaF(d_r*(α + β*(α <= 1 ? α : α-2))/2)*(α + β*(α <= 1 ? α : α-2))/2,
      δ_l, δ_r, γ_l, γ_r,
      κ_l + Int(ceil(max(2/(α-β*(α <= 1 ? α : α-2)),log(2)*2/(3*etaF(d_l*(α-β*(α <= 1 ? α : α-2))/2)*(α-β*(α <= 1 ? α : α-2)) ) ))),
      κ_r + Int(ceil(max(2/(α+β*(α <= 1 ? α : α-2)),log(2)*2/(3*etaF(d_r*(α+β*(α <= 1 ? α : α-2))/2)*(α+β*(α <= 1 ? α : α-2)) ) ))),
      Δ_l, Δ_r, mAst_l, mAst_r, f)
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer,f::Function) = LocMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,mAst_l,mAst_r,.5^16,f)
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,f::Function) = LocMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,12,12,f)
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,f::Function) = LocMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,
    Int(ceil(17*log(2)/log(gamma(1+1/α+(1-β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1-β*(α <= 1 ? 1 : (α-2)/α))/2))))),
    Int(ceil(17*log(2)/log(gamma(1+1/α+(1+β*(α <= 1 ? 1 : (α-2)/α))/2)/(gamma(1+1/α)*gamma(1+(1+β*(α <= 1 ? 1 : (α-2)/α))/2))))))
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,f::Function) = LocMvStableMeander(α,β,t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,4,4,f)
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},f::Function) = LocMvStableMeander(α,β,t,
    (2/3)*2/(α-β*(α <= 1 ? α : α-2)),(2/3)*2/(α+β*(α <= 1 ? α : α-2)),
    (1/3)*2/(α-β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,.95*α,f)
  LocMvStableMeander(α::Real,β::Real,t::Array{Real,1},Δ_l::Integer,Δ_r::Integer,f::Function) = LocMvStableMeander(α,β,t,
    (2/3)*2/(α-β*(α <= 1 ? α : α-2)),(2/3)*2/(α+β*(α <= 1 ? α : α-2)),
    (1/3)*2/(α-β*(α <= 1 ? α : α-2)),(1/3)*2/(α+β*(α <= 1 ? α : α-2)),.95*α,.95*α,4,4,Δ_l,Δ_r,12,12,f)
end

function local_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,mAst_l::Integer,mAst_r::Integer,f::Function)
  return LocMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,mAst_l,mAst_r,f)
end

function local_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,Δ_l::Integer,Δ_r::Integer,f::Function)
  return LocMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,Δ_l,Δ_r,f)
end

function local_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,κ_l::Real,κ_r::Real,f::Function)
  return LocMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,κ_l,κ_r,f)
end

function local_sampler(d::MvStableMeander,d_l::Real,d_r::Real,δ_l::Real,δ_r::Real,γ_l::Real,γ_r::Real,f::Function)
  return LocMvStableMeander(d.α,d.β,d.t,d_l,d_r,δ_l,δ_r,γ_l,γ_r,f)
end

function local_sampler(d::MvStableMeander,Δ_l::Integer,Δ_r::Integer,f::Function)
  return LocMvStableMeander(d.α,d.β,d.t,Δ_l,Δ_r,f)
end

function local_sampler(d::MvStableMeander,f::Function)
  return LocMvStableMeander(d.α,d.β,d.t,f)
end

import Base.length
function length(d::LocMvStableMeander)
  return Int(2*length(d.t) + 2)
end

function rand(d::LocMvStableMeander)
  ra = 1 /d.α

  if d.β == 1 && d.α <= 1
    return f(rand(MvStableMeander(d.α,1,d.t)))
  end

  if length(d.t) == 1
    g(x) = d.f(x* t[1]^ra)
    return rand(LocStableMeander(d.α,d.β,d.d_r,δ_r,γ_r,κ_r,Δ_r,mAst_r,g))
  end

  m_r = length(d.t)
  m_l = Int(m_r-1)

  while true
    τ = rand(Beta(1-d.ρ,d.ρ),m_l)
    T_l = [d.t[i]-d.t[i-1] for i = 2:length(d.t)] .* τ
    T_r = [dt[1]]
    append!(T_r, [d.t[i]-d.t[i-1] for i = 2:length(d.t)] .* (1 .- τ))

    T_l = T_l .^ ra
    T_r = T_r .^ ra

    ar_l, ar_r = d.α*(1-d.ρ), d.α*d.ρ
    et1_l, et1_r = 1/(ar_l+d.η_l), 1/(ar_r+d.η_r)
    rar_l, rar_r = 1/ar_l, 1/ar_r

    # Conditionally positive stable distribution
    dPos_l, dPos_r = PositiveStable(d.α,-d.β), PositiveStable(d.α,d.β)
    sep_l, sep_r = d.d_l - d.δ_l, d.d_r - d.δ_r
    e2_l, e2_r = 1 /(1 -exp(-sep_l)), 1 /(1 -exp(-sep_r))
    mom_l, mom_r = mellin(dPos_l,d.γ_l), mellin(dPos_r,d.γ_r)
    dega_l, dega_r = d.δ_l*d.γ_l, d.δ_r*d.γ_r
    dega2_l, dega2_r = -1 /(1 -exp(-dega_l)), -1 /(1 -exp(-dega_r))
    mAst_l, mAst_r = d.mAst_l + Int(ceil( max(0,log(mom_l)/dega_l) + rar_l )), d.mAst_r + Int(ceil( max(0,log(mom_r)/dega_r) + rar_r ))
    # θ sequence
    U_l, U_r = [Float64[] for i = 1:m_l], [Float64[] for i = 1:m_r]
    # Line 1
    x_l, x_r = [Inf for i = 1:m_l], [Inf for i = 1:m_r]
    σ = 0
    # Line 2
    lagU_l, lagU_r = [rand(Beta(1-d.ρ,1),d.Δ_l) for i = 1:m_l], [rand(Beta(d.ρ,1),d.Δ_r) for i = 1:m_r]
    lagS_l, lagS_r = [rand(dPos_l,d.Δ_l) for i = 1:m_l], [rand(dPos_r,d.Δ_r) for i = 1:m_r]
    prod_l, prod_r = [prod(lagU_l[i]) for i = 1:m_l], [prod(lagU_r[i]) for i = 1:m_r]
    # Line 3 (first iteration)
    # Line 4 (Algorithm 3 with unconditional probabilities)
    S_l, S_r = [rand(dPos_l,mAst_l) for i = 1:m_l], [rand(dPos_r,mAst_r) for i = 1:m_r]
    for i = 1:m_l
      append!(S_l[i],chiStable1(dPos_l,d.δ_l,dega_l,dega2_l,mom_l,mAst_l-1))
    end
    for i = 1:m_r
      append!(S_r[i],chiStable1(dPos_r,d.δ_r,dega_r,dega2_r,mom_r,mAst_r-1))
    end

    R_l, R_r = [Float64[] for i = 1:m_l], [Float64[] for i = 1:m_r]
    C_l, C_r = [[0.] for i = 1:m_l], [[0.] for i = 1:m_r]
    F_l, F_r = [Float64[] for i = 1:m_l], [Float64[] for i = 1:m_r]
    # Last value of C
    endC_l, endC_r = [0. for i = 1:m_l], [0. for i = 1:m_r]
    # First iteration
    # Line 5
    for i = 1:m_l
      while (length(R_l[i]) == σ) || (length(S_l[i]) >= length(C_l[i]))
        t_l = length(C_l[i])
        # Lines 6 and 7
        (C1_l[i],F1_l[i]) = downRW(d.d_l,rar_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
        append!(F_l[i],F1_l[i])
        append!(C_l[i],endC_l[i] .+ C1_l[i])
        endC_l[i] = C_l[i][end]
        # Lines 8 and 9
        if reflectedProcess(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
          # Line 10
          x_l[i] = endC_l[i] + d.κ_l
          R1_l = [maximum(C_l[i][t_l:end])]
          for j = (t_l-1):-1:(length(R_l[i])+1)
            push!(R1_l,max(R1_l[end],C_l[i][j]))
          end
          append!(R_l,R1_l[end:-1:1] .- C_l[i][(length(R_l[i])+1):t_l])
        else # Line 11
          # Lines 12 and 13
          (C1_l,F1_l) = upRW(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
          append!(F_l[i],F1_l)
          append!(C_l[i],endC_l .+ C1_l)
          endC_l[i] = C_l[i][end]
        end # Line 14
      end # Line 15 - left
    end
    for i = 1:m_r
      while (length(R_r[i]) == σ) || (length(S_r[i]) >= length(C_r[i]))
        t_r = length(C_r[i])
        # Lines 6 and 7
        (C1_r,F1_r) = downRW(d.d_r,rar_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
        append!(F_r[i],F1_r)
        append!(C_r[i],endC_r[i] .+ C1_r)
        endC_r[i] = C_r[i][end]
        # Lines 8 and 9
        if reflectedProcess(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
          # Line 10
          x_r[i] = endC_r[i] + d.κ_r
          R1_r = [maximum(C_r[i][t_r:end])]
          for j = (t_r-1):-1:(length(R_r[i])+1)
            push!(R1_r,max(R1_r[end],C_r[i][j]))
          end
          append!(R_r[i],R1_r[end:-1:1] .- C_r[i][(length(R_r[i])+1):t_r])
        else # Line 11
          # Lines 12 and 13
          (C1_r,F1_r) = upRW(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
          append!(F_r[i],F1_r)
          append!(C_r[i],endC_r[i] .+ C1_r)
          endC_r[i] = C_r[i][end]
        end # Line 14
      end # Line 15 - right
    end
    # Line 16
    for i = 1:m_l
      while length(U_l[i]) < length(S_l[i])
        push!(U_l[i],exp(-d.α*F_l[i][length(U_l[i])+1]))
      end
    end
    for i = 1:m_r
      while length(U_r[i]) < length(S_r[i])
        push!(U_r[i],exp(-d.α*F_r[i][length(U_r[i])+1]))
      end
    end
    # Lines 17, 18 and 19
    σ = 1
    D_l = [exp(R_l[i][σ])*( e2_l*exp(-sep_l*(length(S_l[i])-σ-1)) + sum(S_l[i][σ:end] .*
        exp.(-d.d_l .* (0:(length(S_l[i])-σ))) .* (1 .- U_l[i][σ:end]) .^ ra) ) for i = 1:m_l]
    D_r = [exp(R_r[i][σ])*( e2_r*exp(-sep_r*(length(S_r[i])-σ-1)) + sum(S_r[i][σ:end] .*
        exp.(-d.d_r .* (0:(length(S_r[i])-σ))) .* (1 .- U_r[i][σ:end]) .^ ra) ) for i = 1:m_r]

    for i = 1:m_l
      push!(lagS_l[i],S_l[i][σ])
      push!(lagU_l[i],U_l[i][σ])
    end
    for i = 1:m_r
      push!(lagS_r[i],S_r[i][σ])
      push!(lagU_r[i],U_r[i][σ])
    end
    # Line 20
    # Line 21
    L_l = [(prod_l[i]*U_l[i][1])^ra for i = 1:m_l]
    L_r = [(prod_r[i]*U_r[i][1])^ra for i = 1:m_r]

    X_l = [(1-U_l[i][1])^ra * S_l[i][1] for i = 1:m_l]
    X_r = [(1-U_r[i][1])^ra * S_r[i][1] for i = 1:m_r]

    for i = 1:m_l
      for j = d.Δ_l:-1:1
        X_l[i] = lagU_l[i][j]^ra * X_l[i] + (1-lagU_l[i][j])^ra * lagS_l[i][j]
      end
    end
    for i = 1:m_r
      for j = d.Δ_r:-1:1
        X_r[i] = lagU_r[i][j]^ra * X_r[i] + (1-lagU_r[i][j])^ra * lagS_r[i][j]
      end
    end

    CX_lu = [0.]
    append!(CX_lu, cumsum(X_l .* T_l))
    CX_ld = [0.]
    append!(CX_ld, cumsum((X_l .+ D_l .* L_l) .* T_l))
    CX_ru = cumsum((X_r .+ D_r .* L_r) .* T_r)
    CX_rd = cumsum(X_r .* T_r)
    X_u = CX_ru .- CX_lu
    X_d = CX_rd .- CX_ld

    if !(prod(X_u .> 0))
      continue
    end
    aux = d.f(X_u)
    if aux == d.f(X_d) && prod(X_d .> 0)# Precision reached!
      return (aux,X_d,X_u,σ)
    end # Line 22
    # Line 3 (second to last iterations)
    while true
      # Line 4 (Algorithm 3 with conditional probabilities)
      for i = 1:m_l
        append!(S_l[i],chiStable2(dPos_l,d.δ_l,dega_l,dega2_l,mom_l,length(S_l[i])-σ-1))
      end
      for i = 1:m_r
        append!(S_r[i],chiStable2(dPos_r,d.δ_r,dega_r,dega2_r,mom_r,length(S_r[i])-σ-1))
      end
      # Line 5
      for i = 1:m_l
        while (length(R_l[i]) == σ) || (length(S_l[i]) >= length(C_l[i]))
          t_l = length(C_l[i])-1
          # Lines 6 and 7
          (C1_l,F1_l) = downRW(d.d_l,rar_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
          append!(F_l[i],F1_l)
          append!(C_l[i],endC_l[i] .+ C1_l)
          endC_l[i] = C_l[i][end]
          # Lines 8 and 9
          if reflectedProcess(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
            # Line 10
            x_l[i] = endC_l[i] + d.κ_l
            R1_l = [maximum(C_l[i][t_l:end])]
            for j = (t_l-1):-1:(length(R_l[i])+1)
              push!(R1_l,max(R1_l[end],C_l[i][j]))
            end
            append!(R_l[i],R1_l[end:-1:1] .- C_l[i][(length(R_l[i])+1):t_l])
          else # Line 11
            # Lines 12 and 13
            (C1_l,F1_l) = upRW(d.d_l,d.η_l,et1_l,d.κ_l,x_l[i]-endC_l[i])
            append!(F_l[i],F1_l)
            append!(C_l[i],endC_l .+ C1_l)
            endC_l[i] = C_l[i][end]
          end # Line 14
        end # Line 15
      end
      for i = 1:m_r
        while (length(R_r[i]) == σ) || (length(S_r[i]) >= length(C_r[i]))
          t_r = length(C_r[i])-1
          # Lines 6 and 7
          (C1_r,F1_r) = downRW(d.d_r,rar_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
          append!(F_r[i],F1_r)
          append!(C_r[i],endC_r[i] .+ C1_r)
          endC_r[i] = C_r[i][end]
          # Lines 8 and 9
          if reflectedProcess(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
            # Line 10
            x_r[i] = endC_r[i] + d.κ_r
            R1_r = [maximum(C_r[t_r:end])]
            for j = (t_r-1):-1:(length(R_r[i])+1)
              push!(R1_r,max(R1_r[end],C_r[i][j]))
            end
            append!(R_r[i],R1_r[end:-1:1] .- C_r[i][(length(R_r[i])+1):t_r])
          else # Line 11
            # Lines 12 and 13
            (C1_r,F1_r) = upRW(d.d_r,d.η_r,et1_r,d.κ_r,x_r[i]-endC_r[i])
            append!(F_r[i],F1_r)
            append!(C_r[i],endC_r[i] .+ C1_r)
            endC_r[i] = C_r[i][end]
          end # Line 14
        end # Line 15
      end
      # Line 16
      for i = 1:m_l
        while length(U_l[i]) < length(S_l[i])
          push!(U_l[i],exp(-d.α*F_l[i][length(U_l[i])+1]))
        end
      end
      for i = 1:m_r
        while length(U_r[i]) < length(S_r[i])
          push!(U_r[i],exp(-d.α*F_r[i][length(U_r[i])+1]))
        end
      end
      # Lines 17, 18 and 19
      σ += 1
      D_l = [exp(R_l[i][σ])*( e2_l*exp(-sep_l*(length(S_l[i])-σ-1)) + sum(S_l[i][σ:end] .*
          exp.(-d.d_l .* (0:(length(S_l[i])-σ))) .* (1 .- U_l[i][σ:end]) .^ ra) ) for i = 1:m_l]
      D_r = [exp(R_r[σ])*( e2_r*exp(-sep_r*(length(S_r[i])-σ-1)) + sum(S_r[i][σ:end] .*
          exp.(-d.d_r .* (0:(length(S_r[i])-σ))) .* (1 .- U_r[i][σ:end]) .^ ra) ) for i = 1:m_r]
      for i = 1:m_l
        push!(lagS_l[i],S_l[i][σ])
        push!(lagU_l[i],U_l[i][σ])
      end
      for i = 1:m_r
        push!(lagS_r[i],S_r[i][σ])
        push!(lagU_r[i],U_r[i][σ])
      end
      # Line 24
      X_l .+= [L_l[i]*(1-U_l[i][σ])^ra * S_l[i][σ] for i = 1:m_l]
      X_r .+= [L_r[i]*(1-U_r[i][σ])^ra * S_r[i][σ] for i = 1:m_r]

      L_l .*= [U_l[i][σ]^ra for i = 1:m_l]
      L_r .*= [U_r[i][σ]^ra for i = 1:m_r]

      CX_lu = [0.]
      append!(CX_lu, cumsum(X_l .* T_l))
      CX_ld = [0.]
      append!(CX_ld, cumsum((X_l .+ D_l .* L_l) .* T_l))
      CX_ru = cumsum((X_r .+ D_r .* L_r) .* T_r)
      CX_rd = cumsum(X_r .* T_r)
      X_u = CX_ru .- CX_lu
      X_d = CX_rd .- CX_ld

      if !(prod(X_u .> 0))
        continue
      end
      aux = d.f(X_u)
      if aux == d.f(X_d) && prod(X_d .> 0)# Precision reached!
        return (aux,X_d,X_u,σ)
      end # Line 26
    end # Line 27
  end
end

export StableMeander, LocStableMeander, PreciseStableMeander, MvStableMeander, LocMvStableMeander, PreciseMvStableMeander, rand, precise_sampler, local_sampler, minimum, maximum, insupport, mean, params, length


# Set random seed:
srand(1234)

#uniform.jl

"""
Create a NxM uniformly distributed matrix using the method rand()*(b-a)-a where a,b are the Unif Parameters.

Arguments
---------
a
    Lower Bound
b
    Upper Bound
N
    Row Dimension
M
    Column Dimension

"""
function uniform(a,b,N,M)
    #Returns a NxM uniformly distributed matrix
    #for random number in range [a,b] we note that it can be generated by: rand() * (b-a) - a
    #where rand() prints real numbers:
    rand(N,M)*(b-a)+a
end

#sigmoid.jl

"""
Return the sigmoid of x:

Arguments
---------
x
    input

"""

#Later on we will add a hyperparemeter to this function:

function sigmoid(x)
    return 1/(1+exp(-x))
    end

#sigmoid_deriv.jl

"""
Return the derivative of the sigmoid of x:

Arguments
---------
x
    input

"""

function sigmoid_deriv(x)
    return 1/(1+exp(-x))*(1-1/(1+exp(-x)))
end

#Train_Neural_Net.jl

"""
Train the XOR neural net for a given number of epochs:

Arguments
---------
epochs
    number of times the network will foward and back propagate
Layer_1
    weighting for hidden layer
Layer_2
    weighting for output layer
learning_rate
    Learning Rate for Gradient Descent
"""

function Train_Neural_Net_Loop(epochs,Layer_1,Layer_2,learning_rate)

    #Initialise XOR truth values:
    X=[ 0 0 ; 0 1 ; 1 0 ; 1 1] #When selected X[i,:] we have column vectors so need to transpose for matmult
    Y=[ 0;1;1;0]

    MSE=0

    for i=1:epochs #For the weight matrix wij is the weight for input i going to Neuron j:

        s_1=map(sigmoid,X*Layer_1) #Applies Sigmoid to X*Layer_1 the weighting matrix
        s_2=map(sigmoid,s_1*Layer_2) #Applies Sigmoid to a2 * second weighting matrix
        direct_error=s_2-Y
        MSE=0.5*sum(direct_error.*direct_error) #element wise squares direct_error then summs up and divides by length

    #Begin updating:
    delta_outer=-1.0*direct_error.*map(sigmoid_deriv,s_1*Layer_2) #this is the delta (which is the raw loss element-wise
                                                        #multiplied by z(3) see explanation in readme

    delta_inner=delta_outer*transpose(Layer_2).*map(sigmoid_deriv,X*Layer_1)
    # map(sigmoid_deriv,X*Layer_1) is f'(z2)

    #Update the weights:

    Layer_2 +=learning_rate*transpose(s_1)*delta_outer #Outer layer has been updated.
    Layer_1 += learning_rate*transpose(X)*delta_inner

    print(string(MSE,"\r"))

    end
    return Layer_1, Layer_2, MSE
    end

#Train_Neural_Net.jl

"""
Train the XOR neural net for a given mean square error it needs to reach:

Arguments
---------
MSE_Min
    Error the network trains till
Layer_1
    weighting for hidden layer
Layer_2
    weighting for output layer
learning_rate
    Learning Rate for Gradient Descent
"""

function Train_Neural_Net_MSE(MSE_Min,Layer_1,Layer_2,learning_rate)

    #Initialise XOR truth values:
    X=[ 0 0 ; 0 1 ; 1 0 ; 1 1] #When selected X[i,:] we have column vectors so need to transpose for matmult
    Y=[ 0;1;1;0]

    MSE=MSE_Min+1

    while MSE>=MSE_Min #For the weight matrix wij is the weight for input i going to Neuron j:

        s_1=map(sigmoid,X*Layer_1) #Applies Sigmoid to X*Layer_1 the weighting matrix
        s_2=map(sigmoid,s_1*Layer_2) #Applies Sigmoid to a2 * second weighting matrix
        direct_error=s_2-Y
        MSE=0.5*sum(direct_error.*direct_error) #element wise squares direct_error then summs up and divides by length

#This is not actually MSE it is the square error
    #Begin updating:
    delta_outer=-1.0*direct_error.*map(sigmoid_deriv,s_1*Layer_2) #this is the delta (which is the raw loss element-wise
                                                        #multiplied by z(3) see explanation in readme

    delta_inner=delta_outer*transpose(Layer_2).*map(sigmoid_deriv,X*Layer_1)
    # map(sigmoid_deriv,X*Layer_1) is f'(z2)

    #Update the weights:

    Layer_2 +=learning_rate*transpose(s_1)*delta_outer #Outer layer has been updated.
    Layer_1 += learning_rate*transpose(X)*delta_inner

    print(string(MSE,"\r"))

    end
    return Layer_1, Layer_2, MSE
    end

#XOR_Net.jl

"""
Output the values of a given XOR_Net

Arguments
---------
M
    Input for the XOR
w1
    Layer 1
w2
    Layer 2

"""

function XOR_Net(M,w1,w2)
    #Where M is a matrix that
    s_1=map(sigmoid,M*w1) #Applies Sigmoid to X*Layer_1 the weighting matrix
    s_2=map(sigmoid,s_1*w2)
    return s_2

end

#----------------------------- We can add tanh functions and hyperparameters-----------#

# #TEST RUN:---

# #Initialise Layers of Neuron Weights:

# Layer_1=uniform(0,1,2,2) #(In video above W1)
# Layer_2=uniform(0,1,2,1) #Column vector for the outer layer (in video above W2)

# # #Initialise Learning Rate:

# # learning_rate1=0.1
# # learning_rate2=0.01
# # # learning_rate3=0.4

# # w1, w2, MSE1 =Train_Neural_Net_Loop(100000,Layer_1,Layer_2,learning_rate1)
# x1, x2, MSE2 =Train_Neural_Net_Loop(1000000,Layer_1,Layer_2,learning_rate2)
# # z1, z2, MSE3=Train_Neural_Net_Loop(100000,Layer_1,Layer_2,learning_rate3)

# print(string(MSE1,""),string(MSE2,""),string(MSE3,""))

# # #================================================================================================#
# # #If kernal has hyper-parameter: set it here and uncomment the curry function named ker:
# # # function c_ker(hyp,ker)
# # #     return ((x,y) -> ker(x,y,hyp))
# # # end
# # # new_kernal = c_ker(0.1,hyper_exp_square_ker)


using Manifolds, Manopt, Test

@testset "Subgradient Plan" begin
    M = Euclidean(2)
    x = [4.0, 2.0]
    x0 = [5.0, 2.0]
    o = SubGradientMethodOptions(M, x0, StopAfterIteration(200), ConstantStepsize(1.0))
    o.∂ = [1.0, 0.0]
    f = y -> distance(M, y, x)
    ∂f = y -> if distance(M, x, y) == 0
        zero_tangent_vector(M, y)
    else
        -2 * log(M, y, x) / max(10 * eps(Float64), distance(M, x, y))
    end
    p = SubGradientProblem(M, f, ∂f)
    oR = solve(p, o)
    xHat = get_solver_result(oR)
    @test get_initial_stepsize(p, o) == 1.0
    @test get_stepsize(p, o, 1) == 1.0
    @test get_last_stepsize(p, o, 1) == 1.0
    # Check Fallbacks of Problen
    @test get_cost(p, x) == 0.0
    @test norm(M, x, get_subgradient(p, x)) == 0
    @test_throws ErrorException get_gradient(p, o.x)
    @test_throws ErrorException get_proximal_map(p, 1.0, o.x, 1)
    o2 = subgradient_method(M, f, ∂f, copy(x0); return_options=true)
    xhat2 = get_solver_result(o2)
    @test f(xhat2) <= f(x0)
end


using Distributions
using Statistics

@testset "Radial Uniform" begin
    dist = RadialUniform(0, 1)

    @testset "Interface" begin
        @test minimum(dist) == 0
        @test maximum(dist) == 1

        t = RadialUniform(0, 1)
        @test typeof(t) === RadialUniform{Float64}
        t = RadialUniform(0, 1.0)
        @test typeof(t) === RadialUniform{Float64}
        t = RadialUniform(BigInt(0), BigInt(1))
        @test typeof(t) === RadialUniform{BigFloat}
    end

    @testset "Accuracy" begin
        @test pdf(dist, -1) ≈ 0
        @test pdf(dist, 0.5) ≈ 1
        @test pdf(dist, 2) ≈ 0

        @test logpdf(dist, -1) ≈ -Inf
        @test logpdf(dist, 0.5) ≈ 0
        @test logpdf(dist, 2) ≈ -Inf

        @test cdf(dist, -1) ≈ 0
        @test cdf(dist, 0.5) ≈ 1 / 4
        @test cdf(dist, 2) ≈ 1

        @test logcdf(dist, -1) ≈ -Inf
        @test logcdf(dist, 0.5) ≈ -log(4)
        @test logcdf(dist, 2) ≈ 0

        @test quantile(dist, 1) ≈ 1
        @test quantile(dist, 0) ≈ 0

        for i in range(0, 1, length = 10)
            @test cdf(dist, quantile(dist, i)) ≈ i
        end
    end
end

@testset "Poisson Invariant" begin
    dist = PoissonInvariant(0, 1)

    @testset "Interface" begin
        @test minimum(dist) == 0
        @test maximum(dist) == 1

        t = PoissonInvariant(0, 1)
        @test typeof(t) === PoissonInvariant{Float64}
        t = PoissonInvariant(0, 1.0)
        @test typeof(t) === PoissonInvariant{Float64}
        t = PoissonInvariant(BigInt(0), BigInt(1))
        @test typeof(t) === PoissonInvariant{BigFloat}
    end

    @testset "Accuracy" begin
        @test pdf(dist, -1) ≈ 0
        @test pdf(dist, 0.5) ≈ 1 / 2 / √0.5
        @test pdf(dist, 2) ≈ 0

        @test logpdf(dist, -1) ≈ -Inf
        @test logpdf(dist, 0.5) ≈ -log(2 * √0.5)
        @test logpdf(dist, 2) ≈ -Inf

        @test cdf(dist, -1) ≈ 0
        @test cdf(dist, 0.5) ≈ √0.5
        @test cdf(dist, 2) ≈ 1

        @test logcdf(dist, -1) ≈ -Inf
        @test logcdf(dist, 0.5) ≈ -log(2) / 2
        @test logcdf(dist, 2) ≈ 0

        @test quantile(dist, 1) ≈ 1
        @test quantile(dist, 0) ≈ 0

        for i in range(0, 1, length = 10)
            @test cdf(dist, quantile(dist, i)) ≈ i
        end
    end
end


# This uses reduce instead of sum because sum can't handle empty collections properly.
# There is no universal zero-element, so it has to be specified manually and cannot
# be determined automatically. Otherwise, n=0 will cause an error.
sum_of_squares(n::Int) = reduce(+, i^2 for i in 1:n; init=0)

# However, sum{T<:Real}(r::Range{T}) can handle "empty" ranges.
square_of_sum(n::Int) = sum(1:n)^2

difference(n::Int) = square_of_sum(n) - sum_of_squares(n)


# Defining regions on the plane, testing for inclusion, tessellations, and interpolation

using VoronoiDelaunay  # for creating Delaunay meshes on unstructured data

# Region types

"A region on the plane"
abstract type Region end

"A boxed region [`xmin`, `xmax`] ⊗ [`ymin`, `ymax`]"
struct BoundingBox <: Region
    xmin::Float64
    xmax::Float64
    ymin::Float64
    ymax::Float64
end

""" A polygon whose corners are given in the vectors `x` and `y`

    For example, Polygon([0, 1, 1, 0], [0, 0, 1, 1]) defines the unit square.
    """
struct Polygon <: Region
    x::Vector{Float64}
    y::Vector{Float64}
    boundingbox::BoundingBox

    # we precalculate the bounding box since we'll be using it a lot
    function Polygon(x, y)
        @assert (length(x) == length(y)) "Uneven number of x and y points"
        new(x, y, BoundingBox(extrema(x)..., extrema(y)...))
    end
end
"Get the corners of a `Polygon`"
corners(p::Polygon) = zip(p.x, p.y)
BoundingBox(p::Polygon) = p.boundingbox

# Inclusion testing

Base.in(xy, r::Region) = in(xy..., r)
Base.in(x, y, bb::BoundingBox) = (bb.xmin ≤ x ≤ bb.xmax) && (bb.ymin ≤ y ≤ bb.ymax)
function Base.in(x, y, p::Polygon)
    if !((x, y) in p.boundingbox)
        return false
    end

    # O(n) polygon inclusion test - this could definitely be faster
    inpoly = false
    lastcx = p.x[end]
    lastcy = p.y[end]
    for (cx, cy) in corners(p)
        if cy < y && lastcy ≥ y || lastcy < y && cy ≥ y
            if (cx + (y - cy)/(lastcy - cy)*(lastcx - cx)) < x
                inpoly = !inpoly
            end
        end
        lastcx, lastcy = cx, cy
    end

    return inpoly
end

# Tesselation utils: mapping to the interval (1+eps(), 2-2*eps())

"Map `x` from the interval [`lb`, `ub`] to (1.0, 2.0)"
norm12(x, lb, ub) = (x - lb) ./ (ub - lb) * (max_coord - min_coord) + min_coord

"Map each element of the vector `x` to the interval (1.0, 2.0)"
norm12(x::Vector) = norm12(x, extrema(x)...)

"Map `x` from the interval (1.0, 2.0) to [`lb`, `ub`]"
unnorm(x, lb, ub) = (x - min_coord) / (max_coord - min_coord) .* (ub - lb) + lb

"Map the log of `x` from [`lb`, `ub`] to the interval (1.0, 2.0)"
lognorm12(x, lb, ub) = norm12(log10.(x), log10(lb), log10(ub))

"Map the log of the vector `x` to the interval (1.0, 2.0)"
lognorm12(x::Vector) = norm12(log10.(x))

"Map the log of `x` from (1.0, 2.0) back to [`lb`, `ub`]"
unlognorm(x, lb, ub) = 10.^unnorm(x, log10(lb), log10(ub))

# Tessellations

const Tessellation = DelaunayTessellation2D

BoundingBox(t::Tessellation) = BoundingBox(min_coord, max_coord, min_coord, max_coord)

function Base.in(xn, yn, tess::Tessellation)
    if !in(xn, yn, BoundingBox(tess))
        return false
    else
        tri = findtriangle(tess, xn, yn)
        return !isexternal(tri)
    end
end

"Make a Delaunay tessellation on the plane using points in `xs` and `ys`"
function get_tessellation(xs::Vector, ys::Vector; uselog=false)
    xn = uselog ? lognorm12(xs) : norm12(xs)
    yn = uselog ? lognorm12(ys) : norm12(ys)
    tess = DelaunayTessellation(length(xs))
    for (x, y) in zip(xn, yn)
        push!(tess, Point(x, y))
    end

    tess
end

"Find the triangle in the tessellation `tess` containing points `xn` and `yn`"
function findtriangle(t::Tessellation, xn, yn)
    triangle = locate(t, Point(xn, yn))
end

"Get the vertices of triangle `tri`"
getvertices(tri) = map(f -> f(tri), [geta, getb, getc])

"Get barycentric coordinates on the triangle `tri` from cartesian (`xn`, `yn`)"
function barycoords(tri, xn, yn)
    a, b, c = getvertices(tri)
    x = map(getx, [a, b, c])
    y = map(gety, [a, b, c])

    r3 = [x[3], y[3]]
    r = [xn, yn]
    T = [x[1]-x[3]  x[2]-x[3];
         y[1]-y[3]  y[2]-y[3]]
    λ1, λ2 = T \ (r - r3)
    λ = [λ1, λ2, 1 - λ1 - λ2]
end

"Linear interpolation from barycentric coordinates `λ` between vertex values `x`"
lininterp(λ, x) = λ⋅x


using RecurrenceAnalysis, DelimitedFiles, Statistics

# Measure the times (in ms) of evaluating an expression n times
macro measuretime(ex, n)
    quote
        # Train the expression and get the result
        result = $(esc(ex))
        t = zeros($n)
        for i in 1:$n
            t[i] = 1000*(@elapsed $(esc(ex)))
        end
        t, result
    end
end

# Function that will be measured
function fun_rqa(x,metric)
    xe = embed(x,3,6)
    rmat = RecurrenceMatrix(xe,1.2,metric=metric)
    rqa(rmat,theiler=1)
end

# Analyse 12 series from 250 to 3000 points
# (With variable metric)
function benchmark(metric)
    m = readdlm("rossler.txt")
    for r=1:12
        x=m[1:250r,2r-1]
        tt, res = @measuretime fun_rqa(x,metric) 5
        t = median(tt)
        # Write table of results
        open("benchmark_rqa_julia_$metric.txt","a") do f
            write(f,"$r\t$t\t")
            for k in ["RR","DET","L","Lmax","ENTR","LAM","TT"]
                write(f, "$(res[k])\t")
            end
            write(f,"\n")
        end
    end
end
# Do it with max and euclidean norms
benchmark("euclidean")
benchmark("max")


nt = 1000
m = ABCDQR_simula(nt)

y = m["y"]
u = m["u"]
A = m["A"]
B = m["B"]
C = m["C"]
D = m["D"]
Q = m["Q"]
R = m["R"]
x10 = 0*A[:,1]
P10 = 0*A

###########################
print("Testing ABCDQR_kfilter: ")
xtt,Ptt,xtt1,Ptt1,et,St,Kt,loglik = ABCDQR_kfilter(y,u,A,B,C,D,Q,R,x10,P10)
@test sum(xtt.^2) > 1e-6
println("OK")

#######################
print("Testing ABCDQR_kfilter_s: ")
xtt,Ptt,xtt1,Ptt1,et,St,Kt,loglik = ABCDQR_kfilter_s(y,u,A,B,C,D,Q,R,x10)
@test sum(xtt.^2) > 1e-6
println("OK")


function kmeans(
    X::AbstractMatrix{<:Real},
    k::Integer;
    T::Float64 = 0.98,
    iseeds = randomcenters
)
    d, n = size(X)

    # assign initial cluster centers
    centers = iseeds(X, k)
    assignments = Vector{Int}(undef, n)

    dist = pairwise(Euclidean(), centers, X, dims = 2)
    D = eltype(dist)

    costs = Vector{D}(undef, n)
    counts = Vector{Int}(undef, k)
    sd = Array{D}(undef, d, k)
    iter = 0

    while true
        iter += 1
        if iter > 1000
            return ClusteringResult(centers, assignments, costs, counts, sd, iter)
        end

        dist = pairwise(Euclidean(), centers, X, dims = 2)
        prev_assignments = deepcopy(assignments)
        update_assignments!(dist, assignments, costs, counts)
        update_centers!(X, assignments, centers, counts)

        # check convergence threshold
        updates = assignments .== prev_assignments
        if (sum(updates)/length(updates)) > T
            return ClusteringResult(centers, assignments, costs, counts, sd, iter)
        end

        # compute std dev of each component in each cluster
        sd = Array{D}(undef, d, k)
        update_sd!(X, assignments, centers, counts, sd)
    end
end


function uhlig04()
#
#  test polynomial used by F. Uhlig
#
    r = [(3/11)*ones(12); 11/3; 11/3; (2*im/7)*ones(4); (2.5+im/4)*ones(2); 1/8]
    p = poly(r);
    z = [3/11 12; 11/3 2; 2*im/7 4; 2.5+im/4 2; 1/8 1];

    p, PolyZeros(z)
end


using Distributions
using Lora
using PGUManifoldMC

DATADIR = "../../data"
SUBDATADIR = "mala"

nchains = 10
nmcmc = 110000
nburnin = 10000

function C(n::Int, c::Float64)
  X = eye(n)
  [(j <= n-i) ? X[i+j, i] = X[i, i+j] = c^j : nothing for i = 1:(n-1), j = 1:(n-1)]
  X
end

n = 20
μ = zeros(n)
Σ = C(n, 0.9)
ν = 30.

Σt = (ν-2)*Σ/ν
Σtinv = inv(Σt)

function plogtarget(p::Vector{Float64}, v::Vector)
  hdf = 0.5*ν
  hdim = 0.5*n
  shdfhdim = hdf+hdim
  v = lgamma(shdfhdim)-lgamma(hdf)-hdim*log(ν)-hdim*log(pi)-0.5*logdet(Σt)
  z = p-μ
  v-shdfhdim*log1p(dot(z, Σtinv*z)/ν)
end

sampler = MALA(0.05)

mcrange = BasicMCRange(nsteps=nmcmc, burnin=nburnin)

outopts = Dict{Symbol, Any}(:monitor=>[:value], :diagnostics=>[:accept])

times = Array(Float64, nchains)
stepsizes = Array(Float64, nchains)
i = 1

while i <= nchains
  v0 = Dict(:p=>rand(Normal(0, 2), n))

  p = BasicContMuvParameter(
    :p,
    logtarget=plogtarget,
    nkeys=1,
    autodiff=:reverse,
    init=Any[(:p, v0[:p]), (:v, Any[v0[:p]])],
    order=1
  )

  model = likelihood_model([p], isindexed=false)

  job = BasicMCJob(
    model,
    sampler,
    mcrange,
    v0,
    tuner=AcceptanceRateMCTuner(0.574, score=x -> logistic_rate_score(x, 3.), verbose=false),
    outopts=outopts
  )

  tic()
  run(job)
  runtime = toc()

  chain = output(job)
  ratio = acceptance(chain)

  if 0.5 < ratio < 0.65
    writedlm(joinpath(DATADIR, SUBDATADIR, "chain"*lpad(string(i), 2, 0)*".csv"), chain.value, ',')
    writedlm(joinpath(DATADIR, SUBDATADIR, "diagnostics"*lpad(string(i), 2, 0)*".csv"), vec(chain.diagnosticvalues), ',')

    times[i] = runtime
    stepsizes[i] = job.sstate.tune.step

    println("Iteration ", i, " of ", nchains, " completed with acceptance ratio ", ratio)
    i += 1
  end
end

writedlm(joinpath(DATADIR, SUBDATADIR, "times.csv"), times, ',')
writedlm(joinpath(DATADIR, SUBDATADIR, "stepsizes.csv"), stepsizes, ',')


using PyPlot

rcParams = PyPlot.PyDict(PyPlot.matplotlib."rcParams")
rcParams["text.usetex"] = true
rcParams["mathtext.fontset"]  = "cm";
rcParams["font.family"]  = "serif";
rcParams["font.serif"]  = "cm";
plt.ioff()

function phi(t::Real)

    return (2 * pi)^(-0.5) * exp(-(t^2) / 2)
end

function generate_f(evals, p)

    f = (p[1]^2 + p[3]^2) * phi.(evals .- 1)
    f += p[2]*(2 - p[2]) * phi.(evals)
    f += 2*p[1]*p[3] * phi.(evals .+ 1)

    return f
end

function generate_Var_f_given_A(evals, p)

    Var_f_given_A = (p[1] + p[3])^2 * p[2] * phi.(evals).^2
    Var_f_given_A += p[3] * (p[3] * phi.(evals .- 1) + p[1] * phi.(evals .+ 1)).^2
    Var_f_given_A += p[1] * (p[1] * phi.(evals .- 1) + p[3] * phi.(evals .+ 1)).^2
    Var_f_given_A -= ((p[1]^2 + p[3]^2) * phi.(evals .- 1) +
        (p[1] + p[3]) * p[2] * phi.(evals) + 2 * p[1] * p[3] * phi.(evals .+ 1)).^2

    return Var_f_given_A

end

function plot_confidence_intervals(ax, x, y_lower, y_upper, num_intervals,
                                   line_color, line_width, line_style)

    inds = range(1, stop=length(x), length=2*num_intervals+1)
    inds = [inds[i] for i in 1:length(inds) if i%2 == 0]
    inds = Int.(round.(inds))
    @assert size(inds, 1) == num_intervals
    xr = x[inds]
    ylr = y_lower[inds]
    yur = y_upper[inds]

    ax.vlines(
        xr[1], ylr[1], yur[1], linewidth=line_width,
        linestyle=line_style, color=line_color)

    for i in 1:num_intervals

        ax.vlines(
            xr[i], ylr[i], yur[i], linewidth=line_width,
            linestyle=line_style, color=line_color)

        marker = PyPlot.matplotlib.path.Path([[-1,0], [1,0], [-1,0], [1,0]])

        ax.plot(
            xr[i], ylr[i], linewidth=0.0,
            color=line_color, marker=marker,
            markersize=4.0)

        ax.plot(
            xr[i], yur[i], linewidth=0.0,
            color=line_color, marker=marker,
            markersize=4.0)
    end
end


mutable struct upop_data
    n # number of variables
    nb # number of binary variables
    x # set of variables
    f # objective function
    supp # support data
    coe # coefficient data
    basis # monomial basis
    ksupp # extending support at the k-th step
    blocks # the block structrue
    sb # sizes of different blocks
    numb # numbers of different blocks
    GramMat # Gram matrix
    solver # SDP solver
    tol # tolerance to certify global optimality
    flag # 0 if global optimality is certified; 1 otherwise
end

"""
    opt,sol,data = tssos_first(f, x; nb=0, newton=true, reducebasis=false, TS="block", merge=false,
    md=3, solver="Mosek", QUIET=false, solve=true, MomentOne=false, solution=false, tol=1e-4)

Compute the first step of the TSSOS hierarchy for unconstrained polynomial optimization.
If `newton=true`, then compute a monomial basis by the Newton polytope method.
If `reducebasis=true`, then remove monomials from the monomial basis by diagonal inconsistency.
If `TS="block"`, use maximal chordal extensions; if `TS="MD"`, use approximately smallest chordal
extensions. If `merge=true`, perform the PSD block merging. If `MomentOne=true`, add an extra first
order moment matrix to the moment relaxation.
Return the optimum, the (near) optimal solution (if `solution=true`) and other auxiliary data.

# Arguments
- `f`: the objective function for unconstrained polynomial optimization.
- `x`: the set of variables.
- `nb`: the number of binary variables in `x`.
- `md`: the tunable parameter for merging blocks.
- `tol`: the relative tolerance to certify global optimality.
"""
function tssos_first(f, x; nb=0, newton=true, reducebasis=false, TS="block", merge=false,
    md=3, solver="Mosek", QUIET=false, solve=true, MomentOne=false, Gram=false, solution=false, tol=1e-4)
    println("************************TSSOS************************")
    println("TSSOS is launching...")
    n = length(x)
    mon = monomials(f)
    coe = coefficients(f)
    lm = length(mon)
    supp = zeros(UInt8, n, lm)
    for i = 1:lm, j = 1:n
        @inbounds supp[j,i] = MultivariatePolynomials.degree(mon[i], x[j])
    end
    d = Int(maxdegree(f)/2)
    if newton == true
       if sum(supp[:,end]) != 0
          supp = [supp zeros(UInt8, n)]
          coe = [coe; 0]
       end
       basis = newton_basis(n, d, supp)
    else
       basis = get_basis(n, d, nb=nb)
    end
    tsupp = [supp bin_add(basis, basis, nb)]
    tsupp = sortslices(tsupp, dims=2)
    tsupp = unique(tsupp, dims=2)
    if TS != false && QUIET == false
        println("Starting to compute the block structure...")
    end
    blocks,cl,blocksize,sb,numb,_ = get_blocks(tsupp, basis, nb=nb, TS=TS, QUIET=QUIET, merge=merge, md=md)
    if reducebasis == true
        psupp = [supp zeros(UInt8, n)]
        basis,flag = reducebasis!(psupp, basis, blocks, cl, blocksize, nb=nb)
        if flag == 1
            tsupp = [supp bin_add(basis, basis, nb)]
            tsupp = sortslices(tsupp, dims=2)
            tsupp = unique(tsupp, dims=2)
            blocks,cl,blocksize,sb,numb,_ = get_blocks(tsupp, basis, nb=nb, TS=TS, QUIET=QUIET, merge=merge, md=md)
        end
    end
    if TS != false && QUIET == false
        mb = maximum(maximum.(sb))
        println("Obtained the block structure. The maximal size of blocks is $mb.")
    end
    opt,ksupp,moment,GramMat = blockupop(n, supp, coe, basis, blocks, cl, blocksize, nb=nb, solver=solver, QUIET=QUIET, solve=solve, solution=solution, MomentOne=MomentOne, Gram=Gram)
    data = upop_data(n, nb, x, f, supp, coe, basis, ksupp, blocks, sb, numb, GramMat, solver, tol, 1)
    sol = nothing
    if solution == true
        sol,data.flag = extract_solutions(moment, opt, [f], x, tol=tol)
        if data.flag == 1
            sol,ub,gap = refine_sol(opt, sol, data, QUIET=true)
            if gap != nothing
                if gap < tol
                    data.flag = 0
                else
                    println("Found a local optimal solution giving an upper bound: $ub and a relative optimality gap: $gap.")
                end
            end
        end
    end
    return opt,sol,data
end

"""
    opt,sol,data = tssos_higher!(data; TS="block", merge=false, md=3, QUIET=false, solve=true,
    MomentOne=false, solution=false, tol=1e-4)

Compute higher steps of the TSSOS hierarchy.
Return the optimum, the (near) optimal solution (if `solution=true`) and other auxiliary data.
"""
function tssos_higher!(data::upop_data; TS="block", merge=false, md=3, QUIET=false, solve=true,
    MomentOne=false, Gram=false, solution=false)
    n = data.n
    nb = data.nb
    x = data.x
    f = data.f
    supp = data.supp
    coe = data.coe
    basis = data.basis
    ksupp = data.ksupp
    sb = data.sb
    numb = data.numb
    solver = data.solver
    tol = data.tol
    if QUIET == false
        println("Starting to compute the block structure...")
    end
    blocks,cl,blocksize,sb,numb,status = get_blocks(ksupp, basis, sb=sb, numb=numb, nb=nb, TS=TS, QUIET=QUIET, merge=merge, md=md)
    opt = nothing
    sol = nothing
    if status == 1
        if QUIET == false
            mb = maximum(maximum.(sb))
            println("Obtained the block structure. The maximal size of blocks is $mb.")
        end
        opt,ksupp,moment,GramMat = blockupop(n, supp, coe, basis, blocks, cl, blocksize, nb=nb, solver=solver, QUIET=QUIET, solve=solve, solution=solution, MomentOne=MomentOne, Gram=Gram)
        if solution == true
            sol,data.flag = extract_solutions(moment, opt, [f], x, tol=tol)
            if data.flag == 1
                sol,ub,gap = refine_sol(opt, sol, data, QUIET=true)
                if gap != nothing
                    if gap < tol
                        data.flag = 0
                    else
                        println("Found a local optimal solution giving an upper bound: $ub and a relative optimality gap: $gap.")
                    end
                end
            end
        end
        data.ksupp = ksupp
        data.GramMat = GramMat
        data.sb = sb
        data.numb = numb
        data.blocks = blocks
    end
    return opt,sol,data
end

function bin_add(bi, bj, nb)
    bs = bi + bj
    if nb > 0
        bs[1:nb,:] = isodd.(bs[1:nb,:])
    end
    return bs
end

function get_basis(n, d; nb=0, lead=[])
    lb = binomial(n+d, d)
    basis = zeros(UInt8, n, lb)
    i = 0
    t = 1
    while i < d+1
        t += 1
        if basis[n,t-1] == i
           if i < d
              basis[1,t] = i+1
           end
           i += 1
        else
            j = findfirst(x->basis[x,t-1]!=0, 1:n)
            basis[:,t] = basis[:,t-1]
            if j == 1
               basis[1,t] -= 1
               basis[2,t] += 1
            else
               basis[1,t] = basis[j,t] - 1
               basis[j,t] = 0
               basis[j+1,t] += 1
            end
        end
    end
    if nb > 0
        basis_bin = basis[1:nb,:]
        basis_valid = all.(x->x<=1, eachcol(basis_bin))
        basis = basis[:, basis_valid]
    end
    if !isempty(lead)
        basis_valid = map(a->!divide(a, lead, n, size(lead,2)), eachcol(basis))
        basis = basis[:, basis_valid]
    end
    return basis
end

function divide(a, lead, n, llead)
    return any(j->all(i->lead[i,j]<=a[i], 1:n), 1:llead)
end

function reminder(a, x, gb, n)
    remind = rem(prod(x.^a), gb)
    mon = monomials(remind)
    coe = coefficients(remind)
    lm = length(mon)
    supp = zeros(UInt8,n,lm)
    for i = 1:lm, j = 1:n
        @inbounds supp[j,i]=MultivariatePolynomials.degree(mon[i],x[j])
    end
    return lm,supp,coe
end

function newton_basis(n, d, supp; e=1e-5)
    lsupp = size(supp,2)
    basis = get_basis(n, d)
    lb = size(basis,2)
    A0 = [-1/2*supp' ones(lsupp,1)]
    t = 1
    indexb = [i for i=1:lb]
    temp = sortslices(supp, dims=2)
    while t <= lb
          i = indexb[t]
          if bfind(temp, lsupp, UInt8(2)*basis[:,i]) != 0
             t += 1
          else
             model = Model(optimizer_with_attributes(Mosek.Optimizer))
             set_optimizer_attribute(model, MOI.Silent(), true)
             @variable(model, x[1:n+1], lower_bound=-10, upper_bound=10)
             @constraint(model, [A0; [basis[:,i]' -1]]*x .<= zeros(lsupp+1))
             @objective(model, Min, [basis[:,i]' -1]*x)
             optimize!(model)
             vx = value.(x)
             if abs(objective_value(model)) <= e && sum(abs.(vx)) <= e
                t += 1
             else
                if abs(objective_value(model)) <= e && sum(abs.(vx)) > e
                   t += 1
                else
                   lb -= 1
                   indexb = deleteat!(indexb, t)
                end
                r = t
                while lb >= r
                      j = indexb[r]
                      if [basis[:,j]' -1]*vx <= -e
                         lb -= 1
                         indexb = deleteat!(indexb, r)
                      else
                         r += 1
                      end
                end
             end
          end
    end
    return basis[:,indexb]
end

function generate_basis!(supp, basis)
    supp = sortslices(supp, dims=2)
    supp = unique(supp, dims=2)
    lsupp = size(supp, 2)
    lb = size(basis, 2)
    indexb = UInt32[]
    for i = 1:lb, j = i:lb
        bi = basis[:,i] + basis[:,j]
        if bfind(supp, lsupp, bi) != 0
             push!(indexb, i, j)
        end
    end
    sort!(indexb)
    unique!(indexb)
    return basis[:,indexb]
end

function bfind(A, l, a)
    low=1
    high=l
    while low <= high
        mid = Int(ceil(1/2*(low+high)))
        if ndims(A) == 2
            temp = A[:, mid]
        else
            temp = A[mid]
        end
        if temp == a
           return mid
        elseif temp < a
           low = mid+1
        else
           high = mid-1
        end
    end
    return 0
end

function get_graph(tsupp::Array{UInt8, 2}, basis::Array{UInt8, 2}; nb=0)
    lb = size(basis,2)
    G = SimpleGraph(lb)
    ltsupp = size(tsupp,2)
    for i = 1:lb, j = i+1:lb
        bi = bin_add(basis[:,i], basis[:,j], nb)
        if bfind(tsupp, ltsupp, bi) != 0
           add_edge!(G, i, j)
        end
    end
    return G
end

function get_blocks(tsupp, basis; sb=[], numb=[], nb=0, TS="block", minimize=false,
    QUIET=true, merge=false, md=3)
    if TS == false
        blocksize = [size(basis,2)]
        blocks = [[i for i=1:size(basis,2)]]
        cl = 1
    else
        G = get_graph(tsupp, basis, nb=nb)
        if TS == "block"
            blocks = connected_components(G)
            blocksize = length.(blocks)
            cl = length(blocksize)
        else
            blocks,cl,blocksize = chordal_cliques!(G, method=TS, minimize=minimize)
            if merge == true
                blocks,cl,blocksize = clique_merge!(blocks, d=md, QUIET=true)
            end
        end
    end
    nsb = sort(unique(blocksize), rev=true)
    nnumb = [sum(blocksize.== i) for i in nsb]
    if isempty(sb) || nsb!=sb || nnumb!=numb
        status = 1
        if QUIET == false
            println("------------------------------------------------------")
            println("The sizes of PSD blocks:\n$nsb\n$nnumb")
            println("------------------------------------------------------")
        end
    else
        status = 0
        println("No higher TSSOS hierarchy!")
    end
    return blocks,cl,blocksize,nsb,nnumb,status
end

function blockupop(n, supp, coe, basis, blocks, cl, blocksize; nb=0, solver="Mosek",
    QUIET=true, solve=true, solution=false, MomentOne=false, Gram=false)
    tsupp = zeros(UInt8, n, Int(sum(Int.(blocksize).^2+blocksize)/2))
    k = 1
    for i = 1:cl, j = 1:blocksize[i], r = j:blocksize[i]
        @inbounds bi = bin_add(basis[:,blocks[i][j]], basis[:,blocks[i][r]], nb)
        @inbounds tsupp[:,k] = bi
        k += 1
    end
    if MomentOne == true || solution == true
        ksupp = copy(tsupp)
        ksupp = unique(ksupp, dims=2)
        ksupp = sortslices(ksupp, dims=2)
        tsupp = [tsupp get_basis(n,2,nb=nb)]
        tsupp = unique(tsupp, dims=2)
        tsupp = sortslices(tsupp, dims=2)
    else
        tsupp = unique(tsupp, dims=2)
        tsupp = sortslices(tsupp, dims=2)
        ksupp = tsupp
    end
    objv = nothing
    moment = nothing
    GramMat = nothing
    if solve == true
        ltsupp = size(tsupp,2)
        if QUIET == false
            println("Assembling the SDP...")
            println("There are $ltsupp affine constraints.")
        end
        if solver == "Mosek"
            model = Model(optimizer_with_attributes(Mosek.Optimizer))
        elseif solver == "COSMO"
            model = Model(optimizer_with_attributes(COSMO.Optimizer, "max_iter" => 10000))
        elseif solver == "SDPT3"
            model = Model(optimizer_with_attributes(SDPT3.Optimizer))
        else
            @error "The solver is currently not supported!"
            return nothing,nothing,nothing
        end
        set_optimizer_attribute(model, MOI.Silent(), QUIET)
        cons = [AffExpr(0) for i=1:ltsupp]
        if MomentOne == true || solution == true
            pos0 = @variable(model, [1:n+1, 1:n+1], PSD)
            for j = 1:n+1, k = j:n+1
                @inbounds bi = bin_add(basis[:,j],basis[:,k],nb)
                Locb = bfind(tsupp, ltsupp, bi)
                if j == k
                   @inbounds add_to_expression!(cons[Locb], pos0[j,k])
                else
                   @inbounds add_to_expression!(cons[Locb], 2, pos0[j,k])
                end
            end
        end
        pos = Vector{Union{VariableRef,Symmetric{VariableRef}}}(undef, cl)
        for i = 1:cl
            bs = blocksize[i]
            if bs == 1
               @inbounds pos[i] = @variable(model, lower_bound=0)
               @inbounds bi = bin_add(basis[:,blocks[i][1]], basis[:,blocks[i][1]], nb)
               Locb = bfind(tsupp, ltsupp, bi)
               @inbounds add_to_expression!(cons[Locb], pos[i])
            else
               @inbounds pos[i] = @variable(model, [1:bs, 1:bs], PSD)
               for j = 1:blocksize[i], r = j:blocksize[i]
                   @inbounds bi = bin_add(basis[:,blocks[i][j]], basis[:,blocks[i][r]], nb)
                   Locb = bfind(tsupp, ltsupp, bi)
                   if j == r
                       @inbounds add_to_expression!(cons[Locb], pos[i][j,r])
                   else
                       @inbounds add_to_expression!(cons[Locb], 2, pos[i][j,r])
                   end
               end
            end
        end
        bc = zeros(ltsupp)
        for i = 1:size(supp, 2)
            Locb = bfind(tsupp, ltsupp, supp[:,i])
            if Locb == 0
               @error "The monomial basis is not enough!"
               return nothing,nothing,nothing
            else
               bc[Locb] = coe[i]
            end
        end
        @variable(model, lower)
        cons[1] += lower
        @constraint(model, con[i=1:ltsupp], cons[i]==bc[i])
        @objective(model, Max, lower)
        if QUIET == false
            println("Solving the SDP...")
        end
        time = @elapsed begin
        optimize!(model)
        end
        if QUIET == false
            println("SDP solving time: $time seconds.")
        end
        status = termination_status(model)
        objv = objective_value(model)
        if status != MOI.OPTIMAL
           println("termination status: $status")
           status = primal_status(model)
           println("solution status: $status")
        end
        println("optimum = $objv")
        if Gram == true
            GramMat = [value.(pos[i]) for i = 1:cl]
        end
        if solution == true
            dual_var = -dual.(con)
            moment = zeros(Float64, n+1, n+1)
            for j = 1:n+1, k = j:n+1
                bi = bin_add(basis[:,j], basis[:,k], nb)
                Locb = bfind(tsupp, ltsupp, bi)
                moment[j,k] = dual_var[Locb]
            end
            moment = Symmetric(moment,:U)
        end
    end
    return objv,ksupp,moment,GramMat
end

function extract_solutions(moment, opt, pop, x; numeq=0, tol=1e-4)
    n = length(x)
    m = length(pop) - 1
    F = eigen(moment, n+1:n+1)
    sol = sqrt(F.values[1])*F.vectors[:,1]
    if abs(sol[1]) < 1e-8
        sol = zeros(n)
        flag = 1
    else
        sol = sol[2:end]/sol[1]
        flag = 0
        upper_bound = polynomial(pop[1])(x => sol)
        gap = abs(upper_bound)>1 ? abs((opt-upper_bound)/upper_bound) : abs(opt-upper_bound)
        if gap >= tol
            flag = 1
        end
        for i = 1:m-numeq
            if polynomial(pop[i+1])(x => sol) <= -tol
                flag = 1
            end
        end
        for i = m-numeq+1:m
            if abs(polynomial(pop[i+1])(x => sol)) >= tol
                flag = 1
            end
        end
    end
    if flag == 0
        println("Global optimality certified!")
    end
    return sol,flag
end


using ForwardDiff, Distributions, FiniteDifferences
using Tracker, Zygote, ReverseDiff, Random, LinearAlgebra, PDMats
using DistributionsAD, Test, LinearAlgebra, Combinatorics
using ForwardDiff: Dual
using StatsFuns: binomlogpdf, logsumexp
const FDM = FiniteDifferences
using DistributionsAD: TuringMvNormal, TuringMvLogNormal, TuringUniform
using Distributions: meanlogdet

include("test_utils.jl")
include("distributions.jl")
include("others.jl")


using LinLogQuantization
using Test

@testset "minpos" begin
    @test minpos([0,0,0,0]) == 0
    @test minpos([0,0,1,0]) == 1

    A = randn(100)
    @test minpos(A) > 0
    @test minpos(A) == minimum(A[A.>0])

    A = rand(100) .+ 1
    @test minpos(A) == minimum(A)
end

@testset "Linear quantization" begin
    for T in [Float64,Float32,Float16]
        for s in [(100,),
                    (10,20),
                    (13,14,15),
                    (23,17,12,5)]

            A = rand(T,s...)

            for LinQ in [LinQuant8Array,
                        LinQuant16Array,
                        LinQuant24Array,
                        LinQuant32Array]

                # initial conversion is not reversible
                # due to rounding errors
                A2 = Array{T}(LinQ(A))

                # then test whether back&forth conversion is reversible
                @test A2 == Array{T}(LinQ(A2))
            end
        end
    end
end

@testset "Log quantization" begin
    for T in [Float64,Float32,Float16]
        for s in [(100,),
                    (10,20),
                    (13,14,15),
                    (23,17,12,5)]

            A = rand(T,s...)

            for LogQ in [LogQuant8Array,
                        LogQuant16Array,
                        LogQuant24Array,
                        LogQuant32Array]

                for rn in [:linspace,:logspace]

                    # initial conversion is not reversible
                    # due to rounding errors
                    A2 = Array{T}(LogQ(A,rn))

                    # then test whether back&forth conversion is reversible
                    @test A2 == Array{T}(LogQ(A2,rn))
                end
            end
        end
    end
end

@testset "LinQuant along dimension" begin
    A = rand(Float32,10,20,30,40)
    Q = LinQuant32Array(A,4)
    @test A ≈ Array{Float32}(Q)

    Q = LinQuant24Array(A,4)
    @test A ≈ Array{Float32}(Q)

    Q = LinQuant16Array(A,4)
    @test A ≈ Array{Float32}(Q)

    Q = LinQuant8Array(A,4)
    @test all(isapprox.(A,Array{Float32}(Q),atol=1e-1))
end

@testset "LogQuant along dimension" begin
    A = rand(Float32,10,20,30,40)
    Q = LogQuant32Array(A,4)
    @test A ≈ Array{Float32}(Q)

    Q = LogQuant24Array(A,4)
    @test A ≈ Array{Float32}(Q)

    Q = LogQuant16Array(A,4)
    @test A ≈ Array{Float32}(Q)

    Q = LogQuant8Array(A,4)
    @test all(isapprox.(A,Array{Float32}(Q),atol=1e-1))
end

@testset "Linear quantization will all 0" begin
    for T in [Float64,Float32,Float16]
        for s in [(100,),
                    (10,20),
                    (3,4,5),
                    (6,7,8,9)]

            A = zeros(T,s...)

            for LinQ in [LinQuant8Array,
                        LinQuant16Array,
                        LinQuant24Array,
                        LinQuant32Array]

                # initial conversion is not reversible
                # due to rounding errors
                A2 = Array{T}(LinQ(A))

                # then test whether back&forth conversion is reversible
                @test A2 == Array{T}(LinQ(A2))
            end
        end
    end
end

@testset "Log quantization will all 0" begin
    for T in [Float64,Float32,Float16]
        for s in [(100,),
                    (10,20),
                    (3,4,5),
                    (6,7,8,9)]

            A = zeros(T,s...)

            for LogQ in [LogQuant8Array,
                LogQuant16Array,
                LogQuant24Array,
                LogQuant32Array]

                # initial conversion is not reversible
                # due to rounding errors
                A2 = Array{T}(LogQ(A))

                # then test whether back&forth conversion is reversible
                @test A2 == Array{T}(LogQ(A2))
            end
        end
    end
end

@testset "Linear quantization will all c" begin
    for T in [Float64,Float32,Float16]
        for s in [(100,),
                    (10,20),
                    (3,4,5),
                    (6,7,8,9)]

            A = zeros(T,s...) .+ T(randn())

            for LinQ in [LinQuant8Array,
                        LinQuant16Array,
                        LinQuant24Array,
                        LinQuant32Array]

                # initial conversion is not reversible
                # due to rounding errors
                A2 = Array{T}(LinQ(A))

                # then test whether back&forth conversion is reversible
                @test A2 == Array{T}(LinQ(A2))
            end
        end
    end
end

@testset "Log quantization will all c" begin
    for T in [Float64,Float32,Float16]
        for s in [(100,),
                    (10,20),
                    (3,4,5),
                    (6,7,8,9)]

            A = zeros(T,s...) .+ T(rand())

            for LogQ in [LogQuant8Array,
                LogQuant16Array,
                LogQuant24Array,
                LogQuant32Array]

                # initial conversion is not reversible
                # due to rounding errors
                A2 = Array{T}(LogQ(A))

                # then test whether back&forth conversion is reversible
                @test A2 == Array{T}(LogQ(A2))
            end
        end
    end
end


module test_flux_basic

using Test
using Flux.Tracker

W = # rand(2, 5)
[0.857747   0.291713  0.179873  0.938979  0.51022
 0.0852085  0.977716  0.246164  0.460672  0.772312]

b = # rand(2)
[0.663369
 0.132996]

predict(x) = W*x .+ b
loss(x, y) = sum((predict(x) .- y).^2)

x, y = # rand(5), rand(2)
([0.496864, 0.947507, 0.874288, 0.251528, 0.192234], [0.901991, 0.0802404])

@test loss(x, y) ≈ 3.166070569359008

W = param(W)
b = param(b)
l = loss(x, y)

@test W isa TrackedArray
@test b isa TrackedArray
@test l isa Tracker.TrackedReal
@test l.tracker.grad == 0

@test W.grad == [0.0  0.0  0.0  0.0  0.0
                 0.0  0.0  0.0  0.0  0.0]

back!(l)
@test W.grad ≈ [0.949491  1.81065  1.67074  0.480662  0.367353
                1.49163   2.84449  2.62468  0.755107  0.577102] atol=0.00001

W.data .-= 0.1(W.grad)
@test W.data ≈ [0.762798   0.110648   0.0127994  0.890913  0.473485
               -0.0639541  0.693267  -0.0163043  0.385161  0.714602] atol=0.00001

end # module test_flux_basic