1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Classes, methods and functions required to write QCD color information
17 for a diagram and build a color basis, and to square a QCD color string for
18 squared diagrams and interference terms."""
19
20 import copy
21 import fractions
22 import operator
23 import re
24 import array
25
26 import madgraph.core.color_algebra as color_algebra
27 import madgraph.core.diagram_generation as diagram_generation
28 import madgraph.core.base_objects as base_objects
34 """The ColorBasis object is a dictionary created from an amplitude. Keys
35 are the different color structures present in the amplitude. Values have
36 the format (diag,(index c1, index c2,...), coeff, is_imaginary, Nc_power)
37 where diag is the diagram index, (index c1, index c2,...) the list of
38 indices corresponding to the chose color parts for each vertex in the
39 diagram, coeff the corresponding coefficient (a fraction), is_imaginary
40 if this contribution is real or complex, and Nc_power the Nc power."""
41
42
43 _canonical_dict = {}
44
45
46 _list_color_dict = []
47
48
50 """Exception raised if an error occurs in the definition
51 or the execution of a color basis object."""
52 pass
53
55 """Takes a diagram and a model and outputs a dictionary with keys being
56 color coefficient index tuples and values a color string (before
57 simplification)."""
58
59
60 min_index = -1000
61
62 res_dict = {}
63
64 repl_dict = {}
65
66 for i, vertex in enumerate(diagram.get('vertices')):
67 min_index, res_dict = self.add_vertex(vertex, diagram, model,
68 repl_dict, res_dict, min_index)
69
70
71
72 empty_colorstring = color_algebra.ColorString()
73 if all(cs == empty_colorstring for cs in res_dict.values()):
74 res_dict = dict((key, color_algebra.ColorString(
75 [color_algebra.ColorOne()])) for key in res_dict)
76
77 return res_dict
78
79
80
81 - def add_vertex(self, vertex, diagram, model,
82 repl_dict, res_dict, min_index, id0_rep=[]):
83 """Update repl_dict, res_dict and min_index for normal vertices.
84 Returns the min_index reached and the result dictionary in a tuple.
85 If the id0_rep list is not None, perform the requested replacement on the
86 last leg number before going further."""
87
88
89
90
91 color_num_pairs = []
92 pdg_codes = []
93
94 for index, leg in enumerate(vertex.get('legs')):
95 curr_num = leg.get('number')
96 curr_part = model.get('particle_dict')[leg.get('id')]
97 curr_color = curr_part.get_color()
98 curr_pdg = curr_part.get_pdg_code()
99
100
101
102
103 if index == len(vertex.get('legs')) - 1 and \
104 curr_num in id0_rep:
105 curr_num = id0_rep[id0_rep.index(curr_num) - 1]
106
107
108
109
110 if index == len(vertex.get('legs')) - 1 and \
111 vertex != diagram.get('vertices')[-1]:
112 curr_color = curr_part.get_anti_color()
113 curr_pdg = curr_part.get_anti_pdg_code()
114 if not id0_rep:
115 if not ( diagram.get('vertices')[-1].get('id')==-1 and \
116 vertex == diagram.get('vertices')[-2]):
117 repl_dict[curr_num] = min_index
118 min_index = min_index - 1
119 else:
120 repl_dict[curr_num] = \
121 max(l.get('number') for l in \
122 diagram.get('vertices')[-1].get('legs'))
123
124
125 try:
126 curr_num = repl_dict[curr_num]
127 except KeyError:
128 pass
129
130 color_num_pairs.append((curr_color, curr_num))
131 pdg_codes.append(curr_pdg)
132
133 if vertex != diagram.get('vertices')[-1]:
134
135
136 last_color_num = color_num_pairs.pop(-1)
137 color_num_pairs.insert(0, last_color_num)
138 last_pdg = pdg_codes.pop(-1)
139 pdg_codes.insert(0, last_pdg)
140
141
142 if vertex.get('id')!=-1:
143 interaction_pdgs = [p.get_pdg_code() for p in \
144 model.get_interaction(vertex.get('id')).\
145 get('particles')]
146 else:
147 interaction_pdgs = [l.get('id') for l in vertex.get('legs')]
148
149 sorted_color_num_pairs = []
150
151
152 for i, pdg in enumerate(interaction_pdgs):
153 index = pdg_codes.index(pdg)
154 pdg_codes.pop(index)
155 sorted_color_num_pairs.append(color_num_pairs.pop(index))
156
157 if color_num_pairs:
158 raise base_objects.PhysicsObject.PhysicsObjectError
159
160 color_num_pairs = sorted_color_num_pairs
161
162
163 list_numbers = [p[1] for p in color_num_pairs]
164
165
166 match_dict = dict(enumerate(list_numbers))
167
168 if vertex['id'] == -1:
169 return (min_index, res_dict)
170
171
172
173 inter_color = model.get_interaction(vertex['id'])['color']
174 inter_indices = [i for (i,j) in \
175 model.get_interaction(vertex['id'])['couplings'].keys()]
176
177
178
179 if not inter_color:
180 new_dict = {}
181 for k, v in res_dict.items():
182 new_key = tuple(list(k) + [0])
183 new_dict[new_key] = v
184
185 if not new_dict:
186 new_dict[(0,)] = color_algebra.ColorString()
187 return (min_index, new_dict)
188
189 new_res_dict = {}
190 for i, col_str in \
191 enumerate(inter_color):
192
193
194 if i not in inter_indices:
195 continue
196
197
198 assert type(col_str) == color_algebra.ColorString
199 mod_col_str = col_str.create_copy()
200
201
202 list_neg = []
203 for col_obj in mod_col_str:
204 list_neg.extend([ind for ind in col_obj if ind < 0])
205 internal_indices_dict = {}
206
207 for index in list(set(list_neg)):
208 internal_indices_dict[index] = min_index
209 min_index = min_index - 1
210 mod_col_str.replace_indices(internal_indices_dict)
211
212
213 mod_col_str.replace_indices(match_dict)
214
215
216
217
218 if not res_dict:
219 new_res_dict[tuple([i])] = mod_col_str
220
221
222 else:
223 for ind_chain, col_str_chain in res_dict.items():
224 new_col_str_chain = col_str_chain.create_copy()
225 new_col_str_chain.product(mod_col_str)
226 new_res_dict[tuple(list(ind_chain) + [i])] = \
227 new_col_str_chain
228
229 return (min_index, new_res_dict)
230
231
233 """Update the current color basis by adding information from
234 the colorize dictionary (produced by the colorize routine)
235 associated to diagram with index index. Keep track of simplification
236 results for maximal optimization."""
237
238
239 for col_chain, col_str in colorize_dict.items():
240
241 canonical_rep, rep_dict = col_str.to_canonical()
242
243 try:
244
245
246 col_fact = self._canonical_dict[canonical_rep].create_copy()
247 except KeyError:
248
249
250
251 col_fact = color_algebra.ColorFactor([col_str])
252 col_fact = col_fact.full_simplify()
253
254
255
256 for colstr in col_fact: colstr.order_summation()
257
258
259 canonical_col_fact = col_fact.create_copy()
260 canonical_col_fact.replace_indices(rep_dict)
261
262 for cs in canonical_col_fact:
263 cs.coeff = cs.coeff / col_str.coeff
264 self._canonical_dict[canonical_rep] = canonical_col_fact
265 else:
266
267
268
269
270 col_fact.replace_indices(self._invert_dict(rep_dict))
271
272
273 for cs in col_fact:
274 cs.coeff = cs.coeff * col_str.coeff
275
276 col_fact = col_fact.simplify()
277
278
279
280 for colstr in col_fact: colstr.order_summation()
281
282
283 for col_str in col_fact:
284 immutable_col_str = col_str.to_immutable()
285
286
287 basis_entry = (index,
288 col_chain,
289 col_str.coeff,
290 col_str.is_imaginary,
291 col_str.Nc_power)
292 try:
293 self[immutable_col_str].append(basis_entry)
294 except KeyError:
295 self[immutable_col_str] = [basis_entry]
296
298 """Returns a list of colorize dict for all diagrams in amplitude. Also
299 update the _list_color_dict object accordingly """
300
301 list_color_dict = []
302
303 for diagram in amplitude.get('diagrams'):
304 colorize_dict = self.colorize(diagram,
305 amplitude.get('process').get('model'))
306 list_color_dict.append(colorize_dict)
307
308 self._list_color_dict = list_color_dict
309
310 return list_color_dict
311
312 - def build(self, amplitude=None):
313 """Build the a color basis object using information contained in
314 amplitude (otherwise use info from _list_color_dict).
315 Returns a list of color """
316
317 if amplitude:
318 self.create_color_dict_list(amplitude)
319 for index, color_dict in enumerate(self._list_color_dict):
320 self.update_color_basis(color_dict, index)
321
323 """Initialize a new color basis object, either empty or filled (0
324 or 1 arguments). If one arguments is given, it's interpreted as
325 an amplitude."""
326
327 assert len(args) < 2, "Object ColorBasis must be initialized with 0 or 1 arguments"
328
329
330 dict.__init__(self)
331
332
333 self._canonical_dict = {}
334
335
336 self._list_color_dict = []
337
338
339 if args:
340 assert isinstance(args[0], diagram_generation.Amplitude), \
341 "%s is not a valid Amplitude object" % str(args[0])
342
343 self.build(*args)
344
346 """Returns a nicely formatted string for display"""
347
348 my_str = ""
349 for k, v in self.items():
350 for name, indices in k:
351 my_str = my_str + name + str(indices)
352 my_str = my_str + ': '
353 for contrib in v:
354 imag_str = ''
355 if contrib[3]:
356 imag_str = 'I'
357 my_str = my_str + '(diag:%i, chain:%s, coeff:%s%s, Nc:%i) ' % \
358 (contrib[0], contrib[1], contrib[2],
359 imag_str, contrib[4])
360 my_str = my_str + '\n'
361 return my_str
362
364 """Helper method to invert dictionary dict"""
365
366 return dict([v, k] for k, v in mydict.items())
367
368 @staticmethod
370 """Return the color_flow_string (i.e., composed only of T's with 2
371 indices) associated to my_color_string. Take a list of the external leg
372 color octet state indices as an input. Returns only the leading N
373 contribution!"""
374
375 my_cf = color_algebra.ColorFactor([my_color_string])
376
377
378 for indices in octet_indices:
379 if indices[0] == -6:
380
381
382 my_cf[0].append(color_algebra.K6(indices[1],
383 indices[2],
384 indices[3]))
385 if indices[0] == 6:
386
387
388 my_cf[0].append(color_algebra.K6Bar(indices[1],
389 indices[2],
390 indices[3]))
391 if abs(indices[0]) == 8:
392
393
394 my_cf[0].append(color_algebra.T(indices[1],
395 indices[2],
396 indices[3]))
397
398 my_cf = my_cf.full_simplify()
399
400
401 if not my_cf:
402 return my_cf
403
404
405
406 max_coeff = max([cs.Nc_power for cs in my_cf])
407
408 res_cs = [cs for cs in my_cf if cs.Nc_power == max_coeff]
409
410
411 if len(res_cs) > 1 and any([not cs.near_equivalent(res_cs[0]) \
412 for cs in res_cs]):
413 raise ColorBasis.ColorBasisError, \
414 "More than one color string with leading N coeff: %s" % str(res_cs)
415
416 res_cs = res_cs[0]
417
418
419
420 for col_obj in res_cs:
421 if not isinstance(col_obj, color_algebra.T) and \
422 not col_obj.__class__.__name__.startswith('Epsilon'):
423 raise ColorBasis.ColorBasisError, \
424 "Color flow decomposition %s contains non T/Epsilon elements" % \
425 str(res_cs)
426 if isinstance(col_obj, color_algebra.T) and len(col_obj) != 2:
427 raise ColorBasis.ColorBasisError, \
428 "Color flow decomposition %s contains T's w/o 2 indices" % \
429 str(res_cs)
430
431 return res_cs
432
434 """Returns the color flow decomposition of the current basis, i.e. a
435 list of dictionaries (one per color basis entry) with keys corresponding
436 to external leg numbers and values tuples containing two color indices
437 ( (0,0) for singlets, (X,0) for triplet, (0,X) for antitriplet and
438 (X,Y) for octets). Other color representations are not yet supported
439 here (an error is raised). Needs a dictionary with keys being external
440 leg numbers, and value the corresponding color representation."""
441
442
443 offset1 = 1000
444 offset2 = 2000
445 offset3 = 3000
446
447 res = []
448
449 for col_basis_entry in sorted(self.keys()):
450
451 res_dict = {}
452 fake_repl = []
453
454
455 col_str = color_algebra.ColorString()
456 col_str.from_immutable(col_basis_entry)
457 for (leg_num, leg_repr) in repr_dict.items():
458
459 res_dict[leg_num] = [0, 0]
460
461
462 if abs(leg_repr) not in [1, 3, 6, 8]:
463 raise ColorBasis.ColorBasisError, \
464 "Particle ID=%i has an unsupported color representation" % leg_repr
465
466
467 if abs(leg_repr) == 8:
468 fake_repl.append((leg_repr, leg_num,
469 offset1 + leg_num,
470 offset2 + leg_num))
471
472 elif leg_repr in [-6, 6]:
473 fake_repl.append((leg_repr, leg_num,
474 offset1 + leg_num,
475 offset3 + leg_num))
476
477
478 col_str_flow = self.get_color_flow_string(col_str, fake_repl)
479
480
481 offset = 500
482
483 for col_obj in col_str_flow:
484 if isinstance(col_obj, color_algebra.T):
485
486 offset = offset + 1
487 for i, index in enumerate(col_obj):
488 if isinstance(col_obj, color_algebra.Epsilon):
489
490 i = 0
491
492 offset = offset+1
493 elif isinstance(col_obj, color_algebra.EpsilonBar):
494
495 i = 1
496
497 offset = offset+1
498 if index < offset1:
499 res_dict[index][i] = offset
500 elif index > offset1 and index < offset2:
501 res_dict[index - offset1][i] = offset
502 elif index > offset2 and index < offset3:
503 res_dict[index - offset2][i] = offset
504 elif index > offset3:
505
506
507
508
509
510 res_dict[index - offset3][1-i] = -offset
511
512
513
514
515 for key in res_dict.keys():
516 if key <= ninitial:
517 res_dict[key].reverse()
518
519 res.append(res_dict)
520
521 return res
522
530 """A color matrix, meaning a dictionary with pairs (i,j) as keys where i
531 and j refer to elements of color basis objects. Values are Color Factor
532 objects. Also contains two additional dictionaries, one with the fixed Nc
533 representation of the matrix, and the other one with the "inverted" matrix,
534 i.e. a dictionary where keys are values of the color matrix."""
535
536 _col_basis1 = None
537 _col_basis2 = None
538 col_matrix_fixed_Nc = {}
539 inverted_col_matrix = {}
540
541 - def __init__(self, col_basis, col_basis2=None,
542 Nc=3, Nc_power_min=None, Nc_power_max=None):
543 """Initialize a color matrix with one or two color basis objects. If
544 only one color basis is given, the other one is assumed to be equal.
545 As options, any value of Nc and minimal/maximal power of Nc can also be
546 provided. Note that the min/max power constraint is applied
547 only at the end, so that it does NOT speed up the calculation."""
548
549 self.col_matrix_fixed_Nc = {}
550 self.inverted_col_matrix = {}
551
552 self._col_basis1 = col_basis
553 if col_basis2:
554 self._col_basis2 = col_basis2
555 self.build_matrix(Nc, Nc_power_min, Nc_power_max)
556 else:
557 self._col_basis2 = col_basis
558
559
560 self.build_matrix(Nc, Nc_power_min, Nc_power_max, is_symmetric=True)
561
562 - def build_matrix(self, Nc=3,
563 Nc_power_min=None,
564 Nc_power_max=None,
565 is_symmetric=False):
566 """Create the matrix using internal color basis objects. Use the stored
567 color basis objects and takes Nc and Nc_min/max parameters as __init__.
568 If is_isymmetric is True, build only half of the matrix which is assumed
569 to be symmetric."""
570
571 canonical_dict = {}
572
573 for i1, struct1 in \
574 enumerate(sorted(self._col_basis1.keys())):
575 for i2, struct2 in \
576 enumerate(sorted(self._col_basis2.keys())):
577
578 if is_symmetric and i2 < i1:
579 continue
580
581
582
583 new_struct2 = self.fix_summed_indices(struct1, struct2)
584
585
586 canonical_entry, dummy = \
587 color_algebra.ColorString().to_canonical(struct1 + \
588 new_struct2)
589
590 try:
591
592 result, result_fixed_Nc = canonical_dict[canonical_entry]
593 except KeyError:
594
595 result, result_fixed_Nc = \
596 self.create_new_entry(struct1,
597 new_struct2,
598 Nc_power_min,
599 Nc_power_max,
600 Nc)
601
602 canonical_dict[canonical_entry] = (result, result_fixed_Nc)
603
604
605 self[(i1, i2)] = result
606 if is_symmetric:
607 self[(i2, i1)] = result
608
609
610 self.col_matrix_fixed_Nc[(i1, i2)] = result_fixed_Nc
611 if is_symmetric:
612 self.col_matrix_fixed_Nc[(i2, i1)] = result_fixed_Nc
613
614 if result_fixed_Nc in self.inverted_col_matrix.keys():
615 self.inverted_col_matrix[result_fixed_Nc].append((i1,
616 i2))
617 if is_symmetric:
618 self.inverted_col_matrix[result_fixed_Nc].append((i2,
619 i1))
620 else:
621 self.inverted_col_matrix[result_fixed_Nc] = [(i1, i2)]
622 if is_symmetric:
623 self.inverted_col_matrix[result_fixed_Nc] = [(i2, i1)]
624
625 - def create_new_entry(self, struct1, struct2,
626 Nc_power_min, Nc_power_max, Nc):
627 """ Create a new product result, and result with fixed Nc for two color
628 basis entries. Implement Nc power limits."""
629
630
631
632 col_str = color_algebra.ColorString()
633 col_str.from_immutable(struct1)
634
635 col_str2 = color_algebra.ColorString()
636 col_str2.from_immutable(struct2)
637
638
639 col_str.product(col_str2.complex_conjugate())
640
641
642
643 col_fact = color_algebra.ColorFactor([col_str])
644 result = col_fact.full_simplify()
645
646
647 if Nc_power_min is not None:
648 result[:] = [col_str for col_str in result \
649 if col_str.Nc_power >= Nc_power_min]
650 if Nc_power_max is not None:
651 result[:] = [col_str for col_str in result \
652 if col_str.Nc_power <= Nc_power_max]
653
654
655 result_fixed_Nc = result.set_Nc(Nc)
656
657 return result, result_fixed_Nc
658
660 """Returns a nicely formatted string with the fixed Nc representation
661 of the current matrix (only the real part)"""
662
663 mystr = '\n\t' + '\t'.join([str(i) for i in \
664 range(len(self._col_basis2))])
665
666 for i1 in range(len(self._col_basis1)):
667 mystr = mystr + '\n' + str(i1) + '\t'
668 mystr = mystr + '\t'.join(['%i/%i' % \
669 (self.col_matrix_fixed_Nc[(i1, i2)][0].numerator,
670 self.col_matrix_fixed_Nc[(i1, i2)][0].denominator) \
671 for i2 in range(len(self._col_basis2))])
672
673 return mystr
674
676 """Get a list with the denominators for the different lines in
677 the color matrix"""
678
679 den_list = []
680 for i1 in range(len(self._col_basis1)):
681 den_list.append(self.lcmm(*[\
682 self.col_matrix_fixed_Nc[(i1, i2)][0].denominator for \
683 i2 in range(len(self._col_basis2))]))
684 return den_list
685
687 """Returns a list of numerator for line line_index, assuming a common
688 denominator den."""
689
690 return [self.col_matrix_fixed_Nc[(line_index, i2)][0].numerator * \
691 den / self.col_matrix_fixed_Nc[(line_index, i2)][0].denominator \
692 for i2 in range(len(self._col_basis2))]
693
694 @classmethod
696 """Returns a copy of the immutable Color String representation struct2
697 where summed indices are modified to avoid duplicates with those
698 appearing in struct1. Assumes internal summed indices are negative."""
699
700
701
702 list2 = sum((list(elem[1]) for elem in struct1),[])
703 if not list2:
704 min_index = -1
705 else:
706 min_index = min(list2) - 1
707
708
709
710 repl_dict = {}
711
712
713 for summed_index in list(set([i for i in list2 \
714 if list2.count(i) == 2])):
715 repl_dict[summed_index] = min_index
716 min_index -= 1
717
718
719 return_list = []
720 for elem in struct2:
721 fix_elem = [elem[0], []]
722 for index in elem[1]:
723 try:
724 fix_elem[1].append(repl_dict[index])
725 except Exception:
726 fix_elem[1].append(index)
727 return_list.append((elem[0], tuple(fix_elem[1])))
728
729 return tuple(return_list)
730
731 @staticmethod
733 """Return lowest common multiple."""
734 return a * b // fractions.gcd(a, b)
735
736 @staticmethod
743