Skip to main content.

In this article, I want to discuss three questions, which turn out to be closely related. The first question is,

“Given a lattice \Lambda \subseteq \R^n. How do I find a basis of this lattice?”

(Note that this question is far from being well-posed.) The second question is,

“If G is a finite abelian group and g_1, \dots, g_n \in G, how do I compute the structure of \langle g_1, \dots, g_n \rangle, the subgroup generated by these elements?”

The third question comes up in the description of algorithms, for example of the baby-step giant-step algorithm by D. Shanks, an optimization by D. Terr, and more general algorithms, like the Buchmann-Schmidt algorithm. These algorithms can be described in terms of the first question, making them easier to understand.

We begin with sketching the relation between lattices and finite abelian groups. If G is a finite abelian group, and g_1, \dots, g_n \in G some elements, for example, a set of generators, consider the map

\Psi_{(g_1, \dots, g_n)} : \Z^n \to G, \qquad (\lambda_1, \dots, \lambda_n) \mapsto \sum_{i=1}^n \lambda_i g_i.

This turns out to be a group homomorphism onto \langle g_1, \dots, g_n \rangle. The kernel of \Psi_{(g_1, \dots, g_n)}, which we will denote by \Lambda_{(g_1, \dots, g_n)}, is called the relation lattice. This is in fact a lattice in \R^n of volume

\det \Lambda_{(g_1, \dots, g_n)} = \abs{\Z^n / \Lambda_{(g_1, \dots, g_n)}} = \abs{\langle g_1, \dots, g_n\rangle};

note that by the Homomorphism Theorem, \langle g_1, \dots, g_n \rangle \cong \Z^n / \Lambda_{(g_1, \dots, g_n)}. On the other hand, if \Lambda \subseteq \Z^n is a lattice, then G := \Z^n / \Lambda is a finite abelian group of \det G elements. Moreover, if the residue class of e_i, the vector consisting of zeroes except a one at the i-th position, in G is denoted by g_i, then \Lambda = \Lambda_{(g_1, \dots, g_n)}. Therefore, lattices in \Z^n and finite abelian groups with n generators are essentially the same thing.

How is this related to group structure computations? Recall the Smith normal form; this allows to convert a basis (v_1, \dots, v_n) of a lattice \Lambda into another basis, such that if one applies an invertible linear transformation to \Z^n, this basis is sent to \lambda_1 e_1, \dots, \lambda_n e_n with \lambda_i \in \N_{>0} such that \lambda_i divides \lambda_{i+1}, 1 \le i < n; then, \Z^n / \Lambda \cong \prod_{i=1}^n \Z/\lambda_i \Z. Hence, computing the structure of a finite abelian group generated by g_1, \dots, g_n can be split up in the two parts, (a) computation of a basis of the relation lattice \Lambda_{(g_1, \dots, g_n)} and (b) computation of a Smith normal form of this basis. There exist a lot of algorithms for computation of Smith normal forms; usually, the bottleneck is finding a basis of \Lambda_{(g_1, \dots, g_n)}.

Often, the process of finding a lattice equals determining the relation lattice of a finite abelian group, or something similar. One often has a way to test whether v + \Lambda = w + \Lambda, by computing a unique representation of the residue class v + \Lambda; then, one tries to find two ways v + \Lambda = w + \Lambda of writing the same residue class, but with v \neq w: then v - w is a non-trivial element of \Lambda. If one sets G := \Z^n / \Lambda, then this means that one seeks for pairs (v, g), (w, h), where g = v + \Lambda and h = w + \Lambda, such that g = h and v \neq w.

