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]