finish revisions to pentagons.3

This commit is contained in:
queue-miscreant 2025-03-19 06:48:53 -05:00
parent a38b9f10df
commit e6188cc36a

View File

@ -42,6 +42,40 @@ crossref:
}
</style>
```{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.<br>
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]]"}
<a title="Tomruen, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Conway_wC.png"><img width="384" alt="Conway wC" src="https://upload.wikimedia.org/wikipedia/commons/f/f4/Conway_wC.png"></a>
<a title="See page for author, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:Tearoom_layout.svg"><img style="background-color: white !important; padding: 5px" width="384" alt="Tearoom layout" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Tearoom_layout.svg/256px-Tearoom_layout.svg.png"></a>
@ -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.