Skip to content

Commit 50b2461

Browse files
committed
init root renderer
1 parent fa8a672 commit 50b2461

File tree

4 files changed

+459
-1
lines changed

4 files changed

+459
-1
lines changed

pyfeyn2/render/root/pyfeyn.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Adapted code from https://github.com/aminnj/pyfeyn/blob/main/pyfeyn.py
2+
import math
3+
4+
5+
class Label(object):
6+
def __init__(
7+
self,
8+
text="",
9+
x1=0.0,
10+
y1=0.0,
11+
offsetx=0,
12+
offsety=0,
13+
textsize=0.08,
14+
textalign=22,
15+
roman=False,
16+
):
17+
self.x1 = x1
18+
self.y1 = y1
19+
self.text = text
20+
self.offsetx = offsetx
21+
self.offsety = offsety
22+
self.textsize = textsize
23+
self.textalign = textalign
24+
self.roman = roman
25+
26+
def set_location(self, x, y):
27+
self.x1 = x
28+
self.y1 = y
29+
30+
def transform_text(self, text):
31+
# ROOT needs one of these characters to put in a $ and go into mathmode
32+
# otherwise we do it explicitly
33+
if self.roman or any([x in text for x in "#{}^"]):
34+
return text
35+
text = "${0}$".format(text)
36+
return text
37+
38+
def draw(self):
39+
import ROOT
40+
41+
if not self.text:
42+
return
43+
t = ROOT.TLatex()
44+
t.SetTextAlign(self.textalign)
45+
t.SetTextSize(self.textsize)
46+
t.DrawLatex(
47+
self.x1 + self.offsetx,
48+
self.y1 + self.offsety,
49+
self.transform_text(self.text),
50+
)
51+
52+
53+
class Marker(object):
54+
def __init__(self, color=None, radius=2, linewidth=0):
55+
self.x = None
56+
self.y = None
57+
self.color = color
58+
self.radius = radius
59+
self.linewidth = linewidth
60+
61+
def set_location(self, x, y):
62+
self.x = x
63+
self.y = y
64+
65+
def draw(self):
66+
import ROOT
67+
68+
if self.color is None:
69+
return
70+
m = ROOT.TEllipse(self.x, self.y, self.radius)
71+
m.SetFillColor(self.color)
72+
m.SetLineWidth(self.linewidth)
73+
m.Draw()
74+
75+
76+
class Vertex(object):
77+
def __init__(self, x1, y1, label=Label(), marker=Marker(), autolabel=True):
78+
self.x1 = x1
79+
self.y1 = y1
80+
self.label = label
81+
self.marker = marker
82+
83+
self.marker.set_location(x1, y1)
84+
85+
if autolabel:
86+
self.label.set_location(self.x1, self.y1)
87+
88+
def draw(self, _nodelete=[]):
89+
self.label.draw()
90+
self.marker.draw()
91+
92+
93+
class Propagator(object):
94+
def __init__(
95+
self,
96+
v1,
97+
v2,
98+
typ="line",
99+
label=Label(),
100+
autolabel=True,
101+
linewidth=2,
102+
linecolor=None,
103+
fliparrow=False,
104+
noarrow=False,
105+
):
106+
import ROOT
107+
108+
if linecolor is None:
109+
linecolor = ROOT.kBlack
110+
self.v1 = v1
111+
self.v2 = v2
112+
self.typ = typ
113+
self.label = label
114+
self.linewidth = linewidth
115+
self.linecolor = linecolor
116+
self.fliparrow = fliparrow
117+
self.noarrow = noarrow
118+
119+
if autolabel:
120+
self.label.set_location(
121+
0.5 * (self.v1.x1 + self.v2.x1), 0.5 * (self.v1.y1 + self.v2.y1)
122+
)
123+
124+
def draw(self, _nodelete=[]):
125+
import ROOT as r
126+
127+
prop1, prop2 = None, None
128+
drawopt = ""
129+
if self.typ == "line":
130+
prop1 = r.TLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
131+
if self.typ == "dashedline":
132+
prop1 = r.TLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
133+
r.gStyle.SetLineStyleString(11, "50 30")
134+
prop1.SetLineStyle(11)
135+
if self.typ == "dottedline":
136+
prop1 = r.TLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
137+
r.gStyle.SetLineStyleString(11, "20 50")
138+
prop1.SetLineStyle(11)
139+
elif self.typ == "curlyline":
140+
prop1 = r.TCurlyLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
141+
prop1.SetWaveLength(prop1.GetWaveLength() * 1.6)
142+
prop1.SetAmplitude(prop1.GetAmplitude() * 1.4)
143+
elif self.typ == "wavyline":
144+
prop1 = r.TCurlyLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
145+
prop1.SetWavy()
146+
prop1.SetWaveLength(prop1.GetWaveLength() * 1.6)
147+
prop1.SetAmplitude(prop1.GetAmplitude() * 1.4)
148+
elif self.typ == "wavystraightline":
149+
prop1 = r.TCurlyLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
150+
prop1.SetWavy()
151+
prop1.SetWaveLength(prop1.GetWaveLength() * 1.6)
152+
prop1.SetAmplitude(prop1.GetAmplitude() * 1.4)
153+
prop2 = r.TLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
154+
elif self.typ == "curlystraightline":
155+
prop1 = r.TCurlyLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
156+
prop1.SetWaveLength(prop1.GetWaveLength() * 1.6)
157+
prop1.SetAmplitude(prop1.GetAmplitude() * 1.4)
158+
prop2 = r.TLine(self.v1.x1, self.v1.y1, self.v2.x1, self.v2.y1)
159+
elif self.typ.startswith("wavyarc"):
160+
# wavyarc(180,0) -> phimin = 180, phimax = 0
161+
phimin, phimax = list(
162+
map(float, self.typ.split("(", 1)[1].split(")", 1)[0].split(","))
163+
)
164+
xc = 0.5 * (self.v1.x1 + self.v2.x1)
165+
yc = 0.5 * (self.v1.y1 + self.v2.y1)
166+
radius = 0.5 * (self.v2.x1 - self.v1.x1)
167+
prop1 = r.TCurlyArc(xc, yc, radius, phimin, phimax)
168+
prop1.SetWavy()
169+
prop1.SetWaveLength(prop1.GetWaveLength() * 1.6)
170+
prop1.SetAmplitude(prop1.GetAmplitude() * 1.4)
171+
prop1.SetFillColorAlpha(0, 0.0)
172+
drawopt = "only"
173+
elif self.typ.startswith("arc"):
174+
# arc(180,0) -> phimin = 180, phimax = 0
175+
phimin, phimax = list(
176+
map(float, self.typ.split("(", 1)[1].split(")", 1)[0].split(","))
177+
)
178+
xc = 0.5 * (self.v1.x1 + self.v2.x1)
179+
yc = 0.5 * (self.v1.y1 + self.v2.y1)
180+
radius = 0.5 * (self.v2.x1 - self.v1.x1)
181+
prop1 = r.TArc(xc, yc, radius, phimin, phimax)
182+
prop1.SetFillColorAlpha(0, 0.0)
183+
drawopt = "only"
184+
185+
if prop1:
186+
prop1.SetLineColor(self.linecolor)
187+
prop1.SetLineWidth(self.linewidth)
188+
prop1.Draw(drawopt)
189+
_nodelete.append(prop1)
190+
if prop2:
191+
prop2.SetLineColor(self.linecolor)
192+
prop2.SetLineWidth(self.linewidth)
193+
prop2.Draw(drawopt)
194+
_nodelete.append(prop2)
195+
196+
if not self.noarrow:
197+
if self.typ in ["line", "dashedline"]:
198+
c1 = self.v1.x1, self.v1.y1
199+
c2 = self.v2.x1, self.v2.y1
200+
if self.fliparrow:
201+
c1, c2 = c2, c1
202+
mult = 0.54
203+
awidth = 0.025
204+
a1 = r.TArrow(
205+
c1[0],
206+
c1[1],
207+
(1.0 - mult) * c1[0] + mult * c2[0],
208+
(1.0 - mult) * c1[1] + mult * c2[1],
209+
awidth,
210+
"|>",
211+
)
212+
a1.SetLineWidth(0)
213+
a1.SetFillColor(self.linecolor)
214+
a1.SetAngle(40)
215+
a1.Draw()
216+
_nodelete.append(a1)
217+
elif self.typ.startswith("arc"):
218+
phimin, phimax = list(
219+
map(float, self.typ.split("(", 1)[1].split(")", 1)[0].split(","))
220+
)
221+
phi = (0.5 * (phimax - phimin) % 360) * math.pi / 180
222+
radius = 0.5 * (self.v2.x1 - self.v1.x1)
223+
xc = 0.5 * (self.v1.x1 + self.v2.x1) + radius * math.cos(phi)
224+
yc = 0.5 * (self.v1.y1 + self.v2.y1) + radius * math.sin(phi)
225+
dx, dy = -radius * 0.2 * math.sin(phi), -radius * 0.2 * math.cos(phi)
226+
if self.fliparrow:
227+
dx, dy = -dx, -dy
228+
awidth = 0.025
229+
a1 = r.TArrow(
230+
xc - dx / 2, yc - dy / 2, xc + dx / 2, yc + dy / 2, awidth, "|>"
231+
)
232+
a1.SetLineWidth(0)
233+
a1.SetFillColor(self.linecolor)
234+
a1.SetAngle(40)
235+
a1.Draw()
236+
_nodelete.append(a1)
237+
238+
self.v1.draw()
239+
self.v2.draw()
240+
self.label.draw()

