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