142 lines
4.2 KiB
Python

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 - len(str(multiplicity))) + "}"))
if pad_to is not None and len(str(multiplicity)) < pad_to
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:
spectrum: SymmetricSpectrum
vertex_count: int | None = None
edge_count: int | None = None
distance_classes: list[int] | None = None