More generally, assume that X is a finite set and \pi : \Z^n \to X is a map such that \pi(x + \lambda) = \pi(x) for all \lambda \in \Lambda. Our above example, G = \Z^n / \Lambda and \pi(x) = x + \Lambda satisfies this. Moreover, assume that \pi : \Z^n / \Lambda \to X is “mostly injective”, i.e. that \pi(v) = \pi(w) implies that one can find \tilde{v}, \tilde{w} \in \Z^n such that \tilde{v} \approx v, \tilde{w} \approx w, \tilde{v} - \tilde{w} \in \Lambda with very little effort. Then one can work with elements (x, y) with y = \pi(x), and try to find two such pairs (x, y), (x', y') with y = y'; then this gives rise to an element of \Lambda near to x - x'.

Which brings us to the subject of explaining algorithms. Consider, for example, Shanks' baby-step giant- step algorithm. You are given a group element of finite order g together with a bound B > 0. Then, for the algorithm, one computes g^0, g^1, \dots, g^{B-1}, as well as g^B, g^{2 B}, g^{3 B}, \dots, and compares this elements with the first B elements. Any match will result in a multiple of the order of g. But why is this the case? One can of course try to prove this; it is actually not very hard, in fact one just needs Fermat's Little Theorem  as well as long division. But one can do better, by visualizing the algorithm in a way which makes the solution looking obvious, and which allows even people who have no understanding of formal mathematics or computer science to immediately realize that the algorithm produces the correct result.

Namely, you might have guessed it, the order of g generates the relation lattice \Lambda_{(g)}. Hence finding the order is equivalent to finding a the smallest positive element in \Lambda_{(g)} \subseteq \Z. For this correspondence, we use the pairs (n, g^n) with n \in \Z as above. Two pairs (n, g^n), (m, g^m) with g^n = g^m gives a multiple n - m of the order of g. Now, the algorithm can be interpreted as translating the set of elements X_B := \{ -B+1, -B+2, \dots, -2, -1, 0 \} by B, 2 B, 3 B, and checking if a lattice element is contained in any of these translates. If one visualizes this, one immediately sees that this method will eventually find the smallest non-zero element of the lattice. First, this depicts the lattice \Z (gray dots) with its sublattice \Lambda_{(g)} (black dots), with the set X_B = \{ -B+1, \dots, 0 \} drawn in for B = 8:

\tikzstyle{gelt} = [draw, shape = circle, fill=black, inner sep=0pt, minimum size = 0.2cm]; \tikzstyle{empt} = [draw, shape = circle, inner sep=0pt, minimum size = 0.2cm]; \filldraw[black!67, fill=black!20] (-7.4,-0.4) to (-7.4,0.4) to (0.4,0.4) to (0.4,-0.4) to (-7.4,-0.4); \draw (-5,0) to (-5,-0.75); \node[empt] (gm5) at (-5,0) [label=below: \footnotesize \( g^{-5} \)] {}; \draw (0,0) to (0,-0.75); \node[gelt] (g0) at (0,0) [label=below: \footnotesize \( g^0 \)] {}; \draw (5,0) to (5,-0.75); \node[empt] (g5) at (5,0) [label=below: \footnotesize \( g^5 \)] {}; \draw (10,0) to (10,-0.75); \node[empt] (g10) at (10,0) [label=below: \footnotesize \( g^{10} \)] {}; \draw (23,0) to (23,-0.75); \node[gelt] (g23) at (23,0) [label=below: \footnotesize \( g^{23} \)] {}; \foreach \i in {-9,-8,-7,-6,-4,-3,-2,-1} \node[empt] at (\i,0) {}; \foreach \i in {1,2,3,4,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,22} \node[empt] at (\i,0) {}; \foreach \i in {24,25,26,27,28,29} \node[empt] at (\i,0) {};

The next figure depicts the translates of X_B by B, 2 B, 3 B, \dots:

