refactor into package, refactor pentagons.1
This commit is contained in:
parent
e6188cc36a
commit
118b1a2477
0
posts/pentagons/1/goldberg/__init__.py
Normal file
0
posts/pentagons/1/goldberg/__init__.py
Normal file
89
posts/pentagons/1/goldberg/common.py
Normal file
89
posts/pentagons/1/goldberg/common.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
from ast import TypeAlias
|
||||||
|
import itertools
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Iterable, Literal
|
||||||
|
import re
|
||||||
|
|
||||||
|
import sympy
|
||||||
|
|
||||||
|
from goldberg.operators import GoldbergOperator
|
||||||
|
|
||||||
|
t_param = sympy.symbols("T")
|
||||||
|
|
||||||
|
GoldbergClass = Literal["I", "II", "III"]
|
||||||
|
|
||||||
|
def group_dict(xs: Iterable, key=None):
|
||||||
|
"""
|
||||||
|
Group the items of `xs` into a dict using `sorted` and `itertools.groupby`.
|
||||||
|
`key` is passed to both functions.
|
||||||
|
"""
|
||||||
|
return {i: list(j) for i, j in itertools.groupby(sorted(xs, key=key), key=key)}
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
extra: list = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
def link_polyhedronisme(text: str, recipe: str) -> str:
|
||||||
|
return f"[{text}](https://levskaya.github.io/polyhedronisme/?recipe={recipe}){{target=\"_blank\"}}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GCOperator:
|
||||||
|
name: GoldbergOperator
|
||||||
|
matrix: sympy.Matrix
|
||||||
|
parameter: int | sympy.Symbol
|
||||||
|
|
||||||
|
|
||||||
|
def parameter_to_class(parameter: tuple[int, int]) -> GoldbergClass:
|
||||||
|
if min(*parameter) == 0:
|
||||||
|
return "I"
|
||||||
|
elif parameter[0] == parameter[1]:
|
||||||
|
return "II"
|
||||||
|
return "III"
|
||||||
|
|
||||||
|
|
||||||
|
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 seed is not None:
|
||||||
|
conway_notation = " = ".join(
|
||||||
|
operation + "D"
|
||||||
|
for operation in conway_notation.split(" = ")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Latexify and subscript the numbers in u, t, and k
|
||||||
|
return f"${re.sub('([utk])(\\d+)', '\\1_\\2', conway_notation)}$"
|
||||||
|
|
||||||
|
|
||||||
|
def remove_repeats(xs: list[str]) -> list[str]:
|
||||||
|
"""Mutate a list of strings. Repeated entries are replaced with the empty string."""
|
||||||
|
prev = None
|
||||||
|
for i, x in enumerate(xs):
|
||||||
|
if x == prev:
|
||||||
|
xs[i] = ""
|
||||||
|
else:
|
||||||
|
prev = x
|
||||||
|
|
||||||
|
return xs
|
||||||
29
posts/pentagons/1/goldberg/dodecahedral.py
Normal file
29
posts/pentagons/1/goldberg/dodecahedral.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from goldberg.operators import goldberg_parameters_to_operations, verify_recipes
|
||||||
|
|
||||||
|
dodecahedral_goldberg_recipes: dict[tuple[int, int], str] = {
|
||||||
|
# Class I
|
||||||
|
(1, 0): "D",
|
||||||
|
(2, 0): "K300cD",
|
||||||
|
(3, 0): "tkD",
|
||||||
|
(4, 0): "du4I",
|
||||||
|
(5, 0): "du5I",
|
||||||
|
# Class II
|
||||||
|
(1, 1): "tI",
|
||||||
|
(2, 2): "K300tdcD",
|
||||||
|
(3, 3): "tktI",
|
||||||
|
# Class III
|
||||||
|
(2, 1): "K300wD",
|
||||||
|
(3, 1): "*",
|
||||||
|
(3, 2): "*",
|
||||||
|
(4, 1): "K300tdwD",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Assert all the above recipes are correct
|
||||||
|
verify_recipes(
|
||||||
|
{
|
||||||
|
param: goldberg_parameters_to_operations[param] + "D"
|
||||||
|
for param in dodecahedral_recipes.keys()
|
||||||
|
},
|
||||||
|
dodecahedral_recipes,
|
||||||
|
"Dodecahedral recipe does not match!",
|
||||||
|
)
|
||||||
251
posts/pentagons/1/goldberg/operators.py
Normal file
251
posts/pentagons/1/goldberg/operators.py
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import reduce
|
||||||
|
import itertools
|
||||||
|
from operator import matmul, mul
|
||||||
|
from pprint import pformat
|
||||||
|
import re
|
||||||
|
from typing import Iterator, Literal, TypeAlias
|
||||||
|
|
||||||
|
import sympy
|
||||||
|
|
||||||
|
t_param = sympy.symbols("T")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Polyhedron:
|
||||||
|
vertex_count: int
|
||||||
|
edge_count: int
|
||||||
|
face_count: int
|
||||||
|
|
||||||
|
def as_matrix(self) -> sympy.Matrix:
|
||||||
|
return sympy.Matrix([[self.vertex_count, self.edge_count, self.face_count]]).T
|
||||||
|
|
||||||
|
|
||||||
|
def loeschian_norm(a: int, b: int) -> int:
|
||||||
|
"""Euclidean norm with respect to the sixth root of unity"""
|
||||||
|
return a * a + a * b + b * b
|
||||||
|
|
||||||
|
|
||||||
|
def loeschian_numbers() -> Iterator[int]:
|
||||||
|
"""
|
||||||
|
Numbers of the form a^2 + ab + b^2.
|
||||||
|
No guarantees are made that this produces a sorted sequence without repeats.
|
||||||
|
See https://oeis.org/A003136
|
||||||
|
"""
|
||||||
|
for a in itertools.count(1):
|
||||||
|
for b in range(a + 1):
|
||||||
|
yield loeschian_norm(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
GoldbergOperator: TypeAlias = Literal["c", "dk", "tk", "w", "g_T"]
|
||||||
|
|
||||||
|
goldberg_parameters_to_operations = {
|
||||||
|
# Class I
|
||||||
|
(1, 0): "",
|
||||||
|
(2, 0): "dud = c",
|
||||||
|
(3, 0): "du3d = tk",
|
||||||
|
(4, 0): "du4d = cc",
|
||||||
|
(5, 0): "du5d",
|
||||||
|
(6, 0): "duu3d = ctk",
|
||||||
|
# Class II
|
||||||
|
(1, 1): "dk",
|
||||||
|
(2, 2): "dkdud = dkc",
|
||||||
|
(3, 3): "dkdu3d = dktk",
|
||||||
|
(4, 4): "dkduud = dkcc",
|
||||||
|
# Class III
|
||||||
|
(2, 1): "w",
|
||||||
|
(3, 1): "*", # loeschian(3, 1) = 13, which is prime
|
||||||
|
(3, 2): "*", # loeschian(3, 2) = 19, which is prime
|
||||||
|
(4, 1): "wdk",
|
||||||
|
(4, 2): "wc",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reverse-lookup for the above table
|
||||||
|
goldberg_operators_to_parameters = {
|
||||||
|
operation: parameter
|
||||||
|
for parameter in goldberg_parameters_to_operations
|
||||||
|
for operation in goldberg_parameters_to_operations[parameter].split(" = ")
|
||||||
|
if operation != "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Duals of operators appearing in the above table
|
||||||
|
_goldberg_operator_duals = {
|
||||||
|
"k": "t",
|
||||||
|
"c": "u2",
|
||||||
|
"tk": "u3",
|
||||||
|
}
|
||||||
|
# Commutating pairs of operators
|
||||||
|
_goldberg_operator_commutations = {
|
||||||
|
("dk", "c"),
|
||||||
|
("dk", "w"),
|
||||||
|
}
|
||||||
|
# Multiply adjacent subdivisions
|
||||||
|
_gather_subdivisions = lambda formula: re.sub(
|
||||||
|
"(u\\d*){2,}",
|
||||||
|
lambda match: "u"
|
||||||
|
+ str(reduce(mul, [int(i) for i in match.group(0).split("u") if i])),
|
||||||
|
formula,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generalize_recipe(recipe: str | list[str], depth: int = 3) -> set[str]:
|
||||||
|
"""
|
||||||
|
Rewrite a recipe by dualizing, commutating, and gathering operators together
|
||||||
|
"""
|
||||||
|
# get rid of all of the regularization operators and double-dualizations
|
||||||
|
if isinstance(recipe, str):
|
||||||
|
recipe = recipe.split(" = ")
|
||||||
|
|
||||||
|
ret = set(
|
||||||
|
[
|
||||||
|
re.sub(
|
||||||
|
"u(\\D)",
|
||||||
|
"u2\\1",
|
||||||
|
re.sub("([KC]\\d*)", "", rec),
|
||||||
|
).replace("dd", "")
|
||||||
|
for rec in recipe
|
||||||
|
]
|
||||||
|
)
|
||||||
|
prev = ret.copy()
|
||||||
|
for _ in range(depth):
|
||||||
|
new_recipes: set[str] = set(
|
||||||
|
[
|
||||||
|
dualize_polyhedron
|
||||||
|
for op, dual in _goldberg_operator_duals.items()
|
||||||
|
for left_comm, right_comm in _goldberg_operator_commutations
|
||||||
|
for recipe in prev
|
||||||
|
for dualize_operator in [
|
||||||
|
recipe.replace(op, f"d{dual}d").replace("dd", ""),
|
||||||
|
recipe.replace(dual, f"d{op}d").replace("dd", ""),
|
||||||
|
]
|
||||||
|
for commute_operators in set(
|
||||||
|
[
|
||||||
|
dualize_operator,
|
||||||
|
dualize_operator.replace(
|
||||||
|
left_comm + right_comm, right_comm + left_comm
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for subdivide_synonyms in set(
|
||||||
|
[
|
||||||
|
commute_operators,
|
||||||
|
_gather_subdivisions(commute_operators),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# only implementing tetrahedron, dodecahedron, and icosahedron
|
||||||
|
for dualize_polyhedron in set(
|
||||||
|
[
|
||||||
|
subdivide_synonyms,
|
||||||
|
subdivide_synonyms.replace("dT", "T"),
|
||||||
|
subdivide_synonyms.replace("dI", "D"),
|
||||||
|
subdivide_synonyms.replace("dD", "I"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
ret.update(new_recipes)
|
||||||
|
prev = new_recipes
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def verify_recipes(
|
||||||
|
left: dict[tuple[int, int], str], right: dict[tuple[int, int], str], message: str
|
||||||
|
) -> None:
|
||||||
|
"""Ensure all entries in `left` also exist in `right` and agree on at least one generalizationed recipe"""
|
||||||
|
for i, left_recipe in left.items():
|
||||||
|
assert i in right, f"Could not find tuple {i} on the RHS"
|
||||||
|
if left_recipe.find("*") == 0 or right[i].find("*") == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
left_recipes = generalize_recipe(left_recipe)
|
||||||
|
right_recipes = generalize_recipe(right[i])
|
||||||
|
assert (
|
||||||
|
left_recipes & right_recipes
|
||||||
|
), f"{message}\nLeft recipes: {pformat(left_recipes)}, Right recipes: {pformat(right_recipes)}"
|
||||||
|
|
||||||
|
|
||||||
|
def from_same_eigenspace(
|
||||||
|
uneigen: sympy.Matrix, values: list[int | sympy.Symbol]
|
||||||
|
) -> sympy.Matrix:
|
||||||
|
"""Diagonalize `uneigen`, then apply its eigenvectors to `values` as a diagonal matrix."""
|
||||||
|
eigenvectors = uneigen.diagonalize()[0]
|
||||||
|
return eigenvectors @ sympy.diag(*values) @ eigenvectors.inv()
|
||||||
|
|
||||||
|
|
||||||
|
# Matrices applied over column vectors of the form [v, e, f]
|
||||||
|
goldberg_operators: dict[GoldbergOperator, sympy.Matrix] = {
|
||||||
|
"dk": sympy.Matrix(
|
||||||
|
[
|
||||||
|
[0, 2, 0],
|
||||||
|
[0, 3, 0],
|
||||||
|
[1, 0, 1],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"c": sympy.Matrix(
|
||||||
|
[
|
||||||
|
[1, 2, 0],
|
||||||
|
[0, 4, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"w": sympy.Matrix(
|
||||||
|
[
|
||||||
|
[1, 4, 0],
|
||||||
|
[0, 7, 0],
|
||||||
|
[0, 2, 1],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
goldberg_operators["tk"] = goldberg_operators["dk"] @ goldberg_operators["dk"]
|
||||||
|
goldberg_operators["g_T"] = from_same_eigenspace(goldberg_operators["dk"], [t_param % 3, 1, t_param])
|
||||||
|
|
||||||
|
|
||||||
|
def apply_goldberg_operator(
|
||||||
|
operator: GoldbergOperator | tuple[int, int] | str,
|
||||||
|
poly: Polyhedron,
|
||||||
|
) -> Polyhedron:
|
||||||
|
"""Apply a (list of) operator(s) to a polyhedron. Operator can also be given as a parameter."""
|
||||||
|
# Naive combinatorial method
|
||||||
|
if isinstance(operator, tuple):
|
||||||
|
return Polyhedron(
|
||||||
|
*(goldberg_operators["g_T"] @ poly.as_matrix()).subs(t_param, loeschian_norm(*operator))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try lookup parameter
|
||||||
|
if (parameter := goldberg_operators_to_parameters.get(operator)) is not None:
|
||||||
|
return Polyhedron(
|
||||||
|
*(goldberg_operators["g_T"] @ poly.as_matrix()).subs(t_param, loeschian_norm(*parameter))
|
||||||
|
)
|
||||||
|
elif (matrix := goldberg_operators.get(operator)) is not None: # pyright: ignore[reportArgumentType]
|
||||||
|
return Polyhedron(
|
||||||
|
*(matrix @ poly.as_matrix())
|
||||||
|
)
|
||||||
|
|
||||||
|
# Partition the recipe into operators for which we have the matrix
|
||||||
|
partitioned = []
|
||||||
|
for recipe in generalize_recipe(operator):
|
||||||
|
partitioned.clear()
|
||||||
|
bad_recipe = False
|
||||||
|
while recipe != "":
|
||||||
|
for op in goldberg_operators.keys():
|
||||||
|
if recipe.find(op) == 0:
|
||||||
|
partitioned.append(op)
|
||||||
|
recipe = recipe[len(op):]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
bad_recipe = True
|
||||||
|
partitioned.clear()
|
||||||
|
break
|
||||||
|
if not bad_recipe:
|
||||||
|
break
|
||||||
|
|
||||||
|
if partitioned:
|
||||||
|
# Technically you could just pointwise multiply the eigenvalues
|
||||||
|
matrix = reduce(matmul, (goldberg_operators[i] for i in partitioned))
|
||||||
|
return Polyhedron(
|
||||||
|
*(matrix @ poly.as_matrix())
|
||||||
|
)
|
||||||
|
|
||||||
|
raise ValueError("Could not partition operators!")
|
||||||
@ -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>
|
||||||
@ -38,41 +22,21 @@ crossref:
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
from dataclasses import dataclass, asdict
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
from IPython.display import Markdown
|
from IPython.display import Markdown
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
import sympy
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
import goldberg_triangles
|
import goldberg.triangles as goldberg_triangles
|
||||||
|
from goldberg.common import link_polyhedronisme, parameter_to_class, display_conway, remove_repeats
|
||||||
@dataclass
|
from goldberg.operators import (
|
||||||
class PolyData:
|
Polyhedron,
|
||||||
hexagon_count: int
|
goldberg_parameters_to_operations,
|
||||||
vertex_count: int
|
apply_goldberg_operator,
|
||||||
edge_count: int
|
)
|
||||||
|
from goldberg.dodecahedral import dodecahedral_goldberg_recipes
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
def polyhedronisme(recipe: str) -> str:
|
|
||||||
return f"https://levskaya.github.io/polyhedronisme/?recipe={recipe}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Recently I've been trying my hand at a little 3D geometry.
|
Recently I've been trying my hand at a little 3D geometry.
|
||||||
@ -249,7 +213,6 @@ As mentioned above, each sector must transform like a triangle under rotation.
|
|||||||
It therefore makes sense to work on a triangle grid and identify hexagons on it.
|
It therefore makes sense to work on a triangle grid and identify hexagons on it.
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
|
||||||
#| layout-ncol: 2
|
#| layout-ncol: 2
|
||||||
#| fig-cap:
|
#| fig-cap:
|
||||||
#| - "Triangular grid before..."
|
#| - "Triangular grid before..."
|
||||||
@ -306,7 +269,6 @@ Then, we turn to face an adjacent edge, then walk across *b* edges onto another
|
|||||||
|
|
||||||
::: { #fig-goldberg-path }
|
::: { #fig-goldberg-path }
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
|
||||||
#| layout-ncol: 2
|
#| layout-ncol: 2
|
||||||
|
|
||||||
goldberg_triangles.draw_sector((4, 2), path=True)
|
goldberg_triangles.draw_sector((4, 2), path=True)
|
||||||
@ -331,7 +293,6 @@ Geometrically, there are three classes.
|
|||||||
These are points of the form $(a, 0)$ (or symmetrically, $(0, a)$).
|
These are points of the form $(a, 0)$ (or symmetrically, $(0, a)$).
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
|
||||||
#| fig-cap: |
|
#| fig-cap: |
|
||||||
#| Areas corresponding to tuple (4, 0).
|
#| Areas corresponding to tuple (4, 0).
|
||||||
#| This is the only class where we only need to consider pairs.
|
#| This is the only class where we only need to consider pairs.
|
||||||
@ -349,7 +310,6 @@ Coincidentally, $\|(a, 0)\| = a^2$.
|
|||||||
These are points of the form $(a, a)$.
|
These are points of the form $(a, a)$.
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
|
||||||
#| fig-cap: |
|
#| fig-cap: |
|
||||||
#| Areas corresponding to tuple (4, 4).
|
#| Areas corresponding to tuple (4, 4).
|
||||||
|
|
||||||
@ -370,7 +330,6 @@ It should go without saying that this is the most complicated case.
|
|||||||
|
|
||||||
::: { #fig-chiral-triangle }
|
::: { #fig-chiral-triangle }
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
|
||||||
#| layout-ncol: 3
|
#| layout-ncol: 3
|
||||||
|
|
||||||
goldberg_triangles.draw_sector((4, 1), fill_all=True)
|
goldberg_triangles.draw_sector((4, 1), fill_all=True)
|
||||||
@ -562,113 +521,92 @@ Though this list can be found
|
|||||||
I'll duplicate the first few entries here.
|
I'll duplicate the first few entries here.
|
||||||
|
|
||||||
```{python}
|
```{python}
|
||||||
#| echo: false
|
|
||||||
#| label: tbl-goldberg
|
#| label: tbl-goldberg
|
||||||
#| tbl-cap: "\\\\* higher-order whirl needed"
|
#| tbl-cap: "\\\\* higher-order whirl needed <br> $T = a^2 + ab + b^2$"
|
||||||
#| tbl-colwidths: [20, 25, 10, 10, 10, 25]
|
#| tbl-colwidths: [20, 15, 20, 10, 10, 25]
|
||||||
#| classes: plain
|
#| classes: plain
|
||||||
|
|
||||||
goldberg_classes = {
|
@dataclass
|
||||||
"I": [
|
class DodecahedralGoldbergSolution:
|
||||||
GoldbergData(
|
klass: str
|
||||||
parameter=(1, 0),
|
parameter: str
|
||||||
parameter_link="https://en.wikipedia.org/wiki/Regular_dodecahedron",
|
hexagon_count: str
|
||||||
conway="D",
|
edge_count: str
|
||||||
conway_recipe="D",
|
vertex_count: str
|
||||||
),
|
conway: str
|
||||||
GoldbergData(
|
|
||||||
parameter=(2, 0),
|
special_names: dict[tuple[int, int], str] = {
|
||||||
parameter_link="https://en.wikipedia.org/wiki/Chamfered_dodecahedron",
|
# Class I
|
||||||
conway="cD",
|
(1, 0): "https://en.wikipedia.org/wiki/Regular_dodecahedron",
|
||||||
conway_recipe="K300cD",
|
(2, 0): "https://en.wikipedia.org/wiki/Chamfered_dodecahedron",
|
||||||
),
|
# Class II
|
||||||
GoldbergData(
|
(1, 1): "https://en.wikipedia.org/wiki/Truncated_icosahedron",
|
||||||
parameter=(3, 0),
|
|
||||||
conway="tkD = du3I",
|
|
||||||
conway_recipe="tkD",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(4, 0),
|
|
||||||
conway="ccD = du4I",
|
|
||||||
conway_recipe="du4I",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(5, 0),
|
|
||||||
conway="du5I",
|
|
||||||
conway_recipe="du5I",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
"II": [
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(1, 1),
|
|
||||||
parameter_link="https://en.wikipedia.org/wiki/Truncated_icosahedron",
|
|
||||||
conway="tI",
|
|
||||||
conway_recipe="tI",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(2, 2),
|
|
||||||
parameter_link="https://en.wikipedia.org/wiki/Truncated_icosahedron",
|
|
||||||
conway="tdcD",
|
|
||||||
conway_recipe="K300tdcD",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(3, 3),
|
|
||||||
conway="tktI",
|
|
||||||
conway_recipe="tktI",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
"III": [
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(2, 1),
|
|
||||||
conway="wD",
|
|
||||||
conway_recipe="K300wD",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(3, 1),
|
|
||||||
conway="*",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(3, 2),
|
|
||||||
conway="*",
|
|
||||||
),
|
|
||||||
GoldbergData(
|
|
||||||
parameter=(4, 1),
|
|
||||||
conway="dkwD",
|
|
||||||
conway_recipe="K300tdwD",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rows: list[DodecahedralGoldbergSolution] = [
|
||||||
|
# General solution
|
||||||
|
DodecahedralGoldbergSolution(
|
||||||
|
klass="",
|
||||||
|
parameter="(a, b)",
|
||||||
|
hexagon_count=f"${sympy.latex(polyhedron.face_count - 12)}$",
|
||||||
|
edge_count=f"${sympy.latex(polyhedron.edge_count)}$",
|
||||||
|
vertex_count=f"${sympy.latex(polyhedron.vertex_count)}$",
|
||||||
|
conway="",
|
||||||
|
)
|
||||||
|
for polyhedron in (
|
||||||
|
apply_goldberg_operator(
|
||||||
|
"g_T",
|
||||||
|
Polyhedron(face_count=12, edge_count=30, vertex_count=20),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
] + sorted(
|
||||||
|
[
|
||||||
|
# Particular solutions
|
||||||
|
DodecahedralGoldbergSolution(
|
||||||
|
klass=parameter_to_class(parameter),
|
||||||
|
parameter=f"[{parameter}]({special_name})" if special_name is not None else str(parameter),
|
||||||
|
hexagon_count=str(polyhedron.face_count - 12),
|
||||||
|
edge_count=str(polyhedron.edge_count),
|
||||||
|
vertex_count=str(polyhedron.vertex_count),
|
||||||
|
conway=(
|
||||||
|
"*"
|
||||||
|
if recipe == "*"
|
||||||
|
else link_polyhedronisme(
|
||||||
|
display_conway(goldberg_parameters_to_operations[parameter], "D"),
|
||||||
|
recipe,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for parameter, recipe in dodecahedral_goldberg_recipes.items()
|
||||||
|
for polyhedron in (
|
||||||
|
apply_goldberg_operator(
|
||||||
|
parameter,
|
||||||
|
Polyhedron(face_count=12, edge_count=30, vertex_count=20),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for special_name in (special_names.get(parameter),)
|
||||||
|
],
|
||||||
|
key = lambda x: x.klass
|
||||||
|
)
|
||||||
|
|
||||||
def poly_from_dodecahedral_goldberg_parameter(parameter: tuple[int, int]):
|
headers = DodecahedralGoldbergSolution(
|
||||||
a, b = parameter
|
klass="Class",
|
||||||
hexagon_count = 10*(a*a + a*b + b*b - 1)
|
parameter="Parameter",
|
||||||
return PolyData.from_hexagons(hexagon_count)
|
hexagon_count="$F_6$",
|
||||||
|
edge_count="E",
|
||||||
|
vertex_count="V",
|
||||||
def goldberg_table_row(data: GoldbergData) -> list[str]:
|
conway="Conway",
|
||||||
poly_data = poly_from_dodecahedral_goldberg_parameter(data.parameter)
|
)
|
||||||
return [
|
|
||||||
f"[{data.parameter}]({data.parameter_link})"
|
|
||||||
if data.parameter_link is not None
|
|
||||||
else str(data.parameter),
|
|
||||||
str(poly_data.hexagon_count),
|
|
||||||
str(poly_data.vertex_count),
|
|
||||||
str(poly_data.edge_count),
|
|
||||||
f"[{data.conway}]({polyhedronisme(data.conway_recipe)})"
|
|
||||||
if data.conway_recipe is not None
|
|
||||||
else str(data.conway)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
Markdown(tabulate(
|
Markdown(tabulate(
|
||||||
[
|
{
|
||||||
([class_ if i == 0 else ""]) + goldberg_table_row(item)
|
field: [name] + (remove_repeats if field == "klass" else lambda x: x)(
|
||||||
for class_, items in goldberg_classes.items()
|
[getattr(item, field) for item in rows]
|
||||||
for i, item in enumerate(items)
|
)
|
||||||
],
|
for field, name in asdict(headers).items()
|
||||||
headers=["Class", "Parameter", "$F_6$", "V", "E", "Conway"],
|
},
|
||||||
numalign="left",
|
headers="firstrow",
|
||||||
|
numalign="left",
|
||||||
))
|
))
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -678,6 +616,19 @@ Similarly to how the entry (1, 0) is special because the dodecahedron is a Plato
|
|||||||
(1, 1) is special in that it is an [*Archimedean solid*](https://en.wikipedia.org/wiki/Archimedean_solid).
|
(1, 1) is special in that it is an [*Archimedean solid*](https://en.wikipedia.org/wiki/Archimedean_solid).
|
||||||
This means that every vertex has the same configuration: 2 hexagons and 1 pentagon ($V = V^1$).
|
This means that every vertex has the same configuration: 2 hexagons and 1 pentagon ($V = V^1$).
|
||||||
|
|
||||||
|
The Class I and Class II solutions are fairly generalizable.
|
||||||
|
Class I solutions can be achieved dualizing subdivisions.
|
||||||
|
Class II solutions can be generated from Class I solutions by applying an additional *dk*.
|
||||||
|
|
||||||
|
The Class III solutions are not.
|
||||||
|
The whirl operator is fairly special, corresponding directly to the (2, 1) case.
|
||||||
|
The norm of this parameter is 7, which is prime.
|
||||||
|
Similarly, the entries with missing recipes, (3, 1) and (3, 2), have norms of 13 and 19,
|
||||||
|
both of which are prime.
|
||||||
|
Consequently, they do not have operators associated to them.
|
||||||
|
On the other hand, (4, 1) has a norm of 21, which can be factored into 3 and 7 --
|
||||||
|
these correspond to the operators *dk* and *w*, respectively.
|
||||||
|
|
||||||
|
|
||||||
Closing
|
Closing
|
||||||
-------
|
-------
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
../1/goldberg_triangles.py
|
|
||||||
Loading…
x
Reference in New Issue
Block a user