Source code for n3fit.layers.msr_normalization

"""
    Definition of the imposition of the Momentum Sum Rule and Valence Sum Rules to in the PDF fit.

    In the module level constants ``{MSR/VSR}_COMPONENTS`` the flavours affected by the MSR and VSR are defined.
    For the Valence Sum Rule instead `VSR_DENOMINATOR` defines the integral of which flavour are used
    to compute the normalization. Note that for a Nf=4 fit  `v35=v24=v`.
    If the number of flavours were to be changed in the future, this would need to be updated accordingly.
"""

import numpy as np

from n3fit.backends import MetaLayer
from n3fit.backends import operations as op

IDX = {
    'photon': 0,
    'sigma': 1,
    'g': 2,
    'v': 3,
    'v3': 4,
    'v8': 5,
    'v15': 6,
    'v24': 7,
    'v35': 8,
    't3': 9,
    't8': 10,
    't15': 11,
    't24': 12,
    't35': 13,
}
MSR_COMPONENTS = ['g']
MSR_DENOMINATORS = {'g': 'g'}
# The VSR normalization factor of component f is given by
# VSR_CONSTANTS[f] / VSR_DENOMINATORS[f]
VSR_COMPONENTS = ['v', 'v35', 'v24', 'v3', 'v8', 'v15']
VSR_CONSTANTS = {'v': 3.0, 'v35': 3.0, 'v24': 3.0, 'v3': 1.0, 'v8': 3.0, 'v15': 3.0}
VSR_DENOMINATORS = {'v': 'v', 'v35': 'v', 'v24': 'v', 'v3': 'v3', 'v8': 'v8', 'v15': 'v15'}

CSR_COMPONENTS = ['v', 'v35', 'v24']
CSR_DENOMINATORS = {'v': 'v', 'v35': 'v', 'v24': 'v'}
NOV15_COMPONENTS = ['v3', 'v8']
NOV15_CONSTANTS = {'v3': 1.0, 'v8': 3.0}
NOV15_DENOMINATORS = {'v3': 'v3', 'v8': 'v8'}

# The following lays out the SR for Polarised PDFs
TSR_COMPONENTS = ['t3', 't8']
TSR_DENOMINATORS = {'t3': 't3', 't8': 't8'}
# Sum Rules defined as in PDG 2023 (cv, std)
TSR_CONSTANTS = {'t3': 1.2756, 't8': 0.5850}
TSR_CONSTANTS_UNC = {'t3': 0.0013, 't8': 0.025}


[docs]def sample_tsr(sampler, component) -> list: """ Sample the Triplets sum rules according to the PDG uncertainties. Parameters ---------- sampler: np.random.Generator Numpy Generator from which to sample the TSR c: str Component for which to sample from according to TSR_CONSTANTS Returns ------- float: value of the sum rule for this replica for this component sampled according to a normal distribution """ cv = TSR_CONSTANTS[component] std = TSR_CONSTANTS_UNC[component] return sampler.normal(cv, std)
[docs]class MSR_Normalization(MetaLayer): """ Computes the normalisation factors for the sum rules of the PDFs. Parameters ---------- mode: str The type of sum rule to apply, it can be one of ALL, MSR, VSR, TSR, ALLBUTCSR. replica_seeds: List[int] A list of seed-per-replica. Used to sample the polarized sum rules. """ _msr_enabled = False _vsr_enabled = False _tsr_enabled = False _csr_enabled = False def __init__(self, mode: str = "ALL", replica_seeds=None, **kwargs): if mode == True or mode.upper() == "ALL": self._msr_enabled = True self._vsr_enabled = True elif mode.upper() == "MSR": self._msr_enabled = True elif mode.upper() == "VSR": self._vsr_enabled = True elif mode.upper() == "TSR": self._tsr_enabled = True elif mode.upper() == "ALLBUTCSR": self._msr_enabled = True self._csr_enabled = True else: raise ValueError(f"Mode {mode} not accepted for sum rules") if replica_seeds is None: self._replicas = 1 else: self._replicas = len(replica_seeds) indices = [] self.divisor_indices = [] if self._msr_enabled: indices += [IDX[c] for c in MSR_COMPONENTS] self.divisor_indices += [IDX[MSR_DENOMINATORS[c]] for c in MSR_COMPONENTS] if self._vsr_enabled: self.divisor_indices += [IDX[VSR_DENOMINATORS[c]] for c in VSR_COMPONENTS] indices += [IDX[c] for c in VSR_COMPONENTS] self.vsr_factors = op.numpy_to_tensor( [np.repeat(VSR_CONSTANTS[c], self._replicas) for c in VSR_COMPONENTS] ) if self._tsr_enabled: if replica_seeds is None: raise ValueError("To use sum_rules=TSR a list of seeds must be provided") self.divisor_indices += [IDX[TSR_DENOMINATORS[c]] for c in TSR_COMPONENTS] indices += [IDX[c] for c in TSR_COMPONENTS] tsr_factors_per_replica = [] for seed in replica_seeds: sampler = np.random.default_rng(seed) tsr_factors_per_replica.append([sample_tsr(sampler, c) for c in TSR_COMPONENTS]) self.tsr_factors = op.numpy_to_tensor(np.array(tsr_factors_per_replica).T) if self._csr_enabled: # modified vsr for V, V24, V35 indices += [IDX[c] for c in CSR_COMPONENTS] self.divisor_indices += [IDX[CSR_DENOMINATORS[c]] for c in CSR_COMPONENTS] # no V15 vsr self.divisor_indices += [IDX[NOV15_DENOMINATORS[c]] for c in NOV15_COMPONENTS] indices += [IDX[c] for c in NOV15_COMPONENTS] self.vsr_factors = op.numpy_to_tensor( [np.repeat(NOV15_CONSTANTS[c], self._replicas) for c in NOV15_COMPONENTS] ) # Need this extra dimension for the scatter_to_one operation self.indices = [[i] for i in indices] super().__init__(**kwargs)
[docs] def call(self, pdf_integrated, photon_integral): """ Computes the normalization factors for the PDFs: A_g = (1-sigma-photon)/g A_v = A_v24 = A_v35 = 3/V A_v3 = 1/V_3 A_v8 = 3/V_8 A_v15 = 3/V_15 Note that both the input and the output are in the 14-flavours fk-basis Parameters ---------- pdf_integrated: (Tensor(1, replicas, 14)) the integrated PDF photon_integral: (Tensor(1, replicas, 1)) the integrated photon PDF Returns ------- normalization_factor: Tensor(replicas, 1, 14) The normalization factors per flavour. """ # get rid of batch dimension and put replicas last reshape = lambda x: op.transpose(x[0]) y = reshape(pdf_integrated) photon_integral = reshape(photon_integral) numerators = [] if self._msr_enabled: numerators += [ op.batchit(1.0 - y[IDX['sigma']] - photon_integral[0], batch_dimension=0) ] if self._vsr_enabled: numerators += [self.vsr_factors] if self._tsr_enabled: numerators += [self.tsr_factors] if self._csr_enabled: numerators += len(CSR_COMPONENTS) * [ op.batchit(4.0 - 1.0 / 3.0 * y[IDX['v15']], batch_dimension=0) ] numerators += [self.vsr_factors] numerators = op.concatenate(numerators, axis=0) divisors = op.gather(y, self.divisor_indices, axis=0) # Fill in the rest of the flavours with 1 num_flavours = y.shape[0] norm_constants = op.scatter_to_one( numerators / divisors, indices=self.indices, output_shape=(num_flavours, self._replicas) ) return op.batchit(op.transpose(norm_constants), batch_dimension=1)