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]
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
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)
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)
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)
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())
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())
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()
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())
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)
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]