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 import madgraph.various.misc as misc
238
239 for col_chain, col_str in colorize_dict.items():
240
241 canonical_rep, rep_dict = col_str.to_canonical()
242 try:
243
244
245 col_fact = self._canonical_dict[canonical_rep].create_copy()
246 except KeyError:
247
248
249
250 col_fact = color_algebra.ColorFactor([col_str])
251 col_fact = col_fact.full_simplify()
252
253
254
255 for colstr in col_fact: colstr.order_summation()
256
257
258 canonical_col_fact = col_fact.create_copy()
259 canonical_col_fact.replace_indices(rep_dict)
260
261 for cs in canonical_col_fact:
262 cs.coeff = cs.coeff / col_str.coeff
263 self._canonical_dict[canonical_rep] = canonical_col_fact
264 else:
265
266
267
268
269 col_fact.replace_indices(self._invert_dict(rep_dict))
270
271
272 for cs in col_fact:
273 cs.coeff = cs.coeff * col_str.coeff
274
275
276
277
278 col_fact = col_fact.simplify().simplify()
279
280
281
282 for colstr in col_fact: colstr.order_summation()
283
284
285 for col_str in col_fact:
286 immutable_col_str = col_str.to_immutable()
287
288
289 basis_entry = (index,
290 col_chain,
291 col_str.coeff,
292 col_str.is_imaginary,
293 col_str.Nc_power,
294 col_str.loop_Nc_power)
295 try:
296 self[immutable_col_str].append(basis_entry)
297 except KeyError:
298 self[immutable_col_str] = [basis_entry]
299
301 """Returns a list of colorize dict for all diagrams in amplitude. Also
302 update the _list_color_dict object accordingly """
303
304 list_color_dict = []
305
306 for diagram in amplitude.get('diagrams'):
307 colorize_dict = self.colorize(diagram,
308 amplitude.get('process').get('model'))
309 list_color_dict.append(colorize_dict)
310
311 self._list_color_dict = list_color_dict
312
313 return list_color_dict
314
315 - def build(self, amplitude=None):
316 """Build the a color basis object using information contained in
317 amplitude (otherwise use info from _list_color_dict).
318 Returns a list of color """
319
320 if amplitude:
321 self.create_color_dict_list(amplitude)
322 for index, color_dict in enumerate(self._list_color_dict):
323 self.update_color_basis(color_dict, index)
324
326 """Initialize a new color basis object, either empty or filled (0
327 or 1 arguments). If one arguments is given, it's interpreted as
328 an amplitude."""
329
330 assert len(args) < 2, "Object ColorBasis must be initialized with 0 or 1 arguments"
331
332
333 dict.__init__(self)
334
335
336 self._canonical_dict = {}
337
338
339 self._list_color_dict = []
340
341
342 if args:
343 assert isinstance(args[0], diagram_generation.Amplitude), \
344 "%s is not a valid Amplitude object" % str(args[0])
345
346 self.build(*args)
347
349 """Returns a nicely formatted string for display"""
350
351 my_str = ""
352 for k, v in self.items():
353 for name, indices in k:
354 my_str = my_str + name + str(indices)
355 my_str = my_str + ': '
356 for contrib in v:
357 imag_str = ''
358 if contrib[3]:
359 imag_str = 'I'
360 my_str = my_str + '(diag:%i, chain:%s, coeff:%s%s, Nc:%i) ' % \
361 (contrib[0], contrib[1], contrib[2],
362 imag_str, contrib[4])
363 my_str = my_str + '\n'
364 return my_str
365
367 """Helper method to invert dictionary dict"""
368
369 return dict([v, k] for k, v in mydict.items())
370
371 @staticmethod
373 """Return the color_flow_string (i.e., composed only of T's with 2
374 indices) associated to my_color_string. Take a list of the external leg
375 color octet state indices as an input. Returns only the leading N
376 contribution!"""
377
378 my_cf = color_algebra.ColorFactor([my_color_string])
379
380
381 for indices in octet_indices:
382 if indices[0] == -6:
383
384
385 my_cf[0].append(color_algebra.K6(indices[1],
386 indices[2],
387 indices[3]))
388 if indices[0] == 6:
389
390
391 my_cf[0].append(color_algebra.K6Bar(indices[1],
392 indices[2],
393 indices[3]))
394 if abs(indices[0]) == 8:
395
396
397 my_cf[0].append(color_algebra.T(indices[1],
398 indices[2],
399 indices[3]))
400
401 my_cf = my_cf.full_simplify()
402
403
404 if not my_cf:
405 return my_cf
406
407
408
409 max_coeff = max([cs.Nc_power for cs in my_cf])
410
411 res_cs = [cs for cs in my_cf if cs.Nc_power == max_coeff]
412
413
414 if len(res_cs) > 1 and any([not cs.near_equivalent(res_cs[0]) \
415 for cs in res_cs]):
416 raise ColorBasis.ColorBasisError, \
417 "More than one color string with leading N coeff: %s" % str(res_cs)
418
419 res_cs = res_cs[0]
420
421
422
423 for col_obj in res_cs:
424 if not isinstance(col_obj, color_algebra.T) and \
425 not col_obj.__class__.__name__.startswith('Epsilon'):
426 raise ColorBasis.ColorBasisError, \
427 "Color flow decomposition %s contains non T/Epsilon elements" % \
428 str(res_cs)
429 if isinstance(col_obj, color_algebra.T) and len(col_obj) != 2:
430 raise ColorBasis.ColorBasisError, \
431 "Color flow decomposition %s contains T's w/o 2 indices" % \
432 str(res_cs)
433
434 return res_cs
435
437 """Returns the color flow decomposition of the current basis, i.e. a
438 list of dictionaries (one per color basis entry) with keys corresponding
439 to external leg numbers and values tuples containing two color indices
440 ( (0,0) for singlets, (X,0) for triplet, (0,X) for antitriplet and
441 (X,Y) for octets). Other color representations are not yet supported
442 here (an error is raised). Needs a dictionary with keys being external
443 leg numbers, and value the corresponding color representation."""
444
445
446 offset1 = 1000
447 offset2 = 2000
448 offset3 = 3000
449
450 res = []
451
452 for col_basis_entry in sorted(self.keys()):
453
454 res_dict = {}
455 fake_repl = []
456
457
458 col_str = color_algebra.ColorString()
459 col_str.from_immutable(col_basis_entry)
460 for (leg_num, leg_repr) in repr_dict.items():
461
462 res_dict[leg_num] = [0, 0]
463
464
465 if abs(leg_repr) not in [1, 3, 6, 8]:
466 raise ColorBasis.ColorBasisError, \
467 "Particle ID=%i has an unsupported color representation" % leg_repr
468
469
470 if abs(leg_repr) == 8:
471 fake_repl.append((leg_repr, leg_num,
472 offset1 + leg_num,
473 offset2 + leg_num))
474
475 elif leg_repr in [-6, 6]:
476 fake_repl.append((leg_repr, leg_num,
477 offset1 + leg_num,
478 offset3 + leg_num))
479
480
481 col_str_flow = self.get_color_flow_string(col_str, fake_repl)
482
483
484 offset = 500
485
486 for col_obj in col_str_flow:
487 if isinstance(col_obj, color_algebra.T):
488
489 offset = offset + 1
490 for i, index in enumerate(col_obj):
491 if isinstance(col_obj, color_algebra.Epsilon):
492
493 i = 0
494
495 offset = offset+1
496 elif isinstance(col_obj, color_algebra.EpsilonBar):
497
498 i = 1
499
500 offset = offset+1
501 if index < offset1:
502 res_dict[index][i] = offset
503 elif index > offset1 and index < offset2:
504 res_dict[index - offset1][i] = offset
505 elif index > offset2 and index < offset3:
506 res_dict[index - offset2][i] = offset
507 elif index > offset3:
508
509
510
511
512
513 res_dict[index - offset3][1-i] = -offset
514
515
516
517
518 for key in res_dict.keys():
519 if key <= ninitial:
520 res_dict[key].reverse()
521
522 res.append(res_dict)
523
524 return res
525
533 """A color matrix, meaning a dictionary with pairs (i,j) as keys where i
534 and j refer to elements of color basis objects. Values are Color Factor
535 objects. Also contains two additional dictionaries, one with the fixed Nc
536 representation of the matrix, and the other one with the "inverted" matrix,
537 i.e. a dictionary where keys are values of the color matrix."""
538
539 _col_basis1 = None
540 _col_basis2 = None
541 col_matrix_fixed_Nc = {}
542 inverted_col_matrix = {}
543
544 - def __init__(self, col_basis, col_basis2=None,
545 Nc=3, Nc_power_min=None, Nc_power_max=None):
546 """Initialize a color matrix with one or two color basis objects. If
547 only one color basis is given, the other one is assumed to be equal.
548 As options, any value of Nc and minimal/maximal power of Nc can also be
549 provided. Note that the min/max power constraint is applied
550 only at the end, so that it does NOT speed up the calculation."""
551
552 self.col_matrix_fixed_Nc = {}
553 self.inverted_col_matrix = {}
554
555 self._col_basis1 = col_basis
556 if col_basis2:
557 self._col_basis2 = col_basis2
558 self.build_matrix(Nc, Nc_power_min, Nc_power_max)
559 else:
560 self._col_basis2 = col_basis
561
562
563 self.build_matrix(Nc, Nc_power_min, Nc_power_max, is_symmetric=True)
564
565 - def build_matrix(self, Nc=3,
566 Nc_power_min=None,
567 Nc_power_max=None,
568 is_symmetric=False):
569 """Create the matrix using internal color basis objects. Use the stored
570 color basis objects and takes Nc and Nc_min/max parameters as __init__.
571 If is_isymmetric is True, build only half of the matrix which is assumed
572 to be symmetric."""
573
574 canonical_dict = {}
575
576 for i1, struct1 in \
577 enumerate(sorted(self._col_basis1.keys())):
578 for i2, struct2 in \
579 enumerate(sorted(self._col_basis2.keys())):
580
581 if is_symmetric and i2 < i1:
582 continue
583
584
585
586 new_struct2 = self.fix_summed_indices(struct1, struct2)
587
588
589 canonical_entry, dummy = \
590 color_algebra.ColorString().to_canonical(struct1 + \
591 new_struct2)
592
593 try:
594
595 result, result_fixed_Nc = canonical_dict[canonical_entry]
596 except KeyError:
597
598 result, result_fixed_Nc = \
599 self.create_new_entry(struct1,
600 new_struct2,
601 Nc_power_min,
602 Nc_power_max,
603 Nc)
604
605 canonical_dict[canonical_entry] = (result, result_fixed_Nc)
606
607
608 self[(i1, i2)] = result
609 if is_symmetric:
610 self[(i2, i1)] = result
611
612
613 self.col_matrix_fixed_Nc[(i1, i2)] = result_fixed_Nc
614 if is_symmetric:
615 self.col_matrix_fixed_Nc[(i2, i1)] = result_fixed_Nc
616
617 if result_fixed_Nc in self.inverted_col_matrix.keys():
618 self.inverted_col_matrix[result_fixed_Nc].append((i1,
619 i2))
620 if is_symmetric:
621 self.inverted_col_matrix[result_fixed_Nc].append((i2,
622 i1))
623 else:
624 self.inverted_col_matrix[result_fixed_Nc] = [(i1, i2)]
625 if is_symmetric:
626 self.inverted_col_matrix[result_fixed_Nc] = [(i2, i1)]
627
628 - def create_new_entry(self, struct1, struct2,
629 Nc_power_min, Nc_power_max, Nc):
630 """ Create a new product result, and result with fixed Nc for two color
631 basis entries. Implement Nc power limits."""
632
633
634
635 col_str = color_algebra.ColorString()
636 col_str.from_immutable(struct1)
637
638 col_str2 = color_algebra.ColorString()
639 col_str2.from_immutable(struct2)
640
641
642 col_str.product(col_str2.complex_conjugate())
643
644
645
646 col_fact = color_algebra.ColorFactor([col_str])
647 result = col_fact.full_simplify()
648
649
650 if Nc_power_min is not None:
651 result[:] = [col_str for col_str in result \
652 if col_str.Nc_power >= Nc_power_min]
653 if Nc_power_max is not None:
654 result[:] = [col_str for col_str in result \
655 if col_str.Nc_power <= Nc_power_max]
656
657
658 result_fixed_Nc = result.set_Nc(Nc)
659
660 return result, result_fixed_Nc
661
663 """Returns a nicely formatted string with the fixed Nc representation
664 of the current matrix (only the real part)"""
665
666 mystr = '\n\t' + '\t'.join([str(i) for i in \
667 range(len(self._col_basis2))])
668
669 for i1 in range(len(self._col_basis1)):
670 mystr = mystr + '\n' + str(i1) + '\t'
671 mystr = mystr + '\t'.join(['%i/%i' % \
672 (self.col_matrix_fixed_Nc[(i1, i2)][0].numerator,
673 self.col_matrix_fixed_Nc[(i1, i2)][0].denominator) \
674 for i2 in range(len(self._col_basis2))])
675
676 return mystr
677
679 """Get a list with the denominators for the different lines in
680 the color matrix"""
681
682 den_list = []
683 for i1 in range(len(self._col_basis1)):
684 den_list.append(self.lcmm(*[\
685 self.col_matrix_fixed_Nc[(i1, i2)][0].denominator for \
686 i2 in range(len(self._col_basis2))]))
687 return den_list
688
690 """Returns a list of numerator for line line_index, assuming a common
691 denominator den."""
692
693 return [self.col_matrix_fixed_Nc[(line_index, i2)][0].numerator * \
694 den / self.col_matrix_fixed_Nc[(line_index, i2)][0].denominator \
695 for i2 in range(len(self._col_basis2))]
696
697 @classmethod
699 """Returns a copy of the immutable Color String representation struct2
700 where summed indices are modified to avoid duplicates with those
701 appearing in struct1. Assumes internal summed indices are negative."""
702
703
704
705 list2 = sum((list(elem[1]) for elem in struct1),[])
706 if not list2:
707 min_index = -1
708 else:
709 min_index = min(list2) - 1
710
711
712
713 repl_dict = {}
714
715
716 for summed_index in list(set([i for i in list2 \
717 if list2.count(i) == 2])):
718 repl_dict[summed_index] = min_index
719 min_index -= 1
720
721
722 return_list = []
723 for elem in struct2:
724 fix_elem = [elem[0], []]
725 for index in elem[1]:
726 try:
727 fix_elem[1].append(repl_dict[index])
728 except Exception:
729 fix_elem[1].append(index)
730 return_list.append((elem[0], tuple(fix_elem[1])))
731
732 return tuple(return_list)
733
734 @staticmethod
736 """Return lowest common multiple."""
737 return a * b // fractions.gcd(a, b)
738
739 @staticmethod
746