pyfeyn2/render/root/rootpdfrender.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import tempfile
2+
from typing import List
3+
4+
from pylatex import Command
5+
from pylatex.utils import NoEscape
6+
7+
from pyfeyn2.render.latex.latex import LatexRender
8+
from pyfeyn2.render.render import Render
9+
from pyfeyn2.render.root.pyfeyn import Propagator, Vertex
10+
from pyfeyn2.render.root.rootrender import ROOTRender
11+
12+
13+
def root_to_latex(root_canvas) -> str:
14+
# create a tmp tex file
15+
with tempfile.NamedTemporaryFile(
16+
suffix=".tex", delete=True, mode="w+"
17+
) as temp_file:
18+
root_canvas.SaveAs(temp_file.name)
19+
# read the file
20+
tex_src = temp_file.read()
21+
return tex_src
22+
raise RuntimeError("Failed to create temporary file")
23+
24+
25+
class ROOTPDFRender(LatexRender, ROOTRender):
26+
def __init__(
27+
self,
28+
fd=None,
29+
documentclass="standalone",
30+
document_options=None,
31+
*args,
32+
**kwargs,
33+
):
34+
if document_options is None:
35+
document_options = ["preview", "crop", "tikz"]
36+
super().__init__(
37+
*args,
38+
fd=fd,
39+
documentclass=documentclass,
40+
document_options=document_options,
41+
**kwargs,
42+
)
43+
self.preamble.append(Command("RequirePackage", "luatex85"))
44+
self.preamble.append(
45+
Command("usepackage", NoEscape("tikz-feynman"), "compat=1.1.0")
46+
)
47+
48+
def render(
49+
self,
50+
file=None,
51+
show=True,
52+
resolution=100,
53+
width=None,
54+
height=None,
55+
clean_up=True,
56+
):
57+
# First need to convert feynman to ROOT through ROOTRender
58+
ROOTRender.render(
59+
self,
60+
file=None,
61+
show=False,
62+
resolution=resolution,
63+
width=width,
64+
height=height,
65+
)
66+
# Then convert ROOT to LaTeX
67+
self.set_src_diag(NoEscape(root_to_latex(self.get_src_root())))
68+
LatexRender.render(self, file, show, resolution, width, height, clean_up)
69+
70+
@classmethod
71+
def valid_styles(cls) -> bool:
72+
return super().valid_styles() + [
73+
"line",
74+
"symbol",
75+
"symbol-size",
76+
"color",
77+
"opacity",
78+
"bend-direction",
79+
"bend-in",
80+
"bend-out",
81+
"bend-loop",
82+
"bend-min-distance",
83+
"momentum-arrow",
84+
"momentum-arrow-sense",
85+
"double-distance",
86+
"label-side",
87+
]
88+
89+
@classmethod
90+
def valid_attributes(cls) -> List[str]:
91+
return super().valid_attributes() + [
92+
"x",
93+
"y",
94+
"label",
95+
"style",
96+
]
97+
98+
@classmethod
99+
def valid_types(cls) -> List[str]:
100+
return super().valid_types() + []
101+
102+
@classmethod
103+
def valid_shapes(cls) -> List[str]:
104+
return super().valid_types() + []

0 commit comments

Comments
 (0)