diff --git a/posts/permutations/2/graph_data/__init__.py b/posts/permutations/2/graph_data/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/posts/permutations/2/graph_data/base.py b/posts/permutations/2/graph_data/base.py
new file mode 100644
index 0000000..78bc3d0
--- /dev/null
+++ b/posts/permutations/2/graph_data/base.py
@@ -0,0 +1,135 @@
+from dataclasses import dataclass
+import sympy
+from sympy.abc import x
+
+
+def display_integral_root(
+ root: int, multiplicity: int, pad_to: int | None = None
+) -> str:
+ return (
+ "("
+ # denote sign
+ + ("\\pm" if root != 0 else "")
+ + str(root)
+ + ")^{"
+ + str(multiplicity)
+ # pad multiplicity
+ + ("\\phantom{" + ("0" * pad_to) + "}" if pad_to is not None else "")
+ + "}"
+ )
+
+
+@dataclass
+class SymmetricSpectrum:
+ """
+ Dataclass for symmetric spectra -- those for which, if a spectrum contains a root,
+ it also contains its negative.
+
+ If a root polynomial is not symmetric, then its symmetric counterpart will *not* be specified.
+ """
+
+ # Either the integral root or a polynomial with the root, and the multiplicity
+ roots: list[tuple[sympy.Expr | int, int]]
+ not_shown_count: int = 0
+
+ def to_markdown(self) -> str:
+ """
+ Display a spectrum as a markdown string
+ """
+ integer_roots = [
+ (root, multiplicity)
+ for root, multiplicity in self.roots
+ if isinstance(root, int)
+ ]
+ # symmetrify polynomials
+ polynomial_roots = [
+ (root, multiplicity)
+ for base_root, multiplicity in self.roots
+ if isinstance(base_root, sympy.Expr)
+ for root in set(
+ [base_root, (-1) ** (sympy.degree(base_root)) * base_root.subs(x, -x)]
+ )
+ ]
+
+ shown_roots = len(polynomial_roots) > 0 or len(integer_roots) > 0
+ not_shown_string = (
+ (
+ f"Not shown: {self.not_shown_count} other roots"
+ if shown_roots
+ else f"Not shown: all {self.not_shown_count} roots"
+ )
+ if self.not_shown_count > 0
+ else ""
+ )
+
+ # strictly integral spectra
+ if len(polynomial_roots) == 0 and len(integer_roots) != 0:
+ longest_integer = max(
+ len(str(multiplicity)) for _, multiplicity in integer_roots
+ )
+ return (
+ (
+ "$$\\begin{gather*}"
+ + " \\\\ ".join(
+ display_integral_root(*rm, pad_to=longest_integer)
+ for rm in integer_roots
+ )
+ + "\\end{gather*}$$"
+ )
+ if shown_roots
+ else ""
+ ) + not_shown_string
+
+ # conditionally split integral roots onto multiple lines
+ integral_roots_lines = (
+ " ".join(display_integral_root(*rm) for rm in integer_roots)
+ if len(integer_roots) < 5
+ else (
+ " ".join(
+ display_integral_root(*rm)
+ for rm in integer_roots[: len(integer_roots) // 2]
+ )
+ + "\\\\"
+ + " ".join(
+ display_integral_root(*rm)
+ for rm in integer_roots[len(integer_roots) // 2 :]
+ )
+ )
+ )
+
+ return ((
+ "$$\\begin{gather*}"
+ + integral_roots_lines
+ + " \\\\ "
+ + " \\\\ ".join(
+ sympy.latex(root**multiplicity)
+ for root, multiplicity in polynomial_roots
+ )
+ + "\\end{gather*}$$"
+ )
+ if shown_roots
+ else ""
+ ) + not_shown_string
+
+
+def count_roots_spectrum(spectrum: SymmetricSpectrum) -> int:
+ return (
+ sum(
+ [
+ (
+ (multiplicity * 2 if root != 0 else multiplicity)
+ if isinstance(root, int)
+ else (int(sympy.degree(root, x)) * multiplicity)
+ )
+ for root, multiplicity in spectrum.roots
+ ]
+ )
+ + spectrum.not_shown_count
+ )
+
+@dataclass
+class GraphData:
+ vertex_count: int
+ edge_count: int
+ distance_classes: list[int]
+ spectrum: SymmetricSpectrum
diff --git a/posts/permutations/2/graph_data/complete.py b/posts/permutations/2/graph_data/complete.py
new file mode 100644
index 0000000..73dc702
--- /dev/null
+++ b/posts/permutations/2/graph_data/complete.py
@@ -0,0 +1,50 @@
+from .base import SymmetricSpectrum
+
+# TODO: other non-spectral data
+complete_spectra = {
+ 3: SymmetricSpectrum([(0, 4), (3, 1)]),
+ 4: SymmetricSpectrum([(0, 4), (2, 9), (6, 1)]),
+ 5: SymmetricSpectrum(
+ [
+ (0, 36),
+ (2, 25),
+ (5, 16),
+ (10, 1),
+ ]
+ ),
+ 6: SymmetricSpectrum(
+ [
+ (0, 256),
+ (3, 125),
+ (5, 81),
+ (9, 25),
+ (15, 1),
+ ]
+ ),
+ 7: SymmetricSpectrum(
+ [
+ (0, 400),
+ (1, 441),
+ (3, 1225),
+ (6, 196),
+ (7, 225),
+ (9, 196),
+ (14, 36),
+ (21, 1),
+ ]
+ ),
+ # TODO: missing a root (and its negative) of multiplicity 400
+ 8: SymmetricSpectrum(
+ [
+ (0, 9864),
+ (2, 3136),
+ (4, 6125),
+ (7, 4096),
+ (8, 196),
+ (10, 784),
+ (12, 441),
+ (20, 49),
+ (28, 1),
+ ]
+ ),
+}
diff --git a/posts/permutations/2/graph_data/path.py b/posts/permutations/2/graph_data/path.py
new file mode 100644
index 0000000..f2cdd66
--- /dev/null
+++ b/posts/permutations/2/graph_data/path.py
@@ -0,0 +1,50 @@
+from .base import SymmetricSpectrum, x
+
+path_spectra = {
+ 3: SymmetricSpectrum(
+ [(1, 2), (2, 1)]
+ ),
+ 4: SymmetricSpectrum(
+ [
+ (1, 3),
+ (3, 1),
+ (x**2 - 3, 2),
+ (x**2 - 2 * x - 1, 3),
+ (x**2 + 2 * x - 1, 3),
+ ]
+ ),
+ 5: SymmetricSpectrum(
+ [
+ (0, 12),
+ (1, 6),
+ (4, 1),
+ (x**2 - 5, 6),
+ (x**2 + 5 * x + 5, 4),
+ (x**2 + 3 * x + 1, 4),
+ (x**2 + 2 * x - 1, 5),
+ (x**3 + 2 * x**2 - 5 * x - 4, 5),
+ ]
+ ),
+ 6: SymmetricSpectrum(
+ [
+ (0, 20),
+ (1, 25),
+ (2, 15),
+ (3, 5),
+ (4, 5),
+ (5, 1),
+ (x**2 - 3, 20),
+ ],
+ not_shown_count=558,
+ ),
+ 7: SymmetricSpectrum(
+ [
+ (0, 35),
+ (1, 20),
+ (2, 45),
+ (6, 1),
+ ],
+ not_shown_count=4873,
+ ),
+ 8: SymmetricSpectrum([], not_shown_count=40320),
+}
diff --git a/posts/permutations/2/graph_data/star.py b/posts/permutations/2/graph_data/star.py
new file mode 100644
index 0000000..ba180e0
--- /dev/null
+++ b/posts/permutations/2/graph_data/star.py
@@ -0,0 +1,55 @@
+from .base import SymmetricSpectrum
+
+star_spectra = {
+ 3: SymmetricSpectrum([(1, 2), (2, 1)]),
+ 4: SymmetricSpectrum(
+ [
+ (0, 4),
+ (1, 3),
+ (2, 6),
+ (3, 1),
+ ]
+ ),
+ 5: SymmetricSpectrum(
+ [
+ (0, 30),
+ (1, 4),
+ (2, 28),
+ (3, 12),
+ (4, 1),
+ ]
+ ),
+ 6: SymmetricSpectrum(
+ [
+ (0, 168),
+ (1, 30),
+ (2, 120),
+ (3, 105),
+ (4, 20),
+ (5, 1),
+ ]
+ ),
+ 7: SymmetricSpectrum(
+ [
+ (0, 840),
+ (1, 468),
+ (2, 495),
+ (3, 830),
+ (4, 276),
+ (5, 30),
+ (6, 1),
+ ]
+ ),
+ 8: SymmetricSpectrum(
+ [
+ (0, 3960),
+ (1, 5691),
+ (2, 2198),
+ (3, 6321),
+ (4, 3332),
+ (5, 595),
+ (6, 42),
+ (7, 1),
+ ]
+ ),
+}
diff --git a/posts/permutations/2/index.qmd b/posts/permutations/2/index.qmd
index ccca29a..9a8db38 100644
--- a/posts/permutations/2/index.qmd
+++ b/posts/permutations/2/index.qmd
@@ -19,6 +19,8 @@ categories:
from IPython.display import Markdown
from tabulate import tabulate
+
+from graph_data import complete, path, star
```
@@ -234,51 +236,44 @@ In the case of these graphs, they are a partition of the factorial numbers.
```{python}
#| echo: false
+#| classes: plain
+
+mahonian = [
+ [1, 2, 2, 1],
+ [1, 3, 5, 6, 5, 3, 1],
+ [1, 4, 9, 15, 20, 22, 20, 15, 9, 4, 1],
+ [1, 5, 14, 29, 49, 71, 90, 101, 101, 90, 71, 49, 29, 14, 5, 1],
+ [1, 6, 20, 49, 98, 169, 259, 359, 455, 531, 573, 573, 531, 455, 359, 259, 169, 98, 49, 20, 6, 1],
+]
+
+whitney_second_kind = [
+ [1, 2, 2, 1],
+ [1, 3, 6, 9, 5],
+ [1, 4, 12, 30, 44, 26, 3],
+ [1, 5, 20, 70, 170, 250, 169, 35],
+ [1, 6, 30, 135, 460, 1110, 1689, 1254, 340, 15],
+]
+
+stirling_second_kind = [
+ [1, 3, 2],
+ [1, 6, 11, 6],
+ [1, 10, 35, 50, 24],
+ [1, 15, 85, 225, 274, 120],
+ [1, 21, 175, 735, 1624, 1764, 720],
+]
+
+half_break = lambda x: str(x[:len(x) // 2])[:-1] + "
" + str(x[len(x) // 2:])[1:]
Markdown(tabulate(
[
- [
- 3,
- "[1, 2, 2, 1]",
- "[1, 2, 2, 1]",
- "[1, 3, 2]"
- ],
- [
- 4,
- "[1, 3, 5, 6, 5, 3, 1]",
- "[1, 2, 2, 1]",
- "[1, 6, 11, 6]"
- ],
- [
- 5,
- "[1, 4, 9, 15, 20, 22, 20, 15, 9, 4, 1]",
- "[1, 4, 12, 30, 44, 26, 3]",
- "[1, 10, 35, 50, 24]"
- ],
- [
- 6,
- "[1, 5, 14, 29, 49, 71, 90, 101,
101, 90, 71, 49, 29, 14, 5, 1]",
- "[1, 5, 20, 70, 170, 250, 169, 35]",
- "[1, 15, 85, 225, 274, 120]"
- ],
- [
- 7,
- "[1, 6, 20, 49, 98, 169, 259, 359, 455, 531, 573,
573, 531, 455, 359, 259, 169, 98, 49, 20, 6, 1]",
- "[1, 6, 30, 135, 460, 1110, 1689, 1254, 340, 15]",
- "[1, 21, 175, 735, 1624, 1764, 720]"
- ],
- [
- "Rule",
- "Mahonian numbers [OEIS A008302](http://oeis.org/A008302)",
- "Whitney numbers of the second kind (star poset) [OEIS A007799](http://oeis.org/A007799)",
- "Stirling numbers of the first kind [OEIS A132393](http://oeis.org/A132393)"
- ],
+ [i, j if i <= 5 else half_break(j), k, l]
+ for i, j, k, l in zip(range(3,8), mahonian, whitney_second_kind, stirling_second_kind)
],
headers=[
"*n*",
- r"$dists(\exp( P_n ))$",
- r"$dists(\exp( \bigstar_n ))$",
- r"$dists(\exp( K_n ))$"
+ r"$\text{dists}(\exp( P_n ))$",
+ r"$\text{dists}(\exp( \bigstar_n ))$",
+ r"$\text{dists}(\exp( K_n ))$"
]
))
```
@@ -297,130 +292,20 @@ Unfortunately, eigenvalues are not necessarily integers, and therefore not easil
```{python}
#| echo: false
-
-# TODO: less raw latex, more data converted to latex
-
-f = lambda x: x.replace("\n", "")
+#| classes: plain
Markdown(tabulate(
[
- [3, r"$(\pm 1)^2 (\pm 2)$", r"$(\pm 1)^2 (\pm 2)$", r"$(0)^4 (\pm 3)$"],
- [4, f(r"""$$
-\begin{gather*}
- (\pm 1)^3 (\pm 3) \\
- (x^2 -\ 3)^2 \\
- (x^2 -\ 2x -\ 1)^3
- (x^2 + 2x -\ 1)^3
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^4 \\
- (\pm 1)^3 \\
- (\pm 2)^6 \\
- (\pm 3)^{\phantom{0}}
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^4 \\
- (\pm 2)^9 \\
- (\pm 6)^{\phantom{0}}
-\end{gather*}$$""")],
- [5, f(r"""$$
-\begin{gather*}
- (0)^{12} (\pm 1)^6 (\pm 4) \\
- (x^2 -\ 5)^6 \\
- (x^2 -\ 5x + 5)^4
- (x^2 + 5x + 5)^4 \\
- (x^2 -\ 3x + 1)^4
- (x^2 + 3x + 1)^4 \\
- (x^2 -\ 2x -\ 1)^5
- (x^2 + 2x -\ 1)^5 \\
- (x^3 -\ 2x^2 -\ 5x + 4)^5 \\
- (x^3 + 2x^2 -\ 5x -\ 4)^5
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^{30} \\
- (\pm 1)^{4\phantom{0}} \\
- (\pm 2)^{28} \\
- (\pm 3)^{12} \\
- (\pm 4)^{\phantom{00}}
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^{36} \\
- (\pm 2)^{25} \\
- (\pm 5)^{16} \\
- (\pm 10)^{\phantom{00}}
- \end{gather*}$$""")],
- [6, f(r"""$$\begin{gather*}
- (0)^{20} (\pm 1)^{25} (\pm 2)^{15} \\
- (\pm 3)^5 (\pm 4)^5 (\pm 5) \\
- (x^2 -\ 3)^{20} \\
-\end{gather*}
-$$
*Not shown: 558 other roots*"""), f(r"""$$
-\begin{gather*}
- (0)^{168} \\
- (\pm 1)^{30\phantom{0}} \\
- (\pm 2)^{120} \\
- (\pm 3)^{105} \\
- (\pm 4)^{20\phantom{0}} \\
- (\pm 5)^{\phantom{000}}
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^{256} \\
- (\pm 3)^{125} \\
- (\pm 5)^{81\phantom{0}} \\
- (\pm 9)^{25\phantom{0}} \\
- (\pm 15)^{\phantom{000}}
-\end{gather*}$$""")],
- [7, f(r"""
- $(0)^{35} (\pm 1)^{20} (\pm 2)^{45} (6)$
-*Not shown: 4873 other roots*
- """), f(r"""$$
-\begin{gather*}
- (0)^{840} \\
- (\pm 1)^{468} \\
- (\pm 2)^{495} \\
- (\pm 3)^{830} \\
- (\pm 4)^{276} \\
- (\pm 5)^{30\phantom{0}} \\
- (\pm 6)^{\phantom{000}}
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^{400\phantom{0}} \\
- (\pm 1)^{441\phantom{0}} \\
- (\pm 3)^{1225} \\
- (\pm 6)^{196\phantom{0}} \\
- (\pm 7)^{225\phantom{0}} \\
- (\pm 9)^{196\phantom{0}} \\
- (\pm 14)^{36\phantom{00}} \\
- (\pm 21)^{\phantom{0000}}
-\end{gather*}$$""")],
- [8, f(r"""*Not shown: all 40320 roots*"""), f(r"""$$
-\begin{gather*}
- (0)^{3960} \\
- (\pm 1)^{5691} \\
- (\pm 2)^{2198} \\
- (\pm 3)^{6321} \\
- (\pm 4)^{3332} \\
- (\pm 5)^{595\phantom{0}} \\
- (\pm 6)^{42\phantom{00}} \\
- (\pm 7)^{\phantom{0000}}
-\end{gather*}$$"""), f(r"""$$
-\begin{gather*}
- (0)^{9864} \\
- (\pm 2)^{3136} \\
- (\pm 4)^{6125} \\
- (\pm 7)^{4096} \\
- (\pm 8)^{196\phantom{0}} \\
- (\pm 10)^{784\phantom{0}} \\
- (\pm 12)^{441\phantom{0}} \\
- (\pm 20)^{49\phantom{00}} \\
- (\pm 28)^{\phantom{0000}}
-\end{gather*}$$""")
- ],
+ [i, pathG.to_markdown(), starG.to_markdown(), completeG.to_markdown()]
+ for i in range(3, 9)
+ for pathG, starG, completeG in (
+ [path.path_spectra[i], star.star_spectra[i], complete.complete_spectra[i]],
+ )
],
headers=[
"*n*",
r"$\text{Spec}(\exp( P_n ))$",
- r"$\text{Spec}(\exp( \bigstar_n)$",
+ r"$\text{Spec}(\exp( \bigstar_n )$",
r"$\text{Spec}(\exp( K_n ))$"
]
))