Tutorial: LCAs

This is an interactive tutorial written with real code. We start by importing the LCA class and setting up \(\LaTeX\) printing.

In [1]:
from abelian import LCA
from IPython.display import display, Math

def show(arg):
    """This function lets us show LaTeX output."""
    return display(Math(arg.to_latex()))

Initializing a LCA

Initializing a locally compact abelian group (LCA) is simple. Every LCA can be written as a direct sum of groups isomorphic to one of: \(\mathbb{Z}_n\), \(\mathbb{Z}\), \(T = \mathbb{R}/\mathbb{Z}\) or \(\mathbb{R}\). Specifying these groups, we can initialize LCAs. Groups are specified by:

  • Order, where 0 is taken to mean infinite order.
  • Whether or not they are discrete (if not, they are continuous).
In [2]:
# Create the group Z_1 + R + Z_3
G = LCA(orders = [1, 0, 3],
        discrete = [True, False, True])

print(G) # Standard printing
show(G) # LaTeX output
[Z_1, R, Z_3]
$$\mathbb{Z}_{1} \oplus \mathbb{R} \oplus \mathbb{Z}_{3}$$

If no discrete parameter is passed, True is assumed and the LCA initialized will be a finitely generated abelian group (FGA).

In [3]:
# No 'discrete' argument passed,
# so the initializer assumes a discrete group
G = LCA(orders = [5, 11])
show(G)

G.is_FGA() # Check if this group is an FGA
$$\mathbb{Z}_{5} \oplus \mathbb{Z}_{11}$$
Out[3]:
True

Manipulating LCAs

One way to create LCAs is using the direct sum, which “glues” LCAs together.

In [4]:
# Create two groups
# Notice how the argument names can be omitted
G = LCA([5, 11])
H = LCA([7, 0], [True, True])

# Take the direct sum of G and H
# Two ways: explicitly and using the + operator
direct_sum = G.sum(H)
direct_sum = G + H

show(G)
show(H)
show(direct_sum)
$$\mathbb{Z}_{5} \oplus \mathbb{Z}_{11}$$
$$\mathbb{Z}_{7} \oplus \mathbb{Z}$$
$$\mathbb{Z}_{5} \oplus \mathbb{Z}_{11} \oplus \mathbb{Z}_{7} \oplus \mathbb{Z}$$

Python comes with a powerful slice syntax. This can be used to “split up” LCAs. LCAs of lower length can be created by slicing, using the built-in slice notation in Python.

In [5]:
# Return groups 0 to 3 (inclusive, exclusive)
sliced = direct_sum[0:3]
show(sliced)

# Return the last two groups in the LCA
sliced = direct_sum[-2:]
show(sliced)
$$\mathbb{Z}_{5} \oplus \mathbb{Z}_{11} \oplus \mathbb{Z}_{7}$$
$$\mathbb{Z}_{7} \oplus \mathbb{Z}$$

Trivial groups can be removed automatically using remove_trivial. Recall that the trivial group is \(\mathbb{Z}_1\).

In [6]:
# Create a group with several trivial groups
G = LCA([1, 1, 0, 5, 1, 7])
show(G)

# Remove trivial groups
G_no_trivial = G.remove_trivial()
show(G_no_trivial)
$$\mathbb{Z}_{1} \oplus \mathbb{Z}_{1} \oplus \mathbb{Z} \oplus \mathbb{Z}_{5} \oplus \mathbb{Z}_{1} \oplus \mathbb{Z}_{7}$$
$$\mathbb{Z} \oplus \mathbb{Z}_{5} \oplus \mathbb{Z}_{7}$$

Checking if an LCA is a FGA

Recall that a group \(G\) is an FGA if all the groups in the direct sum are discrete.

In [7]:
G = LCA([1, 5], discrete = [False, True])
G.is_FGA()
Out[7]:
False

If \(G\) is an FGA, elements can be generated by max-norm by an efficient algorithm. The algorithm is able to generate approximately 200000 elements per second, but scales exponentially with the free rank of the group.

In [8]:
Z = LCA([0])
for element in (Z**2).elements_by_maxnorm([0, 1]):
    print(element)
[0, 0]
[1, -1]
[-1, -1]
[1, 0]
[-1, 0]
[1, 1]
[-1, 1]
[0, 1]
[0, -1]
In [9]:
Z_5 = LCA([5])
for element in (Z_5**2).elements_by_maxnorm([0, 1]):
    print(element)
