Right-Angled Hyperbolic Heptagons

Moduli and an explicit realization, built by subdividing into a pentagon and a hexagon.

This note continues the series of computing moduli spaces of convex right angled hyperbolic polygons to those with seven sides. There’s one labeled below for reference, followed by the main theorem:

TheoremHeptagon Moduli

The moduli space R7\mathcal{R}_7 of right angled heptagons is homeomorphic to R4\RR^4. One choice of coordinates involves a pair of adjacent side lengths, and the lengths of the two sides separated from these by an edge (sides b,c,g,eb,c,g,e in the diagram). In these coordinates the moduli space is represented as {(b,c,g,e)R4sinhbsinhc>1,g>0,e>0}\{(b,c,g,e)\in\RR^4\mid \sinh b\sinh c>1, g>0, e>0\}

That is, the moduli space is determined by selecting a point (b,c)R5(b,c)\in\mathcal{R}_5 in the moduli of right angled pentagons and attaching two arbitrary positive parameters. We then make this theorem effective, and determine the lengths of the remaining three sides in terms of these moduli:

Theorem

Given a point (b,c,g,e)R7(b,c,g,e)\in\mathcal{R}_7 the remaining lengths are as follows (using the auxiliary XX implicitly determined by coshX=sinhbsinhc\cosh X =\sinh b\sinh c) coshf=coshgcoshe+sinhbsinhcsinhgsinhe\cosh f = \frac{\cosh g\cosh e +\sinh b\sinh c}{\sinh g\sinh e} a=asinh(sinhesinhfsinhX)+asinh(coshcsinhX)a = \asinh\left(\sinh e\frac{\sinh f}{\sinh X}\right)+\asinh\left(\frac{\cosh c}{\sinh X}\right) d=asinh(sinhgsinhfsinhX)+asinh(coshbsinhX)d = \asinh\left(\sinh g\frac{\sinh f}{\sinh X}\right)+\asinh\left(\frac{\cosh b}{\sinh X}\right)

Finally, like we’ve done for pentagons and hexagons we provide an explicit realization in the upper half plane:

TheoremHeptagon Realization

Up to isometry, every right angled hyperbolic heptagon with sides a,b,c,d,e,f,ga,b,c,d,e,f,g is bounded by the following seven geodesics, each given by its pair of endpoints in R\overline{\mathbb{R}}:

γa{0, }γb{1, 1}γc{tanhb2, cothb2}γd{ektanhX2, ekcothX2}γe{eaαtanhY2, eaαcothY2}γf{eatanhg2, eacothg2}γg{ea, ea}\begin{aligned} \gamma_a &\longmapsto \{0,\ \infty\}\\[2pt] \gamma_b &\longmapsto \{-1,\ 1\}\\[2pt] \gamma_c &\longmapsto \left\{\tanh\tfrac{b}{2},\ \coth\tfrac{b}{2}\right\}\\[2pt] \gamma_d &\longmapsto \left\{e^{k}\tanh\tfrac{X}{2},\ e^{k}\coth\tfrac{X}{2}\right\}\\[2pt] \gamma_e &\longmapsto \left\{e^{a-\alpha}\tanh\tfrac{Y}{2},\ e^{a-\alpha}\coth\tfrac{Y}{2}\right\}\\[2pt] \gamma_f &\longmapsto \left\{e^{a}\tanh\tfrac{g}{2},\ e^{a}\coth\tfrac{g}{2}\right\}\\[2pt] \gamma_g &\longmapsto \{-e^{a},\ e^{a}\} \end{aligned}

where the auxiliary quantities are

coshX=sinhbsinhc,k=asinhcoshcsinhX,coshY=sinhfsinhg,α=asinhcoshfsinhY\cosh X=\sinh b\,\sinh c,\qquad k=\asinh\frac{\cosh c}{\sinh X}, \qquad \cosh Y=\sinh f\,\sinh g,\qquad \alpha=\asinh\frac{\cosh f}{\sinh Y}

Proof by Subdivision

Begin by drawing the common perpendicular to the sides aa and dd, and label it XX. This divides the heptagon into a pentagon and hexagon, and since (by definition) our dividing line is perpendicular to both, the new shapes are right angled. Thus we can use our understanding of pentagons and hexagons to inductively build an understanding of heptagons.

The pentagon is determined by any pair of adjacent sides, so we may choose b,cb,c as they are full sides of the heptagon itself. In particular, these sides determine XX, which is shared by the right angled hexagon on the other side. Since such hexagons are uniquely determined by alternating triples of sides, the sides g,eg,e together with the already-determined XX fix all other lengths.

