Skip to content

Commit 81c0da7

Browse files
authored
Merge pull request #2181 from NNPDF/use_eko_v15
Use eko v0.15
2 parents f7e1482 + 1147216 commit 81c0da7

File tree

15 files changed

+186
-232
lines changed

15 files changed

+186
-232
lines changed

conda-recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ requirements:
3939
- prompt_toolkit
4040
- validobj
4141
- pineappl >=0.8.2
42-
- eko >=0.14.2,<0.15
42+
- eko >=0.15.1,<0.16
4343
- fiatlux
4444
- sphinx >=5.0.2
4545
- joblib

doc/sphinx/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Ignore auto generated module references
22
source/modules
33
source/theories.csv
4+
source/theories_central.csv

doc/sphinx/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ help:
1919
@if test $@ != "clean"; then \
2020
sphinx-apidoc -f -o ./$(SOURCEDIR)/modules/validphys ../../validphys2/src/validphys/ ; \
2121
sphinx-apidoc -f -o ./$(SOURCEDIR)/modules/n3fit-code ../../n3fit/src/n3fit/ ; \
22+
sphinx-apidoc -f -o ./$(SOURCEDIR)/modules/n3fit-code ../../n3fit/src/evolven3fit/ ; \
2223
fi
2324
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
2425

n3fit/src/evolven3fit/cli.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
def cli_evolven3fit(
5-
configuration_folder, q_fin, q_points, op_card_info, theory_card_info, force, load, dump, ncores
5+
configuration_folder, q_fin, q_points, op_card_info, theory_card_info, force, load, dump
66
):
77
"""Evolves the fitted PDFs.
88
@@ -23,13 +23,5 @@ def cli_evolven3fit(
2323
"""
2424
utils.check_is_a_fit(configuration_folder)
2525
return evolve.evolve_fit(
26-
configuration_folder,
27-
q_fin,
28-
q_points,
29-
op_card_info,
30-
theory_card_info,
31-
force,
32-
load,
33-
dump,
34-
ncores,
26+
configuration_folder, q_fin, q_points, op_card_info, theory_card_info, force, load, dump
3527
)

n3fit/src/evolven3fit/eko_utils.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
from typing import Any, Dict, Optional
33

4-
from ekobox.cards import _operator as default_op_card
54
import numpy as np
65

76
from eko.io import runcards
@@ -30,6 +29,11 @@
3029
NFREF_DEFAULT = 5
3130

3231

