stripper_xml/
event.rs

1use particle_id::ParticleID;
2use serde::{Deserialize, Serialize, Serializer};
3use serde_repr::*;
4use strum::EnumString;
5use thiserror::Error;
6
7#[derive(Deserialize, Serialize)]
8#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
9pub struct Eventrecord {
10    #[serde(rename = "@nevents")]
11    pub nevents: u64,
12    #[serde(rename = "@nsubevents")]
13    pub nsubevents: u64,
14    #[serde(rename = "@nreweights")]
15    pub nreweights: u64,
16    #[serde(rename = "@as")]
17    pub alpha_s_power: u64,
18    #[serde(rename = "@name")]
19    pub name: String,
20    #[serde(rename = "e")]
21    pub events: Vec<Event>,
22}
23
24#[derive(Deserialize, Serialize)]
25#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
26#[serde(rename = "e")]
27pub struct Event {
28    #[serde(rename = "se")]
29    pub subevents: Vec<SubEvent>,
30}
31
32#[derive(Deserialize, Serialize)]
33#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
34#[serde(rename = "se")]
35pub struct SubEvent {
36    #[serde(rename = "@w")]
37    pub weight: f64,
38    #[serde(rename = "@muR")]
39    pub mu_r: f64,
40    #[serde(rename = "@muF")]
41    pub mu_f: f64,
42    #[serde(rename = "p")]
43    pub particles: Vec<Particle>,
44    #[serde(rename = "rw")]
45    pub reweight: Vec<Reweight>
46}
47
48#[derive(Deserialize, Serialize)]
49#[derive(Clone, Debug, PartialEq, PartialOrd)]
50#[serde(rename = "p")]
51pub struct Particle {
52    #[serde(rename = "@id")]
53    pub id: Id,
54    #[serde(rename = "$text")]
55    pub momentum: Momentum,
56}
57
58#[derive(Deserialize, Serialize)]
59#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
60#[serde(rename = "rw")]
61pub struct Reweight {
62    #[serde(rename = "@ch")]
63    pub channel: u32,
64    #[serde(rename = "$text")]
65    pub reweights: Reweights,
66}
67
68#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
69pub struct Reweights {
70    pub x1: f64,
71    pub x2: f64,
72    pub log_coeff: Vec<f64>,
73}
74
75#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
76pub struct Id {
77    pub status: Status,
78    pub pdg_id: ParticleID,
79}
80
81#[derive(Deserialize_repr, Serialize_repr)]
82#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
83#[derive(EnumString)]
84#[repr(u8)]
85pub enum Status {
86    #[strum(serialize = "0")]
87    Outgoing = 0,
88    #[strum(serialize = "1")]
89    Incoming = 1,
90}
91
92#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
93pub struct Momentum (pub [f64; 4]);
94
95impl<'de> Deserialize<'de> for Momentum {
96    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97    where
98        D: serde::Deserializer<'de> {
99        let momentum_str = String::deserialize(deserializer)?;
100        let mut entries = momentum_str.split(',');
101        let mut momentum = [0.; 4];
102        for q in &mut momentum {
103            let Some(p) = entries.next() else {
104                return Err(serde::de::Error::custom(
105                    ParseErr::NumEntries(momentum_str, 4)
106                ));
107            };
108            *q = p.parse().map_err(serde::de::Error::custom)?;
109        }
110        if entries.next().is_some() {
111            return Err(serde::de::Error::custom(
112                ParseErr::NumEntries(momentum_str, 4)
113            ));
114        }
115        Ok(Self(momentum))
116    }
117}
118impl Serialize for Momentum {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: Serializer,
122    {
123        let p = self.0;
124        serializer.serialize_str(
125            &format!("{},{},{},{}", p[0], p[1], p[2], p[3])
126        )
127    }
128}
129
130impl<'de> Deserialize<'de> for Reweights {
131    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132    where
133        D: serde::Deserializer<'de> {
134        use ParseErr::NumEntries;
135        use serde::de::Error;
136        let reweights_str = String::deserialize(deserializer)?;
137        let mut reweights = reweights_str.split(',');
138        let Some(x1) = reweights.next() else {
139            return Err(Error::custom(
140                NumEntries(reweights_str, 2)
141            ));
142        };
143        let x1 = x1.parse().map_err(Error::custom)?;
144        let Some(x2) = reweights.next() else {
145            return Err(Error::custom(
146                NumEntries(reweights_str, 2)
147            ));
148        };
149        let x2 = x2.parse().map_err(Error::custom)?;
150        let mut log_coeff = Vec::new();
151        for val in reweights {
152            log_coeff.push(val.parse().map_err(Error::custom)?);
153        }
154        Ok(Self {
155            x1,
156            x2,
157            log_coeff,
158        })
159    }
160}
161
162impl Serialize for Reweights {
163    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164    where
165        S: Serializer,
166    {
167        let mut res = format!("{},{}", self.x1, self.x2);
168        for log_coeff in &self.log_coeff {
169            res.push(',');
170            res += &log_coeff.to_string();
171        }
172        serializer.serialize_str(&res)
173    }
174}
175
176impl<'de> Deserialize<'de> for Id {
177    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
178    where
179        D: serde::Deserializer<'de> {
180        let id_str = String::deserialize(deserializer)?;
181        let mut entries = id_str.split(',');
182        let Some(status) = entries.next() else {
183            return Err(serde::de::Error::custom(
184                ParseErr::NumEntries(id_str, 2)
185            ));
186        };
187        let status = status.parse().map_err(
188            serde::de::Error::custom
189        )?;
190
191        let Some(pdg_id) = entries.next() else {
192            return Err(serde::de::Error::custom(
193                ParseErr::NumEntries(id_str, 2)
194            ));
195        };
196        let pdg_id: i32 = pdg_id.parse().map_err(
197            serde::de::Error::custom
198        )?;
199        let pdg_id = ParticleID::new(pdg_id);
200
201        if entries.next().is_some() {
202            return Err(serde::de::Error::custom(
203                ParseErr::NumEntries(id_str, 2)
204            ));
205        }
206
207        Ok(Self{status, pdg_id})
208    }
209}
210impl Serialize for Id {
211    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
212    where
213        S: Serializer,
214    {
215        serializer.serialize_str(
216            &format!("{},{}", self.status as u8, self.pdg_id.id())
217        )
218    }
219}
220
221// serialize as XML
222//
223// we could use serde or quick_xml::se::Serializer, but we want custom spaces
224pub trait WriteXML {
225    type Error;
226
227    fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Self::Error>;
228}
229
230impl WriteXML for Eventrecord {
231    type Error = std::io::Error;
232
233    fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Self::Error> {
234        writeln!(
235            writer,
236            "<Eventrecord nevents=\"{}\" nsubevents=\"{}\" nreweights=\"{}\" as=\"{}\" name=\"{}\">",
237            self.nevents, self.nsubevents, self.nreweights, self.alpha_s_power, self.name
238        )?;
239        writeln!(
240            writer,
241            "<!--\nRecord generated with {} {}\n-->",
242            env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
243        )?;
244        for event in &self.events {
245            event.write(writer)?;
246        }
247        writer.write_all(b"</Eventrecord>\n")
248    }
249}
250
251impl WriteXML for Event {
252    type Error = std::io::Error;
253
254    fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Self::Error> {
255        writer.write_all(b"<e>\n")?;
256        for subevent in &self.subevents {
257            subevent.write(writer)?;
258        }
259        writer.write_all(b"</e>\n")
260    }
261}
262
263impl WriteXML for SubEvent {
264    type Error = std::io::Error;
265
266    fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Self::Error> {
267        use std::fmt::Write;
268
269        writeln!(
270            writer,
271            "<se w=\"{}\" muR=\"{}\" muF=\"{}\">",
272            self.weight, self.mu_r, self.mu_f
273        )?;
274        let mut out = String::new();
275        for p in &self.particles {
276            let ser = quick_xml::se::Serializer::new(&mut out);
277            p.serialize(ser).unwrap();
278            out.write_char('\n').unwrap();
279        }
280        for rw in &self.reweight {
281            let ser = quick_xml::se::Serializer::new(&mut out);
282            rw.serialize(ser).unwrap();
283            out.write_char('\n').unwrap();
284        }
285        writer.write_all(out.as_bytes())?;
286        writer.write_all(b"</se>\n")
287    }
288}
289
290
291#[derive(Debug, Error)]
292pub enum ParseErr {
293    #[error("'{0}' is not a comma-separated list with {1} float values")]
294    NumEntries(String, usize)
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn deser_subevent() {
303        let txt = r#"<se w="-0.0002369763508" muR="91.16253934" muF="91.16253934">
304<p id="1,21"> 5780.608219,0,0,5780.608219 </p>
305<p id="1,21"> 334.3891359,0,0,-334.3891359 </p>
306<p id="0,6"> 357.9061187,-58.25473457,9.621341818,-307.9843429 </p>
307<p id="0,-6"> 5757.091237,58.25473457,-9.621341818,5754.203426 </p>
308<rw ch="12"> 0.8893243414,0.05144448245,-0.0002369763508 </rw>
309</se>"#;
310        use Status::*;
311        use particle_id::sm_elementary_particles::*;
312        let ref_event = SubEvent {
313            weight: -0.0002369763508,
314            mu_r: 91.16253934,
315            mu_f: 91.16253934,
316            particles: vec![
317                Particle{
318                    id: Id{
319                        status: Incoming,
320                        pdg_id: gluon,
321                    },
322                    momentum: Momentum([5780.608219,0.,0.,5780.608219])
323                },
324                Particle{
325                    id: Id{
326                        status: Incoming,
327                        pdg_id: gluon,
328                    },
329                    momentum: Momentum([334.3891359,0.,0.,-334.3891359])
330                },
331                Particle{
332                    id: Id{
333                        status: Outgoing,
334                        pdg_id: top,
335                    },
336                    momentum: Momentum([357.9061187,-58.25473457,9.621341818,-307.9843429])
337                },
338                Particle{
339                    id: Id{
340                        status: Outgoing,
341                        pdg_id: anti_top,
342                    },
343                    momentum: Momentum([5757.091237,58.25473457,-9.621341818,5754.203426])
344                }
345            ],
346            reweight: vec![ Reweight {
347                channel: 12,
348                reweights: Reweights{
349                    x1: 0.8893243414,
350                    x2: 0.05144448245,
351                    log_coeff: vec![-0.0002369763508]
352                }
353            }],
354        };
355        let event: SubEvent = quick_xml::de::from_str(txt).unwrap();
356        assert_eq!(event, ref_event);
357    }
358
359    const REF_RECORD: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
360<Eventrecord nevents="2286" nsubevents="2286" nreweights="2286" as="2" name="Bm">
361<!--
362File generated with STRIPPER v0.1 for online data base
363-->
364<e>
365<se w="-0.0002369763508" muR="91.16253934" muF="91.16253934">
366<p id="1,21"> 5780.608219,0,0,5780.608219 </p>
367<p id="1,21"> 334.3891359,0,0,-334.3891359 </p>
368<p id="0,6"> 357.9061187,-58.25473457,9.621341818,-307.9843429 </p>
369<p id="0,-6"> 5757.091237,58.25473457,-9.621341818,5754.203426 </p>
370<rw ch="12"> 0.8893243414,0.05144448245,-0.0002369763508 </rw>
371</se>
372</e>
373<e>
374<se w="-0.0004385904665" muR="517.0997809" muF="517.0997809">
375<p id="1,1"> 327.0442813,0,0,327.0442813 </p>
376<p id="1,-1"> 4344.52245,0,0,-4344.52245 </p>
377<p id="0,6"> 3334.580936,-386.757619,943.5205498,-3170.151619 </p>
378<p id="0,-6"> 1336.985795,386.757619,-943.5205498,-847.3265495 </p>
379<rw ch="1"> 0.05031450481,0.6683880692,-0.0004385904665 </rw>
380</se>
381</e>
382<e>
383<se w="-2.098171554e-05" muR="1140.717994" muF="1140.717994">
384<p id="1,1"> 1610.067985,0,0,1610.067985 </p>
385<p id="1,-1"> 4034.720799,0,0,-4034.720799 </p>
386<p id="0,6"> 3362.889308,-2194.656814,598.8951393,-2470.642493 </p>
387<p id="0,-6"> 2281.899476,2194.656814,-598.8951393,45.98967904 </p>
388<rw ch="1"> 0.2477027669,0.6207262768,-2.098171554e-05 </rw>
389</se>
390</e>
391<e>
392<se w="-4.834595231e-05" muR="635.8532498" muF="635.8532498">
393<p id="1,1"> 1299.986308,0,0,1299.986308 </p>
394<p id="1,-1"> 3291.996472,0,0,-3291.996472 </p>
395<p id="0,6"> 1510.408466,-1129.902831,557.4950784,814.9210479 </p>
396<p id="0,-6"> 3081.574313,1129.902831,-557.4950784,-2806.931212 </p>
397<rw ch="1"> 0.1999978935,0.5064609956,-4.834595231e-05 </rw>
398</se>
399</e>
400</Eventrecord>
401"#;
402    #[test]
403    fn deser_file() {
404
405        let record: Eventrecord = quick_xml::de::from_str(REF_RECORD).unwrap();
406        assert_eq!(record.nevents, 2286);
407        assert_eq!(record.nsubevents, 2286);
408        assert_eq!(record.nreweights, 2286);
409        assert_eq!(record.alpha_s_power, 2);
410        assert_eq!(record.name, "Bm");
411        assert_eq!(record.events.len(), 4);
412    }
413
414    #[test]
415    fn ser_file() {
416
417        let record: Eventrecord = quick_xml::de::from_str(REF_RECORD).unwrap();
418        let mut tmp = br#"<?xml version="1.0" encoding="UTF-8"?>
419"#.to_vec();
420        record.write(&mut tmp).unwrap();
421        let record_2: Eventrecord = quick_xml::de::from_reader(tmp.as_slice()).unwrap();
422        assert_eq!(record, record_2);
423    }
424}