Now, all side lengths of the heptagon are fixed, and so it is determined up to isometry (as one can see by starting at a single point and creating edges one by one - there are no choices remaining be made at any step; so the construction is unique.) In our construction the adjacent lengths b,cb,c must construct a pentagon, and by the moduli space of pentagons we know sinhbsinhc>1\sinh b\sinh c>1 Any arbitrary lengths g,e,Xg,e,X determine a unique right angled hexagon, so there are no additional constraints on g,eg,e completing the proof of the theorem.

Getting Explicit

We now try to make this theorem effective: and actually come up with an explicit description of the remaining three side lengths a,d,fa,d,f in terms of b,c,g,eb,c,g,e. The argument will follow our subdivision construction above, but take more care to actually track the lengths of things. As a first step, we know by the trigonometry of pentagons that b,cb,c determine XX via

sinhbsinhc=coshX\sinh b\sinh c = \cosh X

Knowing XX, its opposing side in the hexagon is calculable, using what we know of such hexagons (the hexagon law of cosines):

coshf=coshecoshg+coshXsinhesinhg=coshecoshg+sinhbsinhcsinhesinhg\begin{aligned} \cosh f &= \frac{\cosh e\,\cosh g +\cosh X}{\sinh e\,\sinh g}\\[4pt] &= \frac{\cosh e\,\cosh g +\sinh b\,\sinh c}{\sinh e\,\sinh g} \end{aligned}

In the calculation of the remaining sides, we thus take ff as known, and freely use it in our expressions, along with b,c,e,gb,c,e,g. (Though we did not here, we will also do the same with XX). The calculations of aa and dd are symmetric, so we proceed to just do aa, and at the end give the analogous formula for dd.

Label the two sub-segments of aa as h,kh,k, and the sub segments of dd by u,vu,v. Then kk is directly determined by the trigonometry of hyperbolic pentagons:

sinhksinhX=coshc    k=asinh(coshcsinhX)\sinh k\sinh X =\cosh c\implies k = \asinh\left(\frac{\cosh c}{\sinh X}\right)

We determine hh from the fact that it lies in the upper hexagon, using the hyperbolic law of sines for right angled hexagons:

sinhhsinhe=sinhfsinhX    h=asinh(sinhesinhfsinhX)\frac{\sinh h}{\sinh e}=\frac{\sinh f}{\sinh X}\implies h = \asinh\left(\frac{\sinh e\sinh f}{\sinh X}\right)

While rather messy, we now have a direct formula for a=h+ka = h+k:

a=asinh(sinhesinhfsinhX)+asinh(coshcsinhX)a = \asinh\left(\frac{\sinh e\sinh f}{\sinh X}\right)+\asinh\left(\frac{\cosh c}{\sinh X}\right)

Following the same for dd yields

d=asinh(sinhgsinhfsinhX)+asinh(coshbsinhX)d = \asinh\left(\frac{\sinh g\sinh f}{\sinh X}\right)+\asinh\left(\frac{\cosh b}{\sinh X}\right)

Realization

We follow a very similar strategy here as for hexagons and pentagons, so describe the process more briefly. Up to isometries, we choose one vertex to be at ii, and align the sides meeting there to be segments of the vertical geodesic and unit circle:

γa{0,}γb{1,1}\gamma_a\mapsto \{0,\infty\}\hspace{1cm}\gamma_b \mapsto \{-1,1\}

The side of length gg meets the vertical side aa at a right angle - thus is represented by a circle of radius eae^a:

γg{ea,ea}\gamma_g \mapsto \{-e^a,e^a\}

The geodesics c,fc,f also have easily calculable endpoints: cc is the image of the vertical geodesic under translation by distance bb along the unit circle, and ff is translation of the vertical geodesic along geodesic gg by distance gg. Exactly as in the hexagon case, this implies:

γc={tanhb2,cothb2}γf={eatanhg2,eacothg2}\gamma_c =\left\{\tanh \frac{b}{2},\coth\frac{b}{2}\right\}\hspace{1cm} \gamma_f=\left\{e^a\tanh\frac{g}{2},e^a\coth\frac{g}{2}\right\}

This leaves only two sides whose geodesics need to be computed: dd and ee.

Beginning with dd, we recall that we have already analyzed the common perpendicular of aa and dd, and we can leverage this knowledge to express dd as the result of translating the vertical geodesic along this perpendicular by distance XX. We know the hyperbolic distance at which this perpendicular intersects the vertical: it’s ‘kk’ units above the vertex at ii. Thus, we can compute the endpoints of dd much as we did for c,fc,f.

γd={ektanhX2,ekcothX2}\gamma_d=\left\{e^k\tanh\frac{X}{2},e^k\coth\frac{X}{2}\right\}

