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

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)
../_images/examples_grc-kernel_13_0.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
../_images/examples_grc-kernel_27_1.png

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
../_images/examples_grc-kernel_29_1.png

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_levelmethod:

[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)
../_images/examples_grc-kernel_37_0.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:

  1. First, the number of nbrs for each cell except the last layer is verified to match p

  2. 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!