refactor pentagons.2

This commit is contained in:
queue-miscreant 2025-03-23 20:25:31 -05:00
parent 118b1a2477
commit 6caf4640ac
7 changed files with 424 additions and 393 deletions

View File

@ -1,4 +1,3 @@
from ast import TypeAlias
import itertools import itertools
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Iterable, Literal from typing import Iterable, Literal
@ -6,9 +5,7 @@ import re
import sympy import sympy
from goldberg.operators import GoldbergOperator from goldberg.operators import GoldbergOperator, Polyhedron
t_param = sympy.symbols("T")
GoldbergClass = Literal["I", "II", "III"] GoldbergClass = Literal["I", "II", "III"]
@ -20,19 +17,13 @@ def group_dict(xs: Iterable, key=None):
return {i: list(j) for i, j in itertools.groupby(sorted(xs, key=key), key=key)} return {i: list(j) for i, j in itertools.groupby(sorted(xs, key=key), key=key)}
@dataclass def hexagons_to_polyhedron(hexagon_count: int): # | sympy.Expr):
class PolyData: """General Goldberg solution"""
hexagon_count: int return Polyhedron(
vertex_count: int face_count=hexagon_count + 12,
edge_count: int vertex_count=2 * hexagon_count + 20,
edge_count=3 * hexagon_count + 30,
@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 @dataclass
@ -69,7 +60,7 @@ def display_conway(conway_notation: str, seed: str | None = None):
# If a seed is given, split at the equals and apply to both sides # If a seed is given, split at the equals and apply to both sides
if seed is not None: if seed is not None:
conway_notation = " = ".join( conway_notation = " = ".join(
operation + "D" operation + seed
for operation in conway_notation.split(" = ") for operation in conway_notation.split(" = ")
) )

View File

@ -21,9 +21,9 @@ dodecahedral_goldberg_recipes: dict[tuple[int, int], str] = {
# Assert all the above recipes are correct # Assert all the above recipes are correct
verify_recipes( verify_recipes(
{ {
param: goldberg_parameters_to_operations[param] + "D" param: " = ".join([op + "D" for op in goldberg_parameters_to_operations[param].split(" = ")])
for param in dodecahedral_recipes.keys() for param in dodecahedral_goldberg_recipes.keys()
}, },
dodecahedral_recipes, dodecahedral_goldberg_recipes,
"Dodecahedral recipe does not match!", "Dodecahedral recipe does not match!",
) )

View File

@ -98,10 +98,15 @@ def generalize_recipe(recipe: str | list[str], depth: int = 3) -> set[str]:
ret = set( ret = set(
[ [
# Need to run this twice to account for adjacent u's
re.sub( re.sub(
"u(\\D)", "u(\\D)",
"u2\\1", "u2\\1",
re.sub("([KC]\\d*)", "", rec), re.sub(
"u(\\D)",
"u2\\1",
re.sub("([KC]\\d*)", "", rec),
)
).replace("dd", "") ).replace("dd", "")
for rec in recipe for rec in recipe
] ]

View File

@ -0,0 +1,199 @@
from dataclasses import dataclass
from goldberg.common import GoldbergOperator
from goldberg.operators import (
goldberg_operators_to_parameters,
goldberg_parameters_to_operations,
verify_recipes,
)
tetrahedral_goldberg_recipes = {
# Class I
(1, 0): "T",
(2, 0): "K300cT",
(3, 0): "K300tkT",
(4, 0): "K300duC30uT",
(5, 0): "C300du5T",
(6, 0): "K300duK300dtkT",
# Class II
(1, 1): "tT",
(2, 2): "K300duK30dtT",
(3, 3): "K300tktT",
(4, 4): "duK300dK300dudtT",
# Class III
(2, 1): "K300wT",
(3, 1): "*",
(3, 2): "*",
(4, 1): "K300wK30tT",
(4, 2): "K300wK30cT",
}
# Assert all the above recipes are correct
verify_recipes(
{
param: " = ".join(
[op + "T" for op in goldberg_parameters_to_operations[param].split(" = ")]
)
for param in tetrahedral_goldberg_recipes.keys()
},
tetrahedral_goldberg_recipes,
"Tetrahedral recipe does not match!",
)
@dataclass
class HexagonPath:
"""A path along hexagons on a Goldberg polyhedron."""
a: int
b: int
def __hash__(self):
return hash((self.a, self.b))
def __str__(self):
return str(f"{(self.a, self.b)}")
_P = HexagonPath
@dataclass
class WhirledPath:
"""
A path on a tetrahedral solution where leaving an initial pentagon from
the edge not touching another pentagon enters a terminal pentagon through one which is.
In other words, the initial and terminal pentagons are in some sense "rotated"
from one another.
Note that one whirled path signifies two possible paths: (a, b) and (a + b, 0)
"""
a: int
b: int
def __hash__(self):
return hash((self.a, self.b))
def __str__(self):
return str(f"w{(self.a, self.b)}")
_WP = WhirledPath
@dataclass
class TetrahedralAntitruncation:
conway: str # Conway polyhedron notation for constructing the polygon
conway_recipe: str # The recipe for constructing the polyhedron in a viewer, equivalent to `conway` but stabler
intercluster_paths: list[
HexagonPath | WhirledPath
] # Path connecting pentagons of two different clusters
cluster_path: HexagonPath | WhirledPath = _P(
1, 0
) # Path between pentagons in a cluster
_TA = TetrahedralAntitruncation
tetrahedral_goldberg_antitruncations: dict[
tuple[int, int], TetrahedralAntitruncation
] = {
# Class I
(1, 0): _TA("dT", "dT", []),
(2, 0): _TA("C", "C", []),
(3, 0): _TA("t6kT", "K300t6kT", [_P(1, 0)]),
(4, 0): _TA("t6juT", "C300t6juT", [_P(2, 0)]),
(5, 0): _TA("**", "**", [_P(3, 0)]),
(6, 0): _TA("t6kcT", "K300t6kcT", [_P(4, 0)]),
# Class II
(1, 1): _TA("T", "T", []),
(2, 2): _TA("t6uT", "K300t6uT", [_P(1, 1)]),
(3, 3): _TA("t6ktT", "t6ktT", [_P(2, 2)]),
(4, 4): _TA("t6uuT", "K300t6uK30dK30duT", [_P(3, 3)]),
# Class III
(2, 1): _TA("D", "D", [_P(1, 0)]),
(3, 1): _TA("*", "*", []),
(3, 2): _TA("*", "*", []),
(4, 1): _TA("t6gtT", "K300t6gK30tT", [_WP(2, 1)]),
(4, 2): _TA("t6guT = t6gcT", "K300t6gK30cT", [_P(3, 1)]),
}
# Ensure the correctness of all tetrahedral antitruncation recipes
verify_recipes(
{
parameter: data.conway
for parameter, data in tetrahedral_goldberg_antitruncations.items()
},
{
parameter: data.conway_recipe
for parameter, data in tetrahedral_goldberg_antitruncations.items()
},
"Tetrahedral antitruncation recipe does not match!",
)
double_goldberg_operators: list[GoldbergOperator] = ["c", "dk", "tk", "w"]
double_goldberg_paths: dict[
GoldbergOperator, dict[HexagonPath | WhirledPath, list[HexagonPath | WhirledPath]]
] = {
"c": {
_P(1, 0): [_P(2, 0)],
_P(1, 1): [_P(2, 2)],
_WP(2, 1): [_WP(4, 2)], # paths get doubled
},
"dk": {
_P(1, 0): [_P(1, 1)],
_P(1, 1): [_P(3, 0)],
_WP(2, 1): [
_P(4, 1),
_P(3, 3),
], # Whirled path splits into Class II and Class III-types
},
"tk": {
_P(1, 0): [_P(3, 0)],
_P(1, 1): [_P(3, 3)],
_WP(2, 1): [_WP(6, 3)], # paths get tripled
},
"w": {
_P(1, 0): [_P(2, 1)],
_P(1, 1): [_P(4, 1)],
_WP(2, 1): [_P(5, 3), _P(6, 3)], # Whirled path splits into two Class III-types
},
}
def _apply_double(
operation: GoldbergOperator,
) -> dict[tuple[int, int], TetrahedralAntitruncation]:
"""Build data for a double-Goldberg tetrahedral solution"""
return {
entry: TetrahedralAntitruncation(
conway=" = ".join(
[operation + i for i in antitruncation.conway.split(" = ")]
),
conway_recipe=(
"*"
if antitruncation.conway_recipe.find("*") == 0
else operation + antitruncation.conway_recipe
), # TODO: might need to commute whirl with the last regularization operator
intercluster_paths=double_goldberg_paths[operation][
antitruncation.intercluster_paths[0]
],
cluster_path=_P(*goldberg_operators_to_parameters[operation]),
)
for entry in [(3, 0), (2, 2), (4, 1)]
for antitruncation in (tetrahedral_goldberg_antitruncations[entry],)
}
double_goldberg_antitruncations: dict[
GoldbergOperator, dict[tuple[int, int], TetrahedralAntitruncation]
] = {
operator: _apply_double(operator)
for operator in double_goldberg_operators # for some reason, using a literal list causes type errors
}

View File

@ -113,7 +113,7 @@ In this form, the simplest cases of the problem become obvious:
- $n = 5$, and there are only pentagons - $n = 5$, and there are only pentagons
- $V(6 - 5) + F_5(2(5) - 10) = V = 4(5) = 20$ - $V(6 - 5) + F_5(2(5) - 10) = V = 4(5) = 20$
- In this case, all occurrences of $F_n$ above turns out to be 0, - In this case, all occurrences of $F_n$ above turn out to be 0,
since all of the pentagons are counted by $F_5$ since all of the pentagons are counted by $F_5$
- $n = 6$, and the non-pentagons are hexagons - $n = 6$, and the non-pentagons are hexagons
- $V(6 - 6) + F_5(2(6) - 10) = 2F_5 = 4(6) = 24$ - $V(6 - 6) + F_5(2(6) - 10) = 2F_5 = 4(6) = 24$
@ -569,8 +569,8 @@ rows: list[DodecahedralGoldbergSolution] = [
edge_count=str(polyhedron.edge_count), edge_count=str(polyhedron.edge_count),
vertex_count=str(polyhedron.vertex_count), vertex_count=str(polyhedron.vertex_count),
conway=( conway=(
"*" recipe
if recipe == "*" if recipe.find("*") == 0
else link_polyhedronisme( else link_polyhedronisme(
display_conway(goldberg_parameters_to_operations[parameter], "D"), display_conway(goldberg_parameters_to_operations[parameter], "D"),
recipe, recipe,
@ -600,10 +600,10 @@ headers = DodecahedralGoldbergSolution(
Markdown(tabulate( Markdown(tabulate(
{ {
field: [name] + (remove_repeats if field == "klass" else lambda x: x)( field: [name] + (remove_repeats if field == "klass" else lambda x: x)(
[getattr(item, field) for item in rows] [getattr(item, field) for item in rows]
) )
for field, name in asdict(headers).items() for field, name in asdict(headers).items()
}, },
headers="firstrow", headers="firstrow",
numalign="left", numalign="left",

1
posts/pentagons/2/goldberg Symbolic link
View File

@ -0,0 +1 @@
../1/goldberg

View File

@ -12,22 +12,6 @@ categories:
- geometry - geometry
- combinatorics - combinatorics
- symmetry - symmetry
# Get rid of the figure label
crossref:
fig-title: ""
fig-labels: " "
tbl-title: ""
tbl-labels: " "
title-delim: ""
custom:
- key: fig
kind: float
reference-prefix: Figure
space-before-numbering: false
- key: tbl
kind: float
reference-prefix: Table
space-before-numbering: false
--- ---
<style> <style>
@ -42,45 +26,33 @@ figure > p {
</style> </style>
```{python} ```{python}
#| echo: false from dataclasses import dataclass, asdict
from dataclasses import dataclass, field
import itertools
from typing import Literal
from IPython.display import Markdown from IPython.display import Markdown
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np
from tabulate import tabulate from tabulate import tabulate
import goldberg_triangles from goldberg.common import (
GoldbergData,
polyhedronisme = lambda x, y: f"[{x}](https://levskaya.github.io/polyhedronisme/?recipe={y})" GoldbergOperator,
hexagons_to_polyhedron,
@dataclass group_dict,
class PolyData: link_polyhedronisme,
hexagon_count: int parameter_to_class,
vertex_count: int display_conway,
edge_count: int remove_repeats,
)
@classmethod from goldberg.operators import (
def from_hexagons(cls, hexagon_count: int): goldberg_parameters_to_operations,
return cls( goldberg_operators_to_parameters,
hexagon_count=hexagon_count, loeschian_norm,
vertex_count=2*hexagon_count + 20, )
edge_count=3*hexagon_count + 30, from goldberg.tetrahedral import (
) tetrahedral_goldberg_recipes,
tetrahedral_goldberg_antitruncations,
double_goldberg_antitruncations
@dataclass )
class GoldbergData: import goldberg.triangles as goldberg_triangles
parameter: tuple[int, int]
conway: str
parameter_link: str | None = None
conway_recipe: str | None = None
extra: list = field(default_factory=list)
``` ```
This is the second part in an investigation into answering the following question: This is the second part in an investigation into answering the following question:
@ -114,7 +86,7 @@ Recall the triangular plane and hexagonal paths from the previous part
(presented again here for convenience): (presented again here for convenience):
[^1]: [^1]:
<div> ::: {}
The equation we used to obtain the "12 pentagons" figure also applies The equation we used to obtain the "12 pentagons" figure also applies
to the tetrahedron, with some adjustments: to the tetrahedron, with some adjustments:
@ -139,12 +111,10 @@ Recall the triangular plane and hexagonal paths from the previous part
From this, it is discerned that there are 4 vertices ($n = 3$) From this, it is discerned that there are 4 vertices ($n = 3$)
or 4 trianglar faces ($n = 6$), with the rest of the faces being hexagons. or 4 trianglar faces ($n = 6$), with the rest of the faces being hexagons.
</div> :::
::: {.row width="100%"} ::: {.row width="100%"}
```{python} ```{python}
#| echo: false
goldberg_triangles.draw_sector((4, 2), path=True) goldberg_triangles.draw_sector((4, 2), path=True)
plt.show() plt.show()
``` ```
@ -220,141 +190,82 @@ This is because the antitruncations correspond to one of the three Platonic soli
With these exceptional cases taken care of, the table of solution polyhedra of this form is as follows: With these exceptional cases taken care of, the table of solution polyhedra of this form is as follows:
```{python} ```{python}
#| echo: false
#| label: tbl-antitruncation #| label: tbl-antitruncation
#| classes: plain #| classes: plain
#| tbl-cap: \\\* higher-order whirl needed <br> \\\*\\\* Unknown. Possibly nonexistent under standard operators #| tbl-cap: \\\* higher-order whirl needed <br> \\\*\\\* Unknown. Possibly nonexistent under standard operators
count_dodecahedral = lambda a, b: 10*loeschian_norm(a, b) - 10
count_tetrahedral = lambda a, b: 2*loeschian_norm(a, b) - 14
norm = lambda a, b: a**2 + a*b + b**2 @dataclass
count_dodecahedral = lambda a, b: 10*norm(a, b) - 10 class TetrahedralSolution:
count_tetrahedral = lambda a, b: 2*norm(a, b) - 14 klass: str
parameter: str
conway_trunc: str
conway: str
hexagon_count: str
vertex_count: str
edge_count: str
paths: str
tetrahedral_goldberg_classes = { tet_anti_rows = [
"I": [ TetrahedralSolution(
GoldbergData( klass=parameter_to_class(parameter),
parameter=(1, 0), parameter=str(parameter),
extra=[polyhedronisme("T", "T")], conway_trunc=(
conway="dT", truncated_recipe
conway_recipe="dT", if truncated_recipe.find("*") == 0
), else link_polyhedronisme(
GoldbergData( display_conway(goldberg_parameters_to_operations[parameter], "T"),
parameter=(2, 0), truncated_recipe,
extra=[polyhedronisme("cT", "K300cT")], )
conway="C", ),
conway_recipe="C", hexagon_count=str(polyhedron.face_count - 12),
), vertex_count=str(polyhedron.vertex_count),
GoldbergData( edge_count=str(polyhedron.edge_count),
parameter=(3, 0), conway=(
extra=[polyhedronisme("du3T = tkT", "K300tkT")], antitruncation.conway
conway="t6kT", if antitruncation.conway.find("*") == 0
conway_recipe="K300t6kT", else link_polyhedronisme(
), display_conway(antitruncation.conway),
GoldbergData( antitruncation.conway_recipe,
parameter=(4, 0), )
extra=[polyhedronisme("duuT = ccT", "K300duC30uT")], ),
conway="t6juT", paths=(
conway_recipe="C300t6juT", ""
), if not antitruncation.intercluster_paths
GoldbergData( else f"${', '.join(
parameter=(5, 0), map(str, antitruncation.intercluster_paths)
extra=[polyhedronisme("du5T", "K300duC30uT")], )}$"
conway="**", )
), )
GoldbergData( for parameter, antitruncation in tetrahedral_goldberg_antitruncations.items()
parameter=(6, 0), for truncated_recipe in (tetrahedral_goldberg_recipes[parameter].split(" = ")[-1],)
extra=[polyhedronisme("duu3T = ctkT", "K300duK300dtkT")], for polyhedron in (hexagons_to_polyhedron(count_tetrahedral(*parameter)),)
conway="K300", ]
conway_recipe="K300t6kcT",
),
],
"II": [
GoldbergData(
parameter=(1, 1),
extra=[polyhedronisme("tT", "tT")],
conway="T",
conway_recipe="T",
),
GoldbergData(
parameter=(2, 2),
extra=[polyhedronisme("ctT", "K300duK30dtT")],
conway="t6uT",
conway_recipe="K300t6uT",
),
GoldbergData(
parameter=(3, 3),
extra=[polyhedronisme("tktT", "K300tktT")],
conway="t6ktT",
conway_recipe="K30t6ktT",
),
GoldbergData(
parameter=(4, 4),
extra=[polyhedronisme("duuT = cctT", "K300duC30uT")],
conway="t6uuT",
conway_recipe="K300t6uK30dK30duT",
),
],
"III": [
GoldbergData(
parameter=(2, 1),
extra=[polyhedronisme("wT", "K300wT")],
conway="D",
conway_recipe="D",
),
GoldbergData(
parameter=(3, 1),
extra=["*"],
conway="*",
),
GoldbergData(
parameter=(3, 2),
extra=["*"],
conway="*",
),
GoldbergData(
parameter=(4, 1),
extra=[polyhedronisme("wtT", "K300wK30tT")],
conway="t6gtT",
conway_recipe="K300t6gK30tT",
),
GoldbergData(
parameter=(4, 2),
extra=[polyhedronisme("wcT", "K300wK30cT")],
conway="t6guT = t6gcT",
conway_recipe="K300t6gK30cT",
),
],
}
def poly_from_tetrahedral_goldberg_parameter(parameter: tuple[int, int]): tet_anti_headers = TetrahedralSolution(
a, b = parameter klass="Class",
hexagon_count = count_tetrahedral(a, b) parameter="Parameter",
return PolyData.from_hexagons(hexagon_count) conway_trunc="Conway (Trunc)",
hexagon_count="$F_6$",
edge_count="E",
def tetrahedral_goldberg_table_row(data: GoldbergData) -> list[str]: vertex_count="V",
poly_data = poly_from_tetrahedral_goldberg_parameter(data.parameter) conway="Conway",
return [ paths="Paths",
str(data.parameter), )
*map(str, data.extra),
str(poly_data.hexagon_count),
str(poly_data.vertex_count),
str(poly_data.edge_count),
polyhedronisme(data.conway, data.conway_recipe)
if data.conway_recipe is not None
else str(data.conway)
]
Markdown(tabulate( Markdown(tabulate(
[ {
([class_ if i == 0 else ""]) + tetrahedral_goldberg_table_row(item) field: [name] + (remove_repeats if field == "klass" else lambda x: x)(
for class_, items in tetrahedral_goldberg_classes.items() [getattr(item, field) for item in tet_anti_rows]
for i, item in enumerate(items) )
], for field, name in asdict(tet_anti_headers).items()
headers=["Class", "Parameter", "Conway (Trunc)", "$F_6$", "V", "E", "Conway"], },
numalign="left", headers="firstrow",
numalign="left",
)) ))
``` ```
@ -367,6 +278,16 @@ Note also that the links in the above table may have recipes do not match their
Rather, the recipe is equivalent to the simpler string in the table to ensure the viewer Rather, the recipe is equivalent to the simpler string in the table to ensure the viewer
can stably generate the output shape. can stably generate the output shape.
By leaving a pentagon from an edge which does not share a vertex with another pentagon, we can follow
a path on the hexagons to another pentagon.
In the Class I case, this crosses two fewer edges than the parameter,
since the edges of the triangle from the Goldberg operation have been capped.
This is also true in the Class II case, but both terms of the parameter are decreased by 1.
Class III is mostly the same as Class II, except when one of the parameters is 1.
In this case, the path is "whirled" -- it approaches the terminal pentagon from
an edge touching another pentagon.
Also, for a whirled path *w*(*a*, *b*), both paths (*a*, *b*) and (*a + b*, 0) have this property.
The smallest "real" tetrahedral solution has 4 hexagonal faces. The smallest "real" tetrahedral solution has 4 hexagonal faces.
This is the This is the
[(order-6) truncated triakis tetrahedron](https://en.wikipedia.org/wiki/Truncated_triakis_tetrahedron), [(order-6) truncated triakis tetrahedron](https://en.wikipedia.org/wiki/Truncated_triakis_tetrahedron),
@ -405,7 +326,7 @@ In particular, $\|m + mu\|$ is always congruent to 0 (mod 3)
But -2 is congruent to 1, so equality is impossible. But -2 is congruent to 1, so equality is impossible.
[^2]: [^2]:
<div> ::: {}
Consider the equation $a^2 + ab + b^2 = 2$. Consider the equation $a^2 + ab + b^2 = 2$.
Since 0 and 1 are the only squares mod 3, we can build a table out of each possibility: Since 0 and 1 are the only squares mod 3, we can build a table out of each possibility:
@ -417,49 +338,49 @@ But -2 is congruent to 1, so equality is impossible.
| 1 | 1 | $ab \equiv 0$, but neither of *a* or *b* is a multiple of 3 | No | | 1 | 1 | $ab \equiv 0$, but neither of *a* or *b* is a multiple of 3 | No |
On the other hand, if $b \equiv 0$, then the norm is $a^2$, which is either 0 or 1. On the other hand, if $b \equiv 0$, then the norm is $a^2$, which is either 0 or 1.
</div> :::
Only tetrahedral Class III collisions exist, and they must be congruent to the pairs Only tetrahedral Class III collisions exist, and they must be congruent to the pairs
(1, 2), (2, 1), (2, 2), (3, 3), (3, 4), or (4, 3) (mod 5). (1, 2), (2, 1), (2, 2), (3, 3), (3, 4), or (4, 3) (mod 5).
Some of these can be found in the table below. Some of these can be found in the table below.
```{python} ```{python}
#| echo: false
#| classes: plain #| classes: plain
#| tbl-cap: "Red paths denote paths between pentagons in the same \"cluster\".\
#| <br>Blue paths are those between clusters, as from the earlier table."
grouped = lambda x, f: {i: list(j) for i, j in itertools.groupby(sorted(x, key=f), key=f)} # Groupings of dodecahedral and tetrahedral solutions by hexagon counts
dodecahedrals = group_dict(
dodecahedrals = grouped(
[
((a, b), count_dodecahedral(a, b))
for a in range(8)
for b in range(5)
if a > b
],
lambda x: x[1]
)
tetrahedrals = grouped(
[
((m, n), count_tetrahedral(m, n))
for m in range(18)
for n in range(9)
if m > n
],
lambda x: x[1]
)
Markdown(tabulate(
[
[ [
"<br>".join([str(j) for j, _ in dodecahedrals[i]]), ((a, b), count_dodecahedral(a, b))
"<br>".join([str(j) for j, _ in tetrahedrals[i]]), for a in range(8)
i, for b in range(5)
] if a > b
for i in sorted(dodecahedrals.keys()) if tetrahedrals.get(i) is not None ],
], lambda x: x[1]
headers=["Dodecahedral Parameter", "Tetrahedral Parameter", "$F_6$"], )
numalign="left", tetrahedrals = group_dict(
[
((m, n), count_tetrahedral(m, n))
for m in range(18)
for n in range(9)
if m > n
],
lambda x: x[1]
)
# Table of collisions between both groupings
Markdown(tabulate(
[
[
"<br>".join([str(j) for j, _ in dodecahedrals[i]]),
"<br>".join([str(j) for j, _ in tetrahedrals[i]]),
i,
]
for i in sorted(dodecahedrals.keys()) if tetrahedrals.get(i) is not None
],
headers=["Dodecahedral Parameter", "Tetrahedral Parameter", "$F_6$"],
numalign="left",
)) ))
``` ```
@ -613,172 +534,86 @@ Every one of these solution figures, both constructed and otherwise,
Some of the easily-constructible solutions based on Goldberg polyhedra are accumulated in the table below. Some of the easily-constructible solutions based on Goldberg polyhedra are accumulated in the table below.
```{python} ```{python}
#| echo: false
#| label: tbl-double-goldberg #| label: tbl-double-goldberg
#| tbl-colwidths: [20, 15, 10, 10, 10, 35]
#| classes: plain #| classes: plain
double_goldberg: dict[Literal["c", "dk", "tk", "w"], list[GoldbergData]] = { @dataclass
"c": [ class DoubleTetrahedralSolution:
GoldbergData( parameter: str
parameter=(3, 0), conway: str
conway=("ct6kT"), hexagon_count: str
conway_recipe=("K300dudt6kT"), vertex_count: str
extra=[(3, 0), (2, 2), (4, 0)], edge_count: str
), paths: str
GoldbergData(
parameter=(2, 2),
conway=("ct6uT"),
conway_recipe=("K300duK30dt6uT"),
extra=[(2, 2), (2, 0), (4, 2)],
),
GoldbergData(
parameter=(4, 1),
conway=("ct6gtT"),
conway_recipe=("K300duK50dt6gtT"),
extra=[(4, 2), (2, 0), (6, 0)],
),
],
"dk": [
GoldbergData(
parameter=(3, 0),
conway=("dkt6kT"),
conway_recipe=("K300dkt6kT"),
extra=[(1, 1), (2, 2)],
),
GoldbergData(
parameter=(2, 2),
conway=("dkt6uT"),
conway_recipe=("K300dkK30t6uT"),
extra=[(1, 1), (3, 0), (3, 3)],
),
GoldbergData(
parameter=(4, 1),
conway=("dkt6gtT"),
conway_recipe=("K300dkK50t6gtT"),
extra=[(1, 1), (3, 3), (4, 1)],
),
],
"tk": [
GoldbergData(
parameter=(3, 0),
conway=("tkt6kT"),
conway_recipe=("K300tkt6kT"),
extra=[(3, 0), (3, 3)],
),
GoldbergData(
parameter=(2, 2),
conway=("tkt6uT"),
conway_recipe=("K300tkK30t6uT"),
extra=[(3, 0), (3, 3), (6, 0)],
),
GoldbergData(
parameter=(4, 1),
conway=("tkt6gtT"),
conway_recipe=("K300tkK50t6gtT"),
extra=[(3, 0), (6, 3), (9, 0)],
),
],
"w": [
GoldbergData(
parameter=(3, 0),
conway=("wt6kT"),
conway_recipe=("K300wK30t6kT"),
extra=[(2, 1), (4, 1)],
),
GoldbergData(
parameter=(2, 2),
conway=("wt6uT"),
conway_recipe=("K300wK100t6uT"),
extra=[(2, 1), (3, 0), (4, 1)],
),
GoldbergData(
parameter=(4, 1),
conway=("wt6gtT"),
conway_recipe=("K300wK50t6gtT"),
extra=[(2, 1), (5, 3), (6, 3)],
),
],
}
# TODO: add assertions around recipe/conway from earlier table
# matrices applied over column vectors of the form [v, e, f]
goldberg_operators = {
"dk": np.array([
[0, 2, 0],
[0, 3, 0],
[1, 0, 1],
]),
"c": np.array([
[1, 2, 0],
[0, 4, 0],
[0, 1, 1],
]),
"w": np.array([
[1, 4, 0],
[0, 7, 0],
[0, 2, 1],
]),
}
goldberg_operators["tk"] = goldberg_operators["dk"] @ goldberg_operators["dk"]
def apply_goldberg_operator( double_tet_anti_rows = [
poly: PolyData, DoubleTetrahedralSolution(
operator: Literal["c", "dk", "tk", "w"], parameter=f"{operator}{parameter}",
other_face_count: int = 0, hexagon_count=str(polyhedron.face_count - 12),
) -> PolyData: vertex_count=str(polyhedron.vertex_count),
v, e, f = goldberg_operators[operator] @ np.array([ edge_count=str(polyhedron.edge_count),
poly.vertex_count, conway=(
poly.edge_count, double_goldberg.conway
poly.hexagon_count + other_face_count, if double_goldberg.conway.find("*") == 0
]).T else link_polyhedronisme(
return PolyData( display_conway(double_goldberg.conway),
vertex_count=v, double_goldberg.conway_recipe,
edge_count=e, )
hexagon_count=f - other_face_count, ),
) paths=(
f"$\\textcolor{{red}}{{{
double_goldberg.cluster_path
}}}, \\textcolor{{blue}}{{{', '.join(
map(str, double_goldberg.intercluster_paths)
)}}}$"
)
)
for operator, double_goldbergs in double_goldberg_antitruncations.items()
for parameter, double_goldberg in double_goldbergs.items()
for polyhedron in (
hexagons_to_polyhedron(
loeschian_norm(*goldberg_operators_to_parameters[operator])
* (count_tetrahedral(*parameter) + 10) - 10
),
)
]
# XXX: This fails to construct correct data when data.parameter gives a negative number of hexagons!
def tetrahedral_double_goldberg_table_row( double_tet_anti_headers = DoubleTetrahedralSolution(
data: GoldbergData, parameter="(Tetrahedral Goldberg) Parameter",
operator: Literal["c", "dk", "tk", "w"], conway="Conway",
) -> list[str]: hexagon_count="$F_6$",
poly_data = poly_from_tetrahedral_goldberg_parameter(data.parameter) edge_count="E",
doubled = apply_goldberg_operator(poly_data, operator, other_face_count=12) vertex_count="V",
return [ paths="Paths",
operator + str(data.parameter), )
polyhedronisme(data.conway, data.conway_recipe)
if data.conway_recipe is not None
else str(data.conway),
", ".join(map(str, data.extra)),
str(doubled.hexagon_count),
str(doubled.vertex_count),
str(doubled.edge_count),
]
Markdown(tabulate( Markdown(tabulate(
[ {
tetrahedral_double_goldberg_table_row(data, operator) field: [name] + [getattr(item, field) for item in double_tet_anti_rows]
for operator, datas in double_goldberg.items() for field, name in asdict(double_tet_anti_headers).items()
for data in datas },
], headers="firstrow",
headers=["(Tetrahedral Goldberg) Parameter", "Conway", "Paths Between Pairs", "$F_6$", "V", "E"], numalign="left",
numalign="left",
)) ))
``` ```
Note that higher-order chamfers (dual, subdivide, dual) and higher-order whirls are possible.
The same operators can also be applied to the edge-based cases, The same operators can also be applied to the edge-based cases,
but since I have not computationally generated these, I will not attempt to tabulate them. but since I have not computationally generated these, I will not attempt to tabulate them.
Doing so would require that I: Doing so would require that I:
- Formalize the construction of the graph, 1. Formalize the construction of the graph,
preferably by writing a program which can identify identical vertices. preferably by writing a program which can identify identical vertices.
- I did this step manually to sketch the figures - I did this step manually to sketch the figures
- Find good planar and spherical embeddings of the graph.
- (Optionally,) import and display the result in a polyhedron viewer, 2. Find good planar and spherical embeddings of the graph.
preferably one which can apply Conway notation to an arbitrary figure
3. (Optionally,) import and display the result in a polyhedron viewer,
preferably one which can apply Conway notation to an arbitrary figure
Closing Closing
@ -796,9 +631,9 @@ In fact, I made an error while constructing one of the edge-based solutions:
![&#32;](./edge_whirl_net_bad.png){.image-wide} ![&#32;](./edge_whirl_net_bad.png){.image-wide}
The face in the center is actually a horizontal reflection of how it should be. The face in the center has been replaced with its chiral opposite.
You can check this by trying to consistently follow the path (2, 1) -- You can check this by trying to consistently follow the path (2, 1) --
along the center face, you need follow (1, 2) instead. along the center face, you need to follow (1, 2) instead.
As you may be able to guess, not even specifying tetrahedral symmetry is enough As you may be able to guess, not even specifying tetrahedral symmetry is enough
to completely classify every solution. to completely classify every solution.