use core::panic;
use ndarray::{Array1, Array2};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use super::alphas::AlphaS;
use super::interleaved::InterleavedHermite;
use super::interpolator::{DynInterpolator, InterpolationConfig, InterpolatorFactory};
use super::metadata::{InterpolatorType, MetaData};
use super::parser::SubgridData;
use super::strategy::ChebyshevAllPids;
use super::subgrid::{ParamRange, RangeParameters, SubGrid};
#[derive(Debug, Error)]
pub enum Error {
#[error("No subgrid found for x={x}, q2={q2}")]
SubgridNotFound {
x: f64,
q2: f64,
},
#[error("Invalid interpolation parameters: {0}")]
InterpolationError(String),
}
const PID_MIN: i32 = -6;
const PID_MAX: i32 = 22;
const PID_RANGE: usize = (PID_MAX - PID_MIN + 1) as usize;
const PID_NONE: u8 = u8::MAX;
#[derive(Debug, Clone)]
struct PidLookup {
table: [u8; PID_RANGE],
}
impl Default for PidLookup {
fn default() -> Self {
Self {
table: [PID_NONE; PID_RANGE],
}
}
}
impl PidLookup {
fn build(pids: &Array1<i32>) -> Self {
let mut lut = Self::default();
for (idx, &pid) in pids.iter().enumerate() {
let normalized = if pid == 0 { 21 } else { pid };
if (PID_MIN..=PID_MAX).contains(&normalized) {
let slot = (normalized - PID_MIN) as usize;
if lut.table[slot] == PID_NONE {
lut.table[slot] = idx as u8;
}
}
}
lut
}
#[inline(always)]
fn get(&self, pid: i32) -> Option<usize> {
let normalized = if pid == 0 { 21 } else { pid };
if !(PID_MIN..=PID_MAX).contains(&normalized) {
return None;
}
let v = self.table[(normalized - PID_MIN) as usize];
if v == PID_NONE {
None
} else {
Some(v as usize)
}
}
}
#[derive(Debug, Serialize)]
pub struct GridArray {
pub pids: Array1<i32>,
pub subgrids: Vec<SubGrid>,
#[serde(skip)]
pid_lookup: PidLookup,
}
impl<'de> Deserialize<'de> for GridArray {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
pids: Array1<i32>,
subgrids: Vec<SubGrid>,
}
let h = Helper::deserialize(deserializer)?;
let pid_lookup = PidLookup::build(&h.pids);
Ok(Self {
pids: h.pids,
subgrids: h.subgrids,
pid_lookup,
})
}
}
impl GridArray {
pub fn from_parts(pids: Array1<i32>, subgrids: Vec<SubGrid>) -> Self {
let pid_lookup = PidLookup::build(&pids);
Self {
pids,
subgrids,
pid_lookup,
}
}
pub fn new(subgrid_data: Vec<SubgridData>, pids: Vec<i32>) -> Self {
let nflav = pids.len();
let subgrids = subgrid_data
.into_iter()
.map(|data| {
if data.xis.len() > 1 || data.deltas.len() > 1 {
SubGrid::new_8d(
data.nucleons,
data.alphas,
data.xis,
data.deltas,
data.kts,
data.xs,
data.q2s,
nflav,
data.grid_data,
)
} else {
SubGrid::new(
data.nucleons,
data.alphas,
data.kts,
data.xs,
data.q2s,
nflav,
data.grid_data,
)
}
})
.collect();
let pids = Array1::from_vec(pids);
let pid_lookup = PidLookup::build(&pids);
Self {
pids,
subgrids,
pid_lookup,
}
}
#[allow(clippy::too_many_arguments)]
pub fn xf_from_index(
&self,
nucleon_idx: usize,
alpha_idx: usize,
kt_idx: usize,
x_idx: usize,
q2_idx: usize,
flavor_id: i32,
subgrid_idx: usize,
) -> f64 {
let pid_idx = self.pid_index(flavor_id).expect("Invalid flavor ID");
let grid_view = self.subgrids[subgrid_idx].grid.view();
grid_view[[nucleon_idx, alpha_idx, pid_idx, kt_idx, x_idx, q2_idx]]
}
pub fn find_subgrid(&self, points: &[f64]) -> Option<usize> {
if self.subgrids.len() == 1 {
return Some(0);
}
self.subgrids
.iter()
.position(|sg| sg.contains_point(points))
.or_else(|| {
self.subgrids
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
a.distance_to_point(points)
.partial_cmp(&b.distance_to_point(points))
.unwrap()
})
.map(|(idx, _)| idx)
})
}
#[inline(always)]
fn pid_index(&self, flavor_id: i32) -> Option<usize> {
self.pid_lookup.get(flavor_id)
}
pub fn global_ranges(&self) -> RangeParameters {
fn global_range<F>(subgrids: &[SubGrid], extractor: F) -> ParamRange
where
F: Fn(&SubGrid) -> &ParamRange,
{
let min = subgrids
.iter()
.map(|sg| extractor(sg).min)
.fold(f64::INFINITY, f64::min);
let max = subgrids
.iter()
.map(|sg| extractor(sg).max)
.fold(f64::NEG_INFINITY, f64::max);
ParamRange::new(min, max)
}
RangeParameters::new(
global_range(&self.subgrids, |sg| &sg.nucleons_range),
global_range(&self.subgrids, |sg| &sg.alphas_range),
global_range(&self.subgrids, |sg| &sg.xi_range),
global_range(&self.subgrids, |sg| &sg.delta_range),
global_range(&self.subgrids, |sg| &sg.kt_range),
global_range(&self.subgrids, |sg| &sg.x_range),
global_range(&self.subgrids, |sg| &sg.q2_range),
)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub enum ForcePositive {
ClipNegative,
ClipSmall,
NoClipping,
}
fn fp_identity(value: f64) -> f64 {
value
}
fn fp_clip_negative(value: f64) -> f64 {
value.max(0.0)
}
fn fp_clip_small(value: f64) -> f64 {
value.max(1e-10)
}
fn build_interleaved(
subgrid: &SubGrid,
config: InterpolationConfig,
n_pids: usize,
) -> Option<InterleavedHermite> {
let log_xs: Vec<f64> = subgrid.xs.iter().map(|&x| x.ln()).collect();
let log_q2s: Vec<f64> = subgrid.q2s.iter().map(|&q| q.ln()).collect();
match config {
InterpolationConfig::TwoD => {
let extra_grids = vec![log_q2s];
let grid = subgrid.grid.view();
let is_8d = subgrid.is_8d();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
if is_8d {
grid[[0, 0, 0, 0, 0, pid, x_idx, q2_idx]]
} else {
grid[[0, 0, pid, 0, x_idx, q2_idx]]
}
});
Some(interleaved)
}
InterpolationConfig::ThreeDNucleons => {
let log_nucs: Vec<f64> = subgrid.nucleons.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_nucs];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let nuc_idx = extra[1];
grid[[nuc_idx, 0, pid, 0, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::ThreeDAlphas => {
let log_alp: Vec<f64> = subgrid.alphas.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_alp];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let alp_idx = extra[1];
grid[[0, alp_idx, pid, 0, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::ThreeDKt => {
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_kts];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let kt_idx = extra[1];
grid[[0, 0, pid, kt_idx, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::ThreeDXi => {
let log_xis: Vec<f64> = subgrid.xis.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_xis];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let xi_idx = extra[1];
grid[[0, 0, xi_idx, 0, 0, pid, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::ThreeDDelta => {
let log_del: Vec<f64> = subgrid.deltas.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_del];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let del_idx = extra[1];
grid[[0, 0, 0, del_idx, 0, pid, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::FourDNucleonsAlphas => {
let log_nucs: Vec<f64> = subgrid.nucleons.iter().map(|&v| v.ln()).collect();
let log_alp: Vec<f64> = subgrid.alphas.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_alp, log_nucs];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let alp_idx = extra[1];
let nuc_idx = extra[2];
grid[[nuc_idx, alp_idx, pid, 0, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::FourDNucleonsKt => {
let log_nucs: Vec<f64> = subgrid.nucleons.iter().map(|&v| v.ln()).collect();
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_kts, log_nucs];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let kt_idx = extra[1];
let nuc_idx = extra[2];
grid[[nuc_idx, 0, pid, kt_idx, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::FourDAlphasKt => {
let log_alp: Vec<f64> = subgrid.alphas.iter().map(|&v| v.ln()).collect();
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_kts, log_alp];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let kt_idx = extra[1];
let alp_idx = extra[2];
grid[[0, alp_idx, pid, kt_idx, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::FourDXiDelta => {
let log_xis: Vec<f64> = subgrid.xis.iter().map(|&v| v.ln()).collect();
let log_del: Vec<f64> = subgrid.deltas.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_del, log_xis];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let del_idx = extra[1];
let xi_idx = extra[2];
grid[[0, 0, xi_idx, del_idx, 0, pid, x_idx, q2_idx]]
});
Some(interleaved)
}
InterpolationConfig::FiveD => {
let log_xis: Vec<f64> = subgrid.xis.iter().map(|&v| v.ln()).collect();
let log_del: Vec<f64> = subgrid.deltas.iter().map(|&v| v.ln()).collect();
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let extra_grids = vec![log_q2s, log_del, log_xis, log_kts];
let grid = subgrid.grid.view();
let interleaved =
InterleavedHermite::build(log_xs, extra_grids, n_pids, |pid, x_idx, extra| {
let q2_idx = extra[0];
let del_idx = extra[1];
let xi_idx = extra[2];
let kt_idx = extra[3];
grid[[0, 0, xi_idx, del_idx, kt_idx, pid, x_idx, q2_idx]]
});
Some(interleaved)
}
_ => None,
}
}
fn build_chebyshev_fast(
subgrid: &SubGrid,
config: InterpolationConfig,
n_pids: usize,
) -> Option<ChebyshevAllPids> {
let log_xs: Vec<f64> = subgrid.xs.iter().map(|&x| x.ln()).collect();
let log_q2s: Vec<f64> = subgrid.q2s.iter().map(|&q| q.ln()).collect();
match config {
InterpolationConfig::TwoD => {
let coords = vec![log_xs, log_q2s];
let grid = subgrid.grid.view();
let is_8d = subgrid.is_8d();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
if is_8d {
grid[[0, 0, 0, 0, 0, pid, idx[0], idx[1]]]
} else {
grid[[0, 0, pid, 0, idx[0], idx[1]]]
}
}))
}
InterpolationConfig::ThreeDNucleons => {
let log_nucs: Vec<f64> = subgrid.nucleons.iter().map(|&v| v.ln()).collect();
let coords = vec![log_nucs, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[idx[0], 0, pid, 0, idx[1], idx[2]]]
}))
}
InterpolationConfig::ThreeDAlphas => {
let log_alps: Vec<f64> = subgrid.alphas.iter().map(|&v| v.ln()).collect();
let coords = vec![log_alps, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, idx[0], pid, 0, idx[1], idx[2]]]
}))
}
InterpolationConfig::ThreeDKt => {
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let coords = vec![log_kts, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, 0, pid, idx[0], idx[1], idx[2]]]
}))
}
InterpolationConfig::ThreeDXi => {
let log_xis: Vec<f64> = subgrid.xis.iter().map(|&v| v.ln()).collect();
let coords = vec![log_xis, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, 0, idx[0], 0, 0, pid, idx[1], idx[2]]]
}))
}
InterpolationConfig::ThreeDDelta => {
let log_dels: Vec<f64> = subgrid.deltas.iter().map(|&v| v.ln()).collect();
let coords = vec![log_dels, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, 0, 0, idx[0], 0, pid, idx[1], idx[2]]]
}))
}
InterpolationConfig::FourDNucleonsAlphas => {
let coords = vec![
subgrid.nucleons.to_vec(),
subgrid.alphas.to_vec(),
subgrid.xs.to_vec(),
subgrid.q2s.to_vec(),
];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[idx[0], idx[1], pid, 0, idx[2], idx[3]]]
}))
}
InterpolationConfig::FourDNucleonsKt => {
let log_nucs: Vec<f64> = subgrid.nucleons.iter().map(|&v| v.ln()).collect();
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let coords = vec![log_nucs, log_kts, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[idx[0], 0, pid, idx[1], idx[2], idx[3]]]
}))
}
InterpolationConfig::FourDAlphasKt => {
let log_alps: Vec<f64> = subgrid.alphas.iter().map(|&v| v.ln()).collect();
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let coords = vec![log_alps, log_kts, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, idx[0], pid, idx[1], idx[2], idx[3]]]
}))
}
InterpolationConfig::FourDXiDelta => {
let log_xis: Vec<f64> = subgrid.xis.iter().map(|&v| v.ln()).collect();
let log_dels: Vec<f64> = subgrid.deltas.iter().map(|&v| v.ln()).collect();
let coords = vec![log_xis, log_dels, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, 0, idx[0], idx[1], 0, pid, idx[2], idx[3]]]
}))
}
InterpolationConfig::FiveD => {
let log_kts: Vec<f64> = subgrid.kts.iter().map(|&v| v.ln()).collect();
let log_xis: Vec<f64> = subgrid.xis.iter().map(|&v| v.ln()).collect();
let log_dels: Vec<f64> = subgrid.deltas.iter().map(|&v| v.ln()).collect();
let coords = vec![log_kts, log_xis, log_dels, log_xs, log_q2s];
let grid = subgrid.grid.view();
Some(ChebyshevAllPids::build(coords, n_pids, |pid, idx| {
grid[[0, 0, idx[1], idx[2], idx[0], pid, idx[3], idx[4]]]
}))
}
_ => None,
}
}
pub struct GridPDF {
info: MetaData,
pub knot_array: GridArray,
interpolators: Vec<Vec<Box<dyn DynInterpolator>>>,
alphas: AlphaS,
pub force_positive: Option<ForcePositive>,
use_log: bool,
force_positive_fn: fn(f64) -> f64,
interleaved: Option<Vec<InterleavedHermite>>,
chebyshev_fast: Option<Vec<ChebyshevAllPids>>,
}
impl GridPDF {
pub fn new(info: MetaData, knot_array: GridArray) -> Self {
let interpolators = Self::build_interpolators(&info, &knot_array);
let alphas = AlphaS::from_metadata(&info).expect("Failed to create AlphaS calculator");
let use_log = matches!(
info.interpolator_type,
InterpolatorType::LogBilinear
| InterpolatorType::LogBicubic
| InterpolatorType::LogTricubic
| InterpolatorType::LogFourCubic
| InterpolatorType::LogFiveCubic
| InterpolatorType::LogChebyshev
);
let interleaved = if matches!(
info.interpolator_type,
InterpolatorType::LogBicubic
| InterpolatorType::LogTricubic
| InterpolatorType::LogFourCubic
| InterpolatorType::LogFiveCubic
) {
let built: Vec<Option<InterleavedHermite>> = knot_array
.subgrids
.iter()
.map(|sg| build_interleaved(sg, sg.interpolation_config(), knot_array.pids.len()))
.collect();
if built.iter().all(|o| o.is_some()) {
Some(built.into_iter().map(|o| o.unwrap()).collect())
} else {
None
}
} else {
None
};
let chebyshev_fast = if info.interpolator_type == InterpolatorType::LogChebyshev {
let built: Vec<Option<ChebyshevAllPids>> = knot_array
.subgrids
.iter()
.map(|sg| {
build_chebyshev_fast(sg, sg.interpolation_config(), knot_array.pids.len())
})
.collect();
if built.iter().all(|o| o.is_some()) {
Some(built.into_iter().map(|o| o.unwrap()).collect())
} else {
None
}
} else {
None
};
Self {
info,
knot_array,
interpolators,
alphas,
force_positive: None,
use_log,
force_positive_fn: fp_identity,
interleaved,
chebyshev_fast,
}
}
pub fn set_force_positive(&mut self, flag: ForcePositive) {
self.force_positive_fn = match &flag {
ForcePositive::ClipNegative => fp_clip_negative,
ForcePositive::ClipSmall => fp_clip_small,
ForcePositive::NoClipping => fp_identity,
};
self.force_positive = Some(flag);
}
pub fn apply_force_positive(&self, value: f64) -> f64 {
match &self.force_positive {
Some(ForcePositive::ClipNegative) => value.max(0.0),
Some(ForcePositive::ClipSmall) => value.max(1e-10),
Some(ForcePositive::NoClipping) => value,
_ => value,
}
}
fn build_interpolators(
info: &MetaData,
knot_array: &GridArray,
) -> Vec<Vec<Box<dyn DynInterpolator>>> {
knot_array
.subgrids
.iter()
.map(|subgrid| {
(0..knot_array.pids.len())
.map(|pid_idx| {
InterpolatorFactory::create(
info.interpolator_type.clone(),
subgrid,
pid_idx,
)
})
.collect()
})
.collect()
}
pub fn xfxq2(&self, flavor_id: i32, points: &[f64]) -> Result<f64, Error> {
let subgrid_idx = self.knot_array.find_subgrid(points).ok_or_else(|| {
let (x, q2) = self.get_x_q2(points);
Error::SubgridNotFound { x, q2 }
})?;
let pid_idx = match self.knot_array.pid_index(flavor_id) {
Some(idx) => idx,
None => return Ok(0.0),
};
let mut buf = [0.0f64; 8];
for (i, &p) in points.iter().enumerate() {
buf[i] = if self.use_log { p.ln() } else { p };
}
self.interpolators[subgrid_idx][pid_idx]
.interpolate_point(&buf[..points.len()])
.map_err(|e| Error::InterpolationError(e.to_string()))
.map(|result| (self.force_positive_fn)(result))
}
pub(crate) fn xfxq2_fast(&self, flavor_id: i32, points: &[f64]) -> f64 {
let subgrid_idx = match self.knot_array.find_subgrid(points) {
Some(idx) => idx,
None => return 0.0,
};
let pid_idx = match self.knot_array.pid_index(flavor_id) {
Some(idx) => idx,
None => return 0.0,
};
if let Some(ref il) = self.interleaved {
if let Some(val) = il[subgrid_idx].eval_single_fast(pid_idx, points) {
return (self.force_positive_fn)(val);
}
}
let mut buf = [0.0f64; 8];
for (i, &p) in points.iter().enumerate() {
buf[i] = if self.use_log { p.ln() } else { p };
}
match self.interpolators[subgrid_idx][pid_idx].interpolate_point(&buf[..points.len()]) {
Ok(result) => (self.force_positive_fn)(result),
Err(e) => panic!("InterpolationError: {e}"),
}
}
pub(crate) fn xfxq2_allpids(&self, pids: &[i32], points: &[f64], out: &mut [f64]) {
let subgrid_idx = match self.knot_array.find_subgrid(points) {
Some(idx) => idx,
None => {
out.iter_mut().for_each(|v| *v = 0.0);
return;
}
};
if let Some(ref il) = self.interleaved {
let il = &il[subgrid_idx];
let loc = match il.locate(points) {
Some(l) => l,
None => {
out.iter_mut().for_each(|v| *v = 0.0);
return;
}
};
let mut pid_slots: [Option<usize>; 32] = [None; 32];
for (i, &pid) in pids.iter().enumerate().take(32) {
pid_slots[i] = self.knot_array.pid_index(pid);
}
il.eval_allpids(&loc, &pid_slots[..pids.len()], self.force_positive_fn, out);
return;
}
if let Some(ref cf) = self.chebyshev_fast {
let cf = &cf[subgrid_idx];
let mut buf = [0.0f64; 8];
for (i, &p) in points.iter().enumerate() {
buf[i] = if self.use_log { p.ln() } else { p };
}
let log_points = &buf[..points.len()];
let mut pid_slots: [Option<usize>; 32] = [None; 32];
for (i, &pid) in pids.iter().enumerate().take(32) {
pid_slots[i] = self.knot_array.pid_index(pid);
}
let loc = cf.locate(log_points);
cf.eval_allpids(&loc, &pid_slots[..pids.len()], self.force_positive_fn, out);
return;
}
let mut buf = [0.0f64; 8];
for (i, &p) in points.iter().enumerate() {
buf[i] = if self.use_log { p.ln() } else { p };
}
let log_points = &buf[..points.len()];
for (o, &pid) in out.iter_mut().zip(pids.iter()) {
*o = match self.knot_array.pid_index(pid) {
Some(pid_idx) => {
match self.interpolators[subgrid_idx][pid_idx].interpolate_point(log_points) {
Ok(result) => (self.force_positive_fn)(result),
Err(e) => panic!("InterpolationError: {e}"),
}
}
None => 0.0,
};
}
}
pub(crate) fn xfxq2_allpids_with_slots(
&self,
pid_slots: &[Option<usize>],
points: &[f64],
out: &mut [f64],
) {
let subgrid_idx = match self.knot_array.find_subgrid(points) {
Some(idx) => idx,
None => {
out.iter_mut().for_each(|v| *v = 0.0);
return;
}
};
if let Some(ref il) = self.interleaved {
let il = &il[subgrid_idx];
let loc = match il.locate(points) {
Some(l) => l,
None => {
out.iter_mut().for_each(|v| *v = 0.0);
return;
}
};
il.eval_allpids(&loc, pid_slots, self.force_positive_fn, out);
return;
}
if let Some(ref cf) = self.chebyshev_fast {
let cf = &cf[subgrid_idx];
let mut buf = [0.0f64; 8];
for (i, &p) in points.iter().enumerate() {
buf[i] = if self.use_log { p.ln() } else { p };
}
let log_points = &buf[..points.len()];
let loc = cf.locate(log_points);
cf.eval_allpids(&loc, pid_slots, self.force_positive_fn, out);
return;
}
let mut buf = [0.0f64; 8];
for (i, &p) in points.iter().enumerate() {
buf[i] = if self.use_log { p.ln() } else { p };
}
let log_points = &buf[..points.len()];
for (o, slot) in out.iter_mut().zip(pid_slots.iter()) {
*o = match *slot {
Some(pid_idx) => {
match self.interpolators[subgrid_idx][pid_idx].interpolate_point(log_points) {
Ok(result) => (self.force_positive_fn)(result),
Err(e) => panic!("InterpolationError: {e}"),
}
}
None => 0.0,
};
}
}
pub fn xfxq2s(&self, flavors: Vec<i32>, slice_points: &[&[f64]]) -> Array2<f64> {
let n_pids = flavors.len();
let n_points = slice_points.len();
let pid_slots: Vec<Option<usize>> = flavors
.iter()
.map(|&pid| self.knot_array.pid_index(pid))
.collect();
let mut results = Array2::<f64>::zeros((n_pids, n_points));
let mut out_buf = vec![0.0; n_pids];
for (j, point) in slice_points.iter().enumerate() {
self.xfxq2_allpids_with_slots(&pid_slots, point, &mut out_buf);
for i in 0..n_pids {
results[[i, j]] = out_buf[i];
}
}
results
}
pub fn xfxq2_cheby_batch(&self, flavor_id: i32, points: &[&[f64]]) -> Result<Vec<f64>, Error> {
if points.is_empty() {
return Ok(Vec::new());
}
let pid_idx = match self.knot_array.pid_index(flavor_id) {
Some(idx) => idx,
None => return Ok(vec![0.0; points.len()]),
};
if !matches!(self.info.interpolator_type, InterpolatorType::LogChebyshev) {
return Err(Error::InterpolationError(
"xfxq2_cheby_batch only supports LogChebyshev interpolator".to_string(),
));
}
let mut subgrid_groups: HashMap<usize, Vec<(usize, &[f64])>> = HashMap::new();
for (i, point) in points.iter().enumerate() {
let subgrid_idx = self.knot_array.find_subgrid(point).ok_or_else(|| {
let (x, q2) = self.get_x_q2(point);
Error::SubgridNotFound { x, q2 }
})?;
subgrid_groups
.entry(subgrid_idx)
.or_default()
.push((i, *point));
}
let mut all_results: Vec<(usize, f64)> = Vec::new();
for (subgrid_idx, group) in subgrid_groups {
let subgrid = &self.knot_array.subgrids[subgrid_idx];
let (indices, group_points): (Vec<_>, Vec<_>) = group.into_iter().unzip();
let log_points: Vec<Vec<f64>> = group_points
.iter()
.map(|p| p.iter().map(|&v| v.ln()).collect::<Vec<f64>>())
.collect();
let batch_interpolator =
InterpolatorFactory::create_batch_interpolator(subgrid, pid_idx)
.map_err(Error::InterpolationError)?;
let results = batch_interpolator
.interpolate(log_points)
.map_err(|e| Error::InterpolationError(e.to_string()))?;
for (original_index, result) in indices.into_iter().zip(results) {
all_results.push((original_index, result));
}
}
all_results.sort_by_key(|&(i, _)| i);
let final_results = all_results
.into_iter()
.map(|(_, r)| self.apply_force_positive(r))
.collect();
Ok(final_results)
}
pub fn get_x_q2(&self, points: &[f64]) -> (f64, f64) {
match points {
[.., x, q2] => (*x, *q2),
_ => panic!("The inputs must at least be x and Q2."),
}
}
pub fn alphas_q2(&self, q2: f64) -> f64 {
self.alphas.alphas_q2(q2)
}
pub fn metadata(&self) -> &MetaData {
&self.info
}
pub fn param_ranges(&self) -> RangeParameters {
self.knot_array.global_ranges()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::InterpolatorType;
#[test]
fn test_pid_lookup() {
let pids = Array1::from(vec![21, 1, 2, -1, -2, 22]);
let lut = PidLookup::build(&pids);
assert_eq!(lut.get(0), Some(0));
assert_eq!(lut.get(21), Some(0));
assert_eq!(lut.get(1), Some(1));
assert_eq!(lut.get(-1), Some(3));
assert_eq!(lut.get(22), Some(5));
assert_eq!(lut.get(100), None);
}
#[test]
fn test_grid_array_creation() {
let subgrid_data = vec![SubgridData {
nucleons: vec![1.0],
alphas: vec![0.118],
kts: vec![0.0],
xis: vec![0.0],
deltas: vec![0.0],
xs: vec![1.0, 2.0, 3.0],
q2s: vec![4.0, 5.0],
grid_data: vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
],
}];
let flavors = vec![21, 22];
let grid_array = GridArray::new(subgrid_data, flavors);
match &grid_array.subgrids[0].grid {
crate::subgrid::GridData::Grid6D(grid) => {
assert_eq!(grid.shape(), &[1, 1, 2, 1, 3, 2]);
}
_ => std::panic!("Expected 6D grid"),
}
assert!(grid_array.find_subgrid(&[1.5, 4.5]).is_some());
assert_eq!(grid_array.pid_index(22), Some(1));
}
#[test]
fn test_grid_pdf_interpolation() {
let meta = MetaData {
set_desc: "Test".into(),
set_index: 0,
num_members: 1,
x_min: 0.1,
x_max: 1.0,
q_min: 1.0,
q_max: 10.0,
flavors: vec![21],
format: "test".into(),
alphas_q_values: vec![],
alphas_vals: vec![],
polarised: false,
set_type: crate::metadata::SetType::SpaceLike,
interpolator_type: InterpolatorType::Bilinear,
error_type: "test".into(),
hadron_pid: 2212,
git_version: "".into(),
code_version: "".into(),
flavor_scheme: "".into(),
order_qcd: 0,
alphas_order_qcd: 0,
m_w: 0.0,
m_z: 91.18,
m_up: 0.0,
m_down: 0.0,
m_strange: 0.0,
m_charm: 1.5,
m_bottom: 4.5,
m_top: 173.0,
alphas_type: "".into(),
number_flavors: 5,
xi_min: 0.0,
xi_max: 0.0,
delta_min: 0.0,
delta_max: 0.0,
error_conf_level: None,
};
let subgrid_data = vec![SubgridData {
nucleons: vec![1.0],
alphas: vec![0.118],
kts: vec![0.0],
xis: vec![0.0],
deltas: vec![0.0],
xs: vec![0.1, 0.2],
q2s: vec![1.0, 2.0],
grid_data: vec![10.0, 20.0, 30.0, 40.0],
}];
let grid_array = GridArray::new(subgrid_data, vec![21]);
let grid_pdf = GridPDF::new(meta, grid_array);
let res = grid_pdf.xfxq2(21, &[0.15, 1.5]).unwrap();
assert!((res - 25.0).abs() < 1e-12);
let mut out = [0.0];
grid_pdf.xfxq2_allpids(&[21], &[0.15, 1.5], &mut out);
assert!((out[0] - 25.0).abs() < 1e-12);
}
}