1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//! Module containing all traits and supporting structures for grids.
use super::bin::{BinInfo, BinLimits, BinRemapper};
use super::boc::{Channel, Order};
use super::convolutions::Convolution;
use super::pids::PidBasis;
use super::subgrid::{SubgridEnum, SubgridParams};
use ndarray::{Array3, ArrayView3};
use serde::Deserialize;
use std::collections::HashMap;
use std::io::BufRead;
use thiserror::Error;
/// This structure represents a position (`x1`, `x2`, `q2`) in a `Subgrid` together with a
/// corresponding `weight`. The type `W` can either be a `f64` or `()`, which is used when multiple
/// weights should be signaled.
#[derive(Deserialize)]
pub struct Ntuple<W> {
/// Momentum fraction of the first parton.
pub x1: f64,
/// Momentum fraction of the second parton.
pub x2: f64,
/// Squared scale.
pub q2: f64,
/// Weight of this entry.
pub weight: W,
}
/// Error returned when merging two grids fails.
#[derive(Debug, Error)]
pub enum GridError {
/// Returned when failed to read a Grid.
#[error(transparent)]
ReadFailure(bincode::Error),
}
#[derive(Deserialize)]
struct Mmv1;
#[derive(Deserialize)]
struct Mmv2 {
remapper: Option<BinRemapper>,
key_value_db: HashMap<String, String>,
}
#[derive(Deserialize)]
struct Mmv3 {
remapper: Option<BinRemapper>,
key_value_db: HashMap<String, String>,
_subgrid_template: SubgridEnum,
}
#[expect(
clippy::large_enum_variant,
reason = "fixing the warning will break the file format"
)]
#[derive(Deserialize)]
enum MoreMembers {
V1(Mmv1),
V2(Mmv2),
V3(Mmv3),
}
/// Main data structure of `PineAPPL`. This structure contains a `Subgrid` for each `LumiEntry`,
/// bin, and coupling order it was created with.
#[derive(Deserialize)]
pub struct Grid {
subgrids: Array3<SubgridEnum>,
channels: Vec<Channel>,
bin_limits: BinLimits,
orders: Vec<Order>,
_subgrid_params: SubgridParams,
more_members: MoreMembers,
}
impl Grid {
/// Returns all information about the bins in this grid.
#[must_use]
pub const fn bin_info(&self) -> BinInfo<'_> {
BinInfo::new(&self.bin_limits, self.remapper())
}
/// Return the channels for this `Grid`.
#[must_use]
pub fn channels(&self) -> &[Channel] {
&self.channels
}
/// Return a vector containing the type of convolutions performed with this grid.
///
/// # Panics
///
/// Panics if the metadata key--value pairs `convolution_particle_1` and `convolution_type_1`,
/// or `convolution_particle_2` and `convolution_type_2` are not correctly set.
#[must_use]
pub fn convolutions(&self) -> Vec<Convolution> {
self.key_values().map_or_else(
// if there isn't any metadata, we assume two unpolarized proton-PDFs are used
|| vec![Convolution::UnpolPDF(2212), Convolution::UnpolPDF(2212)],
|kv| {
// the current file format only supports exactly two convolutions
(1..=2)
.map(|index| {
// if there are key-value pairs `convolution_particle_1` and
// `convolution_type_1` and the same with a higher index, we convert this
// metadata into `Convolution`
match (
kv.get(&format!("convolution_particle_{index}"))
.map(|s| s.parse::<i32>()),
kv.get(&format!("convolution_type_{index}"))
.map(String::as_str),
) {
(_, Some("None")) => Convolution::None,
(Some(Ok(pid)), Some("UnpolPDF")) => Convolution::UnpolPDF(pid),
(Some(Ok(pid)), Some("PolPDF")) => Convolution::PolPDF(pid),
(Some(Ok(pid)), Some("UnpolFF")) => Convolution::UnpolFF(pid),
(Some(Ok(pid)), Some("PolFF")) => Convolution::PolFF(pid),
(None, None) => {
// if these key-value pairs are missing use the old metadata
match kv
.get(&format!("initial_state_{index}"))
.map(|s| s.parse::<i32>())
{
Some(Ok(pid)) => {
// Addresses: https://github.com/NNPDF/pineappl/issues/334
if self.channels().is_empty() && pid == 2212 {
Convolution::UnpolPDF(2212)
} else {
let condition = !self.channels().iter().all(|entry| {
entry.entry().iter().all(|&(a, b, _)|
match index {
1 => a,
2 => b,
_ => unreachable!()
} == pid
)
});
if condition {
Convolution::UnpolPDF(pid)
} else {
Convolution::None
}
}
}
None => Convolution::UnpolPDF(2212),
Some(Err(err)) => panic!("metadata 'initial_state_{index}' could not be parsed: {err}"),
}
}
(None, Some(_)) => {
panic!("metadata 'convolution_type_{index}' is missing")
}
(Some(_), None) => {
panic!("metadata 'convolution_particle_{index}' is missing")
}
(Some(Ok(_)), Some(type_)) => {
panic!("metadata 'convolution_type_{index} = {type_}' is unknown")
}
(Some(Err(err)), Some(_)) => panic!(
"metadata 'convolution_particle_{index}' could not be parsed: {err}"
),
}
})
.collect()
},
)
}
/// Returns a map with key-value pairs, if there are any stored in this grid.
#[must_use]
pub const fn key_values(&self) -> Option<&HashMap<String, String>> {
match &self.more_members {
MoreMembers::V3(mmv3) => Some(&mmv3.key_value_db),
MoreMembers::V2(mmv2) => Some(&mmv2.key_value_db),
MoreMembers::V1(_) => None,
}
}
/// Returns the subgrid parameters.
#[must_use]
pub fn orders(&self) -> &[Order] {
&self.orders
}
/// Return by which convention the particle IDs are encoded.
#[must_use]
pub fn pid_basis(&self) -> PidBasis {
if let Some(key_values) = self.key_values()
&& let Some(lumi_id_types) = key_values.get("lumi_id_types")
{
match lumi_id_types.as_str() {
"pdg_mc_ids" => return PidBasis::Pdg,
"evol" => return PidBasis::Evol,
_ => unimplemented!("unknown particle ID convention {lumi_id_types}"),
}
}
// if there's no basis explicitly set we're assuming to use PDG IDs
PidBasis::Pdg
}
/// Construct a `Grid` by deserializing it from `reader`.
///
/// # Errors
///
/// If reading from the compressed or uncompressed stream fails an error is returned.
///
/// # Panics
///
/// Panics if the grid version is not `0`.
pub fn read_uncompressed(reader: impl BufRead) -> Result<Self, GridError> {
bincode::deserialize_from(reader).map_err(GridError::ReadFailure)
}
/// Return the currently set remapper, if there is any.
#[must_use]
pub const fn remapper(&self) -> Option<&BinRemapper> {
match &self.more_members {
MoreMembers::V1(_) => None,
MoreMembers::V2(mmv2) => mmv2.remapper.as_ref(),
MoreMembers::V3(mmv3) => mmv3.remapper.as_ref(),
}
}
/// Return all subgrids as an `ArrayView3`.
#[must_use]
pub fn subgrids(&self) -> ArrayView3<'_, SubgridEnum> {
self.subgrids.view()
}
}