Where kk is determined by the heptagon moduli with the formula derived in the effective argument above: k=asinh(coshcsinhX)k = \asinh\left(\frac{\cosh c}{\sinh X}\right). Finally we turn to the side ee, where we attempt to use the same trick! The difference here is we do not already know the common perpendicular to the vertical side and ee, so we must compute it. Call it YY, and label the distance from the aga-g vertex to YY‘s intersection with the vertical α\alpha.

Then if we know YY and α\alpha we are done: our required translation of the vertical geodesic has endpoints

γe{eaαtanhY2,eaαcothY2}\gamma_e \mapsto \left\{e^{a-\alpha}\tanh\frac{Y}{2},e^{a-\alpha}\coth\frac{Y}{2}\right\}

To determine these two final missing quantities, note that YαgfY\alpha g f determines a right angled pentagon

and the trigonometry of pentagons gives us what we need:

coshY=sinhfsinhg\cosh Y = \sinh f\sinh g sinhα=coshfsinhY\sinh \alpha = \frac{\cosh f}{\sinh Y}

The same heptagon in the disk

The half-plane realization works, but it is untidy in two ways: the seven sides come out at wildly different scales, and getting the last two needed auxiliary quantities — XX, kk, YY, α\alpha — that are not side lengths at all. Carrying everything to the Poincaré disk by the Cayley transform

ψ(w)=iw+1w+i\psi(w)=\frac{iw+1}{w+i}

removes both complaints.

In the disk a geodesic is an arc of a Euclidean circle meeting the boundary at right angles. Such a circle with center CC and radius rr has

C2=1+r2|C|^2=1+r^2

so its center alone determines it, and two of them are perpendicular exactly when C1C2=1C_1\cdot C_2=1. Our vertex at ii went to the middle of the disk, so γa\gamma_a and γb\gamma_b pass through the center: they are diameters, and we take them along the two axes.

From there each side is fixed by its neighbors. γc\gamma_c is perpendicular to the diameter γb\gamma_b, so its center sits on that axis, a distance set by bb; γg\gamma_g likewise on the other axis. For γd\gamma_d and γf\gamma_f, one coordinate comes from a dot product with the neighbor just placed and the other from the side separating it from a diameter.

TheoremHeptagon Realization in the Disk

Naming each geodesic by the center of the Euclidean circle carrying it,

γa, γb : the vertical and horizontal diameters\gamma_a,\ \gamma_b\ :\ \text{the vertical and horizontal diameters}

Cc=(cothb, 0),rc=1sinhbCg=(0, cotha),rg=1sinhaCd=(tanhb, cothccoshb),rd=1sinhccoshbCf=(cothgcosha, tanha),rf=1sinhgcosha\begin{aligned} C_c &= \left(\coth b,\ 0\right), & r_c &= \frac{1}{\sinh b}\\[8pt] C_g &= \left(0,\ \coth a\right), & r_g &= \frac{1}{\sinh a}\\[8pt] C_d &= \left(\tanh b,\ \frac{\coth c}{\cosh b}\right), & r_d &= \frac{1}{\sinh c\,\cosh b}\\[8pt] C_f &= \left(\frac{\coth g}{\cosh a},\ \tanh a\right), & r_f &= \frac{1}{\sinh g\,\cosh a} \end{aligned}

and γe\gamma_e, being perpendicular to both γd\gamma_d and γf\gamma_f, is the solution of the two linear equations

CeCd=1,CeCf=1C_e\cdot C_d=1,\qquad C_e\cdot C_f=1

That last line is the real gain. In the half plane the side ee was the awkward one: it needed its own common perpendicular YY, its own offset α\alpha, and a fresh pentagon to compute them. Here it is a 2×22\times2 solve, and XX, kk, YY and α\alpha have all disappeared — every center above is written in the side lengths alone.

In Code

The pentagon and hexagon notes each ended by turning a realization into a shader: a geodesic is the center of a circle, reflecting in it is inversion, and every pixel folds itself back into the fundamental polygon, its tile colored by how many reflections that took.

None of that machinery cares how many sides there are. The heptagon needs only its own list of walls.

Two of the seven sides pass through the center of the disk and are the axes. Four more are read straight off the theorem above. The last, γe\gamma_e, is the only one that takes any work — and in these coordinates being perpendicular to both its neighbors is two linear equations, so it is a 2×22\times2 solve:

float det = C[2].x * C[3].y - C[2].y * C[3].x;
C[4] = vec2(C[3].y - C[2].y, C[2].x - C[3].x) / det; // gamma_e

