diff --git a/posts/pentagons/3/index.qmd b/posts/pentagons/3/index.qmd
index dd24fdb..c1e6993 100644
--- a/posts/pentagons/3/index.qmd
+++ b/posts/pentagons/3/index.qmd
@@ -42,6 +42,40 @@ crossref:
}
+```{python}
+#| echo: false
+from dataclasses import dataclass
+import itertools
+from typing import Callable, TypeAlias
+
+from IPython.display import Markdown
+import sympy
+from tabulate import tabulate
+
+@dataclass
+class PolyData:
+ hexagon_count: int
+ vertex_count: int
+ edge_count: int
+
+ @classmethod
+ def from_hexagons(cls, hexagon_count: int):
+ return cls(
+ hexagon_count=hexagon_count,
+ vertex_count=2*hexagon_count + 20,
+ edge_count=3*hexagon_count + 30,
+ )
+
+
+@dataclass
+class GoldbergData:
+ parameter: tuple[int, int]
+ conway: str
+
+ parameter_link: str | None = None
+ conway_recipe: str | None = None
+```
+
This is the third part in an investigation into answering the following question:
@@ -63,7 +97,7 @@ This prompts the question of how to calculate the vertex, edge, and face counts
without acquiring them from the output of such a program.
Each of the simple GC operators (*c*, *dk*, and *w*) has a combinatoric matrix form
- when applied to a vector of the vertex, edge, and face counts of a figure *S*.
+ which can be applied to a vector of the vertex, edge, and face counts of a figure *S*.
$$
\begin{gather*}
@@ -120,7 +154,7 @@ It's worth noting again that *dk* alternates between producing Class I and Class
*tk* is the square of the *dk* operator.
Unlike it, it preserves solution class.
Powers of a matrix are more readily expressed when the matrix is diagonalized.
-We can diagonalize each of these operators to notice something they have in common.
+Diagonalizing each of these operators shows that they have something in common.
$$
\begin{align*}
@@ -208,7 +242,7 @@ This means that composition of these operators only modifies the diagonal matrix
Some of these eigenvectors have special interpretations:
- The left eigenvector (1, -1, 1) (right matrix, middle row) always has eigenvalue 1.
- - The operation does not change the Euler characteristic.
+ - This means that the operation does not change the Euler characteristic.
- The left eigenvector (3, -2, 0) (right matrix, top row), when applied to general polyhedra
corresponds to the edges and vertices added by the operation:
- In *dk*, this vector has eigenvalue 0, forcing $3V = 2E$, i.e., all vertices to have degree 3.
@@ -233,7 +267,7 @@ We know that integers of this form are never congruent to 2 (mod 3).
Conveniently, this matches with the upper-left eigenvalue.
Assuming for the sake of argument that this is true, it implies something interesting:
- the GC operators produced from *T* are closed under multiplication.
+ **the GC operators produced from *T* are (combinatorially) closed under composition**.
This captures all powers of a given *T*, as well as products between it and other possible *T*s.
However, this combinatorial view misses some of the picture, since some feature counts
can be shared between two different classes at once.
@@ -244,7 +278,7 @@ For example, [*ww*](https://levskaya.github.io/polyhedronisme/?recipe=K30wwD)
[*ww'*](https://levskaya.github.io/polyhedronisme/?recipe=K30wrwD)
= $(2, 1) \circ (1, 2) = (7, 0)$.
-Ignoring this, it means we can characterize the possible hexagon counts given a certain *T*:
+This doesn't affect hexagon counts, so it means we can characterize the possible numbers given a certain *T*:
$$
\begin{align*}
@@ -365,18 +399,72 @@ It inherits the dihedral symmetry of degree 3 from the vertices of the dodecahed
](./truncated gyroelongated bipyramid pole.png)
:::
-:::: {.column}
+```{python}
+#| echo: false
-| Conway | $F_6$ | V | E |
-|---------|----------|-----|-----|
-| t5C = B | 6 | 32 | 48 |
-| dkB | 38 | 96 | 144 |
-| cB | 54 | 128 | 192 |
-| wB | 102 | 224 | 336 |
-| tkB | 134 | 288 | 432 |
-| $g_T B$ | 16T - 10 | 32T | 48T |
+t_param = sympy.symbols("T")
+norm = lambda a, b: a**2 + a*b + b**2
-:::
+@dataclass
+class GCOperator:
+ name: str
+ parameter: int | sympy.Symbol
+
+gc_operators = [
+ GCOperator("dk", norm(1, 1)),
+ GCOperator("c", norm(2, 0)),
+ GCOperator("w", norm(2, 1)),
+ GCOperator("tk", norm(3, 0)),
+ GCOperator("g_T", t_param),
+]
+
+def polyhedronisme(recipe: str) -> str:
+ return f"https://levskaya.github.io/polyhedronisme/?recipe={recipe}"
+
+as_maybe_tex = lambda x: x if isinstance(x, int) or not x.has_free(t_param) else f"${sympy.latex(x)}$"
+as_maybe_recipe_link = lambda x, recipe: f"[{x}]({polyhedronisme(recipe)})" if recipe is not None else x
+
+def mini_conway_table(
+ base_case_hexagons: int,
+ base_case_name: str,
+ recipes: dict[str, str] = {},
+) -> list[tuple[str, PolyData]]:
+ return [
+ (
+ as_maybe_recipe_link(f"${base_case_name}$", recipes.get("")),
+ PolyData.from_hexagons(base_case_hexagons),
+ )
+ ] + [
+ (
+ as_maybe_recipe_link(
+ f"${operator.name + base_case_name.split(' ')[-1]}$",
+ recipes.get(operator.name),
+ ),
+ PolyData.from_hexagons((
+ t_param * (base_case_hexagons + 10) - 10
+ ).expand().subs(t_param, operator.parameter)),
+ )
+ for operator in gc_operators
+ ]
+
+def display_mini_conway_table(
+ base_case_hexagons: int,
+ base_case_name: str,
+ recipes: dict[str, str] = {},
+) -> Markdown:
+ return Markdown(tabulate(
+ [[
+ conway,
+ as_maybe_tex(polyhedron.hexagon_count),
+ as_maybe_tex(polyhedron.vertex_count),
+ as_maybe_tex(polyhedron.edge_count),
+ ] for conway, polyhedron in mini_conway_table(base_case_hexagons, base_case_name, recipes)],
+ headers=["Conway", "$F_6$", "V", "E"],
+ numalign="left",
+ ))
+
+display_mini_conway_table(6, "F")
+```
::::
@@ -415,20 +503,13 @@ The symmetry inherited by this figure is also dihedral of degree 3, but centered
One-third of the figure, showing a group of four pentagons.
Pentagons in green, band hexagons in red, cap hexagons in blue.
](./frustum_third.png){.column .slim-column}
-](./frustum_third.png){target="_blank"}
+](./frustum_third.png){target="_blank_"}
-::: {.column}
+```{python}
+#| echo: false
-| Conway | $F_6$ | V | E |
-|---------|----------|-----|-----|
-| F | 8 | 36 | 54 |
-| dkF | 44 | 108 | 162 |
-| cF | 62 | 144 | 216 |
-| wF | 116 | 252 | 378 |
-| tkF | 152 | 324 | 486 |
-| $g_T F$ | 18T - 10 | 36T | 54T |
-
-:::
+display_mini_conway_table(8, "F")
+```
::::
@@ -485,18 +566,21 @@ This is a symmetry beyond that of the icosahedron, but exists due to the symmetr
Base case: truncated hexagonal trapezohedron
](./polyhedronisme-K300t6dA6.png)
-::: {.column}
+```{python}
+#| echo: false
-Conway | $F_6$ | V | E
--------------------------------------------------------------------------------|----------|-----|-----
-[$t_6dA_6 = T_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300t6dA6) | 2 | 24 | 36
-[$dkT_6$](https://levskaya.github.io/polyhedronisme/?recipe=dkK300t6dA6) | 26 | 72 | 108
-[$cT_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300cK30t6dA6) | 38 | 96 | 144
-[$wT_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300wK30t6dA6) | 86 | 168 | 252
-[$tkT_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300tkK30t6dA6) | 98 | 216 | 324
-$g_T T_6$ | 12T - 10 | 24T | 36T
-
-:::
+display_mini_conway_table(
+ 2,
+ "t_6dA_6 = T_6",
+ recipes = {
+ "": "K300t6dA6",
+ "dk": "dkK300t6dA6",
+ "c": "K300cK30t6dA6",
+ "w": "K300wK30t6dA6",
+ "tk": "K300tkK30t6dA6"
+ }
+)
+```
::::
@@ -509,7 +593,7 @@ Organizing the pentagons into two pairs of 6 is similar to the earlier case with
but with a different arrangement of pentagons.
The resultant figure has dihedral symmetry of degree 5.
-:::: {layout-ncol="2"}
+:::: {layout-ncol="2" layout-valign="center"}
::: {.column .slim-column layout="[[1,1],[1]]"}

