Package madgraph :: Package core :: Module color_amp
[hide private]
[frames] | no frames]

Source Code for Module madgraph.core.color_amp

  1  ################################################################################ 
  2  # 
  3  # Copyright (c) 2009 The MadGraph5_aMC@NLO Development team and Contributors 
  4  # 
  5  # This file is a part of the MadGraph5_aMC@NLO project, an application which  
  6  # automatically generates Feynman diagrams and matrix elements for arbitrary 
  7  # high-energy processes in the Standard Model and beyond. 
  8  # 
  9  # It is subject to the MadGraph5_aMC@NLO license which should accompany this  
 10  # distribution. 
 11  # 
 12  # For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch 
 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 
34 35 #=============================================================================== 36 # ColorBasis 37 #=============================================================================== 38 -class ColorBasis(dict):
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 # Dictionary to save simplifications already done in a canonical form 48 _canonical_dict = {} 49 50 # Dictionary store the raw colorize information 51 _list_color_dict = [] 52 53
54 - class ColorBasisError(Exception):
55 """Exception raised if an error occurs in the definition 56 or the execution of a color basis object.""" 57 pass
58
59 - def colorize(self, diagram, model):
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 # The smallest value used to create new summed indices 65 min_index = -1000 66 # The dictionary to be output 67 res_dict = {} 68 # The dictionary for book keeping of replaced indices 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 # if the process has no QCD particles 76 # Return a list filled with ColorOne if all entries are empty ColorString() 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 # Create a list of (color,leg number) pairs for the vertex, where color 94 # can be negative for anti particles 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 # If this is the next-to-last vertex and the last vertex is 106 # the special identity id=0, start by applying the replacement rule 107 # on the last vertex. 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 # If this is the last leg and not the last vertex 113 # flip color. If it is not the last, AND not the next-to-last 114 # before an id=0 vertex, replace last index by a new summed index. 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 # Take into account previous replacements 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 # Put the resulting wavefunction first, to make 140 # wavefunction call more natural 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 # Order the legs according to the interaction particles 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 #print "interactions_pdg=",interaction_pdgs 156 #print "pdg_codes=",pdg_codes 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 # Create a list of associated leg number following the same order 168 list_numbers = [p[1] for p in color_num_pairs] 169 170 # ... and the associated dictionary for replacement 171 match_dict = dict(enumerate(list_numbers)) 172 173 if vertex['id'] == -1: 174 return (min_index, res_dict) 175 176 # Update the result dict using the current vertex ColorString object 177 # If more than one, create different entries 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 # For colorless vertices, return a copy of res_dict 183 # Where one 0 has been added to each color index chain key 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 # If there is no result until now, create an empty CS... 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 # Ignore color string if it doesn't correspond to any coupling 199 if i not in inter_indices: 200 continue 201 202 # Build the new element 203 assert type(col_str) == color_algebra.ColorString 204 mod_col_str = col_str.create_copy() 205 206 # Replace summed (negative) internal indices 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 # This notation is to remove duplicates 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 # Replace other (positive) indices using the match_dic 218 mod_col_str.replace_indices(match_dict) 219 220 # If we are considering the first vertex, simply create 221 # new entries 222 223 if not res_dict: 224 new_res_dict[tuple([i])] = mod_col_str 225 #... otherwise, loop over existing elements and multiply 226 # the color strings 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
237 - def update_color_basis(self, colorize_dict, index):
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 # loop over possible color chains 244 for col_chain, col_str in colorize_dict.items(): 245 # Create a canonical immutable representation of the the string 246 canonical_rep, rep_dict = col_str.to_canonical() 247 try: 248 # If this representation has already been considered, 249 # recycle the result. 250 col_fact = self._canonical_dict[canonical_rep].create_copy() 251 except KeyError: 252 # If the representation is really new 253 254 # Create and simplify a color factor for the considered chain 255 col_fact = color_algebra.ColorFactor([col_str]) 256 col_fact = col_fact.full_simplify() 257 258 # Here we need to force a specific order for the summed indices 259 # in case we have K6 or K6bar Clebsch Gordan coefficients 260 for colstr in col_fact: colstr.order_summation() 261 262 # Save the result for further use 263 canonical_col_fact = col_fact.create_copy() 264 canonical_col_fact.replace_indices(rep_dict) 265 # Remove overall coefficient 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 # If this representation has already been considered, 271 # adapt the result 272 # Note that we have to replace back 273 # the indices to match the initial convention. 274 col_fact.replace_indices(self._invert_dict(rep_dict)) 275 # Since the initial coeff of col_str is not taken into account 276 # for matching, we have to multiply col_fact by it. 277 for cs in col_fact: 278 cs.coeff = cs.coeff * col_str.coeff 279 # Must simplify up to two times at NLO (since up to two traces 280 # can appear with a loop) to put traces in a canonical ordering. 281 # If it still causes issue, just do a full_simplify(), it would 282 # not bring any heavy additional computational load. 283 col_fact = col_fact.simplify().simplify() 284 285 # Here we need to force a specific order for the summed indices 286 # in case we have K6 or K6bar Clebsch Gordan coefficients 287 for colstr in col_fact: colstr.order_summation() 288 289 # loop over color strings in the resulting color factor 290 for col_str in col_fact: 291 immutable_col_str = col_str.to_immutable() 292 # if the color structure is already present in the present basis 293 # update it 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
305 - def create_color_dict_list(self, amplitude):
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
330 - def __init__(self, *args):
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 # Dictionary to save simplifications already done in a canonical form 341 self._canonical_dict = {} 342 343 # Dictionary store the raw colorize information 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
353 - def __str__(self):
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
371 - def _invert_dict(self, mydict):
372 """Helper method to invert dictionary dict""" 373 374 return dict([v, k] for k, v in mydict.items())
375 376 @staticmethod
377 - def get_color_flow_string(my_color_string, octet_indices):
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 # Create a new color factor to allow for simplification 383 my_cf = color_algebra.ColorFactor([my_color_string]) 384 385 # Add one T per external octet 386 for indices in octet_indices: 387 if indices[0] == -6: 388 # Add a K6 which contracts the antisextet index to a 389 # pair of antitriplets 390 my_cf[0].append(color_algebra.K6(indices[1], 391 indices[2], 392 indices[3])) 393 if indices[0] == 6: 394 # Add a K6Bar which contracts the sextet index to a 395 # pair of triplets 396 my_cf[0].append(color_algebra.K6Bar(indices[1], 397 indices[2], 398 indices[3])) 399 if abs(indices[0]) == 8: 400 # Add a T which contracts the octet to a 401 # triplet-antitriplet pair 402 my_cf[0].append(color_algebra.T(indices[1], 403 indices[2], 404 indices[3])) 405 # Simplify the whole thing 406 my_cf = my_cf.full_simplify() 407 408 # If the result is empty, just return 409 if not my_cf: 410 return my_cf 411 412 # Return the string with the highest N coefficient 413 # (leading N decomposition), and the value of this coeff 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 # If more than one string at leading N... 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 # If the result string does not contain only T's with two indices 426 # and Epsilon/EpsilonBar objects 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
438 - def color_flow_decomposition(self, repr_dict, ninitial):
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 # Offsets used to introduce fake quark indices for gluons 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 # Rebuild a color string from a CB entry 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 # By default, assign a (0,0) color flow 464 res_dict[leg_num] = [0, 0] 465 466 # Raise an error if external legs contain non supported repr 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 # Build the fake indices replacements for octets 471 if abs(leg_repr) == 8: 472 fake_repl.append((leg_repr, leg_num, 473 offset1 + leg_num, 474 offset2 + leg_num)) 475 # Build the fake indices for sextets 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 # Get the actual color flow 482 col_str_flow = self.get_color_flow_string(col_str, fake_repl) 483 484 # Offset for color flow 485 offset = 500 486 487 for col_obj in col_str_flow: 488 if isinstance(col_obj, color_algebra.T): 489 # For T, all color indices should be the same 490 offset = offset + 1 491 for i, index in enumerate(col_obj): 492 if isinstance(col_obj, color_algebra.Epsilon): 493 # Epsilon contracts with antitriplets, 494 i = 0 495 # ...and requires all different color indices 496 offset = offset+1 497 elif isinstance(col_obj, color_algebra.EpsilonBar): 498 # EpsilonBar contracts with antitriplets 499 i = 1 500 # ...and requires all different color indices 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 # For color sextets, use negative triplet 510 # number to reperesent antitriplet and vice 511 # versa, allowing for two triplet or two 512 # antitriplet numbers representing the color 513 # sextet. 514 res_dict[index - offset3][1-i] = -offset 515 516 # Reverse ordering for initial state to stick to the (weird) 517 # les houches convention 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
527 528 529 530 #=============================================================================== 531 # ColorMatrix 532 #=============================================================================== 533 -class ColorMatrix(dict):
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 # If the two color basis are equal, assumes the color matrix is 563 # symmetric 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 # Only scan upper right triangle if symmetric 582 if is_symmetric and i2 < i1: 583 continue 584 585 # Fix indices in struct2 knowing summed indices in struct1 586 # to avoid duplicates 587 new_struct2 = self.fix_summed_indices(struct1, struct2) 588 589 # Build a canonical representation of the two immutable struct 590 canonical_entry, dummy = \ 591 color_algebra.ColorString().to_canonical(struct1 + \ 592 new_struct2) 593 594 try: 595 # If this has already been calculated, use the result 596 result, result_fixed_Nc = canonical_dict[canonical_entry] 597 except KeyError: 598 # Otherwise calculate the result 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 # Store both results 606 canonical_dict[canonical_entry] = (result, result_fixed_Nc) 607 608 # Store the full result... 609 self[(i1, i2)] = result 610 if is_symmetric: 611 self[(i2, i1)] = result 612 613 # the fixed Nc one ... 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 # and update the inverted dict 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 # Create color string objects corresponding to color basis 635 # keys 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 # Complex conjugate the second one and multiply the two 643 col_str.product(col_str2.complex_conjugate()) 644 645 # Create a color factor to store the result and simplify it 646 # taking into account the limit on Nc 647 col_fact = color_algebra.ColorFactor([col_str]) 648 result = col_fact.full_simplify() 649 650 # Keep only terms with Nc_max >= Nc power >= Nc_min 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 # Calculate the fixed Nc representation 659 result_fixed_Nc = result.set_Nc(Nc) 660 661 return result, result_fixed_Nc
662
663 - def __str__(self):
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
679 - def get_line_denominators(self):
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
690 - def get_line_numerators(self, line_index, den):
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
699 - def fix_summed_indices(self, struct1, struct2):
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 # First, determines what is the smallest index appearing in struct1 705 #list2 = reduce(operator.add,[list(elem[1]) for elem in struct1]) 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 # Second, determines the summed indices in struct2 and create a 713 # replacement dictionary 714 repl_dict = {} 715 #list2 = reduce(operator.add, 716 # [list(elem[1]) for elem in struct1]) 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 # Three, create a new immutable struct by doing replacements in struct2 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
736 - def lcm(a, b):
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
743 - def lcmm(*args):
744 """Return lcm of args.""" 745 if args: 746 return reduce(ColorMatrix.lcm, args) 747 else: 748 return 1
749