def test_K6_objects(self): """Test K6 product simplifications""" #K6(m,i,j)K6Bar(m,k,l) = 1/2(T(l,i)T(k,j) # + T(k,i)T(l,j) my_K6 = color.K6(1,101,102) my_K6Bar = color.K6Bar(1,103,104) col_str1 = color.ColorString([color.T(104,101), color.T(103,102)]) col_str2 = color.ColorString([color.T(103,101), color.T(104,102)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(1, 2) self.assertEqual(my_K6.pair_simplify(my_K6Bar), color.ColorFactor([col_str1, col_str2])) #K6(m,i,j)K6Bar(n,j,i) = delta6(m,n) my_K6 = color.K6(1,101,102) my_K6Bar = color.K6Bar(2,102,101) self.assertEqual(my_K6.pair_simplify(my_K6Bar), color.ColorFactor([\ color.ColorString([color.T6(1,2)])])) #K6(m,i,j)K6Bar(n,i,j) = delta6(m,n). my_K6 = color.K6(1,101,102) my_K6Bar = color.K6Bar(2,101,102) self.assertEqual(my_K6.pair_simplify(my_K6Bar), color.ColorFactor([\ color.ColorString([color.T6(1,2)])]))
def closeColorLoop(self, colorize_dict, lcut_charge, lcut_numbers): """ Add a color delta in the right representation (depending on the color charge carried by the L-cut particle whose number are given in the loop_numbers argument) to close the loop color trace.""" # But for T3 and T6 for example, we must make sure to add a delta with # the first index in the fundamental representation. if lcut_charge < 0: lcut_numbers.reverse() if abs(lcut_charge) == 1: # No color carried by the lcut particle, there is nothing to do. return elif abs(lcut_charge) == 3: closingCS=color_algebra.ColorString(\ [color_algebra.T(lcut_numbers[1],lcut_numbers[0])]) elif abs(lcut_charge) == 6: closingCS=color_algebra.ColorString(\ [color_algebra.T6(lcut_numbers[1],lcut_numbers[0])]) elif abs(lcut_charge) == 8: closingCS=color_algebra.ColorString(\ [color_algebra.Tr(lcut_numbers[1],lcut_numbers[0])], fractions.Fraction(2, 1)) else: raise color_amp.ColorBasis.ColorBasisError, \ "L-cut particle has an unsupported color representation %s" % lcut_charge # Append it to all color strings for this diagram. for CS in colorize_dict.values(): # The double full_simplify() below brings significantly slowdown # so that it should be used only when loop_Nc_power is actuall used. if self.compute_loop_nc: # We first compute the NcPower of this ColorString before # *before* the loop color flow is sewed together. max_CS_lcut_diag_Nc_power = max(cs.Nc_power \ for cs in color_algebra.ColorFactor([CS]).full_simplify()) # We add here the closing color structure. CS.product(closingCS) if self.compute_loop_nc: # Now compute the Nc power *after* the loop color flow is sewed # together and again compute the overall maximum power of Nc # appearing in this simplified sewed structure. simplified_cs = color_algebra.ColorFactor([CS]).full_simplify() if not simplified_cs: # It can be that the color structure simplifies to zero. CS.loop_Nc_power = 0 continue max_CS_loop_diag_Nc_power = max(cs.Nc_power \ for cs in simplified_cs) # We can now set the power of Nc brought by the potential loop # color trace to the corresponding attribute of this ColorStructure CS.loop_Nc_power = max_CS_loop_diag_Nc_power - \ max_CS_lcut_diag_Nc_power else: # When not computing loop_nc (whcih is typically used for now # only when doing LoopInduced + Madevent, we set the # CS.loop_Nc_power to None so that it will cause problems if used. CS.loop_Nc_power = None
def test_delta3_pair_simplify(self): """Test delta3 simplify""" self.assertEqual(color.K6(1,101,103).pair_simplify(color.T(101,102)), color.ColorFactor([color.ColorString([color.K6(1,103,102)])])) self.assertEqual(color.K6(1,103,102).pair_simplify(color.T(102,101)), color.ColorFactor([color.ColorString([color.K6(1,103,101)])])) self.assertEqual(color.K6Bar(1,101,103).pair_simplify(color.T(102,101)), color.ColorFactor([color.ColorString([color.K6Bar(1,103,102)])])) self.assertEqual(color.K6Bar(1,103,101).pair_simplify(color.T(102,101)), color.ColorFactor([color.ColorString([color.K6Bar(1,103,102)])]))
def test_f_d_product(self): """Test the fully contracted product of f and d""" my_color_factor = color.ColorFactor([\ color.ColorString([color.f(1, 2, 3), color.d(1, 2, 3)])]) self.assertEqual(str(my_color_factor.full_simplify()), '')
def create_new_entry(self, struct1, struct2, Nc_power_min, Nc_power_max, Nc): """ Create a new product result, and result with fixed Nc for two color basis entries. Implement Nc power limits.""" # Create color string objects corresponding to color basis # keys col_str = color_algebra.ColorString() col_str.from_immutable(struct1) col_str2 = color_algebra.ColorString() col_str2.from_immutable(struct2) # Complex conjugate the second one and multiply the two col_str.product(col_str2.complex_conjugate()) # Create a color factor to store the result and simplify it # taking into account the limit on Nc col_fact = color_algebra.ColorFactor([col_str]) result = col_fact.full_simplify() # Keep only terms with Nc_max >= Nc power >= Nc_min if Nc_power_min is not None: result[:] = [col_str for col_str in result \ if col_str.Nc_power >= Nc_power_min] if Nc_power_max is not None: result[:] = [col_str for col_str in result \ if col_str.Nc_power <= Nc_power_max] # Calculate the fixed Nc representation result_fixed_Nc = result.set_Nc(Nc) return result, result_fixed_Nc
def get_color_flow_string(my_color_string, octet_indices): """Return the color_flow_string (i.e., composed only of T's with 2 indices) associated to my_color_string. Take a list of the external leg color octet state indices as an input. Returns only the leading N contribution!""" # Create a new color factor to allow for simplification my_cf = color_algebra.ColorFactor([my_color_string]) # Add one T per external octet for indices in octet_indices: if indices[0] == -6: # Add a K6 which contracts the antisextet index to a # pair of antitriplets my_cf[0].append( color_algebra.K6(indices[1], indices[2], indices[3])) if indices[0] == 6: # Add a K6Bar which contracts the sextet index to a # pair of triplets my_cf[0].append( color_algebra.K6Bar(indices[1], indices[2], indices[3])) if abs(indices[0]) == 8: # Add a T which contracts the octet to a # triplet-antitriplet pair my_cf[0].append( color_algebra.T(indices[1], indices[2], indices[3])) # Simplify the whole thing my_cf = my_cf.full_simplify() # If the result is empty, just return if not my_cf: return my_cf # Return the string with the highest N coefficient # (leading N decomposition), and the value of this coeff max_coeff = max([cs.Nc_power for cs in my_cf]) res_cs = [cs for cs in my_cf if cs.Nc_power == max_coeff] # If more than one string at leading N... if len(res_cs) > 1 and any([not cs.near_equivalent(res_cs[0]) \ for cs in res_cs]): raise ColorBasis.ColorBasisError, \ "More than one color string with leading N coeff: %s" % str(res_cs) res_cs = res_cs[0] # If the result string does not contain only T's with two indices # and Epsilon/EpsilonBar objects for col_obj in res_cs: if not isinstance(col_obj, color_algebra.T) and \ not col_obj.__class__.__name__.startswith('Epsilon'): raise ColorBasis.ColorBasisError, \ "Color flow decomposition %s contains non T/Epsilon elements" % \ str(res_cs) if isinstance(col_obj, color_algebra.T) and len(col_obj) != 2: raise ColorBasis.ColorBasisError, \ "Color flow decomposition %s contains T's w/o 2 indices" % \ str(res_cs) return res_cs
def test_f_product(self): """Test the fully contracted product of two f's""" my_color_factor = color.ColorFactor([\ color.ColorString([color.f(1, 2, 3), color.f(1, 2, 3)])]) self.assertEqual(str(my_color_factor.full_simplify()), '(-1 Nc^1 )+(1 Nc^3 )')
def test_T_simplify(self): """Test T simplify""" # T(a,b,c,...,i,i) = Tr(a,b,c,...) self.assertEqual(color.T(1, 2, 3, 100, 100).simplify(), color.ColorFactor([\ color.ColorString([color.Tr(1, 2, 3)])])) # T(a,x,b,x,c,i,j) = 1/2(T(a,c,i,j)Tr(b)-1/Nc T(a,b,c,i,j)) my_T = color.T(1, 2, 100, 3, 100, 4, 101, 102) col_str1 = color.ColorString([color.T(1, 2, 4, 101, 102), color.Tr(3)]) col_str2 = color.ColorString([color.T(1, 2, 3, 4, 101, 102)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 self.assertEqual(my_T.simplify(), color.ColorFactor([col_str1, col_str2])) self.assertEqual(my_T.simplify(), color.ColorFactor([col_str1, col_str2]))
def test_Tr_product(self): """Test a non trivial product of two traces""" my_color_factor = color.ColorFactor([\ color.ColorString([color.Tr(1, 2, 3, 4, 5, 6, 7), color.Tr(1, 7, 6, 5, 4, 3, 2)])]) self.assertEqual(str(my_color_factor.full_simplify()), "(1/128 Nc^7 )+(-7/128 Nc^5 )+(21/128 Nc^3 )+(-35/128 Nc^1 )" + \ "+(35/128 1/Nc^1 )+(-21/128 1/Nc^3 )+(3/64 1/Nc^5 )")
def test_T_f_product(self): """Test a non trivial T f f product""" my_color_factor = color.ColorFactor([\ color.ColorString([color.T(-1000, 1, 2), color.f(-1, -1000, 5), color.f(-1, 4, 3)])]) self.assertEqual(str(my_color_factor.full_simplify()), "(-1 T(5,4,3,1,2))+(1 T(5,3,4,1,2))+(1 T(4,3,5,1,2))+(-1 T(3,4,5,1,2))")
def test_T_pair_simplify(self): """Test T object products simplifications""" my_T1 = color.T(1, 2, 3, 101, 102) my_T2 = color.T(4, 5, 102, 103) self.assertEqual(my_T1.pair_simplify(my_T2), color.ColorFactor([color.ColorString([\ color.T(1, 2, 3, 4, 5, 101, 103)])])) my_T3 = color.T(4, 2, 5, 103, 104) col_str1 = color.ColorString([color.T(1, 5, 101, 104), color.T(4, 3, 103, 102)]) col_str2 = color.ColorString([color.T(1, 3, 101, 102), color.T(4, 5, 103, 104)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 self.assertEqual(my_T1.pair_simplify(my_T3), color.ColorFactor([col_str1, col_str2]))
def test_delta6_simplify(self): """Test delta6 simplify""" # delta6(i,i) = 1 col_str1 = color.ColorString() col_str1.Nc_power = 2 col_str1.coeff = fractions.Fraction(1, 2) col_str2 = color.ColorString() col_str2.Nc_power = 1 col_str2.coeff = fractions.Fraction(1, 2) self.assertEqual(color.T6(1, 1).simplify(), color.ColorFactor([col_str1, col_str2]))
def test_three_f_chain(self): """Test a chain of three f's""" my_color_factor = color.ColorFactor([\ color.ColorString([color.f(1, 2, -1), color.f(-1, 3, -2), color.f(-2, 4, 5)])]) self.assertEqual(str(my_color_factor.full_simplify()), "(2 I Tr(1,2,3,4,5))+(-2 I Tr(1,2,4,5,3))+(-2 I Tr(1,2,3,5,4))" + \ "+(2 I Tr(1,2,5,4,3))+(-2 I Tr(1,3,4,5,2))+(2 I Tr(1,4,5,3,2))" + \ "+(2 I Tr(1,3,5,4,2))+(-2 I Tr(1,5,4,3,2))")
def test_f_d_sum(self): """Test f and d sum with the right weights giving a Tr""" col_str1 = color.ColorString([color.d(1, 2, 3)]) col_str1.coeff = fractions.Fraction(1, 4) col_str2 = color.ColorString([color.f(1, 2, 3)]) col_str2.coeff = fractions.Fraction(1, 4) col_str2.is_imaginary = True my_color_factor = color.ColorFactor([col_str1, col_str2]) self.assertEqual(str(my_color_factor.full_simplify()), '(1 Tr(1,2,3))')
def test_gluons(self): """Test simplification of chains of f""" my_col_fact = color.ColorFactor([color.ColorString([color.f(-3, 1, 2), color.f(-1, 3, 4), color.f(-1, 5, -3) ])]) self.assertEqual(str(my_col_fact.full_simplify()), '(2 I Tr(1,2,3,4,5))+(-2 I Tr(1,2,5,3,4))+(-2 I Tr(1,2,4,3,5))+' + \ '(2 I Tr(1,2,5,4,3))+(-2 I Tr(1,3,4,5,2))+(2 I Tr(1,5,3,4,2))+' + \ '(2 I Tr(1,4,3,5,2))+(-2 I Tr(1,5,4,3,2))')
def test_Tr_pair_simplify(self): """Test Tr object product simplification""" my_Tr1 = color.Tr(1, 2, 3) my_Tr2 = color.Tr(4, 2, 5) my_T = color.T(4, 2, 5, 101, 102) col_str1 = color.ColorString([color.Tr(1, 5, 4, 3)]) col_str2 = color.ColorString([color.Tr(1, 3), color.Tr(4, 5)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 self.assertEqual(my_Tr1.pair_simplify(my_Tr2), color.ColorFactor([col_str1, col_str2])) col_str1 = color.ColorString([color.T(4, 3, 1, 5, 101, 102)]) col_str2 = color.ColorString([color.Tr(1, 3), color.T(4, 5, 101, 102)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 self.assertEqual(my_Tr1.pair_simplify(my_T), color.ColorFactor([col_str1, col_str2]))
def test_d_object(self): """Test the d color object""" # T should have exactly 3 indices! self.assertRaises(AssertionError, color.d, 1, 2) # Simplify should always return the same ColorFactor my_d = color.d(1, 2, 3) col_str1 = color.ColorString([color.Tr(1, 2, 3)]) col_str2 = color.ColorString([color.Tr(3, 2, 1)]) col_str1.coeff = fractions.Fraction(2, 1) col_str2.coeff = fractions.Fraction(2, 1) self.assertEqual(my_d.simplify(), color.ColorFactor([col_str1, col_str2]))
def test_epsilon_object(self): """Test the epsilon object""" # Espilon should have exactly 3 indices! self.assertRaises(AssertionError, color.Epsilon, 1, 2) my_epsilon1 = color.Epsilon(2, 3, 1) my_epsilon2 = color.Epsilon(5, 1, 4) my_epsilon2 = my_epsilon2.complex_conjugate() my_goal_str1 = color.ColorString([color.T(2, 4), color.T(3, 5)]) my_goal_str2 = color.ColorString([color.T(2, 5), color.T(3, 4)]) my_goal_str2.coeff = fractions.Fraction(-1) self.assertEqual(my_epsilon1.pair_simplify(my_epsilon2), color.ColorFactor([my_goal_str1, my_goal_str2]))
def test_T6_simplify(self): """Test T6 simplify""" # T6(a,i,j) = 2(K6(i,ii,jj)T(a,jj,kk)K6Bar(j,kk,ii)) my_T6 = color.T6(1,101,102) color.T6.new_index = 10000 k6 = color.K6(101, 10000, 10001) t = color.T(1, 10001, 10002) k6b = color.K6Bar(102, 10002, 10000) col_string = color.ColorString([k6, t, k6b]) col_string.coeff = fractions.Fraction(2, 1) self.assertEqual(my_T6.simplify(), color.ColorFactor([col_string])) my_T6 = color.T6(1,101,102) k6 = color.K6(101, 10003, 10004) t = color.T(1, 10004, 10005) k6b = color.K6Bar(102, 10005, 10003) col_string = color.ColorString([k6, t, k6b]) col_string.coeff = fractions.Fraction(2, 1) self.assertEqual(my_T6.simplify(), color.ColorFactor([col_string]))
def test_f_object(self): """Test the f color object""" # T should have exactly 3 indices! self.assertRaises(AssertionError, color.f, 1, 2, 3, 4) # Simplify should always return the same ColorFactor my_f = color.f(1, 2, 3) col_str1 = color.ColorString([color.Tr(1, 2, 3)]) col_str2 = color.ColorString([color.Tr(3, 2, 1)]) col_str1.coeff = fractions.Fraction(-2, 1) col_str2.coeff = fractions.Fraction(2, 1) col_str1.is_imaginary = True col_str2.is_imaginary = True self.assertEqual(my_f.simplify(), color.ColorFactor([col_str1, col_str2]))
def test_Tr_simplify(self): """Test simplification of trace objects""" # Test Tr(a)=0 self.assertEqual( color.Tr(-1).simplify(), color.ColorFactor([color.ColorString(coeff=0)])) # Test Tr()=Nc col_str = color.ColorString() col_str.Nc_power = 1 self.assertEqual(color.Tr().simplify(), color.ColorFactor([col_str])) # Test cyclicity col_str = color.ColorString([color.Tr(1, 2, 3, 4, 5)]) self.assertEqual( color.Tr(3, 4, 5, 1, 2).simplify(), color.ColorFactor([col_str])) # Tr(a,x,b,x,c) = 1/2(Tr(a,c)Tr(b)-1/Nc Tr(a,b,c)) col_str1 = color.ColorString([color.Tr(1, 2, 4), color.Tr(3)]) col_str2 = color.ColorString([color.Tr(1, 2, 3, 4)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 my_tr = color.Tr(1, 2, 100, 3, 100, 4) self.assertEqual(my_tr.simplify(), color.ColorFactor([col_str1, col_str2])) my_tr = color.Tr(1, 2, 100, 100, 4) col_str1 = color.ColorString([color.Tr(1, 2, 4), color.Tr()]) col_str2 = color.ColorString([color.Tr(1, 2, 4)]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 self.assertEqual(my_tr.simplify(), color.ColorFactor([col_str1, col_str2])) my_tr = color.Tr(100, 100) col_str1 = color.ColorString([color.Tr(), color.Tr()]) col_str2 = color.ColorString([color.Tr()]) col_str1.coeff = fractions.Fraction(1, 2) col_str2.coeff = fractions.Fraction(-1, 2) col_str2.Nc_power = -1 self.assertEqual(my_tr.simplify(), color.ColorFactor([col_str1, col_str2]))
def test_sextet_products(self): """Test non trivial product of sextet operators""" # T6[2, 101, 102] T6[2, 102, 103] = (-1 + Nc) (2 + Nc) delta6[101, 103])/Nc my_color_factor = color.ColorFactor([\ color.ColorString([color.T6(2, 101, 102), color.T6(2, 102, 103)])]) col_str1 = color.ColorString([color.T6(101,103)]) col_str1.Nc_power = 1 col_str2 = copy.copy(col_str1) col_str2.Nc_power = 0 col_str3 = copy.copy(col_str1) col_str3.Nc_power = -1 col_str3.coeff = fractions.Fraction(-2, 1) self.assertEqual(my_color_factor.full_simplify(), color.ColorFactor([col_str1, col_str2, col_str3])) # T6[2, 101, 102] T6[3, 102, 101] = 1/2 (2 + Nc) delta8[2, 3] my_color_factor = color.ColorFactor([\ color.ColorString([color.T6(2, 101, 102), color.T6(3, 102, 101)])]) col_str1 = color.ColorString([color.Tr(2,3)]) col_str1.Nc_power = 1 col_str1.coeff = fractions.Fraction(1) col_str2 = copy.copy(col_str1) col_str2.Nc_power = 0 col_str2.coeff = fractions.Fraction(2) self.assertEqual(my_color_factor.full_simplify(), color.ColorFactor([col_str1, col_str2])) # K6[1, 101, 102] T[2, 102, 103] T[2, 103, 104] K6Bar[1, 104, 101] # = 1/4 (-1 + Nc) (1 + Nc)^2 # = 1/4 (-1 - Nc + Nc^2 + Nc^3) my_color_factor = color.ColorFactor([\ color.ColorString([color.K6(1, 101, 102), color.T(2, 102, 103), color.T(2, 103, 104), color.K6Bar(1, 104, 101)])]) col_str1 = color.ColorString() col_str1.Nc_power = 3 col_str1.coeff = fractions.Fraction(1, 4) col_str2 = color.ColorString() col_str2.Nc_power = 2 col_str2.coeff = fractions.Fraction(1, 4) col_str3 = color.ColorString() col_str3.Nc_power = 1 col_str3.coeff = fractions.Fraction(-1, 4) col_str4 = color.ColorString() col_str4.Nc_power = 0 col_str4.coeff = fractions.Fraction(-1, 4) self.assertEqual(my_color_factor.full_simplify(), color.ColorFactor([col_str1, col_str3, col_str2, col_str4])) # T6[2, 101, 102] T6[2, 102, 103] K6[103, 99, 98] K6Bar[101, 98, 99] # = 1/2 (-1 + Nc) (1 + Nc) (2 + Nc) # = 1/2 (Nc^3 + 2 Nc^2 - Nc - 2) my_color_factor = color.ColorFactor([\ color.ColorString([color.T6(2, 101, 102), color.T6(2, 102, 103), color.K6(103,99, 98), color.K6Bar(101, 98, 99)])]) col_str1 = color.ColorString() col_str1.Nc_power = 3 col_str1.coeff = fractions.Fraction(1, 2) col_str2 = color.ColorString() col_str2.Nc_power = 2 col_str2.coeff = fractions.Fraction(1, 1) col_str3 = color.ColorString() col_str3.Nc_power = 1 col_str3.coeff = fractions.Fraction(-1, 2) col_str4 = color.ColorString() col_str4.Nc_power = 0 col_str4.coeff = fractions.Fraction(-1, 1) self.assertEqual(my_color_factor.full_simplify(), color.ColorFactor([col_str2, col_str1, col_str3, col_str4])) # K6[103, 99, 98] T[80, 98, 100] K6Bar[103, 100, 97] T[80, 99, 97] # = -(1/4) + Nc^2/4 my_color_factor = color.ColorFactor([\ color.ColorString([color.K6(103, 99, 98), color.T(80, 98, 100), color.K6Bar(103, 100, 97), color.T(80, 99, 97)])]) col_str1 = color.ColorString() col_str1.Nc_power = 2 col_str1.coeff = fractions.Fraction(1, 4) col_str2 = color.ColorString() col_str2.Nc_power = 0 col_str2.coeff = fractions.Fraction(-1, 4) self.assertEqual(my_color_factor.full_simplify(), color.ColorFactor([col_str1, col_str2]))
def update_color_basis(self, colorize_dict, index): """Update the current color basis by adding information from the colorize dictionary (produced by the colorize routine) associated to diagram with index index. Keep track of simplification results for maximal optimization.""" import madgraph.various.misc as misc # loop over possible color chains for col_chain, col_str in colorize_dict.items(): # Create a canonical immutable representation of the the string canonical_rep, rep_dict = col_str.to_canonical() try: # If this representation has already been considered, # recycle the result. col_fact = self._canonical_dict[canonical_rep].create_copy() except KeyError: # If the representation is really new # Create and simplify a color factor for the considered chain col_fact = color_algebra.ColorFactor([col_str]) col_fact = col_fact.full_simplify() # Here we need to force a specific order for the summed indices # in case we have K6 or K6bar Clebsch Gordan coefficients for colstr in col_fact: colstr.order_summation() # Save the result for further use canonical_col_fact = col_fact.create_copy() canonical_col_fact.replace_indices(rep_dict) # Remove overall coefficient for cs in canonical_col_fact: cs.coeff = cs.coeff / col_str.coeff self._canonical_dict[canonical_rep] = canonical_col_fact else: # If this representation has already been considered, # adapt the result # Note that we have to replace back # the indices to match the initial convention. col_fact.replace_indices(self._invert_dict(rep_dict)) # Since the initial coeff of col_str is not taken into account # for matching, we have to multiply col_fact by it. for cs in col_fact: cs.coeff = cs.coeff * col_str.coeff # Must simplify up to two times at NLO (since up to two traces # can appear with a loop) to put traces in a canonical ordering. # If it still causes issue, just do a full_simplify(), it would # not bring any heavy additional computational load. col_fact = col_fact.simplify().simplify() # Here we need to force a specific order for the summed indices # in case we have K6 or K6bar Clebsch Gordan coefficients for colstr in col_fact: colstr.order_summation() # loop over color strings in the resulting color factor for col_str in col_fact: immutable_col_str = col_str.to_immutable() # if the color structure is already present in the present basis # update it basis_entry = (index, col_chain, col_str.coeff, col_str.is_imaginary, col_str.Nc_power, col_str.loop_Nc_power) try: self[immutable_col_str].append(basis_entry) except KeyError: self[immutable_col_str] = [basis_entry]