32+
def _eko_theory_from_nnpdf_theory(nnpdf_theory):
33+
"""Takes an NNPDF theory and returns an eko theory"""
34+
return runcards.Legacy(nnpdf_theory, {}).new_theory
35+
36+
3337
def construct_eko_cards(
3438
nnpdf_theory,
3539
q_fin,
@@ -54,10 +58,7 @@ def construct_eko_cards(
5458
# eko needs a value for Qedref and for max nf alphas
5559
theory["Qedref"] = theory["Qref"]
5660
theory["MaxNfAs"] = theory["MaxNfPdf"]
57-
58-
# The Legacy function is able to construct a theory card for eko starting from a NNPDF theory
59-
legacy_class = runcards.Legacy(theory, {})
60-
theory_card = legacy_class.new_theory
61+
theory_card = _eko_theory_from_nnpdf_theory(theory)
6162

6263
# construct mugrid
6364

@@ -127,10 +128,7 @@ def construct_eko_photon_cards(
127128
# Now make sure the Legacy class still gets a Qedref, which is equal to Qref
128129
theory["Qedref"] = theory["Qref"]
129130
theory["MaxNfAs"] = theory["MaxNfPdf"]
130-
131-
# The Legacy function is able to construct a theory card for eko starting from a NNPDF theory
132-
legacy_class = runcards.Legacy(theory, {})
133-
theory_card = legacy_class.new_theory
131+
theory_card = _eko_theory_from_nnpdf_theory(theory)
134132

135133
# The photon needs to be evolved down to Q0
136134
q_fin = theory["Q0"]
@@ -177,10 +175,31 @@ def build_opcard(op_card_dict, theory, x_grid, mu0, mugrid):
177175
if op_card_dict is None:
178176
op_card_dict = {}
179177

180-
op_card = default_op_card
181-
182-
op_card.update({"mu0": mu0, "mugrid": mugrid})
178+
# Taken from cards.py https://github.com/NNPDF/eko/blob/master/src/ekobox/cards.py
179+
# 7735fdb
180+
op_card = dict(
181+
init=(1.65, 4),
182+
mugrid=[(100.0, 5)],
183+
xgrid=np.geomspace(1e-7, 1.0, 50).tolist(),
184+
configs=dict(
185+
# These three values might be set by op_card_dict
186+
ev_op_iterations=10,
187+
n_integration_cores=1,
188+
polarized=False,
189+
# ... but these we fix here
190+
ev_op_max_order=[10, 0],
191+
interpolation_polynomial_degree=4,
192+
interpolation_is_log=True,
193+
scvar_method=None,
194+
inversion_method=None,
195+
evolution_method="iterate-exact",
196+
time_like=False,
197+
),
198+
debug=dict(skip_singlet=False, skip_non_singlet=False),
199+
)
183200

201+
op_card["init"] = (mu0, theory["nf0"])
202+
op_card["mugrid"] = mugrid
184203
op_card["xgrid"] = x_grid
185204

186205
# Specify the evolution options and defaults differently from TRN / EXA

n3fit/src/evolven3fit/evolve.py

Lines changed: 80 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
from collections import defaultdict
2+
import dataclasses
23
import json
34
import logging
45
import pathlib
56
import sys
67

78
from ekobox import apply, genpdf, info_file
8-
from joblib import Parallel, delayed
99
import numpy as np
10-
import psutil
1110

1211
import eko
1312
from eko import basis_rotation, runner
13+
from eko.interpolation import XGrid
14+
from eko.io import manipulate
1415
from validphys.utils import yaml_safe
1516

1617
from . import eko_utils, utils
@@ -24,19 +25,9 @@
2425
"level": logging.DEBUG,
2526
}
2627

27-
NUM_CORES = psutil.cpu_count(logical=False)
28-
2928

3029
def evolve_fit(
31-
fit_folder,
32-
q_fin,
33-
q_points,
34-
op_card_dict,
35-
theory_card_dict,
36-
force,
37-
eko_path,
38-
dump_eko=None,
39-
ncores=1,
30+
fit_folder, q_fin, q_points, op_card_dict, theory_card_dict, force, eko_path, dump_eko=None
4031
):
4132
"""
4233
Evolves all the fitted replica in fit_folder/nnfit
@@ -106,10 +97,7 @@ def evolve_fit(
10697
else:
10798
raise ValueError(f"dump_eko not provided and {eko_path=} not found")
10899

109-
with eko.EKO.edit(eko_path) as eko_op:
110-
x_grid_obj = eko.interpolation.XGrid(x_grid)
111-
eko.io.manipulate.xgrid_reshape(eko_op, targetgrid=x_grid_obj, inputgrid=x_grid_obj)
112-
100+
# Open the EKO in read-only mode, if it needs to be manipulated keep it in memory
113101
with eko.EKO.read(eko_path) as eko_op:
114102
# Read the cards directly from the eko to make sure they are consistent
115103
theory = eko_op.theory_card
@@ -118,6 +106,42 @@ def evolve_fit(
118106
_logger.debug(f"Theory card: {json.dumps(theory.raw)}")
119107
_logger.debug(f"Operator card: {json.dumps(op.raw)}")
120108

109+
eko_original_xgrid = eko_op.xgrid
110+
if XGrid(x_grid) != eko_original_xgrid:
111+
# If the xgrid of the eko is not directly usable, construct a copy in memory
112+
# by replacing the internal inventory of operators in a readonly copy
113+
new_xgrid = XGrid(x_grid)
114+
new_metadata = dataclasses.replace(eko_op.metadata, xgrid=new_xgrid)
115+
116+
new_operators = {}
117+
for target_key in eko_op.operators:
118+
elem = eko_op[target_key.ep]
119+
120+
if eko_op.metadata.version == "0.13.4":
121+
# For eko 0.13.4 xgrid is the internal interpolation so we need to check
122+
# whether the rotation is truly needed
123+
# <in practice> this means checking whether the operator shape matches the grid
124+
oplen = elem.operator.shape[-1]
125+
if oplen != len(eko_original_xgrid):
126+
# The operator and its xgrid have different shape
127+
# either prepare an identity, or this EKO is not supported
128+
if oplen != len(x_grid):
129+
raise ValueError(
130+
f"The operator at {eko_path} is not usable, version not supported"
131+
)
132+
eko_original_xgrid = XGrid(x_grid)
133+
134+
new_operators[target_key] = manipulate.xgrid_reshape(
135+
elem,
136+
eko_original_xgrid,
137+
op.configs.interpolation_polynomial_degree,
138+
targetgrid=XGrid(x_grid),
139+
inputgrid=XGrid(x_grid),
140+
)
141+
142+
new_inventory = dataclasses.replace(eko_op.operators, cache=new_operators)
143+
eko_op = dataclasses.replace(eko_op, metadata=new_metadata, operators=new_inventory)
144+
121145
# Modify the info file with the fit-specific info
122146
info = info_file.build(theory, op, 1, info_update={})
123147
info["NumMembers"] = "REPLACE_NREP"
@@ -126,21 +150,49 @@ def evolve_fit(
126150
info["XMax"] = float(x_grid[-1])
127151
# Save the PIDs in the info file in the same order as in the evolution
128152
info["Flavors"] = basis_rotation.flavor_basis_pids
129-
info["NumFlavors"] = theory.heavy.num_flavs_max_pdf
153+
info.setdefault("NumFlavors", 5)
130154
dump_info_file(usr_path, info)
131155

132-
def _wrap_evolve(pdf, replica):
133-
evolved_blocks = evolve_exportgrid(pdf, eko_op, x_grid)
134-
dump_evolved_replica(evolved_blocks, usr_path, int(replica.removeprefix("replica_")))
135-
136-
# Choose the number of cores to be the Minimal value
137-
nb_cores = min(NUM_CORES, abs(ncores))
138-
Parallel(n_jobs=nb_cores)(
139-
delayed(_wrap_evolve)(pdf, r) for r, pdf in initial_PDFs_dict.items()
140-
)
156+
# Read the information from all the sorted replicas into what eko wants
157+
n_replicas = len(initial_PDFs_dict)
158+
all_replicas = []
159+
for rep_idx in range(1, n_replicas + 1):
160+
# swap photon position to match eko.basis_rotation.flavor_basis_pids
161+
pdfgrid = np.array(initial_PDFs_dict[f"replica_{rep_idx}"]["pdfgrid"])
162+
pdfgrid = np.append(pdfgrid[:, -1].reshape(x_grid.size, 1), pdfgrid[:, :-1], axis=1)
163+
# and divide by x
164+
all_replicas.append(pdfgrid.T / x_grid)
165+
166+
# output is {(Q2, nf): (replica, flavour, x)}
167+
all_evolved, _ = apply.apply_grids(eko_op, np.array(all_replicas))
168+
169+
# Now, replica by replica, break into nf blocks
170+
targetgrid = eko_op.xgrid.tolist()
171+
by_nf = defaultdict(list)
172+
for q2, nf in sorted(eko_op.evolgrid, key=lambda ep: ep[1]):
173+
by_nf[nf].append(q2)
174+
q2block_per_nf = {nf: sorted(q2s) for nf, q2s in by_nf.items()}
175+
176+
for replica in range(n_replicas):
177+
blocks = []
178+
for nf, q2grid in q2block_per_nf.items():
179+
180+
def pdf_xq2(pid, x, Q2):
181+
x_idx = targetgrid.index(x)
182+
pid_idx = info["Flavors"].index(pid)
183+
return x * all_evolved[(Q2, nf)][replica][pid_idx][x_idx]
184+
185+
block = genpdf.generate_block(
186+
pdf_xq2,
187+
xgrid=targetgrid,
188+
sorted_q2grid=q2grid,
189+
pids=basis_rotation.flavor_basis_pids,
190+
)
191+
blocks.append(block)
192+
dump_evolved_replica(blocks, usr_path, replica + 1)
141193

142194
# remove folder:
143-
# The function dump_evolved_replica dumps the replica files in a temporary folder
195+
# The function dump_evolved_replica uses a temporary folder
144196
# We need then to remove it after fixing the position of those replica files
145197
(usr_path / "nnfit" / usr_path.stem).rmdir()
146198

@@ -169,52 +221,6 @@ def load_fit(usr_path):
169221
return pdf_dict
170222

171223

172-
def evolve_exportgrid(exportgrid, eko, x_grid):
173-
"""
174-
Evolves the provided exportgrid for the desired replica with the eko and returns the evolved block
175-
176-
Parameters
177-
----------
178-
exportgrid: dict
179-
exportgrid of pdf at fitting scale
180-
eko: eko object
181-
eko operator for evolution
182-
xgrid: list
183-
xgrid to be used as the targetgrid
184-
Returns
185-
-------
186-
: list(np.array)
187-
list of evolved blocks
188-
"""
189-
# construct LhapdfLike object
190-
pdf_grid = np.array(exportgrid["pdfgrid"]).transpose()
191-
pdf_to_evolve = utils.LhapdfLike(pdf_grid, exportgrid["q20"], x_grid)
192-
# evolve pdf
193-
evolved_pdf = apply.apply_pdf(eko, pdf_to_evolve)
194-
# generate block to dump
195-
targetgrid = eko.bases.targetgrid.tolist()
196-
197-
# Finally separate by nf block (and order per nf/q)
198-
by_nf = defaultdict(list)
199-
for q, nf in sorted(eko.evolgrid, key=lambda ep: ep[1]):
200-
by_nf[nf].append(q)
201-
q2block_per_nf = {nf: sorted(qs) for nf, qs in by_nf.items()}
202-
203-
blocks = []
204-
for nf, q2grid in q2block_per_nf.items():
205-
206-
def pdf_xq2(pid, x, Q2):
207-
x_idx = targetgrid.index(x)
208-
return x * evolved_pdf[(Q2, nf)]["pdfs"][pid][x_idx]
209-
210-
block = genpdf.generate_block(
211-
pdf_xq2, xgrid=targetgrid, sorted_q2grid=q2grid, pids=basis_rotation.flavor_basis_pids
212-
)
213-
blocks.append(block)
214-
215-
return blocks
216-
217-
218224
def dump_evolved_replica(evolved_blocks, usr_path, replica_num):
219225
"""
220226
Dump the evolved replica given by evolved_block as the replica num "replica_num" in

n3fit/src/evolven3fit/q2grids.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
"""
2-
Definition of default Q2 grids
2+
Definition of default Q2 grids
33
4-
This file includes:
4+
This file includes:
55
6-
``Q2GRID_DEFAULT``: default NNPDF Q2 grid for evolution (55 points, starts at Q=1GeV)
7-
``Q2GRID_NNPDF40``: q2 grid used in the fits for the NNPDF4.0 release (49 points, starts at Q=1.65 GeV)
8-
``Q2GRID_Nf03``: q2 grid used in the perturvative charm fits for the NNPDF4.0 release (48 points, starts at Q=1GeV)
6+
- ``Q2GRID_DEFAULT``: default NNPDF Q2 grid for evolution (55 points, starts at Q=1GeV)
7+
- ``Q2GRID_NNPDF40``: q2 grid used in the fits for the NNPDF4.0 release (49 points, starts at Q=1.65 GeV)
8+
- ``Q2GRID_Nf03``: q2 grid used in the perturvative charm fits for the NNPDF4.0 release (48 points, starts at Q=1GeV)
99
"""
10+
1011
import numpy as np
1112

1213
Q2GRID_NNPDF40 = (

0 commit comments

Comments
 (0)