def fermion_propagator(): """ Calculate expression for first-order corrected fermion propagator. """ latex = Latex() # 1. Write out amplitude TODO include regulator terms m = [] ind = "\sigma_1" sum_terms = [Sub("p", ind), C("m")] m.append(Frac(Prod([Sum(sum_terms), Gamma(ind)]), Term("p^2 - m^2 + i\\epsilon"))) m.append(Integral("k", norm=True)) m.append(I()) m.append(C("e")) m.append(Term("\\gamma^\\mu")) ind = "\sigma_2" sum_terms = [Sub("p", ind), Sub("(-k)", ind), C("m")] m.append(Frac(Prod([Sum(sum_terms), Gamma(ind)]), Term("(p - k)^2 - m^2 + i\\epsilon"))) m.append(I()) m.append(C("e")) m.append(Term("\\gamma^\\nu")) m.append(Term("D_{\\mu \\nu}(k)")) ind = "\sigma_3" sum_terms = [Sub("p", ind), C("m")] m.append(Frac(Prod([Sum(sum_terms), Gamma(ind)]), Term("p^2 - m^2 + i\\epsilon"))) latex.add(" ".join([t.latex() for t in m])) # 2. Use Feynman parameterization to rewrite propagators m_ = [] ds = [] for term in m: if term.is_fraction(): # TODO m_.append(term.numer) ds.append(term.denom) else: m_.append(term) # Integrals for i, d in enumerate(ds): a = Term("0") b = Term("1" + "".join([" - z_{0}".format(j + 1) for j in range(i)])) m_.append(Integral("z_{0}".format(i+1), norm=True, limits=(a, b))) # Frac denom = "" denom += "\\left[" ds_ = [] for i, d in enumerate(ds): ds_.append("\\left(" + d.latex() + "\\right) " + "z_{0}".format(i + 1)) denom += " + ".join(ds_) denom += "\\right]" denom += "^{0}".format(len(ds)) frac = Frac(Term("1"), Term(denom)) m_.append(frac) m = m_ latex.add(" ".join([t.latex() for t in m])) # 3. Distribute numerators (p + m) ish terms # 4. Solve each internal momenta integral # 5. Move terms independent of z's to left # 6. Groan. Solve. # 7. Solve remaining integrals, contract metrics, etc. # 8. (OPTIONAL) Evaluate traces for non-Abelian theories. latex.render()
def calculate(config_str, internal_momenta): """ Calculate expression for configuration. """ latex = Latex() ################################################ ######## CONSTRUCT AMPLITUDE ########## ################################################ try: internal_momenta = internal_momenta.split() config, amp = make_amplitude(config_str, internal_momenta) except: raise ParseException("Error while parsing.") # Render latex.add_text("\\section*{Raw amplitude}") amp.latex_add(latex) if RENDER_ALL: latex.render() ################################################ ######## SIMPLIFY NUMERATOR ########## ################################################ amp.numer = amp.numer.expand() # Render latex.add_text("\\section*{Simplified numerator}") amp.latex_add(latex) if RENDER_ALL: latex.render() ################################################ ######## FEYNMAN'S TRICK ########## ################################################ denom_ = [] for arg in amp.denom.args: if type(arg) == sy.Pow: base, power = arg.args for _ in range(power): denom_.append(base) else: denom_.append(arg) n = len(denom_) amp.const *= gamma(n) zs = [sy.Symbol("{{ z_{{ {0} }} }}".format(i + 1)) for i in range(n)] amp.denom = sum([d * z for (d, z) in zip(denom_, zs)]).expand()**n for i, z in enumerate(zs): a = 0 b = 1 - sum(zs[:i]) amp.integrals_zs.append((z, a, b)) # Render latex.add_text("\\section*{Feynman parameterization}") latex.add_text("Here, we perform the following expansion:") latex.add_text("""$$ \\frac{1}{A_1} \\cdots \\frac{1}{A_n} = (n-1)! \\int\\limits_0^1 dz_1 \\int\\limits_0^{1-z_1} dz_2 \\cdots \\int\\limits_0^{1-z_1-\\cdots-z_{n-1}} dz_n \\frac{1}{(z_1 A_1 + \\cdots + z_n A_n)^n} $$""") latex.add_text( "We use this form because a single denominator raised to a power can be simplified with the Golden Integral." ) amp.latex_add(latex) if RENDER_ALL: latex.render() ################################################ ######## SPLIT NUMERATOR INTO TERMS ########## ################################################ amps = [] for numer_ in sy.Add.make_args(amp.numer): amp_ = amp.copy() amp_.numer = numer_ amps.append(amp_) # Render latex.add_text("\\section*{Expanded numerator}") latex.add_text( "We split the numerator into additive terms, to process individually. The following is a list of such terms:" ) for amp_ in amps: amp_.latex_add(latex) if RENDER_ALL: latex.render() ################################################ ######## EVAL. INTERNAL MOMENTA ########## ################################################ # TODO evaluate internal momenta integrals # At this point we stop with numer and denom and combine them into # one expression, `inner`, which is a sum of fractions. # TODO update this comment # Render an explanation latex.add_text("\\section*{{Golden Integral}}") latex.add_text("We resolve internal momentas with this transformation:") latex.add_text(""" $$\\int \\frac{d^d q}{(2 \pi)^d} \\frac{(q^2)^a}{(q^2 + D)^b} = i \\frac{\\Gamma (b-a-\\frac{1}{2}d) \\Gamma (a + \\frac{1}{2} d)}{(4 \\pi)^{d/2} \\Gamma(b) \\Gamma(\\frac{1}{2}d)} D^{-(b-a-d/2)}$$ """) latex.add_text( "After this section, all internal momenta should disappear. We will now resolve each term in a queue. Each term may produce additional terms, which are pushed to the back of the queue and resolved later." ) integrated_amps = [] #for i, amp_ in enumerate(amps): i = 0 while len(amps) > 0: # Pop off one amplitude amp_ = amps[0] amps = amps[1:] i += 1 latex.add_text( "\\section*{{Evaluating internal momenta in this term ({0} terms left)}}" .format(len(amps))) amp_.latex_add(latex) if RENDER_ALL: latex.render() # Find an internal momenta if len(amp_.integrals_internal) > 0: (k, _, _) = amp_.integrals_internal[0] latex.add_text("Integrating over ${0}$\\\\".format(k)) # TODO cleanup weird namespacing k_down_dummy = Momentum(k, "DUMMY", 0) k_up_dummy = Momentum(k, "DUMMY", 1) k2_dummy = k_down_dummy * k_up_dummy # Decompose denominator # denom = denom_nopow ^ b denom_nopow, b = amp_.denom.args[0], amp_.denom.args[1] # Completing the square # Denominator is always quadratic in momenta G = denom_nopow # aliasing for convenience latex.add_text("Completing the square\\") A = G.collect(k2_dummy).coeff(k2_dummy) G = sy.simplify(G - A * k2_dummy) B_up = G.collect(k_down_dummy).coeff(k_down_dummy) B_down = G.collect(k_up_dummy).coeff(k_up_dummy) B = B_up + Amplitude.flip_variant(B_down) G = sy.simplify(G - B_up * k_down_dummy - B_down * k_up_dummy) C = G D = -(B_up * Amplitude.flip_variant(B_up)) / (4 * A) + C latex.add("A = " + latex.get(A)) latex.add("B = " + latex.get(B)) latex.add("C = " + latex.get(C)) """ The denominator is in the form: A k^2 + Bk + C We define a new variable, q, such that q = A^(1/2) k + B / (2 A^(1/2)) and replace k: k = q / A^(1/2) - B / (2A) d^d k = (1 / A^(1/2)) d^d q The substitution k -> q yields: A k^2 + Bk + C |-> q^2 + D where we define D = C - B^2 / (4A) """ # Prepare to replace numerator k_name = k q_name = "q_{0}".format(len(amp_.qs) + 1) # TODO sloppy af amp_.qs.append(q_name) q_up = Momentum(q_name, "DUMMY", 1) q_down = Momentum(q_name, "DUMMY", 0) any_name = sy.Wild("a") any_ind = sy.Wild("b") any_variant = sy.Wild("c") any_B_up = B_up.replace(Momentum(any_name, "DUMMY", any_variant), Momentum(any_name, any_ind, any_variant)) any_B_down = Amplitude.flip_variant(any_B_up) # Actually replace numerator amp_.numer = amp_.numer.replace( Momentum(k_name, any_ind, 1), Momentum(q_name, any_ind, 1) / (A**0.5) - any_B_up / (2 * A)) amp_.numer = amp_.numer.replace( Momentum(k_name, any_ind, 0), Momentum(q_name, any_ind, 0) / (A**0.5) - any_B_down / (2 * A)) amp_.numer = sy.simplify(amp_.numer) # Replace denominator amp_.denom = (q_down * q_up + D)**b # Replace integral # TODO replace integral amp_.integrals_internal[0] = (q_name, _, _) amp_.numer /= A**0.5 latex.add_text("After ${0} \\to {1}$ substitutions".format( k_name, q_name)) amp_.latex_add(latex) if RENDER_ALL: latex.render() # Expand the numerator into different amplitudes and multiply them # back in the end amps__ = [] for numer in sy.Add.make_args(amp_.numer.expand()): new_amp = amp_.copy() new_amp.numer = numer amps__.append(new_amp) # Render latex.add_text( "\\section*{{Expanding numerator into {0} term(s)}}".format( len(amps__))) for amp__ in amps__: amp__.latex_add(latex) if RENDER_ALL: latex.render() # Finish q_name integration for each amplitude separately for amp__ in amps__: prod = sy.Mul.make_args(amp__.numer) # Get k-vectors # TODO Figure out a way to collect qs nicely qs = [ q for q in prod if isinstance(q, Momentum) and q.args[0].name == q_name ] latex.add_text("\\subsection*{Integrating this term:}") amp__.latex_add(latex) latex.add_text( "Found {0} q-vector terms in the numerator.\\\\".format( len(qs))) # Simplify q vectors # Ward identity for odd tensors if len(qs) % 2 == 1: # TODO integral evaluates to zero latex.add_text("Term vanishes due to Ward identity\\\\") amp__.const = 0 continue if len(qs) > 0: # TODO convert higher-order even tensor integral to # scalar integral pass else: # TODO Assume a = 0 for now # This is obviously wrong in general but will be easier # to fix with a good test case a = 0 # Golden integral #c, a = term.as_coeff_exponent(sy.Symbol(k2)) c_ = sy.I * gamma(b - a - (4 - EPS) / 2) * gamma(a + (4 + EPS) / 2) c_ /= gamma(b) c_ /= (4 * sy.pi)**2 # Part of the Golden integral d^q factor c_ *= (2 * sy.pi)**4 amp__.const *= c_ amp__.denom = D**(b - a - 2 ) # TODO generalize to d-dimensions # with 2 -> D / 2 # Add to amps if nonzero amps.append(amp__) # Remove internal integral amp__.integrals_internal = amp__.integrals_internal[1:] # Render latex.add_text("Apply golden integral") amp__.latex_add(latex) if RENDER_ALL: latex.render() else: integrated_amps.append(amp_) ## Compress inners by term #inners_dict = {} #for (c_, expr_) in amp.inners: # if expr_ not in inners_dict: # inners_dict[expr_] = 0 # inners_dict[expr_] += c_ #amp.inners = [(v, k) for (k, v) in inners_dict.items()] #amp.latex_add2(latex) amps = integrated_amps latex.add_text("\\section*{Final amplitudes after momenta integration}") for amp_ in amps: amp_.latex_add(latex) if RENDER_ALL: latex.render() ########################################## ######## CUTOFF INTEGRATIONS ########## ########################################## latex.add_text("\\section*{Integrating cutoffs}") latex.add_text( "Here we integrate all $t$-variables, which represent the upper and lower cutoffs." ) uv = sy.Symbol(config["Lamb"]) integrated_amps = [] for amp_ in amps: latex.add_text("\\subsection*{Integrating this term}") amp_.latex_add(latex) expr_ = 1 / amp_.denom latex.add_text("Denominator only") latex.add(latex.get(expr_)) if RENDER_ALL: latex.render() # Integrate cutoffs for (t, a, b) in amp_.integrals_cutoffs: # Integrate w.r.t. cutoff expr_ = sy.integrate(expr_, (t, a, b)) latex.add_text("Integrating wrt ${0}$...".format(t)) latex.add(latex.get(expr_)) if RENDER_ALL: latex.render() # Collecting highest order term old_expr = expr_ while True: old_expr = expr_ expr_ = sy.expand_log(expr_, force=True) expr_ = get_highest_log_term(expr_, uv) expr_ = sy.simplify(expr_) if old_expr == expr_: # No more changes break latex.add_text("Keeping only highest order term...") latex.add(latex.get(expr_)) if RENDER_ALL: latex.render() amp_.numer *= expr_ amp_.denom = 1 amp_.integrals_cutoffs = [] integrated_amps.append(amp_) amps = integrated_amps ###################################### ######## Z INTEGRATIONS ########## ###################################### latex.add_text("\\section*{Integrating $z$-variables}") latex.add_text( "Here we integrate all $z$-variables, the Feynman parameters.") integrated_amps = [] for amp_ in amps: latex.add_text("\\subsection*{Integrating this term}") amp_.latex_add(latex) # Integrate cutoffs expr_ = amp_.numer for (z, a, b) in amp_.integrals_zs[::-1]: # Integrate w.r.t. cutoff # Rationalize decimal powers first, or sympy breaks expr_ = sy.nsimplify(expr_, tolerance=0.001, rational=True) expr_ = sy.integrate(expr_, (z, a, b)) latex.add_text("Integrating wrt ${0}$...".format(z)) latex.add(latex.get(expr_)) if RENDER_ALL: latex.render() amp_.numer = expr_ amp_.integrals_zs = [] integrated_amps.append(amp_) amps = integrated_amps latex.add_text( "\\section*{Final amplitudes after $z$-variable integration}") for amp_ in amps: amp_.latex_add(latex) if RENDER_ALL: latex.render() ################################################ ######## EVALUATE SPINS AND GAMMAS ########## ################################################ latex.add_text("\\section*{Evaluating spins and gamma matrices}") #latex.add_text("TODO jk do it yourself you slags, here's the sum, have fun. Don't forget to take traces/multiply by -1 for internal fermion loops.") latex.add_text("TODO. Here's the sum for now:") latex.add("\\; + \\;".join([amp_.get_latex(latex) for amp_ in amps])) if RENDER_ALL: latex.render() ################################################ ######## RENDER ########## ################################################ #amp.latex_add2(latex) latex.render() return "\\; + \\;".join([amp_.get_latex(latex) for amp_ in amps])