@testset "projectors" begin

#test project_bounds! with scalar bounds
  x = randn(100)
  l = -0.11
  u = 0.01;
  project_bounds!(x,l,u)
  @test maximum(x) <= u
  @test minimum(x) >= l

#test project_bounds! with vector bounds
  x = 100.0 .* randn(100)
  l = randn(100) .- 10.0
  u = randn(100) .+ 10.0
  project_bounds!(x,l,u)
  [@test x[i] <= u[i] for i=1:100]
  [@test x[i] >= l[i] for i=1:100]

#test project l1_Duchi!
  x=randn(100); tau=norm(x,1)*2; #see if projector returns untouched vector
  y=deepcopy(x)
  project_l1_Duchi!(x,tau)
  @test x == y

  x=randn(100); tau=norm(x,1)*0.234; #choose smaller constraint than vector l1 norm
  project_l1_Duchi!(x,tau)
  @test isapprox(norm(x,1),tau,rtol=10*eps())

  #test project l1_Duchi! with a complex vector
  x=randn(100)+im*randn(100); tau=norm(x,1)*2; #see if projector returns untouched vector
  y=deepcopy(x)
  project_l1_Duchi!(x,tau)
  @test x == y

  x=randn(100)+im*randn(100); tau=norm(x,1)*0.234;
  project_l1_Duchi!(x,tau)
  @test isapprox(norm(x,1),tau,rtol=10*eps())

#test project_cardinality!

  #test vector mode
  x=randn(100)
  project_cardinality!(x,5)
  @test count(!iszero, x)==5

  #test on closed form solution (vector)
  x=[0;0;1;2;3]
  project_cardinality!(x,2)
  @test x==[0;0;0;2;3]

  #test on closed form solution with negative numbers (vector)
  x=[0;0;-1;2;-3]
  project_cardinality!(x,2)
  @test x==[0;0;0;2;-3]

  #test mode that projects each column of a matrix separately
  X=randn(50,100)
  project_cardinality!(X,7,("fiber","x"))
  [@test count(!iszero, X[:,i]) == 7 for i=1:size(X,2)]

  #test mode that projects each row of a matrix separately
  X=randn(50,100)
  project_cardinality!(X,11,("fiber","z"))
  [@test count(!iszero, X[i,:]) == 11 for i=1:size(X,1)]

  #test mode that projects each fiber of a tensor separately (either in x,y or z direction)
  X=randn(50,60,30)
  project_cardinality!(X,7,("fiber","x"))
  [@test count(!iszero, X[:,i,j]) == 7 for i=1:size(X,2) for j=1:size(X,3)]

  X=randn(50,60,30)
  project_cardinality!(X,6,("fiber","y"))
  [@test count(!iszero, X[i,:,j]) == 6 for i=1:size(X,1) for j=1:size(X,3)]

  X=randn(50,60,30)
  project_cardinality!(X,4,("fiber","z"))
  [@test count(!iszero, X[i,j,:]) == 4 for i=1:size(X,1) for j=1:size(X,2)]

  #test mode that projects onto each slice of a tensor separately (either in x,y or z direction)
  X=randn(50,60,30)
  test=project_cardinality!(X,7,("slice","x"),false)
  [@test count(!iszero, test[i,:,:]) == 7 for i=1:size(X,1)]

  X=randn(50,60,30)
  test=project_cardinality!(X,6,("slice","y"),false)
  [@test count(!iszero, test[:,i,:]) == 6 for i=1:size(X,2)]

  X=randn(50,60,30)
  project_cardinality!(X,5,("slice","z"),false)
  [@test count(!iszero, X[:,:,i]) == 5 for i=1:size(X,3)]

# test project_l2!
  #test for sigma smaller than 2-norm of vector
  x=randn(100)
  project_l2!(x,0.123)
  @test isapprox(norm(x,2),0.123,rtol=eps()*10)

  #test for sigma larger than 2-norm of vector
  x=randn(100)
  y=deepcopy(x)
  project_l2!(x,1.234*norm(x,2))
  @test x==y

#test project_rank! (codes assumes input and output are vectors)
    X=randn(100,20)*randn(20,40) #randn(100,40) #test tall matrix
    grid_X=compgrid((1, 1),(100,40))
    Y=randn(40,50)*randn(50,100)#randn(40,100) #test flat matrix
    grid_Y=compgrid((1, 1),(40,100))
    Z=randn(100,30)*randn(30,100) #randn(100,100) #test square matrix
    grid_Z=compgrid((1, 1),(100,100))

    # test if input is returned untouched if input matrix rank is less than the constraint
    XX=vec(deepcopy(X))
    YY=vec(deepcopy(Y))
    ZZ=vec(deepcopy(Z))
    X=vec(X)
    Y=vec(Y)
    Z=vec(Z)
    project_rank!(reshape(X,grid_X.n),30,"")
    project_rank!(reshape(Y,grid_Y.n),40,"")
    project_rank!(reshape(Z,grid_Z.n),35,"")
    @test isapprox(XX,X,rtol=20*eps())
    @test isapprox(YY,Y,rtol=20*eps())
    @test isapprox(ZZ,Z,rtol=20*eps())

    # test if output matrix has correct rank when constraint is smaller than matrix rank
    X=randn(100,40) #test tall matrix
    Y=randn(40,100) #test flat matrix
    Z=randn(100,100) #test square matrix
    X=vec(X); Y=vec(Y); Z=vec(Z)
    project_rank!(reshape(X,grid_X.n),12,"")
    project_rank!(reshape(Y,grid_Y.n),3,"")
    project_rank!(reshape(Z,grid_Z.n),4,"")
    X=reshape(X,grid_X.n[1],grid_X.n[2])
    Y=reshape(Y,grid_Y.n[1],grid_Y.n[2])
    Z=reshape(Z,grid_Z.n[1],grid_Z.n[2])
    r_X=rank(X); r_Y=rank(Y); r_Z=rank(Z);
    @test r_X==12
    @test r_Y==3
    @test r_Z==4

    #test rank projection on slices of a 3D tensor
    X=randn(50,60,30)
    project_rank!(X,7,("slice","x"))
    [@test rank(X[i,:,:]) == 7 for i=1:size(X,1)]

    X=randn(50,60,30)
    project_rank!(X,6,("slice","y"))
    [@test rank(X[:,i,:]) == 6 for i=1:size(X,2)]

    X=randn(50,60,30)
    project_rank!(X,5,("slice","z"))
    [@test rank(X[:,:,i]) == 5 for i=1:size(X,3)]

#test project nuclear! (codes assumes input and output are vectors)
  X=randn(100,40) #test tall matrix
  grid_X=compgrid((1, 1),(100,40))
  Y=randn(40,100) #test flat matrix
  grid_Y=compgrid((1, 1),(40,100))
  Z=randn(100,100) #test square matrix
  grid_Z=compgrid((1, 1),(100,100))

  nn_X = norm(svdvals(X),1)
  nn_Y = norm(svdvals(Y),1)
  nn_Z = norm(svdvals(Z),1)

  # test if input is returned untouched if nuclear norm is less than constraint
  X=vec(X)
  Y=vec(Y)
  Z=vec(Z)
  XX=deepcopy(X)
  YY=deepcopy(Y)
  ZZ=deepcopy(Z)
  project_nuclear!(reshape(X,grid_X.n),nn_X*1.1,"")
  project_nuclear!(reshape(Y,grid_Y.n),nn_Y*1.1,"")
  project_nuclear!(reshape(Z,grid_Z.n),nn_Z*1.1,"")
  @test isapprox(XX,X,rtol=20*eps())
  @test isapprox(YY,Y,rtol=20*eps())
  @test isapprox(ZZ,Z,rtol=20*eps())

  # test if output matrix has correct nuclear norm when constraint is smaller than matrix nuclear norm
  X=randn(100,40) #test tall matrix
  Y=randn(40,100) #test flat matrix
  Z=randn(100,100) #test square matrix
  nn_X = norm(svdvals(X),1)
  nn_Y = norm(svdvals(Y),1)
  nn_Z = norm(svdvals(Z),1)
  X=vec(X)
  Y=vec(Y)
  Z=vec(Z)
  project_nuclear!(reshape(X,grid_X.n),nn_X*0.5,"")
  project_nuclear!(reshape(Y,grid_Y.n),nn_Y*0.5,"")
  project_nuclear!(reshape(Z,grid_Z.n),nn_Z*0.5,"")
  X=reshape(X,grid_X.n[1],grid_X.n[2])
  Y=reshape(Y,grid_Y.n[1],grid_Y.n[2])
  Z=reshape(Z,grid_Z.n[1],grid_Z.n[2])
  nn_Xp = norm(svdvals(X),1)
  nn_Yp = norm(svdvals(Y),1)
  nn_Zp = norm(svdvals(Z),1)
  @test isapprox(nn_Xp,nn_X*0.5,rtol=20*eps())
  @test isapprox(nn_Yp,nn_Y*0.5,rtol=20*eps())
  @test isapprox(nn_Zp,nn_Z*0.5,rtol=20*eps())

  #test nuclear norm projection onto slices of a 3D tensor
  X=randn(50,60,30)
  project_nuclear!(X,1.234,("slice","x"))
  [@test isapprox(norm(svdvals(X[i,:,:]),1), 1.234,rtol=100*eps()) for i=1:size(X,1)]

  X=randn(50,60,30)
  project_nuclear!(X,1.234,("slice","y"))
  [@test isapprox(norm(svdvals(X[:,i,:]),1), 1.234,rtol=100*eps()) for i=1:size(X,2)]

  X=randn(50,60,30)
  project_nuclear!(X,1.234,("slice","z"))
  [@test isapprox(norm(svdvals(X[:,:,i]),1), 1.234,rtol=100*eps()) for i=1:size(X,3)]

