找到球体中晶格所有点的最佳方法



给定一组任意向量(存储在矩阵a中)和半径r,我想找到这些向量的所有整数值线性组合,这些向量落在半径r的球体内。然后我将把必要的坐标存储在矩阵V中。因此,例如,如果线性组合

K=[0; 1; 0]

降落在我的球体内,即类似的东西

if norm(A*K) <= r then
   V(:,1)=K
end

等等。

A中的向量肯定是给定晶格的最简单可能的基,并且最大的向量的长度为1。不确定这是否以任何有用的方式限制了向量,但我怀疑可能。-它们不会像不太理想的基础那样有相似的方向。

我已经尝试了几种方法,但似乎没有一种特别令人满意。我似乎找不到一个好的模式来遍历晶格。

我目前的方法包括从中间开始(即所有0的线性组合),并逐个通过必要的坐标。它包括存储一堆额外的向量来跟踪,这样我就可以遍历坐标的所有八分之一(在3D的情况下),并逐一找到它们。这种实现似乎非常复杂,也不太灵活(尤其是它似乎不容易推广到任意数量的维度——尽管这对当前的目的来说不是绝对必要的,但拥有会很好)

有没有一种很好的方法可以找到所有需要的分数?

(*理想情况下,既高效又优雅**。如果真的有必要,在球体外多几个点也没什么关系,但最好不要多。我确实需要球体内的所有矢量。-如果这有很大的不同,我最感兴趣的是3D情况。

**我很确定我目前的实现两者都不是。)


我发现了类似的问题:

找到任意坐标周围半径为r的球体中的所有点——这实际上是一个比我所寻找的更普遍的情况。我只处理周期晶格,我的球体总是以0为中心,与晶格上的一点重合。但我没有点的列表,而是一个向量矩阵,我可以用它生成所有的点。

如何有效地枚举n维网格中球面的所有点——完全正则超三次格和曼哈顿距离的情况。我在寻找完全任意的格和欧氏距离(或者,为了效率的目的,显然是它的平方)。

在不证明任何断言的情况下,我认为1)如果向量集不是最大秩的,那么解的数量是无限的;2) 如果该集合具有最大秩,则由向量生成的线性变换的图像是目标空间的子空间(例如,平面),该子空间在低维球体中与球体相交;3) 可以将问题简化为1-1线性变换(k维空间上的kxk矩阵);4) 由于矩阵是可逆的,你可以将球体"拉回"到包含格点的空间中的椭球体,作为奖励,你可以得到椭球体的良好几何描述(主轴定理);5) 现在,您的问题变成了确定椭球体内部晶格点的问题。

后一个问题与高斯考虑的一个老问题(计算椭圆内的格点)有关,高斯导出了一个很好的近似值。确定椭圆(oid)内的晶格点可能不是一个整洁的问题,但它可能可以一次减少一个维度(椭球和平面的横截面是另一个椭球)。

我找到了一个让我现在更快乐的方法。可能还有可能的改进,所以如果你有更好的方法,或者在这段代码中发现错误,请分享。尽管这是我现在所拥有的:(全部写在SciLab中)


