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
|
||||
- combinatorics
|
||||
- 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>
|
||||
@ -38,41 +22,21 @@ crossref:
|
||||
</style>
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
from matplotlib import pyplot as plt
|
||||
from IPython.display import Markdown
|
||||
from matplotlib import pyplot as plt
|
||||
import sympy
|
||||
from tabulate import tabulate
|
||||
|
||||
import goldberg_triangles
|
||||
|
||||
@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
|
||||
|
||||
|
||||
def polyhedronisme(recipe: str) -> str:
|
||||
return f"https://levskaya.github.io/polyhedronisme/?recipe={recipe}"
|
||||
import goldberg.triangles as goldberg_triangles
|
||||
from goldberg.common import link_polyhedronisme, parameter_to_class, display_conway, remove_repeats
|
||||
from goldberg.operators import (
|
||||
Polyhedron,
|
||||
goldberg_parameters_to_operations,
|
||||
apply_goldberg_operator,
|
||||
)
|
||||
from goldberg.dodecahedral import dodecahedral_goldberg_recipes
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
#| layout-ncol: 2
|
||||
#| fig-cap:
|
||||
#| - "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 }
|
||||
```{python}
|
||||
#| echo: false
|
||||
#| layout-ncol: 2
|
||||
|
||||
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)$).
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
#| fig-cap: |
|
||||
#| Areas corresponding to tuple (4, 0).
|
||||
#| 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)$.
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
#| fig-cap: |
|
||||
#| 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 }
|
||||
```{python}
|
||||
#| echo: false
|
||||
#| layout-ncol: 3
|
||||
|
||||
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.
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
#| label: tbl-goldberg
|
||||
#| tbl-cap: "\\\\* higher-order whirl needed"
|
||||
#| tbl-colwidths: [20, 25, 10, 10, 10, 25]
|
||||
#| tbl-cap: "\\\\* higher-order whirl needed <br> $T = a^2 + ab + b^2$"
|
||||
#| tbl-colwidths: [20, 15, 20, 10, 10, 25]
|
||||
#| classes: plain
|
||||
|
||||
goldberg_classes = {
|
||||
"I": [
|
||||
GoldbergData(
|
||||
parameter=(1, 0),
|
||||
parameter_link="https://en.wikipedia.org/wiki/Regular_dodecahedron",
|
||||
conway="D",
|
||||
conway_recipe="D",
|
||||
),
|
||||
GoldbergData(
|
||||
parameter=(2, 0),
|
||||
parameter_link="https://en.wikipedia.org/wiki/Chamfered_dodecahedron",
|
||||
conway="cD",
|
||||
conway_recipe="K300cD",
|
||||
),
|
||||
GoldbergData(
|
||||
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",
|
||||
),
|
||||
],
|
||||
@dataclass
|
||||
class DodecahedralGoldbergSolution:
|
||||
klass: str
|
||||
parameter: str
|
||||
hexagon_count: str
|
||||
edge_count: str
|
||||
vertex_count: str
|
||||
conway: str
|
||||
|
||||
special_names: dict[tuple[int, int], str] = {
|
||||
# Class I
|
||||
(1, 0): "https://en.wikipedia.org/wiki/Regular_dodecahedron",
|
||||
(2, 0): "https://en.wikipedia.org/wiki/Chamfered_dodecahedron",
|
||||
# Class II
|
||||
(1, 1): "https://en.wikipedia.org/wiki/Truncated_icosahedron",
|
||||
}
|
||||
|
||||
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]):
|
||||
a, b = parameter
|
||||
hexagon_count = 10*(a*a + a*b + b*b - 1)
|
||||
return PolyData.from_hexagons(hexagon_count)
|
||||
|
||||
|
||||
def goldberg_table_row(data: GoldbergData) -> list[str]:
|
||||
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)
|
||||
]
|
||||
|
||||
headers = DodecahedralGoldbergSolution(
|
||||
klass="Class",
|
||||
parameter="Parameter",
|
||||
hexagon_count="$F_6$",
|
||||
edge_count="E",
|
||||
vertex_count="V",
|
||||
conway="Conway",
|
||||
)
|
||||
|
||||
Markdown(tabulate(
|
||||
[
|
||||
([class_ if i == 0 else ""]) + goldberg_table_row(item)
|
||||
for class_, items in goldberg_classes.items()
|
||||
for i, item in enumerate(items)
|
||||
],
|
||||
headers=["Class", "Parameter", "$F_6$", "V", "E", "Conway"],
|
||||
numalign="left",
|
||||
{
|
||||
field: [name] + (remove_repeats if field == "klass" else lambda x: x)(
|
||||
[getattr(item, field) for item in rows]
|
||||
)
|
||||
for field, name in asdict(headers).items()
|
||||
},
|
||||
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).
|
||||
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
|
||||
-------
|
||||
|
||||
@ -1 +0,0 @@
|
||||
../1/goldberg_triangles.py
|
||||
Loading…
x
Reference in New Issue
Block a user