\tikzstyle{gelt} = [draw, shape = circle, fill=black, inner sep=0pt, minimum size = 0.2cm]; \tikzstyle{empt} = [draw, shape = circle, inner sep=0pt, minimum size = 0.2cm]; \filldraw[black!67, fill=black!20] (0.6,-0.4) to (0.6,0.4) to (8.4,0.4) to (8.4,-0.4) to (0.6,-0.4); \filldraw[black!67, fill=black!20] (8.6,-0.4) to (8.6,0.4) to (16.4,0.4) to (16.4,-0.4) to (8.6,-0.4); \filldraw[black!67, fill=black!20] (16.6,-0.4) to (16.6,0.4) to (24.4,0.4) to (24.4,-0.4) to (16.6,-0.4); \filldraw[black!67, fill=black!20] (29.4,-0.4) to (24.6,-0.4) to (24.6,0.4) to (29.4,0.4); \draw (-5,0) to (-5,-0.75); \node[empt] (gm5) at (-5,0) [label=below: \footnotesize \( g^{-5} \)] {}; \draw (0,0) to (0,-0.75); \node[gelt] (g0) at (0,0) [label=below: \footnotesize \( g^0 \)] {}; \draw (5,0) to (5,-0.75); \node[empt] (g5) at (5,0) [label=below: \footnotesize \( g^5 \)] {}; \draw (10,0) to (10,-0.75); \node[empt] (g10) at (10,0) [label=below: \footnotesize \( g^{10} \)] {}; \draw (23,0) to (23,-0.75); \node[gelt] (g23) at (23,0) [label=below: \footnotesize \( g^{23} \)] {}; \foreach \i in {-9,-8,-7,-6,-4,-3,-2,-1} \node[empt] at (\i,0) {}; \foreach \i in {1,2,3,4,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,22} \node[empt] at (\i,0) {}; \foreach \i in {24,25,26,27,28,29} \node[empt] at (\i,0) {};

One directly sees that the translates X_B + k B, k \in \N_{>0} cover all positive integers, and that every positive integer is contained in exactly one translate. Moreover, one sees that if the first translate contains at most one lattice element, the first translate which contains a lattice element uniquely determines the smallest positive integer. Note that in case n is the order of g, then one can minimize the number of operations by chosing B \approx \sqrt{n}.

Now let us consider Terr's modification of the baby-step giant-step algorithm; there, the situation is a bit more complicated. In Terr's algorithm, the bound B from above constantly changes, starting with B = 1. Written as pseudo-code, the algorithm looks like this:

  1. Let a := g^1 and b := g^1, and let B := 1 and X_B := \{ (g^0, 0) \}.
  2. If (b, n) \in X_B for some n \in \N, return \frac{B (B + 1)}{2} - n.
  3. Set X_B := X_B \cup \{ (a, B) \}, and set B := B + 1.
  4. Compute a := a \cdot g and b := b \cdot a.
  5. Go back to Step 2.

There is no obvious reason why this should work. Note that the test whether (b, n) \in X_B means that one translates \{ -B + 1, \dots, 0 \} by the exponent c of b = g^c, which turns out to be \frac{B (B + 1)}{2}. One immediately gets the idea if one draws the first few translates:

