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]]"} ![ Dodecahedral graph @@ -524,18 +608,11 @@ The resultant figure has dihedral symmetry of degree 5. ](./petrial expanded dodecahedron.png) ::: -::: {.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} ![ Base case 1: truncated pentagonal gyro-pyramid -](./polyhedronisme-K300t5K30gY5.png) +](./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", + }, +) +``` ![ Base case 2: truncated hexagonal gyro-pyramid -](./polyhedronisme-K300t6K30gY6.png) -::: +](./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]]"} Conway wC Tearoom layout @@ -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} -![](./tatami_graph.png) +![](./tatami_graph.png){.image-wide} -![](./regularized_tatami_graph.png) +![](./regularized_tatami_graph.png){.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) - ![ Triacontahedron (not a base case shown above), initially similar to tI. ](./18_hexagon_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.