It is worth seeing what became of the auxiliaries. The half-plane realization needed four quantities that are not side lengths — XX, kk, YY and α\alpha — and YY and α\alpha existed only to place that one awkward side. In the disk, XX and kk survive because they are genuinely how aa is built, while YY and α\alpha are gone entirely.

Here is the whole thing. Swap heptagonSides for the pentagon’s or the hexagon’s, change the three array bounds, and the same program draws those instead.

// A right-angled hyperbolic heptagon and the tiling it generates, in the
// Poincaré disk. Everything but heptagonSides is explained in the pentagon note.
//
// To run on Shadertoy, add these at the top; on the page they are uniforms —
// the four moduli from the sliders, the colors from the site palette.
//
//   const float b = 2.0, c = 1.9, g = 1.45, e = 1.2;
//   const vec3 uBackground = vec3(0.96, 0.95, 0.92);
//   const vec3 uInk  = vec3(0.15, 0.15, 0.16);
//   const vec3 uBlue = vec3(0.20, 0.45, 0.70);
//   const vec3 uGold = vec3(0.80, 0.62, 0.18);

// A geodesic is the center of its circle: orthogonality forces |C|^2 = 1 + r^2.
float radius2(vec2 C){ return dot(C, C) - 1.0; }

vec2 reflectIn(vec2 p, vec2 C){
    vec2 d = p - C;
    return C + radius2(C) * d / dot(d, d);
}

bool inside(vec2 p, vec2 C){
    vec2 d = p - C;
    return dot(d, d) < radius2(C);
}

// sinh of the distance to a geodesic, and to a diameter with unit normal n
float wallDist(vec2 p, vec2 C){
    float r2 = radius2(C);
    vec2 d = p - C;
    return abs(dot(d, d) - r2) / (sqrt(r2) * (1.0 - dot(p, p)));
}

float axisDist(vec2 p, vec2 n){
    return 2.0 * abs(dot(p, n)) / (1.0 - dot(p, p));
}

// gamma_a and gamma_b are the two axes; these are the other five sides.
// The subdivision supplies X and f; gamma_e is the one geodesic perpendicular
// to both of its neighbors, which here is a 2x2 solve.
void heptagonSides(out vec2 C[5]){
    float X = acosh(sinh(b) * sinh(c));
    float f = acosh((cosh(e) * cosh(g) + cosh(X)) / (sinh(e) * sinh(g)));

    float k = asinh(cosh(c) / sinh(X));
    float h = asinh(sinh(e) * sinh(f) / sinh(X));
    float a = h + k;

    C[0] = vec2(1.0 / tanh(b), 0.0);                     // gamma_c
    C[1] = vec2(0.0, 1.0 / tanh(a));                     // gamma_g
    C[2] = vec2(tanh(b), 1.0 / (tanh(c) * cosh(b)));     // gamma_d
    C[3] = vec2(1.0 / (tanh(g) * cosh(a)), tanh(a));     // gamma_f

    float det = C[2].x * C[3].y - C[2].y * C[3].x;
    C[4] = vec2(C[3].y - C[2].y, C[2].x - C[3].x) / det; // gamma_e
}

// Walk a point back into the heptagon, counting reflections. The domain is the
// positive quadrant, outside all five circles.
int fold(inout vec2 p, vec2 C[5]){
    int word = 0;
    for (int step = 0; step < 48; step++){
        bool moved = false;
        if (p.x < 0.0){ p.x = -p.x; word++; moved = true; }
        if (p.y < 0.0){ p.y = -p.y; word++; moved = true; }
        for (int k = 0; k < 5; k++){
            if (inside(p, C[k])){ p = reflectIn(p, C[k]); word++; moved = true; }
        }
        if (!moved) break;
    }
    return word;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord){
    vec2 p = (2.0 * fragCoord - iResolution.xy) / min(iResolution.x, iResolution.y) * 1.04;
    if (dot(p, p) >= 1.0){ fragColor = vec4(uBackground, 1.0); return; }

    // b and c must build a pentagon before anything else exists
    if (sinh(b) * sinh(c) <= 1.0){ fragColor = vec4(uBackground, 1.0); return; }

    vec2 C[5];
    heptagonSides(C);

    int word = fold(p, C);

    float s = min(axisDist(p, vec2(1.0, 0.0)), axisDist(p, vec2(0.0, 1.0)));
    for (int k = 0; k < 5; k++) s = min(s, wallDist(p, C[k]));

    vec3 col = mix(uBackground, (word % 2 == 0) ? uBlue : uGold, 0.30);
    if (word == 0) col = mix(uBackground, uGold, 0.60);
    col = mix(uInk, col, smoothstep(0.0, 2.0 * fwidth(s) + 0.012, s));

    fragColor = vec4(col, 1.0);
}
← All notes