:::
-::: {.column}
+```{python}
+#| echo: false
-| Conway | $F_6$ | V | E |
-|-----------------|----------|-----|-----|
-| $D_p$ | 5 | 30 | 45 |
-| $dkD_p$ | 35 | 90 | 135 |
-| $cD_p$ | 50 | 120 | 180 |
-| $wD_p$ | 95 | 210 | 315 |
-| $tkD_p$ | 125 | 270 | 405 |
-| $g_T D_p$ | 15T - 10 | 30T | 45T |
-
-:::
+display_mini_conway_table(5, "D_\\pi")
+```
::::
Interestingly, this is also the first solution polyhedron with an odd number of faces.
@@ -555,34 +632,45 @@ Solution polyhedra can be found by examining the pentagonal ($t_5 g Y_5$) and he
The former has dihedral symmetry of degree 5 and the latter has that of degree 6.
:::: {layout-ncol="2" layout-valign="center"}
-::: {.column .slim-column}

+](./polyhedronisme-K300t5K30gY5.png){.column .slim-column}
+
+```{python}
+#| echo: false
+
+display_mini_conway_table(
+ 10,
+ "t_5gY_5 = G_5",
+ recipes = {
+ "": "K300t5K30gY5",
+ "dk": "dkK300t5K30gY5",
+ "c": "K300dudt5K30gY5",
+ "w": "K300wK30t5K30gY5",
+ "tk": "tkK300t5K30gY5",
+ },
+)
+```