步骤1:计算出由与晶格向量的轴对齐的边界n-等位多面体定义的最大范围。感谢ElKamina含糊其辞的建议,以及chappers对我在math.se上的另一个问题的回答:https://math.stackexchange.com/a/1230160/49989

    function I=findMaxComponents(A,r) //given a matrix A of lattice basis vectors
                                      //and a sphere radius r,
                                      //find the corners of the bounding parallelotope
                                      //built from the lattice, and store it in I.
        [dims,vecs]=size(A); //figure out how many vectors there are in A (and, unnecessarily, how long they are)
        U=eye(vecs,vecs); //builds matching unit matrix
        iATA=pinv(A'*A); //finds the (pseudo-)inverse of A^T A
        iAT=pinv(A'); //finds the (pseudo-)inverse of A^T
        I=[]; //initializes I as an empty vector
        for i=1:vecs                           //for each lattice vector,
            t=r*(iATA*U(:,i))/norm(iAT*U(:,i)) //find the maximum component such that
                                               //it fits in the bounding n-parallelotope
                                               //of a (n-1)-sphere of radius r
            I=[I,t(i)];                        //and append it to I
        end
        I=[-I;I]; //also append the minima (by symmetry, the negative maxima)
    endfunction

在我的问题中,我只要求一个一般的基础,即对于n维,一组n个任意但线性独立的向量。通过使用伪逆,上述代码适用于任意形状的矩阵,类似地,Scilab的"逆";CCD_ 1";返回共轭转置,而不仅仅是A的转置,因此它同样适用于复杂矩阵。

在最后一步中,我放置了相应的最小组件。

以一个这样的A为例,这在Scilab的控制台中给了我以下内容:

     A  =
     
        0.9701425  - 0.2425356    0.
        0.2425356    0.4850713    0.7276069
        0.2425356    0.7276069  - 0.2425356
    
    r=3;
    I=findMaxComponents(A,r)
    
     I  =
     
      - 2.9494438  - 3.4186986  - 4.0826424
        2.9494438    3.4186986    4.0826424
    
     I=int(I)
     
     I  =
     
      - 2.  - 3.  - 4.
        2.    3.    4.

findMaxComponents找到的值是每个晶格矢量的最大可能系数,使得与该系数的线性组合仍然存在于球体上。由于我正在寻找具有整数系数的最大此类组合,因此我可以安全地删除小数点后的部分,以获得最大的合理整数范围。因此,对于给定的矩阵A,我必须从第一个分量的-2到2,从第二个分量的-3到3,从第三个分量的-4到4,我确信会落在球体内的所有点上(加上多余的额外点,但重要的是,肯定是里面的每个有效点)下一步:


步骤2:使用上述信息,生成所有候选组合。

    function K=findAllCombinations(I) //takes a matrix of the form produced by
                                      //findMaxComponents() and returns a matrix
                                      //which lists all the integer linear combinations
                                      //in the respective ranges.
        v=I(1,:); //starting from the minimal vector
        K=[];
        next=1; //keeps track of what component to advance next
        changed=%F; //keeps track of whether to add the vector to the output
        
        while or(v~=I(2,:)) //as long as not all components of v match all components of the maximum vector
            if v <= I(2,:) then //if each current component is smaller than each largest possible component
                if ~changed then
                    K=[K;v]; //store the vector and
                end
                v(next)=v(next)+1; //advance the component by 1
                next=1; //also reset next to 1
                changed=%F;
            else
                v(1:next)=I(1,1:next); //reset all components smaller than or equal to the current one and
                next=next+1; //advance the next larger component next time
                changed=%T;
            end
        end
        K=[K;I(2,:)]'; //while loop ends a single iteration early so add the maximal vector too
                       //also transpose K to fit better with the other functions
    endfunction

现在我已经知道了,剩下的就是检查给定的组合是否真的位于球体内部或外部。我所要做的就是:


步骤3:过滤组合以找到实际有效的格点

    function points=generatePoints(A,K,r)
        possiblePoints=A*K; //explicitly generates all the possible points
        points=[];
        for i=possiblePoints
            if i'*i<=r*r then //filter those that are too far from the origin
                points=[points i];
            end
        end
    endfunction

我得到了所有的组合,它们确实适合半径r的范围。

对于上面的例子,输出相当长:对于半径为3的球体,在最初的315个可能点中,我得到了163个剩余点。

前4列为:(每列为一列)

  - 0.2425356    0.2425356    1.2126781  - 0.9701425
  - 2.4253563  - 2.6678919  - 2.4253563  - 2.4253563
    1.6977494    0.           0.2425356    0.4850713

所以剩下的工作就是优化。据推测,其中一些循环可以更快地进行,尤其是随着维度数量的增加,我必须生成大量必须丢弃的点,所以也许有一种比以n-1-球体的边界n-平行四边形作为起点更好的方法。

让我们只将K表示为X。

问题可以表示为:

(a11x1+a12x2.)^2+(a21x1+a22x2.)^ 2…<r^2

(x1,x2,…)将不会形成球体。

这可以通过维度递归来完成——选择一个晶格超平面方向并索引所有与r半径球相交的超平面。每个这样的超平面的球交点本身就是一个低维的球。重复以下是Octave中的调用函数代码:

function lat_points(lat_bas_mx,rr)
% **globals for hyperplane lattice point recursive function**
clear global; % this seems necessary/important between runs of this function
global MLB;
global NN_hat;
global NN_len;
global INP; % matrix of interior points, each point(vector) a column vector
global ctr; % integer counter, for keeping track of lattice point vectors added 
    %   in the pre-allocated INP matrix; will finish iteration with actual # of points found
ctr = 0;    % counts number of ball-interior lattice points found
MLB = lat_bas_mx;
ndim = size(MLB)(1);
% **create hyperplane normal vectors for recursion step**
% given full-rank lattice basis matrix MLB (each vector in lattice basis a column),
%   form set of normal vectors between successive, nested lattice hyperplanes;
%   store them as columnar unit normal vectors in NN_hat matrix and their lengths in NN_len vector
NN_hat = [];
for jj=1:ndim-1
    tmp_mx = MLB(:,jj+1:ndim);
    tmp_mx = [NN_hat(:,1:jj-1),tmp_mx];
    NN_hat(:,jj) = null(tmp_mx'); % null space of transpose = orthogonal to columns
    tmp_len = norm(NN_hat(:,jj));
    NN_hat(:,jj) = NN_hat(:,jj)/tmp_len;
    NN_len(jj) = dot(MLB(:,jj),NN_hat(:,jj));
    if (NN_len(jj)<0) % NN_hat(:,jj) and MLB(:,jj) must have positive dot product 
        %   for cutting hyperplane indexing to work correctly
        NN_hat(:,jj) = -NN_hat(:,jj);
        NN_len(jj) = -NN_len(jj);
    endif
endfor
NN_len(ndim) = norm(MLB(:,ndim));
NN_hat(:,ndim) = MLB(:,ndim)/NN_len(ndim); % the lowest recursion level normal
    %   is just the last lattice basis vector
% **estimate number of interior lattice points, and pre-allocate memory for INP**
vol_ppl = prod(NN_len); % the volume of the ndim dimensional lattice paralellepiped 
    %   is just the product of the NN_len's (they amount to the nested altitudes 
    %   of hyperplane "paralellepipeds")
vol_bll = exp( (ndim/2)*log(pi) + ndim*log(rr) - gammaln(ndim/2+1) ); % volume of ndim ball, radius rr
est_num_pts = ceil(vol_bll/vol_ppl); % estimated number of lattice points in the ball
err_fac = 1.1; % error factor for memory pre-allocation--assume max of err_fac*est_num_pts columns required in INP
INP = zeros(ndim,ceil(err_fac*est_num_pts));
% **call the (recursive) function**
% for output, global variable INP (matrix of interior points)
%   stores each valid lattice point (as a column vector)
clp = zeros(ndim,1); % confirmed lattice point (start at origin)
bpt = zeros(ndim,1); % point at center of ball (initially, at origin)
rd = 1; % initial recursion depth must always be 1
hyp_fun(clp,bpt,rr,ndim,rd);
printf("%i lattice points foundn",ctr);
INP = INP(:,1:ctr); % trim excess zeros from pre-allocation (if any)
endfunction

关于NN_len(jj)*NN_hat(:;平行六面体;由晶格基中的矢量MLB形成。格基平行六面体的体积仅为prod(NN_len)——为了快速估计内部格点的数量,将半径为rr的ndim球的体积除以prod(NN_len)。这是递归函数代码:

function hyp_fun(clp,bpt,rr,ndim,rd)
%{
    clp = the lattice point we're entering this lattice hyperplane with
    bpt = location of center of ball in this hyperplane
    rr = radius of ball
    rd = recrusion depth--from 1 to ndim
%}
    global MLB;
    global NN_hat;
    global NN_len;
    global INP;
    global ctr;
    % hyperplane intersection detection step
    nml_hat = NN_hat(:,rd);
    nh_comp = dot(clp-bpt,nml_hat); 
    ix_hi = floor((rr-nh_comp)/NN_len(rd));
    ix_lo = ceil((-rr-nh_comp)/NN_len(rd));
    if (ix_hi<ix_lo)
        return % no hyperplane intersections detected w/ ball; 
                %   get out of this recursion level
    endif
    hp_ix = [ix_lo:ix_hi]; % indices are created wrt the received reference point
    hp_ln = length(hp_ix);
    % loop through detected hyperplanes (updated)
    if (rd<ndim)
        bpt_new_mx = bpt*ones(1,hp_ln) + NN_len(rd)*nml_hat*hp_ix; % an ndim by length(hp_ix) matrix
        clp_new_mx = clp*ones(1,hp_ln) + MLB(:,rd)*hp_ix; % an ndim by length(hp_ix) matrix
        dd_vec = nh_comp + NN_len(rd)*hp_ix; % a length(hp_ix) row vector
        rr_new_vec = sqrt(rr^2-dd_vec.^2);
        for jj=1:hp_ln
            hyp_fun(clp_new_mx(:,jj),bpt_new_mx(:,jj),rr_new_vec(jj),ndim,rd+1);
        endfor
    else  % rd=ndim--so at deepest level of recursion; record the points on the given 1-dim 
          %   "lattice line" that are inside the ball
        INP(:,ctr+1:ctr+hp_ln) = clp + MLB(:,rd)*hp_ix;
        ctr += hp_ln;
        return
    endif
endfunction

这里面有一些八分之一/匹配的东西,但大多数应该很容易理解;M(:,jj)引用矩阵M的列jj;tic的意思是转置;[AB]连接矩阵A和B;A=[]声明了一个空矩阵。

更新/更好地优化了原始答案:

  • "矢量化的";代码中的递归函数,以避免大多数";对于";循环(这些循环使它慢了大约10倍;不过现在代码有点难以理解)
  • 为内部点的INP矩阵预先分配的内存(这将其加速了另一个数量级;在此之前,Octave必须为每次调用最内部递归级别调整INP矩阵的大小——对于真正会减慢速度的大型矩阵/数组)

因为这个例程是项目的一部分,所以我也用Python对它进行了编码。从非正式测试来看,Python版本比这个(Octave)版本快2-3倍。

作为参考,这里是这个答案最初发布的旧的、慢得多的代码:

    % (OLD slower code, using for loops, and constantly resizing
    %   the INP matrix) loop through detected hyperplanes
    if (rd<ndim)
        for jj=1:length(hp_ix)
            bpt_new = bpt + hp_ix(jj)*NN_len(rd)*nml_hat;
            clp_new = clp + hp_ix(jj)*MLB(:,rd);
            dd = nh_comp + hp_ix(jj)*NN_len(rd);
            rr_new = sqrt(rr^2-dd^2);
            hyp_fun(clp_new,bpt_new,rr_new,ndim,rd+1);
        endfor
    else  % rd=ndim--so at deepest level of recursion; record the points on the given 1-dim
          %   "lattice line" that are inside the ball
        for jj=1:length(hp_ix)
            clp_new = clp + hp_ix(jj)*MLB(:,rd);
            INP = [INP clp_new];
        endfor
        return
    endif

相关内容

  • 没有找到相关文章