From be362831d9bdb289e540d51b6feacf8cfe49a6cb Mon Sep 17 00:00:00 2001 From: juacrumar Date: Wed, 24 Apr 2024 14:12:16 +0200 Subject: [PATCH 1/3] add annotations and hints and a default for IterEv --- nnpdf_data/nnpdf_data/theory.py | 96 +++++++++++++------ nnpdf_data/nnpdf_data/utils.py | 4 +- .../src/validphys/tests/test_theorydbutils.py | 25 +++++ 3 files changed, 95 insertions(+), 30 deletions(-) diff --git a/nnpdf_data/nnpdf_data/theory.py b/nnpdf_data/nnpdf_data/theory.py index 8115eda35d..deda488686 100644 --- a/nnpdf_data/nnpdf_data/theory.py +++ b/nnpdf_data/nnpdf_data/theory.py @@ -4,7 +4,11 @@ """ import dataclasses +from functools import lru_cache import logging +from typing import Literal, Optional + +from .utils import parse_yaml_inp DEPRECATED_KEYS = ["MaxNfAs", "SxRes", "SxOrd" "EScaleVar", "Qedref", "global_nx"] @@ -15,14 +19,18 @@ class TheoryCardError(Exception): pass +class TheoryNotFoundInDatabase(FileNotFoundError): + pass + + @dataclasses.dataclass(frozen=True) class TheoryCard: ID: int # ID number of the theory - PTO: int # Perturbative order (0 = LO, 1 = NLO, 2 = NNLO ...) + PTO: Literal[0, 1, 2, 3] # Perturbative order (0 = LO, 1 = NLO, 2 = NNLO ...) FNS: str # Flavor number scheme (e.g. FONLL-C) DAMP: int # Whether a damping function is applied or not for FONLL IC: int # 0 = perturbative charm only , 1 = intrinsic charm allowed - ModEv: str # DGLAP evolution solution method (e.g. EXA or TRN) + ModEv: Literal["EXA", "TRN"] # DGLAP evolution solution method (e.g. EXA or TRN) XIR: float # Renormalization scale over the hard scattering scale ratio XIF: float # Factorization scale over the hard scattering scale ratio NfFF: int # Number of active flavors, only for FFNS or FFN0 schemes @@ -36,7 +44,9 @@ class TheoryCard: mt: float # # [GeV] top mass Qmt: float # [GeV] MSbar mass reference scale of the top ktThr: float # Threshold ratio of the top - CKM: list[float] # CKM matrix elements (running on the columns first, i.e. CKM_11 is CKM[0] and CKM_12 is CKM[1] and so on) + CKM: list[ + float + ] # CKM matrix elements (running on the columns first, i.e. CKM_11 is CKM[0] and CKM_12 is CKM[1] and so on) MZ: float # [GeV] Mass of Z MW: float # [GeV] Mass of W GF: float # Fermi constant @@ -44,41 +54,52 @@ class TheoryCard: TMC: int # Include target mass corrections: 0 = disabled, 1 = leading twist, 2 = higher twist approximated, 3 = higher twist exact MP: float # [GeV] Mass of the proton Comments: str # Comments on the theory - MaxNfPdf: int = 5 # Used by pineko and the photon module to define the thresholds + MaxNfPdf: Optional[int] = 5 # Used by pineko and the photon module to define the thresholds # Fit theory parameters default - nf0: int = 4 # Number of active flavors at the parametrization scale Q0 - Q0: float = 1.65 # [GeV] Parametrization scale - nfref: int = 5 # Number of active flavors at Qref - Qref: float = 91.2 # [GeV] Reference scale for alphas and alphaqed - alphas: float = 0.118 # Value of alpha_s at the scale Qref - alphaqed: float = 0.007496252 # Values of alpha QED at the scale Qref + nf0: Optional[int] = 4 # Number of active flavors at the parametrization scale Q0 + Q0: Optional[float] = 1.65 # [GeV] Parametrization scale + nfref: Optional[int] = 5 # Number of active flavors at Qref + Qref: Optional[float] = 91.2 # [GeV] Reference scale for alphas and alphaqed + alphas: Optional[float] = 0.118 # Value of alpha_s at the scale Qref + alphaqed: Optional[float] = 0.007496252 # Values of alpha QED at the scale Qref # Evolution parameters - HQ: str = "POLE" # Heavy quark mass scheme, POLE for pole masses (default), MSBAR for running masses (used currently only in eko). - IterEv: int = None # iterations for the evolution of the PDF. Defaults to 40 when ModEv = EXA - ModSV: str = None # Scale variations method in EKO (expanded or exponentiated) - DAMPPOWERc: int = None # Power of the damping factor in FONLL for the c - DAMPPOWERb: int = None # Power of the damping factor in FONLL for the b + HQ: Optional[Literal["POLE", "MSBAR"]] = ( + "POLE" # Heavy quark mass scheme, POLE for pole masses (default), MSBAR for running masses (used currently only in eko). + ) + IterEv: Optional[int] = ( + None # iterations for the evolution of the PDF. Defaults to 40 when ModEv = EXA + ) + ModSV: Optional[str] = None # Scale variations method in EKO (expanded or exponentiated) + DAMPPOWERc: Optional[int] = None # Power of the damping factor in FONLL for the c + DAMPPOWERb: Optional[int] = None # Power of the damping factor in FONLL for the b # N3LO parameters - n3lo_ad_variation: list = dataclasses.field( + n3lo_ad_variation: Optional[list] = dataclasses.field( default_factory=lambda: 7 * [0] ) # N3LO anomalous dimension variations - n3lo_cf_variation: int = ( - 0 # N3LO coefficient functions variation: -1 = lower bound, 0 = central, 1 = upper bound + n3lo_cf_variation: Optional[int] = ( + 0 # N3LO coefficient functions variation: -1 = lower bound, 0 = central, 1 = upper bound ) - use_fhmruvv: bool = ( - False # N3LO splitting functions approximation: if True use the FHMRUVV parametrization, otherwise use EKO parametrization. + use_fhmruvv: Optional[bool] = ( + False # N3LO splitting functions approximation: if True use the FHMRUVV parametrization, otherwise use EKO parametrization. ) ###### Keys for compatibility with old NNPDF theories, their values will be dropped immediately after reading to avoid problems ###### they will be set to ``None`` immediately after loading the theory - MaxNfAs: int = None - SxRes: int = None - SxOrd: str = None - EScaleVar: int = None - Qedref: float = None - global_nx: int = None + MaxNfAs: Optional[int] = None + SxRes: Optional[int] = None + SxOrd: Optional[str] = None + EScaleVar: Optional[int] = None + Qedref: Optional[float] = None + global_nx: Optional[int] = None def __post_init__(self): - """Drop deprecated keys and apply some checks""" + self._set_defaults() + self._apply_checks() + + for key in DEPRECATED_KEYS: + object.__setattr__(self, key, None) + + def _apply_checks(self): + """Apply complex checks to the input keys after parsing is complete""" if self.Qedref is not None and self.QED != 0: # Check that nobody is trying to use this with a wrong Qedref! if self.Qedref != self.Qref: @@ -86,8 +107,15 @@ def __post_init__(self): f"Trying to use {self.ID} with {self.Qedref} != {self.Qref}. This is not supported!" ) - for key in DEPRECATED_KEYS: - object.__setattr__(self, key, None) + def _set_defaults(self): + """Function to be called by __post_init__ to set defaults that might depends + on other variables""" + if self.ModEv == "EXA" and self.IterEv is None: + object.__setattr__(self, "IterEv", 60) + if self.ModEv == "TRN" and self.IterEv is not None: + raise TheoryCardError( + f"IterEv should not be given when ModEv=TRN, received {self.IterEv}" + ) def _raise_or_warn(self, msg): """Raise an error for new theories and a warning for old ones""" @@ -97,3 +125,13 @@ def _raise_or_warn(self, msg): def asdict(self): return dataclasses.asdict(self) + + +@lru_cache +def parse_theory_card(theory_card): + """Read the theory card using validobj parsing + Returns the theory as a dictionary + """ + if theory_card.exists(): + return parse_yaml_inp(theory_card, TheoryCard) + raise TheoryNotFoundInDatabase(f"Theory card {theory_card} not found") diff --git a/nnpdf_data/nnpdf_data/utils.py b/nnpdf_data/nnpdf_data/utils.py index 0803ba34e8..33987134bd 100644 --- a/nnpdf_data/nnpdf_data/utils.py +++ b/nnpdf_data/nnpdf_data/utils.py @@ -22,7 +22,9 @@ def parse_yaml_inp(input_yaml, spec): return parse_input(inp, spec) except ValidationError as e: current_exc = e - current_inp = inp + # In order to provide a more complete error information, use round_trip_load + # to read the .yaml file again (insetad of using the CLoader) + current_inp = yaml.round_trip_load(input_yaml.open("r", encoding="utf-8")) error_text_lines = [] while current_exc: if hasattr(current_exc, 'wrong_field'): diff --git a/validphys2/src/validphys/tests/test_theorydbutils.py b/validphys2/src/validphys/tests/test_theorydbutils.py index c812508445..a72d86900d 100644 --- a/validphys2/src/validphys/tests/test_theorydbutils.py +++ b/validphys2/src/validphys/tests/test_theorydbutils.py @@ -1,4 +1,6 @@ import pytest +from ruamel import yaml +from validobj import ValidationError from nnpdf_data.theorydbutils import TheoryNotFoundInDatabase, fetch_all, fetch_theory from validphys.api import API @@ -25,3 +27,26 @@ def test_vp_theoryinfo_tables(): assert all_tables.loc[53, 'Comments'] == 'NNPDF3.1 NNLO central' tb53 = API.theory_info_table(theory_db_id=53) assert tb53.loc['Comments'].iloc[0] == 'NNPDF3.1 NNLO central' + + +def _dump_and_check_error(tdict, tmp, bad_number=999): + """Dump theory dict to a file and load expecting an error""" + tdict["ID"] = bad_number + ofile = tmp / f"{bad_number}.yaml" + yaml.dump(tdict, ofile.open("w")) + with pytest.raises(ValidationError): + fetch_theory(tmp, bad_number) + + +def test_fetch_with_errors(tmp): + """Test some of the errors that theories can raise""" + # Get a good theory and copy it before doing evil to it + th_good = fetch_theory(DBPATH, 700) + # Wrong PTO + bad_pto = dict(th_good) + bad_pto["PTO"] = 7 + _dump_and_check_error(bad_pto, tmp) + # Wrong ModEv + bad_ev = dict(th_good) + bad_ev["ModEv"] = "WRONG" + _dump_and_check_error(bad_ev, tmp) From 490ee74d23a397316879bdcb735f807f34e40a5c Mon Sep 17 00:00:00 2001 From: juacrumar Date: Wed, 24 Apr 2024 14:20:43 +0200 Subject: [PATCH 2/3] utilize the theory iterations by default ; fail inmediately with inconsistent options --- n3fit/src/evolven3fit/eko_utils.py | 56 ++++++++++------------- n3fit/src/n3fit/scripts/evolven3fit.py | 14 ++++-- n3fit/src/n3fit/tests/conftest.py | 13 ++++++ n3fit/src/n3fit/tests/test_evolven3fit.py | 7 ++- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/n3fit/src/evolven3fit/eko_utils.py b/n3fit/src/evolven3fit/eko_utils.py index 7f47d7f5c0..ec32e2ff68 100644 --- a/n3fit/src/evolven3fit/eko_utils.py +++ b/n3fit/src/evolven3fit/eko_utils.py @@ -7,7 +7,6 @@ from eko.io import runcards from eko.matchings import Atlas, nf_default from eko.quantities.heavy_quarks import MatchingScales -from validphys.loader import Loader from . import utils @@ -22,7 +21,6 @@ } EVOLVEN3FIT_CONFIGS_DEFAULTS_EXA = { - "ev_op_iterations": 30, "ev_op_max_order": (1, 0), "evolution_method": "iterate-exact", "inversion_method": "exact", @@ -33,7 +31,7 @@ def construct_eko_cards( - theoryID, + nnpdf_theory, q_fin, q_points, x_grid, @@ -43,13 +41,13 @@ def construct_eko_cards( ): """ Return the theory and operator cards used to construct the eko. - theoryID is the ID of the theory for which we are computing the theory and operator card. + nnpdf_theory is a NNPDF theory card for which we are computing the operator card and eko q_fin is the final point of the q grid while q_points is the number of points of the grid. x_grid is the x grid to be used. op_card_dict and theory_card_dict are optional updates that can be provided respectively to the operator card and to the theory card. """ - theory, thresholds = load_theory(theoryID, theory_card_dict) + theory, thresholds = load_theory(nnpdf_theory, theory_card_dict) # if is eko_photon then mu0 = q_gamma mu0 = theory["Q0"] @@ -62,7 +60,7 @@ def construct_eko_cards( theory["Qedref"] = theory["Qref"] theory["MaxNfAs"] = theory["MaxNfPdf"] - # The Legacy function is able to construct a theory card for eko starting from an NNPDF theory + # The Legacy function is able to construct a theory card for eko starting from a NNPDF theory legacy_class = runcards.Legacy(theory, {}) theory_card = legacy_class.new_theory @@ -111,7 +109,7 @@ def construct_eko_cards( def construct_eko_photon_cards( - theoryID, + nnpdf_theory, q_fin, x_grid, q_gamma, @@ -120,13 +118,13 @@ def construct_eko_photon_cards( ): """ Return the theory and operator cards used to construct the eko_photon. - theoryID is the ID of the theory for which we are computing the theory and operator card. + nnpdf_theory is a NNPDF theory card for which we are computing the operator card and eko q_fin is the final point of the q grid while q_points is the number of points of the grid. x_grid is the x grid to be used. op_card_dict and theory_card_dict are optional updates that can be provided respectively to the operator card and to the theory card. """ - theory, thresholds = load_theory(theoryID, theory_card_dict) + theory, thresholds = load_theory(nnpdf_theory, theory_card_dict) # if is eko_photon then mu0 = q_gamma mu0 = q_gamma @@ -135,7 +133,7 @@ def construct_eko_photon_cards( if "nf0" not in theory: theory["nf0"] = find_nf(mu0, theory, thresholds) - # The Legacy function is able to construct a theory card for eko starting from an NNPDF theory + # The Legacy function is able to construct a theory card for eko starting from a NNPDF theory legacy_class = runcards.Legacy(theory, {}) theory_card = legacy_class.new_theory @@ -152,12 +150,12 @@ def construct_eko_photon_cards( return theory_card, op_card -def load_theory(theoryID, theory_card_dict): +def load_theory(nnpdf_theory, theory_card_dict): """loads and returns the theory dictionary and the thresholds""" if theory_card_dict is None: theory_card_dict = {} # theory_card construction - theory = Loader().check_theoryID(theoryID).get_description() + theory = dict(nnpdf_theory) theory.pop("FNS") theory.update(theory_card_dict) @@ -178,7 +176,9 @@ def load_theory(theoryID, theory_card_dict): def build_opcard(op_card_dict, theory, x_grid, mu0, mugrid): - """builds the opcard""" + """Build the operator card. + The user provided options should be given as part of ``op_card_dict`` + """ if op_card_dict is None: op_card_dict = {} @@ -187,12 +187,17 @@ def build_opcard(op_card_dict, theory, x_grid, mu0, mugrid): op_card.update({"mu0": mu0, "mugrid": mugrid}) op_card["xgrid"] = x_grid - # Specific defaults for evolven3fit evolution - if theory["ModEv"] == "TRN": - op_card["configs"].update(EVOLVEN3FIT_CONFIGS_DEFAULTS_TRN) - if theory["ModEv"] == "EXA": - op_card["configs"].update(EVOLVEN3FIT_CONFIGS_DEFAULTS_EXA) - # User can still change the configs via op_card_dict + + # Specify the evolution options and defaults differently from TRN / EXA + configs = op_card["configs"] + if theory.get("ModEv") == "TRN": + configs.update(EVOLVEN3FIT_CONFIGS_DEFAULTS_TRN) + elif theory.get("ModEv") == "EXA": + # Set the default from the theory card unless it was given in the input + op_card_dict.setdefault("ev_op_iterations", theory.get("IterEv")) + + configs.update(EVOLVEN3FIT_CONFIGS_DEFAULTS_EXA) + configs["ev_op_iterations"] = op_card_dict["ev_op_iterations"] # Note that every entry that is not a dictionary should not be # touched by the user and indeed an user cannot touch them @@ -202,19 +207,7 @@ def build_opcard(op_card_dict, theory, x_grid, mu0, mugrid): elif key in op_card_dict: _logger.warning("Entry %s is not a dictionary and will be ignored", key) - # if no -e was given, take ev_op_iterations from EVOLVEN3FIT_CONFIGS_DEFAULTS_{TRN,EXA} - if op_card['configs']['ev_op_iterations'] is None: - if theory["ModEv"] == "TRN": - op_card['configs']['ev_op_iterations'] = EVOLVEN3FIT_CONFIGS_DEFAULTS_TRN[ - "ev_op_iterations" - ] - if theory["ModEv"] == "EXA": - op_card['configs']['ev_op_iterations'] = EVOLVEN3FIT_CONFIGS_DEFAULTS_EXA[ - "ev_op_iterations" - ] - op_card = runcards.OperatorCard.from_dict(op_card) - return op_card @@ -228,4 +221,3 @@ def find_nf(mu, theory, thresholds): nf = 5 else: nf = 6 - return nf diff --git a/n3fit/src/n3fit/scripts/evolven3fit.py b/n3fit/src/n3fit/scripts/evolven3fit.py index d0509bee5e..c1d6c0a143 100644 --- a/n3fit/src/n3fit/scripts/evolven3fit.py +++ b/n3fit/src/n3fit/scripts/evolven3fit.py @@ -1,6 +1,7 @@ """ This module contains the CLI for evolven3fit """ + from argparse import ArgumentParser import logging import pathlib @@ -11,6 +12,7 @@ from eko.runner.managed import solve from n3fit.io.writer import XGRID +from validphys.loader import FallbackLoader _logger = logging.getLogger(__name__) @@ -117,7 +119,7 @@ def main(): "--ev-op-iterations", type=int, default=None, - help="ev_op_iterations for the EXA theory", + help="ev_op_iterations for the EXA theory. Overrides the settings given in the theory card.", ) parser.add_argument( "--use-fhmruvv", @@ -151,6 +153,12 @@ def main(): args.force, ) else: + # If we are in the business of producing an eko, do some checks before starting: + # 1. load the nnpdf theory early to check for inconsistent options and theory problems + nnpdf_theory = FallbackLoader().check_theoryID(args.theoryID).get_description() + if nnpdf_theory.get("ModEv") == "TRN" and args.ev_op_iterations is not None: + raise ValueError("ev_op_iterations is not accepted with ModEv=TRN solution") + stdout_log = logging.StreamHandler(sys.stdout) stdout_log.setLevel(evolve.LOGGING_SETTINGS["level"]) stdout_log.setFormatter(evolve.LOGGING_SETTINGS["formatter"]) @@ -173,7 +181,7 @@ def main(): x_grid = np.geomspace(args.x_grid_ini, 1.0, args.x_grid_points) if args.actions == "produce_eko": tcard, opcard = eko_utils.construct_eko_cards( - args.theoryID, + nnpdf_theory, args.q_fin, args.q_points, x_grid, @@ -183,7 +191,7 @@ def main(): ) elif args.actions == "produce_eko_photon": tcard, opcard = eko_utils.construct_eko_photon_cards( - args.theoryID, args.q_fin, x_grid, args.q_gamma, op_card_info, theory_card_info + nnpdf_theory, args.q_fin, x_grid, args.q_gamma, op_card_info, theory_card_info ) solve(tcard, opcard, args.dump) diff --git a/n3fit/src/n3fit/tests/conftest.py b/n3fit/src/n3fit/tests/conftest.py index c2acb69852..91489169b1 100644 --- a/n3fit/src/n3fit/tests/conftest.py +++ b/n3fit/src/n3fit/tests/conftest.py @@ -1,9 +1,22 @@ """ Add markers for pytest """ + import sys + import pytest +from validphys.loader import FallbackLoader + +THEORYID = 399 + + +@pytest.fixture(scope='module') +def nnpdf_theory_card(): + """Return a theory card already loaded as a dictionary""" + th = FallbackLoader().check_theoryID(THEORYID) + return th.get_description() + def pytest_runtest_setup(item): ALL = {"darwin", "linux"} diff --git a/n3fit/src/n3fit/tests/test_evolven3fit.py b/n3fit/src/n3fit/tests/test_evolven3fit.py index 955fec3071..52799829f6 100644 --- a/n3fit/src/n3fit/tests/test_evolven3fit.py +++ b/n3fit/src/n3fit/tests/test_evolven3fit.py @@ -118,16 +118,15 @@ def test_utils(): assert ID == 162 -def test_eko_utils(tmp_path): +def test_eko_utils(tmp_path, nnpdf_theory_card): # Testing construct eko cards - theoryID = 162 q_fin = 100 q_points = 5 x_grid = [1.0e-3, 0.1, 1.0] pto = 2 comments = "Test" t_card, op_card = eko_utils.construct_eko_cards( - theoryID, + nnpdf_theory_card, q_fin, q_points, x_grid, @@ -140,7 +139,7 @@ def test_eko_utils(tmp_path): t_card_dict["order"][0] == pto + 1 ) # This is due to a different convention in eko orders due to QED np.testing.assert_allclose(op_card_dict["xgrid"], x_grid) - # In theory 162 the charm threshold is at 1.51 + # In theory 399 the charm threshold is at 1.51 # and we should find two entries, one for nf=3 and another one for nf=4 np.testing.assert_allclose(op_card_dict["mugrid"][0], (1.51, 3)) np.testing.assert_allclose(op_card_dict["mugrid"][1], (1.51, 4)) From 1d894af1412bb0b3c35be09047db60ff194822fe Mon Sep 17 00:00:00 2001 From: juacrumar Date: Thu, 25 Apr 2024 08:51:48 +0200 Subject: [PATCH 3/3] add a default-depending-on-Q0 for nf0 --- n3fit/src/evolven3fit/eko_utils.py | 26 ++------- nnpdf_data/nnpdf_data/theory.py | 54 +++++++++++-------- .../nnpdf_data/theory_cards/41000000.yaml | 2 + 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/n3fit/src/evolven3fit/eko_utils.py b/n3fit/src/evolven3fit/eko_utils.py index ec32e2ff68..955ecacd9a 100644 --- a/n3fit/src/evolven3fit/eko_utils.py +++ b/n3fit/src/evolven3fit/eko_utils.py @@ -52,10 +52,6 @@ def construct_eko_cards( # if is eko_photon then mu0 = q_gamma mu0 = theory["Q0"] - # Set nf_0 according to the fitting scale unless set explicitly - if "nf0" not in theory: - theory["nf0"] = find_nf(mu0, theory, thresholds) - # eko needs a value for Qedref and for max nf alphas theory["Qedref"] = theory["Qref"] theory["MaxNfAs"] = theory["MaxNfPdf"] @@ -124,22 +120,18 @@ def construct_eko_photon_cards( op_card_dict and theory_card_dict are optional updates that can be provided respectively to the operator card and to the theory card. """ - theory, thresholds = load_theory(nnpdf_theory, theory_card_dict) + theory, _ = load_theory(nnpdf_theory, theory_card_dict) # if is eko_photon then mu0 = q_gamma mu0 = q_gamma - # Set nf_0 according to mu0 unless set explicitly - if "nf0" not in theory: - theory["nf0"] = find_nf(mu0, theory, thresholds) - # The Legacy function is able to construct a theory card for eko starting from a NNPDF theory legacy_class = runcards.Legacy(theory, {}) theory_card = legacy_class.new_theory + # The photon needs to be evolved down to Q0 q_fin = theory["Q0"] - - nf_fin = find_nf(q_fin, theory, thresholds) + nf_fin = theory["nf0"] # construct mugrid mugrid = [(q_fin, nf_fin)] @@ -209,15 +201,3 @@ def build_opcard(op_card_dict, theory, x_grid, mu0, mugrid): op_card = runcards.OperatorCard.from_dict(op_card) return op_card - - -def find_nf(mu, theory, thresholds): - """compute nf for a given mu""" - if mu < theory["mc"] * thresholds["c"]: - nf = 3 - elif mu < theory["mb"] * thresholds["b"]: - nf = 4 - elif mu < theory["mt"] * thresholds["t"]: - nf = 5 - else: - nf = 6 diff --git a/nnpdf_data/nnpdf_data/theory.py b/nnpdf_data/nnpdf_data/theory.py index deda488686..a17a3d7128 100644 --- a/nnpdf_data/nnpdf_data/theory.py +++ b/nnpdf_data/nnpdf_data/theory.py @@ -35,6 +35,7 @@ class TheoryCard: XIF: float # Factorization scale over the hard scattering scale ratio NfFF: int # Number of active flavors, only for FFNS or FFN0 schemes QED: int # Max order of alpha_qed in the evolution + Q0: float # [GeV] Parametrization scale for the fit (and the photon) mc: float # [GeV] charm mass Qmc: float # [GeV] MSbar mass reference scale of the charm kcThr: float # Threshold ratio of the charm @@ -44,9 +45,8 @@ class TheoryCard: mt: float # # [GeV] top mass Qmt: float # [GeV] MSbar mass reference scale of the top ktThr: float # Threshold ratio of the top - CKM: list[ - float - ] # CKM matrix elements (running on the columns first, i.e. CKM_11 is CKM[0] and CKM_12 is CKM[1] and so on) + # CKM matrix elements (running on the columns first, i.e. CKM_11 is CKM[0] and CKM_12 is CKM[1] and so on) + CKM: list[float] MZ: float # [GeV] Mass of Z MW: float # [GeV] Mass of W GF: float # Fermi constant @@ -55,33 +55,28 @@ class TheoryCard: MP: float # [GeV] Mass of the proton Comments: str # Comments on the theory MaxNfPdf: Optional[int] = 5 # Used by pineko and the photon module to define the thresholds - # Fit theory parameters default - nf0: Optional[int] = 4 # Number of active flavors at the parametrization scale Q0 - Q0: Optional[float] = 1.65 # [GeV] Parametrization scale + ## Fit theory parameters default + # Number of active flavors at the parametrization scale Q0, its default depends on Q0 + nf0: Optional[int] = None nfref: Optional[int] = 5 # Number of active flavors at Qref Qref: Optional[float] = 91.2 # [GeV] Reference scale for alphas and alphaqed alphas: Optional[float] = 0.118 # Value of alpha_s at the scale Qref alphaqed: Optional[float] = 0.007496252 # Values of alpha QED at the scale Qref - # Evolution parameters - HQ: Optional[Literal["POLE", "MSBAR"]] = ( - "POLE" # Heavy quark mass scheme, POLE for pole masses (default), MSBAR for running masses (used currently only in eko). - ) - IterEv: Optional[int] = ( - None # iterations for the evolution of the PDF. Defaults to 40 when ModEv = EXA - ) + ## Evolution parameters + # Heavy quark mass scheme, POLE for pole masses (default), MSBAR for running masses (used currently only in eko). + HQ: Optional[Literal["POLE", "MSBAR"]] = "POLE" + # iterations for the evolution of the PDF. Defaults to 60 when ModEv = EXA + IterEv: Optional[int] = None ModSV: Optional[str] = None # Scale variations method in EKO (expanded or exponentiated) DAMPPOWERc: Optional[int] = None # Power of the damping factor in FONLL for the c DAMPPOWERb: Optional[int] = None # Power of the damping factor in FONLL for the b - # N3LO parameters - n3lo_ad_variation: Optional[list] = dataclasses.field( - default_factory=lambda: 7 * [0] - ) # N3LO anomalous dimension variations - n3lo_cf_variation: Optional[int] = ( - 0 # N3LO coefficient functions variation: -1 = lower bound, 0 = central, 1 = upper bound - ) - use_fhmruvv: Optional[bool] = ( - False # N3LO splitting functions approximation: if True use the FHMRUVV parametrization, otherwise use EKO parametrization. - ) + ## N3LO parameters + # N3LO anomalous dimension variations + n3lo_ad_variation: Optional[list] = dataclasses.field(default_factory=lambda: 7 * [0]) + # N3LO coefficient functions variation: -1 = lower bound, 0 = central, 1 = upper bound + n3lo_cf_variation: Optional[int] = 0 + # N3LO splitting functions approximation: if True use the FHMRUVV parametrization, otherwise use EKO parametrization. + use_fhmruvv: Optional[bool] = False ###### Keys for compatibility with old NNPDF theories, their values will be dropped immediately after reading to avoid problems ###### they will be set to ``None`` immediately after loading the theory MaxNfAs: Optional[int] = None @@ -107,6 +102,17 @@ def _apply_checks(self): f"Trying to use {self.ID} with {self.Qedref} != {self.Qref}. This is not supported!" ) + def find_nf(self, mu): + """Given a value of q, find the corresponding number of flavours + for this theory.""" + if mu < self.mc * self.kcThr: + return 3 + elif mu < self.mb * self.kbThr: + return 4 + elif mu < self.mt * self.ktThr: + return 5 + return 6 + def _set_defaults(self): """Function to be called by __post_init__ to set defaults that might depends on other variables""" @@ -116,6 +122,8 @@ def _set_defaults(self): raise TheoryCardError( f"IterEv should not be given when ModEv=TRN, received {self.IterEv}" ) + if self.nf0 is None: + object.__setattr__(self, "nf0", self.find_nf(self.Q0)) def _raise_or_warn(self, msg): """Raise an error for new theories and a warning for old ones""" diff --git a/nnpdf_data/nnpdf_data/theory_cards/41000000.yaml b/nnpdf_data/nnpdf_data/theory_cards/41000000.yaml index a22f920575..83e0fbb12f 100644 --- a/nnpdf_data/nnpdf_data/theory_cards/41000000.yaml +++ b/nnpdf_data/nnpdf_data/theory_cards/41000000.yaml @@ -4,7 +4,9 @@ PTO: 2 FNS: FONLL-C DAMP: 0 IC: 1 +Q0: 1.65 ModEv: EXA +IterEv: 60 XIR: 1.0 XIF: 1.0 NfFF: 5