-:::
+](./polyhedronisme-K300t6K30gY6.png){.column .slim-column}
-::: {.column}
+```{python}
+#| echo: false
-| Conway | $F_6$ | V | E |
-|-----------------------------------------------------------------------------------|----------|-----|-----|
-| [$t_5gY_5 = G_5$](https://levskaya.github.io/polyhedronisme/?recipe=K300t5K30gY5) | 10 | 40 | 60 |
-| [$dkG_5$](https://levskaya.github.io/polyhedronisme/?recipe=dkK300t5K30gY5) | 50 | 120 | 180 |
-| [$cG_5$](https://levskaya.github.io/polyhedronisme/?recipe=K300dudt5K30gY5) | 70 | 160 | 240 |
-| [$wG_5$](https://levskaya.github.io/polyhedronisme/?recipe=K300wK30t5K30gY5) | 130 | 280 | 420 |
-| [$tkG_5$](https://levskaya.github.io/polyhedronisme/?recipe=tkK300t5K30gY5) | 170 | 360 | 540 |
-| $g_T G_5$ | 20T - 10 | 40T | 60T |
-| [$t_6gY_6 = G_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300t6K30gY6) | 14 | 48 | 72 |
-| [$dkG_6$](https://levskaya.github.io/polyhedronisme/?recipe=dkK300t6K30gY6) | 62 | 144 | 216 |
-| [$cG_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300dudK40t6gY6) | 86 | 192 | 288 |
-| [$wG_6$](https://levskaya.github.io/polyhedronisme/?recipe=K300wK30t6K30gY6) | 158 | 336 | 504 |
-| [$tkG_6$](https://levskaya.github.io/polyhedronisme/?recipe=tkK300t6K30gY6) | 206 | 432 | 648 |
-| $g_T G_6$ | 24T - 10 | 48T | 72T |
-
-:::
+display_mini_conway_table(
+ 14,
+ "t_6gY_6 = G_6",
+ recipes = {
+ "": "K300t6K30gY6",
+ "dk": "dkK300t6K30gY6",
+ "c": "K300dudK40t6gY6",
+ "w": "K300wK30t6K30gY6",
+ "tk": "tkK300t6K30gY6",
+ },
+)
+```
::::
The pentagonal case demonstrates something interesting: an appeal to the partition
@@ -629,7 +717,7 @@ In fact, the graph of the whirl operation on the surface of a cube
clearly makes the shape of one of these arrangements (with a half-mat included):
:::: {#fig-tatami-polyhedron}
-::: {layout="[[-2,1,-1,1,-2]]"}
+::: {layout="[[-1,2,-1,2,-1]]"}
@@ -652,23 +740,16 @@ As a 3D figure, it has only three hexagons and degree-3 dihedral symmetry, anoth
:::: {layout-ncol="2" layout-valign="center"}
::: {.column .slim-column}
-
+{.image-wide}
-
+{.image-wide}
:::
-::: {.column}
+```{python}
+#| echo: false
-| Conway | $F_6$ | V | E |
-|---------------|----------|-----|-----|
-| $M$ | 3 | 26 | 39 |
-| $dkM$ | 29 | 78 | 117 |
-| $cM$ | 42 | 104 | 156 |
-| $wM$ | 81 | 182 | 273 |
-| $tkM$ | 107 | 234 | 351 |
-| $g_T M$ | 13T - 10 | 26T | 39T |
-
-:::
+display_mini_conway_table(3, "M")
+```
::::
While *c* and *w* produce counts which are multiples of 3,
@@ -698,7 +779,6 @@ Some of these are shown below:
Truncated icosahedral graph. Note the path of 2x1 mats.
](./truncated_icosahedron_tatami.png)
-

