decaylanguage 0.1.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
File without changes
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ from __future__ import absolute_import
5
+ from __future__ import division
6
+ from __future__ import print_function
7
+
8
+ import datetime
9
+ from six import StringIO
10
+
11
+ from functools import partial
12
+
13
+ from plumbum import colors
14
+
15
+ from decaylanguage.decay.goofit import GooFitChain
16
+ from decaylanguage.decay.goofit import SF_4Body
17
+ from decaylanguage.particle import SpinType
18
+
19
+
20
+ def ampgen2goofit(filename, ret_output=False):
21
+ if ret_output:
22
+ output = StringIO()
23
+ printer = partial(print, file=output)
24
+ else:
25
+ printer = print
26
+
27
+ lines, all_states = GooFitChain.read_AmpGen(str(filename))
28
+
29
+ printer(r'/* Autogenerated file by AmpGen2GooFit')
30
+ printer('Generated on ', datetime.datetime.now())
31
+
32
+ printer('\n')
33
+ for seen_factor in {p.spindetails() for p in lines}:
34
+ my_lines = [p for p in lines if p.spindetails() == seen_factor]
35
+ printer(colors.bold | seen_factor, ":", *my_lines[0].spinfactors)
36
+ for line in my_lines:
37
+ printer(' ', colors.blue | str(line))
38
+
39
+ printer('\n')
40
+ for spintype in SpinType:
41
+ ps = [format(str(p), '11')
42
+ for p in sorted(GooFitChain.all_particles) if p.spintype == spintype]
43
+ printer("{spintype.name:>12}:".format(spintype=spintype), *ps)
44
+
45
+ printer('\n')
46
+ for n, line in enumerate(lines):
47
+ printer('{n:2} {line!s:<70} spinfactors: {lensf} L: {line.L} [{Lr[0]}-{Lr[1]}]'
48
+ .format(n=n, line=line, lensf=len(line.spinfactors), Lr=line.L_range()))
49
+
50
+ # We can make the GooFit Intro code:
51
+
52
+ printer(colors.bold & colors.green | '\n\nAll discovered spin configurations:')
53
+
54
+ for line in sorted({line.spindetails() for line in lines}):
55
+ printer(colors.green | line)
56
+
57
+ printer(colors.bold & colors.blue | '\n\nAll known spin configurations:')
58
+
59
+ # TODO: 4 body only
60
+ for e in SF_4Body:
61
+ printer(colors.blue | e.name)
62
+
63
+ printer('\n*/\n\n // Intro')
64
+ printer(GooFitChain.make_intro(all_states))
65
+
66
+ printer('\n\n // Parameters')
67
+ printer(GooFitChain.make_pars())
68
+
69
+ # And the lines can be turned into code, as well:
70
+
71
+ printer('\n\n // Lines')
72
+ for n, line in enumerate(lines):
73
+ printer(' // Line', n)
74
+ printer(line.to_goofit(all_states[1:]), end='\n\n\n')
75
+
76
+ if(ret_output):
77
+ return output.getvalue()
@@ -0,0 +1,79 @@
1
+ '''
2
+ ampline.py
3
+
4
+ A collection of regular expressions that pick out information about line.
5
+
6
+ The regular expressions make named groups that you can access.
7
+ '''
8
+
9
+ from __future__ import absolute_import
10
+ from __future__ import division
11
+ from __future__ import print_function
12
+
13
+ import re
14
+
15
+ inverted = re.compile(r'''
16
+ ^ # Beginning of string
17
+ (?P<line> [^=]+ ) # All chars until =
18
+ = # Must have an equals sign
19
+ \s* (?P<factor> [0-9\-\.]* ) # Optional
20
+ \s* \*? # Eat a * sign if needed
21
+ \s* (?P<invline> [^=]+ ) # The inverted line
22
+ $ # End of string
23
+ ''', re.VERBOSE)
24
+
25
+ dual = re.compile(r'''
26
+ ^ # Beginning of string
27
+ (?P<name> [^\{^\[]+ ) # All chars until { or [
28
+ (?:\[ # Start of []
29
+ (?P<spinfactor> \w # Optional 1 char spin factor
30
+ (?= \W ) # - Look ahead for non-word
31
+ )? ;? # Remove ; if exists
32
+ (?P<lineshape> [^\]]* )? # The rest of the lineshape
33
+ \] )? # Done with []
34
+ (?:\{ (?P<daughters> .* ) \} )? # All daughters
35
+ \s+ (?P<fix_r> [02] ) # Real fix
36
+ \s+ (?P<A> [0-9\.\-]+ ) # Real value
37
+ \s+ (?P<dA> [0-9\.\-]+ ) # Real error
38
+ \s+ (?P<fix_i> [02] ) # Imag fix
39
+ \s+ (?P<theta> [0-9\.\-]+ ) # Imag value
40
+ \s+ (?P<dtheta> [0-9\.\-]+ ) # Imag error
41
+ $ # End of string
42
+ ''', re.VERBOSE)
43
+
44
+ cartesian = re.compile(r'''
45
+ ^ # Beginning of string
46
+ (?P<name> .+ ) # All chars of name
47
+ _ (?P<cart> Re|Im ) # Real or imaginary
48
+ \s+ (?P<fix> [02] ) # Value fix
49
+ \s+ (?P<amp> [0-9\.\-]+ ) # Value
50
+ \s+ (?P<err> [0-9\.\-]+ ) # Error
51
+ $ # End of string
52
+ ''', re.VERBOSE)
53
+
54
+ partial = re.compile(r'''
55
+ ^ # Beginning of string
56
+ (?P<name> [^\{^\[]+ ) # All chars until { or [
57
+ (?:\[ # Start of []
58
+ (?P<spinfactor> \w # Optional 1 char spin factor
59
+ (?= \W ) # - Look ahead for non-word
60
+ )? ;? # Remove ; if exists
61
+ (?P<lineshape> [^\]]* )? # The rest of the lineshape
62
+ \] )? # Done with []
63
+ (?:\{ (?P<daughters> .* ) \} )? # All daughters
64
+ $ # End of string
65
+ ''', re.VERBOSE)
66
+
67
+ settings = re.compile(r'''
68
+ ^ # Beginning of string
69
+ (?P<name> # Choice list
70
+ FastCoherentSum::UseCartesian
71
+ | output
72
+ | nEvents
73
+ | EventType
74
+ )
75
+ \s+ # White space
76
+ (?P<value> .*? ) # Everything else
77
+ \s* # Strip remainging whitespace
78
+ $ # End of string
79
+ ''', re.VERBOSE)
@@ -0,0 +1,323 @@
1
+ from __future__ import absolute_import
2
+ from __future__ import division
3
+ from __future__ import print_function
4
+
5
+ from copy import copy
6
+ from enum import Enum
7
+ from itertools import combinations
8
+ from itertools import product
9
+
10
+ import attr
11
+ import graphviz
12
+ import numpy as np
13
+ import pandas as pd
14
+
15
+ from . import ampline
16
+ from ..particle import Particle
17
+ from .utilities import filter_lines
18
+ from .utilities import iter_flatten
19
+ from .utilities import split
20
+
21
+
22
+ class LS(Enum):
23
+ 'Line shapes supported (currently)'
24
+ RBW = 0
25
+ GSpline = 1
26
+ kMatrix = 2
27
+ FOCUS = 3
28
+
29
+
30
+ @attr.s(slots=True)
31
+ class AmplitudeChain(object):
32
+ particle = attr.ib()
33
+ daughters = attr.ib([], convert=lambda x: x if x else [])
34
+ lineshape = attr.ib(None)
35
+ spinfactor = attr.ib(None)
36
+ amp = attr.ib(1+0j, cmp=False, validator=attr.validators.instance_of(complex))
37
+ err = attr.ib(0+0j, cmp=False, validator=attr.validators.instance_of(complex))
38
+ fix = attr.ib(True, cmp=False, validator=attr.validators.instance_of(bool))
39
+ name = attr.ib(None)
40
+
41
+ def __attrs_post_init__(self):
42
+ if self.name is None:
43
+ self.name = self.particle.name
44
+
45
+ # Class members keep track of additions
46
+ all_particles = set()
47
+ final_particles = set()
48
+
49
+ # This is set class-wide, and only used when a line is made
50
+ cartesian = False
51
+
52
+ @classmethod
53
+ def from_matched_line(cls, mat):
54
+ '''
55
+ This operates on an already-matched line.
56
+
57
+ :param mat: The groupdict output of a match
58
+ :return: A new amplitude chain instance
59
+ '''
60
+ mat['particle'] = Particle.from_AmpGen(mat['name'])
61
+
62
+ if mat['particle'] not in cls.all_particles:
63
+ cls.all_particles |= {mat['particle']}
64
+
65
+ if mat['daughters']:
66
+ mat['daughters'] = [cls.from_matched_line(ampline.partial.match(
67
+ x).groupdict()) for x in split(mat['daughters'])]
68
+
69
+ # This is only true if not a parital line
70
+ if 'fix_r' in mat:
71
+ # Slightly odd, since we use cartesian, so fixing theta or mag
72
+ # of amplitude only doesn't work
73
+ mat['fix'] = mat['fix_r'] == '2' and mat['fix_i'] == '2'
74
+ del mat['fix_r'], mat['fix_i']
75
+
76
+ if cls.cartesian:
77
+ mat['amp'] = float(mat['A']) + float(mat['theta'])*1j
78
+ mat['err'] = float(mat['theta']) + float(mat['dtheta'])*1j
79
+
80
+ else:
81
+ A = float(mat['A'])
82
+ dA = float(mat['dA'])
83
+ theta = float(mat['theta'])
84
+ dtheta = float(mat['dtheta'])
85
+ mat['amp'] = A * np.exp(theta*1j)
86
+
87
+ mat['err'] = ((dA*np.cos(theta) + A*np.sin(dtheta))
88
+ + (dA*np.sin(theta) + A*np.cos(dtheta))*1j)
89
+
90
+ del mat['A'], mat['dA'], mat['theta'], mat['dtheta']
91
+
92
+ return cls(**mat)
93
+
94
+ def expand_lines(self, linelist):
95
+ '''
96
+ Take a DecayTree -> list of DecayTrees with each dead-end daughter
97
+ expanded to every possible combination. (recursive)
98
+
99
+ '''
100
+
101
+ # If the Tree has daugthers, run on those
102
+ if self.daughters:
103
+ dlist = [d.expand_lines(linelist) for d in self.daughters]
104
+ retlist = []
105
+ for ds in product(*dlist):
106
+ newd = copy(self)
107
+ newd.daughters = ds
108
+ retlist.append(newd)
109
+ return retlist
110
+
111
+ # If the tree ends
112
+ new_trees = [l for line in linelist if line.name ==
113
+ self.name for l in line.expand_lines(linelist)]
114
+ if new_trees:
115
+ return new_trees
116
+ else:
117
+ self.__class__.final_particles |= {self.particle}
118
+ return [self]
119
+
120
+ @property
121
+ def ls_enum(self):
122
+ if not self.lineshape:
123
+ return LS.RBW
124
+ elif 'GSpline.EFF' == self.lineshape:
125
+ return LS.GSpline
126
+ elif self.lineshape.startswith('kMatrix'):
127
+ return LS.kMatrix
128
+ elif self.lineshape.startswith('FOCUS'):
129
+ return LS.FOCUS
130
+ else:
131
+ raise RuntimeError("Unimplemented lineshape {0}".format(self.lineshape))
132
+
133
+ @property
134
+ def full_amp(self):
135
+ amp = self.amp
136
+ for d in self.daughters:
137
+ amp *= d.full_amp
138
+ return amp
139
+
140
+ def is_vertex(self):
141
+ return len(self) == 2
142
+
143
+ def is_strong(self):
144
+ if not self.is_vertex():
145
+ return None
146
+ return set(self.particle.quarks) == set(self[0].particle.quarks).union(set(self[1].particle.quarks))
147
+
148
+ def L_range(self, conserveParity=False):
149
+ S = self.particle.J
150
+ s1 = self[0].particle.J
151
+ s2 = self[1].particle.J
152
+ min_spin = abs(S-s1-s2)
153
+ min_spin = min(abs(S+s1-s2), min_spin)
154
+ min_spin = min(abs(S-s1+s2), min_spin)
155
+ max_spin = S + s1 + s2
156
+ if not conserveParity:
157
+ return (min_spin, max_spin)
158
+ else:
159
+ raise RuntimeError("Strong decays not implemented yet")
160
+
161
+ @property
162
+ def L(self):
163
+ if self.spinfactor:
164
+ return 'S P D F'.split().index(self.spinfactor)
165
+ min_L, _ = self.L_range()
166
+ return min_L # Ground state unless specified
167
+
168
+ def __len__(self):
169
+ return len(self.daughters)
170
+
171
+ def __getitem__(self, item):
172
+ return self.daughters[item]
173
+
174
+ def _get_html(self):
175
+ name = self.particle.html_name
176
+
177
+ if self.spinfactor or self.lineshape:
178
+ name += '<br/><br/>'
179
+ if self.spinfactor:
180
+ name += '<font color="blue">[' + self.spinfactor + ']</font>'
181
+ if self.lineshape:
182
+ name += '<font color="red">[' + self.lineshape + ']</font>'
183
+ return name
184
+
185
+ def _add_nodes(self, drawing):
186
+ name = self._get_html()
187
+ drawing.node(str(id(self)), "<" + name + ">")
188
+ for p in self.daughters:
189
+ drawing.edge(str(id(self)), str(id(p)))
190
+ p._add_nodes(drawing)
191
+
192
+ def _make_graphvis(self):
193
+ d = graphviz.Digraph()
194
+ d.attr(labelloc='t', label=str(self))
195
+ self._add_nodes(d)
196
+ return d
197
+
198
+ def _repr_svg_(self):
199
+ return self._make_graphvis()._repr_svg_()
200
+
201
+ @property
202
+ def vertexes(self):
203
+ verts = []
204
+ for d in self.daughters:
205
+ if d.is_vertex():
206
+ verts.append(d)
207
+ verts += d.vertexes
208
+ return verts
209
+
210
+ @property
211
+ def structure(self):
212
+ '''
213
+ The structure of the decay chain, simplified to only final state particles
214
+ '''
215
+ if self.daughters:
216
+ return [d.structure for d in self.daughters]
217
+ else:
218
+ return self.particle
219
+
220
+ def list_structure(self, final_states):
221
+ '''
222
+ The structure in the form [(0,1,2,3)], where the dual-list is used
223
+ for permutations for bose symmatrization.
224
+ So for final_states=[a,b,c,c], [a,c,[c,b]] would be:
225
+ [(0,2,3,1),(0,3,2,1)]
226
+ '''
227
+
228
+ structure = list(iter_flatten(self.structure))
229
+
230
+ if set(structure) - set(final_states):
231
+ raise RuntimeError("The final states must encompass all particles in final states!")
232
+
233
+ possibilities = [[i for i, v in enumerate(final_states) if v == name] for name in structure]
234
+ return [a for a in product(*possibilities) if len(set(a)) == len(a)]
235
+
236
+ def __str__(self):
237
+ name = str(self.particle)
238
+ if self.lineshape and self.spinfactor:
239
+ name += '[' + self.spinfactor + ';' + self.lineshape + ']'
240
+ elif self.lineshape:
241
+ name += '[' + self.lineshape + ']'
242
+ elif self.spinfactor:
243
+ name += '[' + self.spinfactor + ']'
244
+ if self.daughters:
245
+ name += '{'+','.join(map(str, self.daughters))+'}'
246
+ return name
247
+
248
+ @classmethod
249
+ def read_AmpGen(cls, filename=None, text=None):
250
+ '''
251
+ Read in an ampgen file
252
+
253
+ :param filename: Filename
254
+ :return: array of AmplitudeChains, parameters, constants, event type
255
+ '''
256
+
257
+ # Read the file in, ignore empty lines and comments
258
+ if filename is not None:
259
+ with open(filename) as f:
260
+ valid_lines = [l.strip().rstrip(',') for l in f if l and not l.startswith('#')]
261
+ elif text is not None:
262
+ valid_lines = [l.strip().rstrip(',')
263
+ for l in text.splitlines() if l and not l.startswith('#')]
264
+ else:
265
+ raise RuntimeError("Must have filename or text")
266
+
267
+ # Collect known options
268
+ option_lines, valid_lines = filter_lines(ampline.settings, valid_lines)
269
+
270
+ # Collect lines with an = in them
271
+ relation_lines, valid_lines = filter_lines(ampline.inverted, valid_lines)
272
+
273
+ # Collect "normal" non-Cartesian lines
274
+ real_lines, valid_lines = filter_lines(ampline.dual, valid_lines)
275
+
276
+ # Collect Cartesian lines (need combining)
277
+ cart_lines, valid_lines = filter_lines(ampline.cartesian, valid_lines)
278
+
279
+ # The other lines do not need explicit filtering
280
+ variable_lines = [l for l in valid_lines if len(l.split()) == 4]
281
+ constant_lines = [l for l in valid_lines if len(l.split()) == 2]
282
+
283
+ # Process the options
284
+ all_states = None
285
+ for mat in option_lines:
286
+ if mat['name'] == 'EventType':
287
+ all_states = [Particle.from_AmpGen(name) for name in mat['value'].split()]
288
+ elif mat['name'] == 'FastCoherentSum::UseCartesian':
289
+ cls.cartesian = bool(mat['value'])
290
+
291
+ # Make sure this exists!
292
+ if all_states is None:
293
+ raise RuntimeError("EventType is missing! Cannot compute decay.")
294
+
295
+ # Combine dual line Cartesian lines into traditional cartesian lines
296
+ for a, b in combinations(cart_lines, 2):
297
+ if a['name'] == b['name']:
298
+ if a['cart'] == 'Re' and b['cart'] == 'Im':
299
+ pass
300
+ elif a['cart'] == 'Im' and b['cart'] == 'Re':
301
+ a, b = b, a
302
+ else:
303
+ raise RuntimeError("Can't process a line with *both* components Re or Im")
304
+ new_string = "{a[name]} {a[fix]} {a[amp]} {a[err]} {b[fix]} {b[amp]} {b[err]}".format(
305
+ a=a, b=b)
306
+ real_lines.append(ampline.dual.match(new_string).groupdict())
307
+
308
+ # Make the partial lines and constants as dataframes
309
+ parameters = pd.DataFrame(((v.strip() for v in p.split()) for p in variable_lines),
310
+ columns='name fix value error'.split()).set_index('name')
311
+
312
+ constants = pd.DataFrame(((v.strip() for v in p.split()) for p in constant_lines),
313
+ columns='name value'.split()).set_index('name')
314
+
315
+ # Convert the matches into AmplitudeChains
316
+ line_arr = [cls.from_matched_line(a) for a in real_lines]
317
+
318
+ # Expand partial lines into complete lines
319
+ new_line_arr = [l for line in line_arr if line.particle == all_states[0]
320
+ for l in line.expand_lines(line_arr)]
321
+
322
+ # Return
323
+ return new_line_arr, parameters, constants, all_states