@inline function calc_A_third_order(x::VInt16, ::Val{N}) where N
    p = 15; r = 1; A = Int16(23170); C = Int16(-54292 >> N)
    rounding = one(x) << (p - N - 1)
    (rounding + A + (x * (x * C) >> N) >> r) >> (p - N)
end

@inline function calc_B_third_order(x::VInt16, ::Val{N}) where N
    p = 14; r = 3; B = Int16(-18199); D = Int16(58049 >> N);
    rounding = one(x) << (p - N - 1)
    (rounding + x * (B + (x * (x * D) >> N) >> r) >> N) >> (p - N)
end

@inline function calc_A_third_order(x::VInt32, ::Val{N}) where N
    p = 31; r = 1; A = Int32(1518500249); C = Int32(-3558067408 >> N)
    rounding = one(x) << (p - N - 1)
    (rounding + A + (x * (x * C) >> N) >> r) >> (p - N)
end
@inline function calc_B_third_order(x::VInt32, ::Val{N}) where N
    p = 30; r = 3; B = Int32(-1192627308); D = Int32(3804335470 >> N);
    rounding = one(x) << (p - N - 1)
    (rounding + x * (B + (x * (x * D) >> N) >> r) >> N) >> (p - N)
end

@inline function calc_A(x::VInt16, bits::B) where B <: Union{Val{6}, Val{7}}
    calc_A_third_order(x, bits)
end
@inline function calc_B(x::VInt16, bits::B) where B <: Union{Val{6}, Val{7}}
    calc_B_third_order(x, bits)
end
@inline function calc_A(x::VInt32, bits::B) where B <: Union{Val{8}}
    calc_A_third_order(x, bits)
end
@inline function calc_B(x::VInt32, bits::B) where B <: Union{Val{8}}
    calc_B_third_order(x, bits)
end


function resultConverter_v2(x, V, anchoLado, matConexionVertices, vecVertices, vecAlturas)

    template = Int(x[end])
    alt = min(x[1], maximum(vecAlturas))
    theta = x[2]

    # Genera Poligono que corta VolTeor a altura alt
    idAlt = findfirst(y -> y >= alt, vecAlturas) - 1
    vert_poly_0 = Int.(vecVertices[idAlt])
    vert_poly_1 = Int.(matConexionVertices[vert_poly_0,2])
    poly_0 = copy(V[vert_poly_0, 1:2])[:,1,:]
    poly_1 = copy(V[vert_poly_1, 1:2])[:,1,:]
    alfa_ = (vecAlturas[idAlt + 1] - alt) / (vecAlturas[idAlt + 1] - vecAlturas[idAlt])
    polyCorte_alt = alfa_ .* poly_0 .+ (1 - alfa_) .* poly_1

    if template == 1

        alfa = x[3]
        pos_x = x[4]
        pos_y = x[5]
        largo1 = x[6]
        largo2 = x[7]

        R1 = poly2D.rotationMatrix(theta);
        cr1 = [pos_x; pos_y]
        p1_1 = [pos_x; pos_y];
        p2_1 = R1 * ([pos_x + largo1; pos_y] - cr1) + cr1;
        p3_1 = R1 * ([pos_x + largo1; pos_y + anchoLado] - cr1) + cr1;
        p4_1 = R1 * ([pos_x ; pos_y + anchoLado] - cr1) + cr1;
        R2 = poly2D.rotationMatrix(alfa + theta);
        cr2 = [pos_x; pos_y]
        p1_2 = [pos_x; pos_y];
        p2_2 = R2 * ([pos_x + anchoLado; pos_y] - cr2) + cr2;
        p3_2 = R2 * ([pos_x + anchoLado; pos_y + largo2] - cr2) + cr2;
        p4_2 = R2 * ([pos_x; pos_y + largo2] - cr2) + cr2;
        V1 = [p1_1';p2_1';p3_1';p4_1']
        V2 = [p1_2';p2_2';p3_2';p4_2'];

        ps1 = PolyShape([V1], 1)
        ps2 = PolyShape([V2], 1)
        psCorte = PolyShape([polyCorte_alt], 1)

        ps_base = polyShape.polyUnion_v2(ps1, ps2)
        areaBasal = polyShape.polyArea_v2(ps_base)
        ps_baseSeparada = PolyShape([V1, V2], 2)

    elseif template == 2
        phi1 = x[3]
        phi2 = x[4]
        pos_x0 = x[5]
        pos_y0 = x[6]
        largo0 = x[7]
        largo1 = x[8]
        largo2 = x[9]

        R_theta = poly2D.rotationMatrix(theta);
        cr_theta  = [pos_x0; pos_y0];

        p1_0  = (R_theta * ([pos_x0; pos_y0] - cr_theta) + cr_theta)'
        p2_0  = (R_theta * ([pos_x0 + largo0; pos_y0] - cr_theta) + cr_theta)'
        p3_0  = (R_theta * ([pos_x0 + largo0; pos_y0 + anchoLado] - cr_theta) + cr_theta)'
        p4_0  = (R_theta * ([pos_x0; pos_y0 + anchoLado] - cr_theta) + cr_theta)'

        R_phi1 = poly2D.rotationMatrix(phi1);
        cr_phi1  = [pos_x0; pos_y0];
        p1_1  = (R_theta * ((R_phi1 * ([pos_x0; pos_y0] - cr_phi1) + cr_phi1) - cr_theta) + cr_theta)';
        p2_1  = (R_theta * ((R_phi1 * ([pos_x0 + anchoLado; pos_y0] - cr_phi1) + cr_phi1) - cr_theta) + cr_theta)';
        p3_1  = (R_theta * ((R_phi1 * ([pos_x0 + anchoLado; pos_y0 + largo1] - cr_phi1) + cr_phi1) - cr_theta) + cr_theta)';
        p4_1  = (R_theta * ((R_phi1 * ([pos_x0; pos_y0 + largo1] - cr_phi1) + cr_phi1) - cr_theta) + cr_theta)';

        R_phi2 = poly2D.rotationMatrix(-phi2);
        cr_phi2  = [pos_x0 + largo0; pos_y0];
        p1_2  = (R_theta * ((R_phi2 * ([pos_x0 + largo0 - anchoLado; pos_y0] - cr_phi2) + cr_phi2) - cr_theta) + cr_theta)';
        p2_2  = (R_theta * ((R_phi2 * ([pos_x0 + largo0; pos_y0] - cr_phi2) + cr_phi2) - cr_theta) + cr_theta)';
        p3_2  = (R_theta * ((R_phi2 * ([pos_x0 + largo0; pos_y0 + largo2] - cr_phi2) + cr_phi2) - cr_theta) + cr_theta)';
        p4_2  = (R_theta * ((R_phi2 * ([pos_x0 + largo0 - anchoLado; pos_y0 + largo2] - cr_phi2) + cr_phi2) - cr_theta) + cr_theta)';

        V0 = [p1_0;p2_0;p3_0;p4_0]
        V1 = [p1_1;p2_1;p3_1;p4_1]
        V2 = [p1_2;p2_2;p3_2;p4_2]

        ps0 = PolyShape([V0], 1)
        ps1 = PolyShape([V1], 1)
        ps2 = PolyShape([V2], 1)
        psCorte = PolyShape([polyCorte_alt], 1)

        ps_base = polyShape.polyUnion_v2(ps0, ps1)
        ps_base = polyShape.polyUnion_v2(ps_base, ps2)
        areaBasal = polyShape.polyArea_v2(ps_base)
        ps_baseSeparada = PolyShape([V0, V1, V2], 3)

    elseif template == 3

    elseif template == 4

        alfa = x[3]
        pos_x = x[4]
        pos_y = x[5]

        largo1 = x[6] #
        largo2 = x[7] #

        R1 = poly2D.rotationMatrix(theta);
        cr1 = [pos_x; pos_y]
        p1_1 = [pos_x; pos_y];
        p2_1 = R1 * ([pos_x + largo1; pos_y] - cr1) + cr1;
        p3_1 = R1 * ([pos_x + largo1; pos_y + anchoLado] - cr1) + cr1;
        p4_1 = R1 * ([pos_x ; pos_y + anchoLado] - cr1) + cr1;
        R2 = poly2D.rotationMatrix(theta - alfa);
        cr2 = [pos_x; pos_y]
        p1_2 = R2 * ([pos_x - anchoLado; pos_y] - cr2) + cr2;
        p2_2 = R2 * ([pos_x; pos_y] - cr2) + cr2;
        p3_2 = R2 * ([pos_x; pos_y + largo2] - cr2) + cr2;
        p4_2 = R2 * ([pos_x - anchoLado; pos_y + largo2] - cr2) + cr2;
        V1 = [p1_1';p2_1';p3_1';p4_1']
        V2 = [p1_2';p2_2';p3_2';p4_2'];

        ps1 = PolyShape([V1], 1)
        ps2 = PolyShape([V2], 1)
        psCorte = PolyShape([polyCorte_alt], 1)

        ps_base = polyShape.polyUnion_v2(ps1, ps2)
        areaBasal = polyShape.polyArea_v2(ps_base)
        ps_baseSeparada = PolyShape([V1, V2], 2)

    end

    return alt, areaBasal, ps_base, ps_baseSeparada, psCorte

end


function champs_2(t, fft_ubar, fft_vbar, fft_ug, fft_vg,
                  A1, A2, matr, conjmatr, sigma, llambda,
                  ktau, epsilon, Ntaumm)

    champu, champv = ftau(t, fft_ubar, fft_vbar,
                     A1, A2, matr, conjmatr, sigma, llambda)

    champu_fft = fft(champu, 1)
    champv_fft = fft(champv, 1)

    champu_fft[1, :] .= 0.0
    champv_fft[1, :] .= 0.0

    dtauh1u = ifft(champu_fft, 1)
    dtauh1v = ifft(champv_fft, 1)

    h1u = epsilon * ifft(champu_fft ./ (1im * ktau), 1)
    h1v = epsilon * ifft(champv_fft ./ (1im * ktau), 1)

    C1u = fft_ubar .+ h1u
    C1v = fft_vbar .+ h1v

    ffu, ffv = ftau(t,
                    C1u .+ fft_ug,
                    C1v .+ fft_vg,
                    A1, A2, matr, conjmatr,
                    sigma, llambda)

    champu, champv = ftau(t, C1u, C1v, A1, A2, matr, conjmatr, sigma, llambda)

    champu_fft = fft(champu, 1)
    champv_fft = fft(champv, 1)

    champubaru = champu_fft[1, :] / Ntaumm
    champubarv = champv_fft[1, :] / Ntaumm

    champu1, champv1 = duftau(t, fft_ubar, fft_vbar,
                              champubaru, champubarv,
                              A1, A2, matr, conjmatr, sigma, llambda,
                              Ntaumm)

    champu2, champv2 = dtftau(t, fft_ubar, fft_vbar,
                              A1, A2, matr, conjmatr,
                              sigma, llambda, Ntaumm)

    champu_fft = fft(champu1 .+ champu2, 1)
    champv_fft = fft(champv1 .+ champv2, 1)

    champu_fft[1, :] .= 0.0
    champv_fft[1, :] .= 0.0

    dtC1u = champubaru .+ epsilon * ifft(champu_fft / (1im * ktau), 1)
    dtC1v = champubarv .+ epsilon * ifft(champv_fft / (1im * ktau), 1)

    champgu = ffu .- dtauh1u .- dtC1u
    champgv = ffv .- dtauh1v .- dtC1v

    champgu_fft = fft(champgu, 1)
    champgv_fft = fft(champgv, 1)

    champmoyu = champgu_fft[1, :] / Ntaumm
    champmoyv = champgv_fft[1, :] / Ntaumm

    champgu_fft[1, :] .= 0.0
    champgv_fft[1, :] .= 0.0

    ichampgu = ifft(champgu_fft ./ (1im * ktau), 1)
    ichampgv = ifft(champgv_fft ./ (1im * ktau), 1)

    champubaru, champubarv, ichampgu, ichampgv, champmoyu, champmoyv

end


function kpp_psis(xi)
if xi>=0
  y=1+5*xi;
elseif (xi<0) & (xi>=-1.)
  y=(1-16*xi)^(-1/2);
else
  y=(-28.86-98.96*xi)^(-1/3);
end
return y
end


using KernelFunctions
using MLDataUtils
using Zygote
using Flux
using Distributions, LinearAlgebra
using Plots

N = 100 #Number of samples
μ = randn(2,2) # Random Centers
xgrid = range(-3,3,length=100) # Create a grid
Xgrid = hcat(collect.(Iterators.product(xgrid,xgrid))...)' #Combine into a 2D grid
y = rand([-1,1],N) # Select randomly between the two classes
X = zeros(N,2)
X[y.==1,:] = rand(MvNormal(μ[:,1],I),count(y.==1))' #Attribute samples from class 1
X[y.==-1,:] = rand(MvNormal(μ[:,2],I),count(y.==-1))' # Attribute samples from class 2

k = SqExponentialKernel(2.0) # Create kernel function
f(x,k,λ) = kernelmatrix(k,x,X,obsdim=1)*inv(kernelmatrix(k,X,obsdim=1)+exp(λ[1])*I)*y # Optimal prediction f
svmloss(y,ŷ)= f(X,k,λ) |> ŷ -> sum(maximum.(0.0,1-y*ŷ)) - λ*norm(ŷ) # Total svm loss with regularisation
pred = f(Xgrid,k,λ) #Compute prediction on a grid
contourf(xgrid,xgrid,pred)
scatter!(eachcol(X)...,color=y,lab="data")


# Outputs the value of the objective function at x.
function mixobjective(L, x, eps = 0)
  return -sum(log.(L * x .+ eps))
end