@@ -710,30 +790,165 @@ Final Tabulation
The solutions examined have been collected in the table below.
+```{python}
+#| echo: false
-| Symmetry | Classification | $F_6$ | Example values
-|-------------|--------------------------------------|------------|-----------------------------------------
-| Icosahedral | Dodecahedral Goldberg | $10T - 10$ | 20, 30, 60, 80, 110, 120, 150, 180, 200
-| Tetrahedral | Tetrahedral Goldberg Antitruncations | $2T - 14$ | 4, 10, 12, 18, 24, 28, 36, 40, 42, 48, 58, 60, 64
-| | Edge-preserving | $4((n+1)^2 - 3)$ | 24, 52, 88, 132, 184, 244, 312, 388, 472, 564
-| | Other | $2TT' - 4T' - 10$ | 32, 46, 50, 56, 70, 74, 78, 88, 92, 102
-| | | $(4(n+1)^2 - 2)T - 10$ | 172, 176, 184, 238, 424, 548
-| $Dih_3$ | Rhombohedron | $16T - 10$ | 6, 38, 54, 102, 134, 182, 198
-| | Triangular Frustum | $18T - 10$ | 8, 44, 62, 116, 152, 206, 224, 278, 332
-| | [Tatamihedron](https://en.wikipedia.org/wiki/26-fullerene_graph) | $13T - 10$ | 3, 29, 42, 81, 107, 146, 159, 198, 237, 263, 315
-| $Dih_5$ | Medially-separated Dodecahedron | $15T - 10$ | 5, 35, 50, 95, 125, 170, 185, 230, 275, 305
-| | Truncated Pentagonal Gyropyramid | $20T - 10$ | 10, 50, 70, 130, 170, 230, 250
-| $Dih_6$ | Truncated Hexaagonal Trapezohedron | $12T - 10$ | 2, 26, 38, 74, 98, 134, 146, 182, 218, 242, 290, 314
-| | Truncated Hexaagonal Gyropyramid | $24T - 10$ | 14, 62, 86, 158, 206, 278, 302
+TFilter: TypeAlias = Callable[[int], bool] | None
+n_param, t_param, aux_t_param, temp_param = sympy.symbols("n T T' x")
+
+@dataclass
+class TotalHexagonRow:
+ symmetry_group: str
+ class_name: str
+ f6_expression: sympy.Expr
+ t_filter: TFilter = None
+
+from_base_case = lambda x: (t_param * (x + 10) - 10)
+
+antitruncation = TotalHexagonRow(
+ symmetry_group="Tetrahedral",
+ class_name="Tetrahedral Goldberg Antitruncations",
+ f6_expression=(2*t_param - 14),
+ t_filter=lambda x: x > 7,
+)
+centered_edge = TotalHexagonRow(
+ symmetry_group="Tetrahedral",
+ class_name="Class I Edge-preserving",
+ f6_expression=(4*((n_param + 1)**2 - 3)),
+)
+
+def apply_gc(row: TotalHexagonRow, class_name: str) -> TotalHexagonRow:
+ return TotalHexagonRow(
+ symmetry_group = row.symmetry_group,
+ class_name = class_name,
+ f6_expression = from_base_case(
+ row.f6_expression.subs(t_param, temp_param)
+ ).subs(t_param, aux_t_param).subs(temp_param, t_param),
+ t_filter=row.t_filter,
+ )
+
+full_tabulation_rows = [
+ TotalHexagonRow(
+ symmetry_group="Icosahedral",
+ class_name="Dodecahedral Goldberg",
+ f6_expression=from_base_case(0),
+ ),
+ antitruncation,
+ apply_gc(
+ antitruncation,
+ "GC(Antitruncations)*",
+ ),
+ centered_edge,
+ apply_gc(
+ centered_edge,
+ "GC(Edge-preserves)",
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_3$",
+ class_name="Triangulated Rhombohedron",
+ f6_expression=from_base_case(6),
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_3$",
+ class_name="Triangular Frustum",
+ f6_expression=from_base_case(8),
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_3$",
+ class_name="[Tatamihedron](https://en.wikipedia.org/wiki/26-fullerene_graph)",
+ f6_expression=from_base_case(3),
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_5$",
+ class_name="Medially-separated Dodecahedron",
+ f6_expression=from_base_case(5),
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_5$",
+ class_name="Truncated Pentagonal Gyropyramid",
+ f6_expression=from_base_case(10),
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_6$",
+ class_name="Truncated Hexagonal Trapezohedron",
+ f6_expression=from_base_case(2),
+ ),
+ TotalHexagonRow(
+ symmetry_group="$Dih_6$",
+ class_name="Truncated Hexagonal Gyropyramid",
+ f6_expression=from_base_case(14),
+ ),
+]
+
+def loeschian():
+ """
+ Loeschian numbers -- those of the form a^2 + ab + b^2
+ See https://oeis.org/A003136
+ """
+ for a in itertools.count(1):
+ for b in range(a + 1):
+ yield a*a + a*b + b*b
+
+
+def get_example_counts(expr: sympy.Expr, amount: int, t_filter: TFilter = None):
+ t_params = list(itertools.islice(loeschian(), 2*amount))
+
+ return [
+ count
+ for count, _ in itertools.groupby(sorted([
+ int(
+ expr.subs(
+ aux_t_param, t1
+ ).subs(
+ t_param, t2
+ ).subs(
+ n_param, n
+ ) # type: ignore
+ )
+ for t1 in t_params
+ for t2 in t_params
+ for n in range(1, 2*amount)
+ if t_filter is None or t_filter(t2) and t1 > 1
+ ]))
+ if count > 0
+ ][:amount]
+
+Markdown(tabulate(
+ [
+ [
+ group_name if i == 0 else "",
+ row.class_name,
+ f"${sympy.latex(row.f6_expression)}$",
+ ", ".join(map(str, get_example_counts(row.f6_expression, 10, row.t_filter))),
+ ]
+ for group_name, group in itertools.groupby(
+ full_tabulation_rows,
+ lambda x: x.symmetry_group,
+ )
+ for i, row in enumerate(group)
+ ],
+ headers=["Symmetry", "Classification", "$F_6$", "Example Values"],
+))
+```
T, T' are members of the sequence 1, 3, 4, 7, 9, 12, 13, 16, 19, 21... ([OEIS A003136](http://oeis.org/A003136))
\*: This is the result of a GC operator applied to $2T - 14$, which requires *T* > 7
-The first few possible values for $F_6$ are 2, 3, 4, 5, 6, 8, 10, 12, 14, 18, 20, 24, 26, 28, 29...,
- though there is nothing exhaustive about the examples considered.
-In particular, selective GC operators which preserve certain symmetries are the best candidates
- for producing new solutions.
+Sorting the entries in this table, we get the following possible hexagon counts:
+
+```{python}
+#| echo: false
+
+Markdown(", ".join(list(map(str, sorted(itertools.chain(*[
+ get_example_counts(row.f6_expression, 10, row.t_filter) for row in full_tabulation_rows
+]))))[:15]))
+```
+
+However, keep in mind that this list is *not* exhaustive.
+In particular, it may be possible to construct additional entries by applying
+ selective GC operators which preserve certain symmetries,
+ like in the edge-preserving tetrahedral and deltahedron cases.
Some small naturals which do not appear on the list are 1, 7, 9, 11, and 13.
Without constructing them, I am unsure whether they can exist.