#test project_subspace!
  #on a vector inputs
  #orthogonal subspace
  M=randn(100,50)
  F=svd(M)
  x=randn(100)
  y=deepcopy(x)
  project_subspace!(x,F.U,true)
  @test isapprox(x,F.U*(F.U'*y),rtol=eps()*10)

  #non-orthogonal subspace
  M=randn(100,50)
  x=randn(100)
  y=deepcopy(x)
  project_subspace!(x,M,false)
  @test isapprox(x,M*((M'*M)\(M'*y)),rtol=eps()*10)

  #on matrix input: project every column onto the subspace
  M=randn(100,50)
  x=randn(100,5)
  y=deepcopy(x)
  project_subspace!(x,M,false,("fiber","x"))
  @test isapprox(x,M*((M'*M)\(M'*y)),rtol=eps()*10)

  #on matrix input: project every row onto the subspace
  M=randn(23,50)
  x=randn(5,23)
  y=deepcopy(x)
  project_subspace!(x,M,false,("fiber","z"))
  @test isapprox(x,(M*((M'*M)\(M'*y')))',rtol=eps()*10)

  #on tensor input: project every z slice onto the subspace
  x=randn(23,50,11)
  y=deepcopy(x)
  M=randn(23*50,5)
  project_subspace!(x,M,false,("slice","z"))
  x=reshape(x,size(y))
  [@test isapprox(vec(x[:,:,i]),M*((M'*M)\(M'*vec(y[:,:,i]))),rtol=eps()*100) for i=1:11]

  #on tensor input: project every y slice onto the subspace
  x=randn(23,11,50)
  y=deepcopy(x)
  M=randn(23*50,5)
  x=project_subspace!(x,M,false,("slice","y"))
  x=reshape(x,size(y))
  [@test isapprox(vec(x[:,i,:]),M*((M'*M)\(M'*vec(y[:,i,:]))),rtol=eps()*100) for i=1:11]

  #on tensor input: project every x slice onto the subspace
  x=randn(11,23,50)
  y=deepcopy(x)
  M=randn(23*50,5)
  x=project_subspace!(x,M,false,("slice","x"))
  x=reshape(x,size(y))
  [@test isapprox(vec(x[i,:,:]),M*((M'*M)\(M'*vec(y[i,:,:]))),rtol=eps()*100) for i=1:11]

#test projection onto relaxed histogram
  #first test exact histogram projection
  ref = sort(randn(100))
  x   = randn(100)
  project_histogram_relaxed!(x,ref,ref)
  @test ref == sort(x)

  #relaxed histogram projection
  LB = sort(randn(100))
  UB = LB.+0.7
  x   = randn(100)
  project_histogram_relaxed!(x,LB,UB)
  x = sort(x)
  [@test x[i] <= UB[i] for i=1:100]
  [@test x[i] >= LB[i] for i=1:100]

end


#==============================================================================
This example generates two noiseless measurement vectors corresponding to one
binary matrix and two different frequency vectors.

For each measurement vector, we compute MAP estimates, all global minimizers of
the negative log-likelihood, posterior means, and posterior standard deviations.

This is the first set of examples presented in Section 4.2 of the article:

L. Mustonen, X. Gao, A. Santana, R. Mitchell, Y. Vigfusson, and L. Ruthotto,
A Bayesian framework for molecular strain identification from mixed diagnostic samples
https://arxiv.org/abs/1803.02916
==============================================================================#

using StrainRecon
srand(0) # initialize the random number generator for reproducibility

# binary matrix and frequency vectors (ground truths)
M = [0 1; 1 0; 1 1]
w1 = [0.6; 0.4]
w2 = [0.5; 0.5]

# measurement data (without noise)
d1 = M*w1
d2 = M*w2

# dimensions of the problem (the number of classes is implicitly p=2)
m = size(M,1)
n = size(M,2)

R = 100 # inverse square root of the diagonal entry in the matrix Gamma
param1 = getStrainReconParam(d1,n,R=R)
param2 = getStrainReconParam(d2,n,R=R)

# verify that the block coordinate descent solver finds a global minimum
Mreco1,wreco1 = solveMAP(param1)
Mreco2,wreco2 = solveMAP(param2)
misfit1 = misfit(param1,Mreco1,wreco1)[1]
misfit2 = misfit(param2,Mreco2,wreco2)[1]
println("Misfit for the first MAP estimate is $misfit1")
println("Misfit for the second MAP estimate is $misfit2")
println()

# generate all possible binary matrices of size (m,n)
Mbits = [[bit=='1' for bit in bin(k,m*n)] for k in 0:2^(m*n)-1]
Mall = true*reshape(hcat(Mbits...),(m,n,2^(m*n)))
N = size(Mall,3)

# compute minimizing frequency vectors and the corresponding misfits for
# each binary matrix
wmins1 = zeros(n,N)
misfits1 = zeros(N)
wmins2 = zeros(n,N)
misfits2 = zeros(N)
for idx = 1:N
    wmins1[:,idx] = findw(param1,Mall[:,:,idx])
    misfits1[idx] = misfit(param1,Mall[:,:,idx],wmins1[:,idx])[1]

    wmins2[:,idx] = findw(param2,Mall[:,:,idx])
    misfits2[idx] = misfit(param2,Mall[:,:,idx],wmins2[:,idx])[1]
end

# sort misfits in ascending order
srt1 = sortperm(misfits1)
srt2 = sortperm(misfits2)

# print all pairs (M,w) that correspond to the minimum misfit
println("Case 1 minimizers:")
for idx = 1:N
    if abs(misfits1[srt1[idx]] - misfits1[srt1[1]]) > 1e-8
        break # not a global minimum anymore
    end
    Base.showarray(STDOUT,1*Mall[:,:,srt1[idx]],false)
    println()
    println(wmins1[:,srt1[idx]])
end
println()

println("Case 2 minimizers:")
for idx = 1:N
    if abs(misfits2[srt2[idx]] - misfits2[srt2[1]]) > 1e-8
        break # not a global minimum anymore
    end
    Base.showarray(STDOUT,1*Mall[:,:,srt2[idx]],false)
    println()
    println(wmins2[:,srt2[idx]])
end

# compute posterior means and variances
Mcm1,wcm1,dummy1,Mvar1,wvar1 = wrapperIntegratePosterior(param1)
Mcm2,wcm2,dummy2,Mvar2,wvar2 = wrapperIntegratePosterior(param2)

println("Case 1 posterior means:")
Base.showarray(STDOUT,Mcm1,false)
println()
println(wcm1)
println()

println("Case 1 posterior standard deviations:")
Base.showarray(STDOUT,sqrt.(Mvar1),false)
println()
println(sqrt.(wvar1))
println()

println("Case 2 posterior means:")
Base.showarray(STDOUT,Mcm2,false)
println()
println(wcm2)
println()

println("Case 2 posterior standard deviations:")
Base.showarray(STDOUT,sqrt.(Mvar2),false)
println()
println(sqrt.(wvar2))
println()


using DSP, Test
using DelimitedFiles: readdlm
using Polynomials: Polynomial

#######################################
#
#  http://www.mathworks.com.au/help/signal/ref/freqz.html
#  Example 1: Frequency response from transfer function
#
#  dlmwrite('freqz-eg1.txt',[w, abs(h)], 'delimiter', '\t', 'precision', '%.12f')
#
#######################################
@testset "freqresp from TF" begin
    # Matlab
    freqz_eg1_w_abs = readdlm(joinpath(dirname(@__FILE__), "data", "freqz-eg1.txt"),'\t')
    matlab_abs   = freqz_eg1_w_abs[:,2]

    # Julia
    b0 = 0.05634
    b1 = [1,  1]
    b2 = [1, -1.0166, 1]
    a1 = [1, -0.683]
    a2 = [1, -1.4461, 0.7957]
    b = b0*conv(b1, b2)
    a = conv(a1, a2)

    w     = range(0, stop=6.280045284537, length=2001)
    h     = freqresp(PolynomialRatio(b, a), w)
    h_abs = convert(Array{Float64}, abs.(h))
    h_ref = [b0 * Polynomial(b1)(z) * Polynomial(b2)(z) / (Polynomial(a1)(z) * Polynomial(a2)(z)) for z ∈ exp.(-im*w)]

    # Test
    @test h_abs ≈ matlab_abs
    @test h ≈ h_ref
end

#######################################
#
# freqresp with conversion to PolynomialRatio may lead to undesirable roundoff errors
# check by using the same poles and zeros such that they should cancel
#
#######################################
@testset "freqresp pole/zero cancellation" begin
    rs = [1-10.0^-n for n in 1:15]
    @test freqresp(ZeroPoleGain(rs, reverse(rs), 42.0), range(0, stop=2π, length=50)) ≈ fill(42., 50)

    biq = Biquad(42.0, 84.0*real(.999999*exp(im)), 42.0*.999999^2, 2.0*real(.999999*exp(im)), .999999^2)
    @test freqresp(biq, range(0, stop=2π, length=50)) ≈ fill(42.0, 50)

    pr = [1-10.0^-n for n in 1:10]
    zr = reverse(pr)
    bs = [Biquad(42.0, 84.0*real(r[1]*exp(im)), 42.0*r[1]^2, 2.0*real(r[2]*exp(im)), r[2]^2) for r in zip(zr, pr)]
    sos = SecondOrderSections(bs, 42.0^-9)
    @test freqresp(sos, range(0, stop=2π, length=50)) ≈ fill(42.0, 50)

    @test freqresp(ZeroPoleGain(ComplexF64[], ComplexF64[], 42.0), range(0, stop=2π, length=50)) == fill(42.0, 50)
    @test freqresp(SecondOrderSections(Biquad{:z,Float64}[], 42.0), range(0, stop=2π, length=50)) == fill(42.0, 50)
end

#######################################
#
#  Test frequency, phase, impulse and step response
#
#  Data from Matlab using b,a and a from above:
#  ir = impz(b,a, 512);
#  stepr = stepz(b,a, 512);
#  h = abs(freqz(b,a));
#  [phi,f] =  phasez(b, a);
#  all = [f, ir, stepr, h, phi];
#  dlmwrite('responses-eg1.txt',all, 'delimiter', '\t', 'precision', '%.12f')
#
#######################################
@testset "impresp, stepresp, freqresp, phaseresp" begin
    Hdelay = PolynomialRatio{:z}([0, 1], [1])
    W = range(0, stop=2π, length=100)
    for Tf in (PolynomialRatio, ZeroPoleGain, Biquad, SecondOrderSections)
        H = convert(Tf, Hdelay)
        @test freqresp(H, W) ≈ exp.(-W*im)
        @test phaseresp(H, W) ≈ -W
        @test grpdelay(H, W) ≈ fill(1.0, length(W))
        @test impresp(H, 10) == [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
        @test stepresp(H, 10) == [0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    end

    matlab_resp = readdlm(joinpath(dirname(@__FILE__), "data", "responses-eg1.txt"),'\t')
    b0 = 0.05634
    b1 = [1,  1]
    b2 = [1, -1.0166, 1]
    a1 = [1, -0.683]
    a2 = [1, -1.4461, 0.7957]
    b = b0*conv(b1, b2)
    a = conv(a1, a2)
    df = PolynomialRatio(b, a)
    w = matlab_resp[:,1]

    #Impulse response
    impz_matlab = matlab_resp[:,2]
    @test impresp(df, 512) ≈ impz_matlab

    #Step response
    stepz_matlab = matlab_resp[:,3]
    @test stepresp(df, 512) ≈ stepz_matlab

    h_matlab = matlab_resp[:,4]
    @test abs.(freqresp(df, w)) ≈ h_matlab
    @test abs.(freqresp(SecondOrderSections(df), w)) ≈ h_matlab
    @test abs.(freqresp(ZeroPoleGain(df), w)) ≈ h_matlab

    phi_matlab = matlab_resp[:,5]
    @test phaseresp(df, w) ≈ phi_matlab

    # Test diffent versions of the functions
    H, w = freqresp(df)
    @test H == freqresp(df, w)
    @test minimum(w) ≤ 0                 # w should cover frequencies from 0 ...
    @test maximum(w) ≥ float(π)          # ... to π ...
    @test maximum(abs.(diff(w))) < 0.03  # and be sufficiently dense

    phi, w = phaseresp(df)
    @test phi == phaseresp(df, w)
    @test minimum(w) ≤ 0                 # w should cover frequencies from 0 ...
    @test maximum(w) ≥ float(π)          # ... to π ...
    @test maximum(abs.(diff(w))) < 0.03  # and be sufficiently dense

    @test cumsum(impresp(df)) ≈ stepresp(df)
end

#######################################
#
#  http://www.mathworks.com.au/help/signal/ref/freqs.html
#  Example 1: Frequency response from the transfer function
#
#  dlmwrite('freqs-eg1.txt',[w; mag; phasedeg]', 'delimiter', '\t', 'precision', '%.10f')
#
#######################################
@testset "freqresp(::FilterCoefficients{:s})" begin
    w = 10 .^ range(-1, stop=1, length=50)
    for (b, a, H) in (
            ([1, 0], [1], w*im), # s
            ([1], [1, 0], 1 ./ (w*im)), # 1/s
            ([0, 0, 1], [2, 10], 1 ./ (2*w*im .+ 10)), # 1/(2s + 10)
            ([1, 0, 1], [2, 10], (1 .- w.^2) ./ (2*w*im .+ 10)), # (s^2 + 1)/(2s + 10)
        )
        @test freqresp(PolynomialRatio{:s}(b, a), w) ≈ H
        @test freqresp(ZeroPoleGain(PolynomialRatio{:s}(b, a)), w) ≈ H
        if isone(a[1]) && lastindex(Polynomial(reverse(b))) <= lastindex(Polynomial(reverse(a)))
            @test freqresp(Biquad(PolynomialRatio{:s}(b, a)), w) ≈ H
        else
            # cannot represent this PolynomialRatio as a Biquad due to implied a0=1
            @test_broken freqresp(Biquad(PolynomialRatio{:s}(b, a)), w) ≈ H
        end
        if lastindex(Polynomial(reverse(b))) <= lastindex(Polynomial(reverse(a)))
            @test freqresp(SecondOrderSections(PolynomialRatio{:s}(b, a)), w) ≈ H
        else
            # cannot represent this PolynomialRatio as a Biquad due to implied a0=1
            @test_broken freqresp(SecondOrderSections(PolynomialRatio{:s}(b, a)), w) ≈ H
        end
    end

    # Julia
    a = [1.0, 0.4, 1.0]
    b = [0.2, 0.3, 1.0]
    w = 10 .^ range(-1, stop=1, length=50)

    h        = freqresp(PolynomialRatio{:s}(b, a), w)
    mag      = convert(Array{Float64}, abs.(h))
    phasedeg = (180/pi)*phaseresp(PolynomialRatio{:s}(b, a), w)

    # Matlab
    freqs_eg1_w_mag_phasedeg = readdlm(joinpath(dirname(@__FILE__), "data", "freqs-eg1.txt"),'\t')
    matlab_w        = freqs_eg1_w_mag_phasedeg[:,1]
    matlab_mag      = freqs_eg1_w_mag_phasedeg[:,2]
    matlab_phasedeg = freqs_eg1_w_mag_phasedeg[:,3]
    h_ref = [Polynomial(reverse(b))(s) / Polynomial(reverse(a))(s) for s ∈ im*w]

    # Test
    @test w ≈ matlab_w
    @test h ≈ h_ref
    @test mag ≈ matlab_mag
    @test phasedeg ≈ matlab_phasedeg

    @test h ≈ freqresp(ZeroPoleGain(PolynomialRatio{:s}(b, a)), w)
    @test h ≈ freqresp(SecondOrderSections(PolynomialRatio{:s}(b, a)), w)
end

# ######################################
#
#  Test grpdelay
#
#  Data from Matlab using b,a and a from above:
#  [gd, w] = grpdelay(b, a, 512)
#  all = [w gd]
#  dlmwrite('grpdelay_eg1.txt', all, 'delimiter', '\t', 'precision', '%.12f')
#
# ######################################
@testset "grpdelay" begin
    matlab_delay = readdlm(joinpath(dirname(@__FILE__), "data", "grpdelay_eg1.txt"),'\t')
    b0 = 0.05634
    b1 = [1,  1]
    b2 = [1, -1.0166, 1]
    a1 = [1, -0.683]
    a2 = [1, -1.4461, 0.7957]
    b = b0*conv(b1, b2)
    a = conv(a1, a2)
    df = PolynomialRatio(b, a)
    w = matlab_delay[:, 1]

    grpdelay_matlab = matlab_delay[:, 2]
    @test grpdelay(df, w) ≈ grpdelay_matlab

    # Test with IIR filters types I-IV
    tau, w = grpdelay(PolynomialRatio([1, 1, 1, 1, 1], [1]))
    @test tau ≈ fill(2.0, length(w))
    tau, w = grpdelay(PolynomialRatio([1, 1, 1, 1, 1, 1], [1]))
    @test tau ≈ fill(2.5, length(w))
    tau, w = grpdelay(PolynomialRatio([1, 0, -1], [1]))
    @test tau ≈ fill(1.0, length(w))
    tau, w = grpdelay(PolynomialRatio([1, -1], [1]))
    @test tau ≈ fill(0.5, length(w))
    @test minimum(w) ≤ 0                 # w should cover frequencies from 0 ...
    @test maximum(w) ≥ float(π)          # ... to π ...
    @test maximum(abs.(diff(w))) < 0.03  # and be sufficiently dense

    # group delay of analog filters
    w = matlab_delay[:, 1]
    @test grpdelay(PolynomialRatio{:s}([1, 2], [123]), w) ≈ 0.5 ./ (1 .+ w.^2/4)
    @test grpdelay(PolynomialRatio{:s}([123], [1, 2]), w) ≈ -0.5 ./ (1 .+ w.^2/4)
    @test grpdelay(PolynomialRatio{:s}([1, 2], [3, 9]), w) ≈ 0.5 ./ (1 .+ w.^2/4) - 1/3 ./ (1 .+ w.^2/9)
    tau, w = grpdelay(PolynomialRatio{:s}([1, 2], [123]))
    @test tau ≈ 0.5 ./ (1 .+ w.^2/4)
    @test minimum(w) < 2 < maximum(w) # position of zero covered
    tau, w = grpdelay(PolynomialRatio{:s}([123], [1, 2]))
    @test tau ≈ -0.5 ./ (1 .+ w.^2/4)
    @test minimum(w) < 2 < maximum(w) # position of pole covered
    tau, w = grpdelay(PolynomialRatio{:s}([1, 2], [3, 9]))
    @test tau ≈ 0.5 ./ (1 .+ w.^2/4) - 1/3 ./ (1 .+ w.^2/9)
    @test minimum(w) < 2 && 3 < maximum(w) # position of pole and zero covered
end


#=##############################################################################
# # DESCRIPTION
#     Examples of VTK formatting and usage of VTKtools
# # AUTHORSHIP
#   * Author    : Eduardo J Alvarez
#   * Email     : Edo.AlvarezR@gmail.com
#   * Created   : Nov 2017
#   * License   : MIT License
=###############################################################################

function airfoil_example()
  x,y = readcontour(data_path*"S809.txt"; header_len=2)
  plot_airfoil(x,y; style="--.k")

  println("Close figure and press ENTER")
  readline()

  # Separate upper and lower surfaces to make the contour injective in x
  upper, lower = splitcontour(x,y)

  # Parameterize both surfaces independently
  fun_upper = gt.parameterize(upper[1], upper[2], zeros(Float64, size(upper[1])); inj_var=1)
  fun_lower = gt.parameterize(lower[1], lower[2], zeros(Float64, size(lower[1])); inj_var=1)

  # New discretization for both surfaces
  upper_points = gt.discretize(fun_upper, 0, 1, 50, 1.0)
  lower_points = gt.discretize(fun_lower, 0, 1, 50, 1.0)

  # Put both surfaces back together from TE over the top and from LE over the bottom.
  reverse!(upper_points)                           # Trailing edge over the top
  new_x = [point[1] for point in upper_points]
  new_y = [point[2] for point in upper_points]     # Leading edge over the bottom
  new_x = vcat(new_x, [point[1] for point in lower_points])
  new_y = vcat(new_y, [point[2] for point in lower_points])

  plot_airfoil(new_x, new_y; style="^r", label="Parameterized")
  plot_airfoil(x,y; style="--.k", label="Original")
  legend(loc="best")

  println("Close figure and press ENTER")
  readline()

  # Upper surface sections
  sec1 = (0.35, 20, 3.0, true) # 35% of the line has 20 sections in ratio 3.0 around center
  sec2 = (0.65, 33, 3.0, true) # 65% of the line has 33 sections in ratio 3.0 around center

  # New discretization for both surfaces
  upper_points = gt.multidiscretize(fun_upper, 0, 1, [sec1,sec2])
  lower_points = gt.discretize(fun_lower, 0, 1, 50, 8.0; central=true)

  # Put both surfaces back together from TE over the top and from LE over the bottom.
  reverse!(upper_points)                           # Trailing edge over the top
  new_x = [point[1] for point in upper_points]
  new_y = [point[2] for point in upper_points]     # Leading edge over the bottom
  new_x = vcat(new_x, [point[1] for point in lower_points])
  new_y = vcat(new_y, [point[2] for point in lower_points])

  plot_airfoil(new_x, new_y; style="--^r", label="Parameterized")
end

### INTERNAL FUNCTIONS #########################################################
"Plots the contour of an airfoil given in x,y"
function plot_airfoil(x::Array{Float64,1}, y::Array{Float64,1};
                      label="Airfoil", style="-k", figfactor=1.0,
                      title_str="Airfoil geometry")
  # Sizes the figure
  figsize = [7*1.5,5*0.5]*figfactor
  xmin, xmax = -0.05, 1.05
  yrange = (xmax-xmin)/figsize[1] * figsize[2]
  ymin, ymax = -yrange/2, yrange/2
  fig1 = figure(title_str, figsize=(figsize[1], figsize[2]))
  xlim([xmin, xmax])
  ylim([ymin, ymax])

  PyPlot.plot(x,y, style, label=label)
  xlabel("x")
  ylabel("y")
  grid(true, color="0.8", linestyle="--")
  title(title_str)

  if label!="Airfoil"; legend(loc="best"); end;
end

"Receives a .dat file as pulled from airfoiltools.com containing the x and y
contour coordinates of an airfoil, and returns arrays x and y."
function readcontour(file_name; header_len=1)
  x, y = Float64[], Float64[]

  open(file_name) do f
    for (i,line) in enumerate(eachline(f))

      # Ignores header
      if i<=header_len
        nothing
      # Parses each line
      else
        this_x, this_y = split(line)
        push!(x, parse(Float64, this_x))
        push!(y, parse(Float64, this_y))
      end

    end
  end
  return x,y
end

"""
  Receives an airfoil contour and splits it up in upper and lower surfaces as
  divided by the chord line. It returns `(upper, lower)` with `upper=(x,y)` the
  points of the upper surface, ditto for `lower`. Both `upper` and `lower` are
  given in increasing order in x (i.e., from leading to trailing edge).
"""
function splitcontour(x,y)
  # ERROR CASES
  if !(x[1] in [0.0, 1.0])
    error("Invalid contour. x[1] must be either 0.0 or 1.0, got $(x[1]).")
  end

  # Flag indicating whether the contour start at the trailing or leading edge
  start_TE = x[1]==1.0

  # Find the opposite end of the contour
  end_i = -1
  for (i, xi) in enumerate(x)
    if i==1
      nothing
    # Case of starting from the trailing edge
    elseif start_TE && xi > x[i-1]
      end_i = i-1
      break
    # Case of leading edge
    elseif !start_TE  && xi < x[i-1]
      end_i = i-1
      break
    end
  end

  # ERROR CASE
  if end_i==-1
    error("Logic error! End of airfoil not found!")
  end

  # Splits them up
  x_sec1, y_sec1 = x[1:end_i], y[1:end_i]
  x_sec2, y_sec2 = x[end_i:end], y[end_i:end]

  # Sorts them from LE to TE
  if x_sec1[1] > 0.5; reverse!(x_sec1); reverse!(y_sec1); end;
  if x_sec2[1] > 0.5; reverse!(x_sec2); reverse!(y_sec2); end;

  # Determines upper and lower surfaces
  if Statistics.mean(y_sec1) > Statistics.mean(y_sec2)
    upper = [x_sec1, y_sec1]
    lower = [x_sec2, y_sec2]
  else
    upper = [x_sec2, y_sec2]
    lower = [x_sec1, y_sec1]
  end

  return upper, lower
end
################################################################################


using DiffEqBase, OrdinaryDiffEq, Sundials, ApproxFun, DiffEqApproxFun
using Base.Test

S=Fourier()
u0=Fun(θ->cos(cos(θ-0.1))-cos(cos(0-0.1)),S)
c=Fun(cos,S)

ode_prob = ODEProblem((u,p,t)->u''+(c+1)*u',u0,(0.,1.))
prob = ApproxFunProblem(ode_prob)
@time sol=solve(prob,Euler(),dt=1/1000)
@time sol=solve(prob,Tsit5())
sol(0.5)
sol(0.5,0.2)

@time sol=solve(prob,CVODE_BDF())

sol[1]
sol[5](0.2)

function bc(t,u)
  C=eye(S)[3:end,:]
  tmp = [Evaluation(0);
         Evaluation(2π);
         C]\[0.;0.;u]
end

u0=Fun(θ->cos(cos(θ)) - cos(cos(0)),S)
u0 = bc(0.0,u0)
c=Fun(cos,S)
ode_prob = ODEProblem((u,p,t)->u''+(c+1)*u',u0,(0.,1.))

prob = ApproxFunProblem(ode_prob)

condition(t,u,integrator) = true
function affect!(integrator)
  S=Fourier()
  u = Fun(S,integrator.u)
  B=Dirichlet()
  C=eye(S)[3:end,:]
  tmp = [Evaluation(0);
         Evaluation(2π);
         C]\[0.;0.;u]
  integrator.u=pad!(tmp.coefficients,length(integrator.u))
end
boundary_projection = DiscreteCallback(condition,affect!)

@time sol=solve(prob,Tsit5())
@test sol(0.0,0.0) ≈ 0.0 atol=1e-12
@test sol(0.0,2π) ≈ 0.0 atol=1e-12
@test sol(0.5,0.0) > 0.3
@test sol(0.5,2π) > 0.3
@test sol(1.0,0.0) > 0.3
@test sol(1.0,2π) > 0.3

@time sol=solve(prob,Tsit5(),callback=boundary_projection)
@test sol(0.0,0.0) ≈ 0.0 atol=1e-12
@test sol(0.0,2π) ≈ 0.0 atol=1e-12
@test sol(0.5,0.0) ≈ 0.0 atol=1e-3
@test sol(0.5,2π) ≈ 0.0 atol=1e-3
@test sol(1.0,0.0) ≈ 0.0 atol=1e-3
@test sol(1.0,2π) ≈ 0.0 atol=1e-3

boundary_projection2 = BoundaryCallback(bc)

@time sol=solve(prob,Tsit5(),callback=boundary_projection2)
@test sol(0.0,0.0) ≈ 0.0 atol=1e-12
@test sol(0.0,2π) ≈ 0.0 atol=1e-12
@test sol(0.5,0.0) ≈ 0.0 atol=1e-3
@test sol(0.5,2π) ≈ 0.0 atol=1e-3
@test sol(1.0,0.0) ≈ 0.0 atol=1e-3
@test sol(1.0,2π) ≈ 0.0 atol=1e-3


using Test
using ClimaCore.Geometry
using LinearAlgebra, UnPack, StaticArrays

@testset "1D X,Y,Z Points" begin

    for FT in (Float32, Float64, BigFloat)
        for pt in (
            Geometry.XPoint(one(FT)),
            Geometry.YPoint(one(FT)),
            Geometry.ZPoint(one(FT)),
        )
            @test Geometry.ncomponents(pt) == 1
            @test Geometry.components(pt) isa SVector{1, FT}
            @test Geometry.component(pt, 1) isa FT
            @test Geometry.component(pt, 1) == one(FT)
            @test_throws Exception Geometry.component(pt, 2)
            @test Geometry.coordinate(pt, 1) === pt
            @test_throws Exception Geometry.coordinate(pt, 2)
            @test pt * FT(2) == typeof(pt)(one(FT) * FT(2))
        end
    end
end

@testset "2D XY,XZ Points" begin

    for FT in (Float32, Float64, BigFloat)
        for pt in (
            Geometry.XYPoint(one(FT), zero(FT)),
            Geometry.XZPoint(one(FT), zero(FT)),
        )
            @test Geometry.ncomponents(pt) == 2
            @test Geometry.components(pt) isa SVector{2, FT}
            @test Geometry.component(pt, 1) isa FT
            @test Geometry.component(pt, 2) isa FT
            @test Geometry.component(pt, 1) == one(FT)
            @test Geometry.component(pt, 2) == zero(FT)
            @test_throws Exception Geometry.component(pt, 3)
            @test Geometry.coordinate(pt, 1) == Geometry.XPoint(one(FT))
            if pt isa Geometry.XYPoint
                @test Geometry.coordinate(pt, 2) == Geometry.YPoint(zero(FT))
            elseif pt isa Geometry.XZPoint
                @test Geometry.coordinate(pt, 2) == Geometry.ZPoint(zero(FT))
            end
            @test_throws Exception Geometry.coordinate(pt, 3)
            @test pt * FT(2) == typeof(pt)(one(FT) * FT(2), zero(FT))
        end
    end
end

@testset "3D XYZ Points" begin

    for FT in (Float32, Float64, BigFloat)
        for pt in (Geometry.XYZPoint(one(FT), zero(FT), -one(FT)),)
            @test Geometry.ncomponents(pt) == 3
            @test Geometry.components(pt) isa SVector{3, FT}
            @test Geometry.component(pt, 1) isa FT
            @test Geometry.component(pt, 2) isa FT
            @test Geometry.component(pt, 3) isa FT
            @test Geometry.component(pt, 1) == one(FT)
            @test Geometry.component(pt, 2) == zero(FT)
            @test Geometry.component(pt, 3) == -one(FT)
            @test_throws Exception Geometry.component(pt, 4)
            @test Geometry.coordinate(pt, 1) == Geometry.XPoint(one(FT))
            @test Geometry.coordinate(pt, 2) == Geometry.YPoint(zero(FT))
            @test Geometry.coordinate(pt, 3) == Geometry.ZPoint(-one(FT))
            @test_throws Exception Geometry.coordinate(pt, 4)
            @test pt * FT(2) ==
                  typeof(pt)(one(FT) * FT(2), zero(FT), -2 * one(FT))
        end
    end
end

@testset "Vectors" begin
    wᵢ = Geometry.Covariant12Vector(1.0, 2.0)
    vʲ = Geometry.Contravariant12Vector(3.0, 4.0)

    @test wᵢ[1] == 1.0
    @test wᵢ[2] == 2.0
    @test vʲ[1] == 3.0
    @test vʲ[2] == 4.0

    @test wᵢ * 3 == Geometry.Covariant12Vector(3.0, 6.0)
    @test wᵢ + wᵢ == Geometry.Covariant12Vector(2.0, 4.0)

    uᵏ = Geometry.Contravariant12Vector(1.0, 2.0)
    @test_throws Exception wᵢ * uᵏ

    T = uᵏ ⊗ uᵏ
    @test uᵏ ⊗ uᵏ isa Geometry.Contravariant2Tensor

    @test T * wᵢ == Geometry.Contravariant12Vector(5.0, 10.0)
    @test_throws Exception T * uᵏ
end

@testset "Sample flux calculation" begin
    state = (ρ = 2.0, ρu = Geometry.UVVector(1.0, 2.0), ρθ = 0.5)

    function flux(state, g)
        @unpack ρ, ρu, ρθ = state
        u = ρu / ρ
        return (ρ = ρu, ρu = (ρu ⊗ u) + (g * ρ^2 / 2) * I, ρθ = ρθ * u)
    end
    @test flux(state, 10.0) == (
        ρ = Geometry.UVVector(1.0, 2.0),
        ρu = Geometry.Axis2Tensor(
            (Geometry.UVAxis(), Geometry.UVAxis()),
            SMatrix{2, 2}(0.5 + 20.0, 1.0, 1.0, 2.0 + 20.0),
        ),
        ρθ = Geometry.UVVector(0.25, 0.5),
    )
end

@testset "XYZ -> Cartesian coordinate conversions" begin

    global_geom = Geometry.CartesianGlobalGeometry()

    @test Geometry.CartesianPoint(Geometry.XYPoint(1.0, 2.0), global_geom) ==
          Geometry.Cartesian12Point(1.0, 2.0)
    @test Geometry.Cartesian123Point(Geometry.XYPoint(1.0, 2.0), global_geom) ==
          Geometry.Cartesian123Point(1.0, 2.0, 0.0)

    @test Geometry.CartesianPoint(Geometry.XPoint(1.0), global_geom) ==
          Geometry.Cartesian1Point(1.0)
    @test Geometry.Cartesian123Point(Geometry.XPoint(1.0), global_geom) ==
          Geometry.Cartesian123Point(1.0, 0.0, 0.0)

    @test Geometry.CartesianPoint(Geometry.ZPoint(3.0), global_geom) ==
          Geometry.Cartesian3Point(3.0)
    @test Geometry.Cartesian123Point(Geometry.ZPoint(3.0), global_geom) ==
          Geometry.Cartesian123Point(0.0, 0.0, 3.0)

    @test Geometry.CartesianPoint(Geometry.XZPoint(1.0, 3.0), global_geom) ==
          Geometry.Cartesian13Point(1.0, 3.0)
    @test Geometry.Cartesian123Point(Geometry.XZPoint(1.0, 3.0), global_geom) ==
          Geometry.Cartesian123Point(1.0, 0.0, 3.0)
end

@testset "LatLong -> Cartesian coordinate conversions" begin
    global_geom = Geometry.SphericalGlobalGeometry(2.0)

    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) == Geometry.Cartesian123Point(2.0, 0.0, 0.0)
    @test Geometry.Cartesian123Point(
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) == Geometry.Cartesian123Point(2.0, 0.0, 0.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(0.0, 90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 2.0, 0.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(0.0, -90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, -2.0, 0.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(0.0, 180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(-2.0, 0.0, 0.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(0.0, -180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(-2.0, 0.0, 0.0)

    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(45.0, 0.0),
        global_geom,
    ) == Geometry.Cartesian123Point(sqrt(2.0), 0.0, sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(45.0, 90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, sqrt(2.0), sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(45.0, -90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, -sqrt(2.0), sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(45.0, 180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(-sqrt(2.0), 0.0, sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(45.0, -180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(-sqrt(2.0), 0.0, sqrt(2.0))

    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-45.0, 0.0),
        global_geom,
    ) == Geometry.Cartesian123Point(sqrt(2.0), 0.0, -sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-45.0, 90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, sqrt(2.0), -sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-45.0, -90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, -sqrt(2.0), -sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-45.0, 180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(-sqrt(2.0), 0.0, -sqrt(2.0))
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-45.0, -180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(-sqrt(2.0), 0.0, -sqrt(2.0))

    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(90.0, 0.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, 2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(90.0, 90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, 2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(90.0, -90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, 2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(90.0, 180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, 2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(90.0, -180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, 2.0)

    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-90.0, 0.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, -2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-90.0, 90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, -2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-90.0, -90.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, -2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-90.0, 180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, -2.0)
    @test Geometry.CartesianPoint(
        Geometry.LatLongPoint(-90.0, -180.0),
        global_geom,
    ) == Geometry.Cartesian123Point(0.0, 0.0, -2.0)

end

@testset "Cartesian -> LatLong coordinate conversions" begin
    global_geom = Geometry.SphericalGlobalGeometry(2.0)

    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(2.0, 0.0, 0.0),
        global_geom,
    ) == Geometry.LatLongPoint(0.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, 2.0, 0.0),
        global_geom,
    ) == Geometry.LatLongPoint(0.0, 90.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-2.0, 0.0, 0.0),
        global_geom,
    ) == Geometry.LatLongPoint(0.0, 180.0)
    # check we handle the branch cut smoothly
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-2.0, -0.0, 0.0),
        global_geom,
    ) == Geometry.LatLongPoint(0.0, -180.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, -2.0, 0.0),
        global_geom,
    ) == Geometry.LatLongPoint(0.0, -90.0)

    # should be invariant to radius
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(2.0, 0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(45.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, 2.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(45.0, 90.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-2.0, 0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(45.0, 180.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-2.0, -0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(45.0, -180.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, -2.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(45.0, -90.0)

    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(2.0, 0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-45.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, 2.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-45.0, 90.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-2.0, 0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-45.0, 180.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-2.0, -0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-45.0, -180.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, -2.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-45.0, -90.0)

    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, 0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(90.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, -0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(90.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-0.0, 0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(90.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-0.0, -0.0, 2.0),
        global_geom,
    ) == Geometry.LatLongPoint(90.0, 0.0)

    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, 0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-90.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(0.0, -0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-90.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-0.0, 0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-90.0, 0.0)
    @test Geometry.LatLongPoint(
        Geometry.Cartesian123Point(-0.0, -0.0, -2.0),
        global_geom,
    ) == Geometry.LatLongPoint(-90.0, 0.0)
end

@testset "great circle distance" begin
    global_geom = Geometry.SphericalGlobalGeometry(2.0)

    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) == 0.0
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(22.0, 32.0),
        Geometry.LatLongPoint(22.0, 32.0),
        global_geom,
    ) == 0.0

    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(0.0, 1e-20),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(0.0, -1e-20),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 1e-20),
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, -1e-20),
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(0.0, 10.0),
        global_geom,
    ) ≈ 2 * deg2rad(10.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 30.0),
        Geometry.LatLongPoint(0.0, 40.0),
        global_geom,
    ) ≈ 2 * deg2rad(10.0) rtol = 2eps()

    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(1e-20, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(-1e-20, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 1e-20),
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, -1e-20),
        Geometry.LatLongPoint(0.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(10.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(10.0) rtol = 2eps()

    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 30.0),
        Geometry.LatLongPoint(1e-20, 30.0),
        global_geom,
    ) ≈ 2 * deg2rad(1e-20) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 30.0),
        Geometry.LatLongPoint(10.0, 30.0),
        global_geom,
    ) ≈ 2 * deg2rad(10.0) rtol = 2eps()

    # points near poles
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(89.99, 30.0),
        Geometry.LatLongPoint(90.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(90.0 - 89.99) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(89.99, 90.0),
        Geometry.LatLongPoint(89.99, -90.0),
        global_geom,
    ) ≈ 2 * 2 * deg2rad(90.0 - 89.99) rtol = 2eps()

    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(-89.99, 30.0),
        Geometry.LatLongPoint(-90.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(90.0 - 89.99) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(-89.99, 90.0),
        Geometry.LatLongPoint(-89.99, -90.0),
        global_geom,
    ) ≈ 2 * 2 * deg2rad(90.0 - 89.99) rtol = 2eps()

    # antipodal pairs
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(0.0, 180.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 0.0),
        Geometry.LatLongPoint(0.0, -180.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(0.0, 90.0),
        Geometry.LatLongPoint(0.0, -90.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(20.0, 0.0),
        Geometry.LatLongPoint(-20.0, 180.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(20.0, 0.0),
        Geometry.LatLongPoint(-20.0, -180.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(20.0, 0.0),
        Geometry.LatLongPoint(-20.0, 180.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(20.0, 90.0),
        Geometry.LatLongPoint(-20.0, -90.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(89.99, 90.0),
        Geometry.LatLongPoint(-89.99, -90.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(90.0, 0.0),
        Geometry.LatLongPoint(-90.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()
    @test Geometry.great_circle_distance(
        Geometry.LatLongPoint(-90.0, 0.0),
        Geometry.LatLongPoint(90.0, 0.0),
        global_geom,
    ) ≈ 2 * deg2rad(180.0) rtol = 2eps()

end

@testset "UVW -> Cartesian spherical vector conversions" begin
    global_geom = Geometry.SphericalGlobalGeometry(2.0)

    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 0.0, 1.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 0.0),
    ) == Geometry.Cartesian123Vector(1.0, 0.0, 0.0)

    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(45.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(45.0, 0.0),
    ) == Geometry.Cartesian123Vector(-sqrt(0.5), 0.0, sqrt(0.5))
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(45.0, 0.0),
    ) == Geometry.Cartesian123Vector(sqrt(0.5), 0.0, sqrt(0.5))

    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(-45.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(-45.0, 0.0),
    ) == Geometry.Cartesian123Vector(sqrt(0.5), 0.0, sqrt(0.5))
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(-45.0, 0.0),
    ) == Geometry.Cartesian123Vector(sqrt(0.5), 0.0, -sqrt(0.5))

    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 90.0),
    ) == Geometry.Cartesian123Vector(-1.0, 0.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 90.0),
    ) == Geometry.Cartesian123Vector(0.0, 0.0, 1.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 90.0),
    ) == Geometry.Cartesian123Vector(0.0, 1.0, 0.0)

    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 180.0),
    ) == Geometry.Cartesian123Vector(0.0, -1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 180.0),
    ) == Geometry.Cartesian123Vector(0.0, 0.0, 1.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(0.0, 180.0),
    ) == Geometry.Cartesian123Vector(-1.0, 0.0, 0.0)

    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, -180.0),
    ) == Geometry.Cartesian123Vector(0.0, -1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(0.0, -180.0),
    ) == Geometry.Cartesian123Vector(0.0, 0.0, 1.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(0.0, -180.0),
    ) == Geometry.Cartesian123Vector(-1.0, 0.0, 0.0)

    # north pole
    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(90.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(90.0, 0.0),
    ) == Geometry.Cartesian123Vector(-1.0, 0.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(90.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 0.0, 1.0)

    # south pole
    @test Geometry.CartesianVector(
        Geometry.UVWVector(1.0, 0.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(-90.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 1.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 1.0, 0.0),
        global_geom,
        Geometry.LatLongPoint(-90.0, 0.0),
    ) == Geometry.Cartesian123Vector(1.0, 0.0, 0.0)
    @test Geometry.CartesianVector(
        Geometry.UVWVector(0.0, 0.0, 1.0),
        global_geom,
        Geometry.LatLongPoint(-90.0, 0.0),
    ) == Geometry.Cartesian123Vector(0.0, 0.0, -1.0)
end


using CellListMap
using StaticArrays
import Random

#
# In this test we compute the complete neighbor list of particles, meaning all the pairs
# that are within the cutoff distance. This function is implemented in the
#
# CellListMap.neighborlist
#
# routine. Here we illustrate how is is this implementation, to facilitate customization.
#
function neighborlist(;N=100_000,parallel=true,x=nothing)

    # Number of particles, sides and cutoff
    sides = @SVector [250,250,250]
    cutoff = 10.
    box = Box(sides, cutoff)

    # Particle positions
    Random.seed!(321)
    if x === nothing
        x = [ sides .* rand(SVector{3,Float64}) for i in 1:N ]
    end

    # Initialize auxiliary linked lists
    cl = CellList(x, box, parallel=parallel)

    # Function to be evalulated for each pair: push pair if d<cutoff
    function push_pair!(i, j, d2, pairs, cutoff)
        d = sqrt(d2)
        if d < cutoff
            push!(pairs, (i, j, d))
        end
        return pairs
    end

    # Reduction function
    function reduce_pairs(pairs, pairs_threaded)
        for i in 1:length(pairs_threaded)
            append!(pairs, pairs_threaded[i])
        end
        return pairs
    end

    # Initialize empty output
    pairs = Tuple{Int,Int,Float64}[]

    # Run pairwise computation
    map_pairwise!(
        (x, y, i, j, d2, pairs) -> push_pair!(i, j, d2, pairs, cutoff),
        pairs,box,cl,
        reduce=reduce_pairs,
        parallel=parallel
    )

    return pairs
end


@safetestset hi_g_definition = "g is the direction of gray code" begin
using BijectiveHilbert: brgc, hi_g
for i in 0:1000
    @test brgc(i) ⊻ brgc(i + 1) == 1<<hi_g(i)
    @test brgc(i) == brgc(i + 1) ⊻ 1<<hi_g(i)  # xor. It's how it works.
end
end

@safetestset binary_reflection_of_gc = "gray code is binary reflected" begin
using BijectiveHilbert: brgc
n = 10
for i in 0:(1<<n - 1)
    @test brgc(1<<n - 1 - i) == brgc(i) ⊻ 1<<(n-1)
end
end

@safetestset hi_g_matches_long_form = "hi_g is the same as its definition" begin
using BijectiveHilbert: hi_g, hi_g_orig
for i = 0:100
    @test hi_g(i) == hi_g_orig(i)
end
end

@safetestset hi_g_symmetry = "hi_g symmetry invariant" begin
using BijectiveHilbert: brgc, hi_g, bshow
# n = 3
# for i in 0:(1<<n)
#     println(
#         bshow(brgc(i)), " ",
#         bshow(brgc(i + 1)), " ",
#         bshow(brgc(i) ⊻ brgc(i + 1)), " ",
#         bshow(1<<hi_g(i)), " ",
#         bshow(i)
#     )
# end

# Test for the hi_g function.
n = 5
for i in 0:(1<<n - 2)
    # println(hi_g(i), " ", hi_g(1<<n - 2 - i))
    @test(hi_g(i) == hi_g(1<<n - 2 - i))
end
end

@safetestset efdg_match_table = "directions match figure 5 in hamilton report" begin
using BijectiveHilbert: hi_e, hi_f, hi_d, hi_g
n = 2
trials = [
    #i     e     f  d  g
    [0, 0b00, 0b01, 0, 0],
    [1, 0b00, 0b10, 1, 1],
    [2, 0b00, 0b10, 1, 0],
    [3, 0b11, 0b10, 0, 2]
]
for (i, e, f, d, g) in trials
    @test hi_e(i) == Int(e)
    @test hi_f(i, n) == Int(f)
    @test hi_d(i, n) == d
    if g != 42
        @test hi_g(i) == g
    end
end
end

# The 2d example in the paper has a simpler 1d equivalent,
# which is two segments in a row. d=0 for both. f is 1.
@safetestset efdg_match_1d = "directions match a one-dimensional equivalent" begin
using BijectiveHilbert: hi_e, hi_f, hi_d, hi_g
n = 1
trials = [
    #i  e  f  d  g
    [0, 0, 1, 0, 0],  # the first segment should match n=2
    [1, 0, 1, 0, 1]  # the second segment will be redirected for n=2
]
for (i, e, f, d, g) in trials
    @test hi_e(i) == e
    @test hi_f(i, n) == f
    @test hi_d(i, n) == d
    @test hi_g(i) == g
end
end

# A 3d example.
@safetestset efdg_match_3d = "directions match a three-dimensional equivalent" begin
using BijectiveHilbert: hi_e, hi_f, hi_d, hi_g
n = 3
trials = [
    #i      e      f  d  g
    [0, 0b000, 0b001, 0, 0], # segments 0-2 match n=2
    [1, 0b000, 0b010, 1, 1],
    [2, 0b000, 0b010, 1, 0],
    [3, 0b011, 0b111, 2, 2], # segment 3 gets redirected in z-direction
    [4, 0b011, 0b111, 2, 0], # segment 4 also in z direction
    [5, 0b110, 0b100, 1, 1], # then we repeat the 2d plane in reverse.
    [6, 0b110, 0b100, 1, 0],
    [7, 0b101, 0b100, 0, 3]
]
for (i, e, f, d, g) in trials
    @test hi_e(i) == Int(e)
    @test hi_f(i, n) == Int(f)
    @test hi_d(i, n) == d
    @test hi_g(i) == g
end
end

@safetestset hi_e_matches_float_version = "hi_e matches its floating point version" begin
using BijectiveHilbert: hi_e, hi_e_orig
# Show our implementation for hi_e matches the specification.
for i = 1:100
    @test(hi_e(i) == hi_e_orig(i))
end
end

@safetestset cubes_neighbor_along_g_coord = "cubes are neighbors along gth coordinate" begin
# From page 12, just below the figure. The definition of the directions.
using BijectiveHilbert: hi_e, hi_f, hi_g
for n in 2:5
    # Goes to 2^n-2, not 2^n-1 because there is no neighbor for the last point.
    for i in 0:(1<<n - 2)
        fside = hi_f(i, n) ⊻ (1<<hi_g(i))
        eside = hi_e(i + 1)
        @test fside == eside
        if fside != eside
            @show n, i, fside, eside
            break
        end
    end
end
end

@safetestset hi_d_symmetry_invariant = "hi_d symmetry invariant works corollary 2.7" begin
using BijectiveHilbert: hi_d, bshow
# n = 3
# for i = 0:(1<<n)
#     println(i, " ", bshow(hi_d(i, n)))
# end

# invariant for d on page 12. Corollary 2.7.
# Does not hold true for i=0 and i=1<<n - 1.
n = 5
for i = 0:(1<<n - 1)
    #println(hi_d(i, n), " ", hi_d((1<<n)-1-i, n))
    d = hi_d(i, n)
    dflip = hi_d((1<<n) - 1 - i, n)
    @test(d == dflip)
end
end

@safetestset hi_d_compare_g = "hi_d equals hi_g at 2n-1 lemma 2.8" begin
using BijectiveHilbert: hi_d, hi_g
for n = 1:5
    d = hi_d(1<<n - 1, n)
    g = hi_g(1<<n - 1)
    @test d % n == g % n
end
end

@safetestset hi_d_compares_with_g = "hi_d equals g for mod 2" begin
using BijectiveHilbert: hi_d, hi_g
for n = 2:5
    for i = 0:(1<<n - 1)
        d = hi_d(i, n)
        if i == 0
            @test d == 0
        elseif i % 2 == 0
            g = hi_g(i - 1)
            @test d == (g % n)
        else
            g = hi_g(i)
            @test d == (g % n)
        end
    end
end
end

@safetestset hi_edg_invariant = "hi e, d, g invariant is consistent" begin
using BijectiveHilbert: hi_d, hi_e, hi_g
# invariant for e, d, and g. pg. 11. Eq. 1.
# Contrary to the tech report, this is NOT true for 2^n-1.
n = 5
for i = 0:(1<<n - 2)
    @test(hi_e(i + 1) == hi_e(i) ⊻ (1 << hi_d(i, n)) ⊻ (1 << hi_g(i)))
end
end

@safetestset hi_e_reflection = "hi_e reflects to f" begin
using BijectiveHilbert: hi_d, hi_e, hi_f
n = 5
for i = 0:(1<<n - 1)
    e = hi_e(i)
    d = hi_d(i, n)
    f = hi_f(i, n)
    # Corollary 2.7, pg 12, states that e ⊻ d = f, but that's not true.
    # Page 11, first paragraph, says e(i) \xor f(i) = 2^d(i). That's this.
    @test e ⊻ (1<<d) == f
end
end

@safetestset rotation_invariant_zero = "rotation invariant on hi_e is zero" begin
using BijectiveHilbert: hi_T, hi_e, hi_d
# lemma 2.11, page 15
# Assert T_{e,d}(e) == 0
n = 3
for i = 0:(1<<n - 1)
    # println(hi_T(hi_e(i), hi_d(i, n), hi_e(i), n))
    @test(0 == hi_T(hi_e(i), hi_d(i, n), hi_e(i), n))
end
end

# "rotation invariant on hi_f is power of two" begin
# lemma 2.11, page 15
# Assert T_{e,d}(f) == 2^(n-1)
# Contrary to the tech report, this is NOT true.
# @test(0x1<<(n-1) == hi_T(hi_f(i, n), hi_d(i, n), hi_e(i), n))

# Top of page 16, stated invariant:
# (T_(e,d)(a) rotleft (d + 1)) ⊻ e == a
# Also NOT true

@safetestset ef_invariant = "ef relationship holds" begin
using BijectiveHilbert: hi_e, hi_f
# invariant bottom of pg. 11. Lemma 2.6.
# fails for i=0 and i=2^n
n = 5
for i = 0b1:(0b1<<(n - 1))
    e = hi_e(i)
    ff = hi_f((0b1<<n) - 0b1 - i, n) ⊻ (0b1<<(n-1))
    # println(e, " ", ff)
    @test(e == ff)
    @test typeof(e) == UInt8
    @test typeof(ff) == UInt8
end
end

@safetestset inverse_t_itself = "transform is its own inverse" begin
using BijectiveHilbert: hi_d, hi_e, hi_T, hi_T_inv
# Check that the inverse of T is an inverse.
n = 3
for i = 0x0:(0x1<<n - 0x1)
    for b = 0x0:(0x1<<n - 0x1)
        @test(typeof(i) == UInt8)
        d = UInt8(hi_d(i, n))
        e = UInt8(hi_e(i))
        a = hi_T(b, d, e, n)
        b1 = hi_T_inv(a, d, e, n)
        # println(join(string.(typeof.((i, b, a, b1))), " "))
        # println("b ", bitstring(b), " a ", bitstring(a), " b1 ", bitstring(b1))
        @test(b == b1)
    end
end
end

@safetestset get_ith_bit_trials = "trials show can get ith bit of vectors" begin
using BijectiveHilbert: ith_bit_of_indices, get_location

v = [0b01110100, 0b01101010, 0b01011001]
trials = [
    [0, "00000100"],
    [1, "00000010"],
    [2, "00000001"],
    [3, "00000110"],
    [4, "00000101"],
    [5, "00000011"],
    [6, "00000111"],
    [7, "00000000"]
]
for (idx, t0) in trials
    @test bitstring(ith_bit_of_indices(3, v, idx)) == t0
    # Look, Hamilton's version matches.
    @test bitstring(get_location(v, idx)) == t0
end
end

@safetestset ith_bit_types = "ith_bit ops use all types" begin
using BijectiveHilbert: ith_bit_of_indices, set_indices_bits!
using Random
rng = MersenneTwister(4972433234)
tt = [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128]
for T in tt
    for j in 1:10
    n = 1
    p = zeros(T, 1)
    p[1] = T(rand(rng, 0:typemax(T)))
    q = zeros(T, 1)
    for i in 0:(8*sizeof(T) - 1)
        b = ith_bit_of_indices(n, p, i)
        set_indices_bits!(q, b, n, i)
    end
    @test q[1] == p[1]
    end
end
end

@safetestset hilbert_index_complete4d = "hilbert index is a complete set for 4d" begin
using BijectiveHilbert: SpaceGray
using ..HilbertTestSuite: check_complete_set
b = 3
n = 4
gg = SpaceGray(b, n)
@test check_complete_set(gg, b, n)
end

# @safetestset hilbert_index_paper_tr = "paper and techreport agree" begin
# using BijectiveHilbert: hilbert_index, hilbert_index_paper
# They do NOT agree.
# m = 0x4  # One larger won't fit into a byte and must fail.
# for i in 0:(1<<m - 1)
#     for j in 0:(1<<m - 1)
#         h_tr = hilbert_index(0x2, m, UInt8[i, j])
#         h_p = hilbert_index_paper(0x2, m, UInt8[i, j])
#         @test typeof(h_tr) == typeof(h_p)
#         @test h_p == h_tr
#         if h_p != h_tr
#             break
#         end
#     end
# end
# end

@safetestset libhilbert_matches = "libhilbert output matches" begin
using BijectiveHilbert: SpaceGray, encode_hilbert_zero
n = 4
b = 5
gg = SpaceGray(b, n)
trials = [
    [10, 1, 1, 1, 1],
    [11, 1, 0, 1, 1],
    [12, 1, 0, 1, 0],
    [13, 1, 1, 1, 0],
    [14, 1, 1, 0, 0],
    [15, 1, 0, 0, 0],
    [16, 2, 0, 0, 0],
    [17, 2, 0, 1, 0],
    [18, 2, 0, 1, 1],
    [19, 2, 0, 0, 1]
]
for trial in trials
    v = convert(Vector{UInt8}, trial[2:end])
    result = Int(encode_hilbert_zero(gg, v))
    @test result == trial[1]
end
end

@safetestset correct_bits_set_in_indices = "indices bits match" begin
using BijectiveHilbert: set_indices_bits!
m = 3
i = 4
l = 0b110
p = zeros(Int, 3)
set_indices_bits!(p, l, m, i)
@test p == [0, 1<<i, 1<<i]
end

@safetestset paper_inv = "paper hilbert is own inverse" begin
using BijectiveHilbert: SpaceGray
using ..HilbertTestSuite: check_own_inverse
for n in 2:5
    for b in 2:4
        gg = SpaceGray(b, n)
        @test check_own_inverse(gg, b, n)
    end
end
end

@safetestset brgc_mask_equal = "mask calculation matches paper" begin
using BijectiveHilbert: extract_mask, extract_mask_paper, hi_d
using Random
rng = MersenneTwister(974073242)
for trial in 1:1000
    n = rand(rng, 2:7)
    m = rand(rng, 2:5, n)
    i = rand(rng, 0:(n - 1))
    d = hi_d(i, n)
    a = extract_mask(m, n, d, i)
    b = extract_mask_paper(m, n, d, i)
    @test a == b
end
end

@safetestset libhilbert_hc_matches = "libhilbert compressed output matches" begin
using BijectiveHilbert: encode_hilbert_zero, Compact
n = 4
b = 5
trials = [
    [10, 1, 1, 1, 1],
    [11, 1, 0, 1, 1],
    [12, 1, 0, 1, 0],
    [13, 1, 1, 1, 0],
    [14, 1, 1, 0, 0],
    [15, 1, 0, 0, 0],
    [16, 2, 0, 0, 0],
    [17, 2, 0, 1, 0],
    [18, 2, 0, 1, 1],
    [19, 2, 0, 0, 1]
]
for trial in trials
    v = convert(Vector{UInt8}, trial[2:end])
    ms = [b, b, b, b]
    gg = Compact(ms)
    result = Int(encode_hilbert_zero(gg, v))
    @test result == trial[1]
end
end

@safetestset compact_index_is_its_inverse = "compact index is its inverse" begin
using BijectiveHilbert: Compact
using ..HilbertTestSuite: check_own_inverse
using Random
rng = MersenneTwister(432479874)
for n = [5]
    for i in 1:1
        ms = rand(rng, 2:5, n)
        gg = Compact(ms)
        @test check_own_inverse(gg, ms, n)
    end
end
end

@safetestset compact_index_matches_examples = "compact index matches examples" begin
include(joinpath(dirname(@__FILE__), "check_hamilton.jl"))
using BijectiveHilbert: Compact, decode_hilbert_zero!, index_type, axis_type
fns = ["test/3_2_5_7.txt", "test/2_5_3.txt", "test/3_4_2_3.txt",
       "test/3_5_2_3.txt", "test/4_5_5_5.txt"]
for fn in fns
    if !isfile(fn)
        fn = basename(fn)
    end
    if isfile(fn)
        h, X, ms = hamilton_example(fn)
        n = size(X, 1)
        gg = Compact(ms)
        p = zeros(axis_type(gg), n)
        H = index_type(gg)
        for i in eachindex(h)
            decode_hilbert_zero!(gg, p, H(h[i]))
            @test convert(Vector{UInt8}, p) == X[:, i]
        end
    end
end
end

@safetestset compact_index_inv_matches_examples = "inverse compact index matches examples" begin
include(joinpath(dirname(@__FILE__), "check_hamilton.jl"))
using BijectiveHilbert: Compact, encode_hilbert_zero, axis_type, index_type
fns = ["test/3_2_5_7.txt", "test/2_5_3.txt", "test/3_4_2_3.txt",
       "test/3_5_2_3.txt", "test/4_5_5_5.txt"]
for fn in fns
    if !isfile(fn)
        fn = basename(fn)
    end
    if isfile(fn)
        h, X, ms = hamilton_example(fn)
        n = size(X, 1)
        gg = Compact(ms)
        H = index_type(gg)
        for i in eachindex(h)
            XX = convert(Vector{axis_type(gg)}, X[:, i])
            hc = encode_hilbert_zero(gg, XX)
            @test hc == H(h[i])
            if hc != H(h[i])
                @show n, ms, i, h[i], hc, X[:, i]
                break
            end
        end
    end
end
end

@safetestset compact_for_signed = "compact hilbert for signed integers" begin
using BijectiveHilbert
using Random
rng = MersenneTwister(9742439)
for i in 1:1000
    n = rand(rng, 3:5)
    ms = rand(rng, 2:7, n)
    gg = Compact(Int, ms)
    gg2 = Compact(ms)
    x = zeros(Int, n)
    A = axis_type(gg2)
    y = zeros(A, n)
    for i in 1:n
        x[i] = rand(rng, 0:(1<<ms[i] - 1))
        y[i] = A(x[i])
    end
    a = encode_hilbert_zero(gg, x)
    b = Int(encode_hilbert_zero(gg2, y))
    @test a == b
end
end

@safetestset hamilton_zero_based = "hamilton works for zero based" begin
using BijectiveHilbert
gg = Compact(UInt, [4, 3, 3])
X = [9, 4, 5]
h = encode_hilbert(gg, X)
Y = zeros(Int, 3)
decode_hilbert!(gg, Y, h)
@test X == Y
end

@safetestset spacegray_type_interactions = "SpaceGray type interactions" begin
    using BijectiveHilbert
    using UnitTestDesign
    using Random
    rng = Random.MersenneTwister(9790323)
    for retrial in 1:5
        AxisTypes = shuffle(rng, [Int8, Int, UInt, Int128, UInt8, UInt128])
        IndexTypes = shuffle(rng, [Union{}, Int8, UInt8, Int, UInt, Int128, UInt128])
        Count= shuffle(rng, [0, 1])
        Dims = shuffle(rng, [2, 3, 4])
        Bits = shuffle(rng, [2, 3, 4, 5])
        test_set = all_pairs(
            AxisTypes, IndexTypes, Count, Dims, Bits;
        )
        for (A, I, C, D, B) in test_set
            if I == Union{}
                gg = SpaceGray(B, D)
                I = index_type(gg)
            else
                gg = SpaceGray(I, B, D)
            end
            if B * D > log2(typemax(I))
                continue
            end
            last = (one(I) << (B * D)) - one(I) + I(C)
            mid = one(I) << (B * D - 1)
            few = 5
            X = zeros(A, D)
            hlarr = vcat(C:min(mid, few), max(mid + 1, last - few):last)
            for hl in hlarr
                hli = I(hl)
                if C == 0
                    decode_hilbert_zero!(gg, X, hli)
                    hl2 = encode_hilbert_zero(gg, X)
                    if hl2 != hli
                        @show A, I, C, D, B, X
                        @test hl2 == hli
                    end
                    @test typeof(hl2) == typeof(hli)
                else
                    decode_hilbert!(gg, X, hli)
                    hl3 = encode_hilbert(gg, X)
                    @test hl3 == hli
                    @test typeof(hl3) == typeof(hli)
                end
            end
        end
    end
end

@safetestset spacegray_random = "spacegray random" begin
using BijectiveHilbert
aas = [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128]
tts = [UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128]
ggbase = SpaceGray(UInt32, 4, 4)
hli = UInt32(2)
Xbase = zeros(UInt8, 4)
decode_hilbert_zero!(ggbase, Xbase, hli)

for idx in Base.IteratorsMD.CartesianIndices((length(aas), length(tts)))
    i, j = Tuple(idx)
    A = aas[i]
    T = tts[j]
    gg = SpaceGray(T, 4, 4)
    X = zeros(A, 4)
    hli = T(2)
    decode_hilbert_zero!(gg, X, hli)
    @test X == Xbase
    hl2 = encode_hilbert_zero(gg, X)
    @test hl2 == hli
end
end

@safetestset compact_type_interactions = "Compact type interactions" begin
    using BijectiveHilbert
    using UnitTestDesign
    using Random
    rng = Random.MersenneTwister(9790323)
    for retrial in 1:5
        AxisTypes = shuffle(rng, [Int8, Int, UInt, Int128, UInt8, UInt128])
        IndexTypes = shuffle(rng, [Union{}, Int8, UInt8, Int, UInt, Int128, UInt128])
        Count= shuffle(rng, [0, 1])
        Dims = shuffle(rng, [2, 3, 4])
        Bits = shuffle(rng, [2, 3, 4, 5])
        test_set = all_pairs(
            AxisTypes, IndexTypes, Count, Dims, Bits;
        )
        for (A, I, C, D, B) in test_set
            if I == Union{}
                gg = Compact(fill(B, D))
                I = index_type(gg)
            else
                gg = Compact(I, fill(B, D))
            end
            if B * D > log2(typemax(I))
                continue
            end
            last = (one(I) << (B * D)) - one(I) + I(C)
            mid = one(I) << (B * D - 1)
            few = 5
            X = zeros(A, D)
            hlarr = vcat(C:min(mid, few), max(mid + 1, last - few):last)
            for hl in hlarr
                hli = I(hl)
                if C == 0
                    decode_hilbert_zero!(gg, X, hli)
                    hl2 = encode_hilbert_zero(gg, X)
                    if hl2 != hli
                        @show A, I, C, D, B, X
                        @test hl2 == hli
                    end
                    @test typeof(hl2) == typeof(hli)
                else
                    decode_hilbert!(gg, X, hli)
                    hl3 = encode_hilbert(gg, X)
                    @test hl3 == hli
                    @test typeof(hl3) == typeof(hli)
                end
            end
        end
    end
end


using Test

function find_smallest(arr)
    smallest = arr[1]
    smallest_index = 1
    for i = 1:length(arr)
        if arr[i] < smallest
            smallest = arr[i]
            smallest_index = i
        end
    end
    return smallest_index
end

function selection_sort(arr)
    new_arr = Array{Int64}(undef,0)
    for i = 1:length(arr)
        smallest = find_smallest(arr)
        append!(new_arr,arr[smallest])
        deleteat!(arr,smallest)
    end
    return new_arr
end

arr = [5,3,6,2,10]
@test selection_sort(arr) == [2,3,5,6,10]


module SplineInterpolations

using ..BSplines
using ..Collocation
using ..Splines

using BandedMatrices
using LinearAlgebra

import ..BSplines:
    order, knots, basis

import ..Splines:
    coefficients, integral

import Interpolations:
    interpolate, interpolate!

export
    SplineInterpolation, spline, interpolate, interpolate!

"""
    SplineInterpolation

Spline interpolation.

This is the type returned by [`interpolate`](@ref).

A `SplineInterpolation` `I` can be evaluated at any point `x` using the `I(x)`
syntax.

It can also be updated with new data on the same data points using
[`interpolate!`](@ref).

---

    SplineInterpolation(undef, B::AbstractBSplineBasis, x::AbstractVector, [T = eltype(x)])

Initialise a `SplineInterpolation` from B-spline basis and a set of
interpolation (or collocation) points `x`.

Note that the length of `x` must be equal to the number of B-splines.

Use [`interpolate!`](@ref) to actually interpolate data known on the `x`
locations.
"""
struct SplineInterpolation{
        S <: Spline,
        F <: Factorization,
        Points <: AbstractVector,
    } <: SplineWrapper{S}

    spline :: S
    C :: F  # factorisation of collocation matrix
    x :: Points

    function SplineInterpolation(
            B::AbstractBSplineBasis, C::Factorization, x::AbstractVector,
        )
        N = length(B)
        size(C) == (N, N) ||
            throw(DimensionMismatch("collocation matrix has wrong dimensions"))
        length(x) == N ||
            throw(DimensionMismatch("wrong number of collocation points"))
        T = eltype(C)
        s = Spline(undef, B, T)  # uninitialised spline
        new{typeof(s), typeof(C), typeof(x)}(s, C, x)
    end
end

# Construct SplineInterpolation from basis and collocation points.
function SplineInterpolation(
        ::UndefInitializer, B, x::AbstractVector, ::Type{T},
    ) where {T}
    # Here we construct the collocation matrix and its LU factorisation.
    N = length(B)
    if length(x) != N
        throw(DimensionMismatch(
            "incompatible lengths of B-spline basis and collocation points"))
    end
    C = collocation_matrix(B, x, CollocationMatrix{T})
    SplineInterpolation(B, lu!(C), x)
end

interpolation_points(S::SplineInterpolation) = S.x

SplineInterpolation(init, B, x::AbstractVector) =
    SplineInterpolation(init, B, x, eltype(x))

function Base.show(io::IO, I::SplineInterpolation)
    println(io, nameof(typeof(I)), " containing the ", spline(I))
    let io = IOContext(io, :compact => true, :limit => true)
        println(io, " interpolation points: ", interpolation_points(I))
    end
    nothing
end

"""
    interpolate!(I::SplineInterpolation, y::AbstractVector)

Update spline interpolation with new data.

This function allows to reuse a [`SplineInterpolation`](@ref) returned by a
previous call to [`interpolate`](@ref), using new data on the same locations
`x`.

See [`interpolate`](@ref) for details.
"""
function interpolate!(I::SplineInterpolation, y::AbstractVector)
    s = spline(I)
    if length(y) != length(s)
        throw(DimensionMismatch("input data has incorrect length"))
    end
    ldiv!(coefficients(s), I.C, y)  # determine B-spline coefficients
    I
end

"""
    interpolate(x, y, BSplineOrder(k))

Interpolate values `y` at locations `x` using B-splines of order `k`.

Grid points `x` must be real-valued and are assumed to be in increasing order.

Returns a [`SplineInterpolation`](@ref) which can be evaluated at any intermediate
point.

See also [`interpolate!`](@ref).

# Examples

```jldoctest
julia> xs = -1:0.1:1;

julia> ys = cospi.(xs);

julia> itp = interpolate(xs, ys, BSplineOrder(4))
SplineInterpolation containing the 21-element Spline{Float64}:
 order: 4
 knots: [-1.0, -1.0, -1.0, -1.0, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3  …  0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, 1.0, 1.0, 1.0]
 coefficients: [-1.0, -1.00111, -0.8975, -0.597515, -0.314147, 1.3265e-6, 0.314142, 0.597534, 0.822435, 0.96683  …  0.96683, 0.822435, 0.597534, 0.314142, 1.3265e-6, -0.314147, -0.597515, -0.8975, -1.00111, -1.0]
 interpolation points: -1.0:0.1:1.0

julia> itp(-1)
-1.0

julia> itp(0)
1.0

julia> itp(0.42)
0.2486897676885842
```
"""
function interpolate(x::AbstractVector, y::AbstractVector, k::BSplineOrder)
    t = make_knots(x, order(k))
    B = BSplineBasis(k, t; augment = Val(false))  # it's already augmented!
    T = float(eltype(y))
    itp = SplineInterpolation(undef, B, x, T)
    interpolate!(itp, y)
end

# Define B-spline knots from collocation points and B-spline order.
# Note that the choice is not unique.
function make_knots(x::AbstractVector{Tx}, k) where {Tx <: Real}
    Base.require_one_based_indexing(x)
    T = float(Tx)  # just in case Tx is Integer...
    N = length(x)
    t = Array{T}(undef, N + k)  # knots

    # First and last knots have multiplicity `k`.
    t[1:k] .= x[1]
    t[(N + 1):(N + k)] .= x[end]

    # Use initial guess for optimal set of knot locations (de Boor 2001, p. 193).
    # One could get the actual optimal using Newton iterations...
    kinv = one(T) / (k - 1)
    @inbounds for i = 1:(N - k)
        ti = zero(T)
        for j = 1:(k - 1)
            ti += x[i + j]
        end
        t[k + i] = ti * kinv
    end

    t
end

end


"""
    index(dgt::AbstractVector{T}; base::Integer=2) where T <: Integer

Convert a digits to the integer index using the relation:

`number = ∑ᵢ bits[i] * base^(L-i) + 1`

The summaton is evaluate using the efficieint polynomial evaluation method.
"""
@inline function index(dgt::AbstractVector{T}; base::Integer=2) where T <: Integer
    N = zero(T)
    for i = 1:length(dgt)
        N *= base
        N += dgt[i]
    end
    N + one(T)
end

"""
    index(dgt::AbstractVector{T}, sites::AbstractVector{<:Integer}; base::Integer=2) where T <: Integer

Convert a sub-digits (subarray of `dgt`) to the integer index.
"""
@inline function index(dgt::AbstractVector{T}, sites::AbstractVector{<:Integer}; base::Integer=2) where T <: Integer
    N = zero(T)
    for i in sites
        N *= base
        N += dgt[i]
    end
    N + one(T)
end

"""
    change!(dgt::AbstractVector{T}, ind::Integer; base::Integer=2) where T <: Integer

Change the digits to that with the target index.
This method is the inverse of `index`.
"""
@inline function change!(dgt::AbstractVector{T}, ind::Integer; base::Integer=2) where T <: Integer
    N = convert(T, ind) - one(T)
    for i = length(dgt):-1:1
        N, dgt[i] = divrem(N, base)
    end
end

"""
    change!(dgt::AbstractVector{T}, sites::AbstractVector{<:Integer}, ind::Integer; base::Integer=2) where T <: Integer

Change the sub-digits to that with the target index.
This method is the inverse of `index`.
"""
@inline function change!(dgt::AbstractVector{T}, sites::AbstractVector{<:Integer}, ind::Integer; base::Integer=2) where T <: Integer
    N = convert(T, ind) - one(T)
    for i = length(sites):-1:1
        N, dgt[sites[i]] = divrem(N, base)
    end
end

"""
    binary_search(list::AbstractVector{<:Integer}, i<:Integer)

Return the position of i in a sorted list using binary search algorithm.
"""
function binary_search(list::AbstractVector{<:Integer}, i::Integer)
    l::Int = 1
    r::Int = length(list)
    c::Int = (l + r) ÷ 2
    while true
        t = list[c]
        (i < t) ? (r = c - 1) : (i > t) ? (l = c + 1) : break
        (l > r) ? (c = 0; break) : (c = (l + r) ÷ 2)
    end
    c
end

"""
    selectindex(f, L, rg; base=2, alloc=1000)

Select the legal indices for a basis.

Inputs:
-------
- `f`    : Function for digits that tells whether a digits is valid.
- `L`    : Length of the system.
- `rg`   : Range of interation.
- `base` : (Optional) Base, `base=2` by default.
- `alloc`: (Optional) Pre-allocation memory for the list of indices, `alloc=1000` by default.

Outputs:
--------
- `I`: List of indices in a basis.
"""
function selectindex(
    f,
    L::Integer,
    rg::UnitRange;
    base::Integer=2,
    alloc::Integer=1000
)
    dgt = zeros(Int64, L)
    I = Int[]
    sizehint!(I, alloc)
    for i in rg
        change!(dgt, i, base=base)
        f(dgt) && append!(I, i)
    end
    I
end

"""
    selectindexnorm(f, L, rg; base=2, alloc=1000)

Select the legal indices and corresponding norm for a basis.

Inputs:
-------
- `f`    : Function for digits that tells whether a digits is valid as well as its normalization.
- `L`    : Length of the system.
- `rg`   : Range of interation.
- `base` : (Optional) Base, `base=2` by default.
- `alloc`: (Optional) Pre-allocation memory for the list of indices, `alloc=1000` by default.

Outputs:
--------
- `I`: List of indices in a basis.
- `R`: List of normalization for each states.
"""
function selectindexnorm(
    f,
    L::Integer,
    rg::UnitRange;
    base::Integer=2,
    alloc::Integer=1000
)
    dgt = zeros(Int64, L)
    I, R = Int[], Float64[]
    sizehint!(I, alloc)
    sizehint!(R, alloc)
    for i in rg
        change!(dgt, i, base=base)
        Q, N = f(dgt, i)
        if Q
            append!(I, i)
            append!(R, N)
        end
    end
    I, R
end

"""
    dividerange(maxnum::Integer, nthreads::Integer)

Divide the interation range equally according to the number of threads.

Inputs:
-------
- `maxnum`  : Maximum number of range.
- `nthreads`: Number of threads.

Outputs:
--------
- `list`: List of ranges.
"""
function dividerange(maxnum::Integer, nthreads::Integer)
    list = Vector{UnitRange{Int64}}(undef, nthreads)
    eachthreads, left = divrem(maxnum, nthreads)
    start = 1
    for i = 1:left
        stop  = start + eachthreads
        list[i] = start:stop
        start = stop+1
    end
    for i = left+1:nthreads-1
        stop  = start + eachthreads - 1
        list[i] = start:stop
        start = stop+1
    end
    list[nthreads] = start:maxnum
    list
end

"""
    selectindex_threaded(f, L, rg; base=2, alloc=1000)

Select the legal indices with multi-threads.

Inputs:
-------
- `f`    : Function for digits that tells whether a digits is valid as well as its normalization.
- `L`    : Length of the system.
- `base` : (Optional) Base, `base=2` by default.
- `alloc`: (Optional) Pre-allocation memory for the list of indices, `alloc=1000` by default.

Outputs:
--------
- `I`: List of indices in a basis.
"""
function selectindex_threaded(
    f,
    L::Integer;
    base::Integer=2,
    alloc::Integer=1000
)
    nt = Threads.nthreads()
    ni = dividerange(base^L, nt)
    nI = Vector{Vector{Int}}(undef, nt)
    Threads.@threads for ti in 1:nt
        nI[ti] = selectindex(f, L, ni[ti], base=base, alloc=alloc)
    end
    I = vcat(nI...)
    I
end

"""
    selectindexnorm_threaded(f, L, rg; base=2, alloc=1000)

Select the legal indices and corresponding norm for a basis with multi-threads.

Inputs:
-------
- `f`    : Function for digits that tells whether a digits is valid as well as its normalization.
- `L`    : Length of the system.
- `base` : (Optional) Base, `base=2` by default.
- `alloc`: (Optional) Pre-allocation memory for the list of indices, `alloc=1000` by default.

Outputs:
--------
- `I`: List of indices in a basis.
- `R`: List of normalization for each states.
"""
function selectindexnorm_threaded(
    f,
    L::Integer;
    base::Integer=2,
    alloc::Integer=1000
)
    nt = Threads.nthreads()
    ni = dividerange(base^L, nt)
    nI = Vector{Vector{Int}}(undef, nt)
    nR = Vector{Vector{Float64}}(undef, nt)
    Threads.@threads for ti in 1:nt
        nI[ti], nR[ti] = selectindexnorm(f, L, ni[ti], base=base, alloc=alloc)
    end
    I, R = vcat(nI...), vcat(nR...)
    I, R
end

# Level statistics
export gapratio, meangapratio
function gapratio(E::AbstractVector{<:Real})
    dE = diff(E)
    r = zeros(length(dE)-1)
    for i = 1:length(r)
        if dE[i] < dE[i+1]
            r[i] = dE[i]/dE[i+1]
        elseif dE[i] > dE[i+1]
            r[i] = dE[i+1]/dE[i]
        else
            r[i] = 1.0
        end
    end
    r
end

meangapratio(E::AbstractVector{<:Real}) = sum(gapratio(E)) / (length(E) - 2)


# SPDX-License-Identifier: X11
# 2020-09-01
# Making Triangle (200pt)

function main()
  N = parse(Int, readline())
  L = parse.(Int, split(readline()))
  sort!(L)

  c = 0
  for i ∈ 1:N - 2
    for j ∈ i + 1:N - 1
      for k ∈ j + 1:N
        L[i] != L[j] != L[k] && L[i] + L[j] > L[k] && (c += 1)
      end
    end
  end

  println(c)
end

main()


# generate data
if Pkg.installed("JLD") == nothing
    Pkg.add("JLD")
end

using JLD

function gendata(n)

	# covariance matrix
	covar = (arg = randn(n,n); arg*arg'/1000)
    # expected return
    u = rand(Float64,n)/100

#return(covar)

save("randdata.jld","covar", covar, "u",u)

end


using NKLandscapes
using Base.Test

const BOUNDARIES = ["Nearest-neighbor", "Random"]
const LC = 100 # Landscape count
const PAIRS = [(8, 0), (8, 2), (8, 4), (8, 8),
(16, 0), (16, 2), (16, 4), (16, 8), (16, 16),
(24, 0), (24, 2), (24, 4), (24, 8), (24, 16), (24, 24),
(48, 0), (48, 2), (48, 4), (48, 8), (48, 16), (48, 24), (48, 48),
(96, 0), (96, 2), (96, 4), (96, 8), (96, 16), (96, 24), (96, 48), (96, 96)]

# Mean fitness of local optima (nearest-neighbor interactions) & Mean walk
# lengths to local optima (nearest-neighbor interactions) Kauffman, p. 55-56

# Mean fitness of local optima (random interactions) & Mean walk lengths to
# local optima (random interactions) Kauffman, p. 57

println("Length of Paths to Optima and Fitness of Optima")
for b = [1, 2]
  println(BOUNDARIES[b], " Interactions")
  println("N\tK\tμ len\tσ len\tμ fit\tσ fit")
  for (n, k) = PAIRS
    if n == k
      k = k - 1
      # Periodic boundary requires even K
      if b == 1
        k = k - 1
      end
    end
    lengths = zeros(LC)
    fitnesses = zeros(LC)
    for i = 1:LC
      l = NKLandscape(n, k, near=b == 1)
      g = rand(Genotype, l)
      w = random_adaptive_walk(g)
      lengths[i] = w.length
      fitnesses[i] = fitness(w.history_list[end])
    end
    @printf("%d\t%d\t%.2f\t%.2f\t%.3f\t%.3f\n", n, k, mean(lengths), std(lengths), mean(fitnesses), std(fitnesses))
  end
end

# Number of optima. Kauffman, p. 60.

MAX_FOUND = 10_000
MAX_FAILS = 100

println("Number of Optima")
for b = [1, 2]
  println(BOUNDARIES[b], " Interactions")
  println("N\tK\tμ\tσ")
  for (n, k) = PAIRS[1:9]  # Only runs tests for N = 8 and N = 16.  Modify to run more tests (time consuming).
    if n == k
      k = k - 1
      if b == 1
        k = k - 1
      end
    end
    counts = zeros(LC)
    for i = 1:LC
      since_last = 0
      found_opts = Set{Genotype}()
      l = NKLandscape(n, k, near=b == 1)
      while length(found_opts) <= MAX_FOUND && since_last <= MAX_FAILS
        g = rand(Genotype, l)
        w = random_adaptive_walk(g)
        opt = w.history_list[end]
        if !(opt in found_opts)
          push!(found_opts, opt)
          since_last = 0
        else
          since_last += 1
        end
      end
      counts[i] = length(found_opts)
    end
    @printf("%d\t%d\t%.2f\t%.2f\n", n, k, mean(counts), std(counts))
  end
end


const _UniformEncodingTables = Dict()
"""
    uniform_encoding_table(T, X::AbstractUnitRange, degree::Int)

Get the encoding table of `X` using uniform degree filter.

A uniform degree filter is defined as `f(x) = U(x) > degree ? P+1 : x`, where `P` is number
of bits of `x`, and `U(x)` is the circular bit transition of `x` with bit representation of
datatype `T`.

For example, `0b0000001` has ``2`` bit transitions thus is unchanged, while `0b00000101` has
``4`` bit transitions. The transition is count in circular sense, i.e., the highest and
lowest bits are also compared.

```jldoctest
julia> using LocalBinaryPatterns: uniform_encoding_table

julia> X = uniform_encoding_table(UInt8, 0:255, 2);

julia> X[0b0000001]
0x01

julia> X[0b0000101] # miscellaneous
0x09
```

This function is used to distinguish local binary patterns from texture patterns and
miscellaneous patterns. See [1] for more information.

!!! info "Runtime caches"
    For better performance, this function uses a runtime cache to store the lookup and
    shared among all callers. The result is expected to be used in read-only mode.

# References

- [1] T. Ojala, M. Pietikäinen, and T. Mäenpää, “A Generalized Local Binary Pattern Operator for Multiresolution Gray Scale and Rotation Invariant Texture Classification,” in _Advances in Pattern Recognition — ICAPR 2001, vol. 2013, S. Singh, N. Murshed, and W. Kropatsch, Eds. Berlin, Heidelberg: Springer Berlin Heidelberg_, 2001, pp. 399–408. doi: 10.1007/3-540-44732-6_41.
"""
function uniform_encoding_table(::Type{T}, X::AbstractUnitRange, degree::Int) where T
    d = _UniformEncodingTables
    k = (T, degree, minimum(X), maximum(X))
    haskey(d, k) && return d[k]
    lookup = d[k] = _uniform_encoding_table(T, X, degree)
    return lookup
end
uniform_encoding_table(::Type{T}, nbits::Int, degree::Int) where T = uniform_encoding_table(T, 0:2^nbits-1, degree)

function _uniform_encoding_table(::Type{T}, X::AbstractUnitRange, degree::Int) where T<:Unsigned
    if !((maximum(X) <= typemax(T)) && (minimum(X) >= typemin(T)))
        throw(ArgumentError("range $(first(X)):$(last(X)) can't be uniquely represented by type $T."))
    end

    function _count_bit_transitions(x)
        # Eq. (10) in Ojala 2002
        count_ones(x << 7 & typemax(typeof(x))) + count_ones(x ⊻ (x >> 1))
    end
    # Eq. (9) for Ojala 2002
    lookup = map(X) do x
        n = _count_bit_transitions(T(x))
        T(ifelse(n > degree, 8sizeof(T)+1, x))
    end
    OffsetVector(lookup, X)
end


using Test

using SIMD
using ChipSort

@testset "Sorting networks" begin

function test_sorting_networks(T, N)
    vv = rand(T, N)
    srtref = sort(vv)
    srthat = sort_net(vv...)

    @test all(srthat[2:end] .>= srthat[1:end-1])
    @test all(srthat .== srtref)
end

@testset for T in [Int8, Int16, Int32, Int64, Float32, Float64]
    for L in 2 .^ (2:5)
        test_sorting_networks(T, L)
    end
end

function test_sort_simd_vec(T, N)
    va = ntuple(n->Vec(tuple(rand(T, N)...)),N)
    srthat = sort_net(va...)

    xx = [va[j][k] for j in 1:N, k in 1:N]
    srtref = sort(xx; dims=1)
    srthat_arr = [srthat[j][k] for j in 1:N, k in 1:N]

    @test srthat_arr == srtref
end

@testset for T in [Int8, Int16, Int32, Int64, Float32, Float64]
    Niterations = 16
    for L in 2 .^ (2:5)
        for it in 1:Niterations
            test_sort_simd_vec(T, L)
        end
    end
end

@testset for N in [16, 32]
    for it in 1:2^16
        test_sort_simd_vec(UInt32, N)
    end
end

function test_sort_heaviside(N, Na)
    va = ntuple(n->if (Na&(1<<(n-1))) > 0 1 else 0 end, N)
    srthat = sort_net(va...)
    srtref = sort([va...])
    @test srthat == tuple(srtref...)
end

@testset for N in 2 .^ (2:4)
    for Na in 0:(2^N-1)
            test_sort_heaviside(N, Na)
    end
end

end