[0, 0]
[1, 4]
[4, 4]
[1, 0]
[4, 0]
[1, 1]
[4, 1]
[0, 1]
[0, 4]

Dual groups

The dual() method returns a group isomorphic to the Pontryagin dual.

In [10]:
show(G)
show(G.dual())
$$T \oplus \mathbb{Z}_{5}$$
$$\mathbb{Z} \oplus \mathbb{Z}_{5}$$

Iteration, containment and lengths

LCAs implement the Python iteration protocol, and they subclass the abstract base class (ABC) Sequence. A Sequence is a subclass of Reversible and Collection ABCs. These ABCs force the subclasses that inherit from them to implement certain behaviors, namely:

  • Iteration over the object: this yields the LCAs in the direct sum one-by-one.
  • The G in H statement: this checks whether \(G\) is a contained in \(H\).
  • The len(G) built-in, this check the length of the group.

We now show this behavior with examples.

In [11]:
G = LCA([10, 1, 0, 0], [True, False, True, False])

# Iterate over all subgroups in G
for subgroup in G:
    dual = subgroup.dual()
    print('The dual of', subgroup, 'is', dual)

    # Print if the group is self dual
    if dual == subgroup:
        print('   ->', subgroup, 'is self dual')
The dual of [Z_10] is [Z_10]
   -> [Z_10] is self dual
The dual of [T] is [Z]
The dual of [Z] is [T]
The dual of [R] is [R]
   -> [R] is self dual

Containment

A LCA \(G\) is contained in \(H\) iff there exists an injection \(\phi: G \to H\) such that every source/target of the mapping are isomorphic groups.

In [12]:
# Create two groups
G = LCA([1, 3, 5])
H = LCA([3, 5, 1, 8])

# Two ways, explicitly or using the `in` keyword
print(G.contained_in(H))
print(G in H)
True
True

The length can be computed using the length() method, or the built-in method len. In contrast with rank(), this does not remove trivial groups.

In [13]:
# The length is available with the len built-in function
# Notice that the length is not the same as the rank,
# since the rank will remove trivial subgroups first
G = LCA([1, 3, 5])
show(G)

print(G.length()) # Explicit
print(len(G)) # Using the built-in len function
print(G.rank())

$$\mathbb{Z}_{1} \oplus \mathbb{Z}_{3} \oplus \mathbb{Z}_{5}$$
3
3
2

Ranks and lengths of groups

The rank can be computed by the rank() method.

  • The rank() method removes trivial subgroups.
  • The length() method does not remove trivial subgroups.
In [14]:
G = LCA([1, 5, 7, 0])
show(G)
G.rank()
$$\mathbb{Z}_{1} \oplus \mathbb{Z}_{5} \oplus \mathbb{Z}_{7} \oplus \mathbb{Z}$$
Out[14]:
3

Canonical forms and isomorphic groups

FGAs can be put into a canonical form using the Smith normal form (SNF). Two FGAs are isomorphic iff their canonical form is equal.

In [15]:
G = LCA([1, 3, 3, 5, 8])
show(G)
show(G.canonical())
$$\mathbb{Z}_{1} \oplus \mathbb{Z}_{3} \oplus \mathbb{Z}_{3} \oplus \mathbb{Z}_{5} \oplus \mathbb{Z}_{8}$$
$$\mathbb{Z}_{3} \oplus \mathbb{Z}_{120}$$

The groups \(G = \mathbb{Z}_3 \oplus \mathbb{Z}_4\) and \(H = \mathbb{Z}_{12}\) are isomorphic because they can be put into the same canonical form using the SNF.

In [16]:
G = LCA([3, 4, 0])
H = LCA([12, 0])
G.isomorphic(H)
Out[16]:
True

General LCAs are isomorphic if the FGAs are isomorphic and the remaining groups such as \(\mathbb{R}\) and \(T\) can be obtained with a permutation. We show this by example.

In [17]:
G = LCA([12, 13, 0], [True, True, False])
H = LCA([12 * 13, 0], [True, False])
show(G)
show(H)
G.isomorphic(H)
$$\mathbb{Z}_{12} \oplus \mathbb{Z}_{13} \oplus \mathbb{R}$$
$$\mathbb{Z}_{156} \oplus \mathbb{R}$$
Out[17]:
True

Projecting elements to groups

It is possible to project elements onto groups.

In [18]:
element = [8, 17, 7]
G = LCA([10, 15, 20])
G(element)
Out[18]:
[8, 2, 7]