Source code for yadism.coefficient_functions

"""Collect all kernels for given |FNS|."""

import numpy as np
from eko.matchings import nf_default

from . import asy, heavy, intrinsic, kernels, light
from .partonic_channel import EmptyPartonicChannel


[docs] class Component(list): """Used for organize elements and debugging purpose.""" heavyness = {0: "light", 4: "charm", 5: "bottom", 6: "top"} def __init__(self, heavy, kernels=None): self.heavy = self.heavyness[heavy] if kernels is None: kernels = [] super().__init__(kernels)
[docs] def __repr__(self): return self.heavy + f"({len(self)} kernels)"
# TODO add more doc strings
[docs] class Combiner: """Does the matching between coefficient functions and partons with their appropriate coupling strength. Parameters ---------- esf : EvaluatedStructureFunction current ESF """ def __init__(self, esf): self.esf = esf self.masses = {4 + i: not mass for i, mass in enumerate(esf.info.ZMq)} self.obs_name = esf.info.obs_name self.nf = nf_default(esf.Q2, esf.info.threshold) self.target = esf.info.target self.scheme = esf.info.scheme self.fonllparts = esf.info.fonllparts
[docs] def collect(self): """Collect all kernels.""" comps = [] family = self.obs_name.flavor_family # Adding light component if family in ["light", "total"] and self.fonllparts in ["massless", "full"]: comps.append(self.light_component()) if family == "heavy" and self.fonllparts in ["massless", "full"]: # the only case in which an heavy contribution is not present in those # accounted for in total, it's when heavy already became heavylight comps.extend(self.heavylight_components()) if family in ["heavy", "total"] and self.fonllparts in ["massive", "full"]: comps.extend(self.heavy_components()) return comps
[docs] def light_component(self): """Collect massless kernels.""" nf = self.nf masses = self.masses comp = Component(0) # light does not contain any mass effects and so is just always there comp.extend(light.kernels.generate(self.esf, nf)) # instead missing encodes mass effects and so we need to fork: for ihq in range(nf + 1, 7): if masses[ihq]: if "FFN0" in self.scheme: comp.extend( asy.kernels.generate_missing_asy( self.esf, nf, ihq, self.esf.info.theory["pto_evol"], ) ) else: comp.extend(heavy.kernels.generate_missing(self.esf, nf, ihq)) return comp
[docs] def heavylight_components(self): """Collect single-flavor massless kernels.""" nf = self.nf hq = self.obs_name.hqnumber masses = self.masses comps = [] if hq < nf or (hq == nf and not masses[hq]): heavylight = Component(hq) heavylight.extend(kernels.generate_single_flavor_light(self.esf, nf, hq)) comps.append(heavylight) return comps
[docs] def heavy_components(self): """Collect massive kernels.""" nf = self.nf hq = self.obs_name.hqnumber masses = self.masses comps = [] heavy_comps = {} # The loop starts at nf because nf counts the number of quarks of which # are above the mass threshold. For these quarks the masses are not # considered. for sfh in range(nf, 7): # exclude sfh=3, since heavy contributions are there for [4,5,6] # if it's ZM you don't even have the component if sfh not in masses: continue # There is no massive heavy contribution for ZM if not masses[sfh]: continue heavy_comps[sfh] = Component(sfh) # calculate only the contribution corresponding to the observable # i.e. charm for F_charm, bottom for F_bottom. In the case of # F_total (if hq=0), sum over all massive contributions. if hq not in (0, sfh): continue # heavy quark is intrinsic if "FFN0" in self.scheme: heavy_comps[sfh].extend( asy.kernels.generate_intrinsic_asy( self.esf, nf, self.esf.info.theory["pto_evol"], ihq=sfh ), ) else: heavy_comps[sfh].extend(intrinsic.kernels.generate(self.esf, ihq=sfh)) if "FFN0" in self.scheme: heavy_comps[sfh].extend( asy.kernels.generate_heavy_asy( self.esf, nf, self.esf.info.theory["pto_evol"], ihq=sfh ) ) else: heavy_comps[sfh].extend(heavy.kernels.generate(self.esf, nf, ihq=sfh)) comps.append(heavy_comps[sfh]) return comps
[docs] @staticmethod def apply_isospin(full, z, a): """Apply isospin symmetry to u and d distributions. Parameters ---------- full : list(yadism.kernels.Kernel) all participants z : float number of protons a : float atomic mass number """ nucl_factors = np.array([[z, a - z], [a - z, z]]) / a for ker in full: for sign in [-1, 1]: ps = np.array( [ker.partons.get(sign * 1, 0), ker.partons.get(sign * 2, 0)] ) ker.partons[sign * 1], ker.partons[sign * 2] = nucl_factors @ ps
[docs] @staticmethod def drop_empty(full): """Drop kernels with :class:`EmptyPartonicChannel` or its partons with empty weight. Parameters ---------- elems : list(yadism.kernels.Kernel) all participants Returns ------- filtered_kernels : list(yadism.kernels.Kernel) improved participants """ filtered_kernels = [] for ker in full: ker.partons = {p: w for p, w in ker.partons.items() if w != 0} if len(ker.partons) > 0 and not isinstance(ker.coeff, EmptyPartonicChannel): filtered_kernels.append(ker) return filtered_kernels
[docs] def collect_elems(self): """Collect all kernels according to the |FNS|. Returns ------- elems : list(yadism.kernels.Kernel) all participants """ components = self.collect() full = [] for comp in components: full.extend(comp) # add level-0 nuclear correction: apply isospin symmetry self.apply_isospin(full, self.target["Z"], self.target["A"]) # drop all kernels with 0 weight, or empty coeffs return self.drop_empty(full)