Tutorial: Functions on LCAs¶
This is an interactive tutorial written with real code. We start by
setting up \(\LaTeX\) printing, and importing the classes LCA
,
HomLCA
and LCAFunc
.
In [1]:
# Imports from abelian
from abelian import LCA, HomLCA, LCAFunc
# Other imports
import math
import matplotlib.pyplot as plt
from IPython.display import display, Math
def show(arg):
return display(Math(arg.to_latex()))
Initializing a new function¶
There are two ways to create a function \(f: G \to \mathbb{C}\):
- On general LCAs \(G\), the function is represented by an analytical expression.
- If \(G = \mathbb{Z}_{\mathbf{p}}\) with \(p_i \geq 1\) for every \(i\) (\(G\) is a direct sum of discrete groups with finite period), a table of values (multidimensional array) can also be used.
With an analytical representation¶
If the representation of the function is given by an analytical expression, initialization is simple.
Below we define a Gaussian function on \(\mathbb{Z}\), and one on \(T\).
In [2]:
def gaussian(vector_arg, k = 0.1):
return math.exp(-sum(i**2 for i in vector_arg)*k)
# Gaussian function on Z
Z = LCA([0])
gauss_on_Z = LCAFunc(gaussian, domain = Z)
print(gauss_on_Z) # Printing
show(gauss_on_Z) # LaTeX output
# Gaussian function on T
T = LCA([1], [False])
gauss_on_T = LCAFunc(gaussian, domain = T)
show(gauss_on_T) # LaTeX output
LCAFunc on domain [Z]
Notice how the print
built-in and the to_latex()
method will
show human-readable output.
With a table of values¶
Functions on \(\mathbb{Z}_\mathbf{p}\) can be defined using a table of values, if \(p_i \geq 1\) for every \(p_i \in \mathbf{p}\).
In [3]:
# Create a table of values
table_data = [[1,2,3,4,5],
[2,3,4,5,6],
[3,4,5,6,7]]
# Create a domain matching the table
domain = LCA([3, 5])
table_func = LCAFunc(table_data, domain)
show(table_func)
print(table_func([1, 1])) # [1, 1] maps to 3
3
Function evaluation¶
A function \(f \in \mathbb{C}^G\) is callable. To call (i.e. evaluate) a function, pass a group element.
In [4]:
# An element in Z
element = [0]
# Evaluate the function
gauss_on_Z(element)
Out[4]:
1.0
The sample()
method can be used to sample a function on a list of
group elements in the domain.
In [5]:
# Create a list of sample points [-6, ..., 6]
sample_points = [[i] for i in range(-6, 7)]
# Sample the function, returns a list of values
sampled_func = gauss_on_Z.sample(sample_points)
# Plot the result of sampling the function
plt.figure(figsize = (8, 3))
plt.title('Gaussian function on $\mathbb{Z}$')
plt.plot(sample_points, sampled_func, '-o')
plt.grid(True)
plt.show()
Shifts¶
Let \(f: G \to \mathbb{C}\) be a function. The shift operator (or translation operator) \(S_{h}\) is defined as
The shift operator shifts \(f(g)\) by \(h\), where \(h, g \in G\).
The shift operator is implemented as a method called shift
.
In [6]:
# The group element to shift by
shift_by = [3]
# Shift the function
shifted_gauss = gauss_on_Z.shift(shift_by)
# Create sample poits and sample
sample_points = [[i] for i in range(-6, 7)]
sampled1 = gauss_on_Z.sample(sample_points)
sampled2 = shifted_gauss.sample(sample_points)
# Create a plot
plt.figure(figsize = (8, 3))
ttl = 'Gaussians on $\mathbb{Z}$, one is shifted'
plt.title(ttl)
plt.plot(sample_points, sampled1, '-o')
plt.plot(sample_points, sampled2, '-o')
plt.grid(True)
plt.show()
Pullbacks¶
Let \(\phi: G \to H\) be a homomorphism and let \(f:H \to \mathbb{C}\) be a function. The pullback of \(f\) along \(\phi\), denoted \(\phi^*(f)\), is defined as
The pullback “moves” the domain of the function \(f\) to \(G\),
i.e. \(\phi^*(f) : G \to \mathbb{C}\). The pullback is of f
is
calculated using the pullback
method, as shown below.
In [7]:
def linear(arg):
return sum(arg)
# The original function
f = LCAFunc(linear, LCA([10]))
show(f)
# A homomorphism phi
phi = HomLCA([2], target = [10])
show(phi)
# The pullback of f along phi
g = f.pullback(phi)
show(g)
We now sample the functions and plot them.
In [8]:
# Sample the functions and plot them
sample_points = [[i] for i in range(-5, 15)]
f_sampled = f.sample(sample_points)
g_sampled = g.sample(sample_points)
# Plot the original function and the pullback
plt.figure(figsize = (8, 3))
plt.title('Linear functions')
label = '$f \in \mathbb{Z}_{10}$'
plt.plot(sample_points, f_sampled, '-o', label = label)
label = '$g \circ \phi \in \mathbb{Z}$'
plt.plot(sample_points, g_sampled, '-o', label = label)
plt.grid(True)
plt.legend(loc = 'best')
plt.show()
Pushforwards¶
Let \(\phi: G \to H\) be a epimorphism and let \(f:G \to \mathbb{C}\) be a function. The pushforward of \(f\) along \(\phi\), denoted \(\phi_*(f)\), is defined as
The pullback “moves” the domain of the function \(f\) to \(H\), i.e. \(\phi_*(f) : H \to \mathbb{C}\). First a solution is obtained, then we sum over the kernel. Since such a sum may contain an infinite number of terms, we bound it using a norm. Below is an example where we:
- Define a Gaussian \(f(x) = \exp(-kx^2)\) on \(\mathbb{Z}\)
- Use pushforward to “move” it with \(\phi(g) = g \in \operatorname{Hom}(\mathbb{Z}, \mathbb{Z}_{10})\)
In [9]:
# We create a function on Z and plot it
def gaussian(arg, k = 0.05):
"""
A gaussian function.
"""
return math.exp(-sum(i**2 for i in arg)*k)
# Create gaussian on Z, shift it by 5
gauss_on_Z = LCAFunc(gaussian, LCA([0]))
gauss_on_Z = gauss_on_Z.shift([5])
# Sample points and sampled function
s_points = [[i] for i in range(-5, 15)]
f_sampled = gauss_on_Z.sample(s_points)
# Plot it
plt.figure(figsize = (8, 3))
plt.title('A gaussian function on $\mathbb{Z}$')
plt.plot(s_points, f_sampled, '-o')
plt.grid(True)
plt.show()
In [10]:
# Use a pushforward to periodize the function
phi = HomLCA([1], target = [10])
show(phi)
First we do a pushforward with only one term. Not enough terms are present in the sum to capture what the pushforward would look like if the sum went to infinity.
In [11]:
terms = 1
# Pushforward of the function along phi
gauss_on_Z_10 = gauss_on_Z.pushforward(phi, terms)
# Sample the functions and plot them
pushforward_sampled = gauss_on_Z_10.sample(sample_points)
plt.figure(figsize = (8, 3))
label = 'A gaussian function on $\mathbb{Z}$ and \
pushforward to $\mathbb{Z}_{10}$ with few terms in the sum'
plt.title(label)
plt.plot(s_points, f_sampled, '-o', label ='Original')
plt.plot(s_points, pushforward_sampled, '-o', label ='Pushforward')
plt.legend(loc = 'best')
plt.grid(True)
plt.show()
Next we do a pushforward with more terms in the sum, this captures what the pushforward would look like if the sum went to infinity.
In [12]:
terms = 9
gauss_on_Z_10 = gauss_on_Z.pushforward(phi, terms)
# Sample the functions and plot them
pushforward_sampled = gauss_on_Z_10.sample(sample_points)
plt.figure(figsize = (8, 3))
plt.title('A gaussian function on $\mathbb{Z}$ and \
pushforward to $\mathbb{Z}_{10}$ with enough terms')
plt.plot(s_points, f_sampled, '-o', label ='Original')
plt.plot(s_points, pushforward_sampled, '-o', label ='Pushforward')
plt.legend(loc = 'best')
plt.grid(True)
plt.show()