1use super::boc::{Channel, Kinematics, Order};
4use super::convolutions::ConvType;
5use super::error::{Error, Result};
6use super::grid::Grid;
7use super::packed_array::PackedArray;
8use super::pids::PidBasis;
9use super::subgrid::{self, ImportSubgridV1, Subgrid, SubgridEnum};
10use float_cmp::approx_eq;
11use itertools::izip;
12use itertools::Itertools;
13use ndarray::linalg;
14use ndarray::{
15 s, Array1, Array2, Array3, ArrayD, ArrayView1, ArrayView4, ArrayViewD, ArrayViewMutD, Axis,
16 Ix1, Ix2,
17};
18use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
19use std::iter;
20
21pub struct EvolveInfo {
24 pub fac1: Vec<f64>,
26 pub frg1: Vec<f64>,
28 pub pids1: Vec<i32>,
30 pub x1: Vec<f64>,
32 pub ren1: Vec<f64>,
34}
35
36#[derive(Clone)]
53pub struct OperatorSliceInfo {
54 pub fac0: f64,
56 pub pids0: Vec<i32>,
58 pub x0: Vec<f64>,
60 pub fac1: f64,
62 pub pids1: Vec<i32>,
65 pub x1: Vec<f64>,
67
68 pub pid_basis: PidBasis,
70 pub conv_type: ConvType,
75}
76
77pub struct AlphasTable {
80 pub ren1: Vec<f64>,
82 pub alphas: Vec<f64>,
84}
85
86impl AlphasTable {
87 pub fn from_grid(grid: &Grid, xir: f64, alphas: &dyn Fn(f64) -> f64) -> Self {
91 let mut ren1: Vec<_> = grid
92 .subgrids()
93 .iter()
94 .flat_map(|subgrid| {
95 grid.scales()
96 .ren
97 .calc(&subgrid.node_values(), grid.kinematics())
98 .into_owned()
99 .into_iter()
100 .map(|ren| xir * xir * ren)
101 })
102 .collect();
103 ren1.sort_by(f64::total_cmp);
104 ren1.dedup_by(subgrid::node_value_eq_ref_mut);
105 let ren1 = ren1;
106 let alphas: Vec<_> = ren1.iter().map(|&mur2| alphas(mur2)).collect();
107
108 Self { ren1, alphas }
109 }
110}
111
112fn gluon_has_pid_zero(grid: &Grid) -> bool {
113 grid.channels()
115 .iter()
116 .any(|entry| entry.entry().iter().any(|(pids, _)| pids.contains(&0)))
117 && *grid.pid_basis() == PidBasis::Pdg
119}
120
121type Pid01IndexTuples = Vec<(usize, usize)>;
122type Pid01Tuples = Vec<(i32, i32)>;
123
124fn pid_slices(
125 operator: &ArrayView4<f64>,
126 info: &OperatorSliceInfo,
127 gluon_has_pid_zero: bool,
128 pid1_nonzero: &dyn Fn(i32) -> bool,
129) -> Result<(Pid01IndexTuples, Pid01Tuples)> {
130 let pid_indices: Vec<_> = (0..operator.dim().2)
132 .cartesian_product(0..operator.dim().0)
133 .filter(|&(pid0_idx, pid1_idx)| {
134 operator
137 .slice(s![pid1_idx, .., pid0_idx, ..])
138 .iter()
139 .any(|&value| value != 0.0)
140 && pid1_nonzero(if gluon_has_pid_zero && info.pids1[pid1_idx] == 21 {
141 0
142 } else {
143 info.pids1[pid1_idx]
144 })
145 })
146 .collect();
147
148 if pid_indices.is_empty() {
149 return Err(Error::General(
150 "no non-zero operator found; result would be an empty FkTable".to_owned(),
151 ));
152 }
153
154 let pids = pid_indices
156 .iter()
157 .map(|&(pid0_idx, pid1_idx)| {
158 (
159 info.pids0[pid0_idx],
160 if gluon_has_pid_zero && info.pids1[pid1_idx] == 21 {
161 0
162 } else {
163 info.pids1[pid1_idx]
164 },
165 )
166 })
167 .collect();
168
169 Ok((pid_indices, pids))
170}
171
172fn operator_slices(
173 operator: &ArrayView4<f64>,
174 info: &OperatorSliceInfo,
175 pid_indices: &[(usize, usize)],
176 x1: &[f64],
177) -> Result<Vec<Array2<f64>>> {
178 let x1_indices: Vec<_> = x1
180 .iter()
181 .map(|&x1p| {
182 info.x1
183 .iter()
184 .position(|&x1| subgrid::node_value_eq(x1p, x1))
185 .ok_or_else(|| Error::General(format!("no operator for x = {x1p} found")))
186 })
187 .collect::<Result<_>>()?;
189
190 let operators: Vec<_> = pid_indices
192 .iter()
193 .map(|&(pid0_idx, pid1_idx)| {
194 operator
195 .slice(s![pid1_idx, .., pid0_idx, ..])
196 .select(Axis(0), &x1_indices)
197 .reversed_axes()
198 .as_standard_layout()
199 .into_owned()
200 })
201 .collect();
202
203 Ok(operators)
204}
205
206type X1aX1bOpDTuple = (Vec<Vec<f64>>, Option<ArrayD<f64>>);
207
208fn ndarray_from_subgrid_orders_slice(
209 grid: &Grid,
210 fac1: f64,
211 kinematics: &[Kinematics],
212 subgrids: &ArrayView1<SubgridEnum>,
213 orders: &[Order],
214 order_mask: &[bool],
215 (xir, xif, xia): (f64, f64, f64),
216 alphas_table: &AlphasTable,
217) -> Result<X1aX1bOpDTuple> {
218 assert_eq!(grid.kinematics()[0], Kinematics::Scale(0));
220 let x_start = grid
221 .kinematics()
222 .iter()
223 .position(|kin| matches!(kin, Kinematics::X(_)))
224 .unwrap();
226
227 let mut x1n: Vec<_> = kinematics
229 .iter()
230 .enumerate()
231 .filter_map(|(idx, kin)| matches!(kin, Kinematics::X(_)).then_some(idx))
232 .map(|kin_idx| {
233 subgrids
234 .iter()
235 .enumerate()
236 .filter(|&(ord_idx, subgrid)| {
237 order_mask.get(ord_idx).copied().unwrap_or(true)
238 && !subgrid.is_empty()
240 })
241 .flat_map(|(_, subgrid)| subgrid.node_values()[kin_idx].clone())
242 .collect::<Vec<_>>()
243 })
244 .collect();
245
246 for x1 in &mut x1n {
247 x1.sort_by(f64::total_cmp);
248 x1.dedup_by(subgrid::node_value_eq_ref_mut);
249 }
250
251 let dim: Vec<_> = x1n.iter().map(Vec::len).collect();
252 let mut array = ArrayD::<f64>::zeros(dim);
253 let mut zero = true;
254 let mut x1_idx = vec![0; grid.convolutions().len()];
255
256 for (subgrid, order) in subgrids
258 .iter()
259 .zip(orders.iter())
260 .zip(order_mask.iter().chain(iter::repeat(&true)))
261 .filter_map(|((subgrid, order), &enabled)| {
262 (enabled && !subgrid.is_empty()).then_some((subgrid, order))
263 })
264 {
265 let mut logs = 1.0;
266
267 if order.logxir > 0 {
268 if approx_eq!(f64, xir, 1.0, ulps = 4) {
269 continue;
270 }
271
272 logs *= (xir * xir).ln();
273 }
274
275 if order.logxif > 0 {
276 if approx_eq!(f64, xif, 1.0, ulps = 4) {
277 continue;
278 }
279
280 logs *= (xif * xif).ln();
281 }
282
283 if order.logxia > 0 {
284 if approx_eq!(f64, xia, 1.0, ulps = 4) {
285 continue;
286 }
287
288 logs *= (xia * xia).ln();
289 }
290
291 let node_values = subgrid.node_values();
292 let scale_dims: Vec<_> = grid
293 .kinematics()
294 .iter()
295 .zip(&node_values)
296 .filter_map(|(kin, node_values)| {
297 matches!(kin, Kinematics::Scale(_)).then_some(node_values.len())
298 })
299 .collect();
300
301 let x1_indices: Vec<Vec<_>> = kinematics
302 .iter()
303 .enumerate()
304 .filter_map(|(idx, kin)| matches!(kin, Kinematics::X(_)).then_some(idx))
305 .zip(&x1n)
306 .map(|(kin_idx, x1)| {
307 node_values[kin_idx]
308 .iter()
309 .map(|&xs| {
310 x1.iter()
311 .position(|&x| subgrid::node_value_eq(x, xs))
312 .unwrap()
314 })
315 .collect()
316 })
317 .collect();
318
319 let rens = grid.scales().ren.calc(&node_values, grid.kinematics());
320 let facs = grid.scales().fac.calc(&node_values, grid.kinematics());
321
322 for (indices, value) in subgrid.indexed_iter() {
323 let ren = rens[grid.scales().ren.idx(&indices, &scale_dims)];
325 let fac = facs[grid.scales().fac.idx(&indices, &scale_dims)];
326
327 if !subgrid::node_value_eq(xif * xif * fac, fac1) {
328 continue;
329 }
330
331 let mur2 = xir * xir * ren;
332
333 let als = if order.alphas == 0 {
334 1.0
335 } else if let Some(alphas) = alphas_table
336 .ren1
337 .iter()
338 .zip(alphas_table.alphas.iter())
339 .find_map(|(&ren1, &alphas)| subgrid::node_value_eq(ren1, mur2).then_some(alphas))
340 {
341 alphas.powi(order.alphas.into())
342 } else {
343 return Err(Error::General(format!("no alphas for mur2 = {mur2} found")));
344 };
345
346 zero = false;
347
348 for (i, &index) in indices.iter().skip(x_start).enumerate() {
349 x1_idx[i] = x1_indices[i][index];
350 }
351
352 array[x1_idx.as_slice()] += als * logs * value;
353 }
354 }
355
356 Ok((x1n, (!zero).then_some(array)))
357}
358
359pub(crate) fn evolve_slice(
360 grid: &Grid,
361 operators: &[ArrayView4<f64>],
362 infos: &[OperatorSliceInfo],
363 scale_values: &[f64],
364 order_mask: &[bool],
365 xi: (f64, f64, f64),
366 alphas_table: &AlphasTable,
367) -> Result<(Array3<SubgridEnum>, Vec<Channel>)> {
368 let gluon_has_pid_zero = gluon_has_pid_zero(grid);
369
370 let mut fac1_scales: Vec<_> = infos.iter().map(|info| info.fac1).collect();
372 fac1_scales.sort_by(f64::total_cmp);
373 assert!(fac1_scales
374 .windows(2)
375 .all(|scales| subgrid::node_value_eq(scales[0], scales[1])));
376 let fac1 = fac1_scales[0];
377
378 assert_eq!(operators.len(), infos.len());
379 assert_eq!(operators.len(), grid.convolutions().len());
380
381 let (pid_indices, pids01): (Vec<_>, Vec<_>) = izip!(0..infos.len(), operators, infos)
382 .map(|(d, operator, info)| {
383 pid_slices(operator, info, gluon_has_pid_zero, &|pid1| {
384 grid.channels()
385 .iter()
386 .flat_map(Channel::entry)
387 .any(|(pids, _)| pids[d] == pid1)
388 })
389 })
390 .collect::<Result<Vec<_>>>()?
391 .into_iter()
392 .unzip();
393
394 let mut channels0: Vec<_> = pids01
395 .iter()
396 .map(|pids| pids.iter().map(|&(pid0, _)| pid0))
397 .multi_cartesian_product()
398 .collect();
399 channels0.sort_unstable();
400 channels0.dedup();
401 let channels0 = channels0;
402
403 let mut sub_fk_tables = Vec::with_capacity(grid.bwfl().len() * channels0.len());
404 let mut last_x1 = vec![Vec::new(); infos.len()];
405 let mut eko_slices = vec![Vec::new(); infos.len()];
406 let dim: Vec<_> = infos.iter().map(|info| info.x0.len()).collect();
407
408 for subgrids_oc in grid.subgrids().axis_iter(Axis(1)) {
409 let mut tables = vec![ArrayD::zeros(dim.clone()); channels0.len()];
410
411 for (subgrids_o, channel1) in subgrids_oc.axis_iter(Axis(1)).zip(grid.channels()) {
412 let (x1, array) = ndarray_from_subgrid_orders_slice(
413 grid,
414 fac1,
415 grid.kinematics(),
416 &subgrids_o,
417 grid.orders(),
418 order_mask,
419 xi,
420 alphas_table,
421 )?;
422
423 let Some(array) = array else {
425 continue;
426 };
427
428 for (last_x1, x1, pid_indices, slices, operator, info) in izip!(
429 &mut last_x1,
430 x1,
431 &pid_indices,
432 &mut eko_slices,
433 operators,
434 infos
435 ) {
436 if (last_x1.len() != x1.len())
437 || last_x1
438 .iter()
439 .zip(x1.iter())
440 .any(|(&lhs, &rhs)| !subgrid::node_value_eq(lhs, rhs))
441 {
442 *slices = operator_slices(operator, info, pid_indices, &x1)?;
443 *last_x1 = x1;
444 }
445 }
446
447 tables
449 .par_iter_mut()
450 .zip(&channels0)
451 .for_each(|(fk_table, pids0)| {
452 for (pids1, factor) in channel1.entry() {
454 let ops: Option<Box<[_]>> = izip!(pids0, pids1, &pids01, &eko_slices)
456 .map(|(&pid0, &pid1, pids, slices)| {
457 pids.iter().zip(slices).find_map(|(pid01, op)| {
459 (pid01 == &(pid0, pid1)).then_some(op)
461 })
462 })
463 .collect();
466
467 if let Some(ops) = ops {
469 general_tensor_mul(*factor, array.view(), &ops, fk_table.view_mut());
470 }
471 }
472 });
473 }
474
475 let mut node_values = vec![scale_values.to_vec()];
476 for info in infos {
477 node_values.push(info.x0.clone());
478 }
479
480 sub_fk_tables.extend(tables.into_iter().map(|table| {
481 ImportSubgridV1::new(
482 PackedArray::from(table.insert_axis(Axis(0)).view()),
484 node_values.clone(),
485 )
486 .into()
487 }));
488 }
489
490 Ok((
491 Array1::from_iter(sub_fk_tables)
492 .into_shape((1, grid.bwfl().len(), channels0.len()))
493 .unwrap(),
494 channels0
495 .into_iter()
496 .map(|c| Channel::new(vec![(c, 1.0)]))
497 .collect(),
498 ))
499}
500
501fn general_tensor_mul(
502 factor: f64,
503 array: ArrayViewD<f64>,
504 ops: &[&Array2<f64>],
505 mut fk_table: ArrayViewMutD<f64>,
506) {
507 let convolutions = array.shape().len();
508
509 match convolutions {
510 1 => {
512 let array = array
513 .into_dimensionality::<Ix1>()
514 .unwrap();
516 let mut fk_table = fk_table
517 .into_dimensionality::<Ix1>()
518 .unwrap();
520
521 linalg::general_mat_vec_mul(factor, ops[0], &array, 1.0, &mut fk_table);
523 }
524 2 => {
525 let array = array
526 .into_dimensionality::<Ix2>()
527 .unwrap();
529 let mut fk_table = fk_table
530 .into_dimensionality::<Ix2>()
531 .unwrap();
533
534 let mut tmp = Array2::zeros((array.shape()[0], ops[1].shape()[0]));
535 linalg::general_mat_mul(1.0, &array, &ops[1].t(), 0.0, &mut tmp);
537 linalg::general_mat_mul(factor, ops[0], &tmp, 1.0, &mut fk_table);
539 }
540 _ => {
541 let (ops_0, ops_dm1) = ops.split_first().unwrap();
542
543 for (mut fk_table_i, ops_0_i) in fk_table
544 .axis_iter_mut(Axis(0))
545 .zip(ops_0.axis_iter(Axis(0)))
546 {
547 for (array_j, ops_0_ij) in array.axis_iter(Axis(0)).zip(ops_0_i.iter()) {
548 general_tensor_mul(factor * ops_0_ij, array_j, ops_dm1, fk_table_i.view_mut());
549 }
550 }
551 }
552 }
553}