├── .gitignore ├── README.md ├── drawer.py ├── example ├── README-example.md ├── model └── qgraf.dat ├── header.tex ├── line.py ├── main.tex ├── propagator.py ├── vertex.py └── xmldraw.sty /.gitignore: -------------------------------------------------------------------------------- 1 | diagrams.tex 2 | grafs 3 | propagator.pyc vertex.pyc 4 | main.pdf 5 | *.pyc 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QGRAF Diagram Drawer 2 | ## Dependencies 3 | This code uses [QGRAF](http://cfif.ist.utl.pt/~paulo/qgraf.html) and [tikz-feynman](https://github.com/JP-Ellis/tikz-feynman) 4 | 5 | ## Use 6 | 7 | The program *qgraf-xml-drawer* is a Python program for drawing Feynman diagrams. The code translates [QGRAF](http://cfif.ist.utl.pt/~paulo/qgraf.html) diagrams into a *LuaLaTeX* file using [tikz-feynman](https://github.com/JP-Ellis/tikz-feynman) to draw them after compilation 8 | 9 | ### QGRAF 10 | The program is provided with a *QGRAF* style file called `xmldraw.sty`. Any set of feynman rules compatible with *QGRAF* can be handled and the output should be put in the package folder to be processed. 11 | ### LuaLaTeX Generation 12 | The user is required to modify the code to adapt it to their situation. 13 | * The *QGRAF* output file name should be provided by modifying the variable `INPUTS` in the file `drawer.py`. 14 | * The particle dictionnary `pt` in `drawer.py` should also be modified to contain the list of particles and anti-particles appearing in the diagrams. Each key in the dictionnary is the name of the particle as defined in *QGRAF* and the associated value should be the associated propagator type as defined in *tikz-feynman*. 15 | 16 | Once this is defined, run the code using `python drawer.py` and compile the output using `lualatex main.tex`. The diagrams ared drawn in `main.pdf`. 17 | 18 | 19 | ## Limitations 20 | 21 | The code is designed to make loop diagrams look nice. At the moment, it returns a `ValueError` if two vertices are joined by 4 or more propagators. 22 | 23 | ## Citing 24 | 25 | This code is citeable using the following DOI: 26 | 27 | [![DOI](https://zenodo.org/badge/59492920.svg?maxAge=0)](https://zenodo.org/badge/latestdoi/59492920) 28 | -------------------------------------------------------------------------------- /drawer.py: -------------------------------------------------------------------------------- 1 | from xml.etree.ElementTree import * 2 | from xml.etree.ElementInclude import * 3 | import re 4 | import sys 5 | from propagator import * 6 | from vertex import * 7 | from copy import copy 8 | 9 | INPUT="grafs" #QGRAF output file 10 | graphs=XML(default_loader(INPUT,parse)) 11 | diagrams=graphs.find("diagrams") 12 | 13 | #Particle dictionnary. Adapt this to your model 14 | pt = {"g": "gluon", "t": "fermion", "tbar": "fermion","H": "scalar"} 15 | 16 | 17 | file = open("diagrams.tex","w+") 18 | 19 | for diagram in diagrams.getchildren(): 20 | DiagID = diagram.find("id").text 21 | print "Doing diagram: "+DiagID 22 | file.write(DiagID+"~\\feynmandiagram[small]{\n") 23 | NOpropagators=diagram.find("propagators").getchildren() 24 | NOvertices=diagram.find("vertices").getchildren() 25 | propagators=[] 26 | for p in NOpropagators: 27 | propagators.append(propagator(p)) 28 | vertices=[] 29 | for v in NOvertices: 30 | vertices.append(Vertex(v)) 31 | bundles = [] 32 | for p in propagators: 33 | if len(bundles) > 0: 34 | found = False 35 | for b in bundles: 36 | if p.fromto == b[0].fromto: 37 | print "adding my propagator to an existing bundle" 38 | b.append(p) 39 | found = True 40 | break 41 | if not found: 42 | bundles.append([p]) 43 | else: 44 | bundles = [[p]] 45 | for b in bundles: 46 | if len(b)==1: 47 | if b[0].vfrom != b[0].vto: 48 | b[0].texprint(file,pt) 49 | else: #TADPOLE 50 | tadfrom = copy(b[0]) 51 | tadto = copy(b[0]) 52 | tadfrom.vto = "tad"+tadfrom.id 53 | tadto.vfrom = "tad"+tadfrom.id 54 | shape = "half right" 55 | tadfrom.texprint(file,pt,shape) 56 | tadto.texprint(file,pt,shape) 57 | if len(b)==2: 58 | shapedict = ["quarter right", "quarter left"] 59 | 60 | b[0].texprint(file,pt,shapedict[0]) 61 | if b[1].vfrom == b[0].vfrom: 62 | b[1].texprint(file,pt,shapedict[1]) 63 | else: 64 | b[1].texprint(file,pt,shapedict[0]) 65 | if len(b)==3: 66 | shapedict = ["quarter right", "quarter left"] 67 | b[0].texprint(file,pt,shapedict[0]) 68 | if b[1].vfrom == b[0].vfrom: 69 | b[1].texprint(file,pt,shapedict[1]) 70 | else: 71 | b[1].texprint(file,pt,shapedict[0]) 72 | b[2].texprint(file,pt) 73 | if len(b)>4: 74 | print "I don't know how to deal with this !" 75 | raise ValueError('Too many propagators in a bundle') 76 | 77 | for v in vertices: 78 | for i in range(len(v.fields)): 79 | if re.search('[a-zA-Z]',v.fields[i]): 80 | file.write("{} [particle={}] -- [{}] {},\n".format(v.fields[i],v.fields[i],pt[v.types[i]],v.id)) 81 | # 82 | # file.write("ext1 -- [opacity = 0] mid,\n") add a comma above ! 83 | # file.write("ext3 -- [opacity = 0] mid\n") 84 | file.write("q -- [opacity = 0] q\n") 85 | file.write("};\n") 86 | -------------------------------------------------------------------------------- /example/README-example.md: -------------------------------------------------------------------------------- 1 | # How to use this example 2 | 3 | *You need to have installed the dependecies to execute this example. Start by downloading and setting up QGRAF and tikz-feynman.* 4 | 5 | Run *QGRAF* with this input file `qgraf.dat`. This will generate the Standard Model diagrams for Higgs production through gluon fusion at one loop in an XML format file called `grafs`. Put this file in the code main folder and execute `python drawer.py`. Then compile using `lualatex main.tex`. This generates `main.pdf` with the two diagrams. 6 | -------------------------------------------------------------------------------- /example/model: -------------------------------------------------------------------------------- 1 | [ H, H, + ] 2 | 3 | [ t, tbar, -] 4 | 5 | [ g, g, +] 6 | 7 | [ g, g, g; g_power = 1 ] 8 | 9 | [ g, g, g, g; g_power = 2 ] 10 | 11 | [ tbar, t, g ; g_power = 1] 12 | 13 | [ tbar, t, H ; g_power = 0] 14 | 15 | [ H, H, H; g_power = 0] 16 | 17 | -------------------------------------------------------------------------------- /example/qgraf.dat: -------------------------------------------------------------------------------- 1 | output = 'grafs' ; 2 | 3 | style = 'xmldraw.sty' ; 4 | 5 | model = 'model' ; 6 | 7 | in = g, g ; 8 | 9 | out = H; 10 | 11 | loops = 1 ; 12 | 13 | loop_momentum = k ; 14 | 15 | options = onshell; 16 | 17 | true = vsum[ g_power, 2, 2]; 18 | 19 | true = iprop[H,0,0]; -------------------------------------------------------------------------------- /header.tex: -------------------------------------------------------------------------------- 1 | % *********************************************************** 2 | % ******************* NICO's HEADER ********************** 3 | % *********************************************************** 4 | 5 | 6 | %==================================== 7 | %Packages 8 | %==================================== 9 | \documentclass[11pt]{article} 10 | \usepackage{amsmath} % AMS Math Package 11 | \usepackage{amsthm} % Theorem Formatting 12 | \usepackage{amssymb} % Math symbols such as \mathbb 13 | \usepackage{graphicx} % Allows for eps images 14 | \usepackage{multicol} % Allows for multiple columns 15 | \usepackage[squaren,Gray]{SIunits} 16 | \usepackage{subfigure} 17 | \usepackage{multirow} 18 | \usepackage{makecell} 19 | \usepackage{tikz-feynman} 20 | \usepackage[textsize=tiny,backgroundcolor=white]{todonotes} 21 | \usepackage[top=1in, bottom=1.25in, left=0.8in, right=0.8in]{geometry} 22 | 23 | %==================================== 24 | %Definitions 25 | %==================================== 26 | \unitlength = 1mm 27 | \graphicspath{ {img/} } 28 | \makeatletter 29 | \def\input@path{ {content/}} 30 | \makeatother 31 | 32 | 33 | %==================================== 34 | %Commands 35 | %==================================== 36 | \let\vaccent=\v % rename builtin command \v{} to \vaccent{} 37 | 38 | \renewcommand{\v}[1]{\ensuremath{\mathbf{#1}}} % bold vectors 39 | 40 | \newcommand{\gv}[1]{\ensuremath{\mbox{\boldmath$ #1 $}}} 41 | % bold vectors of Greek letters 42 | \newcommand{\uv}[1]{\ensuremath{\mathbf{\hat{#1}}}} % for unit vector 43 | \newcommand{\abs}[1]{\left| #1 \right|} % for absolute value 44 | \newcommand{\avg}[1]{\left< #1 \right>} % for average 45 | \let\underdot=\d % rename builtin command \d{} to \underdot{} 46 | \renewcommand{\d}[2]{\frac{d #1}{d #2}} % for derivatives 47 | \newcommand{\dd}[2]{\frac{d^2 #1}{d #2^2}} % for double derivatives 48 | \newcommand{\pd}[2]{\frac{\partial #1}{\partial #2}} 49 | % for partial derivatives 50 | \newcommand{\pdd}[2]{\frac{\partial^2 #1}{\partial #2^2}} 51 | % for double partial derivatives 52 | \newcommand{\pdc}[3]{\left( \frac{\partial #1}{\partial #2} 53 | \right)_{#3}} % for thermodynamic partial derivatives 54 | \newcommand{\ket}[1]{\left| #1 \right>} % for Dirac bras 55 | \newcommand{\bra}[1]{\left< #1 \right|} % for Dirac kets 56 | \newcommand{\braket}[2]{\left< #1 \vphantom{#2} \right| 57 | \left. #2 \vphantom{#1} \right>} % for Dirac brackets 58 | \newcommand{\matrixel}[3]{\left< #1 \vphantom{#2#3} \right| 59 | #2 \left| #3 \vphantom{#1#2} \right>} % for Dirac matrix elements 60 | \newcommand{\grad}[1]{\gv{\nabla} #1} % for gradient 61 | \let\divsymb=\div % rename builtin command \div to \divsymb 62 | \renewcommand{\div}[1]{\gv{\nabla} \cdot #1} % for divergence 63 | \newcommand{\curl}[1]{\gv{\nabla} \times #1} % for curl 64 | \let\baraccent=\= % rename builtin command \= to \baraccent 65 | \renewcommand{\=}[1]{\stackrel{#1}{=}} % for putting numbers above = 66 | \newtheorem{prop}{Proposition} 67 | \newtheorem{thm}{Theorem}[section] 68 | \newtheorem{lem}[thm]{Lemma} 69 | \theoremstyle{definition} 70 | \newtheorem{dfn}{Definition} 71 | \newtheorem*{rst}{Result} 72 | \theoremstyle{remark} 73 | \newtheorem*{rmk}{Remark} 74 | 75 | \newcommand{\gev}[1]{$\unit{#1}{\giga\electronvolt}$} 76 | \newcommand{\tev}[1]{$\unit{#1}{\tera\electronvolt}$} 77 | 78 | \newcommand{\gevm}[1]{\unit{#1}{\giga\electronvolt}} 79 | \newcommand{\tevm}[1]{\unit{#1}{\tera\electronvolt}} 80 | 81 | \newcommand{\half}{$\frac{1}{2}$} 82 | 83 | \newcommand{\ddk}[1]{\frac{d^d k_{#1}}{(4\pi)^d}} 84 | \newcommand{\sidenote}[1]{\todo[noline]{#1}} 85 | 86 | \newcommand{\prog}[1]{\texttt{#1}} 87 | 88 | % *********************************************************** 89 | % ********************** END HEADER ************************* 90 | % *********************************************************** 91 | -------------------------------------------------------------------------------- /line.py: -------------------------------------------------------------------------------- 1 | import re 2 | from vertex import * 3 | 4 | class Line: 5 | def __init__(self): 6 | self.vertices = [] 7 | self.open = False 8 | def __getitem__(self,index): 9 | return self.vertices[index] 10 | def __setitem__(self,index,value): 11 | self.vertices[index]=value 12 | def additem(self,v): 13 | if re.search('[a-zA-Z]',v.fields[0]): 14 | self.vertices.insert(0,v) 15 | self.open = True 16 | elif re.search('[a-zA-Z]',v.fields[1]): 17 | self.vertices.append(v) 18 | self.open = True 19 | else: 20 | (next,prev)=(str(int(v.fields[1])+1),str(int(v.fields[0])-1)) 21 | i = 0 22 | found = False 23 | for w in self.vertices: 24 | if next == w.fields[0]: 25 | self.vertices.insert(i,v) 26 | found = True 27 | break 28 | if prev == w.fields[1]: 29 | self.vertices.insert(i+1,v) 30 | found = True 31 | break 32 | i=i+1 33 | if not found: 34 | self.vertices.append(v) 35 | 36 | 37 | def __iter__(self): 38 | return iter(self.vertices) 39 | def __len__(self): 40 | len(self.vertices) 41 | def __contains__(self,v): 42 | print "" 43 | print "trying to see if" 44 | print v.fields 45 | print "is connected to" 46 | for w in self.vertices: 47 | print w.fields 48 | contained = False 49 | for w in self.vertices: 50 | try: 51 | if int(v.fields[0])-1 == int(w.fields[1]): 52 | contained = True 53 | break 54 | except ValueError: 55 | pass 56 | try: 57 | if int(v.fields[1])+1 == int(w.fields[0]): 58 | contained = True 59 | break 60 | except ValueError: 61 | pass 62 | 63 | return contained 64 | 65 | #v in self.vertices 66 | def __add__(self,line2): 67 | nline = Line() 68 | for v in self: 69 | nline.additem(v) 70 | for v in line2: 71 | nline.additem(v) 72 | return nline 73 | -------------------------------------------------------------------------------- /main.tex: -------------------------------------------------------------------------------- 1 | \input{header.tex} 2 | 3 | \title{Diagrams} 4 | \author{} 5 | 6 | \begin{document} 7 | \maketitle 8 | \centering 9 | \input{diagrams.tex} 10 | 11 | \end{document} 12 | -------------------------------------------------------------------------------- /propagator.py: -------------------------------------------------------------------------------- 1 | class propagator: 2 | def __init__(self, element): 3 | try: 4 | self.vfrom = element.find("from").text 5 | self.vto = element.find("to").text 6 | self.fromto = {self.vfrom, self.vto} 7 | self.field = element.find("field").text 8 | self.id = element.find("id").text 9 | except: 10 | print "Error while defining propagator object" 11 | def texprint(self,file,particledict,shape=""): 12 | if shape=="": 13 | file.write("{} -- [ {} ] {},\n ".format(self.vfrom, particledict[self.field] ,self.vto)) 14 | else: 15 | file.write("{} -- [ {},{} ] {},\n".format(self.vfrom, particledict[self.field],shape ,self.vto)) 16 | -------------------------------------------------------------------------------- /vertex.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | 4 | def pparse(p): 5 | lp=re.split("[+-]",p) 6 | for mom in [x for x in lp if x!='']: 7 | p=p.replace(mom,mom+",") 8 | lp=p.split(",") 9 | return [x for x in lp if x!=''] 10 | 11 | 12 | class Vertex: 13 | def __init__(self,element): 14 | try: 15 | self.momenta=element.find("momenta").text.split(",") 16 | self.type=element.find("type").text 17 | self.types=element.find("type").text.split(",") 18 | self.fields=element.find("fields").text.split(",") 19 | self.id=element.find("id").text 20 | for i in range(0,len(self.fields)): 21 | if int(self.fields[i])<0: 22 | self.fields[i]="ext"+str(-int(self.fields[i])) 23 | except: 24 | print "Error while defining vertex object" 25 | 26 | def openline(self,file,line): 27 | "print opening the line" 28 | if re.search('[a-zA-Z]',self.fields[0]): 29 | if int(self.fields[0].split("t")[1])%2==0: # a general code should put ubar here 30 | file.write("*(-g_({},{})+m{})".format(line,self.momenta[0],self.types[0].split("bar")[0])) 31 | if int(self.fields[0].split("t")[1])%2 == 1: # a general code should put vbar here 32 | file.write("*(g_({},{})-m{})".format(line,self.momenta[0],self.types[0].split("bar")[0])) 33 | else: 34 | print "this vertex does no seem to contain a psibar !" 35 | 36 | def writenextprop(self,file,line=1): 37 | if not (self.types[1] in ["t","b"]): 38 | print "No fermion coming out of this vertex ! Something is wrong." 39 | elif re.search('[a-zA-Z]',self.fields[1]): 40 | print "This this the end of the line !" 41 | if int(self.fields[1].split("t")[1])%2==1: 42 | file.write("(g_({},{})+m{})".format(line,self.momenta[1],self.types[1])) 43 | if int(self.fields[1].split("t")[1])%2==0: 44 | file.write("(-g_({},{})-m{})".format(line,self.momenta[1],self.types[1])) 45 | else: 46 | file.write("i_*(") 47 | lp=pparse(self.momenta[1]) 48 | for p in lp: 49 | file.write("g_({},{})+".format(line,p)) 50 | file.write("m{})*Denom({},m{})*d_(col{},col{})".format(self.types[1],self.momenta[1],self.types[1],self.fields[1],int(self.fields[1])+1)) 51 | 52 | def __contains__(self,f): 53 | f in self.fields 54 | 55 | def output(self,file,line=1): 56 | if self.type=="tbar,t,g": 57 | file.write("i_*g*g_({},mu{})*T(b{},col{},col{})".format(line,self.fields[2],self.fields[2],self.fields[0],self.fields[1])) 58 | elif self.type=="bbar,b,g": 59 | file.write("i_*g*g_({},mu{})*T(b{},col{},col{})".format(line,self.fields[2],self.fields[2],self.fields[0],self.fields[1])) 60 | elif self.type=="tbar,t,H": 61 | file.write("i_*Y*d_(col{},col{})".format(self.fields[0],self.fields[1])) 62 | elif self.type=="H,H,H": 63 | file.write("i_*h3") 64 | elif self.type=="H,H,H,H": 65 | file.write("i_*h4") 66 | elif self.type=="g,g,g": 67 | file.write("(-g3*f(b{},b{},b{})*(0".format(self.fields[0],self.fields[1],self.fields[2])) 68 | for i in range(0,3): 69 | j=(i+1)%3 70 | k=(i+2)%3 71 | p1=self.momenta[i] 72 | p2=self.momenta[j] 73 | lp1=pparse(p1) 74 | lp2=pparse(p2) 75 | #add the index k to each individual momentum and then p-k 76 | for mom in lp1: 77 | if p1!="0": 78 | p1=p1.replace(mom,mom+"(mu{})".format(self.fields[k])) 79 | for mom in lp2: 80 | if p2!="0": 81 | p2=p2.replace(mom,mom+"(mu{})".format(self.fields[k])) 82 | p="({}-({}))".format(p1,p2) 83 | file.write("+d_(mu{},mu{})*{}".format(self.fields[i],self.fields[j],p)) 84 | file.write("))") 85 | elif self.type=="g,g,g,g": 86 | i=self.fields[0] 87 | j=self.fields[1] 88 | k=self.fields[2] 89 | l=self.fields[3] 90 | file.write("(-i_)*g^2*(") 91 | 92 | 93 | file.write("f(b{},b{},bdummy)*f(b{},b{},bdummy)*(d_(mu{},mu{})*d_(mu{},mu{})-d_(mu{},mu{})*d_(mu{},mu{}))".format(i,j,k,l,i,k,j,l,i,l,j,k)) 94 | 95 | file.write(" +f(b{},b{},bdummy)*f(b{},b{},bdummy)*(d_(mu{},mu{})*d_(mu{},mu{})-d_(mu{},mu{})*d_(mu{},mu{}))".format(i,k,j,l,i,j,k,l,i,l,j,k)) 96 | 97 | file.write(" +f(b{},b{},bdummy)*f(b{},b{},bdummy)*(d_(mu{},mu{})*d_(mu{},mu{})-d_(mu{},mu{})*d_(mu{},mu{}))".format(i,l,j,k,i,j,k,l,i,k,j,l)) 98 | file.write(")") 99 | else: 100 | print "ERROR: Unknown vertex type" 101 | print self.type 102 | -------------------------------------------------------------------------------- /xmldraw.sty: -------------------------------------------------------------------------------- 1 | 2 | <> 3 | <><> 4 | <> 5 | <> 6 | <> 7 | 8 | 9 | <> 10 | 11 | <><> 12 | <> 13 | <> 14 | 15 | <> 16 | <> 17 | 18 | <> 19 | <> 20 | <><> 21 | 22 | <> 23 | 24 | <> 25 | <><> 26 | <><> 27 | <>in<> 28 | <><> 29 | <> 30 | 31 | 32 | <> 33 | <><> 34 | <><> 35 | <>out<> 36 | <><> 37 | <> 38 | 39 | <> 40 | 41 | <> 42 | 43 | <> 44 | <>m<> 45 | <><> 46 | <><> 47 | <><> 48 | <><> 49 | <><> 50 | <> 51 | 52 | <> 53 | 54 | <> 55 | 56 | <> 57 | <><> 58 | <>,<> 59 | <>,<> 60 | <>,<> 61 | <> 62 | 63 | <> 64 | 65 | <> 66 | 67 | <> 68 | <> 69 | 70 | 71 | --------------------------------------------------------------------------------