Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dd2cb06
Introduction of template.py to contain constants and xgrid
evagroenendijk May 23, 2025
51eb28b
Introduction of template.py to contain constants and xgrid
evagroenendijk May 23, 2025
756be45
Removal of need of _template.yaml
evagroenendijk Jun 19, 2025
ed25c7d
Remove test that checks q0 from theory card & _template.py
evagroenendijk Jun 19, 2025
b0350dc
Remove unnecessary stuff from template.py
evagroenendijk Jun 19, 2025
44d784a
Resolve conflicts with master
evagroenendijk Jun 20, 2025
22d9ac2
Forgot to add template.py
evagroenendijk Jun 20, 2025
5f7394b
Remove eko version from template.py
evagroenendijk Apr 14, 2026
7c973d2
changes needed for merge with main
evagroenendijk Apr 23, 2026
f560b91
change ev_op_iterations
evagroenendijk Apr 23, 2026
5b931b6
Adjusted function in cli and changed last thing in evolve.py
evagroenendijk Apr 23, 2026
a2973ab
Merge branch 'remove_template' of github.com:NNPDF/pineko into remove…
evagroenendijk Apr 23, 2026
53b193b
Change ValueError of evolution method
evagroenendijk Apr 23, 2026
d8332f9
Adjust evolve benchmark
evagroenendijk Apr 27, 2026
4c9ee46
Change ValueError into warning
evagroenendijk May 5, 2026
7b06030
Change error into warning and remove unnecessary import
evagroenendijk May 5, 2026
b714df0
Add missing keys to template
evagroenendijk May 18, 2026
8e23b88
fix formatting of scripts and change debug settings to false
evagroenendijk May 18, 2026
3664fa2
Last changes to pass last test, had forgotten some keys in template.py
evagroenendijk May 19, 2026
89304e2
new regression data
evagroenendijk May 19, 2026
4e0fde0
Removed _template from & added extra keys to CLI & evolution method f…
evagroenendijk May 21, 2026
37242b3
Set inversion method to expanded for truncated and exact for iterate-…
evagroenendijk May 21, 2026
7a3167c
Remove time_like and polarized keys from operator card
evagroenendijk May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions benchmarks/bench_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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),
],
Expand Down
13 changes: 4 additions & 9 deletions benchmarks/bench_evolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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"))
Expand Down
Binary file modified benchmarks/regression_data/LHCBWZMU8TEV.npy
Binary file not shown.
1 change: 0 additions & 1 deletion pineko.ci.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion pineko.debug.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 15 additions & 7 deletions src/pineko/cli/opcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pathlib

import rich
import rich_click as click
import yaml

Expand All @@ -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`).
Expand All @@ -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
)
7 changes: 5 additions & 2 deletions src/pineko/cli/theory_.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 1 addition & 4 deletions src/pineko/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
NEEDED_KEYS = [
"operator_cards",
"grids",
"operator_card_template_name",
THEORY_PATH_KEY,
"fktables",
"ekos",
]
NEEDED_FILES = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed entirely?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I did that, but then I thought it would be useful to leave it in case at any point there is another adjustment to the code that needs some files. It's what I thought, but let me know if you think differently

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove unnecessary code and then re-add it if needed given past experiences with having a very big graveyard of code


NEEDED_FILES = ["operator_card_template_name"]
GENERIC_OPTIONS = "general"


Expand Down Expand Up @@ -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:
Expand Down
98 changes: 53 additions & 45 deletions src/pineko/evolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -94,22 +94,31 @@ 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.

Parameters
----------
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
-------
Expand All @@ -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(
Expand Down Expand Up @@ -166,23 +171,29 @@ 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.

Parameters
----------
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
-------
Expand All @@ -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...
Expand All @@ -232,44 +253,32 @@ 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
operators_card["eko_version"] = metadata.version("eko")

# Choose the evolution method according to the theory if the key is included
if "ModEv" in tcard:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a friend of early exits in code, since they safe indents - thus I suggests

if "ModEv" not in tcard:
    raise ValueError("blub")
# no explicit `else` needed nor the associated indentation

same for "IterEv" I would say

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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you maybe add a # TODO there saying something like "we impose a default now, but we may at later point allow the user to choose (e.g. via CLI)"

Copy link
Copy Markdown
Contributor Author

@evagroenendijk evagroenendijk May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's now already possible to determine these from the CLI (when doing "pineko theory opcards ..." and when doing "pineko opcard ... ")

edit: ah no, I'm sorry I was confused and talking about the other settings! I can add the to do

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 (
Expand All @@ -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


Expand Down
Loading
Loading