\tikzstyle{gelt} = [draw, shape = circle, fill=black, inner sep=0pt, minimum size = 0.2cm]; \tikzstyle{empt} = [draw, shape = circle, inner sep=0pt, minimum size = 0.2cm]; \filldraw[black!67, fill=black!20] (0.6,-0.4) to (0.6,0.4) to (1.4,0.4) to (1.4,-0.4) to (0.6,-0.4); \filldraw[black!67, fill=black!20] (1.6,-0.4) to (1.6,0.4) to (3.4,0.4) to (3.4,-0.4) to (1.6,-0.4); \filldraw[black!67, fill=black!20] (3.6,-0.4) to (3.6,0.4) to (6.4,0.4) to (6.4,-0.4) to (3.6,-0.4); \filldraw[black!67, fill=black!20] (6.6,-0.4) to (6.6,0.4) to (10.4,0.4) to (10.4,-0.4) to (6.6,-0.4); \filldraw[black!67, fill=black!20] (10.6,-0.4) to (10.6,0.4) to (15.4,0.4) to (15.4,-0.4) to (10.6,-0.4); \filldraw[black!67, fill=black!20] (15.6,-0.4) to (15.6,0.4) to (21.4,0.4) to (21.4,-0.4) to (15.6,-0.4); \filldraw[black!67, fill=black!20] (21.6,-0.4) to (21.6,0.4) to (28.4,0.4) to (28.4,-0.4) to (21.6,-0.4); \filldraw[black!67, fill=black!20] (29.4,-0.4) to (28.6,-0.4) to (28.6,0.4) to (29.4,0.4); \draw (-5,0) to (-5,-0.75); \node[empt] (gm5) at (-5,0) [label=below: \footnotesize \( g^{-5} \)] {}; \draw (0,0) to (0,-0.75); \node[gelt] (g0) at (0,0) [label=below: \footnotesize \( g^0 \)] {}; \draw (5,0) to (5,-0.75); \node[empt] (g5) at (5,0) [label=below: \footnotesize \( g^5 \)] {}; \draw (10,0) to (10,-0.75); \node[empt] (g10) at (10,0) [label=below: \footnotesize \( g^{10} \)] {}; \draw (23,0) to (23,-0.75); \node[gelt] (g23) at (23,0) [label=below: \footnotesize \( g^{23} \)] {}; \foreach \i in {-9,-8,-7,-6,-4,-3,-2,-1} \node[empt] at (\i,0) {}; \foreach \i in {1,2,3,4,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,22} \node[empt] at (\i,0) {}; \foreach \i in {24,25,26,27,28,29} \node[empt] at (\i,0) {};

This is another tiling of \N_{>0}, where every positive integer is contained in exactly one translate. This tiling has the property that the first time (b, n) \in X_B occurs for some n \in \N is when the order of g is found, and one no longer has to fix a bound B before. The asymptotic complexity of this method is the same as the previous method in case B \approx \sqrt{n}, but in case B is chosen the wrong way, the first algorithm will perform worse than the second. Another way to visualize the second agorithm is to depict the set \{ -B+1, \dots, 0 \} together with \frac{B (B + 1)}{2}, where a = g^B and b = g^{B (B + 1)/2}, for B = 1, 2, \dots, 7:

\tikzstyle{gelt} = [draw, shape = circle, fill=black, inner sep=0pt, minimum size = 0.2cm]; \tikzstyle{empt} = [draw, shape = circle, inner sep=0pt, minimum size = 0.2cm]; \foreach \B in { 1, 2, 3, 4, 5, 6, 7 } { \filldraw[black!20, fill=black!67] (-\B+0.6,-\B-0.4) to (-\B+0.6,-\B+0.4) to (0.4,-\B+0.4) to (0.4,-\B-0.4) to (-\B+0.6,-\B-0.4); \filldraw[black!67, fill=black!20] (\B*\B/2-\B/2+0.6,-\B-0.4) to (\B*\B/2-\B/2+0.6,-\B+0.4) to (\B*\B/2+\B/2+0.4,-\B+0.4) to (\B*\B/2+\B/2+0.4,-\B-0.4) to (\B*\B/2-\B/2+0.6,-\B-0.4); \filldraw[black!20, fill=black!67] (\B*\B/2+\B/2-0.4,-\B-0.4) to (\B*\B/2+\B/2-0.4,-\B+0.4) to (\B*\B/2+\B/2+0.4,-\B+0.4) to (\B*\B/2+\B/2+0.4,-\B-0.4) to (\B*\B/2+\B/2-0.4,-\B-0.4); \foreach \i in {-9,-8,-7,-6,-5,-4,-3,-2,-1} \node[empt] at (\i,-\B) {}; \node[gelt] at (0,-\B) {}; \foreach \i in {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22} \node[empt] at (\i,-\B) {}; \node[gelt] at (23,-\B) {}; \foreach \i in {24,25,26,27,28,29} \node[empt] at (\i,-\B) {}; }

Comments.

No comments.