Tutorial: Homomorphisms

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

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

def show(arg):
    return display(Math(arg.to_latex()))

Initializing a homomorphism

Homomorphisms between general LCAs are represented by the HomLCA class. To define a homomorphism, a matrix representation is needed. In addition to the matrix, the user can also define a target and source explicitly.

Some verification of the inputs is performed by the initializer, for instance a matrix \(A \in \mathbb{Z}^{2 \times 2}\) cannot represent \(\phi: \mathbb{Z}^m \to \mathbb{Z}^n\) unless both \(m\) and \(n\) are \(2\). If no target/source is given, the initializer will assume a free, discrete group, i.e. \(\mathbb{Z}^m\).

In [2]:
from abelian import LCA, HomLCA

# Initialize the target group for the homomorphism
target = LCA([0, 5], discrete = [False, True])

# Initialize a homomorphism between LCAs
phi = HomLCA([[1, 2], [3, 4]], target = target)
show(phi)

# Initialize a homomorphism with no source/target.
# Source and targets are assumed to be
# of infinite order and discrete (free-to-free)
phi = HomLCA([[1, 2], [3, 4]])
show(phi)
$$\begin{pmatrix}1 & 2\\3 & 4\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \to \mathbb{R} \oplus \mathbb{Z}_{5}$$
$$\begin{pmatrix}1 & 2\\3 & 4\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$

Homomorphisms between finitely generated abelian groups (FGAs) are also represented by the HomLCA class.

In [3]:
from abelian import HomLCA
phi = HomLCA([[4, 5], [9, -3]])
show(phi)
$$\begin{pmatrix}4 & 5\\9 & -3\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$

Roughly speaking, for a HomLCA instance to represent a homomorphism between FGAs, it must have:

  • FGAs as source and target.
  • The matrix must contain only integer entries.

Compositions

A fundamental way to combine two functions is to compose them. We create two homomorphisms and compose them: first \(\psi\), then \(\phi\). The result is the function \(\phi \circ \psi\).

In [4]:
# Create two HomLCAs
phi = HomLCA([[4, 5], [9, -3]])
psi = HomLCA([[1, 0, 1], [0, 1, 1]])

# The composition of phi, then psi
show(phi * psi)
$$\begin{pmatrix}4 & 5 & 9\\9 & -3 & 6\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$

If the homomorphism is an endomorphism (same source and target), repeated composition can be done using exponents.

\(\phi^{n} = \phi \circ \phi \circ \dots \circ \phi, \quad n \geq 1\)

In [5]:
show(phi**3)
$$\begin{pmatrix}289 & 290\\522 & -117\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$

Numbers and homomorphisms can be added to homomorphisms, in the same way that numbers and matrices are added to matrices in other software packages.

In [6]:
show(psi)

# Each element in the matrix is multiplied by 2
show(psi + psi)

# Element-wise addition
show(psi + 10)
$$\begin{pmatrix}1 & 0 & 1\\0 & 1 & 1\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$
$$\begin{pmatrix}2 & 0 & 2\\0 & 2 & 2\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$
$$\begin{pmatrix}11 & 10 & 11\\10 & 11 & 11\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$

Slice notation

Slice notation is available. The first slice works on rows (target group) and the second slice works on columns (source group). Notice that in Python, indices start with 0.

In [7]:
A = [[10, 10], [10, 15]]
# Notice how the HomLCA converts a list
# into an LCA, this makes it easier to create HomLCAs
phi = HomLCA(A, target = [20, 20])
phi = phi.project_to_source()

# Slice in different ways
show(phi)
show(phi[0, :]) # First row, all columns
show(phi[:, 0]) # All rows, first column
show(phi[1, 1]) # Second row, second column
$$\begin{pmatrix}10 & 10\\10 & 15\end{pmatrix}:\mathbb{Z}_{2} \oplus \mathbb{Z}_{4} \to \mathbb{Z}_{20} \oplus \mathbb{Z}_{20}$$
$$\begin{pmatrix}10 & 10\end{pmatrix}:\mathbb{Z}_{2} \oplus \mathbb{Z}_{4} \to \mathbb{Z}_{20}$$
$$\begin{pmatrix}10\\10\end{pmatrix}:\mathbb{Z}_{2} \to \mathbb{Z}_{20} \oplus \mathbb{Z}_{20}$$
$$\begin{pmatrix}15\end{pmatrix}:\mathbb{Z}_{4} \to \mathbb{Z}_{20}$$

Stacking homomorphisms

There are three ways to stack morphisms:

  • Diagonal stacking
  • Horizontal stacking
  • Vertical stacking

They are all shown below.

Diagonal stacking

In [8]:
# Create two homomorphisms
phi = HomLCA([2], target = LCA([0], [False]))
psi = HomLCA([2])

# Stack diagonally
show(phi.stack_diag(psi))
$$\begin{pmatrix}2 & 0\\0 & 2\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \to \mathbb{R} \oplus \mathbb{Z}$$

Horizontal stacking

In [9]:
# Create two homomorphisms with the same target
target = LCA([0], [False])
phi = HomLCA([[1, 3]], target = target)
source = LCA([0], [False])
psi = HomLCA([7], target=target, source=source)

# Stack horizontally
show(phi.stack_horiz(psi))
$$\begin{pmatrix}1 & 3 & 7\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \oplus \mathbb{R} \to \mathbb{R}$$

Vertical stacking

In [10]:
# Create two homomorphisms, they have the same source
phi = HomLCA([[1, 2]])
psi = HomLCA([[3, 4]])

# Stack vertically
show(phi.stack_vert(psi))
$$\begin{pmatrix}1 & 2\\3 & 4\end{pmatrix}:\mathbb{Z} \oplus \mathbb{Z} \to \mathbb{Z} \oplus \mathbb{Z}$$

Calling homomorphisms

In Python, a callable is an object which implements a method for function calls. A homomorphism is a callable object, so we can use phi(x) to evaluate x, i.e. send x from the source to the target.

We create a homomorphism.

In [11]:
# Create a homomorphism, specify the target
phi = HomLCA([[2, 0], [0, 4]], [10, 12])
# Find the source group (orders)
phi = phi.project_to_source()
show(phi)
$$\begin{pmatrix}2 & 0\\0 & 4\end{pmatrix}:\mathbb{Z}_{5} \oplus \mathbb{Z}_{3} \to \mathbb{Z}_{10} \oplus \mathbb{Z}_{12}$$

We can now call it. The argument must be in the source group.

In [12]:
# An element in the source, represented as a list
group_element = [1, 1]

# Calling the homomorphism
print(phi(group_element))

# Since [6, 4] = [1, 1] mod [5, 3] (source group)
# the following is equal
print(phi([6, 4]) == phi([1, 1]))
[2, 4]
True

Calling and composing

We finish this tutorial by showing two ways to calculate the same thing:

  • \(y = (\phi \circ \psi)(x)\)
  • \(y = \phi(\psi(x))\)
In [13]:
# Create two HomLCAs
phi = HomLCA([[4, 5], [9, -3]])
psi = HomLCA([[1, 0, 1], [0, 1, 1]])

x = [1, 1, 1]
# Compose, then call
answer1 = (phi * psi)(x)

# Call, then call again
answer2 = phi(psi(x))

# The result is the same
print(answer1 == answer2)
True