Generative Reflection Combinatorial (GRC) kernel
In this notebook we provide examples of how tilings made with the GRC kernel, introduced in v1.4 can be used
[1]:
from hypertiling import HyperbolicTiling, HyperbolicGraph, TilingKernels, GraphKernels
from hypertiling.graphics.plot import plot_tiling
import matplotlib.cm as cmap
import numpy as np
import time
Initialization
The GRC kernel can be accessed by the HyperbolicTiling and HyperbolicGraph factory pattern. In both cases, the same GRC class object is initalized. The difference between both calls are the default values given to the GRC class for initialization:
In general, GRC takes three different keyword arguments with the corresponding defaults
sector = True: Controlls whether the whole tiling is constructed (False) or only one symmetry sector (True) with generators providing the remaining sectors
tiling = False: Controlls whether cell coordinates are calculated (True)
nbrs = False: Controlls whether the neighborhood relations are calculated (True)
On one hand, advantage of having coordinates is that we can plot the tilings. On the other hand, tilings require more time and memory to be generated.
If GRC is called by the HyperbolicTiling factory pattern, the tiling keyword is adjusted such that tiling=True. Similarily, if GRC is called by the HyperbolicGraph factory pattern, the graph keyword is adjusted such that graph=True.
Please note that in both cases, the other keyword can be manually set to either value. In case of tiling and nbrs being True, the results of HyperbolicTiling and HyperbolicGraph are identical.
[2]:
p, q, n = 5, 4, 14
# Create some tilings
t0 = time.time()
t = HyperbolicTiling(p, q, n, kernel=TilingKernels.GRC) # sector = True, tiling = True, nbrs = False
t1 = time.time()
t = HyperbolicTiling(p, q, n, kernel=TilingKernels.GRC, sector=False) # sector = False, tiling = True, nbrs = False
t2 = time.time()
print("Tilings:")
print(f"\tsector=True, nbrs=False took {t1 - t0}")
print(f"\tsector=False, nbrs=False took {t2 - t1}")
print("=======================================")
# Create some graphs
t0 = time.time()
t = HyperbolicGraph(p, q, n, kernel=GraphKernels.GRC) # sector = True, tiling = True, nbrs = False
t1 = time.time()
t = HyperbolicGraph(p, q, n, kernel=GraphKernels.GRC, sector=False) # sector = False, tiling = True, nbrs = False
t2 = time.time()
print("Graphs:")
print(f"\tsector=True, tiling=False took {t1 - t0}")
print(f"\tsector=False, tiling=False took {t2 - t1}")
print("=======================================")
# if tiling and nbrs are True, HyperbolicTiling and HyperbolicGraph return identical results
# HyperbolicTiling pattern
t0 = time.time()
t = HyperbolicTiling(p, q, n, kernel=TilingKernels.GRC, nbrs=True) # sector = True, tiling = True, nbrs = True
t1 = time.time()
t = HyperbolicTiling(p, q, n, kernel=TilingKernels.GRC, sector=False, nbrs=True) # sector = False, tiling = True, nbrs = True
t2 = time.time()
# HyperbolicGraph pattern
t3 = time.time()
t = HyperbolicGraph(p, q, n, kernel=GraphKernels.GRC, tiling=True) # sector = True, tiling = True, nbrs = False
t4 = time.time()
t = HyperbolicGraph(p, q, n, kernel=GraphKernels.GRC, sector=False, tiling=True) # sector = False, tiling = True, nbrs = False
t5 = time.time()
print("Identical (tiling=True, nbrs=True):")
print("\tsector=True:")
print(f"\t\tHyperbolicTiling: {t1 - t0}")
print(f"\t\tHyperbolicGraph: {t4 - t3}")
print("\tsector=False:")
print(f"\t\tHyperbolicTiling: {t2 - t1}")
print(f"\t\tHyperbolicGraph: {t5 - t4}")
Tilings:
sector=True, nbrs=False took 0.3798403739929199
sector=False, nbrs=False took 1.6771588325500488
=======================================
Graphs:
sector=True, tiling=False took 0.017539024353027344
sector=False, tiling=False took 0.06253170967102051
=======================================
Identical (tiling=True, nbrs=True):
sector=True:
HyperbolicTiling: 0.3804655075073242
HyperbolicGraph: 0.3526735305786133
sector=False:
HyperbolicTiling: 1.660423994064331
HyperbolicGraph: 1.693098545074463
Examples
The hypertiling logo
[3]:
t = HyperbolicTiling(7, 3, 3, kernel=TilingKernels.GRC)
[4]:
ct = np.zeros(len(t))
for i, poly in enumerate(t):
val = np.real(poly[0])-np.imag(poly[0])
ct[i] = np.sign(val)*(np.abs(val))**1.4
[5]:
plot_tiling(t, ct, cmap=cmap.RdYlGn, edgecolor="w", lw=5, clim=[-1,1]);

