refactor pentagons.2
This commit is contained in:
parent
118b1a2477
commit
6caf4640ac
@ -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(" = ")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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!",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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
|
||||||
]
|
]
|
||||||
|
|||||||
199
posts/pentagons/1/goldberg/tetrahedral.py
Normal file
199
posts/pentagons/1/goldberg/tetrahedral.py
Normal 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
|
||||||
|
}
|
||||||
@ -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
1
posts/pentagons/2/goldberg
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../1/goldberg
|
||||||
@ -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:
|
|||||||
|
|
||||||
{.image-wide}
|
{.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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user