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()))
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)
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)
Roughly speaking, for a HomLCA
instance to represent a homomorphism between FGAs, it must have:
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)
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)
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)
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
There are three ways to stack morphisms:
They are all shown below.
In [8]:
# Create two homomorphisms
phi = HomLCA([2], target = LCA([0], [False]))
psi = HomLCA([2])
# Stack diagonally
show(phi.stack_diag(psi))
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))
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))
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)
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]))
We finish this tutorial by showing two ways to calculate the same thing:
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)