Last active
September 11, 2026 09:21
-
-
Save Hermann-SW/12c7644ac0c75b4eb019f76c3f023fe5 to your computer and use it in GitHub Desktop.
Latex labels for Graphviz (work in progress)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ Latex labels for Graphviz """ | |
| import html | |
| import re | |
| import subprocess | |
| from pylatexenc.latex2text import ( | |
| LatexNodes2Text, | |
| MacroTextSpec, | |
| get_default_latex_context_db as get_default_l2t_context_db, | |
| ) | |
| from pylatexenc.latexwalker import ( | |
| LatexMacroNode, | |
| LatexEnvironmentNode, | |
| LatexWalker, | |
| get_default_latex_context_db as get_default_walker_context_db, | |
| ) | |
| from pylatexenc.macrospec import EnvironmentSpec, MacroSpec | |
| BLACKBOARD_MAP = {"Z": "ℤ", "N": "ℕ", "C": "ℂ", "R": "ℝ", "Q": "ℚ"} | |
| def custom_mathbb(node, l2tobj): | |
| """ foobar """ | |
| arg_nodes = getattr( | |
| node.nodeargd, "argnlist", getattr(node.nodeargd, "argnodelist", []) | |
| ) | |
| arg = l2tobj.nodelist_to_text(arg_nodes) | |
| return BLACKBOARD_MAP.get(arg, arg) | |
| EXTRA_WALKER_MACROS = [ | |
| MacroSpec("textbf", "{"), | |
| MacroSpec("cong"), | |
| MacroSpec("mathbb", "{"), | |
| MacroSpec("text", "{"), | |
| MacroSpec("longrightarrow"), | |
| MacroSpec("Longrightarrow"), | |
| MacroSpec("iff"), | |
| MacroSpec("implies"), | |
| MacroSpec("sim"), | |
| MacroSpec("approx"), | |
| MacroSpec("equiv"), | |
| MacroSpec("lcm"), | |
| MacroSpec("lbr"), | |
| MacroSpec("bar", "{"), | |
| MacroSpec("vspace", "{"), | |
| MacroSpec("ord"), | |
| MacroSpec("gcd"), | |
| MacroSpec("mod"), | |
| ] | |
| walker_ctx = get_default_walker_context_db() | |
| walker_ctx.add_context_category( | |
| "math-extra", macros=EXTRA_WALKER_MACROS, prepend=True | |
| ) | |
| walker_ctx.add_context_category( | |
| "env-extra", environments=[EnvironmentSpec("cases")], prepend=True | |
| ) | |
| l2t_ctx = get_default_l2t_context_db() | |
| l2t_ctx.add_context_category( | |
| "math-extra-override", | |
| macros=[ | |
| MacroTextSpec("textbf", "@@@BOLDSTART@@@%(1)s@@@BOLDEND@@@"), | |
| MacroTextSpec("cong", " ≅ "), | |
| MacroTextSpec("mathbb", custom_mathbb), | |
| MacroTextSpec("text", "%(1)s"), | |
| MacroTextSpec("vert", "|"), | |
| MacroTextSpec("longrightarrow", "⟶"), | |
| MacroTextSpec("Longrightarrow", "⟹"), | |
| MacroTextSpec("iff", "⇔ "), | |
| MacroTextSpec("implies", "⇒"), | |
| MacroTextSpec("sim", "~"), | |
| MacroTextSpec("approx", "≈"), | |
| MacroTextSpec("equiv", "≡"), | |
| MacroTextSpec("Z", "ℤ"), | |
| MacroTextSpec("N", "ℕ"), | |
| MacroTextSpec("lcm", "lcm"), | |
| MacroTextSpec("lbr", "@@@BRLEFT@@@"), | |
| MacroTextSpec("bar", "@@@BARSTART@@@%(1)s@@@BAREND@@@"), | |
| MacroTextSpec("vspace", "@@@VSPACESTART@@@%(1)s@@@VSPACEEND@@@"), | |
| MacroTextSpec("ord", "ord"), | |
| MacroTextSpec("gcd", "gcd"), | |
| MacroTextSpec("mod", "mod"), | |
| ], | |
| prepend=True, | |
| ) | |
| def preprocess_sub_super_scripts(text): | |
| """Replaces superscripts/subscripts with safe placeholder tags free of underscores.""" | |
| text = re.sub(r"\^\{([^}]+)\}", r"@@@SUPSTART@@@\1@@@SUPEND@@@", text) | |
| text = re.sub( | |
| r"\^([a-zA-Z0-9\+\-]|\\iota)", r"@@@SUPSTART@@@\1@@@SUPEND@@@", text | |
| ) | |
| text = re.sub(r"_\{([^}]+)\}", r"@@@SUBSTART@@@\1@@@SUBEND@@@", text) | |
| text = re.sub(r"_([a-zA-Z0-9\+\-])", r"@@@SUBSTART@@@\1@@@SUBEND@@@", text) | |
| return text | |
| def postprocess_html_tags(text): | |
| """Converts safe tokens to valid Graphviz HTML labels.""" | |
| # Convert \vspace{N} to a small empty break tag | |
| text = re.sub( | |
| r"@@@VSPACESTART@@@(\d+)@@@VSPACEEND@@@", | |
| r'<BR ALIGN="LEFT"/><FONT POINT-SIZE="\1"> </FONT><BR ALIGN="LEFT"/>', | |
| text | |
| ) | |
| return ( | |
| text.replace("@@@BOLDSTART@@@", "<B>") | |
| .replace("@@@BOLDEND@@@", "</B>") | |
| .replace("@@@SUPSTART@@@", "<SUP>") | |
| .replace("@@@SUPEND@@@", "</SUP>") | |
| .replace("@@@SUBSTART@@@", "<SUB>") | |
| .replace("@@@SUBEND@@@", "</SUB>") | |
| .replace("@@@BRLEFT@@@", '<BR ALIGN="LEFT"/>') | |
| .replace("@@@BARSTART@@@", "<O>") | |
| .replace("@@@BAREND@@@", "</O>") | |
| ) | |
| def find_unknown_macro(node, ctx): | |
| """ foobar """ | |
| if node is None: | |
| return None | |
| if isinstance(node, LatexMacroNode): | |
| if ctx.get_macro_spec(node.macroname) is None: | |
| return node | |
| if hasattr(node, "nodeargd") and node.nodeargd: | |
| arg_list = getattr( | |
| node.nodeargd, | |
| "argnlist", | |
| getattr(node.nodeargd, "argnodelist", []), | |
| ) | |
| for child in arg_list: | |
| err_node = find_unknown_macro(child, ctx) | |
| if err_node: | |
| return err_node | |
| elif hasattr(node, "nodelist") and node.nodelist: | |
| for child in node.nodelist: | |
| err_node = find_unknown_macro(child, ctx) | |
| if err_node: | |
| return err_node | |
| return None | |
| # pylint: disable=too-many-locals | |
| def process_cases_environment(env_node, converter, full_latex_str): | |
| """ foobar """ | |
| rows = [[]] | |
| for child in env_node.nodelist: | |
| if isinstance(child, LatexMacroNode) and child.macroname == "\\": | |
| rows.append([]) | |
| else: | |
| rows[-1].append(child) | |
| table_rows = [] | |
| num_rows = len([r for r in rows if r]) | |
| for row_nodes in rows: | |
| if not row_nodes: | |
| continue | |
| first_pos = row_nodes[0].pos | |
| last_node = row_nodes[-1] | |
| last_pos = last_node.pos + last_node.len | |
| row_str = full_latex_str[first_pos:last_pos] | |
| if "&" in row_str: | |
| expr_str, cond_str = row_str.split("&", 1) | |
| else: | |
| expr_str, cond_str = row_str, "" | |
| w_expr = LatexWalker(expr_str, latex_context=walker_ctx) | |
| n_expr, _, _ = w_expr.get_latex_nodes() | |
| expr = converter.nodelist_to_text(n_expr).strip() | |
| w_cond = LatexWalker(cond_str, latex_context=walker_ctx) | |
| n_cond, _, _ = w_cond.get_latex_nodes() | |
| cond = converter.nodelist_to_text(n_cond).strip() | |
| expr = postprocess_html_tags(html.escape(expr)) | |
| cond = postprocess_html_tags(html.escape(cond)) | |
| if len(table_rows) == 0: | |
| table_rows.append( | |
| f"<TR>" | |
| f'<TD ROWSPAN="{num_rows}" VALIGN="MIDDLE" ALIGN="RIGHT" BORDER="0">' | |
| f'<FONT POINT-SIZE="22">{</FONT></TD>' | |
| f'<TD ALIGN="LEFT" VALIGN="MIDDLE" BORDER="0">{expr}</TD>' | |
| f'<TD ALIGN="LEFT" VALIGN="MIDDLE" BORDER="0"> {cond}</TD>' | |
| f"</TR>" | |
| ) | |
| else: | |
| table_rows.append( | |
| f"<TR>" | |
| f'<TD ALIGN="LEFT" VALIGN="MIDDLE" BORDER="0">{expr}</TD>' | |
| f'<TD ALIGN="LEFT" VALIGN="MIDDLE" BORDER="0"> {cond}</TD>' | |
| f"</TR>" | |
| ) | |
| return ( | |
| '<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="1">' | |
| + "".join(table_rows) | |
| + "</TABLE>" | |
| ) | |
| def latex_to_graphviz_html(latex_str): | |
| """ foobar """ | |
| latex_str = preprocess_sub_super_scripts(latex_str) | |
| walker = LatexWalker(latex_str, latex_context=walker_ctx) | |
| nodes, _, _ = walker.get_latex_nodes() | |
| for node in nodes: | |
| missing_node = find_unknown_macro(node, l2t_ctx) | |
| if missing_node: | |
| raise ValueError(f"Unknown macro \\{missing_node.macroname}") | |
| converter = LatexNodes2Text(latex_context=l2t_ctx) | |
| if r"\begin{cases}" in latex_str: | |
| for node in nodes: | |
| if ( | |
| isinstance(node, LatexEnvironmentNode) | |
| and node.environmentname == "cases" | |
| ): | |
| cases_table = process_cases_environment( | |
| node, converter, latex_str | |
| ) | |
| prefix_str = latex_str[: node.pos] | |
| suffix_str = latex_str[node.pos + node.len :] | |
| if r"\\" in prefix_str: | |
| lines = prefix_str.split(r"\\") | |
| line1_html = latex_to_graphviz_html(lines[0])[1:-1] | |
| line2_html = latex_to_graphviz_html(lines[1])[1:-1] | |
| suffix_html = latex_to_graphviz_html(suffix_str)[1:-1] | |
| full_html = ( | |
| '<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0">' | |
| f'<TR><TD BORDER="0" ALIGN="LEFT" COLSPAN="2">{line1_html}</TD></TR>' | |
| f'<TR><TD BORDER="0" VALIGN="MIDDLE" ALIGN="LEFT">{line2_html}</TD>' | |
| f'<TD BORDER="0" VALIGN="MIDDLE" ALIGN="LEFT">{cases_table}</TD>' | |
| f'<TD BORDER="0" VALIGN="MIDDLE" ALIGN="LEFT">{suffix_html}</TD></TR>' | |
| "</TABLE>" | |
| ) | |
| return f"<{full_html}>" | |
| prefix_html = latex_to_graphviz_html(prefix_str)[1:-1] | |
| suffix_html = latex_to_graphviz_html(suffix_str)[1:-1] | |
| full_html = ( | |
| '<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0"><TR>' | |
| f'<TD BORDER="0" VALIGN="MIDDLE">{prefix_html}</TD>' | |
| f'<TD BORDER="0" VALIGN="MIDDLE">{cases_table}</TD>' | |
| f'<TD BORDER="0" VALIGN="MIDDLE">{suffix_html}</TD>' | |
| "</TR></TABLE>" | |
| ) | |
| return f"<{full_html}>" | |
| plain_text = converter.nodelist_to_text(nodes) | |
| escaped_text = html.escape(plain_text) | |
| # Convert placeholders to Graphviz HTML tags for standard nodes | |
| html_label_body = postprocess_html_tags(escaped_text.replace("\n", "<BR/>")) | |
| return f"<{html_label_body}>" | |
| def_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 2.1 }Number-theoretic function:\\""" | |
| r"""a: \mathbb{N}\to \mathbb{C}""" | |
| ) | |
| ex_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 2.2a }for s\in\mathbb{C}: \iota^{s}(n) := n^s \forall n \in | |
| \mathbb{N} is s-th power\\function (s=0 constant one function, identity \iota^1 =: \iota)""" | |
| ) | |
| def_3_13_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.13 } Ring R, x\in R: if \exists y\in R\setminus \{0\}: xy=0, | |
| then x is called zero divisor""" | |
| ) | |
| ex_3_14_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.14 } zero divisors in \Z/3\Z: \bar{0}, so has no zero divisors\vspace{3}""" | |
| r"""zero divisors in \Z/4\Z: \bar{0},\bar{2}, so has zero divisor\\""" | |
| ) | |
| def_3_15_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.15 } Ring R, x\in R: if \exists y\in R: xy=1,\\then x is called a unit""" | |
| ) | |
| ex_3_16_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.16 } units in \Z/3\Z: \bar{1},\bar{2}\vspace{3}""" | |
| r"""units in \Z/4\Z: \bar{1},\bar{3}""" | |
| ) | |
| prop_3_17_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.17 } R ring\lbr""" | |
| r"""(a) R^{\times}:=\{x\in R: x is unit\} with \cdot\ is (abelian) group of units\lbr""" | |
| r"""(b) x\in R^{\times} \implies\ x no zero divisor\lbr""" | |
| r"""(c) R finite, then also: x\in R no zero divisor \implies\ x is unit\lbr""" | |
| ) | |
| ex_3_18_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.18 } Finite ring R=\Z/n\Z, n\in \N, x\in R:\vspace{5}""" | |
| r"""\bar{x} unit \iff \bar{x} no zero divisor\lbr """ | |
| r"""Units in R are called coprime residue classes modulo n\lbr """ | |
| r"""(\Z/n\Z)^{\times} is called multiplicative group of integers modulo n""" | |
| ) | |
| def_3_19_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.19 } (Commutative) ring R | |
| with R^{\times} = R \setminus \{0\} is called field""" | |
| ) | |
| ex_3_20_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.20 } \Z/3\Z\ is field, \Z/4\Z\ and zero ring are no fields.""" | |
| ) | |
| thm_3_21_label = latex_to_graphviz_html( | |
| r"""\textbf{Thm 3.21 } n\in \N, these are equivalent\lbr""" | |
| r"""(i) n is prime\lbr""" | |
| r"""(ii) \Z/n\Z\ is field\lbr""" | |
| r"""(iii) \Z/n\Z\ has no zero divisors\lbr""" | |
| ) | |
| prop_3_22_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.22 } a\in \mathbb{N}, \bar{a}\in \Z/n\Z:\vspace{3}""" | |
| r"""\bar{a}\in (\Z/n\Z)^{\times} \iff \gcd(a,n)=1""" | |
| ) | |
| cor_3_23_label = latex_to_graphviz_html( | |
| r"""\textbf{Cor 3.23 } n\in \mathbb{N}, a\in \Z:\vspace{3}""" | |
| r"""(\exists x\in \Z: ax\equiv 1 (\mod\ n)) \iff \gcd(a,n)=1""" | |
| ) | |
| ex_3_24_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.24 } Solutions for\\""" | |
| r"""3x\equiv 1 (\mod\ 37): L=25+37\Z""" | |
| ) | |
| prop_3_25_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.25 } a,b\in \Z, n\in\N\\""" | |
| r"""(\exists x\in\Z: ax\equiv b (\mod\ n)) \iff \gcd(a,n)\vert b""" | |
| ) | |
| ex_3_26_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.26 } (a) \nexists x\in \Z: 15x\equiv 7 (\mod\ 21)\lbr""" | |
| r"""(b) \exists x\in\Z: 15x\equiv 6 (\mod\ 21)\lbr""" | |
| ) | |
| def_3_27_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.27 } Order \vert G\vert\ of group\\""" | |
| r"""is number of its elements""" | |
| ) | |
| ex_3_28_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.28 }\vert(\Z/n\Z)^{\times}\vert=\phi(n)""" | |
| ) | |
| prop_3_29_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.29 } G finite abelian group.\lbr | |
| \forall g\in G: g^{\vert G\vert} =1 """ | |
| ) | |
| thm_3_30_label = latex_to_graphviz_html( | |
| r"""\textbf{Thm 3.30 } (Fermat-Euler theorem) \vspace{5}""" | |
| r"""n\in \mathbb{N}. """ | |
| r"""\forall \bar{a}\in (\Z/n\Z)^{\times}:\ \bar{a}^{\phi(n)} =\bar{1} """ | |
| ) | |
| ex_3_31_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.31 } 3^{19}\equiv 10 (\mod\ 17)""" | |
| ) | |
| cor_3_32_label = latex_to_graphviz_html( | |
| r"""\textbf{Cor 3.32 } (Fermat's little theorem) p prime:\vspace{3}""" | |
| r"""(a) \forall \bar{a}\in \mathbb{F}_p^{\times}: \bar{a}^{p-1}=\bar{1}\vspace{5}""" | |
| r"""(b) \forall \bar{a}\in \mathbb{F}_p: \bar{a}^p=\bar{a}\lbr""" | |
| ) | |
| def_3_33_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.33 } Group G is cyclic:\lbr | |
| \exists g\in G generator with\lbr | |
| G = \{g^n: n\in \Z\}=:<g>\lbr""" | |
| ) | |
| rem_3_34_label = latex_to_graphviz_html( | |
| r"""\textbf{Rem 3.34 } Every cyclic group is abelian.""" | |
| ) | |
| ex_3_35_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.35 }(a) group (\Z/5\Z)^{\times} = \{\bar{1},\bar{2},""" | |
| r"""\bar{3},\bar{4}\} is cyclic\vspace{3}""" | |
| r"""(b) group (\Z/8\Z)^{\times} = \{\bar{1},\bar{3},\bar{5},\bar{7}\} is not cyclic\lbr""" | |
| r"""(c) additive group \Z\ is cyclic\vspace{3}""" | |
| r"""(d) additive group \Z/m\Z =\{\bar{0},\bar{1},\dots,\bar{m-1}\}\ is cyclic\lbr""" | |
| ) | |
| def_3_36_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.36 } G finite abelian group.\lbr | |
| Order \ord(G):=\min\{n\in \mathbb{N}: g^n=1\}\lbr """ | |
| ) | |
| ex_3_37_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.37 }For G=(\Z/5\Z)^{\times}:\vspace{3}""" | |
| r"""(a) ord(\bar{2})=4\vspace{3}""" | |
| r"""(b) ord(\bar{4})=2\lbr""" | |
| ) | |
| prop_3_38_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.38 }G finite abelian group: | |
| G is cyclic \iff \exists g\in G with ord(g)=\vert G\vert""" | |
| ) | |
| prop_3_39_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.39 }G finite abelian group,\\g\in G, m\in \Z: ord(g)\vert m \iff g^m=1""" | |
| ) | |
| cor_3_40_label = latex_to_graphviz_html( | |
| r"""\textbf{Cor 3.40 } G finite abelian group.\lbr | |
| \forall g\in G: ord(g) \vert\ \vert G\vert """ | |
| ) | |
| def_3_41_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.41 } G finite abelian group.\lbr | |
| Exponent \exp(G):=\min\{n\in \mathbb{N}: g^n=1 \forall g\in G\}\lbr """ | |
| ) | |
| ex_3_42_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.42 }(a) exp((\Z/5\Z)^{\times})=4\lbr | |
| (b) exp((\Z/8\Z)^{\times})=2\lbr""" | |
| ) | |
| prop_3_43_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.43 }G finite abelian group G:\lbr """ | |
| r"""(a) \exp(G) \vert\ \vert G\vert\ \lbr """ | |
| r"""(b) \exp(G)= \lcm(\{ord(g): g\in G\})\lbr""" | |
| ) | |
| def_3_44_label = latex_to_graphviz_html( | |
| r"""\textbf{Def 3.44 } homomorphism\\ isomorphism, \cong """ | |
| ) | |
| ex_3_45_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.45 }K field implies\lbr | |
| det:GL_n(K)\to K^{\times} homomorphism""" | |
| ) | |
| prop_3_46_label = latex_to_graphviz_html( | |
| r"""\textbf{Prop 3.46 }G, H groups, \psi: G\to\ H homomorphism:\lbr """ | |
| r"""(a) \psi \text{ injective} \iff \text{ker}(\psi):=\{g\in G: \psi(g)=1\}=\{1\}\lbr """ | |
| r"""(b) \psi \text{ isomorphism} \text{implies} \psi^{-1}: H\to G isomorphism\lbr""" | |
| ) | |
| thm_3_47_label = latex_to_graphviz_html( | |
| r"""\textbf{Thm 3.47 }For cyclic group G:\\ G \cong \begin{cases} | |
| \mathbb{Z} & \text{for }G\text{ infinite,}\\ | |
| \Z /|G|\mathbb{Z} & \text{for }G\text{ finite.} \end{cases}""" | |
| ) | |
| ex_3_48_label = latex_to_graphviz_html( | |
| r"""\textbf{Ex 3.48 } (\Z/5\Z)^{\times} \cong \Z/4\Z\\ \psi: \begin{cases} | |
| \Z/4\Z &\to\ (\Z/5\Z)^{\times}\\ | |
| a &\mapsto\ \bar{2}^a \end{cases}\lbr""" | |
| ) | |
| thm_3_49_label = latex_to_graphviz_html( | |
| r"""\textbf{Thm 3.49 }G finite abelian group: | |
| G is cyclic \iff \exp(G) = \vert G\vert""" | |
| ) | |
| dot_content = f"""digraph G {{ | |
| layout=neato; | |
| overlap="false"; | |
| sep="+15"; | |
| node [shape=box, fontsize=12, margin="0.15,0.1"]; | |
| labelloc=t | |
| label="3.3 prime residue classes and Fermat-Euler theorem (Def 3.13 — Cor 3.32)\n3.4 cyclic groups (Def 3.33 — Thm 3.49)\n\n " | |
| # def_2_1 [label={def_label}]; | |
| # ex_2_2a [label={ex_label}]; | |
| def_3_13 [label={def_3_13_label}]; | |
| ex_3_14 [label={ex_3_14_label}]; | |
| def_3_15 [label={def_3_15_label}]; | |
| ex_3_16 [label={ex_3_16_label}]; | |
| prop_3_17 [label={prop_3_17_label}]; | |
| ex_3_18 [label={ex_3_18_label}]; | |
| def_3_19 [label={def_3_19_label}]; | |
| ex_3_20 [label={ex_3_20_label}]; | |
| thm_3_21 [label={thm_3_21_label}]; | |
| prop_3_22 [label={prop_3_22_label}]; | |
| cor_3_23 [label={cor_3_23_label}]; | |
| ex_3_24 [label={ex_3_24_label}]; | |
| prop_3_25 [label={prop_3_25_label}]; | |
| ex_3_26 [label={ex_3_26_label}]; | |
| def_3_27 [label={def_3_27_label}]; | |
| ex_3_28 [label={ex_3_28_label}]; | |
| prop_3_29 [label={prop_3_29_label}]; | |
| thm_3_30 [label={thm_3_30_label}]; | |
| ex_3_31 [label={ex_3_31_label}]; | |
| cor_3_32 [label={cor_3_32_label}]; | |
| def_3_33 [label={def_3_33_label}]; | |
| rem_3_34 [label={rem_3_34_label}]; | |
| ex_3_35 [label={ex_3_35_label}]; | |
| def_3_36 [label={def_3_36_label}]; | |
| ex_3_37 [label={ex_3_37_label}]; | |
| prop_3_38 [label={prop_3_38_label}]; | |
| prop_3_39 [label={prop_3_39_label}]; | |
| cor_3_40 [label={cor_3_40_label}]; | |
| def_3_41 [label={def_3_41_label}]; | |
| ex_3_42 [label={ex_3_42_label}]; | |
| prop_3_43 [label={prop_3_43_label}]; | |
| def_3_44 [label={def_3_44_label}]; | |
| ex_3_45 [label={ex_3_45_label}]; | |
| prop_3_46 [label={prop_3_46_label}]; | |
| thm_3_47 [label={thm_3_47_label}]; | |
| ex_3_48 [label={ex_3_48_label}]; | |
| thm_3_49 [label={thm_3_49_label}]; | |
| def_3_13 -> ex_3_14 [style=dotted]; | |
| def_3_15 -> ex_3_16 [style=dotted]; | |
| {{ def_3_13 def_3_15 }} -> prop_3_17 [style=dotted]; | |
| ex_3_14 -> ex_3_16 [style=invis,arrowhead=none]; | |
| prop_3_17 -> ex_3_18 [style=dotted]; | |
| prop_3_17 -> thm_3_21; | |
| {{ ex_3_16 def_3_19 }} -> ex_3_20 [style=dotted]; | |
| ex_3_20 -> thm_3_21; | |
| prop_3_29 -> def_3_36; | |
| thm_3_21 -> prop_3_22 [style=dotted]; | |
| prop_3_22 -> cor_3_23 [style=dotted]; | |
| def_3_19 -> thm_3_21; | |
| cor_3_23 -> ex_3_24 [style=dotted]; | |
| cor_3_23 -> prop_3_25 [style=dotted]; | |
| prop_3_25 -> ex_3_26 [style=dotted]; | |
| ex_3_24 -> def_3_27 [style=invis,arrowhead=none]; | |
| prop_3_22 -> ex_3_28; | |
| def_3_27 -> ex_3_28 [style=dotted]; | |
| def_3_33 -> ex_3_35 [style=dotted]; | |
| def_3_36 -> ex_3_37 [style=dotted]; | |
| thm_3_30 -> {{ ex_3_31 cor_3_32 }}; | |
| {{ ex_3_28 prop_3_29 }} -> thm_3_30; | |
| ex_3_31 -> cor_3_32 [style=invis,arrowhead=none]; | |
| def_3_33 -> rem_3_34 [style=dotted label="https://gist.github.com/Hermann-SW/12c7644ac0c75b4eb019f76c3f023fe5 " | |
| URL="https://gist.github.com/Hermann-SW/12c7644ac0c75b4eb019f76c3f023fe5"] | |
| def_3_41 -> prop_3_43 [style=dotted]; | |
| prop_3_39 -> cor_3_40; | |
| prop_3_39 -> prop_3_43; | |
| def_3_44 -> {{ ex_3_45 prop_3_46 }} [style=dotted]; | |
| prop_3_38 -> thm_3_47; | |
| prop_3_39 -> thm_3_47; | |
| def_3_36 -> prop_3_38 [style=dotted]; | |
| prop_3_46 -> thm_3_47 [style=dotted]; | |
| prop_3_29 -> cor_3_40; | |
| def_3_41 -> ex_3_42 [style=dotted]; | |
| {{ ex_3_35 thm_3_47 }} -> ex_3_48; | |
| {{ prop_3_39 prop_3_43 }} -> thm_3_49; | |
| prop_3_38 -> thm_3_49; | |
| }}""" | |
| DOTFILENAME = "graph_unicode.dot" | |
| with open(DOTFILENAME, "w", encoding="utf-8") as f: | |
| f.write(dot_content) | |
| print("Generated .dot file:\n") | |
| print(dot_content) | |
| subprocess.run( | |
| ["dot", "-Tpdf", DOTFILENAME, "-o", "graph_output.pdf"], check=True | |
| ) |
Author
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added sections 3.3 “prime residue classes and Fermat-Euler theorem” and 3.4 “cyclic groups” of (German language) lecture script sitting behind Heidelberg University VPN. From definition 3.13 until theorem 3.49. More:
https://forum.graphviz.org/t/latex-labels-for-graphviz-work-in-progress/3402/3
Latest 10% screenshot below, full PDF for browser view:

https://stamm-wilbrandt.de/prime_residue_classess_and_Fermat-Euler_theorem.cyclic_groups.pdf