# Fit a mixture model using the sequential quadratic programming
# algorithm ("mix-SQP"). Input argument L is the n x m conditional
# likelihood matrix, where n is the number of samples and m is the
# number of mixture components. Optional input argument x is the
# initial estimate of the mixture weights (when one or more entries of
# x are negative, the default initial estimate is used in which all
# the entries are set to 1/k, where k = size(L,2).
function mixSQP(L; x = -1, convtol = 1e-8, pqrtol = 1e-8, eps = 1e-8,
                sptol = 1e-6, maxiter = 100, maxqpiter = 100,
                lowrank = "svd", qpsubprob = "activeset",
                verbose = true)

  # Get the number of rows (n) and columns (k) of L.
  n = size(L,1);
  k = size(L,2);

  # If any entries of input x are negative, set the initial estimate
  # to the default setting. When the MOSEK solver is used, the initial
  # estimate is set to a sparse vector in which all but two of the
  # entries are zero.
  if any(x .< 0)
    x = ones(k)/k;
  end

  # If requested (i.e., if pqrtol > 0), compute the partial QR
  # decomposition with relative precision "tol", then retrieve the
  # permutation matrix, P. For details on the partial QR, see
  # https://github.com/klho/LowRankApprox.jl.
  if lowrank != "none" && qpsubprob == "mosek"
    error("lowrank must be \"none\" when qpsubprob = \"mosek\"");
  end
  if lowrank == "qr"
    F = pqrfact(L, rtol = pqrtol);
    P = sparse(F[:P]);
  elseif lowrank == "svd"
    F = psvdfact(L, rtol = pqrtol);
    S = Diagonal(F[:S]);
  end

  # Summarize the analysis here.
  if verbose
    @printf("Running SQP algorithm with the following settings:\n")
    @printf("- %d x %d data matrix\n",n,k)
    @printf("- convergence tolerance  = %0.2e\n",convtol)
    @printf("- zero threshold         = %0.2e\n",sptol)
    if lowrank == "qr"
      err = maximum(abs.(F[:Q]*F[:R]*P' - L));
      @printf("- partial QR tolerance   = %0.2e\n",pqrtol)
      @printf("- partial QR max. error  = %0.2e\n",err)
    elseif lowrank == "svd"
      err = maximum(abs.(F[:U]*S*F[:Vt] - L));
      @printf("- partial SVD tolerance  = %0.2e\n",pqrtol)
      @printf("- partial SVD max. error = %0.2e\n",err)
    else
      @printf("- Exact derivative computation (partial QR not used).\n")
    end
  end

  # Initialize storage for the outputs obj, gmin, nnz and nqp.
  obj      = zeros(maxiter);
  gmin     = zeros(maxiter);
  nnz      = zeros(Int,maxiter);
  nqp      = zeros(Int,maxiter);
  timing   = zeros(maxiter);
  qptiming = zeros(maxiter);

  # Print the column labels for reporting the algorithm's progress.
  if verbose
    @printf("iter      objective -min(g+1)  #nz #qp #ls\n")
  end

  # Repeat until we reach the maximum number of iterations, or until
  # convergence is reached.
  numiter = 0;
  numls   = 0;
  for i = 1:maxiter
    numiter = i;

    out, timing[i] = @timed begin

      # Compute the gradient and Hessian, optionally using the partial
      # QR decomposition to increase the speed of these computations.
      # gradient and Hessian computation -- Rank reduction method
      if lowrank == "qr"
        D = 1 ./ (F[:Q]*(F[:R]*(P'*x)) .+ eps);
        g = -P * (F[:R]' * (F[:Q]'*D)/n);
        H = P * (F[:R]' * (F[:Q]'*Diagonal(D.^2)*F[:Q])*F[:R])*P'/n +
            eps*Diagonal(ones(k));
      elseif lowrank == "svd"
        D = 1 ./ (F[:U]*(S*(F[:Vt]*x)) .+ eps);
        g = -F[:Vt]'*(S * (F[:U]'*D))/n;
        H = (F[:V]*S*(F[:U]'*Diagonal(D.^2)*F[:U])* S*F[:Vt])/n +
            eps*Diagonal(ones(k));
      else
        D = 1 ./ (L*x .+ eps);
        g = -L'*D/n;
        H = L'*Diagonal(D.^2)*L/n .+ eps * Diagonal(ones(k));
      end

      # Report on the algorithm's progress.
      obj[i]  = mixobjective(L,x,eps);
      gmin[i] = minimum(g) .+ 1;
      nnz[i]  = length(findall(x .> sptol));
      if verbose
        if i == 1
          @printf("%4d %0.8e %+0.2e %4d\n",i,obj[i],-gmin[i],nnz[i]);
        else
          @printf("%4d %0.8e %+0.2e %4d %3d %3d\n",i,obj[i],
                  -gmin[i],nnz[i],nqp[i - 1],numls);
        end
      end

      # Check convergence of outer loop.
      if gmin[i] >= -convtol
        break
      end

      # Solve the QP subproblem using either the active-set or
      # interior-point (MOSEK) method.
      out, qptiming[i] = @timed if qpsubprob == "activeset"
        y, nqp[i] = qpactiveset(x,g,H,convtol = convtol,sptol = sptol,
                                maxiter = maxqpiter);
      elseif qpsubprob == "mosek"
        y = qpmosek(x,g,H);
      end

      # Perform backtracking line search
      for t = 1:10
        numls = t;
        if lowrank == "qr"
          D_new = 1 ./ (F[:Q]*(F[:R]*(P'*y)) .+ eps);
        elseif lowrank == "svd"
          D_new = 1 ./ (F[:U]*(S*(F[:Vt]*y)) .+ eps);
        else
          D_new = 1 ./ (L*y .+ eps);
        end
        if all(D_new .> 0)
          if sum(log.(D)) - sum(log.(D_new)) > sum((x - y) .* g) / 2
            break
          end
        end
        y = (y-x)/2 + x;
      end

      numiter = i;
    end

    # Update the solution to the original optimization problem.
    x = copy(y);
  end

  # Return: (1) the solution (after zeroing out any values below the
  # tolerance); (2) the value of the objective at each iteration; (3)
  # the minimum gradient value of the modified objective at each
  # iteration; (4) the number of nonzero entries in the vector at each
  # iteration; (5) the number of inner iterations taken to solve the
  # QP subproblem at each outer iteration; (6) the total runtime for
  # each iteration; and (7) the runtime for solving the quadratic
  # subproblem at each iteration.
  x[x .< sptol] .= 0;
  x = x/sum(x);
  return Dict([("x",Vector(x)),
               ("obj",obj[1:numiter]),
               ("gmin",gmin[1:numiter]),
               ("nnz",nnz[1:numiter]),
               ("nqp",nqp[1:numiter]),
               ("timing",timing[1:numiter]),
               ("qptiming",qptiming[1:numiter])])
end

# Solve the QP subproblem for the mix-SQP algorithm using an active
# set method.
function qpactiveset(x, g, H; convtol = 1e-8, sptol = 1e-6, maxiter = 100)

  # Get the number of degrees of freedom in the optimization problem.
  k = length(x);

  # Initialize the solution to the QP subproblem.
  y       = zeros(k);
  ind     = findall(x .> sptol);
  y[ind] .= 1/length(ind);

  # Repeat until we reach the maximum number of iterations, or until
  # convergence is reached.
  numiter = 0;
  for i = 1:maxiter
    numiter = i;

    # Define the smaller QP subproblem.
    s   = length(ind);
    H_s = H[ind,ind];
    d   = H*y + 2*g .+ 1;
    d_s = d[ind];

    # Solve the smaller problem.
    p      = zeros(k);
    p_s    = -H_s \ d_s;
    p[ind] = p_s;

    # Check convergence using KKT
    if norm(p_s) < convtol

      # Compute the Lagrange multiplier.
      z = d;
      if all(z .>= -convtol)
        break
      elseif length(ind) < k
        notind  = setdiff(1:k,ind);
        ind_min = notind[findmin(z[notind])[2]];
        ind     = sort([ind; ind_min]);
      end
    else

      # Find a feasible step length.
      alpha     = 1;
      alpha0    = -y[ind] ./ p_s;
      ind_block = findall(p_s .< 0);
      alpha0    = alpha0[ind_block];
      if ~isempty(ind_block)
        v, t = findmin(alpha0);
        if v < 1

          # Blocking constraint.
          ind_block = ind[ind_block[t]];
          alpha     = v;

          # Update working set if there is a blocking constraint.
          deleteat!(ind,findall(ind .- ind_block .== 0));
        end
      end

      # Move to the new "inner loop" iterate (y) along the search
      # direction.
      y = y + alpha * p;
    end
  end

  # Return the solution to the quadratic program.
  return y, numiter
end

# Solve the QP subproblem for the mix-SQP algorithm using MOSEK.
function qpmosek(x, g, H)
  k      = length(g);
  y      = copy(x);
  bkx    = repeat([MSK_BK_LO],k,1)[:];
  blx    = zeros(k);
  bux    = Inf * ones(k);
  numvar = length(bkx);
  c      = 2*g .+ 1;

  maketask() do task
    appendvars(task,numvar)
    putclist(task,[1:numvar;],c)
    putvarboundslice(task,1,numvar+1,bkx,blx,bux)
    Q    = LowerTriangular(H);
    ind  = findall(Q .> 0);
    a    = (i->i[1]).(ind)
    b    = (i->i[2]).(ind)
    putqobj(task,a,b,Q[ind])
    putobjsense(task,MSK_OBJECTIVE_SENSE_MINIMIZE)
    optimize(task)
    y = getxx(task,MSK_SOL_ITR)
  end
  return y
end


"""
find_gaps(time, Δt = median(diff(time)); radius = 0.1Δt)

Find gaps in a time vector and encode them into a matrix S. S' * y results in the original signal with zero filling.

# Arguments

* time:     A vector that encodes the time (eg. seconds, samples, ...)
* Δt:       The expected distance between two measurements, defaults to median(diff(time))
* radius:   If the signal is not non-uniformly sampled, the radius can be used to search in a broader area around the expected sample.
"""
function find_gaps(time, Δt = median(diff(time)); radius = 0.1Δt)
    present = Bool[]
    diffTime = diff(time)
    currentGapLen = 1
    push!(present, true)
    for difft in diffTime
        while currentGapLen * Δt + radius < difft
            push!(present, false)
            currentGapLen += 1
        end
        push!(present, true)
        currentGapLen = 1
    end
    S = I(length(present))[present, :]
    return S
end

"""
simulate_data(f; percentage_gaps = 0.2, max_gap_ratio = 0.3, percentage_outliers = 0.05, σ_noise = 0.2, outlier_strength = 1)

Put some gaps and outlier in the smooth signal f. The underlying model takes the form:
Sy = Sf + x + w

# Keyword Arguments

* percentage_gaps: The percentage of gaps ∈ [0,1]
* max_gap_ratio: Maximal allowed ratio between gap length to total gap length ∈ ]0,1]
* percentage_outliers: The percentage of outliers ∈ [0,1]
* outlier_strength: Strength of the gaussian outlier distribution
* σ_noise: σ of the additive gaussian noise w

# Returns

(y, x, w, S)
"""
function simulate_data(f; percentage_gaps = 0.4, max_gap_ratio = 0.1, percentage_outliers = 0.05, σ_noise = 0.2, outlier_strength = 1)
    N = length(f)
    # Gaps
    present = place_gaps(N, percentage_gaps = percentage_gaps, max_gap_ratio = max_gap_ratio)
    S = I(N)[present, :]
    # Outliers
    x = zeros(N)
    outliers = sample(present, round(Int, percentage_outliers * N), replace = false)
    x[outliers] += rand(Normal(0, outlier_strength), length(outliers))
    # Noise
    w = rand(Normal(0, σ_noise), N)
    y = f .+ x .+ w
    return (y, x, w, S)
end

"""
Get the indices where data is present from the encoding matrix S
"""
get_present(S) = [1:size(S,2);][diag(S' * S) .== 1]

# Distribute the gaps onto the signal, return which samples are present
function place_gaps(N; percentage_gaps = 0.2, max_gap_ratio = 0.3)
    present = collect(1:N)
    total_gaps = round(Int, percentage_gaps * N)
    max_gap = max_gap_ratio * total_gaps
    while total_gaps > 0
        gap_len = sample(1:min(max_gap, total_gaps))
        start = sample(1:(length(present) - gap_len))
        filter!(x -> !(x in start:(start + gap_len - 1)), present)
        total_gaps = length(present) - (N - round(Int, percentage_gaps * N))
    end
    return present
end

# L1 norm
norm1(x) = sum(abs, x)

# L2 norm
norm2(x) = sqrt(sum(abs2, x))


module AbstractOperators

using LinearAlgebra, AbstractFFTs, DSP, FFTW, RecursiveArrayTools

const RealOrComplex{R} = Union{R, Complex{R}}
abstract type AbstractOperator end

abstract type LinearOperator    <: AbstractOperator end
abstract type NonLinearOperator <: AbstractOperator end

import LinearAlgebra: mul!

export LinearOperator,
       NonLinearOperator,
       AbstractOperator
export mul!

# Predicates and properties

include("properties.jl")
include("calculus/AdjointOperator.jl")

## Linear operators

include("linearoperators/MyLinOp.jl")
include("linearoperators/Zeros.jl")
include("linearoperators/ZeroPad.jl")
include("linearoperators/Eye.jl")
include("linearoperators/DiagOp.jl")
include("linearoperators/GetIndex.jl")
include("linearoperators/MatrixOp.jl")
include("linearoperators/LMatrixOp.jl")
include("linearoperators/DFT.jl")
include("linearoperators/RDFT.jl")
include("linearoperators/IRDFT.jl")
include("linearoperators/DCT.jl")
include("linearoperators/FiniteDiff.jl")
include("linearoperators/Variation.jl")
include("linearoperators/Conv.jl")
include("linearoperators/Filt.jl")
include("linearoperators/MIMOFilt.jl")
include("linearoperators/Xcorr.jl")
include("linearoperators/LBFGS.jl")

# Calculus rules

include("calculus/Scale.jl")
include("calculus/DCAT.jl")
include("calculus/HCAT.jl")
include("calculus/VCAT.jl")
include("calculus/Compose.jl")
include("calculus/Reshape.jl")
include("calculus/BroadCast.jl")
include("calculus/Sum.jl")
include("calculus/AffineAdd.jl")
include("calculus/Jacobian.jl")
include("calculus/Axt_mul_Bx.jl")
include("calculus/Ax_mul_Bxt.jl")
include("calculus/Ax_mul_Bx.jl")
include("calculus/HadamardProd.jl")

# Non-Linear operators
include("nonlinearoperators/Pow.jl")
include("nonlinearoperators/Exp.jl")
include("nonlinearoperators/Sin.jl")
include("nonlinearoperators/Cos.jl")
include("nonlinearoperators/Atan.jl")
include("nonlinearoperators/Tanh.jl")
include("nonlinearoperators/Sech.jl")
include("nonlinearoperators/Sigmoid.jl")
include("nonlinearoperators/SoftMax.jl")
include("nonlinearoperators/SoftPlus.jl")

# Syntax
include("syntax.jl")

end


using IncrementalInference
# using KernelDensityEstimate, Gadfly # for vstack

fg = emptyFactorGraph()

N=100

doors = [-100.0;0.0;100.0;300.0]'
cov = [3.0]

v1 = addNode!(fg,:x1,ContinuousScalar,N=N)
f1  = addFactor!(fg,[v1], Obsv2(doors, cov', [1.0]))

# tem = 2.0*randn(1,N)+getVal(v1)+50.0
v2 = addNode!(fg,:x2, ContinuousScalar, N=N)
addFactor!(fg,[v1;v2],Odo([50.0]',[2.0]',[1.0]))

v3=addNode!(fg,:x3,ContinuousScalar, N=N)
addFactor!(fg,[v2;v3],Odo([50.0]',[4.0]',[1.0]))

v4=addNode!(fg,:x4,ContinuousScalar, N=N)
addFactor!(fg,[v3;v4],Odo([50.0]',[2.0]',[1.0]))

v5=addNode!(fg,:x5,ContinuousScalar, N=N)
addFactor!(fg,[v4;v5],Odo([50.0]',[2.0]',[1.0]))

l1=addNode!(fg, :l1, ContinuousScalar, N=N)
addFactor!(fg, [v3,l1], Ranged([64.0],[0.5],[1.0]))

l2=addNode!(fg, :l2, ContinuousScalar, N=N)
addFactor!(fg, [v4,l2], Ranged([16.0],[0.5],[1.0]))

l3=addNode!(fg, :l3, ContinuousScalar, N=N)
addFactor!(fg, [v5,l3], Ranged([16.0],[0.5],[1.0]))

# writeGraphPdf(fg);

tree = prepBatchTree!(fg,drawpdf=true);
# run(`evince bt.pdf`)

# do belief propagation inference over tree once
# inferOverTreeR!(fg, tree, N=100)


# # [Multirate Runge-Kutta Timestepping](@id Multirate-RK-Timestepping)

# In this tutorial, we shall explore the use of Multirate Runge-Kutta
# methods for the solution of nonautonomous (or non time-invariant) equations.
# For our model problem, we shall reuse the acoustic wave test in the GCM
# configuration. See its [code](@ref Acoustic-Wave-Configuration)
# for details on the model and parameters. For the purposes of this tutorial,
# we will only run the experiment for a total of 3600 simulation seconds.
# Details on this test case can be found in Sec. 4.3 of [Giraldo2013](@cite).

using ClimateMachine
const clima_dir = dirname(dirname(pathof(ClimateMachine)));
include(joinpath(
    clima_dir,
    "tutorials",
    "Numerics",
    "TimeStepping",
    "tutorial_acousticwave_config.jl",
))

FT = Float64;

# The typical context for Multirate splitting methods is given by problems
# in which the tendency ``\mathcal{T}`` is assumed to have single parts that
# operate on different time rates (such as a slow time scale and a fast time scale).
# A general form is given by
#
# ``
# 	\dot{\boldsymbol{q}} = \mathcal{T}(\boldsymbol{q}) \equiv
# 	{\mathcal{T}}_{f}(\boldsymbol{q}) + {\mathcal{T}}_{s}(\boldsymbol{q}),
# ``
#
# where the right-hand side has been split into a "fast" component ``{\mathcal{T}_{f}}``,
# and a "slow" component ``{\mathcal{T}_{s}}``.

# Referencing the canonical form introduced in [Time integration](@ref
# Time-integration), both ``{\mathcal{T}_{f}}`` and ``{\mathcal{T}_{s}}``
# could be discretized either explicitly or implicitly, hence, they could
# belong to either ``\mathcal{F}(t, \boldsymbol{q})`` or ``\mathcal{G}(t, \boldsymbol{q})``
# term.
#
# For a given time-step size ``\Delta t``, the two-rate method in [Schlegel2009](@cite)
# is summarized as the following:
#
# ```math
# \begin{align}
#     \boldsymbol{Q}_1 &= \boldsymbol{q}(t_n), \\
#     \boldsymbol{r}_{i} &= \sum_{j=1}^{i-1}\tilde{a}^O_{ij} {\mathcal{T}_{s}}(\boldsymbol{Q}_{j}), \\
#     \boldsymbol{w}_{i,1} &= \boldsymbol{Q}_{i-1},\\
#     \boldsymbol{w}_{i,k} &= \boldsymbol{w}_{i,k-1} + \Delta t \tilde{c}_i^O \sum_{j=1}^{k-1}\tilde{a}^I_{k,j}
#     \left(\frac{1}{\tilde{c}_i^O}\boldsymbol{r}_i + {\mathcal{T}_{f}}(\boldsymbol{w}_{i,j})\right),\\
#     & \quad\quad i = 2, \cdots, s^O + 1 \text{ and } k = 2, \cdots, s^I + 1,\nonumber \\
#     \boldsymbol{Q}_i &= \boldsymbol{w}_{i,s^I + 1}
# \end{align}
# ```
#
# where the tilde parameters denote increments per RK stage:
#
# ```math
# \begin{align}
#     \tilde{a}_{ij} &= \begin{cases}
#         a_{i,j} - a_{i-1, j} & \text{if } i < s + 1 \\
#         b_j - a_{s,j} & \text{if } i = s + 1
#     \end{cases},\\
#     \tilde{c}_{i} &= \begin{cases}
#         c_{i} - c_{i-1} & \text{if } i < s + 1 \\
#         1 - c_{s} & \text{if } i = s + 1
#     \end{cases},
# \end{align}
# ```
#
# where the coefficients ``a``, ``b``, and ``c`` correspond to the Butcher
# tableau for a given RK method. The superscripts ``O`` and ``I`` denote the
# *outer* (slow) and *inner* (fast) components of the multirate method
# respectively. Thus, tilde coefficients should be associated with the RK
# method indicated by the superscripts. In other words, the RK methods
# for the slow ``{\mathcal{T}_{s}}`` and fast
# ``{\mathcal{T}_{f}}`` components have Butcher tables given by:
#
# ```math
# \begin{align}
#     \begin{array}{c|c}
#     \boldsymbol{c}_{O} &\boldsymbol{A}_{O} \\
#     \hline
#     & \boldsymbol{b}_O^T
#     \end{array}, \quad
#     \begin{array}{c|c}
#     \boldsymbol{c}_{I} &\boldsymbol{A}_{I} \\
#     \hline
#     & \boldsymbol{b}_I^T
#     \end{array},
# \end{align}
# ```
#
# where ``\boldsymbol{A}_O = \lbrace a_{i,j}^O\rbrace``, ``\boldsymbol{b}_O = \lbrace b_i^O \rbrace``, and
# ``c_O = \lbrace c_i^O \rbrace`` (similarly for ``\boldsymbol{A}_I``, ``\boldsymbol{b}_I``, and ``\boldsymbol{c}_I``).
# The method described here is for an explicit RK outer method with ``s`` stages.
# More details can be found in [Schlegel2012](@cite).

# The acoustic wave test case used in this tutorial represents a global-scale
# problem with inertia-gravity waves traveling around the entire planet.
# It has a hydrostatically balanced initial state that is given a pressure
# perturbation.
# This initial pressure perturbation causes an acoustic wave to travel to
# the antipode, coalesce, and return to the initial position. The exact solution
# of this test case is simple in that the (linear) acoustic theory allows one
# to verify the analytic speed of sound based on the thermodynamics variables.
# The initial condition is defined as a hydrostatically balanced atmosphere
# with background (reference) potential temperature.

ode_solver = ClimateMachine.MultirateSolverType(
    splitting_type = ClimateMachine.HEVISplitting(),
    slow_method = LSRK54CarpenterKennedy,
    fast_method = ARK2GiraldoKellyConstantinescu,
    implicit_solver_adjustable = true,
    timestep_ratio = 100,
)

timeend = FT(3600)
CFL = FT(5)
cfl_direction = HorizontalDirection()
run_acousticwave(ode_solver, CFL, cfl_direction, timeend);

# The interested reader can explore the combination of different slow and
# fast methods for Multirate solvers, consulting the ones available in
# `ClimateMachine.jl`, such as the
# [`Low-Storage-Runge-Kutta-methods`](@ref ClimateMachine.ODESolvers.LowStorageRungeKutta2N),
# [`Strong-Stability-Preserving-RungeKutta-methods`](@ref ClimateMachine.ODESolvers.StrongStabilityPreservingRungeKutta),
# and [`Additive-Runge-Kutta-methods`](@ref ClimateMachine.ODESolvers.AdditiveRungeKutta).

# ## References
# - [Giraldo2013](@cite)
# - [Schlegel2009](@cite)
# - [Schlegel2012](@cite)


using RecipesBase

struct MountainCarParams{T} <:Any where {T<:Real}
    ucoeff::T # action coefficient for acceleration
    g::T # gravity coeff
    h::T # cosine frequency parameter

    MountainCarParams() = new{Float64}(0.001, 0.0025, 3.)
    MountainCarParams(T::Type) = new{T}(T(0.001), T(0.0025), T(3.))
	MountainCarParams(T::Type, ucoeff, g, h) = new{T}(T(ucoeff), T(g), T(h))
end

mutable struct MountainCar{T,TA} <: AbstractEnvironment where {T<:Real,TA<:Real}
    state::Array{T,1}
    reward::T
    done::Bool
    params::MountainCarParams{T}
    t::T # time
	deterministic_start::Bool

    function MountainCar(T::Type=Float64, TA::Type=Int, deterministic_start::Bool=false)
        new{T,TA}(zeros(T,2), T(0.0), false,
           MountainCarParams(T), T(0.), deterministic_start
       )
    end

	function MountainCar(::Type{T}, ::Type{TA}, deterministic_start::Bool, params::MountainCarParams{T}) where {T, TA}
        new{T,TA}(zeros(T,2), T(0.0), false,
           params, T(0.), deterministic_start
       )
    end
end

function get_gamma(env::MountainCar{T})::T where {T}
	return T(1.0)
end

function randomize_env(::Type{MountainCar{T,TA}}, rng::AbstractRNG, deterministic_start::Bool=false) where {T, TA}
	ubase = 0.001
	u = rand(rng, Uniform(0.8*ubase, 1.2*ubase))  # coefficient for action force
	# fix these to keep the problem around the same difficulty
	g = 0.0025   	# "gravity" parameter
	h = 3.  		# mountain frequency parameter  (fixed because it changes the state width. Need to recompute xlims)

	params = MountainCarParams(T, u, g, h)
	return MountainCar(T, TA, deterministic_start, params)
end

function get_state_desc(env::MountainCar{T})::Array{T,2} where {T<:Real}
    ranges = zeros(T, (2,2))
    ranges[1,:] .= [-1.2, 0.5]       # x range
	ranges[2,:] .= [-0.07, 0.07]     # xDot range
	# ranges[3,:] .= [0., 5000.]       # time range
    return ranges
end

function get_action_desc(env::MountainCar{T,TA})::Int where{T<:Real,TA<:Integer}
    return 3
end

function get_action_desc(env::MountainCar{T,TA})::Array{TA,1} where{T<:Real,TA<:AbstractFloat}
	ranges = zeros(TA, (1,2))
	ranges[1, 1] = -1.
	ranges[1, 2] = 1.
	return ranges
end

function mountaincar_sim!(state, u, params)
	x, xDot = state
	xDot = xDot + params.ucoeff * u - params.g * cos(params.h * x)
	xDot = clamp(xDot, -0.07, 0.07)
	x += xDot

	if x < -1.2
		x = -1.2
		xDot = 0.
	end

	state[1:2] .= x, xDot
end

function step!(env::MountainCar{T}, action::Int, rng::AbstractRNG)::T where {T<:Real}
    if action <= 0 || action > 3
        error("Action needs to be an integer in [1, 3]")
    end
    u::T = 0.
	u = T(action) - 2.

	mountaincar_sim!(env.state, u, env.params)
	env.t += 1
	# env.state[3] = env.t

    env.reward = -1.0
	env.done = terminalCheck(env)

    return env.reward
end

function terminalCheck(env::MountainCar)::Bool
	goalcond = env.state[1] ≥ 0.5
	timecond = env.t ≥ 5000
	done = goalcond | timecond

	return done
end

function step!(env::MountainCar{T}, action::T, rng::AbstractRNG)::T where {T<:AbstractFloat}
	u = clamp(action, -1., 1.)

	mountaincar_sim!(env.state, u, env.params)
	env.t += 1
	# env.state[3] = env.t

    env.reward = -1.0
	env.done = terminalCheck(env)

    return env.reward
end

function get_state(env::MountainCar{T})::Array{T,1} where {T<:Real}
    return env.state
end

function get_reward(env::MountainCar)
    return env.reward
end

function is_terminal(env::MountainCar)::Bool
    return env.done
end

function env_reset!(env::MountainCar, rng)
	env.state[1] = -0.5
	env.state[2] = 0.
	# env.state[3] = 0.
	if !env.deterministic_start
		env.state[1] = rand(rng, Uniform(-0.6, -0.4))
	end
    env.t = 0.
	env.done = false
end

function clone(env::MountainCar{T,TA}) where {T<:Real,TA<:Union{Int,<:AbstractFloat}}
	env2 = MountainCar(T,TA, env.deterministic_start)
	env2.state .= deepcopy(env.state)
	env2.reward = deepcopy(env.reward)
	env2.done = deepcopy(env.done)
	env2.t = deepcopy(env.t)
	env2.params = deepcopy(env.params)
	return env2
end

@recipe function f(env::MountainCar)
	x, xvel = env.state
	h = env.params.h
	y = sin(h * x)

	xpts = range(-1.2, stop=0.5, length=50)
	ypts = map(x->sin(h*x), xpts)

	legend := false
	xlims := (-1.2, 0.5)
	ylims := (min(ypts...), max(ypts...))
	grid := false
	ticks := nothing
	foreground_color := :white
	aspect_ratio := 1.

	# mountain
	@series begin
		seriestype := :line
		linecolor := :black
		linewidth := 2

		xpts, ypts
	end

	# car
	@series begin
		seriestype := :shape
		linecolor := nothing
		color := :blue
		aspect_ratio := 1.

		circleShape(x, y, 0.05)
	end
end


using Random, Plots, Distributions, Statistics

# Example solution for exercise 8.8 using Julia 1.4.1
#
# My solution has similar shape with the book, but different start state value
# under the greedy policy. I am not sure where goes wrong, probably in the
# reward calculation? But my results are similar to all the other people's
# results which I found online (see below reference implementation). So just
# take my solution as one of the references, don't treat it absolutely correct.
#
# By @burmecia at GitHub, 15 May 2020
#
# Other reference implementation:
#
# 1. https://github.com/ShangtongZhang/reinforcement-learning-an-introduction/blob/master/chapter08/trajectory_sampling.py
# 2. https://github.com/JuliaReinforcementLearning/ReinforcementLearningAnIntroduction.jl/blob/b5c718a891a4b3db4fae177b8b33ca506df1ecea/notebooks/Chapter08_Trajectory_Sampling.ipynb
# 3. https://github.com/enakai00/rl_book_solutions/blob/master/Chapter08/Exercise_8_8_Solution.ipynb

stat_len = 10000
T = 200000

ε = 0.1
act_len = 2
term_prob = 0.1
tick_num = 100

# make transition matrix, (state, action) -> (next_state, reward)
# element is b tuples: [(next_state, reward),...]
function make_transition_matrix(b)
    trans = [
        map(s -> (s, randn()), rand(1:stat_len, b))
        for s = 1:stat_len, a = 1:act_len
    ]
    # terminal state, transit to itself with zero reward
    trans[stat_len, 1] = [(stat_len, 0) for _ = 1:b]
    trans[stat_len, 2] = [(stat_len, 0) for _ = 1:b]
    trans
end

# take action, observe the next state and reward
function next_state(s, a, trans)
    if rand() < term_prob
        return stat_len, 0
    end
    sample(trans[s, a])
end

# evaluate start state value under greedy policy
function start_state_value(Q, trans)
    n = 200
    returns = zeros(Float64, n)

    for i = 1:n
        s = 1
        while s != stat_len
            a = argmax(Q[s, :])
            s, r = next_state(s, a, trans)
            returns[i] += r
        end
    end

    mean(returns)
end

function run_uniform(b, trans)
    Q = zeros(Float64, stat_len, act_len)
    interval = T ÷ tick_num
    vπ_s0 = zeros(Float64, tick_num + 1)

    for t = 0:T
        s = t % stat_len + 1
        a = (t % (stat_len * act_len)) ÷ stat_len + 1
        next_states = trans[s, a]

        Q[s, a] =
            (1 - term_prob) *
            mean(map(ns -> (ns[2] + maximum(Q[ns[1], :])), next_states))

        if t % interval == 0
            vπ_s0[t ÷ interval + 1] = start_state_value(Q, trans)
        end
    end

    vπ_s0
end

function run_on_policy(b, trans)
    Q = zeros(Float64, stat_len, act_len)
    interval = T ÷ tick_num
    vπ_s0 = zeros(Float64, tick_num + 1)
    s = 1

    for t = 0:T
        if rand() < ε
            a = rand(1:act_len)
        else
            a = argmax(Q[s, :])
        end
        next_states = trans[s, a]

        Q[s, a] =
            (1 - term_prob) *
            mean(map(ns -> (ns[2] + maximum(Q[ns[1], :])), next_states))

        s, r = next_state(s, a, trans)
        if s == stat_len
            s = 1
        end

        if t % interval == 0
            vπ_s0[t ÷ interval + 1] = start_state_value(Q, trans)
        end
    end

    vπ_s0
end

function experiment(b_list, p, subplot)
    task_cnt = 100
    interval = T ÷ tick_num
    x = 0:interval:T

    for b in b_list
        uniform = zeros(Float64, task_cnt, length(x))
        on_policy = zeros(Float64, task_cnt, length(x))

        println("start subplot $(subplot), b=$(b)")
        for i = 1:task_cnt
            if i % 10 == 0
                println("  task $(i)")
            end
            trans = make_transition_matrix(b)
            uniform[i, :] = run_uniform(b, trans)
            on_policy[i, :] = run_on_policy(b, trans)
        end

        uniform = mean(uniform, dims = 1)
        on_policy = mean(on_policy, dims = 1)

        plot!(
            p,
            x,
            uniform[1, :],
            subplot = subplot,
            title = "$(stat_len) states",
            label = "b=$(b), uniform",
        )
        plot!(
            p,
            x,
            on_policy[1, :],
            subplot = subplot,
            label = "b=$(b), on-policy",
        )
    end
end

function main()
    global stat_len, T

    p = plot(
        xlabel = "Computation time, in expected updates",
        ylabel = "Start state value",
        legend = :best,
        size = (600, 800),
        layout = (2, 1),
    )

    stat_len, T = 1000, 20000
    experiment([1, 3, 10], p, 1)
    stat_len, T = 10000, 200000
    experiment([1, 3], p, 2)

    display(p)
end

# uncomment the main function below to start learning
#main()


# The structure of the fractional derivative:
# (1) Type definition.
# (2) Checks function to ensure the input parameters are correct and handle some special cases, for example, 0 end point case.
# (3) Multiple dispatch for fracdiff of different algorithms, the first is used to compute the value of a specific point, the second is used to compute the fractional derivative of a Vector, other functions are auxillary functions to help with the coefficients.
# (4) Fractional derivative macros for convenient computing.

using QuadGK, SpecialFunctions, SpecialMatrices
using LinearAlgebra, InvertedIndices

"""
Base type of fractional differentiation algorithms, in FractionalCalculus.jl, all of the fractional derivative algorithms belong to ```FracDiffAlg```
"""
abstract type FracDiffAlg end

function checks(f::Number, α::Float64, start_point::Float64, end_point::Float64)
    f = f == 0 ? 0 : f/sqrt(pi*end_point)
end

include("Caputo.jl")
include("GL.jl")
include("Hadamard.jl")
include("Riesz.jl")
include("RL.jl")

"""
    fracdiff(f, α, point, h, FracDiffAlg())

```fracdiff``` is the general API for computing fractional derivative
"""
function fracdiff(f, α, point, h, ::FracDiffAlg)

end

## Macros for convenient computing.
"""
    @fracdiff(f, α, point)

Return the α-order derivative of **f** at specific point.

```julia-repl
julia> @fracdiff(x->x, 0.5, 1)
1.1283791670955188
```
"""
macro fracdiff(f, α, point)
    return :(fracdiff($f, $α, $point, 0.0001, RLDiff_Approx()))
end

"""
    @semifracdiff(f, point)

Return the semi-derivative of **f** at spedific point.

```julia-repl
julia> @semifracdiff(x->x, 1)
1.1283791670955188
```
"""
macro semifracdiff(f::Union{Number, Function}, point)
    return :(fracdiff($f, 0.5, $point, 0.0001, RLDiff_Approx()))
end


using Simpsons, Distributions, DataFrames, CSV, Plots, Test

# see wikipedia entry at http://en.wikipedia.org/wiki/Simpsons_paradox
const data = vcat(
    repeat([["A", "small", 1]], 81), repeat([["A", "small", 0]], 87 - 81),
    repeat([["B", "small", 0]], 270 - 234), repeat([["B", "small", 1]], 234),
    repeat([["B", "large", 1]], 55), repeat([["B", "large", 0]], 80 - 55),
    repeat([["A", "large", 0]], 263 - 192), repeat([["A", "large", 1]], 192)
)
const df = DataFrame(treatment = [d[1] for d in data],
               recovery = [d[3] for d in data],
               kidney_stone_size = [d[2] for d in data])

@test has_simpsons_paradox(df, :treatment, :recovery, :kidney_stone_size) == true

const pathname = "cars.csv"
const dfc = DataFrame(CSV.File(pathname, datarow = 3))
@test has_simpsons_paradox(dfc, :Weight, :MPG, :Cylinders) == false
@test has_simpsons_paradox(dfc, :Weight, :MPG, :Acceleration) == false
@test has_simpsons_paradox(dfc, :Weight, :MPG, :Horsepower) == false

Plots.scalefontsizes(0.6)
simpsons_analysis(dfc, :Horsepower, :Acceleration)
simpsons_analysis(df, :treatment, :recovery)

dfp = make_paradox()
@test has_simpsons_paradox(dfp, :x, :y, :z) == true
plot_kmeans_by_factor(dfp, :x, :y, :z)


# For the conversion to go through ForwardDiff, I need
#Base.convert(FDT::Type{ForwardDiff.Dual{T,V,N}}, p::AIBECS.Parameters) where {T,V,N} = AIBECS.Parameters(convert(Vector{FDT}, vec(p))...)

@testset "Derivatives" begin
    nt = length(T_all)
    n = nt * nb
    x₀ = p₀.xgeo * ones(n)
    @testset "∇ₓF" begin
        @test ForwardDiff.jacobian(x -> F(x, p₀), x₀) ≈ ∇ₓF(x₀, p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(x -> F(x, 2p₀), x₀) ≈ ∇ₓF(x₀, 2p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(x -> F(x, p₀), 2x₀) ≈ ∇ₓF(2x₀, p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(x -> F(x, p₀), -x₀) ≈ ∇ₓF(-x₀, p₀) rtol = 1e-14
    end
    @testset "∇ₚf" begin
        @test ForwardDiff.jacobian(p -> [f(x₀, p)], p₀) ≈ ∇ₚf(x₀, p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(p -> [f(x₀, p)], 2p₀) ≈ ∇ₚf(x₀, 2p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(p -> [f(2x₀, p)], p₀) ≈ ∇ₚf(2x₀, p₀) rtol = 1e-14
    end
    @testset "∇ₓf" begin
        @test ForwardDiff.jacobian(x -> [f(x, p₀)], x₀) ≈ ∇ₓf(x₀, p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(x -> [f(x, 2p₀)], x₀) ≈ ∇ₓf(x₀, 2p₀) rtol = 1e-14
        @test ForwardDiff.jacobian(x -> [f(x, p₀)], 2x₀) ≈ ∇ₓf(2x₀, p₀) rtol = 1e-14
    end
end


using RoME, IncrementalInference, TransformUtils
using Distributions
using KernelDensityEstimate
using Base.Test

tf = SE3([0.0;0.0;0.0], AngleAxis(pi/4,[0;0;1.0]))# Euler(pi/4,0.0,0.0) )

N = 1
initCov = 0.0001*eye(6)
[initCov[i,i] = 0.000001 for i in 4:6];
odoCov = deepcopy(initCov)
odo = Pose3Pose3( MvNormal(veeEuler(tf), odoCov) )

X = [0.01*randn(5,N);0*pi/4+0.01*randn(1,N)]

Y = X ⊕ odo

@test norm(Y[1:3]-zeros(3)) < 1.0
@test norm(Y[4:6]-[zeros(2);pi/4]) < 0.15

tf = SE3([0.0;0.0;0.0], AngleAxis(pi/4,[0;0;1.0]))# Euler(pi/4,0.0,0.0) )

N = 1
initCov = 0.01*eye(6)
[initCov[i,i] = 0.001 for i in 4:6];
odoCov = deepcopy(initCov)
odo = Pose3Pose3(  MvNormal(veeEuler(tf), odoCov) )

X = [0.01*randn(5,N);0*pi/4+0.01*randn(1,N)]

Y = X ⊕ odo

@test norm(Y[1:3]-zeros(3)) < 1.0
@test norm(Y[4:6]-[zeros(2);pi/4]) < 0.2

N = 75
fg = initfg()

initCov = eye(6)
[initCov[i,i] = 0.01 for i in 4:6];
odoCov = deepcopy(initCov)

println("Adding PriorPose3 to graph...")
v1 = addNode!(fg, :x1,  0.1*randn(6,N),  N=N)
initPosePrior = PriorPose3( MvNormal(zeros(6), initCov) )
f1  = addFactor!(fg,[v1], initPosePrior)

println("Ensure vertex initialized properly")
# start with to tight an initialization
muX1 = Base.mean(getVal(fg,:x1),2)
stdX1 = Base.std(getVal(fg,:x1),2)
@test sum(map(Int,abs.(muX1) .< 0.1)) == 6
@test sum(map(Int, 0.05 .< stdX1 .< 0.15)) == 6

println("Testing PriorPose3 evaluation...")
priorpts = evalFactor2(fg, fg.g.vertices[2], 1)
means = Base.mean(priorpts,2)
@test sum(map(Int,abs.(means[1:3]) .> 0.5)) == 0
@test sum(map(Int,abs.(means[4:6]) .> 0.05)) == 0

println("Adding Pose3Pose3 to graph...")
odo = SE3([10;0;0], Quaternion(0))
pts0X2 = projectParticles(getVal(fg,:x1), MvNormal(veeEuler(odo), odoCov) )
odoconstr = Pose3Pose3( MvNormal(veeEuler(odo), odoCov) )
v2 = addNode!(fg,:x2,  pts0X2, N=N)
addFactor!(fg,[v1;v2],odoconstr)

println("Testing Pose3Pose3 evaluation...")
X1pts = evalFactor2(fg, fg.g.vertices[4], 1)
X2pts = evalFactor2(fg, fg.g.vertices[4], 3)
X2ptsMean = Base.mean(X2pts,2)
X1ptsMean = Base.mean(X1pts,2)
@show X1ptsMean
@test  sum(map(Int, abs.(X1ptsMean) .< 1.0 )) == 6
@test  sum(map(Int, abs.(X2ptsMean - [10.0;0;0;0;0;0]) .< 1.0 )) == 6

println("Construct Bayes tree and perform inference...")
tree = prepBatchTree!(fg);
inferOverTree!(fg, tree, N=N)

println("Ensure basic parameters on x1,x2 after inference...")
# check mean and covariances after one up and down pass over the tree
muX1 = Base.mean(getVal(fg,:x1),2)
stdX1 = Base.std(getVal(fg,:x1),2)
@test sum(map(Int,abs.(muX1[1:3]) .< 1.0)) == 3
@test sum(map(Int,abs.(muX1[4:6]) .< 0.1)) == 3
@test sum(map(Int, 0.5 .< stdX1[1:3] .< 1.5)) == 3
@test sum(map(Int, 0.025 .< stdX1[4:6] .< 0.25)) == 3
muX2 = Base.mean(getVal(fg,:x2),2)
stdX2 = Base.std(getVal(fg,:x2),2)
@test sum(map(Int, abs.(muX2[1:3]-[10.0;0;0]) .< 1.0)) == 3
@test sum(map(Int, abs.(muX2[4:6]) .< 0.1)) == 3
@show println("previous test failure 0.75 .< $(round.(stdX2[1:3],2)) .< 2.25")
@test sum(map(Int, 0.75 .< stdX2[1:3] .< 2.25)) == 3
@test sum(map(Int, 0.05 .< stdX2[4:6] .< 0.25)) == 3

# println("Plot marginals to see what is happening")
# plotKDE(marginal(getVertKDE(fg,:x1),[1]))
# plotKDE(marginal(getVertKDE(fg,:x2),[1]))

#


using OffsetArrays
using Chain
using Utils

function parse_file(f)
    collect(eachline(f))
end

function load()
    open(joinpath(@__DIR__, "24.txt"), "r") do f
        parse_file(f)
    end
end

problem = load()

function ex1(problem)
    dim = 128 | 1
    @assert dim % 2 != 0 "Later code expects odd here."

    black = falses(2dim, 2dim)
    black = OffsetMatrix(black, 1-dim:dim, 1-dim:dim)
    for line in problem
        cur = 1
        c = r = 0
        while cur <= length(line)
            if line[cur] == 'w'
                c -= 2
            elseif line[cur] == 'e'
                c += 2
            else
                if line[cur] == 'n'
                    r -= 1
                elseif line[cur] == 's'
                    r += 1
                else
                    error("shouldn't get here (n or s)")
                end
                cur += 1
                if line[cur] == 'w'
                    c -= 1
                elseif line[cur] == 'e'
                    c += 1
                else
                    error("shouldn't get here (w or e)")
                end
            end
            cur += 1
        end
        @assert abs(r) % 2 == abs(c) % 2 "row and columns need to have the same LSB"
        black[r, c] = !black[r, c]
    end

    (count = count(values(black)), field = black)
end

function black_neighbors(field, r, c)
    @assert abs(r) % 2 == abs(c) % 2 "row and columns need to have the same LSB"
    coords = [
        (-1, -1),
        (-1, 1),
        (0, -2),
        (0, 2),
        (1, -1),
        (1, 1),
    ]
    @chain coords begin
        map(tup -> (r + tup[1], c + tup[2]), _)
        map(((rr, cc),) -> field[rr,cc], _)
        count
    end
end

function step(field)
    prev = copy(field)
    axr, axc = axes(prev)
    for r in firstindex(axr)+1:lastindex(axr)-1
        if r % 2 == 0
            range = firstindex(axc)+2:2:lastindex(axc)-2
            @assert 0 in range
        else
            range = firstindex(axc)+3:2:lastindex(axc)-3
            @assert 1 in range
        end
        for c in range
            ns = black_neighbors(prev, r, c)
            if prev[r,c] && (ns == 0 || ns > 2)
                field[r,c] = false
            elseif !prev[r,c] && ns == 2
                field[r,c] = true
            end
        end
    end
end

function ex2(field)
    for i in 1:100
        step(field)
    end
    count(field)
end

answer = ex1(problem)
println("Black side up initially: ", answer.count)
println("Black side up after 100 days: ", ex2(answer.field))

using Test

@testset "Lobby Layout" begin
    @testset "example 1" begin
        input1 = """
        sesenwnenenewseeswwswswwnenewsewsw
        neeenesenwnwwswnenewnwwsewnenwseswesw
        seswneswswsenwwnwse
        nwnwneseeswswnenewneswwnewseswneseene
        swweswneswnenwsewnwneneseenw
        eesenwseswswnenwswnwnwsewwnwsene
        sewnenenenesenwsewnenwwwse
        wenwwweseeeweswwwnwwe
        wsweesenenewnwwnwsenewsenwwsesesenwne
        neeswseenwwswnwswswnw
        nenwswwsewswnenenewsenwsenwnesesenew
        enewnwewneswsewnwswenweswnenwsenwsw
        sweneswneswneneenwnewenewwneswswnese
        swwesenesewenwneswnwwneseswwne
        enesenwswwswneneswsenwnewswseenwsese
        wnwnesenesenenwwnenwsewesewsesesew
        nenewswnwewswnenesenwnesewesw
        eneswnwswnwsenenwnwnwwseeswneewsenese
        neswnwewnwnwseenwseesewsenwsweewe
        wseweeenwnesenwwwswnew
        """

        example1 = parse_file(IOBuffer(input1))

        answer = ex1(example1)
        @test ex1(example1).count == 10
        @test ex2(answer.field) == 2208
    end

    @testset "results" begin
        answer = ex1(problem)
        @test answer.count == 332
        @test ex2(answer.field) == 3900
    end
end


@testset "JuMP direct model - Linear Constraints - Affine parameters" begin
    optimizer = POI.ParametricOptimizer(GLPK.Optimizer())

    model = direct_model(optimizer)

    @variable(model, x[i = 1:2] >= 0)

    @variable(model, y in POI.Parameter(0))
    @variable(model, w in POI.Parameter(0))
    @variable(model, z in POI.Parameter(0))

    @constraint(model, 2 * x[1] + x[2] + y <= 4)
    @constraint(model, 1 * x[1] + 2 * x[2] + z <= 4)

    @objective(model, Max, 4 * x[1] + 3 * x[2] + w)

    optimize!(model)

    @test isapprox.(value(x[1]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(y), 0, atol = ATOL)

    # ===== Set parameter value =====
    MOI.set(model, POI.ParameterValue(), y, 2.0)
    optimize!(model)

    @test isapprox.(value(x[1]), 0.0, atol = ATOL)
    @test isapprox.(value(x[2]), 2.0, atol = ATOL)
    @test isapprox.(value(y), 2.0, atol = ATOL)
end

@testset "JuMP direct model - Linear Constraints - Parameter x variable" begin
    optimizer = POI.ParametricOptimizer(GLPK.Optimizer())

    model = direct_model(optimizer)

    @variable(model, x[i = 1:2] >= 0)

    @variable(model, y in POI.Parameter(0))
    @variable(model, w in POI.Parameter(0))
    @variable(model, z in POI.Parameter(0))

    @constraint(model, 2 * x[1] + x[2] + y <= 4)
    @constraint(model, (1 + y) * x[1] + 2 * x[2] + z <= 4)

    @objective(model, Max, 4 * x[1] + 3 * x[2] + w)

    optimize!(model)

    @test isapprox.(value(x[1]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(y), 0, atol = ATOL)

    # ===== Set parameter value =====
    MOI.set(model, POI.ParameterValue(), y, 2.0)
    optimize!(model)

    @test isapprox.(value(x[1]), 0.0, atol = ATOL)
    @test isapprox.(value(x[2]), 2.0, atol = ATOL)
    @test isapprox.(value(y), 2.0, atol = ATOL)
end

@testset "JuMP - Linear Constraints - Affine parameters" begin
    model = Model(() -> POI.ParametricOptimizer(GLPK.Optimizer()))

    @variable(model, x[i = 1:2] >= 0)

    @variable(model, y in POI.Parameter(0))
    @variable(model, w in POI.Parameter(0))
    @variable(model, z in POI.Parameter(0))

    @constraint(model, 2 * x[1] + x[2] + y <= 4)
    @constraint(model, 1 * x[1] + 2 * x[2] + z <= 4)

    @objective(model, Max, 4 * x[1] + 3 * x[2] + w)

    optimize!(model)

    @test isapprox.(value(x[1]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(y), 0, atol = ATOL)

    # ===== Set parameter value =====
    MOI.set(model, POI.ParameterValue(), y, 2.0)
    optimize!(model)

    @test isapprox.(value(x[1]), 0.0, atol = ATOL)
    @test isapprox.(value(x[2]), 2.0, atol = ATOL)
    @test isapprox.(value(y), 2.0, atol = ATOL)
end

@testset "JuMP - Linear Constraints - Parameter x variable" begin
    model = Model(() -> POI.ParametricOptimizer(GLPK.Optimizer()))

    @variable(model, x[i = 1:2] >= 0)

    @variable(model, y in POI.Parameter(0))
    @variable(model, w in POI.Parameter(0))
    @variable(model, z in POI.Parameter(0))

    @constraint(model, 2 * x[1] + x[2] + y <= 4)
    @constraint(model, (1 + y) * x[1] + 2 * x[2] + z <= 4)

    @objective(model, Max, 4 * x[1] + 3 * x[2] + w)

    optimize!(model)

    @test isapprox.(value(x[1]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL)
    @test isapprox.(value(y), 0, atol = ATOL)

    # ===== Set parameter value =====
    MOI.set(model, POI.ParameterValue(), y, 2.0)
    optimize!(model)

    @test isapprox.(value(x[1]), 0.0, atol = ATOL)
    @test isapprox.(value(x[2]), 2.0, atol = ATOL)
    @test isapprox.(value(y), 2.0, atol = ATOL)
end


using SimpleSolvers
using SimpleSolvers: initialize!, solver_step!
using Test

struct NonlinearSolverTest{T} <: NonlinearSolver end

test_solver = NonlinearSolverTest{Float64}()

@test_throws ErrorException config(test_solver)
@test_throws ErrorException status(test_solver)
@test_throws ErrorException initialize!(test_solver, rand(3))
@test_throws ErrorException solver_step!(test_solver)

function F!(f, x)
    f .= x.^2
end

function J!(g, x)
    g .= 0
    for i in eachindex(x)
        g[i,i] = 2x[i]
    end
end

for (Solver, kwarguments) in (
                (NewtonSolver, (linesearch = Static(),)),
                (NewtonSolver, (linesearch = Backtracking(),)),
                (NewtonSolver, (linesearch = Quadratic(),)),
                (NewtonSolver, (linesearch = Bisection(),)),
                (QuasiNewtonSolver, (linesearch = Static(),)),
                (QuasiNewtonSolver, (linesearch = Backtracking(),)),
                (QuasiNewtonSolver, (linesearch = Quadratic(),)),
                (QuasiNewtonSolver, (linesearch = Bisection(),)),
                (NLsolveNewton, NamedTuple()),
            )

    n = 1
    x = ones(n)
    y = zero(x)
    nl = Solver(x, y, F!; kwarguments...)

    @test config(nl) == nl.config
    @test status(nl) == nl.status

    solve!(x, nl)
    # println(status(nl))
    for x in nl.x
        @test x ≈ 0 atol=1E-7
    end

    x = ones(n)
    nl = Solver(x, y, F!; J! = J!, kwarguments...)
    solve!(x, nl)
    # println(status(nl))
    for x in nl.x
        @test x ≈ 0 atol=1E-7
    end
end


using DifferentialEquations, Random, Distributions
using Plots, ColorSchemes
using DataFrames, CSV, LaTeXStrings
using Statistics, GLM, Measures
using LightGraphs, SimpleWeightedGraphs#, GraphPlot
using Compose, Cairo, Fontconfig
using LsqFit, Dates
import Gadfly
g = Gadfly # so we can use gadfly too!

pyplot(grid=false)
#  plotly()

epsilon = 1e-8

fnt = Plots.font("Arial")
default(titlefont=fnt, guidefont=fnt, tickfont=fnt, legendfont=fnt)

################################################################################
#### FIGURE 2B
################################################################################

c_starts = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]

function one_cr_model(dc, c, k, t)
    ka = k['a']
    ks = k['s']
    ki = k['i']

    dc[1] = ka * c[2] - ks * c[1]
    dc[2] = ks * c[1] - ka * c[2] - ki * c[2] * c[1]
    dc[3] = ki * c[2] * c[1]

    @assert abs(sum(dc))<=epsilon
end

function run_one_cr_recruitment(c0_val)
    t = fill(Float64, 0)
    u = fill(Float64[], 0)

    k_sil = Dict('a' => 1.0,
                 's' => 1.0,
                 'i' => 0,
                 'p' => 0)
    kt_pairs = [[k_sil, (0.0, 2.0)]]

    c0 = [c0_val; 1.0-c0_val; 0.0]

    for pair in kt_pairs
        k = pair[1]
        times = pair[2]
        prob = ODEProblem(one_cr_model, c0, times, k)
        sol = solve(prob, saveat=0.001)
        t = [t; sol.t]
        u = [u; sol.u]
        c0 = sol.u[end]
    end
    a1s = [x[1] for x in u]

    return [t, a1s]
end

function plot_c0_vals(c0_vals)
    cscheme = get(ColorSchemes.viridis, range(0.0, stop=0.9, length=length(c0_vals)))
    p = plot()
    for i in 1:length(c0_vals)
        c0 = c0_vals[i]
        t, d = run_one_cr_recruitment(c0)
        p = plot!(p, t, d,
                 label=string(c0), color=cscheme[i],
                 xlabel="Days",
                 ylabel="Fraction Active",
                 yticks = 0:0.5:1.0,
                 ylims=(-0.1, 1.1),
                 xlims=(-0.1, 2.1),
                 xticks=0:1.0:2.0,
                 legend=:outerright,
                 legendtitle="A(0)",
                 #  legendtitlefontsize=14,
                 linewidth=2,
                 size=(600, 300))
    end

    return p
end

savefig(plot_c0_vals(c_starts), "fig_2b.pdf")

################################################################################
#### FIGURE 2C
################################################################################
function steady_state(r)
    ka = 1.0
    ks = r
    #  return (-ka + sqrt(ka^2 + 4*ka*ks))/(2*ks)
    return ka/(ka + ks)
end

xs = collect(0:0.001:10)
ys = [steady_state(x) for x in xs]

fnt = Plots.font("Arial")
default(titlefont=fnt, guidefont=fnt, tickfont=fnt, legendfont=fnt)

p = plot(xs, ys,
         xlabel=L"$k_s/k_a$",
         ylabel="Steady-State
Fraction Active",
         label="", color=:green,
         yticks = [0.0, 1.0],
         ylims=(-0.1, 1.1),
         xlims=(-0.1, 10),
         xticks=[0, 10],
         linewidth=2,
         size=(300, 261))
savefig(p, "fig_2c.pdf")

################################################################################
#### FIGURE 2E
################################################################################

function ksval(p)
    exp = 3.0
    kd = 0.33
    return (p^exp)/(p^exp + (kd)^exp)
end

pvals = 0:0.01:100
kvals = [ksval(p) for p in pvals]

p = plot(pvals, kvals,
         linewidth=2,
         xlims=[0, 1.5],
         xticks=[0, 1],
         xformatter = x -> [L"$0$", L"$[CR]_{max}$"][Int(x+1)],
         ylims=[0, 1],
         yticks=[0, 1],
         yformatter = y -> [L"$0$", L"$k_{s\ max}$"][Int(y+1)],
         xlabel="[CR]",
         ylabel=L"$k_s($CR$)$",
         label=false,
         size=(300, 261))
savefig(p, "fig_2e.pdf")


# From https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
#algorithm fnv-1 is
# hash := FNV_offset_basis
#    for each byte_of_data to be hashed do
#        hash := hash × FNV_prime
#        hash := hash XOR byte_of_data
#    return hash
#and fnv-1a is the same but with switched order of statements
#in loop body.

const FNV_Primes = Dict(
    32 => UInt32(0x01000193),
    64 => UInt64(0x00000100000001B3),
    128 => UInt128(0x0000000001000000000000000000013B)
)
#const FNV_Prime_256 = parse(BigInt, "374144419156711147060143317175368453031918731002211")

const FNV_Offsets = Dict(
    32 => UInt32(0x811c9dc5),
    64 => UInt64(0xcbf29ce484222325),
    128 => UInt128(0x6c62272e07bb014262b821756295c58d)
)

abstract type AbstractHashFunction{B} end

@inline unsafe_calcwithoffset(hf::AbstractHashFunction, unp::Ptr, len::Integer, offset::Integer = 0) =
    unsafe_calc(hf, reinterpret(Ptr{UInt8}, unp) + offset, len)

@inline calc(hf::AbstractHashFunction, bytes::Vector{UInt8}) =
    unsafe_calc(hf, pointer(bytes), length(bytes))

@inline calc(hf::AbstractHashFunction, s::String) =
    unsafe_calc(hf, pointer(s), length(s))

@inline unsafe_calc(hf::AbstractHashFunction, s::String, offset::Integer, len::Integer) =
    unsafe_calcwithoffset(hf, pointer(s), len, offset)

@inline function calc(hf::AbstractHashFunction, s::String, offset::Integer, len::Integer)
    @assert (offset + len) <= length(s)
    unsafe_calc(hf, s, offset, len)
end

const HashLenToType = Dict(
    32 => UInt32,
    64 => UInt64,
    128 => UInt128
)

struct FNV1A{B,T} <: AbstractHashFunction{B}
    offset::T
    prime::T
end

function unsafe_calc(hf::FNV1A, pnt::Ptr{UInt8}, len::Integer, hash = hf.offset)
    for i in 1:len
        hash = xor(hash, unsafe_load(pnt))
        hash = hash * hf.prime
        pnt += 1
    end
    hash
end

struct FNV1{B,T} <: AbstractHashFunction{B}
    offset::T
    prime::T
end

function unsafe_calc(hf::FNV1, pnt::Ptr{UInt8}, len::Integer, hash = hf.offset)
    for i in 1:len
        hash = hash * hf.prime
        hash = xor(hash, unsafe_load(pnt))
        pnt += 1
    end
    hash
end

function FNVHash(HT, hlen::Integer)
    @assert in(hlen, keys(HashLenToType))
    T = HashLenToType[hlen]
    HT{hlen, T}(FNV_Offsets[hlen], FNV_Primes[hlen])
end

FNV1A(hlen::Integer) = FNVHash(FNV1A, hlen)
FNV1(hlen::Integer) = FNVHash(FNV1, hlen)


function mock_spectrum(n::Int = Int(1e3); use_units::Bool = false)
    wave = range(1e4, 3e4, length = n)
    sigma = 0.1 .* sin.(wave)
    T = 6700
    flux = @. 1e14 / (wave^5 * (exp(1 / (wave * T)) - 1)) ± sigma
    if use_units
        wave *= u"angstrom"
        flux *= u"erg/s/cm^2/angstrom"
    end
    spectrum(wave, flux, name="Test Spectrum")
end

include("redden.jl")


coordvalue(s, onechar) = reduce((x, c) -> (x << 1) ⊻ (c == onechar), s; init=0)

seatid(s) = 8coordvalue(s[1:7], 'B') + coordvalue(s[8:10], 'R')

part1(seats) = maximum(seats)

part2(seats) = seats[findfirst((seats[2:end] - seats[1:end-1]) .== 2)] + 1

let seats = sort!(map(seatid, readlines("input.txt")))
    @info "Solutions" part1(seats) part2(seats)
end


module MaxLFSR

export LFSR, FastLFSR

# Coefficients are taken from https://users.ece.cmu.edu/~koopman/lfsr/index.html
#
# Index by the number of bits needed. Using fewer than 4 bits is apparently not supported,
# which is fine because usually one would want longer sequences anyways.
#
# Shorter sequences are simply generated using a longer LFSR and iterating multiple times.
include("terms.jl")

function nbits(x::Integer)
    if x <= zero(x) || x > typemax(Int64)
        throw(ArgumentError("Expected `length` to be between 1 and 2^64 - 1: Got: $x"))
    end

    # Add 1 if `x` is a power of 2 because a LFSR with `n` bits generates `2^n - 1` unique
    # numbers (it cannot generate zero)
    bits = ceil(Int, log2(x)) + convert(Int, ispow2(x))
    return max(bits, 4)
end

abstract type AbstractLFSR end

#####
##### Generic Length LFSR
#####
struct LFSR <: AbstractLFSR
    len::Int
    mask::Int
    seed::Int
end

function Base.show(io::IO, A::T) where {T <: AbstractLFSR}
    print(io, "$(string(T)) for 1:$(length(A)) starting at $(seed(A))")
end

"""
    LFSR(length; seed = 1)

Construct a maximum length shift register that will randomly cycle once through the numbers
`1` to `length`.

Keyword argument `seed` defines the starting point for the LFSR.

Example
-------
```julia
# Standard LFSR
julia> A = LFSR(10)
LFSR for 1:10 starting at 1

julia> for i in A
           println(i)
       end
1
9
7
10
5
6
3
8
4
2

# Change the starting location
julia> A = LFSR(10; seed = 7)
LFSR for 1:10 starting at 7

julia> for i in A
           println(i)
       end
7
10
5
6
3
8
4
2
1
9
```
"""
function LFSR(length::Integer; seed = 1)
    # Bounds checking on `seed`
    if seed < 1 || seed > length
        throw(ArgumentError("Expected `seed` to be between 1 and `length`"))
    end

    # Find the number of bits needed to represent length
    bits = nbits(length)
    mask = FEEDBACK[bits]
    return LFSR(length, mask, seed)
end

Base.eltype(L::AbstractLFSR) = Int
Base.length(L::AbstractLFSR) = L.len
mask(L::AbstractLFSR) = L.mask
seed(L::AbstractLFSR) = L.seed

# Iterator Interface
@inline Base.iterate(A::AbstractLFSR) = (seed(A), seed(A))
@inline function Base.iterate(A::AbstractLFSR, x)
    # Iterate until we reach a result that is within the correct range.
    while true
        x = step(A, x)

        # If we arrive back at out seed, we're done
        (x == seed(A)) && return nothing

        # Otherwise, perform a length check and exit.
        (x <= length(A)) && return (x, x)
    end
end

@inline function step(A::AbstractLFSR, x)
    m = isodd(x) ? mask(A) : 0
    return xor(x >> 1, m)
end

#####
##### Fast LFSR
#####
struct FastLFSR <: AbstractLFSR
    len::Int
    mask::Int
    seed::Int
end

"""
    FastLFSR(length; seed = 1)

Construct a **fast** maximum length shift register that will randomly cycle once through the numbers
`1` to `length`.
**NOTE:** This type requires `ispow2(length + 1) == true`.

Keyword argument `seed` defines the starting point for the LFSR.

Example
-------
```julia
# Standard FastLFSR
julia> A = FastLFSR(15)
LFSR for 1:10 starting at 1

julia> println.(A);
1
9
13
15
14
7
10
5
11
12
6
3
8
4
2

# Change the starting location
julia> A = LFSR(15; seed = 7)
FastLFSR for 1:15 starting at 7

julia> println.(A);
7
10
5
11
12
6
3
8
4
2
1
9
13
15
14

# Errors if length is incorrect.
julia> FastLFSR(10)
ERROR: ArgumentError: FastLFSR expects `ispow2(length + 1) == true`
```
"""
function FastLFSR(length::Integer; seed = 1)
    # Check that `length` is a power of 2
    if !ispow2(length + 1)
        throw(ArgumentError("FastLFSR expects `ispow2(length + 1) == true`"))
    end

    # Because we're restricted to a minimum of 4 bits, the minimum length we can provide
    # is 15
    if length < 15
        throw(ArgumentError("Length must be 15 or greater!"))
    end

    # Bounds checking on `seed`
    if seed < 1 || seed > length
        throw(ArgumentError("Expected `seed` to be between 1 and `length`"))
    end

    # Find the number of bits needed to represent length
    bits = nbits(length)
    mask = FEEDBACK[bits]
    return FastLFSR(length, mask, seed)
end

@inline function Base.iterate(A::FastLFSR, x)
    x = step(A, x)
    return (x == seed(A)) ? nothing : (x, x)
end

end # module


@testset "Constant" begin
    # Instantiation
    N1 = 10
    A1 = rand(Float64, N1) * 100
    A2 = rand(Float64, N1, N1) * 100
    A3 = rand(Float64, N1, N1, N1) * 100

    @testset "constructor" begin
        @test Constant() == Constant{Nearest}()
        for T in (Nearest, Previous, Next)
            it = Constant{T}()
            @test it isa Constant{T}
        end
    end

    @testset "Constant{Nearest}" begin
        for (constructor, copier) in ((interpolate, x -> x), (interpolate!, copy))
            isinplace = constructor == interpolate!
            itp1 = @inferred(constructor(copier(A1), BSpline(Constant())))
            itp2 = @inferred(constructor(copier(A2), BSpline(Constant())))
            itp3 = @inferred(constructor(copier(A3), BSpline(Constant())))

            @test parent(itp1) === itp1.coefs
            @test Interpolations.lbounds(itp1) == (1,)
            @test Interpolations.ubounds(itp1) == (N1,)

            # Evaluation on provided data points
            for (itp, A) in ((itp1, A1), (itp2, A2), (itp3, A3))
                check_axes(itp, A, isinplace)
                check_inbounds_values(itp, A)
                check_oob(itp)
                can_eval_near_boundaries(itp)
            end

            # Evaluation between data points (tests constancy)
            for i in 2:N1 - 1
                @test A1[i] == itp1(i + .3) == itp1(i - .3)
            end
            # 2D
            for i in 2:N1 - 1, j in 2:N1 - 1
                @test A2[i,j] == itp2(i + .4, j - .3) == itp2(i - .4, j + .3)
            end
            # 3D
            for i in 2:N1 - 1, j in 2:N1 - 1, k in 2:N1 - 1
                @test A3[i,j,k] == itp3(i + .4, j - .3, k + .1) == itp3(i + .4, j - .3, k + .2)
            end
        end
    end

    @testset "Constant{Previous}" begin
        for (constructor, copier) in ((interpolate, x -> x), (interpolate!, copy))
            isinplace = constructor == interpolate!
            itp1 = @inferred(constructor(copier(A1), BSpline(Constant{Previous}())))
            itp2 = @inferred(constructor(copier(A2), BSpline(Constant{Previous}())))
            itp3 = @inferred(constructor(copier(A3), BSpline(Constant{Previous}())))

            @test parent(itp1) === itp1.coefs
            @test Interpolations.lbounds(itp1) == (1,)
            @test Interpolations.ubounds(itp1) == (N1,)

            # Evaluation on provided data points
            for (itp, A) in ((itp1, A1), (itp2, A2), (itp3, A3))
                check_axes(itp, A, isinplace)
                check_inbounds_values(itp, A)
                check_oob(itp)
                can_eval_near_boundaries(itp)
            end

            # Evaluation between data points (tests constancy)
            for i in 2:N1 - 1
                @test A1[i] == itp1(i + .3) == itp1(i + .6)
                @test A1[i - 1] == itp1(i - .3) == itp1(i - .6)
            end
            # 2D
            for i in 2:N1 - 1, j in 2:N1 - 1
                @test A2[i,j - 1] == itp2(i + .4, j - .3) == itp2(i + .4, j - .6)
            end
            # 3D
            for i in 2:N1 - 1, j in 2:N1 - 1, k in 2:N1 - 1
                @test A3[i,j - 1,k] == itp3(i + .4, j - .3, k + .1) == itp3(i + .4, j - .3, k + .2)
            end
        end
    end

    @testset "Constant{Next}" begin
        for (constructor, copier) in ((interpolate, x -> x), (interpolate!, copy))
            isinplace = constructor == interpolate!
            itp1 = @inferred(constructor(copier(A1), BSpline(Constant{Next}())))
            itp2 = @inferred(constructor(copier(A2), BSpline(Constant{Next}())))
            itp3 = @inferred(constructor(copier(A3), BSpline(Constant{Next}())))

            @test parent(itp1) === itp1.coefs
            @test Interpolations.lbounds(itp1) == (1,)
            @test Interpolations.ubounds(itp1) == (N1,)

            # Evaluation on provided data points
            for (itp, A) in ((itp1, A1), (itp2, A2), (itp3, A3))
                check_axes(itp, A, isinplace)
                check_inbounds_values(itp, A)
                check_oob(itp)
                can_eval_near_boundaries(itp)
            end

            # Evaluation between data points (tests constancy)
            for i in 2:N1 - 1
                @test A1[i + 1] == itp1(i + .3) == itp1(i + .6)
                @test A1[i] == itp1(i - .3) == itp1(i - .6)
            end
            # 2D
            for i in 2:N1 - 1, j in 2:N1 - 1
                @test A2[i + 1,j] == itp2(i + .4, j - .3) == itp2(i + .4, j - .6)
            end
            # 3D
            for i in 2:N1 - 1, j in 2:N1 - 1, k in 2:N1 - 1
                @test A3[i + 1,j,k + 1] == itp3(i + .4, j - .3, k + .1) == itp3(i + .4, j - .3, k + .2)
            end
        end
    end

    @testset "Constant periodic" begin
        # Constructors
        @test Constant() === Constant(Throw(OnGrid())) === Constant{Nearest,Throw{OnGrid}}()
        @test Constant() isa Constant{Nearest,Throw{OnGrid}}
        @test Constant(Periodic()) === Constant(Periodic(OnCell())) === Constant{Nearest,Periodic{OnCell}}()
        @test Constant(Periodic()) isa Constant{Nearest,Periodic{OnCell}}
        for T in (Nearest, Previous, Next)
            it = Constant{T}()
            @test it isa Constant{T, Throw{OnGrid}}
            @test "$it" == "Constant{$T}()"
            it = Constant{T}(Periodic())
            @test it isa Constant{T, Periodic{OnCell}}
            @test "$it" == "Constant{$T}(Periodic(OnCell()))"
        end

        for (constructor, copier) in ((interpolate, x -> x), (interpolate!, copy))
            isinplace = constructor == interpolate!
            itp_periodic = @inferred(constructor(copier(A1), BSpline(Constant(Periodic()))))
            itp_previous = @inferred(constructor(copier(A1), BSpline(Constant{Previous}(Periodic()))))
            itp_next = @inferred(constructor(copier(A1), BSpline(Constant{Next}(Periodic()))))

            for itp in (itp_periodic, itp_previous, itp_next)
                @test parent(itp) === itp.coefs
                @test all(Interpolations.lbounds(itp) .≈ (0.5,))
                @test all(Interpolations.ubounds(itp) .≈ (N1 + 0.5,))

                check_axes(itp, A1, isinplace)
                check_inbounds_values(itp, A1)
                check_oob(itp)
                can_eval_near_boundaries(itp)
            end

            # Evaluation between data points (tests constancy)
            for i in 2:N1-1
                @test A1[i] == itp_periodic(i + .3) == itp_periodic(i - .3)
                @test A1[i] == itp_previous(i + .3) == itp_previous(i + .6)
                @test A1[i - 1] == itp_previous(i - .3) == itp_previous(i - .6)
                @test A1[i + 1] == itp_next(i + .3) == itp_next(i + .6)
                @test A1[i] == itp_next(i - .3) == itp_next(i - .6)
            end

            # Evaluation between data points in [0.5, 1.5], [N1 - 0.5, N1 + 0.5].
            @test A1[1] == itp_periodic(1 - .3) == itp_periodic(1 + .3)
            @test A1[N1] == itp_periodic(N1 - .3) == itp_periodic(N1 + .3)
            @test A1[1] == itp_previous(1 + .3)
            @test A1[N1] == itp_previous(N1 + .3) == itp_previous(1 - .3)
            @test A1[1] == itp_next(1 - .3) == itp_next(N1 + .3)
            @test A1[N1] == itp_next(N1 - .3)
        end
    end
end


using CCBlade
using Test

@testset "normal operation" begin

# --- verification using example from "Wind Turbine Blade Analysis using the Blade Element Momentum Method"
# --- by Grant Ingram: https://community.dur.ac.uk/g.l.ingram/download/wind_turbine_design.pdf

# Note: There were various problems with the pdf data. Fortunately the excel
# spreadsheet was provided: http://community.dur.ac.uk/g.l.ingram/download.php

# - They didn't actually use the NACA0012 data.  According to the spreadsheet they just used cl = 0.084*alpha with alpha in degrees.
# - There is an error in the spreadsheet where CL at the root is always 0.7.  This is because the classical approach doesn't converge properly at that station.
# - the values for gamma and chord were rounded in the pdf.  The spreadsheet has more precise values.
# - the tip is not actually converged (see significant error for Delta A).  I converged it further using their method.

# --- rotor definition ---
turbine = true
Rhub = 0.01
Rtip = 5.0
Rtip_eff = 5.0*100  # to eliminate tip effects as consistent with their study.
B = 3  # number of blades

rotor = Rotor(Rhub, Rtip_eff, B, turbine)

# --- section definitions ---

r = [0.2, 1, 2, 3, 4, 5]
gamma = [61.0, 74.31002131, 84.89805553, 89.07195504, 91.25038415, 92.58003871]
theta = (90.0 .- gamma)*pi/180
chord = [0.7, 0.706025153, 0.436187551, 0.304517933, 0.232257636, 0.187279622]

function affunc(alpha, Re, M)

    cl = 0.084*alpha*180/pi

    return cl, 0.0
end

sections = Section.(r, chord, theta, affunc)

# --- inflow definitions ---

Vinf = 7.0
tsr = 8
Omega = tsr*Vinf/Rtip
rho = 1.0

inflows = simpleinflow.(Vinf, Omega, r, rho)

# --- evaluate ---

outputs = solve.(rotor, sections, inflows)

Np, Tp = loads(outputs)
avec = getfield.(outputs, :a)
apvec = getfield.(outputs, :ap)
phivec = getfield.(outputs, :phi)

ivec = phivec*180/pi .- theta*180/pi
betavec = 90 .- phivec*180/pi

# outputs[1] is uncomparable because the classical method fails at the root so they fixed cl

@test isapprox(avec[2], 0.2443, atol=1e-4)
@test isapprox(apvec[2], 0.0676, atol=1e-4)
@test isapprox(avec[3], 0.2497, atol=1e-4)
@test isapprox(apvec[3], 0.0180, atol=1e-4)
@test isapprox(avec[4], 0.2533, atol=1e-4)
@test isapprox(apvec[4], 0.0081, atol=1e-4)
@test isapprox(avec[5], 0.2556, atol=1e-4)
@test isapprox(apvec[5], 0.0046, atol=1e-4)
@test isapprox(avec[6], 0.25725, atol=1e-4)  # note that their spreadsheet is not converged so I ran their method longer.
@test isapprox(apvec[6], 0.0030, atol=1e-4)

@test isapprox(betavec[2], 66.1354, atol=1e-3)
@test isapprox(betavec[3], 77.0298, atol=1e-3)
@test isapprox(betavec[4], 81.2283, atol=1e-3)
@test isapprox(betavec[5], 83.3961, atol=1e-3)
@test isapprox(betavec[6], 84.7113, atol=1e-3)  # using my more converged solution

#
#

# idx = 6

# aguess = avec[idx]
# apguess = apvec[idx]

# # aguess = 0.2557
# # apguess = 0.0046

# for i = 1:100

#     sigmap = B*chord[idx]/(2*pi*r[idx])
#     lambdar = inflows[idx].Vy/inflows[idx].Vx
#     beta2 = atan(lambdar*(1 + apguess)/(1 - aguess))
#     inc2 = gamma[idx]*pi/180 - beta2
#     cl2, cd2 = affunc(inc2, 1.0, 1.0)
#     global aguess = 1.0/(1 + 4*cos(beta2)^2/(sigmap*cl2*sin(beta2)))
#     global apguess = sigmap*cl2/(4*lambdar*cos(beta2))*(1 - a2)
#     # global apguess = 1.0 / (4*sin(beta2)/(sigmap*cl2) - 1)

# end

# aguess
# apguess

# --------------------------------------------------------------

# -------- verification: propellers.  using script at http://www.aerodynamics4students.com/propulsion/blade-element-propeller-theory.php ------
# I increased their tolerance to 1e-6

# inputs
chord = 0.10
D = 1.6
RPM = 2100
rho = 1.225
pitch = 1.0  # pitch distance in meters.

# --- rotor definition ---
turbine = false
Rhub = 0.0
Rtip = D/2
Rhub_eff = 1e-6  # something small to eliminate hub effects
Rtip_eff = 100.0  # something large to eliminate tip effects
B = 2  # number of blades

rotor_no_F = Rotor(Rhub_eff, Rtip_eff, B, turbine)
rotor = Rotor(Rhub, Rtip, B, turbine)

# --- section definitions ---

R = D/2.0
r = range(R/10, stop=R, length=11)
theta = atan.(pitch./(2*pi*r))

function affunc(alpha, Re, M)

    cl = 6.2*alpha
    cd = 0.008 - 0.003*cl + 0.01*cl*cl

    return cl, cd
end

sections = Section.(r, chord, theta, affunc)

# --- inflow definitions ---

tsim = 1e3*[1.045361193032356, 1.025630300048415, 1.005234466788998, 0.984163367036026, 0.962407923825140, 0.939960208707079, 0.916813564966455, 0.892962691000145, 0.868403981825492, 0.843134981103815, 0.817154838249790, 0.790463442573673, 0.763063053839278, 0.734956576558370, 0.706148261507327, 0.676643975451150, 0.646450304160057, 0.615575090105131, 0.584027074365864, 0.551815917391907, 0.518952127358381, 0.485446691671386, 0.451311288662196, 0.416557935286392, 0.381199277009438, 0.345247916141561, 0.308716772800348, 0.271618894441869, 0.233967425339051, 0.195775319296371, 0.157055230270717, 0.117820154495231, 0.078082266879117, 0.037854059197644, -0.002852754149850, -0.044026182837742, -0.085655305814570, -0.127728999394140, -0.170237722799272, -0.213169213043848, -0.256515079286031, -0.300266519551194, -0.344414094748869, -0.388949215983616, -0.433863576642539, -0.479150401337354, -0.524801553114807, -0.570810405128802, -0.617169893200684, -0.663873474163182, -0.710915862524620, -0.758291877949762, -0.805995685105502, -0.854022273120508, -0.902366919041604, -0.951025170820984, -0.999992624287163, -1.049265666456123, -1.098840222937414, -1.148712509929845]
qsim = 1e2*[0.803638686218187, 0.806984572453978, 0.809709290183008, 0.811743686838315, 0.813015017103876, 0.813446921530685, 0.812959654049620, 0.811470393912576, 0.808893852696513, 0.805141916379142, 0.800124489784850, 0.793748780791057, 0.785921727832179, 0.776548246109426, 0.765532528164390, 0.752778882688809, 0.738190986274448, 0.721673076180745, 0.703129918771009, 0.682467282681955, 0.659592296506578, 0.634413303042323, 0.606840565246423, 0.576786093366321, 0.544164450503912, 0.508891967461804, 0.470887571011192, 0.430072787279711, 0.386371788290446, 0.339711042057184, 0.290019539402947, 0.237229503458026, 0.181274942660876, 0.122093307308376, 0.059623821454727, -0.006190834182631, -0.075406684829235, -0.148076528546541, -0.224253047813501, -0.303980950928302, -0.387309291734422, -0.474283793689904, -0.564946107631716, -0.659336973911858, -0.757495165410553, -0.859460291551374, -0.965266648683888, -1.074949504731187, -1.188540970723477, -1.306072104649531, -1.427575034895290, -1.553080300508925, -1.682614871422754, -1.816205997296014, -1.953879956474228, -2.095662107769925, -2.241576439746701, -2.391647474158875, -2.545897099743367, -2.704346566395035]

for i = 1:60

  Vinf = float(i)
  Omega = RPM * pi/30

  inflows = simpleinflow.(Vinf, Omega, r, rho)

  # --- evaluate ---

  outputs = solve.(rotor_no_F, sections, inflows)

  Np, Tp = loads(outputs)

  # T, Q = thrusttorque(r[1], r, r[end], Np, Tp, B)
  # their spreadsheet did not use trapzezoidal rule, so just do a rect sum.
  T = sum(Np*(r[2]-r[1]))*B
  Q = sum(r.*Tp*(r[2]-r[1]))*B

  @test isapprox(T, tsim[i], atol=1e-2)  # 2 decimal places
  @test isapprox(Q, qsim[i], atol=2e-3)

end

Vinf = 20.0
Omega = RPM * pi/30

inflows = simpleinflow.(Vinf, Omega, r, rho)

outputs = solve.(rotor_no_F, sections, inflows)

Np, Tp = loads(outputs)

T = sum(Np*(r[2]-r[1]))*B
Q = sum(r.*Tp*(r[2]-r[1]))*B
eff, CT, CQ = nondim(T, Q, Vinf, Omega, rho, rotor)

@test isapprox(CT, 0.056110238632657, atol=1e-7)
@test isapprox(CQ, 0.004337202960642, atol=1e-8)
@test isapprox(eff, 0.735350632777002, atol=1e-6)

# ---------------------------------------------

# ------ hover verification --------

end


import KahanSummation
using LinearAlgebra
using SparseArrays

using Test
@testset "diffusions" begin
    function _normout(P::SparseArrays.SparseMatrixCSC{T,Int64}) where T
        n = size(P,1)
        colsums = sum(P,dims=1)
        (pi,pj,pv) = findnz(P)
        P = sparse(pi,pj,pv./colsums[pj],n,n)
    end

    function _normout(P::LinearAlgebra.Adjoint{Float64,SparseArrays.SparseMatrixCSC{T,Int64}}) where T
        n = size(P,1)
        colsums = sum(P,dims=1)
        pj,pi,pv = findnz(P.parent)
        P = sparse(pi,pj,pv./colsums[pj],n,n)
    end

    function prlinsys(A::SparseMatrixCSC{Float64,Int64},alpha::Float64,v::Vector{Float64})
        P = Matrix(_normout(A'))
        n = size(A,1)
        z =  (Matrix(1.0I,n,n) - alpha*P) \ v
        z = z /sum(z)
        return z
    end

    @testset "pagerank" begin

        A = load_matrix_network("celegans")
        x = pagerank(A, 0.85)
        y = pagerank(A, 0.85, 1e-2)

        n = 10
        A = sparse(1.0I,10,10)
        v = rand(n)
        vn = v/sum(v)
        x = personalized_pagerank(A,0.85,v)
        @test norm(x-vn,1) <= n*eps(Float64)

        v = 5;
        x = personalized_pagerank(A,0.85,v)
        xtrue = zeros(n)
        xtrue[5] = 1.
        @test norm(x -xtrue,1) <= n*eps(Float64)

        x = seeded_pagerank(A,0.85,Set([5]))

        x = personalized_pagerank(A,0.85,Set([5]))

        @test norm(x -xtrue,1) <= n*eps(Float64)

        x = personalized_pagerank(A,0.85,Dict{Int64,Float64}(5 => 6.))

        @test norm(x -xtrue,1) <= n*eps(Float64)

        x = personalized_pagerank(A,0.85,Set(collect(1:8)))

        @test norm(x - sparsevec(collect(1:8),1.0/8,n),1) <= n*eps(Float64)

        x = personalized_pagerank(A,0.85,Dict{Int64,Float64}(zip(1:8, ones(8))))
        @test norm(x - sparsevec(collect(1:8),1.0/8,n),1) <= n*eps(Float64)

        @test_throws ArgumentError personalized_pagerank(A,0.85,sparsevec(Dict{Int64,Float64}(zip(1:8, ones(8)))))

        x = personalized_pagerank(A,0.85,sparsevec(Dict{Int64,Float64}(zip(4:10, ones(7)))))
        @test norm(x - sparsevec(collect(4:10),1.0/7,n),1) <= n*eps(Float64)

        x = personalized_pagerank(A, 0.85, sparsevec([5], [2.], 10))
        @test norm(x -xtrue,1) <= n*eps(Float64)

        A = sparse(1I,n,n)
        x = personalized_pagerank(A,0.85,5)
        @test norm(x -xtrue,1) <= n*eps(Float64)

        n = 25
        A = LinearAlgebra.fillstored!(copy(sprand(n,n,2/n)), 1)
        x = pagerank(A,0.85)
        xtrue = prlinsys(A,0.85,ones(n))
        @test norm(x -xtrue,1) <= n*eps(Float64)

        x = pagerank(MatrixNetwork(A),0.85)
        @test norm(x -xtrue,1) <= n*eps(Float64)

        n = 10
        A = spdiagm(1=>ones(n-1))
        v = 1.0/n
        tol = 1e-8
        x = pagerank(A,0.85)
        z = zeros(n)
        z[1] = 1.
        z[2] = 1.85
        for ii=3:n
            z[ii] = z[ii-1] + 0.85*(z[ii-1]-z[ii-2])
        end
        z = z/KahanSummation.sum_kbn(z)
        @test norm(x-z,1) <= n*tol

    end

    @testset "pagerank_error" begin
        @test_throws DimensionMismatch pagerank(spzeros(6,5),0.85)

        @test_throws ArgumentError pagerank(spzeros(5,5),-0.85)
        @test_throws ArgumentError pagerank(spzeros(5,5),1.0)
        @test_throws ArgumentError pagerank(spzeros(5,5),1.01)

        @test_throws ArgumentError personalized_pagerank(spzeros(5,5), 0.85, rand(11))
        @test_throws DomainError personalized_pagerank(spzeros(5,5), 0.85, -ones(5))
        @test_throws DomainError personalized_pagerank(spzeros(5,5), 0.85, -sparsevec(ones(5)))
    end

    @testset "pagerank_alg" begin
        n = 10
        P = sparse(1.0I,n,n)

        x = zeros(n)
        y = zeros(n)
        v = rand(n)
        v = v/sum(v)
        tol = 1e-8
        maxiter = 1000
        iterfunc = MatrixNetworks._noiterfunc

        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,iterfunc)
        @test norm(x-v,1) <= n*tol

        # P = spdiagm(ones(n-1),-1,n,n)
        P = spdiagm(-1=>ones(n-1))
        v .= 0.0
        v[1] = 1.
        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,iterfunc)
        z = zeros(n)
        z[1] = 1.

        for ii=2:n
            z[ii] = 0.85*z[ii-1]
        end
        z = z/KahanSummation.sum_kbn(z)
        @test norm(x-z,1) <= n*tol

        tol = 1e-12
        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,iterfunc)
        @test norm(x-z,1) <= n*tol

        tol = 1e-12
        x = pagerank_power!(x,y,P,0.85,sparsevec(v),tol,maxiter,iterfunc)
        @test norm(x-z,1) <= n*tol

        tol = 1e-15
        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,iterfunc)
        @test norm(x-z,1) <= n*tol

        tol = 1e-3
        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,(iter,x) -> @show iter, norm(x,1))
        @test norm(x-z,1) <= n*tol

        v = 1.0/n
        tol = 1e-8
        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,iterfunc)
        z[1] = 1.
        z[2] = 1.85

        for ii=3:n
            z[ii] = z[ii-1] + 0.85*(z[ii-1]-z[ii-2])
        end
        z = z/KahanSummation.sum_kbn(z)

        @test norm(x-z,1) <= n*tol

    end

    @testset "pagerank_perf" begin
        maxiter=500

        n = 1_000
        x = zeros(n)
        y = zeros(n)
        z = zeros(n)
        tol = 1e-8
        v = 1.0/n
        A = LinearAlgebra.fillstored!(copy(sprand(n,n,25/n)),1)

        P = _normout(A')

        x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        P2 = MatrixNetworks._create_stochastic_mult(A)
        y = pagerank_power!(y,z,P2,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        P3 = MatrixNetworks._create_stochastic_mult(MatrixNetwork(A))
        y = pagerank_power!(y,z,P3,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)

        n = 10_000
        x = zeros(n)
        y = zeros(n)
        z = zeros(n)
        v = 1.0/n
        tol = 1e-8
        A = LinearAlgebra.fillstored!(copy(sprand(n,n,25/n)), 1)
        P = _normout(A')

        dt = @elapsed x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        dt = @elapsed x = pagerank_power!(x,y,P,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        P2 = MatrixNetworks._create_stochastic_mult(A)
        dt2 = @elapsed y = pagerank_power!(y,z,P2,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        dt2 = @elapsed y = pagerank_power!(y,z,P2,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)

        #@test dt2 <= 2*dt
        @test norm(x-y) <= n*eps(Float64)

        # now test with matrix networks
        P3 = MatrixNetworks._create_stochastic_mult(MatrixNetwork(A))

        y = zeros(n)
        z = zeros(n)
        dt2 = @elapsed y = pagerank_power!(y,z,P3,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        dt2 = @elapsed y = pagerank_power!(y,z,P3,0.85,v,tol,maxiter,MatrixNetworks._noiterfunc)
        @test norm(x-y) <= n*eps(Float64)
        #@test dt2 <= 2*dt

        dt = @elapsed x = pagerank_power!(x,y,P,0.85,v,eps(Float64),1000,MatrixNetworks._noiterfunc)
        dt2 = @elapsed y = pagerank(A,0.85)
        @test norm(x-y) <= n*eps(Float64)
        @test dt2 <= 2*dt
    end

    function shkexpm(A,t,v)
        P = Matrix(_normout(A'))
        return exp(-t*(Matrix(1.0I,size(A,1),size(A,1))-P))*v
    end

    @testset "heatkernel" begin

        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,1)
        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,Set([1]))
        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,Dict{Int,Float64}(1 => 1.))
        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,sparsevec([1],[1.],5))
        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,sparse([1],[1],[1.], 5, 1))
        v = zeros(5)
        v[1] = 1.
        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,v)
        seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,1.0/5.0)

        n = 5
        v = rand(5)
        vn = v/sum(v)
        x = seeded_stochastic_heat_kernel(sparse(1.0I,5,5),2.,vn)
        @test norm(x-vn,1) <= n*eps(Float64)

        n = 25
        A = LinearAlgebra.fillstored!(copy(sprand(n,n,2/n)), 1)

        v = ones(n)./n
        x = seeded_stochastic_heat_kernel(A,2.,v)
        xtrue = shkexpm(A,2.,v)
        @test norm(x -xtrue,1) <= n*eps(Float64)

        x = seeded_stochastic_heat_kernel(MatrixNetwork(A),2.,v)
        @test norm(x -xtrue,1) <= n*eps(Float64)

        n = 10
        # A = spdiagm(ones(n-1),-1,n,n)'
        A = copy(spdiagm(-1=>ones(n-1))')
        v = zeros(n)
        v[1] = 1.
        t = 2.
        x = seeded_stochastic_heat_kernel(A,t,v)
        z = zeros(n)
        z[1] = exp(-t)
        for ii=2:n
            z[ii] = z[ii-1]*t./(ii-1.)
        end
        @test norm(x-z,1) <= n*eps(Float64)

        @test abs(seeded_stochastic_heat_kernel(spzeros(1,1),2.,1)[1] - exp(-2.)) <= 10*eps(Float64)
        @test abs(seeded_stochastic_heat_kernel(spzeros(1,1),5.,1)[1] - exp(-5.)) <= 10*eps(Float64)
        @test abs(seeded_stochastic_heat_kernel(sparse(1.0I,1,1),2.,1)[1] - 1.) <= 10*eps(Float64)
    end
    @testset "StochasticMult" begin
        n = 10

        A = MatrixNetwork(LinearAlgebra.fillstored!(copy(sprand(n,n,2/n)), 1))

        MNSM = MatrixNetworks.MatrixNetworkStochasticMult(rand(n), A)
        @test size(MNSM) == (n,n)
        @test size(MNSM, 1) == n
        @test length(MNSM) == n*n
        @test eltype(MNSM) == Float64
        @test ndims(MNSM) == 2

        A = LinearAlgebra.fillstored!(copy(sprand(n,n,2/n)), 1)
        SMSM = MatrixNetworks.SparseMatrixStochasticMult(rand(n), A)
        @test size(SMSM) == (n,n)
        @test size(SMSM, 1) == n
        @test length(SMSM) == n*n
        @test eltype(SMSM) == Float64
        @test ndims(SMSM) == 2
    end
end


# # Iris data

# In this example, we create a logistic regression model that classifies iris flowers.
# It consists of a [single-layer neural network](https://sebastianraschka.com/faq/docs/logisticregr-neuralnet.html)
# that outputs **three** probabilities (one for each species of iris flowers).
# We use Fisher's classic dataset to train the model. This dataset is retrieved from
# the [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/datasets/iris).

# In Machine Learning, a classification task can be performed by a logistic regression model.
# However, we also can create a logistic regression model as a single-layer neural network.
# This neural network has the following characteristics:

# * Uses the [logitcrossentropy](https://fluxml.ai/Flux.jl/stable/models/losses/#Flux.Losses.logitcrossentropy) loss function.
# * Expects the class labels of the iris flowers encoded using [One-Hot encoding](https://fluxml.ai/Flux.jl/stable/data/onehot/#One-Hot-Encoding).
# * Outputs the index in the output vector with the highest value as the class label using
# [onecold](https://fluxml.ai/Flux.jl/stable/data/onehot/#Flux.onecold) which is the inverse operation of One-Hot encoding.

# To run this example, we need the following packages:

using Flux
using Flux: logitcrossentropy, normalise, onecold, onehotbatch
using Statistics: mean
using Parameters: @with_kw

# We set default values for the learning rate *lr* (for the training routine) and the number of
# times that we repeat the train data (more information below):

@with_kw mutable struct Args
    lr::Float64 = 0.5
    repeat::Int = 110
end

# ## Data

# We create the function `get_processed_data` to load the iris data, preprocess
# it (normalize and One-Hot encode the class labels), and split it into train and test datasets.

function get_processed_data(args)
    labels = Flux.Data.Iris.labels()
    features = Flux.Data.Iris.features()

    ## Subract mean, divide by std dev for normed mean of 0 and std dev of 1.
    normed_features = normalise(features, dims=2)

    klasses = sort(unique(labels))
    onehot_labels = onehotbatch(labels, klasses)

    ## Split into training and test sets, 2/3 for training, 1/3 for test.
    train_indices = [1:3:150 ; 2:3:150]

    X_train = normed_features[:, train_indices]
    y_train = onehot_labels[:, train_indices]

    X_test = normed_features[:, 3:3:150]
    y_test = onehot_labels[:, 3:3:150]

    ## Repeat the data `args.repeat` times
    train_data = Iterators.repeated((X_train, y_train), args.repeat)
    test_data = (X_test,y_test)

    return train_data, test_data
end

# The iris data is a 4×150 matrix. This means that the iris data has 150 examples,
# and each example has four features as well as a class label.
# After normalizing and encoding the data, the `get_processed_data` function divides it into train and test data.
# Also, it repeats the examples in the train data so that we have more data to train the neural network.

# ## Metrics

# We use two functions to assess the output of the model: `accuracy` and `confusion matrix`.
# The [accuracy function](https://developers.google.com/machine-learning/crash-course/classification/accuracy)
# measures the percentage of the labels that the model classified correctly.
# On the other hand, the [confusion matrix](https://machinelearningmastery.com/confusion-matrix-machine-learning/)
# is a table that summarises how good the model is for predicting data.

accuracy(x, y, model) = mean(onecold(model(x)) .== onecold(y))

function confusion_matrix(X, y, model)
    ŷ = onehotbatch(onecold(model(X)), 1:3)
    y * transpose(ŷ)
end

# ## Train function

# We define the `train` function that defines the model and trains it:

function train(; kws...)
    ## Initialize hyperparameter arguments
    args = Args(; kws...)

    ## Load processed data
    train_data, test_data = get_processed_data(args)

    ## #Declare model taking 4 features as inputs and outputting 3 probabiltiies,
    ## one for each species of iris.
    model = Chain(Dense(4, 3))

    ## Define loss function to be used in training
    ## For numerical stability, we use here logitcrossentropy
    loss(x, y) = logitcrossentropy(model(x), y)

    ## Training
    ## Gradient descent optimiser with learning rate `args.lr`
    optimiser = Descent(args.lr)

    println("Starting training.")
    Flux.train!(loss, params(model), train_data, optimiser)

    return model, test_data
end

# The function above loads the train and test data.
# Then, it creates the model as a single-layer network that expects as an input
# a four-element vector (features) and outputs a three-element vector
# (the number of classes of species of iris flowers).
# Also, it sets [logitcrossentropy](https://fluxml.ai/Flux.jl/stable/models/losses/#Flux.Losses.logitcrossentropy)
# as the loss function and the Gradient descent optimiser
# [Descent](https://fluxml.ai/Flux.jl/stable/training/optimisers/#Flux.Optimise.Descent).
# Finally, it runs a training step with the
# [train! function](https://fluxml.ai/Flux.jl/stable/training/training/#Flux.Optimise.train!).

# ## Test function

# After training the model, we define the `test` function that
# computes the model performance on the test data.
# It calls the `accuracy` function and displays the confusion matrix.
# This function validates that the model should achieve at least a 0.8 accuracy score.

function test(model, test)
    ## Testing model performance on test data
    X_test, y_test = test
    accuracy_score = accuracy(X_test, y_test, model)

    println("\nAccuracy: $accuracy_score")

    ## Sanity check.
    @assert accuracy_score > 0.8

    ## To avoid confusion, here is the definition of a
    ## Confusion Matrix: https://en.wikipedia.org/wiki/Confusion_matrix
    println("\nConfusion Matrix:\n")
    display(confusion_matrix(X_test, y_test, model))
end

# ## Run the example

# We call the `train` function to run the iris data example and compute the model performance:

cd(@__DIR__)
model, test_data = train()
test(model, test_data)


using Test, StatsModels
using DataFrames

using Unfold
include("test_utilities.jl")

data, evts = loadtestdata("test_case_3a") #
##

f_spl = @formula 0 ~ 1 + conditionA + spl(continuousA, 4) # 1
f = @formula 0 ~ 1 + conditionA + continuousA # 1
data_r = reshape(data, (1, :))
data_e, times = Unfold.epoch(data = data_r, tbl = evts, τ = (-1.0, 1.0), sfreq = 10) # cut the data into epochs

m_mul = coeftable(fit(UnfoldModel, f, evts, data_e, times))
m_mul_spl = coeftable(fit(UnfoldModel, f_spl, evts, data_e, times))

# asking for 4 splines should generate 4 splines
@test length(unique(m_mul_spl.coefname)) == 6 # XXX check back with Unfold whether this is the same! could be n-1 splines in Unfold. We should keep that comparable I guess

# test safe prediction
m = fit(UnfoldModel, f_spl, evts, data_e, times)
r = predict(m,DataFrame(conditionA=[0,0],continuousA=[0.9,1.9]))
@test all(ismissing.(r.yhat[r.continuousA.==1.9]))
@test !any(ismissing.(r.yhat[r.continuousA.==0.9]))

# test time expanded
basisfunction = firbasis(τ = (-1, 1), sfreq = 10, name = "A")
m_tul = coeftable(fit(UnfoldModel, f, evts, data_r, basisfunction))
m_tul_spl = coeftable(fit(UnfoldModel, f_spl, evts, data_r, basisfunction))

# test safe predict
m = fit(UnfoldModel, f_spl, evts, data_r, basisfunction)
@test_broken predict(m,DataFrame(conditionA=[0,0,0],continuousA=[0.9,0.9,1.9]))
#@test_broken all(ismissing.)

#evts_grid = gridexpand()
# results from timeexpanded and non should be equal
#yhat_tul  = predict(m_tul_spl,evts_grid)
#yhat_mul  = predict(m_mul_spl,evts_grid)
if 1 == 0
    using AlgebraOfGraphics
    yhat_mul.conditionA = categorical(yhat_mul.conditionA)
    yhat_mul.continuousA = categorical(yhat_mul.continuousA)
    m = mapping(:times, :yhat, color = :continuousA, linestyle = :conditionA)
    df = yhat_mul
    AlgebraOfGraphics.data(df) * visual(Lines) * m |> draw
end

# test much higher number of splines
f_spl_many = @formula 0 ~ 1 + spl(continuousA, 131) # 1
m_mul_spl_many = coeftable(fit(UnfoldModel, f_spl_many, evts, data_e, times))
@test length(unique(m_mul_spl_many.coefname)) == 132