44
55from validphys .utils import yaml_safe
66
7- from .q2grids import Q2GRID_DEFAULT , Q2GRID_NNPDF40
7+ from .q2grids import Q2GRID_NNPDF40
8+
9+ LAMBDA2 = 0.0625
810
911
1012def read_runcard (usr_path ):
@@ -19,43 +21,98 @@ def get_theoryID_from_runcard(usr_path):
1921 return my_runcard ["theory" ]["theoryid" ]
2022
2123
22- def generate_q2grid (Q0 , Qfin , Q_points , match_dict , nf0 = None , legacy40 = False ):
23- """Generate the q2grid used in the final evolved pdfs or use the default grid if Qfin or Q_points is
24- not provided.
24+ def Q2_to_t (q2 : float , lambda2 ) -> float :
25+ """Map to define loglog spacing of the Q2grid"""
26+ return np .log (np .log (q2 / lambda2 ))
27+
28+
29+ def t_to_Q2 (t : float , lambda2 ) -> float :
30+ """Map to go from the loglog spacing back to Q2"""
31+ return lambda2 * np .exp (np .exp (t ))
32+
33+
34+ def generate_q2grid (Q0 , Qmin , Qmax , match_dict , total_points , total_points_ic , legacy40 = False ):
35+ """Generate the q2grid used in the final evolved pdfs or use the default grid if legacy40 is set.
2536
26- match_dict contains the couples (mass : factor) where factor is the number to be multiplied to mass
27- in order to obtain the relative matching scale.
37+ The grid uses $\log(\log(Q^2/\t ext{`LAMBDA2`})) spacing between the points.
38+
39+ The grid from `Q2_min` --> `mc` is made separately from the rest to be able to let it contain at
40+ least 5 points. The reason for this is to always have some points in the intrinsic charm regime.
41+ The rest of the grids contains the same loglog spacing from threshold to threshold.
42+
43+ Since the grid needs to contain the $Q^2$ values `Q2_min`, `mc`^2, `Q0`^2, `mb`^2 and `Q2_max`,
44+ we divide the grid in batches that have these values as boundaries, and add them together
45+ at the end. The batches ad boundaries are thus like this:
46+
47+ Q2_min --> mc^2 --> Q0^2 --> mb^2 --> Q2_max
48+
49+ `match_dict` contains the quark mass thresholds and factors. This "factor" is the number to be
50+ multiplied to mass in order to obtain the relative matching scale (e.g. `match_dict["kbThr"]`
51+ in the case of the bottom threshold).
2852 """
29- if Qfin is None and Q_points is None :
30- if legacy40 :
31- return Q2GRID_NNPDF40
32- elif nf0 in (3 , 4 , 5 ):
33- return Q2GRID_DEFAULT
34- elif nf0 is None :
35- raise ValueError ("In order to use a default grid, a value of nf0 must be provided" )
36- else :
37- raise NotImplementedError (f"No default grid in Q available for { nf0 = } " )
38- elif Qfin is None or Q_points is None :
39- raise ValueError ("q_fin and q_points must be specified either both or none of them" )
40- else :
41- grids = []
42- Q_ini = Q0
43- num_points_list = []
44- for masses in match_dict :
45- match_scale = masses * match_dict [masses ]
46- # Fraction of the total points to be included in this batch is proportional
47- # to the log of the ratio between the initial scale and final scale of the
48- # batch itself (normalized to the same log of the global initial and final
49- # scales)
50- if match_scale < Qfin :
51- frac_of_point = np .log (match_scale / Q_ini ) / np .log (Qfin / Q0 )
52- num_points = int (Q_points * frac_of_point )
53- num_points_list .append (num_points )
54- grids .append (np .geomspace (Q_ini ** 2 , match_scale ** 2 , num = num_points , endpoint = False ))
55- Q_ini = match_scale
56- num_points = Q_points - sum (num_points_list )
57- grids .append (np .geomspace (Q_ini ** 2 , Qfin ** 2 , num = num_points ))
58- return np .concatenate (grids ).tolist ()
53+
54+ # If flag --legacy40 is set return handmade legacy grid
55+ if legacy40 :
56+ return Q2GRID_NNPDF40
57+ # Otherwise dynamically create the grid from Q2_min --> Q2_max
58+
59+ if Q0 < Qmin :
60+ raise ValueError ("Q0 cannot be smaller than Qmin because the grid needs to contain Q0" )
61+
62+ if Qmax < Qmin :
63+ raise ValueError ("Qmax cannot be smaller than Qmin" )
64+
65+ if total_points <= 5 :
66+ raise ValueError ("You need minimally 6 points (total_points > 5)" )
67+
68+ if total_points_ic < 2 and Qmin < 1.502 :
69+ raise ValueError ("You need minimally 2 points below Q0 (total_points_ic > 1)" )
70+
71+ Q2_min = Qmin ** 2 # 1.0**2
72+ Q2_max = Qmax ** 2 # 1e5**2
73+
74+ # Collect all node Q2's from Q0^2 --> Q2_max
75+ q0_2 = Q0 ** 2
76+ node_Q2 = [q0_2 , (match_dict ["mb" ] * match_dict ["kbThr" ]) ** 2 , Q2_max ]
77+
78+ # Make initial uniform grid in t from Q0^2 --> Q2_max
79+ t_min = Q2_to_t (q0_2 , LAMBDA2 )
80+ t_max = Q2_to_t (Q2_max , LAMBDA2 )
81+ t_vals = np .linspace (t_min , t_max , total_points )
82+ q2_vals = t_to_Q2 (t_vals , LAMBDA2 )
83+
84+ # Count how many points fall into each subgrid
85+ n_intervals = len (node_Q2 ) - 1
86+ nQpoints = np .zeros (n_intervals , dtype = int )
87+ subgridindex = 0
88+
89+ for q2 in q2_vals :
90+ while subgridindex < n_intervals - 1 and q2 >= node_Q2 [subgridindex + 1 ]:
91+ subgridindex += 1
92+ nQpoints [subgridindex ] += 1
93+
94+ # Make t grid from Q2_min --> Q0^2
95+ t_min_ic = Q2_to_t (Q2_min , LAMBDA2 )
96+ t_max_ic = Q2_to_t ((match_dict ["mc" ] * match_dict ["kcThr" ]) ** 2 , LAMBDA2 )
97+ t_vals_ic = np .linspace (t_min_ic , t_max_ic , total_points_ic )
98+ q2_vals_ic = t_to_Q2 (t_vals_ic , LAMBDA2 )
99+
100+ # Now build each subgrid to contain the points we want
101+ grids = []
102+ grids .append (q2_vals_ic )
103+ for i in range (len (node_Q2 ) - 1 ):
104+ q2_lo , q2_hi = node_Q2 [i ], node_Q2 [i + 1 ]
105+ t_lo , t_hi = Q2_to_t (q2_lo , LAMBDA2 ), Q2_to_t (q2_hi , LAMBDA2 )
106+ npts = int (nQpoints [i ])
107+ t_subgr = np .linspace (t_lo , t_hi , npts )
108+ q2_subgr = t_to_Q2 (t_subgr , LAMBDA2 )
109+ if i < n_intervals - 1 :
110+ q2_subgr = q2_subgr [:- 1 ]
111+ grids .append (q2_subgr )
112+
113+ # Combine all subgrids and return as an array
114+ q2_full = np .concatenate (grids )
115+ return q2_full
59116
60117
61118def check_is_a_fit (config_folder ):
0 commit comments