Source code for dot

import copy

import dot2tex
from pylatex import Command
from pylatex.utils import NoEscape

from pyfeyn2.feynmandiagram import Connector
from pyfeyn2.render.latex.latex import LatexRender

# workaround for dot2tex bug in math mode labels
[docs]REPLACE_THIS_WITH_A_BACKSLASH = "¬"
# https://tikz.dev/tikz-decorations
[docs]map_feyn_to_tikz = { "vector": "decorate,decoration=snake", "boson": "decorate,decoration=snake", "photon": "decorate,decoration=snake", "gluon": "decorate,decoration={coil,aspect=0.3,segment length=1mm}", "ghost": "dotted", "fermion": "decorate,postaction={decorate,draw,red,decoration={markings,mark=at position 0.5 with {\\arrow{>}}}}", "higgs": "densely dashed", "scalar": "densely dashed", "slepton": "densely dashed", "squark": "densely dashed", "zigzag": "decorate,decoration=zigzag", "phantom": "draw=none", }
[docs]def stylize_connect(c: Connector) -> str: style = 'style="{}",texmode="raw"'.format(map_feyn_to_tikz[c.type]) if c.label is None: label = "" else: label = c.label.replace("\\", REPLACE_THIS_WITH_A_BACKSLASH) if c.length is not None: style += f",len={c.length}" style += f',label="{label}"' return style
[docs]def feynman_adjust_points(feyndiag, size=5, delete_vertices=True): # deepcopy fd = copy.deepcopy(feyndiag) if delete_vertices: for v in fd.vertices: v.x = None v.y = None norm = size dot = feynman_to_dot(fd) positions = dot_to_positions(dot) mmax = 0 for _, p in positions.items(): if p[0] > mmax: mmax = p[0] if p[1] > mmax: mmax = p[1] for v in fd.vertices: if v.id in positions: v.x = positions[v.id][0] / mmax * norm v.y = positions[v.id][1] / mmax * norm for l in fd.legs: l.x = positions[l.id][0] / mmax * norm l.y = positions[l.id][1] / mmax * norm return fd
[docs]def dot_to_positions(dot): return dot2tex.dot2tex(dot, format="positions")
[docs]def dot_to_tikz(dot): ret = dot2tex.dot2tex(dot, format="tikz", figonly=True) ret = ret.replace(REPLACE_THIS_WITH_A_BACKSLASH, "\\") return ret
[docs]def feynman_to_dot(fd): # TODO better use pydot? still alive? # TODO style pick neato or dot or whatever src = "graph G {\n" src += "rankdir=LR;\n" src += "layout=neato;\n" # src += "mode=hier;\n" src += 'node [style="invis"];\n' for l in fd.legs: if l.x is not None and l.y is not None: src += f'\t\t{l.id} [ pos="{l.x},{l.y}!"];\n' for l in fd.vertices: if l.x is not None and l.y is not None: src += f'\t\t{l.id} [ pos="{l.x},{l.y}!"];\n' for p in fd.propagators: style = stylize_connect(p) src += "edge [{}];\n".format(style) src += f"\t\t{p.source} -- {p.target};\n" rank_in = "{rank=min; " rank_out = "{rank=max; " for l in fd.legs: style = stylize_connect(l) if l.sense == "incoming": src += "edge [{}];\n".format(style) src += f"\t\t{l.id} -- {l.target};\n" rank_in += f"{l.id} " elif l.sense == "outgoing": src += "edge [{}];\n".format(style) src += f"\t\t{l.target} -- {l.id};\n" rank_out += f"{l.id} ;" else: # TODO maybe not error but just use the default raise Exception("Unknown sense") src += rank_in + "}\n" src += rank_out + "}\n" src += "}" return src
[docs]class DotRender(LatexRender): def __init__( self, fd=None, documentclass="standalone", document_options=None, *args, **kwargs, ): if document_options is None: document_options = ["preview", "crop", "tikz"] super().__init__( *args, fd=fd, documentclass=documentclass, document_options=document_options, **kwargs, ) # super(Render,self).__init__(*args, fd=fd,**kwargs) self.preamble.append(Command("usepackage", NoEscape("tikz"))) self.preamble.append( Command("usetikzlibrary", NoEscape("snakes,arrows,shapes")) ) self.preamble.append(Command("usepackage", NoEscape("amsmath"))) self.preamble.append( Command("usetikzlibrary", NoEscape("decorations.markings")) ) if fd is not None: self.set_feynman_diagram(fd)
[docs] def set_feynman_diagram(self, fd): super().set_feynman_diagram(fd) self.src_dot = feynman_to_dot(fd) self.set_src_diag(dot_to_tikz(self.src_dot)) self.src_dot = self.src_dot.replace(REPLACE_THIS_WITH_A_BACKSLASH, "\\")
[docs] def get_src_dot(self): return self.src_dot
@staticmethod
[docs] def valid_attribute( attr: str) -> bool: return super(DotRender,DotRender).valid_attribute(attr) or attr in ["x", "y", "label"]
@staticmethod
[docs] def valid_type( typ): if typ.lower() in map_feyn_to_tikz: return True return False