diff --git a/benchmarks/bench_cli.py b/benchmarks/bench_cli.py index 5cf195a8..0b24ec25 100644 --- a/benchmarks/bench_cli.py +++ b/benchmarks/bench_cli.py @@ -50,9 +50,6 @@ def benchmark_opcard_cli(tmp_path, test_files): grid_path = pathlib.Path( test_files / "data/grids/400/HERA_NC_225GEV_EP_SIGMARED.pineappl.lz4" ) - default_card_path = pathlib.Path( - test_files / "data/operator_cards/400/_template.yaml" - ) thcard_path = pathlib.Path(test_files / "data" / "theory_cards" / "400.yaml") target_path = pathlib.Path(tmp_path / "test_ope_card.yaml") runner = CliRunner() @@ -61,7 +58,6 @@ def benchmark_opcard_cli(tmp_path, test_files): [ "opcard", str(grid_path), - str(default_card_path), str(thcard_path), str(target_path), ], diff --git a/benchmarks/bench_evolve.py b/benchmarks/bench_evolve.py index 7da92ed8..fd061087 100644 --- a/benchmarks/bench_evolve.py +++ b/benchmarks/bench_evolve.py @@ -25,7 +25,6 @@ def benchmark_write_operator_card_from_file_num_fonll( / "400" / "HERA_NC_225GEV_EP_SIGMARED.pineappl.lz4" ) - default_path = test_files / "data" / "operator_cards" / "400" / "_template.yaml" num_opcard = 7 if is_mixed else 5 targets_path_list = [ tmp_path / f"test_opcard_{num}.yaml" for num in range(num_opcard) @@ -34,7 +33,7 @@ def benchmark_write_operator_card_from_file_num_fonll( with open(tcard_path, encoding="utf-8") as f: tcard = yaml.safe_load(f) _x_grid, _q2_grid = pineko.evolve.write_operator_card_from_file( - pine_path, default_path, target_path, tcard + pine_path, target_path, tcard ) # Check if the opcards are ok for opcard_path, cfg in zip( @@ -49,11 +48,10 @@ def benchmark_write_operator_card_from_file_num_fonll( def benchmark_write_operator_card_from_file(tmp_path, test_files, test_configs): pine_path = test_files / "data/grids/400/HERA_NC_225GEV_EP_SIGMARED.pineappl.lz4" - default_path = test_files / "data/operator_cards/400/_template.yaml" target_path = pathlib.Path(tmp_path / "test_operator.yaml") tcard = pineko.theory_card.load(400) x_grid, _q2_grid = pineko.evolve.write_operator_card_from_file( - pine_path, default_path, target_path, tcard + pine_path, target_path, tcard ) # Load the operator card @@ -64,22 +62,19 @@ def benchmark_write_operator_card_from_file(tmp_path, test_files, test_configs): wrong_pine_path = test_files / "data/grids/208/HERA_CC_318GEV_EM_wrong.pineappl.lz4" with pytest.raises(FileNotFoundError): _ = pineko.evolve.write_operator_card_from_file( - wrong_pine_path, default_path, target_path, 1.0 + wrong_pine_path, target_path, 1.0 ) def benchmark_dglap(tmp_path, test_files, test_configs): pine_path = test_files / "data/grids/400/HERA_NC_225GEV_EP_SIGMARED.pineappl.lz4" - default_path = test_files / "data/operator_cards/400/_template.yaml" target_path = pathlib.Path(tmp_path / "test_operator.yaml") theory_id = 400 tcard = pineko.theory_card.load(theory_id) # In order to check if the operator card is enough for eko, let's compute the eko - pineko.evolve.write_operator_card_from_file( - pine_path, default_path, target_path, tcard - ) + pineko.evolve.write_operator_card_from_file(pine_path, target_path, tcard) # Load the opcard myopcard = yaml.safe_load(target_path.read_text(encoding="utf-8")) diff --git a/benchmarks/regression_data/LHCBWZMU8TEV.npy b/benchmarks/regression_data/LHCBWZMU8TEV.npy index f6824c20..06bb9ca7 100644 Binary files a/benchmarks/regression_data/LHCBWZMU8TEV.npy and b/benchmarks/regression_data/LHCBWZMU8TEV.npy differ diff --git a/pineko.ci.toml b/pineko.ci.toml index 0a202214..b0ff54c1 100644 --- a/pineko.ci.toml +++ b/pineko.ci.toml @@ -4,7 +4,6 @@ nnpdf=true [paths] # inputs grids = "./theory_productions/data/grids" -operator_card_template_name = "_template.ci.yaml" # outputs operator_cards = "./theory_productions/operator_cards" ekos = "./theory_productions/data/ekos" diff --git a/pineko.debug.toml b/pineko.debug.toml index e9adec0f..5df63423 100644 --- a/pineko.debug.toml +++ b/pineko.debug.toml @@ -3,7 +3,6 @@ ymldb = "data/ymldb" grids = "data/grids" theory_cards = "data/theory_cards" -operator_card_template_name = "_template.yaml" # outputs operator_cards = "data/operator_cards" ekos = "data/ekos" diff --git a/src/pineko/cli/opcard.py b/src/pineko/cli/opcard.py index 0f175a01..10c5859d 100644 --- a/src/pineko/cli/opcard.py +++ b/src/pineko/cli/opcard.py @@ -2,6 +2,7 @@ import pathlib +import rich import rich_click as click import yaml @@ -11,16 +12,23 @@ @command.command("opcard") @click.argument("pineappl-path", metavar="PINEAPPL", type=click.Path(exists=True)) -@click.argument( - "default-card-path", metavar="DEFAULT_CARD", type=click.Path(exists=True) -) @click.argument("thcard-path", metavar="THCARD", type=click.Path()) @click.argument("opcard-path", metavar="OPCARD", type=click.Path()) -def subcommand(pineappl_path, default_card_path, thcard_path, opcard_path): +@click.option("--ipd", default=4, show_default=True, help="interpolation polynomial degree") +@click.option("--iil", default=True, show_default=True, help="interpolation is log") +@click.option("--int-cores", default=1, show_default=True, help="number of integration cores") +def subcommand( + pineappl_path, + thcard_path, + opcard_path, + ipd, + iil, + int_cores, +): """Write EKO card for PineAPPL grid. - Writes a copy of the default card from DEFAULT_CARD to OPCARD - with the adjusted x grid and Q2 grid read from PINEAPPL. + Writes an operator card OPCARD from the information in + opcard_template.py and the theory card. A THCARD is required, since some of the EKO's OPCARD information come from the NNPDF theory entries (e.g. :math:`Q0`). @@ -29,5 +37,5 @@ def subcommand(pineappl_path, default_card_path, thcard_path, opcard_path): tcard = yaml.safe_load(pathlib.Path(thcard_path).read_text(encoding="utf-8")) opcard_path = pathlib.Path(opcard_path) _x_grid, q2_grid = evolve.write_operator_card_from_file( - pineappl_path, default_card_path, opcard_path, tcard + pineappl_path, opcard_path, tcard, ipd, iil, int_cores ) diff --git a/src/pineko/cli/theory_.py b/src/pineko/cli/theory_.py index 1e1f8696..47817e80 100644 --- a/src/pineko/cli/theory_.py +++ b/src/pineko/cli/theory_.py @@ -29,9 +29,12 @@ def inherit_grids(source_theory_id, target_theory_id, datasets, overwrite): @click.argument("theory_id", type=click.INT) @click.argument("datasets", type=click.STRING, nargs=-1) @click.option("--overwrite", is_flag=True, help="Allow files to be overwritten") -def opcards(theory_id, datasets, overwrite): +@click.option("--ipd", default=4, show_default=True, help="interpolation polynomial degree") +@click.option("--iil", default=True, show_default=True, help="interpolation is log") +@click.option("--int-cores", default=1, show_default=True, help="number of integration cores") +def opcards(theory_id, datasets, overwrite, ipd, iil, int_cores): """Write EKO card for all FK tables in all datasets.""" - theory.TheoryBuilder(theory_id, datasets, overwrite=overwrite).opcards() + theory.TheoryBuilder(theory_id, datasets, overwrite=overwrite).opcards(ipd, iil, int_cores) @theory_.command() diff --git a/src/pineko/configs.py b/src/pineko/configs.py index 6afbb25f..bcddeff8 100644 --- a/src/pineko/configs.py +++ b/src/pineko/configs.py @@ -16,13 +16,12 @@ NEEDED_KEYS = [ "operator_cards", "grids", - "operator_card_template_name", THEORY_PATH_KEY, "fktables", "ekos", ] +NEEDED_FILES = [] -NEEDED_FILES = ["operator_card_template_name"] GENERIC_OPTIONS = "general" @@ -87,8 +86,6 @@ def enhance_paths(configs_): for key in required_keys: if key not in configs_["paths"]: raise ValueError(f"Configuration is missing a 'paths.{key}' key") - if key in NEEDED_FILES: - continue if pathlib.Path(configs_["paths"][key]).anchor == "": configs_["paths"][key] = configs_["paths"]["root"] / configs_["paths"][key] else: diff --git a/src/pineko/evolve.py b/src/pineko/evolve.py index 9ae02ba2..1558878f 100644 --- a/src/pineko/evolve.py +++ b/src/pineko/evolve.py @@ -22,7 +22,7 @@ from eko.matchings import Atlas, nf_default from eko.quantities import heavy_quarks -from . import check, comparator, version +from . import check, comparator, opcard_template, version logger = logging.getLogger(__name__) @@ -94,9 +94,11 @@ def get_convolution_suffix(convolution: pineappl.convolutions.Conv) -> str: def write_operator_card_from_file( pineappl_path: os.PathLike, - default_card_path: os.PathLike, card_path: os.PathLike, tcard: dict, + ipd=4, + iil=True, + int_cores=1, ): """Generate operator card for a grid. @@ -104,12 +106,19 @@ def write_operator_card_from_file( ---------- pineappl_path : str or os.PathLike path to grid to evolve - default_card : str or os.PathLike - base operator card card_path : str or os.PathLike target path tcard: dict theory card for the run + ipd: + interpolation polynomial degree, taken from cli. + Set to default value + iil: + interpolation is log, taken from cli. + Set to default value + int_cores: + number of integration cores, taken + from cli. Set to default value Returns ------- @@ -124,11 +133,7 @@ def write_operator_card_from_file( raise FileNotFoundError(pineappl_path) pineappl_grid = pineappl.grid.Grid.read(pineappl_path) pineappl_grid.optimize() - default_card = yaml.safe_load( - pathlib.Path(default_card_path).read_text(encoding="utf-8") - ) - - return write_operator_card(pineappl_grid, default_card, card_path, tcard) + return write_operator_card(pineappl_grid, card_path, tcard, ipd, iil, int_cores) def dump_card( @@ -166,9 +171,11 @@ def dump_card( def write_operator_card( pineappl_grid: pineappl.grid.Grid, - default_card: dict, card_path: Union[str, os.PathLike], tcard: dict, + ipd, + iil, + int_cores ): """Generate operator card for this grid. @@ -176,13 +183,17 @@ def write_operator_card( ---------- pineappl_grid : pineappl.grid.Grid grid to evolve - default_card : dict - base operator card card_path : str or os.PathLike target path tcard: dict theory card for the run, since some information in EKO is now required in operator card, but before was in the theory card + ipd: + interpolation polynomial degree, taken from cli + iil: + interpolation is log, taken from cli + int_cores: + number of integration cores, taken from cli Returns ------- @@ -204,18 +215,28 @@ def write_operator_card( # ... to get the x and muF grids for the eko evol_info = pineappl_grid.evolve_info(order_mask) muf2_grid = evol_info.fac1 - operators_card = copy.deepcopy(default_card) + operators_card = {} + operators_card["configs"] = {} sv_method = sv_scheme(tcard) xif = 1.0 if sv_method is not None else tcard["XIF"] # update scale variation method - operators_card["configs"]["scvar_method"] = sv_method - + operators_card["configs"]["scvar_method"] = sv_scheme(tcard) + operators_card["configs"]["ev_op_max_order"] = opcard_template.CONSTANTS["configs"]["ev_op_max_order"] + operators_card["debug"] = opcard_template.CONSTANTS["debug"] operators_card["init"] = (tcard["Q0"], tcard["nf0"]) - if default_card.get("init") is not None and default_card["init"] != [ + # setting the parameters from the cli + operators_card["configs"]["interpolation_polynomial_degree"] = ipd + operators_card["configs"]["interpolation_is_log"] = iil + operators_card["configs"]["n_integration_cores"] = int_cores + if opcard_template.CONSTANTS["init"] is not None and opcard_template.CONSTANTS["init"] != ( tcard["Q0"], tcard["nf0"], - ]: - raise ValueError("Template declares a value of Q0, nf0 different from theory") + ): + raise logger.warning( + f"Warning! Q0 and nf0 from your theory are different " + f"than default settings ({tcard['Q0']}, {tcard['nf0']} vs " + f"{opcard_template.CONSTANTS['init']}). Check if this is really what you want!" + ) q2_grid = (xif * xif * muf2_grid).tolist() # If we are producing nFONLL FKs we need to look to NfFF... @@ -232,6 +253,8 @@ def write_operator_card( x_grid = np.append(x_grid, 1.0) operators_card["configs"]["interpolation_polynomial_degree"] = 1 operators_card["xgrid"] = x_grid.tolist() + else: + operators_card["xgrid"] = opcard_template.xgrid # Add the version of eko and pineko to the operator card # using importlib.metadata.version to get the correct tag in editable mode @@ -239,37 +262,23 @@ def write_operator_card( # Choose the evolution method according to the theory if the key is included if "ModEv" in tcard: - opconf = operators_card["configs"] if tcard["ModEv"] == "TRN": - opconf["evolution_method"] = "truncated" - opconf["ev_op_iterations"] = 1 + operators_card["configs"]["evolution_method"] = "truncated" + operators_card["configs"]["ev_op_iterations"] = 1 + operators_card["configs"]["inversion_method"] = "expanded" elif tcard["ModEv"] == "EXA": - opconf["evolution_method"] = "iterate-exact" + operators_card["configs"]["evolution_method"] = "iterate-exact" + operators_card["configs"]["inversion_method"] = "exact" if "IterEv" in tcard: - opconf["ev_op_iterations"] = tcard["IterEv"] - elif "ev_op_iterations" not in default_card["configs"]: + operators_card["configs"]["ev_op_iterations"] = tcard["IterEv"] + else: raise ValueError( - "EXA used but IterEv not found in the theory card and not ev_op_iterations set in the template" + "EXA used but IterEv not found in the theory card" ) - - # If the evolution method is defined in the template and it is different, fail - template_method = default_card["configs"].get("evolution_method") - if ( - template_method is not None - and template_method != opconf["evolution_method"] - ): - raise ValueError( - f"The template and the theory have different evolution method ({template_method} vs {opconf['key']})" - ) - - # If the change is on the number of iterations, take the template value but warn the user - template_iter = default_card["configs"].get("ev_op_iterations") - if template_iter is not None and int(template_iter) != int( - opconf["ev_op_iterations"] - ): - raise ValueError( - f"The number of iteration in the theory and template is different, ({template_iter} vs {opconf['ev_op_iterations']})" - ) + else: + raise ValueError( + "Evolution method not set in theory card" + ) # Some safety checks if ( @@ -286,7 +295,6 @@ def write_operator_card( for conv in convolutions: dump_card(card_path, operators_card, conv) - return operators_card["xgrid"], q2_grid diff --git a/src/pineko/opcard_template.py b/src/pineko/opcard_template.py new file mode 100644 index 00000000..1a27925e --- /dev/null +++ b/src/pineko/opcard_template.py @@ -0,0 +1,67 @@ +"""Default settings and interpolation xgrid (previously in _template.yaml).""" + +# Define the interpolation x-grid as previously defined as 'xgrid' in _template.yaml +xgrid = [ + 1.9999999999999954e-07, + 3.034304765867952e-07, + 4.6035014748963906e-07, + 6.984208530700364e-07, + 1.0596094959101024e-06, + 1.607585498470808e-06, + 2.438943292891682e-06, + 3.7002272069854957e-06, + 5.613757716930151e-06, + 8.516806677573355e-06, + 1.292101569074731e-05, + 1.9602505002391748e-05, + 2.97384953722449e-05, + 4.511438394964044e-05, + 6.843744918967896e-05, + 0.00010381172986576898, + 0.00015745605600841445, + 0.00023878782918561914, + 0.00036205449638139736, + 0.0005487795323670796, + 0.0008314068836488144, + 0.0012586797144272762, + 0.0019034634022867384, + 0.0028738675812817515, + 0.004328500638820811, + 0.006496206194633799, + 0.009699159574043398, + 0.014375068581090129, + 0.02108918668378717, + 0.030521584007828916, + 0.04341491741702269, + 0.060480028754447364, + 0.08228122126204893, + 0.10914375746330703, + 0.14112080644440345, + 0.17802566042569432, + 0.2195041265003886, + 0.2651137041582823, + 0.31438740076927585, + 0.3668753186482242, + 0.4221667753589648, + 0.4798989029610255, + 0.5397572337880445, + 0.601472197967335, + 0.6648139482473823, + 0.7295868442414312, + 0.7956242522922756, + 0.8627839323906108, + 0.9309440808717544, + 1, +] + +CONSTANTS = { + "configs": { + "scvar_method": "null", + "ev_op_max_order": [10, 0], + }, + "init": (1.65, 4), + "debug": { + "skip_non_singlet": False, + "skip_singlet": False, + }, +} diff --git a/src/pineko/scaffold.py b/src/pineko/scaffold.py index d9539301..fa364a68 100644 --- a/src/pineko/scaffold.py +++ b/src/pineko/scaffold.py @@ -2,9 +2,11 @@ import dataclasses import pathlib +import logging from .configs import NEEDED_FILES, NEEDED_KEYS +logger = logging.getLogger(__name__) @dataclasses.dataclass class CheckResult: @@ -45,6 +47,10 @@ def set_up_project(configs): path[log_path].mkdir(parents=True, exist_ok=True) else: raise TypeError(f"Not recognized entry {log_path} in configs") + elif path == "_template.yaml": + logger.warning(f"{path} is no longer a supported method for setting up " + "your operator card, remove it from your configuration" + ) else: raise TypeError(f"Not recognized entry {path} in configs") diff --git a/src/pineko/theory.py b/src/pineko/theory.py index 4a8e3e36..7614b288 100644 --- a/src/pineko/theory.py +++ b/src/pineko/theory.py @@ -287,7 +287,7 @@ def iterate(self, f, **kwargs): f(name, grid, **kwargs) rich.print() - def opcard(self, name, grid, tcard): + def opcard(self, name, grid, tcard, ipd=4, iil=True, int_cores=1): """Write a single operator card. Parameters @@ -309,17 +309,18 @@ def opcard(self, name, grid, tcard): return _x_grid, q2_grid = evolve.write_operator_card_from_file( grid, - self.operator_cards_path - / configs.configs["paths"]["operator_card_template_name"], opcard_path, tcard, + ipd, + iil, + int_cores, ) - def opcards(self): + def opcards(self, ipd=4, iil=True, int_cores=1): """Write operator cards.""" tcard = theory_card.load(self.theory_id) self.operator_cards_path.mkdir(exist_ok=True) - self.iterate(self.opcard, tcard=tcard) + self.iterate(self.opcard, tcard=tcard, ipd=ipd, iil=iil, int_cores=int_cores) def load_operator_card(self, name): """Read current operator card. diff --git a/tests/conftest.py b/tests/conftest.py index b39879fa..53b2cf9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,7 +34,6 @@ def fake_configs(tmp_path): "ymldb": tmp_path / "data" / "ymldb", "operator_cards": tmp_path / "data" / "operator_cards", "grids": tmp_path / "data" / "grids", - "operator_card_template_name": "_template.yaml", "theory_cards": tmp_path / "data" / "theory_cards", "fktables": tmp_path / "data" / "fktables", "ekos": tmp_path / "data" / "ekos", diff --git a/tests/test_evolve.py b/tests/test_evolve.py index 477e7731..7d6b10c4 100644 --- a/tests/test_evolve.py +++ b/tests/test_evolve.py @@ -50,28 +50,3 @@ def evolve_info(self, _): @property def metadata(self): return {"convolution_particle_1": 2212, "convolution_particle_2": 11} - - -def test_write_operator_card_q0(tmp_path): - """Checks https://github.com/NNPDF/pineko/issues/146""" - p = tmp_path / "q0.yaml" - g = FakePine() - t = copy.deepcopy(default_card) - o = copy.deepcopy(example.raw_operator()) - # 1. Same Q0 and init, all ok - t["Q0"] = 5.0 - o["init"] = [5.0, 4] - _xs, _mu2s = pineko.evolve.write_operator_card(g, o, p, t) - with open(p, encoding="utf8") as f: - oo = yaml.safe_load(f) - np.testing.assert_allclose(oo["init"][0], t["Q0"]) - # 2. Q0 only in theory, all ok - o.pop("init") - _xs, _mu2s = pineko.evolve.write_operator_card(g, o, p, t) - with open(p, encoding="utf8") as f: - oo = yaml.safe_load(f) - np.testing.assert_allclose(oo["init"][0], t["Q0"]) - # 3. op is different, raises error - o["init"] = [11.0, 3] - with pytest.raises(ValueError): - _xs, _mu2s = pineko.evolve.write_operator_card(g, o, p, t) diff --git a/tests/test_scaffold.py b/tests/test_scaffold.py index 795b85fe..50994451 100644 --- a/tests/test_scaffold.py +++ b/tests/test_scaffold.py @@ -19,7 +19,6 @@ def test_check_folder(fake_configs_incomplete, fake_configs): assert [ "operator_cards", "grids", - "operator_card_template_name", "theory_cards", "fktables", "ekos",