From b767593fd5fc7dd48b37472970b918dfd0287bc2 Mon Sep 17 00:00:00 2001 From: Christian Peel <6899870+christianpeel@users.noreply.github.com> Date: Sun, 31 Mar 2019 18:26:22 -0700 Subject: [PATCH] Added brun, cvp, svp, subsetsum, integerFeasibility (#10) * Added "cvp" for closest vector problem * Added "svp" for shortest vector problem * Added example lll applications: "subsetsum" function which solves subset sum problem , "integerFeasibility" for integer programming feasibility. * As a learning tool, added "gauss" decomposition * updated README, docs for all functions --- README.md | 146 ++++++++++++++++-------------- benchmark/lrtest.jl | 75 ++++++++-------- benchmark/perfVsDataType.png | Bin 0 -> 25118 bytes benchmark/perfVsNfloat32.png | Bin 29875 -> 0 bytes benchmark/perfVsNfloat64.png | Bin 0 -> 23286 bytes benchmark/perftest.jl | 11 +-- src/LLLplus.jl | 16 +++- src/applications.jl | 148 +++++++++++++++++++++++++++++++ src/cvp.jl | 167 +++++++++++++++++++++++++++++++++++ src/hard_sphere.jl | 2 +- src/lll.jl | 148 ++++++++++++++++++++++--------- src/seysen.jl | 105 ++++++++++------------ test/runtests.jl | 34 +++---- 13 files changed, 626 insertions(+), 226 deletions(-) create mode 100644 benchmark/perfVsDataType.png delete mode 100644 benchmark/perfVsNfloat32.png create mode 100644 benchmark/perfVsNfloat64.png create mode 100644 src/applications.jl create mode 100644 src/cvp.jl diff --git a/README.md b/README.md index b7f6457..d2506b6 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,41 @@ -# LLLplus +# LLLplus.jl [![Build Status](https://travis-ci.org/christianpeel/LLLplus.jl.svg?branch=master)](https://travis-ci.org/christianpeel/LLLplus.jl) + + +Lattice reduction and related lattice tools are used in +cryptography, digital communication, and integer programming. LLLplus +includes Lenstra-Lenstra-Lovacsz (LLL) lattice reduction, Seysen +lattice reduction, VBLAST matrix decomposition, and a closest vector +problem (CVP) solver. + +[LLL](https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm) +[1] lattice reduction is a powerful tool that is widely used in +cryptanalysis, in cryptographic system design, in digital +communications, and to solve other integer problems. LLL reduction is +often used as an approximate solution to the [shortest vector problem](https://en.wikipedia.org/wiki/Lattice_problem#Shortest_vector_problem_.28SVP.29) -(SVP). -Seysen [2] introduced a lattice reduction which focuses on global -optimization rather than local optimization as in LLL. -The -[closest vector problem](https://en.wikipedia.org/wiki/Lattice_problem#Closest_vector_problem_.28CVP.29) -(CVP) is related to the SVP; in the context of multi-antenna decoding -it is referred to as -[sphere-decoding](https://en.wikipedia.org/wiki/Lattice_problem#Sphere_decoding). - -Finally, we include code to do a -[V-BLAST](https://en.wikipedia.org/wiki/Bell_Laboratories_Layered_Space-Time) -(Vertical-Bell Laboratories Layered Space-Time) matrix -decomposition. This decomposition is used in a detection algorithm [3] -for decoding spatially-multiplexed streams of data on multiple -antennas or other multi-terminal systems. V-BLAST is not as widely -used outside of the wireless communication community as lattice -reduction and CVP techniques such as the sphere decoder. In the Julia -package [MUMIMO.jl](https://github.com/christianpeel/MUMIMO.jl) we use -the LLL, sphere-decoder, and V-BLAST functions in this package to -decode multi-user, multi-antenna signals. +(SVP). We also include Gauss/Lagrange 2-dimensional lattice reduction +as a learning tool, and Seysen [2] lattice reduction which +simultaneously reduces a basis `B` and its dual `inv(B)'`. The LLL and +Seysen algorithms are based on [3]. The +[CVP](https://en.wikipedia.org/wiki/Lattice_problem#Closest_vector_problem_.28CVP.29) +solver is based on [4] and can handle lattices and bounded integer +constellations. + + + + +We also include code to do a +[Vertical-Bell Laboratories Layered Space-Time](https://en.wikipedia.org/wiki/Bell_Laboratories_Layered_Space-Time) +(V-BLAST) [5] matrix decomposition which is used in digital +communications. In digital communications (see +[MUMIMO.jl](https://github.com/christianpeel/MUMIMO.jl)) the LLL, +Seysen, V-BLAST, and CVP functions are used to solve (exactly or +approximately) CVP problems in encoding and decoding multi-terminal +signals. ### Examples @@ -52,19 +50,19 @@ using LLLplus N = 1000; H = randn(N,N) + im*randn(N,N); println("Testing LLL on $(N)x$(N) complex matrix...") -@time (B,T,Q,R) = lll(H); +@time B,T,Q,R = lll(H); M = 200; println("Testing VBLAST on $(M)x$(M) chunk of same matrix...") -@time (W,P,B) = vblast(H[1:M,1:M]); +@time W,P,B = vblast(H[1:M,1:M]); # Time LLL, Seysen decompositions of a 100x100 Int64 matrix with # rand entries distributed uniformly between -100:100 N = 100; H = rand(-100:100,N,N); println("Testing LLL on $(N)x$(N) real matrix...") -@time (B,T,Q,R) = lll(H); +@time B,T,Q,R = lll(H); println("Testing Seysen on same $(N)x$(N) matrix...") -@time (B,T) = seysen(H); +@time B,T = seysen(H); ``` ### Execution Time results @@ -76,31 +74,42 @@ In the tests we time execution of the lattice-reduction functions, average the results over multiple random matrices, and show results as a function of the size of the matrix and of the data type. -We first show how the time varies with matrix size (1,2,4,...64); the +We first show how the time varies with matrix size (1,2,4,...256); the vertical axis shows execution time on a logarithmic scale; the x-axis is also logarithmic. The generally linear nature of the LLL curve supports the polynomial-time nature of the algorithm. Each data point -is the average of execution time of 10 runs of a lattice-reduction +is the average of execution time of 40 runs of a lattice-reduction technique, where the matrices used were generated using 'randn' to emulate unit-variance Gaussian-distributed values. -![Time vs matrix size](benchmark/perfVsNfloat32.png) + +![Time vs matrix size](benchmark/perfVsNfloat64.png) Though the focus of the package is on floating-point, all the modules can handle a variety of data types. In the next figure we show execution time for several datatypes (Int32, Int64, -Int128, Float64, BitInt, and BigFloat) which are used to -generate 100 4x4 matrices, over which execution time for the lattice +Int128, Float32, Float64, DoublFloat, BitInt, and BigFloat) which are used to +generate 40 128x128 matrices, over which execution time for the lattice reduction techniques is averaged. The vertical axis is a logarithmic representation of execution time as in the previous -figure. ![Time vs data type](benchmark/perfVsDataTypeN16.png) +figure. + +![Time vs data type](benchmark/perfVsDataType.png) + +The algorithm pseudocode in the monograph [6] and the survey paper [3] +were very helpful in writing the lattice reduction tools in LLLplus +and are a good resource for further study. + +LLLplus does not pretend to be a tool for number-theory work. For +that, see the [Nemo.jl](https://github.com/wbhart/Nemo.jl) package +which uses the [FLINT](http://flintlib.org/) C library to do LLL +reduction on Nemo-specific data types. ### Future Possible improvements include: -* Change from "LLLplus" to "LLLtoy" or some such to emphasize - the nature of this package. * Add Block-Korkin-Zolotarev lattice redution, with improvements - as in [4], and Brun lattice reduction + as in [7], code for Babai's simple CVP approximation [8], and + Brun's integer relation decomposition. * The [SVP](http://www.latticechallenge.org/svp-challenge/) Challenge and the [Ideal](http://www.latticechallenge.org/ideallattice-challenge/) @@ -109,26 +118,33 @@ Possible improvements include: performance tests. The main [Lattice](http://www.latticechallenge.org/) Challenge also lists references which could be used to replicate tests. -* Compare with the [Number Theory Library](http://www.shoup.net/ntl/). +* Compare with the [fplll](https://github.com/fplll/fplll) library, + the [Number Theory Library](http://www.shoup.net/ntl/), and + NEMO/FLINT. + + + + ### References -[1] Lenstra, A. K.; Lenstra, H. W., Jr.; Lovász, L. (1982). "Factoring -polynomials with rational coefficients". Mathematische Annalen 261 -(4): 515–534. +[1] A. K. Lenstra; H. W. Lenstra Jr.; L. Lovász, ["Factoring polynomials with rational coefficients"](http://ftp.cs.elte.hu/~lovasz/scans/lll.pdf). Mathematische Annalen 261, 1982 + +[2] M. Seysen, ["Simultaneous reduction of a lattice basis and its reciprocal basis"](http://link.springer.com/article/10.1007%2FBF01202355) Combinatorica, 1993. + +[3] D. Wuebben, D. Seethaler, J. Jalden, and G. Matz, ["Lattice Reduction - A Survey with Applications in Wireless Communications"](http://www.ant.uni-bremen.de/sixcms/media.php/102/10740/SPM_2011_Wuebben.pdf). IEEE Signal Processing Magazine, 2011. + +[4] A. Ghasemmehdi, E. Agrell, ["Faster Recursions in Sphere Decoding"](https://publications.lib.chalmers.se/records/fulltext/local_141586.pdf) IEEE +Transactions on Information Theory, vol 57, issue 6 , June 2011. + +[5] P. W. Wolniansky, G. J. Foschini, G. D. Golden, R. A. Valenzuela, ["V-BLAST: An Architecture for Realizing Very High Data Rates Over the Rich-Scattering Wireless Channel"](http://ieeexplore.ieee.org/xpl/login.jsp?tp=&arnumber=738086). Proc. URSI +ISSSE: 295–300, 1998. -[2] M. Seysen, -["Simultaneous reduction of a lattice basis and its reciprocal basis"] -(http://link.springer.com/article/10.1007%2FBF01202355) Combinatorica, -Vol 13, no 3, pp 363-376, 1993. +[6] M. R. Bremner, ["Lattice Basis Reduction: An Introduction to the LLL + Algorithm and Its Applications"](https://www.amazon.com/Lattice-Basis-Reduction-Introduction-Applications/dp/1439807027) CRC Press, 2012. -[3] P. W. Wolniansky, G. J. Foschini, G. D. Golden, R. A. Valenzuela -(September 1998). ["V-BLAST: An Architecture for Realizing Very High -Data Rates Over the Rich-Scattering Wireless Channel"] -(http://ieeexplore.ieee.org/xpl/login.jsp?tp=&arnumber=738086). Proc. URSI -ISSSE: 295–300. +[7] Y. Chen, P. Q. Nguyen, ["BKZ 2.0: Better Lattice Security Estimates"](http://www.iacr.org/archive/asiacrypt2011/70730001/70730001.pdf). Proc. ASIACRYPT 2011. -[4] Y. Chen, P. Q. Nguyen (2011) ["BKZ 2.0: Better Lattice Security Estimates"] -(http://www.iacr.org/archive/asiacrypt2011/70730001/70730001.pdf). -Proc. ASIACRYPT 2011. +[8] L. Babai, ["On Lovász’ lattice reduction and the nearest lattice point problem"](https://link.springer.com/article/10.1007/BF02579403), +Combinatorica, 1986. diff --git a/benchmark/lrtest.jl b/benchmark/lrtest.jl index 3228899..91f7c05 100644 --- a/benchmark/lrtest.jl +++ b/benchmark/lrtest.jl @@ -19,7 +19,7 @@ function lrtest(Ns::Int,N::Array{Int,1},L::Array{Int,1}, # Packages that need to be loaded for lrtest to work include PyPlot and # BenchmarkTools - + #lrAlgs = [lll, lllrecursive,seysen] lrAlgs = [lll, seysen] @@ -35,16 +35,18 @@ if length(N)>1 for s = 1:length(N) push!(out,lrsim(Ns,N[s],L[1],dataType[1], distType, lrAlgs)); end - plotfun = PyPlot.loglog; + xscale=:log10; + yscale=:log10; xval = N; xlab = "Matrix Size"; - tstr = @sprintf("Ns=%d,L=%d,Type=%s,dist=%s", - Ns,L[1],string(dataType[1]),distType); + tstr = @sprintf("Ns=%d,Type=%s,dist=%s", + Ns,string(dataType[1]),distType); elseif length(L)>1 for s = 1:length(L) push!(out,lrsim(Ns,N[1],L[s],dataType[1], distType, lrAlgs)); end - plotfun = semilogy; + xscale = :identity; + yscale = :log10; xval = L; xlab = "L"; tstr = @sprintf("Ns=%d,N=%d,Type=%s,dist=%s", @@ -53,11 +55,17 @@ elseif length(dataType)>1 for s = 1:length(dataType) push!(out,lrsim(Ns,N[1],L[1],dataType[s], distType, lrAlgs)); end - plotfun = semilogy; + xscale = :identity; + yscale = :log10; xval = 1:length(dataType) xtickStrs = map(string,dataType) xlab = "dataType"; tstr = @sprintf("Ns=%d,N=%d,L=%d,dist=%s",Ns,N[1],L[1],distType); + for n=xval + if dataType[n]== DoubleFloat{Float64} + xtickStrs[n] = "Double64" + end + end end pColor = ["r-","b.-","k-","g-","c-","m-", @@ -67,31 +75,33 @@ pIdx = 1; Nout = size(out,1); -f=figure(num=1,figsize=(6.5,4.5)) -clf() -#plt.style[:use]("ggplot") times = zeros(Nout); +orthf = zeros(Nout); +pltT = plot(legend=:topleft); +#pltO = plot(legend=:topleft); + for a=1:length(out[1][2]) for k=1:Nout - times[k] = out[k][1][a]; + times[k] = out[k][2][a]; + orthf[k] = out[k][3][a]; end - plotfun(xval,times,pColor[pIdx],label=out[1][2][a]); + plot!(pltT,xval,times,xscale=xscale,yscale=yscale,label=out[1][1][a],linewidth=3); +# plot!(pltO,xval,orthf,xscale=xscale,yscale=yscale,label=out[1][1][a],linewidth=3); pIdx = pIdx==length(pColor) ? 1 : pIdx + 1; end -xlabel(xlab); -ylabel("execution time (sec)"); -legend(loc=2); +plot!(pltT,xlabel=xlab,ylabel="execution time (sec)") +#plot!(pltO,xlabel=xlab,ylabel="orthogonalization factor") if ~isempty(xtickStrs) - xticks(xval,xtickStrs) + plot!(pltT,xticks=(xval,xtickStrs)) +# plot!(pltO,xticks=(xval,xtickStrs)) end -grid(figure=f,which="both",axis="y") -grid(figure=f,which="major",axis="x") +display(pltT) +#display(pltO) -return end ####################################################################### @@ -130,12 +140,12 @@ for ix = 1:Ns # CPUtic(); # (B,T) = lrAlgs[ax](data); # times[ax,ix] = CPUtoq(); - times[ax,ix] = @belapsed (B,T) = $lrAlgs[$ax]($data) samples=2 seconds=1 - # detB = abs(det(B)) - # # Hermite factor - # hermitef[ax,ix] = norm(B[:,1])/detB^(1/N) - # # Orthogonality defect - # orthf[ax,ix] = prodBi(B,N)/detB + times[ax,ix] = @elapsed (B,T) = lrAlgs[ax](data) + detB = abs(det(B)) + # Hermite factor + hermitef[ax,ix] = norm(B[:,1])/detB^(1/N) + # Orthogonality defect + orthf[ax,ix] = prodBi(B,N)/detB # if abs(abs(det(T))-1.0)>1e-6 # println("For $(lrAlgs[ax]), det(T) is $(abs(det(T)))") @@ -152,7 +162,7 @@ end for ax = 1:length(lrAlgs) algNames[ax] = string(lrAlgs[ax]); end -@printf("%8d %6d %6d %10s", Ns, N, L, string(dataType)) +@printf("%8d %6d %6d %10s", Ns, N, L, string(dataType)[1:min(end,10)]) #outtimes = mean(times,2); outtimes = zeros(Nalgs,1); @@ -162,25 +172,18 @@ mean(x) = sum(x)/length(x) for ax = 1:Nalgs # stimes = sort(vec(times[ax,:])); # outtimes[ax] = mean(stimes[1:floor(Ns*2/3)]); +# outtimes[ax] = minimum(times[ax,:]); outtimes[ax] = mean(times[ax,:]); outHF[ax] = mean(hermitef[ax,:]); outOF[ax] = mean(orthf[ax,:]); end -# BOLD= "\x1B[1;30m" -# RESET="\x1B[0;0m" -# (mn,mx)=findmin(outtimes) for ax = 1:min(Nalgs,7) - # if ax==mx - # @printf(" %s%10.4f%s",BOLD,outtimes[ax]*1000,RESET) - # else - @printf(" %10.4f",outtimes[ax]*1000) -# @printf(" %10.4f",outHF[ax]) - # end + @printf(" %10.4f",outtimes[ax]*1000) end @printf("\n") -return outtimes, algNames +return algNames, outtimes, outOF, outHF end ####################################################################### @@ -191,7 +194,7 @@ function prodBi(B,N) for m=1:N pn+=conj(B[m,n])*B[m,n] end - println("pn=$(pn), typeof(pn)=$(typeof(pn))") + # println("pn=$(pn), typeof(pn)=$(typeof(pn))") prod*=sqrt(pn) end return prod diff --git a/benchmark/perfVsDataType.png b/benchmark/perfVsDataType.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d65bc1c8a45646777bf608acb1c0f7cfff72f8 GIT binary patch literal 25118 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�YhMUM^ni6{ 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 R1#CMAeeEk+MH;t5{|7>8?ezcv literal 0 HcmV?d00001 diff --git a/benchmark/perfVsNfloat32.png b/benchmark/perfVsNfloat32.png deleted file mode 100644 index 53219ee769aba7e84baaacfd2c9e2c6ff8388285..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29875 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 idzT~OC&qKL3Zlym_#>w#` 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

MR_pi&FcH6-3`iY@VBKk|gK@yT=EDmLQD? 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

=@ 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 bwKFLvFAD5)XNyOJQ8GChCF#_2w_X1S8*NBT diff --git a/benchmark/perfVsNfloat64.png b/benchmark/perfVsNfloat64.png new file mode 100644 index 0000000000000000000000000000000000000000..de2ce9ff69b7119a1d47c056060f129354f6a4b4 GIT binary patch literal 23286 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 X`WibZs%{4mX@r)BzIq A=[10 1 -9; 1 8 8]; xtrue=[0; 2; 9]; d=A*xtrue; +julia> integerfeasibility(A,d) +3-element Array{Int64,1}: + 0 + 2 + 9 + +julia> n=20;m=30; A = rand(-10:10,n,m); xtrue = rand(0:10,m); d=A*xtrue; +julia> sum(abs.(xtrue-integerfeasibility(A,d))) +0 + +``` +""" +function integerfeasibility(A::AbstractArray{Td,2}, + d::AbstractArray{Td,1}, flag=false) where {Td<:Number} + m,n=size(A) + N1=10^3 + N2=10^4 + B = [[I zeros(Td,n,1)]; + zeros(Td,1,n) N1; + N2*A -N2*d] + + Bhat,_ = lll(B) + Bprime = Bhat[1:n+1,1:n-m+1] + x_d = Bprime[1:n,end] + + if flag + xNull=Bprime[1:n,1:n-m] + return x_d,xNull + else + return x_d + end +end + + + +""" + x = subsetsum(a,s) + +For a vector of integers `a`, and an integer `s`, try to find a binary +vector `x` such that `x'*a=s`. We use the LLL algorithm to find the +solution. + +This follows the technique described by Lagarias and Odlyzko in +"Solving Low-Density Subset Sum Problems" in Journal of ACM, Jan 1985. +Code based on http://web.eecs.umich.edu/~cpeikert/lic15/lec05.pdf +The original technique came from Lagarias and Odlyzko. + +It's odd that permuting the `a` vector in the second example given below +causes the alg to often not find a solution. The example doesn't have the +required density, so maybe we should be asking why it finds an answer at +all. + +# Examples +```jldoctest +julia> a=[1,2,4,9,20,38]; s=30; x=subsetsum(a,s); s-x'*a +0 + +julia> a=[32771,65543,131101,262187,524387,1048759, # from Bremner p 117 + 2097523,4195057,8390143,16780259,33560539, + 67121039,134242091,268484171,536968403]; +julia> s=891221976; x=subsetsum(a,s); s-x'*a +0 + +julia> N=40;a=rand(1:2^BigInt(256),N);xtrue=rand(Bool,N); s=a'*xtrue; +julia> setprecision(BigFloat,300); x=subsetsum(a,s); s-x'*a +0 +``` +""" +function subsetsum(a::AbstractArray{Ti,1},s::Ti) where {Ti<:Integer} + # page numbers below refer to lecture note above + + n = length(a) + + flag = false; + if s1)) + binarySolution= true + xb = flag ? mod.(x .+ 1,2) : x + end + x = -x + if !(any(x.<0)|any(x.>1)) + binarySolution= true + xb = flag ? mod.(x .+ 1,2) : x + end + end + + if binarySolution + return xb + else + if maximum(a)<2^(n^2/2) + density = n/maximum(log2.(a)) + @printf("The density (%4.2f) of the 'a' vector is not as low as required\n", + density) + @printf("(%4.2f) for Lagarias-Odlyzko to work. We'll look for a ",2/n) + @printf("solution\nanyway... ") + end + + if length(ixMatch)>=1 + print("A non-binary solution was found\n") + return Bp[:,ixMatch[1][2]] + else + print("A solution was not found\n") + end + end + return NaN +end diff --git a/src/cvp.jl b/src/cvp.jl new file mode 100644 index 0000000..6473c4c --- /dev/null +++ b/src/cvp.jl @@ -0,0 +1,167 @@ +""" + x=cvp(y,H,infinite=Val{true},Umax=1) + +Solve the problem `argmin_x ||y-Hx||` for integer x using the technique from +the paper below. The input vector `y` is of length `n`, with `H` of +dimension `n` by `n`, and the returned vector `x` of length `n`. If +`finite==Val{false}` then we search the (infinite) lattice, otherwise we +search integers in `[-Umax,Umax]`. At present cvp does not handle complex +numbers. + +Uses alg from "Faster Recursions in Sphere Decoding" Arash Ghasemmehdi, Erik +Agrell, IEEE Transactions on Information Theory, vol 57, issue 6 , June 2011. + +# Examples +```jldoctest +julia> H=[1 2; 3 4]; Q,R=qr(H); uhat = cvp(Q'*[0,2],R) +2-element Array{Float64,1}: + 2.0 + -1.0 + +julia> n=100;H=randn(n,n);Q,R=qr(H); +julia> u=Int.(rand(0:1e10,n));y=H*u+rand(n)/100; +julia> uhat=cvp(Q'*y,R); sum(abs.(u-uhat)) +0.0 + +julia> n=500;H=randn(n,n);Q,R=qr(H); +julia> u=Int.(rand(-1:1,n));y=H*u+rand(n)/10; +julia> uhat=cvp(Q'*y,R,Val{false},1); sum(abs.(u-uhat)) +0.0 +``` +""" +function cvp(r::AbstractArray{Td,1},G::AbstractArray{Td,2}, + infinite=Val{true},Umax=1) where {Td<:Number} + + roundGA(x) = roundFinite(x,Td(Umax)) +# nx=1 + n = length(r) +# mxNx=Int(ceil(log2(n)*100000)) + C = Inf + i = n+1 + d = ones(Int,n)*n + λ = zeros(n+1) + F = zeros(Td,n,n) + F[:,n] = r + p = zeros(n) + u = NaN*zeros(Int,n) + û = NaN*zeros(Int,n) + Δ = zeros(Int,n) + + begin + @label LOOP + +# nx+=1 + while λ[i]mxNx + if i==n + # if nx>mxNx + # @warn("terminated search after $(nx) iterations.") + # end + return û + else + i+=1 + if infinite ≠ Val{true} + y = typemax(Td) + end + u[i]+=Δ[i] + Δ[i]=-Δ[i]-signGA(Δ[i]) + if infinite ≠ Val{true} + if -Umax ≤ u[i] ≤ Umax + y = (p[i]-u[i])*G[i,i] + else + u[i]+=Δ[i] + Δ[i]=-Δ[i]-signGA(Δ[i]) + if -Umax ≤ u[i] ≤ Umax + y = (p[i]-u[i])*G[i,i] + end + end + else + y = (p[i]-u[i])*G[i,i] + end + λ[i] = λ[i+1]+y*y + end + end + d[m:i-1] .= i + for j=m-1:-1:1 + if d[j]Umax ? Umax*signGA(y) : y +end + + + + +""" + b = svp(B) + +Find the shortest basis vector `b` for the lattice formed by the matrix +`B`. This solves the 'shortest vector problem' (SVP). + +We use the [`cvp`](@ref) function in the library, which is not necessarily +the fastest SVP solver. Roughly follows the CVP-to-SVP reduction in +http://web.eecs.umich.edu/~cpeikert/lic15/lec06.pdf + +# Examples +```jldoctest +julia> H=[1 2; 3 4]; svp(H) +2-element Array{Int64,1}: + -1 + 1 + +julia> H= BigFloat.([2.5 2; 3 4]); svp(H) +2-element Array{BigFloat,1}: + 0.50 + -1.0 + +``` +""" +function svp(B::AbstractArray{Td,2}) where Td + m,n= size(B) + V = zeros(Td,m,n) + c = zeros(Td,n) + + for i = 1:n + Bi = copy(B) + Bi[:,i] *=2 + Q,R = qr(Bi) + vc = cvp(Q'*B[:,i],R) + V[:,i] = Bi*vc-B[:,i] + c[i] = V[:,i]'*V[:,i] + end + idx = sortperm(c) + return V[:,idx[1]] +end diff --git a/src/hard_sphere.jl b/src/hard_sphere.jl index 7739976..78289f6 100644 --- a/src/hard_sphere.jl +++ b/src/hard_sphere.jl @@ -12,7 +12,7 @@ # columns of X. # # Examples: -# X = hard_sphere([1 2]', [1 2; 3 4],2) +# X = hard_sphere([1; 2], [1 2; 3 4],2) # X = hard_sphere(rand(0:20,2,15), [1 2; 3 4],2) # """ -> function hard_sphere(Y::AbstractArray{Td,2},H::AbstractArray{Td,2},Nc::Integer) where {Td} diff --git a/src/lll.jl b/src/lll.jl index e4b890a..4192c3a 100644 --- a/src/lll.jl +++ b/src/lll.jl @@ -1,25 +1,32 @@ -function lll(H::Array{Td,2},δ::Float64=3/4) where {Td} -# (B,T,Q,R) = LLL(H,δ=3/4) -# Do Lenstra–Lenstra–Lovász lattice reduction of matrix H using optional -# parameter δ. The output is B, an LLL-reduced basis; T, a unimodular -# (det(T)=+/-1) transformation matrix such that B= H*T; Q and R such that -# B=Q*R and Q is orthonormal, and R is upper triangular. So H = Q*R*inv(T) -# -# H can be of Integer, FloatingPoint, BigInt, or BigFloat types. The core -# algorithm is designed for floating-point. -# -# Example with large real matrix: -# N=500;H = randn(N,N); (B,T) = lll(H) -# Example with 2x2 complex matrix: -# N=2;H = randn(N,N)+im*randn(N,N); (B,T) = lll(H) -# Example with 2x2 BigInt matrix: -# N=2;H = ones(BigInt,N,N); rand!(H,-1e10:1e10); (B,T) = lll(H) - -# Follows LLL in D. Wueben, et al, MMSE-Based Lattice-Reduction for Near-ML -# Detection of MIMO Systems International IEEE Workshop on Smart Antennas, -# Munich, March 2004. - -# A few cycles can be saved by skipping updates of the Q matrix. +""" + B,T,Q,R = LLL(H,δ=3/4) + +Do Lenstra–Lenstra–Lovász lattice reduction of matrix `H` using optional +parameter `δ`. The output is `B`, an LLL-reduced basis; `T`, a unimodular +(meaning `det(T)=+/-1`) transformation matrix such that `B= H*T`; and +finally `Q` and `R` which are a QR decomposition of `B`. So `H = B*inv(T) = +Q*R*inv(T)`. + +Follows D. Wuebben, et al, "Lattice Reduction - A Survey with Applications +in Wireless Communications". IEEE Signal Processing Magazine, 2011. See +[`subsetsum`](@ref) for an application of `lll`. + +# Examples +```jldoctest +julia> H= [1 2; 3 4];B,_ = lll(H); B +2×2 Array{Int64,2}: + 1 -1 + 1 1 + +julia> H= BigFloat.([1.5 2; 3 4]) .+ 2im; B,_= lll(H); B +2×2 Array{Complex{BigFloat},2}: + 0.50+0.0im 0.0+1.0im + 1.0+0.0im 0.0+0.0im + +julia> N=500;H = randn(N,N); B,T = lll(H); +``` +""" +function lll(H::AbstractArray{Td,2},δ::Float64=3/4) where {Td<:Number} if !(0.25 < δ < 1.0) error("δ must be between 1/4 and 1."); @@ -27,16 +34,28 @@ end B = copy(H); L = size(B,2); -(Qt,R) = qr(B); -Q = Matrix(Qt); +Qt,R = qr(B); +Q = Matrix(Qt); # A few cycles can be saved by skipping updates of the Q matrix. -roundf(r::Td) where {Td<:Complex} = round(real(r)) + im*round(imag(r)); -roundf(r) = round(r); +# of course these should be replaced with different methods... I'll do it +# someday +if Td<:BigInt || Td<:BigFloat + Ti = BigInt +elseif Td==Float32 || Td==Int32 + Ti=Int32 +elseif Td==Int16 + Ti=Int16 +elseif Td==Int8 + Ti=Int8 +else + Ti=Int +end +#Ti = (Td<:BigInt || Td<:BigFloat) ? BigInt : Int if Td<:Complex - T = Matrix{Complex{Int}}(I, L, L) + T = Matrix{Complex{Ti}}(I, L, L) else - T = Matrix{Int}(I, L, L) + T = Matrix{Ti}(I, L, L) end lx = 2; @@ -46,10 +65,13 @@ while lx <= L for k=lx-1:-1:1 rk = R[k,lx]/R[k,k] mu = roundf(rk) - if abs(mu)>0 - B[:,lx] -= mu .* B[:,k] - R[1:k,lx] -= mu .* R[1:k,k] - T[:,lx] -= mu .* T[:,k] + if abs(mu)>zero(Ti) + # B[:,lx] -= mu * B[:,k] + # R[1:k,lx] -= mu * R[1:k,k] + # T[:,lx] -= mu * T[:,k] + B[:,lx] .-= mu .* view(B,:,k) + R[1:k,lx] .-= mu .* view(R,1:k,k) + T[:,lx] .-= mu .* view(T,:,k) end end @@ -57,26 +79,68 @@ while lx <= L if δ*abs(R[lx-1,lx-1])^2 > nrm^2 # swap columns lx-1 and lx in B, T and R - B[:,[lx-1,lx]] = B[:,[lx,lx-1]]; - T[:,[lx-1,lx]] = T[:,[lx,lx-1]]; - R[1:lx,[lx-1,lx]] = R[1:lx,[lx,lx-1]]; + # B[:,[lx-1,lx]] = B[:,[lx,lx-1]] + # T[:,[lx-1,lx]] = T[:,[lx,lx-1]] + # R[1:lx,[lx-1,lx]] = R[1:lx,[lx,lx-1]] + B[:,[lx-1,lx]] .= view(B,:,[lx,lx-1]) + T[:,[lx-1,lx]] .= view(T,:,[lx,lx-1]) + R[1:lx,[lx-1,lx]] .= view(R,1:lx,[lx,lx-1]) # upper triangular by Givens rotation - # mult with matrix Θ achieves R[lx,lx-1] = 0 cc = R[lx-1,lx-1] / nrm # nrm = ||R[lx-1:lx,lx-1]|| after swapping ss = R[lx,lx-1] / nrm Θ = [cc' ss; -ss cc] - # Don't have BLAS working here yet - R[lx-1:lx,lx-1:end] = Θ * R[lx-1:lx,lx-1:end] - #gemm!('N','N',1.0,Θ,R[lx-1:lx,lx-1:end],0.0,R[lx-1:lx,lx-1:end]) - Q[:,lx-1:lx] = Q[:,lx-1:lx] * Θ' - #gemm!('N','C', 1.0, Q[:,lx-1:lx], Θ, 0.0, Q[:,lx-1:lx]) + # the following multiply by Θ results in R[lx,lx-1] = 0 + R[lx-1:lx,lx-1:end] .= Θ * R[lx-1:lx,lx-1:end] + # Q[:,lx-1:lx] = Q[:,lx-1:lx] * Θ' + Q[:,lx-1:lx] .= view(Q,:,lx-1:lx) * Θ' lx = max(lx-1,2) else lx = lx+1; end end +return B,T,Q,R +end + -return (B,T,Q,R) +""" + B = gauss(H) + +Do Gauss (or Lagrange?!) reduction on the lattice defined by the two columns of +H. + +Follows Fig 2.3 of "Lattice Basis Reduction: An Introduction to the LLL +Algorithm and Its Applications" by Murray R. Bremner, CRC Press, 2012. + +# Examples +```jldoctest +julia> H = [1 2; 3 3]; B = gauss(H) +2×2 Array{Float64,2}: + 1.0 0.0 + 0.0 3.0 +``` +""" +function gauss(H::AbstractArray{Td,2}) where Td + if size(H,2)!=2 + error("Gauss reduction only works on two columns.") + end + x = float.(H[:,1]) + y = float.(H[:,2]) + if norm(x)<=norm(y) + v1,v2=x,y + else + v1,v2=y,x + end + finished = false; + while !finished + m = round((v2'*v1)/(v1'*v1)) + v2 -= m*v1 + if norm(v1)<=norm(v2) + finished = true + else + v1,v2 = v2,v1 + end + end + return [v1 v2] end diff --git a/src/seysen.jl b/src/seysen.jl index 997f25f..07583a6 100644 --- a/src/seysen.jl +++ b/src/seysen.jl @@ -1,15 +1,30 @@ +""" + B,T,B_dual,num_it = seysen(H::Array{Td,2}) where Td + +Do greedy Seysen lattice reduction on the matrix `H`, returning `B`, the +reduced lattice basis; `T` a unimodular matrix that reduces `H` (i.e. `B = +H*T`); `B_dual`, dual lattice basis (i.e., `B_dual = pinv(B)`); and num_it the number +of iterations (basis updates). See also [`lll`](@ref), and [`brun`](@ref). + +Follows Seysen algorithm in "Lattice Reduction - A Survey with Applications +in Wireless Communications" by D. Wuebben, et al, IEEE Signal Processing +Magazine, 2011. + +# Examples +```jldoctest +julia> H= [1 2; 3 4];B,_ = seysen(H); B +2×2 Array{Int64,2}: + 1 -1 + 1 1 + +julia> H= BigFloat.([1.5 2; 3 4]) .+ 2im; B,_= seysen(H); B +2×2 Array{Complex{BigFloat},2}: + 0.0+1.0im 0.50+0.0im + 0.0+0.0im 1.0+0.0im + +``` +""" function seysen(H::Array{Td,2}) where {Td} -# [B,T,B_dual,num_it] = seysen(H::Array{Td,2}) where Td -# -# Do greedy Seysen lattice reduction on the matrix H (real or -# complex-valued), returning T a unimodular matrix that reduces H; -# B, the reduced lattice basis (i.e. B = H*T); -# B_dual, dual lattice basis (i.e., B_dual = pinv(B)); and -# the number of iterations (the number of basis updates). - -# Follows LLL in D. Wueben, et al, MMSE-Based Lattice-Reduction for Near-ML -# Detection of MIMO Systems International IEEE Workshop on Smart Antennas, -# Munich, March 2004. if Td<:BigInt || Td<:BigFloat Ti= Td<:Complex ? Complex{BigInt} : BigInt @@ -20,12 +35,8 @@ else Ti= Td<:Complex ? Complex{Int} : Int end end -roundf(r) = Td<:Complex ? round(real(r)) + im*round(imag(r)) : round(r); -#H = H*1.0 - -# get size of input -(n, m) = size(H); # m-dimensional lattice in an n-dimensional space +n,m = size(H); # m-dimensional lattice in an n-dimensional space # initialization, outputs B = copy(H); # reduced lattice basis @@ -35,8 +46,8 @@ A = (H'*H)*1.0; # Gram matrix of H Adual = inv(A); # Inverse gram matrix of H B_dual = H*Adual; # Dual basis -# calculate all possible update values Λ[s,t) -# and their corresponding reduction Δ[s,t) in Seysen's measure +# calculate update values Λ[s,t] and the corresponding reduction Δ[s,t] in +# Seysen's measure Λ = zeros(Ti,m,m); Δ = zeros(Td,m,m)*0.0; @@ -55,14 +66,13 @@ for s = 1:m end # - end calculation of Λ and Δ # find maximum reduction in Seysen's measure (greedy approach) -(zw,max_ind) = findmax(abs.(Δ[:])); -(s, t) = Tuple(CartesianIndices((m,m))[max_ind]) +zw,max_ind = findmax(abs.(Δ[:])); +s,t = Tuple(CartesianIndices((m,m))[max_ind]) # init loop do_reduction = true; -# if no improvement can be achieved -if Δ[s,t] == 0 +if Δ[s,t] == 0 # if no improvement can be achieved do_reduction = false; end @@ -71,23 +81,12 @@ while do_reduction num_it = num_it + 1; - # perform basis update - try - B[:,s] = B[:,s] + Λ[s,t]*B[:,t]; - catch - println("B[:,s] = $(B[:,s]), typeof(B[:,s]) = $(typeof(B[:,s]))") - println("Λ[s,t] = $(Λ[s,t]), typeof(Λ[s,t]) = $(typeof(Λ[s,t]))") - end - - # compute corresponding unimodular trasformation matrix - T[:,s] = T[:,s] + Λ[s,t]*T[:,t]; # basis transformation matrix + + B[:,s] = B[:,s] + Λ[s,t]*B[:,t]; # perform basis update + T[:,s] = T[:,s] + Λ[s,t]*T[:,t]; # updater unimodular transformation + B_dual[:,t] = B_dual[:,t] - Λ[s,t]'*B_dual[:,s]; # update dual basis - # update corresponding dual basis - B_dual[:,t] = B_dual[:,t] - Λ[s,t]'*B_dual[:,s]; - - # Update Gram and inverse Gram matrix for ind = 1:m - # update Gram matrix if ind != s A[s,ind] = A[s,ind]+Λ[s,t]'*A[t,ind]; @@ -95,7 +94,6 @@ while do_reduction else A[s,ind] = norm(B[:,s]).^2; end - # update inverse Gram matrix if ind != t Adual[t,ind] = Adual[t,ind]-Λ[s,t]*Adual[s,ind]; @@ -103,8 +101,7 @@ while do_reduction else Adual[t,ind] = norm(B_dual[:,t])^2; end - - end # - end update Gram und inverse Gram matrix + end # update all possible update values Λ[s,t] # and their corresponding reduction Δ[s,t] in Seysen's measure @@ -113,20 +110,10 @@ while do_reduction if ((ind1==s) | (ind1==t) | (ind2==s) | (ind2==t)) & (ind1!=ind2) x = 0.5*(Adual[ind2,ind1]/Adual[ind1,ind1]-A[ind2,ind1]/ A[ind2,ind2]); - ## There is an intermitent bug that I haven't yet figured - ## out that this try/catch code is aimed at. Yes, it's ugly... - try - Λ[ind1,ind2] = roundf(x); - catch - println("x = $(x), typeof(x) = $(typeof(x))") - println("A[ind2,ind2] = $(A[ind2,ind2]), "* - "typeof(A[ind2,ind2]) = $(typeof(A[ind2,ind2]))") - println("roundf(x)) = $(roundf(x)) "* - "typeof(roundf(x))) = $(typeof(roundf(x)))") - println("Λ[ind1,ind2]) = $(Λ[ind1,ind2]) "* - "typeof(Λ[ind1,ind2])) = $(typeof(Λ[ind1,ind2]))") - println(" ") - end + + # Try Float64 if the following fails w Float32 + Λ[ind1,ind2] = Ti(roundf(x)); + AbsΛ = abs(Λ[ind1,ind2])^2; if AbsΛ != 0 zw = real(Λ[ind1,ind2])*real(x)+imag(Λ[ind1,ind2])*imag(x); @@ -136,19 +123,19 @@ while do_reduction end end end - end # - end update Λ and Δ + end # find maximum reduction in Seysen's measure (greedy approach) - (zw, max_ind) = findmax(abs.(Δ[:])); - (s, t) = Tuple(CartesianIndices((m,m))[max_ind]) + zw,max_ind = findmax(abs.(Δ[:])); + s,t = Tuple(CartesianIndices((m,m))[max_ind]) # if no reduction is possible, exit loop if Δ[s,t] == 0 do_reduction = false; end -end # - end lattice reduction loop +end # while do_reduction -return (B,T,B_dual,num_it) +return B,T,B_dual,num_it end diff --git a/test/runtests.jl b/test/runtests.jl index 3cf1e5d..e8a8965 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -83,31 +83,33 @@ println("Error Rate is $(errRate). It should be zero or very small.\n") # -------------- println("Testing now with 200x200 matrix from latticechallenge.org.") -println("The col norm of the input should be 233, col norm of the "* - "reduced bases should be 30.") +println("The min norm of the input should be 30, min norm of the "* + "reduced bases should be smaller.") mat = readdlm("challenge-200.mod",Int64) #run from parent directory +mat = Matrix(mat'); +N = size(mat,2) -nrms = zeros(200,1) -for ix=1:200 +nrms = zeros(N,1); +for ix=1:N nrms[ix] = norm(mat[:,ix]) end -println("max col-norm of input is $(maximum(nrms))") +println("min norm of input is $(minimum(nrms))") @time (B,T) = lll(mat); -nrms = zeros(200,1) -for ix=1:200 - nrms[ix] = norm(B[:,ix]) +nrms = zeros(N,1); +for ix=1:N + nrms[ix] = norm(B[:,ix]); end -maxlll = maximum(nrms) -println("max col-norm of lll-reduced basis is $(maxlll)") -@test maxlll<=30+1e-6 +mlll=minimum(nrms) +println("min norm of lll-reduced basis is $(mlll)") +@test mlll<=30+1e-6 @time (B,T) = seysen(mat); -nrms = zeros(200,1) -for ix=1:200 +nrms = zeros(N,1) +for ix=1:N nrms[ix] = norm(B[:,ix]) end -maxSeysen = maximum(nrms) -println("max column norm of seysen-reduced basis is $(maxSeysen)") -@test maxSeysen<=30+1e-6 +mSeysen = minimum(nrms) +println("min norm of seysen-reduced basis is $(mSeysen)") +@test mSeysen<=30+1e-6