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