1
1
from collections import defaultdict
2
+ import dataclasses
2
3
import json
3
4
import logging
4
5
import pathlib
5
6
import sys
6
7
7
8
from ekobox import apply , genpdf , info_file
8
- from joblib import Parallel , delayed
9
9
import numpy as np
10
- import psutil
11
10
12
11
import eko
13
12
from eko import basis_rotation , runner
13
+ from eko .interpolation import XGrid
14
+ from eko .io import manipulate
14
15
from validphys .utils import yaml_safe
15
16
16
17
from . import eko_utils , utils
24
25
"level" : logging .DEBUG ,
25
26
}
26
27
27
- NUM_CORES = psutil .cpu_count (logical = False )
28
-
29
28
30
29
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
40
31
):
41
32
"""
42
33
Evolves all the fitted replica in fit_folder/nnfit
@@ -106,10 +97,7 @@ def evolve_fit(
106
97
else :
107
98
raise ValueError (f"dump_eko not provided and { eko_path = } not found" )
108
99
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
113
101
with eko .EKO .read (eko_path ) as eko_op :
114
102
# Read the cards directly from the eko to make sure they are consistent
115
103
theory = eko_op .theory_card
@@ -118,6 +106,42 @@ def evolve_fit(
118
106
_logger .debug (f"Theory card: { json .dumps (theory .raw )} " )
119
107
_logger .debug (f"Operator card: { json .dumps (op .raw )} " )
120
108
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
+
121
145
# Modify the info file with the fit-specific info
122
146
info = info_file .build (theory , op , 1 , info_update = {})
123
147
info ["NumMembers" ] = "REPLACE_NREP"
@@ -126,21 +150,49 @@ def evolve_fit(
126
150
info ["XMax" ] = float (x_grid [- 1 ])
127
151
# Save the PIDs in the info file in the same order as in the evolution
128
152
info ["Flavors" ] = basis_rotation .flavor_basis_pids
129
- info [ "NumFlavors" ] = theory . heavy . num_flavs_max_pdf
153
+ info . setdefault ( "NumFlavors" , 5 )
130
154
dump_info_file (usr_path , info )
131
155
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 )
141
193
142
194
# 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
144
196
# We need then to remove it after fixing the position of those replica files
145
197
(usr_path / "nnfit" / usr_path .stem ).rmdir ()
146
198
@@ -169,52 +221,6 @@ def load_fit(usr_path):
169
221
return pdf_dict
170
222
171
223
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
-
218
224
def dump_evolved_replica (evolved_blocks , usr_path , replica_num ):
219
225
"""
220
226
Dump the evolved replica given by evolved_block as the replica num "replica_num" in
0 commit comments