Tiling as Vector Graphics
[6]:
from hypertiling import HyperbolicTiling
import hypertiling.graphics.svg as svg
[7]:
# generate tiling
tiling = HyperbolicTiling(5, 4, 6, kernel="GRC")
# we color the tiling by layer
colors = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
# query layer information
tiling_colors = [colors[tiling.get_reflection_level(i) % len(colors)] for i in range(len(tiling))]
# create and draw svg image
tiling_svg = svg.make_svg(tiling, tiling_colors, unitcircle=True, cmap="YlOrBr")
svg.draw_svg(tiling_svg)
Voter model
In this simple stochastic process, cells will always take on the color of the majority of their adjacent cells
[8]:
from hypertiling.graphics.plot import plot_tiling
import random
import matplotlib.pyplot as plt
import matplotlib.cm as cmap
Tiling vs Graph
For applications like finding the lowest energy state by quenching its not necessary to have coordinates. In this case, a graph can be used as its construction is faster for large tilings.
However, quenching times are often magnitudes larger than the generation time even in full tiling mode. Nonetheless the advantage of reduced memory requirements holds and can be curical especially for large tilings. In fact, the memory requirement for the coordinates often is a magnitude larger than for the neighbor relations and construction.
Prepare function which computes the energy of a configuration
[9]:
def get_energy(t):
sum_ = 0
for index in range(len(t)):
sum_ += sum([0 if states[nbr] == states[index] else 1 for nbr in t.get_nbrs(index)])
return sum_
Set tiling/graph parameters
[10]:
p, q, n = 8, 3, 10
Tiling
[11]:
t1 = time.time()
t = HyperbolicTiling(p, q, n, kernel="GRC", nbrs=True) # Identical to HyperbolicGraph(p, q, n, kernel="GRC", tiling=True)
print(f"Generation of {len(t)} polygons took {round(time.time() - t1,5)} s")
Generation of 443081 polygons took 0.12208 s
Extract the neighbours
[12]:
t1 = time.time()
nbrs = t.get_nbrs_list()
print(f"Get nbrs took {round(time.time() - t1,5)} s")
Get nbrs took 7.46339 s
Initialize the voter model with random state space
[13]:
states = np.random.randint(0, 2, size=len(t))
plot_tiling(t, states, cmap=cmap.Greys, edgecolor="k", cutoff=0.01, lw=0.7, clim=[0,2]);
print(f"Energy of the state is {get_energy(t):.2e}")
Energy of the state is 1.17e+06

Run the model and display resulting configuration
[14]:
its = 1e5 # number of iterations
for i in range(int(its)):
index = int(len(t) * np.random.random())
sum_ = sum([states[nbr] for nbr in t.get_nbrs(index)])
if sum_ >= p // 2:
states[index] = 1
else:
states[index] = 0
plot_tiling(t, states, cmap=cmap.Greys, edgecolor="k", cutoff=0.01, lw=0.7, clim=[0,2]);
print(f"Energy of the state is {get_energy(t): .2e}")
Energy of the state is 1.07e+06

Graph
When using the graph we are not able to plot the graph as we lack coordinates. However, we can use the spared memory (as we do not calculate the coordinates) to construct the graph in full mode (all sectors). This allows for faster access of neighbors.
[15]:
t1 = time.time()
t = HyperbolicGraph(p, q, n, kernel="GRC", sector=False) # no coordinates
print(f"Generation of {len(t)} polygons took {round(time.time() - t1,5)} s")
Generation of 443081 polygons took 0.03332 s
[16]:
t1 = time.time()
nbrs = t.get_nbrs_list()
print(f"Get nbrs took {round(time.time() - t1,5)} s")
Get nbrs took 1.82204 s
[17]:
states = np.random.randint(0, 2, size=len(t))
print(f"Energy of the state is {get_energy(t): .2e}")
its = 1e5 # number of iterations
for i in range(int(its)):
index = int(len(t) * np.random.random())
sum_ = sum([states[nbr] for nbr in t.get_nbrs(index)])
if sum_ >= p // 2:
states[index] = 1
else:
states[index] = 0
print(f"Energy of the state is {get_energy(t): .2e}")
Energy of the state is 1.17e+06
Energy of the state is 1.07e+06
Further methods
get_reflection_level
The natural defintion of layers of the GR kernel family is different compared to SR and Dunham kernels. Only for tilings/graphs with \(q=3\) the definition coincides. To access the natural layer definition of these kernels, we provide the get_reflection_level
method:
[18]:
from hypertiling import HyperbolicTiling, TilingKernels
import hypertiling.graphics.svg as svg
# some colors for the different layers
colors = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
# For q != 3, the definitons do not match
t = HyperbolicTiling(3, 7, 5, kernel=TilingKernels.GRC)
# plot reflective layers
tiling_colors = [colors[t.get_reflection_level(i) % len(colors)] for i in range(len(t))]
tiling_svg = svg.make_svg(t, tiling_colors, unitcircle=True, cmap="YlOrBr")
svg.draw_svg(tiling_svg)
check_integrity
For GRC, a check_integrity
method is available if nbrs=True. As, even for tilings, the coordinates and cells are generated by a combinatoric algorithm, this method verifies whether the graph structure created by the same process is consistent. This is done in a two step process:
First, the number of nbrs for each cell except the last layer is verified to match p
Second, a bidirectional search is applied around each pair of nbrs, excluding the direct connection. This naturally should result in the paths around the two common vertices. If no path is found in the expected number of steps, the graph is considered corrupted. However, this method is only available up to the (n - (q - 1) // 2)th layer.
[19]:
import hypertiling.ion as ion
# adjust verbosity level to capture function output
ion.set_verbosity_level("Status")
p, q, n = 4, 5, 12
graph = HyperbolicGraph(p, q, n, kernel="GRC")
graph.check_integrity()
[hypertiling] The verbosity level is set to:
1 Warning
>>> 2 Status
3 Debug
4 Develop
[hypertiling] Status: Parameter n is interpreted as number of reflective layers. Compare documentation.
[hypertiling] Status: Integrity can only be ensured for the first n - q // 2 layer and thus layer 9
|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>| Controlling nbrs for 4184 / 4184[hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status:
|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>| Bidirectional search for cell 792 / 792[hypertiling] Status: hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status: [hypertiling] Status:
[hypertiling] Status: Integrity ensured!