Edge Reflections
[3]:
import copy
import numpy as np
import matplotlib.pyplot as plt
from hypertiling import HyperbolicTiling
from hypertiling.graphics.plot import plot_geodesic, convert_edges_to_arcs
from hypertiling.geodesics import geodesic_arc
In this notebook we demonstrate how reflection across polygon edges can be realized with hypertiling
Poincare disk representation
We demonstrate how any polygon in a tiling can be reflect across one of its edges. This is exactly what is heavily done in the GR kernel family in hypertiling. The steps are
translation
rotation
conjugation
inverse rotation
inverse translation
[4]:
p, q = 5, 4
T = HyperbolicTiling(p, q, 2)
[5]:
k = 1 # select polygon
i = 3 # select edge to be reflected at
steps, titles = [], []
orig = T.get_polygon(k)
vrts = T.get_vertices(k)
titles.append("step 1 \n original polygon")
newpoly = copy.deepcopy(orig)
steps.append(copy.deepcopy(newpoly))
# translate, rotate, conjugate, rotate back, translate back
titles.append("step 2 \n translate vertex to origin")
newpoly.moeb_origin(vrts[i])
steps.append(copy.deepcopy(newpoly))
titles.append("step 3 \n rotate edge on x-axis")
angle = np.angle(newpoly.get_polygon()[(i + 1) % p])
newpoly.moeb_rotate(angle)
steps.append(copy.deepcopy(newpoly))
titles.append("step 4 \n conjugate (mirror on x-axis)")
newpoly._vertices = np.conjugate(newpoly.get_polygon())
steps.append(copy.deepcopy(newpoly))
titles.append("step 5 \n inverse rotation")
newpoly.moeb_rotate(-angle)
steps.append(copy.deepcopy(newpoly))
titles.append("step 6 \n inverse translation")
newpoly.moeb_origin(-vrts[i])
steps.append(copy.deepcopy(newpoly))
Visualize sequence of steps
[6]:
# colors help to identify points: blue, green, yellow, orange, purple
C = np.array([[66, 135, 245], [66, 245, 149], [245, 212, 66], [245, 129, 66], [173, 66, 245]])
midx = [(0,0), (1,0), (2,0), (0,1), (1,1), (2,1)] # multi index helper
fig, axes = plt.subplots(3, 2, sharex=True, sharey=True, dpi=100, figsize=(7, 12))
for i, ps in enumerate(steps):
edges, _ = convert_edges_to_arcs(T, ec="0.6", lw=0.8)
for edge in edges:
axes[midx[i]].add_artist(edge)
k = list(ps.get_vertices())
axes[midx[i]].scatter(np.array(k).real, np.array(k).imag, ec="k", lw=0.6, s=150, c=C/255.0, alpha=1, zorder=3)
k = k + [k[0]]
for j in range(len(k)-1):
arc = geodesic_arc(k[j], k[j+1], ec="k", lw=1.0)
axes[midx[i]].add_artist(arc)
axes[midx[i]].axis("off")
axes[midx[i]].set_aspect('equal')
axes[midx[i]].set_xlim(-0.9, 0.9); axes[midx[i]].set_ylim(-0.9, 0.9)
axes[midx[i]].set_title(titles[i])

Weierstraß (hyperboloid) representation
Analogous transformations can be performed in the hyperboloid representation. This is used in the Dunham algorithm, also implemented in hypertiling.
[7]:
from hypertiling.representations import w2p_xyt, p2w_xyt
from scipy.stats import circmean
[8]:
p, q = 5, 4
T = HyperbolicTiling(p, q, 2)
We gonna need this particular reflection matrix
[9]:
b = np.arccosh(np.cos(np.pi / q) / np.sin(np.pi / p))
ReflectPgonEdge = np.array([[-np.cosh(2 * b), 0, np.sinh(2 * b)],
[0, 1, 0],
[-np.sinh(2 * b), 0, np.cosh(2 * b)]])
Define a number of helpers
[10]:
def p2w(zs):
# Poincare to Weierstrass
return [p2w_xyt(x) for x in zs]
def w2p(xyts):
# Weierstrass to Poincare
return [w2p_xyt(z) for z in xyts]
def trafoW(xyts, trafo):
# Apply Weierstrass transformation matrix
return [trafo@k for k in xyts]
def rotationW(phi):
# return Weierstrass rotation matrix
return np.array([[np.cos(phi), -np.sin(phi), 0], [np.sin(phi), np.cos(phi), 0], [0, 0, 1]])
Extract fundamental polygon and convert to Weierstrass representation
[11]:
fund = p2w(T.get_vertices(0))
Build reflection transformation
[12]:
# select edge
i = 4
j = int((i+1) % p)
# calculate angle of mean point of this edge
phi1 = np.angle(complex(fund[i][0], fund[i][1]))
phi2 = np.angle(complex(fund[j][0], fund[j][1]))
phi = circmean([phi1,phi2])
[13]:
# perform a series of transformation
# goal: reflect fundamental poly across selected edge
titles = []
titles.append("step 1 \n fundamental polygon")
step1 = fund
titles.append("step 2 \n rotate such that edge is parallel to y-axis")
step2 = trafoW(step1, rotationW(phi))
titles.append("step 3 \n reflect on this edge")
step3 = trafoW(step2, ReflectPgonEdge)
titles.append("step 4 \n rotate back")
step4 = trafoW(step3, rotationW(-phi))
Visualize sequence of steps
[14]:
# select which steps are to be plotted
steps = [step1, step2, step3, step4]
# colors help to identify points: blue, green, yellow, orange, purple
C = np.array([[66, 135, 245], [66, 245, 149], [245, 212, 66], [245, 129, 66], [173, 66, 245]])
midx = [(0,0), (0,1), (1,0), (1,1)] # multi index helper
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True, dpi=100, figsize=(8, 8))
for i, ps in enumerate(steps):
k = w2p(ps) # transform back to Poincare
for j in range(len(T)):
vs = T.get_vertices(j)
vs = np.append(vs, vs[0])
axes[midx[i]].plot(vs.real, vs.imag, c="0.75", lw=0.7, zorder=1)
axes[midx[i]].scatter(np.array(k).real, np.array(k).imag, ec="k", lw=0.6, s=200, c=C/255.0, alpha=1, zorder=3)
axes[midx[i]].plot(np.array(k+[k[0]]).real, np.array(k+[k[0]]).imag, c="k", lw=1, zorder=2)
axes[midx[i]].axis("off")
axes[midx[i]].set_aspect('equal')
axes[midx[i]].set_xlim(-1, 1); axes[midx[i]].set_ylim(-1, 1)
axes[midx[i]].set_title(titles[i])
