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 ))$" ] ))