diff --git a/benchmarks/eko/benchmark_alphaem.py b/benchmarks/eko/benchmark_alphaem.py new file mode 100644 index 000000000..16e56ac4a --- /dev/null +++ b/benchmarks/eko/benchmark_alphaem.py @@ -0,0 +1,144 @@ +"""This module benchmarks alpha_em against alphaQED23 and validphys. + +alphaQED23 can be obtained from http://www-com.physik.hu-berlin.de/~fjeger/software.html . +""" + +import numpy as np +import pytest + +from eko.couplings import Couplings +from eko.quantities.couplings import CouplingEvolutionMethod, CouplingsInfo +from eko.quantities.heavy_quarks import QuarkMassScheme + + +@pytest.mark.isolated +class BenchmarkCouplings: + def test_alphaQED_high(self): + """testing aginst alphaQED23 for high Q values""" + alphaQED23 = np.array( + [ + 0.0075683, + 0.0075867, + 0.0076054, + 0.0076245, + 0.0076437, + 0.0076631, + 0.0076827, + 0.0077024, + 0.0077222, + 0.0077421, + 0.0077621, + ] + ) + qvec = np.geomspace(10, 100, 11) + couplings = CouplingsInfo.from_dict( + dict( + alphas=0.118, + alphaem=7.7553e-03, + scale=91.2, + num_flavs_ref=5, + max_num_flavs=5, + em_running=True, + ) + ) + eko_alpha = Couplings( + couplings, + (3, 2), + method=CouplingEvolutionMethod.EXACT, + masses=[m**2 for m in [1.51, 4.92, 172.5]], + hqm_scheme=QuarkMassScheme.POLE, + thresholds_ratios=[1.0, 1.0, np.inf], + ) + alpha_eko = np.array([eko_alpha.a_em(q**2) * 4 * np.pi for q in qvec]) + + np.testing.assert_allclose(alphaQED23, alpha_eko, rtol=1.8e-4) + + def test_alphaQED_low(self): + """testing aginst alphaQED23 for low Q values: they are close but not identical""" + alphaQED23 = np.array( + [ + 0.0074192, + 0.007431, + 0.0074434, + 0.0074565, + 0.0074702, + 0.0074847, + 0.0075, + 0.0075161, + 0.0075329, + 0.0075503, + 0.0075683, + ] + ) + qvec = np.geomspace(1, 10, 11) + couplings = CouplingsInfo.from_dict( + dict( + alphas=0.118, + alphaem=7.7553e-03, + scale=91.2, + num_flavs_ref=5, + max_num_flavs=5, + em_running=True, + ) + ) + eko_alpha = Couplings( + couplings, + (3, 2), + method=CouplingEvolutionMethod.EXACT, + masses=[m**2 for m in [1.51, 4.92, 172.5]], + hqm_scheme=QuarkMassScheme.POLE, + thresholds_ratios=[1.0, 1.0, np.inf], + ) + alpha_eko = np.array([eko_alpha.a_em(q**2) * 4 * np.pi for q in qvec]) + + np.testing.assert_allclose(alphaQED23, alpha_eko, rtol=3.2e-3) + + def test_validphys(self): + """testing aginst validphys""" + alpha_vp = np.array( + [ + 0.007408255774054356, + 0.007425240094018394, + 0.007449051094996458, + 0.007476301027742958, + 0.007503751810862984, + 0.007532299008699658, + 0.007561621958607614, + 0.007591174885612722, + 0.007620960508577136, + 0.00765098158940323, + 0.007681240933888789, + 0.007711741392602655, + 0.007742485861781425, + 0.007773477284247778, + 0.007804718650351058, + 0.007836212998930739, + 0.00786796341830342, + 0.007899973047274033, + 0.007932245076171957, + 0.00796478274791273, + ] + ) + qvec = np.geomspace(1, 1000, 20) + couplings = CouplingsInfo.from_dict( + dict( + alphas=0.118, + alphaem=7.7553e-03, + scale=91.2, + num_flavs_ref=5, + max_num_flavs=5, + em_running=True, + ) + ) + eko_alpha = Couplings( + couplings, + (3, 2), + method=CouplingEvolutionMethod.EXACT, + masses=[m**2 for m in [1.51, 4.92, 172.5]], + hqm_scheme=QuarkMassScheme.POLE, + thresholds_ratios=[1.0, 1.0, np.inf], + ) + eko_alpha.decoupled_running = True + alpha_eko = np.array([eko_alpha.a_em(q**2) * 4 * np.pi for q in qvec]) + + np.testing.assert_allclose(alpha_vp, alpha_eko, rtol=5e-6) diff --git a/src/eko/beta.py b/src/eko/beta.py index 8ce6658f1..5f8f88193 100644 --- a/src/eko/beta.py +++ b/src/eko/beta.py @@ -32,7 +32,7 @@ def beta_qcd_as2(nf): @nb.njit(cache=True) -def beta_qed_aem2(nf): +def beta_qed_aem2(nf, nl): r"""Compute the first coefficient of the QED beta function. Implements Eq. (7) of :cite:`Surguladze:1996hx`. @@ -41,6 +41,8 @@ def beta_qed_aem2(nf): ---------- nf : int number of active flavors + nl : int + number of leptons partecipating to alphaem running Returns ------- @@ -50,7 +52,6 @@ def beta_qed_aem2(nf): """ nu = constants.uplike_flavors(nf) nd = nf - nu - nl = 3 # TODO : pass nl as an argument?? beta_qed_aem2 = ( -4.0 / 3 * (nl + constants.NC * (nu * constants.eu2 + nd * constants.ed2)) ) @@ -83,7 +84,7 @@ def beta_qcd_as3(nf): @nb.njit(cache=True) -def beta_qed_aem3(nf): +def beta_qed_aem3(nf, nl): r"""Compute the second coefficient of the QED beta function. Implements Eq. (7) of :cite:`Surguladze:1996hx`. @@ -92,6 +93,8 @@ def beta_qed_aem3(nf): ---------- nf : int number of active flavors + nl : int + number of leptons partecipating to alphaem running Returns ------- @@ -101,7 +104,6 @@ def beta_qed_aem3(nf): """ nu = constants.uplike_flavors(nf) nd = nf - nu - nl = 3 # TODO : pass nl as an argument?? beta_qed_aem3 = -4.0 * ( nl + constants.NC * (nu * constants.eu2**2 + nd * constants.ed2**2) ) @@ -246,7 +248,7 @@ def beta_qcd(k, nf): @nb.njit(cache=True) -def beta_qed(k, nf): +def beta_qed(k, nf, nl): r"""Compute value of a beta_qed coefficients. Parameters @@ -255,6 +257,8 @@ def beta_qed(k, nf): perturbative order nf : int number of active flavors + nl : int + number of leptons partecipating to alphaem running Returns ------- @@ -264,9 +268,9 @@ def beta_qed(k, nf): """ beta_ = 0 if k == (0, 2): - beta_ = beta_qed_aem2(nf) + beta_ = beta_qed_aem2(nf, nl) elif k == (0, 3): - beta_ = beta_qed_aem3(nf) + beta_ = beta_qed_aem3(nf, nl) elif k == (1, 2): beta_ = beta_qed_aem2as1(nf) else: @@ -295,7 +299,7 @@ def b_qcd(k, nf): @nb.njit(cache=True) -def b_qed(k, nf): +def b_qed(k, nf, nl): r"""Compute b_qed coefficient. Parameters @@ -304,6 +308,8 @@ def b_qed(k, nf): perturbative order nf : int number of active flavors + nl : int + number of leptons partecipating to alphaem running Returns ------- @@ -311,4 +317,4 @@ def b_qed(k, nf): b_qed_k(nf) """ - return beta_qed(k, nf) / beta_qed((0, 2), nf) + return beta_qed(k, nf, nl) / beta_qed((0, 2), nf, nl) diff --git a/src/eko/constants.py b/src/eko/constants.py index fbd0df459..55dff40a2 100644 --- a/src/eko/constants.py +++ b/src/eko/constants.py @@ -25,6 +25,9 @@ Defaults to :math:`\frac{N_C^2-1}{2N_C} = 4/3`. """ +MTAU = 1.777 +"""Mass of the tau.""" + eu2 = 4.0 / 9 """Up quarks charge squared.""" @@ -84,7 +87,7 @@ def uplike_flavors(nf): nu : int """ - if nf not in range(2, 6 + 1): + if nf > 6: raise NotImplementedError("Selected nf is not implemented") nu = nf // 2 return nu diff --git a/src/eko/couplings.py b/src/eko/couplings.py index f0849aaf8..e8940b68c 100644 --- a/src/eko/couplings.py +++ b/src/eko/couplings.py @@ -277,7 +277,7 @@ def expanded_qed(ref, order, beta0, b_vec, lmu): @nb.njit(cache=True) def couplings_expanded_alphaem_running( - order, couplings_ref, nf, scale_from, scale_to, decoupled_running + order, couplings_ref, nf, nl, scale_from, scale_to, decoupled_running ): r"""Compute coupled expanded expression of the couplings for running alphaem. @@ -291,6 +291,8 @@ def couplings_expanded_alphaem_running( reference alpha_s and alpha nf : int value of nf for computing the couplings + nl : int + number of leptons partecipating to alphaem running scale_from : float reference scale scale_to : float @@ -308,8 +310,8 @@ def couplings_expanded_alphaem_running( beta0_qcd = beta_qcd((2, 0), nf) b_vec_qcd = [b_qcd((i + 2, 0), nf) for i in range(order[0])] res_as = expanded_qcd(couplings_ref[0], order[0], beta0_qcd, b_vec_qcd, lmu) - beta0_qed = beta_qed((0, 2), nf) - b_vec_qed = [b_qed((0, i + 2), nf) for i in range(order[1])] + beta0_qed = beta_qed((0, 2), nf, nl) + b_vec_qed = [b_qed((0, i + 2), nf, nl) for i in range(order[1])] res_aem = expanded_qed(couplings_ref[1], order[1], beta0_qed, b_vec_qed, lmu) # if order[0] >= 1 and order[1] >= 1: # order[0] is always >=1 @@ -322,7 +324,7 @@ def couplings_expanded_alphaem_running( ) res_aem += ( -couplings_ref[1] ** 2 - * b_qed((1, 2), nf) + * b_qed((1, 2), nf, nl) * np.log(1 + beta0_qed * couplings_ref[0] * lmu) ) return np.array([res_as, res_aem]) @@ -524,7 +526,7 @@ def rge(_t, a, b_vec): ) return res.y[0][-1] - def compute_exact_alphaem_running(self, a_ref, nf, scale_from, scale_to): + def compute_exact_alphaem_running(self, a_ref, nf, nl, scale_from, scale_to): """Compute couplings via |RGE| with running alphaem. Parameters @@ -533,6 +535,8 @@ def compute_exact_alphaem_running(self, a_ref, nf, scale_from, scale_to): reference alpha_s and alpha nf : int value of nf for computing alpha_i + nl : int + number of leptons partecipating to alphaem running scale_from : float reference scale scale_to : float @@ -579,12 +583,12 @@ def compute_exact_alphaem_running(self, a_ref, nf, scale_from, scale_to): # while aem is constant return np.array([rge_qcd, a_ref[1]]) if self.order[1] >= 1: - beta_qed_vec = [beta_qed((0, 2), nf)] + beta_qed_vec = [beta_qed((0, 2), nf, nl)] if not self.decoupled_running: beta_qcd_mix = beta_qcd((2, 1), nf) - beta_qed_mix = beta_qed((1, 2), nf) # order[0] is always at least 1 + beta_qed_mix = beta_qed((1, 2), nf, nl) # order[0] is always at least 1 if self.order[1] >= 2: - beta_qed_vec.append(beta_qed((0, 3), nf)) + beta_qed_vec.append(beta_qed((0, 3), nf, nl)) # integration kernel def rge(_t, a, beta_qcd_vec, beta_qcd_mix, beta_qed_vec, beta_qed_mix): @@ -659,7 +663,7 @@ def compute_exact_fixed_alphaem(self, a_ref, nf, scale_from, scale_to): ) return np.array([rge_qcd, a_ref[1]]) - def compute(self, a_ref, nf, scale_from, scale_to): + def compute(self, a_ref, nf, nl, scale_from, scale_to): """Compute actual couplings. This is a wrapper in order to pass the computation to the corresponding method @@ -671,6 +675,8 @@ def compute(self, a_ref, nf, scale_from, scale_to): reference a nf : int value of nf for computing alpha + nl : int + number of leptons partecipating to alphaem running scale_from : float reference scale scale_to : float @@ -681,7 +687,7 @@ def compute(self, a_ref, nf, scale_from, scale_to): numpy.ndarray couplings at target scale :math:`a(Q^2)` """ - key = (float(a_ref[0]), float(a_ref[1]), nf, scale_from, float(scale_to)) + key = (float(a_ref[0]), float(a_ref[1]), nf, nl, scale_from, float(scale_to)) try: return self.cache[key].copy() except KeyError: @@ -689,7 +695,7 @@ def compute(self, a_ref, nf, scale_from, scale_to): if self.method == "exact": if self.alphaem_running: a_new = self.compute_exact_alphaem_running( - a_ref.astype(float), nf, scale_from, scale_to + a_ref.astype(float), nf, nl, scale_from, scale_to ) else: a_new = self.compute_exact_fixed_alphaem( @@ -701,6 +707,7 @@ def compute(self, a_ref, nf, scale_from, scale_to): self.order, a_ref.astype(float), nf, + nl, scale_from, float(scale_to), self.decoupled_running, @@ -740,7 +747,20 @@ def a( for k, seg in enumerate(path): # skip a very short segment, but keep the matching if not np.isclose(seg.origin, seg.target): - new_a = self.compute(final_a, seg.nf, seg.origin, seg.target) + nli = matchings.lepton_number(seg.origin) + nlf = matchings.lepton_number(seg.target) + if self.order[1] != 0 and nli != nlf: + # it means that MTAU is between origin and target: + # first we evolve from origin to MTAU with nli leptons + a_tmp = self.compute( + final_a, seg.nf, nli, seg.origin, constants.MTAU**2 + ) + # then from MTAU to target with nlf leptons + new_a = self.compute( + a_tmp, seg.nf, nlf, constants.MTAU**2, seg.target + ) + else: + new_a = self.compute(final_a, seg.nf, nli, seg.origin, seg.target) else: new_a = final_a # apply matching conditions: see hep-ph/9706430 diff --git a/src/eko/evolution_operator/__init__.py b/src/eko/evolution_operator/__init__.py index 242fee66e..f334b83e3 100644 --- a/src/eko/evolution_operator/__init__.py +++ b/src/eko/evolution_operator/__init__.py @@ -26,7 +26,7 @@ from ..kernels import singlet_qed as qed_s from ..kernels import utils from ..kernels import valence_qed as qed_v -from ..matchings import Segment +from ..matchings import Segment, lepton_number from ..member import OpMember logger = logging.getLogger(__name__) @@ -520,7 +520,7 @@ def quad_ker_qed( # scale var exponentiated is directly applied on gamma if sv_mode == sv.Modes.exponentiated: gamma_s = sv.exponentiated.gamma_variation_qed( - gamma_s, order, nf, L, alphaem_running + gamma_s, order, nf, lepton_number(mu2_to), L, alphaem_running ) ker = qed_s.dispatcher( order, @@ -550,7 +550,7 @@ def quad_ker_qed( # scale var exponentiated is directly applied on gamma if sv_mode == sv.Modes.exponentiated: gamma_v = sv.exponentiated.gamma_variation_qed( - gamma_v, order, nf, L, alphaem_running + gamma_v, order, nf, lepton_number(mu2_to), L, alphaem_running ) ker = qed_v.dispatcher( order, @@ -577,7 +577,7 @@ def quad_ker_qed( # scale var exponentiated is directly applied on gamma if sv_mode == sv.Modes.exponentiated: gamma_ns = sv.exponentiated.gamma_variation_qed( - gamma_ns, order, nf, L, alphaem_running + gamma_ns, order, nf, lepton_number(mu2_to), L, alphaem_running ) ker = qed_ns.dispatcher( order, @@ -723,38 +723,16 @@ def compute_aem_list(self): as_list = np.array([self.a_s[0], self.a_s[1]]) a_half = np.zeros((ev_op_iterations, 2)) else: - as0 = self.a_s[0] - as1 = self.a_s[1] - aem0 = self.a_em[0] - aem1 = self.a_em[1] - q2ref = self.managers["couplings"].mu2_ref - delta_from = abs(self.q2_from - q2ref) - delta_to = abs(self.q2_to - q2ref) - # I compute the values in aem_list starting from the mu2 - # that is closer to mu_ref. - if delta_from > delta_to: - a_start = np.array([as1, aem1]) - mu2_start = self.q2_to - else: - a_start = np.array([as0, aem0]) - mu2_start = self.q2_from couplings = self.managers["couplings"] mu2_steps = utils.geomspace(self.q2_from, self.q2_to, 1 + ev_op_iterations) mu2_l = mu2_steps[0] as_list = np.array( - [ - couplings.compute( - a_ref=a_start, nf=self.nf, scale_from=mu2_start, scale_to=mu2 - )[0] - for mu2 in mu2_steps - ] + [couplings.a_s(scale_to=mu2, nf_to=self.nf) for mu2 in mu2_steps] ) a_half = np.zeros((ev_op_iterations, 2)) for step, mu2_h in enumerate(mu2_steps[1:]): mu2_half = (mu2_h + mu2_l) / 2.0 - a_s, aem = couplings.compute( - a_ref=a_start, nf=self.nf, scale_from=mu2_start, scale_to=mu2_half - ) + a_s, aem = couplings.a(scale_to=mu2_half, nf_to=self.nf) a_half[step] = [a_s, aem] mu2_l = mu2_h return as_list, a_half diff --git a/src/eko/matchings.py b/src/eko/matchings.py index 0dfcae24f..9ef6b8f9e 100644 --- a/src/eko/matchings.py +++ b/src/eko/matchings.py @@ -3,8 +3,10 @@ from dataclasses import dataclass from typing import List, Union +import numba as nb import numpy as np +from .constants import MTAU from .io.types import EvolutionPoint as EPoint from .io.types import FlavorIndex, FlavorsNumber, SquaredScale from .quantities.heavy_quarks import MatchingScales @@ -209,3 +211,23 @@ def is_downward_path(path: Path) -> bool: def flavor_shift(is_downward: bool) -> int: """Determine the shift to number of light flavors.""" return 4 if is_downward else 3 + + +@nb.njit(cache=True) +def lepton_number(q2): + """Compute the number of leptons. + + Note: muons and electrons are always massless as for up, down and strange. + + Parameters + ---------- + q2 : float + scale + + Returns + ------- + int : + Number of leptons + + """ + return 3 if q2 > MTAU**2 else 2 diff --git a/src/eko/scale_variations/exponentiated.py b/src/eko/scale_variations/exponentiated.py index 13d21c34e..7efb88e79 100644 --- a/src/eko/scale_variations/exponentiated.py +++ b/src/eko/scale_variations/exponentiated.py @@ -46,7 +46,7 @@ def gamma_variation(gamma, order, nf, L): @nb.njit(cache=True) -def gamma_variation_qed(gamma, order, nf, L, alphaem_running): +def gamma_variation_qed(gamma, order, nf, nl, L, alphaem_running): """Adjust the anomalous dimensions with the scale variations. Parameters @@ -57,6 +57,8 @@ def gamma_variation_qed(gamma, order, nf, L, alphaem_running): perturbation order nf : int number of active flavors + nl : int + number of leptons partecipating to alphaem running L : float logarithmic ratio of factorization and renormalization scale @@ -71,6 +73,6 @@ def gamma_variation_qed(gamma, order, nf, L, alphaem_running): gamma[1:, 0] = gamma_variation(gamma[1:, 0], order, nf, L) if alphaem_running: if order[1] >= 2: - beta0qed = beta.beta_qed((0, 2), nf) + beta0qed = beta.beta_qed((0, 2), nf, nl) gamma[0, 2] += beta0qed * gamma[0, 1] * L return gamma diff --git a/tests/eko/evolution_operator/test_init.py b/tests/eko/evolution_operator/test_init.py index fa8eb77a0..66112aff8 100644 --- a/tests/eko/evolution_operator/test_init.py +++ b/tests/eko/evolution_operator/test_init.py @@ -202,6 +202,12 @@ def __init__(self): def a(self, scale_to=None, nf_to=None): return (0.1, 0.01) + def a_s(self, scale_to=None, nf_to=None): + return self.a(scale_to, nf_to)[0] + + def a_em(self, scale_to=None, nf_to=None): + return self.a(scale_to, nf_to)[1] + def compute(self, a_ref, nf, scale_from, scale_to): return a_ref diff --git a/tests/eko/scale_variations/test_exponentiated.py b/tests/eko/scale_variations/test_exponentiated.py index 79cc6b386..ee76f9ed9 100644 --- a/tests/eko/scale_variations/test_exponentiated.py +++ b/tests/eko/scale_variations/test_exponentiated.py @@ -39,13 +39,13 @@ def test_gamma_ns_qed_fact(): [0.15, 0.0, 0.0, 0.0, 0.0], ] ).transpose() - gamma_ns_as1aem1_0 = gamma_variation_qed(gamma_ns.copy(), (1, 1), 3, 0, True) + gamma_ns_as1aem1_0 = gamma_variation_qed(gamma_ns.copy(), (1, 1), 3, 3, 0, True) np.testing.assert_allclose(gamma_ns_as1aem1_0, gamma_ns) - gamma_ns_as1aem1_1 = gamma_variation_qed(gamma_ns.copy(), (1, 1), 3, -1, True) + gamma_ns_as1aem1_1 = gamma_variation_qed(gamma_ns.copy(), (1, 1), 3, 3, -1, True) np.testing.assert_allclose(gamma_ns_as1aem1_1, gamma_ns) - gamma_ns_as2aem2_1 = gamma_variation_qed(gamma_ns.copy(), (2, 2), 3, -1, True) + gamma_ns_as2aem2_1 = gamma_variation_qed(gamma_ns.copy(), (2, 2), 3, 3, -1, True) assert gamma_ns_as2aem2_1[2, 0] < gamma_ns[2, 0] assert gamma_ns_as2aem2_1[0, 2] > gamma_ns[0, 2] # beta0qed < 0 - gamma_ns_as4aem2_1 = gamma_variation_qed(gamma_ns.copy(), (4, 2), 3, -1, True) + gamma_ns_as4aem2_1 = gamma_variation_qed(gamma_ns.copy(), (4, 2), 3, 3, -1, True) gamma_ns_N3LO_1 = gamma_variation(gamma_ns.copy()[1:, 0], (4, 0), 3, -1) np.testing.assert_allclose(gamma_ns_as4aem2_1[1:, 0], gamma_ns_N3LO_1) diff --git a/tests/eko/test_beta.py b/tests/eko/test_beta.py index cd64c2b85..682c79f57 100644 --- a/tests/eko/test_beta.py +++ b/tests/eko/test_beta.py @@ -6,7 +6,7 @@ import pytest from eko import beta -from eko.constants import zeta3 +from eko.constants import uplike_flavors, zeta3 def _flav_test(function): @@ -27,9 +27,14 @@ def test_beta_as2(): def test_beta_aem2(): """Test first beta function coefficient""" # from hep-ph/9803211 - np.testing.assert_approx_equal( - beta.beta_qed_aem2(5), -4.0 / 3 * (3 + 3 * (2 * 4 / 9 + 3 * 1 / 9)) - ) + for nf in range(3, 6 + 1): + for nl in range(2, 3 + 1): + nu = uplike_flavors(nf) + nd = nf - nu + np.testing.assert_approx_equal( + beta.beta_qed_aem2(nf, nl), + -4.0 / 3 * (nl + 3 * (nu * 4 / 9 + nd * 1 / 9)), + ) def test_beta_as3(): @@ -42,9 +47,14 @@ def test_beta_as3(): def test_beta_aem3(): """Test second beta function coefficient""" # from hep-ph/9803211 - np.testing.assert_approx_equal( - beta.beta_qed_aem3(5), -4.0 * (3 + 3 * (2 * 16 / 81 + 3 * 1 / 81)) - ) + for nf in range(3, 6 + 1): + for nl in range(2, 3 + 1): + nu = uplike_flavors(nf) + nd = nf - nu + np.testing.assert_approx_equal( + beta.beta_qed_aem3(nf, nl), + -4.0 * (nl + 3 * (nu * 16 / 81 + nd * 1 / 81)), + ) def test_beta_as4(): @@ -65,22 +75,44 @@ def test_beta_as5(): def test_beta(): """beta-wrapper""" - nf = 3 - np.testing.assert_allclose(beta.beta_qcd((2, 0), nf), beta.beta_qcd_as2(nf)) - np.testing.assert_allclose(beta.beta_qcd((3, 0), nf), beta.beta_qcd_as3(nf)) - np.testing.assert_allclose(beta.beta_qcd((4, 0), nf), beta.beta_qcd_as4(nf)) - np.testing.assert_allclose(beta.beta_qcd((5, 0), nf), beta.beta_qcd_as5(nf)) - np.testing.assert_allclose(beta.beta_qcd((2, 1), nf), beta.beta_qcd_as2aem1(nf)) - np.testing.assert_allclose(beta.beta_qed((0, 2), nf), beta.beta_qed_aem2(nf)) - np.testing.assert_allclose(beta.beta_qed((0, 3), nf), beta.beta_qed_aem3(nf)) - np.testing.assert_allclose(beta.beta_qed((1, 2), nf), beta.beta_qed_aem2as1(nf)) + for nf in range(3, 6 + 1): + for nl in range(2, 3 + 1): + np.testing.assert_allclose(beta.beta_qcd((2, 0), nf), beta.beta_qcd_as2(nf)) + np.testing.assert_allclose(beta.beta_qcd((3, 0), nf), beta.beta_qcd_as3(nf)) + np.testing.assert_allclose(beta.beta_qcd((4, 0), nf), beta.beta_qcd_as4(nf)) + np.testing.assert_allclose(beta.beta_qcd((5, 0), nf), beta.beta_qcd_as5(nf)) + np.testing.assert_allclose( + beta.beta_qcd((2, 1), nf), beta.beta_qcd_as2aem1(nf) + ) + np.testing.assert_allclose( + beta.beta_qed((0, 2), nf, nl), beta.beta_qed_aem2(nf, nl) + ) + np.testing.assert_allclose( + beta.beta_qed((0, 3), nf, nl), beta.beta_qed_aem3(nf, nl) + ) + np.testing.assert_allclose(beta.beta_qed((1, 2), nf, nl), beta.beta_qed_aem2as1(nf)) with pytest.raises(ValueError): beta.beta_qcd((6, 0), 3) with pytest.raises(ValueError): - beta.beta_qed((0, 4), 3) + beta.beta_qed((0, 4), 3, 3) def test_b(): """b-wrapper""" np.testing.assert_allclose(beta.b_qcd((2, 0), 3), 1.0) - np.testing.assert_allclose(beta.b_qed((0, 2), 3), 1.0) + np.testing.assert_allclose(beta.b_qed((0, 2), 3, 3), 1.0) + + +def test_zero(): + """test that beta_qed is zero for nf=nl=0""" + for ord in range(2, 3 + 1): + np.testing.assert_allclose(beta.beta_qed((0, ord), 0, 0), 0) + + +def test_linearity(): + """test linearity of QED beta function when nf=0""" + for ord in range(2, 3 + 1): + for nl in [0, 2]: + np.testing.assert_allclose( + beta.beta_qed((0, ord), 0, nl) / 2, beta.beta_qed((0, ord), 0, nl / 2) + )