Skip to main content

gondola_core/events/
tof_event.rs

1//! Basic event structure for all TOF systems
2// This file is part of gaps-online-software and published 
3// under the GPLv3 license
4
5use crate::prelude::*;
6
7#[cfg(feature="database")]
8use std::f32::consts::PI;
9use std::cmp::Ordering;
10
11/// Main event class for the TOF. This will be sent over telemetry and be 
12/// written to disk
13///
14/// CHANGELOG: v0.11 (gondola-core): Merges TofEvent, TofEventSummary and 
15/// MasterTriggerEvent all into a single TofEvent, based on former TofEventSummary.
16/// The new TofEvent has the ability to cary RBEvents and thus mimick the "old" TofEvent
17/// We are using the version flag to indicate:
18/// * ProtocolVersion::Unknown - The "old" TofEventSummary. (now "TofEvent"). No extra 
19///   variables for the GCU, no RBEvents
20/// * ProtocolVersion::V1      - The version crafted for Antarctica '24/'25 containing 
21///   a bunch of summary variables for the GCU (e.g. nhits(umbrella)). This version will 
22///   add these variables to the bytestream and also if ProtocolVersion::V1 is read out 
23///   from the bytestream, the variables are expected to be in it. This is to keep compatibility
24///   with the gcu
25/// * ProtocolVersion::V2     - v0.11 (gondola-core) version of TofEvent(Summary).
26///   This version will not write out GCU variables and does not expect them to be in the 
27///   bytestream. If desired, this version can read/write RBEvents. 
28/// * ProtocolVersion::V3     - the "latest and greatest". This version has gcuvariables 
29///                             AND rbevents. RBEvents can be stripped off later on.
30#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature="pybindings", pyclass)]
32pub struct TofEvent {
33  pub status            : EventStatus,
34  /// The version of this event. Prior to tof-dataclasses v0.11,
35  /// the default was `ProtocolVersion::Unknown`. 
36  /// Then for the Antarctic campaign we added more variables, 
37  /// so that the gcu doesn't have to compute them. These variables
38  /// will be included in the bytestream for `ProtocolVersion::V1`.
39  /// However, we don't want to write them to disk in the new (gondola-core > v0.11)
40  /// verion, so we will use ProtocolVersionV2 for the version to be
41  /// written to disk. ProtocolVersionV2 can (but not must) have RBEvents
42  pub version           : ProtocolVersion,
43  pub quality           : EventQuality,
44  pub trigger_sources   : u16,
45
46  /// the number of triggered paddles coming
47  /// from the MTB directly. This might NOT be
48  /// the same as the number of hits!
49  pub n_trigger_paddles  : u8,
50  pub event_id           : u32,
51  pub run_id             : u16,
52  pub timestamp32        : u32,
53  pub timestamp16        : u16,
54  /// scalar number of hits missed in
55  /// this event due to DRS on the RB
56  /// being busy
57  pub drs_dead_lost_hits : u16, 
58  pub dsi_j_mask         : u32,
59  pub channel_mask       : Vec<u16>,
60  pub mtb_link_mask      : u64,
61  pub hits               : Vec<TofHit>,
62  // a number of variables which are directly 
63  // read out from the MTB packet and won't get 
64  // serialized in this form but then used
65  // later to calculate other variables
66  pub mt_trigger_sources : u16,
67  pub mt_tiu_gps16       : u16,
68  pub mt_tiu_gps32       : u32, 
69  pub mt_timestamp       : u32,
70  pub mt_tiu_timestamp   : u32,
71  // a bunch of calculated variablels, used 
72  // for online interesting event search
73  // these will be only available in ProtocolVersion 1
74  pub n_hits_umb         : u8,
75  pub n_hits_cbe         : u8,
76  pub n_hits_cor         : u8,
77  pub tot_edep_umb       : f32,
78  pub tot_edep_cbe       : f32,
79  pub tot_edep_cor       : f32,
80  pub paddles_set        : bool,
81  // this is new (v0.11) -> We are expanding TofEvent(Summary) 
82  // so that it is the same as TofEvent in v0.10 and is able 
83  // to carry waveforms. These then can be stripped off
84  pub rb_events          : Vec<RBEvent>,
85  /// Start time for a time to wait for incoming RBEvents
86  pub creation_time      : Instant,
87  pub write_to_disk      : bool, 
88}
89
90impl TofEvent {
91
92  pub fn new() -> Self {
93    Self {
94      status             : EventStatus::Unknown,
95      version            : ProtocolVersion::Unknown,
96      n_hits_umb         : 0,
97      n_hits_cbe         : 0,
98      n_hits_cor         : 0,
99      tot_edep_umb       : 0.0,
100      tot_edep_cbe       : 0.0,
101      tot_edep_cor       : 0.0,
102      quality            : EventQuality::Unknown,
103      trigger_sources    : 0,
104      n_trigger_paddles  : 0,
105      event_id           : 0,
106      run_id             : 0,
107      timestamp32        : 0,
108      timestamp16        : 0,
109      drs_dead_lost_hits : 0,
110      dsi_j_mask         : 0,
111      channel_mask       : Vec::<u16>::new(),
112      mtb_link_mask      : 0,
113      hits               : Vec::<TofHit>::new(),
114      mt_trigger_sources : 0,
115      mt_tiu_gps16       : 0,
116      mt_tiu_gps32       : 0, 
117      mt_timestamp       : 0,
118      mt_tiu_timestamp   : 0,
119      paddles_set        : false,
120      rb_events          : Vec::<RBEvent>::new(),
121      creation_time      : Instant::now(),
122      write_to_disk      : true,
123    }
124  }
125
126  /// Remove hits from the event as proposed by Elena. 
127  /// This is pretty similar to the non-causal hits, 
128  /// however, a bit stricter
129  pub fn elena_cuts(hit : &TofHit) -> bool {
130    let peak_cut   = 10.0f32;
131    let charge_cut = 5.0f32;
132    let time_sat   = 490.032;
133    
134    let hit_a = hit.time_a.to_f32() > 0.1
135             && hit.time_a.to_f32() < time_sat
136             && hit.peak_a.to_f32() > peak_cut
137             && hit.charge_a.to_f32() > charge_cut;
138    let hit_b = hit.time_b.to_f32() > 0.1
139             && hit.time_b.to_f32() < time_sat
140             && hit.peak_b.to_f32() > peak_cut
141             && hit.charge_b.to_f32() > charge_cut;
142    
143    return hit_a && hit_b;
144  }
145
146  /// Remove the hits from the event which are not passing 
147  /// Elena's cuts 
148  ///
149  /// # Arguments:
150  ///   * no_return : If true, don't return anything, which 
151  ///                 will make the execution time shorter
152  ///
153  pub fn remove_elena_hits(&mut self, no_return : bool) -> Option<Vec<TofHit>> {
154    if !no_return {
155      let mut removed = Vec::<TofHit>::new();
156      for h in &self.hits {
157        if Self::elena_cuts(h) {
158          removed.push(h.clone());
159        }
160      }
161      self.hits.retain(|h| Self::elena_cuts(h));
162      return Some(removed);
163    } else {
164      self.hits.retain(|h| Self::elena_cuts(h));
165      return None; 
166    }
167  }
168
169
170  /// The actual time-of-flight as calculated by the 
171  /// first inner hit time - first outer hit time. 
172  ///
173  /// This is a crude version and does not include 
174  /// any reconstruction, just the bare information 
175  /// as available to the TOF. 
176  ///
177  /// Be careful with using this number for anything else 
178  /// than a crude guess
179  ///
180  /// # Returns:
181  ///   * (tof,beta) for events with at least one inner and outer hit, 
182  ///     None for the others
183  pub fn get_tof(&mut self) -> Option<(f32,f32,f32,f32,f32)> {
184    // in case the hits are already normalized, this should do 
185    // nothing 
186    self.normalize_hit_times();
187    let mut outer_h = Vec::<TofHit>::new();
188    let mut inner_h = Vec::<TofHit>::new();
189    for h in &self.hits {
190      if h.paddle_id < 61 {
191        inner_h.push(*h);
192      } else {
193        outer_h.push(*h);
194      }
195    }
196    if inner_h.len() == 0 || outer_h.len() == 0 {
197      return None;
198    }
199    inner_h .sort_by(|a,b| (a.get_t0_with_local_corrections()).partial_cmp(&&b.get_t0_with_local_corrections()).unwrap_or(Ordering::Greater));
200    outer_h .sort_by(|a,b| (a.get_t0_with_local_corrections()).partial_cmp(&&b.get_t0_with_local_corrections()).unwrap_or(Ordering::Greater));
201    //let tof  = (inner_h[0].t0_fr() - outer_h[0].t0_fr()).abs();
202    let dist     = inner_h[0].distance(&outer_h[0])/1000.0; 
203    // fix wrong phase difference calculation 
204    let t0       = inner_h[0].get_t0_with_local_corrections() - inner_h[0].get_phase_ns() - inner_h[0].timing_offset;
205    let t1       = outer_h[0].get_t0_with_local_corrections() - outer_h[0].get_phase_ns() - outer_h[0].timing_offset;
206    let mut pd   = inner_h[0].get_phase_ns() - outer_h[0].get_phase_ns();
207    if pd > 25.0 {
208      pd -= 50.0;
209    }
210    if pd < -25.0 {
211      pd += 50.0; 
212    }
213    let tof = t0 - t1 + pd;  
214    //let mut tof  = inner_h[0].t0_fr() - outer_h[0].t0_fr();
215    //if tof > 50.0 {
216    //  tof -= 50.0; 
217    //}
218    let beta = dist.abs()/(tof*1e-9)/299792458.0;
219    //let pd   = inner_h[0].get_phase_ns() - outer_h[0].get_phase_ns();
220    let hart_diff = inner_h[0].hart_cable_time - outer_h[0].hart_cable_time;
221    Some((tof, beta, pd, dist, hart_diff))
222  }
223
224  /// Calculate the timestamp from the MTB inlcuding GPS and all
225  ///
226  /// This will be the most precise timestamp in GAPS, based on 
227  /// a 100MHz oscillator and if GPS is active, it will fix itself
228  /// with the GPS 1PPS pulse
229  pub fn get_mt_timestamp_abs(&self) -> u64 {
230    let gps = self.mt_tiu_gps32 as u64;
231    let mut timestamp = self.mt_timestamp as u64;
232    if timestamp < self.mt_tiu_timestamp as u64 {
233      // it has wrapped
234      timestamp += u32::MAX as u64 + 1;
235    }
236    let gps_mult = match 100_000_000u64.checked_mul(gps) {
237    //let gps_mult = match 100_000u64.checked_mul(gps) {
238      Some(result) => result,
239      None => {
240          // Handle overflow case here
241          // Example: log an error, return a default value, etc.
242          0 // Example fallback value
243      }
244    };
245    let ts = gps_mult + (timestamp - self.mt_tiu_timestamp as u64);
246    ts
247  }
248
249  /// Move hits out of RBEvents and in the general 
250  /// hitvector. 
251  ///
252  /// This should be done once an event is complete.
253  /// The RBEvents keep the associated waveforms, but 
254  /// the hits all move into a single vector
255  pub fn move_hits(&mut self) {
256    let mut all_hits = Vec::<TofHit>::with_capacity(5);
257    for rbev in &mut self.rb_events {
258       all_hits.append(&mut rbev.hits);
259    }
260    self.hits = all_hits;
261  }
262
263  /// Remove any RBEvents from the event. 
264  ///
265  /// This will move the hits out of the 
266  /// RBEvents and put them in the hit vector.
267  pub fn strip_rbevents(&mut self) {
268    if self.hits.len() == 0 {
269      self.move_hits();
270    }
271    self.rb_events.clear();
272  }
273
274  /// At the moment, this instance of TofEvent gets 
275  /// created, a timer begins to tick. 
276  ///
277  /// The age of the event is the elapsed time since
278  /// this instance (self) was created in seconds.
279  pub fn age(&self) -> u64 {
280    self.creation_time.elapsed().as_secs()
281  }
282 
283  /// The expectedd RBs participating in this event as 
284  /// infered from the RB link ids coming from the MTB
285  pub fn get_expected_rbs(&self, mapping : &HashMap<u8,u8>) -> Vec<u8> {
286    let mut expected_rbs = Vec::<u8>::new();
287    for k in self.get_rb_link_ids() {
288      match mapping.get(&k) {
289        None => {
290          error!("Seeing unassociated link id {k}");
291        }
292        Some(rb_id) => {
293          expected_rbs.push(*rb_id);
294        }
295      }
296    }
297    expected_rbs 
298  }
299
300  /// Simple check if the event contains as much RBEvents 
301  /// as expected from the provided boards masks by the MTB
302  pub fn is_complete(&mut self, exclude_rbs : Option<(&Vec<u8>,&DsiJChRbMapping)>) -> bool {
303    if exclude_rbs.is_none() {
304      return self.get_rb_link_ids().len() == self.rb_events.len();
305    } else {
306      let dead_rbs = exclude_rbs.unwrap();
307      let mut n_known_dead = 0usize;
308      let t_hits = self.get_trigger_hits();
309      for h in t_hits {
310        match dead_rbs.1.get(&h.0) {
311          None => {
312            continue;
313          }
314          Some(dsi) => {
315            match dsi.get(&h.1) {
316              None => {
317                continue;
318              }
319              Some(j) => {
320                match j.get(&h.2.0) {
321                  None => {
322                    continue;
323                  }
324                  Some(rb) => {
325                    if dead_rbs.0.contains(&rb) {
326                      n_known_dead += 1
327                    }
328                  }
329                }
330              }
331            }
332          }
333        }
334      } // end loop over trigger hits
335        //  we use <= here, because sometimes, 
336        //  the link ids are wrong, so n_known_dead 
337        //  might be false positive
338      let n_rb_link_ids = self.get_rb_link_ids().len();
339      //if n_rb_link_ids <= self.rb_events.len() + n_known_dead {
340      //  self.status = EventStatus::KnownDeadRB; 
341      //}
342      return n_rb_link_ids <= self.rb_events.len() + n_known_dead;
343    }
344  }
345  
346  /// The number of hits we did not get 
347  /// becaue of the DRS busy
348  pub fn get_lost_hits(&self) -> u16 {
349    let mut lost_hits = 0u16;
350    for rbev in &self.rb_events {
351      if rbev.header.drs_lost_trigger() {
352        let mut nhits = rbev.header.get_nchan() as u16;
353        // FIXME - I don't understand this - that would only work if the RB 
354        // sees 2 channels, that is 1 hit (?) Potential bug
355        if nhits > 0 {
356          nhits -= 1;
357        }
358        lost_hits += nhits;
359      }
360    }
361    lost_hits
362  }
363
364
365  /// Calculate extra variables for the GCU, 
366  /// set the protocol version to V1 and 
367  /// strip the waveforms if desired
368  /// 
369  /// # Arguments:
370  ///   * strip_rbevents : remove the rbevents from the TofEvent so they
371  ///                      won't bother the poor gcu
372  pub fn prepare_for_gcu(&mut self, strip_rbevents : bool) {
373    if strip_rbevents {
374      self.strip_rbevents();
375    }
376    self.version = ProtocolVersion::V1;
377    if self.n_hits_cbe == 0 && self.n_hits_umb == 0 && self.n_hits_cor == 0 {
378      self.calc_gcu_variables();
379    }
380  }
381 
382  /// Calculate the TOF part of the interesting events mechanism, whcih is
383  /// NHIT (CBE, COR, UMB) and EDEP (CBE, COR, UMB)
384  pub fn calc_gcu_variables(&mut self) {
385    if self.hits.len() == 0 {
386      for rbev in &self.rb_events {
387        for h in &rbev.hits {
388          if h.paddle_id <= 60 {
389            self.n_hits_cbe += 1;
390            self.tot_edep_cbe += h.get_edep();
391          }
392          else if h.paddle_id <= 108 && h.paddle_id > 60 {
393            self.n_hits_umb += 1;
394            self.tot_edep_umb += h.get_edep();
395          }
396          else {
397            self.n_hits_cor += 1;
398            self.tot_edep_cor += h.get_edep();
399          }
400        }
401      }
402    } else { 
403      for h in &self.hits {
404        if h.paddle_id <= 60 {
405          self.n_hits_cbe += 1;
406          self.tot_edep_cbe += h.get_edep();
407        }
408        else if h.paddle_id <= 108 && h.paddle_id > 60 {
409          self.n_hits_umb += 1;
410          self.tot_edep_umb += h.get_edep();
411        }
412        else {
413          self.n_hits_cor += 1;
414          self.tot_edep_cor += h.get_edep();
415        }
416      }
417    }
418  }
419
420  /// Ensure compatibility with older data, which 
421  /// contained a different type of TofEvent
422  pub fn decode_depr_tofevent_size_header(mask : &u32) 
423    -> (usize, usize) {
424    let rb_event_len = (mask & 0xFF)        as usize;
425    let miss_len     = ((mask & 0xFF00)     >> 8)  as usize;
426    (rb_event_len, miss_len)
427  }
428
429  /// Set timing offsets on the hit times of the events' hits,
430  /// which absorb any systematic besides the phase & cable length.
431  ///
432  /// This will NOT yet subtract these numbers. To 
433  /// do so, the hit times have to be "normalized" by 
434  /// calling ev.normalize_hit_times 
435  ///
436  /// # Arguments:
437  ///   * offsets : a hashmap paddle id -> timing offset
438  #[cfg(feature="database")]
439  pub fn set_timing_offsets(&mut self, offsets : &HashMap<u8, f32>) {
440    for h in self.hits.iter_mut() {
441      if offsets.contains_key(&h.paddle_id) {
442        h.timing_offset = offsets[&h.paddle_id]; 
443      }
444    }
445  }
446
447  /// Remove hits from the hitseries which can not 
448  /// be caused by the same particle, which means 
449  /// that for these two specific hits beta with 
450  /// respect to the first hit in the event is 
451  /// larger than one
452  /// That this works, first hits need to be 
453  /// "normalized" by calling normalize_hit_times
454  ///
455  /// # Return:
456  ///
457  ///   * removed paddle ids, twindows
458  pub fn lightspeed_cleaning(&mut self, t_err : f32) -> (Vec<u8>, Vec<f32>) {
459    // first sort the hits in time
460    if self.hits.len() == 0 {
461      return (Vec::<u8>::new(), Vec::<f32>::new());
462    }
463    let mut twindows = Vec::<f32>::new();
464    self.hits.sort_by(|a,b| (a.event_t0).partial_cmp(&b.event_t0).unwrap_or(Ordering::Greater));
465    let first_hit = self.hits[0].clone(); // the clone here is a bit unfortunate, 
466                                           // this can be done better with walking 
467                                           // over the list and updating references
468    let mut clean_hits = Vec::<TofHit>::new(); 
469    let mut rm_hits    = Vec::<u8>::new();
470    // per definition, we can't remove the first hit, ever
471    clean_hits.push(first_hit.clone());
472    //error!("-----");
473    let mut prior_hit = first_hit;
474    //println!("TO FIRST {}",first_hit.event_t0);
475    for h in self.hits.iter().skip(1) {
476      let min_tdiff_cvac = 1e9*1e-3*prior_hit.distance(h)/299792458.0;
477      let twindow            = prior_hit.event_t0 + min_tdiff_cvac;
478
479      // FIXME - implement different strategies
480      // this is the "default" strategy
481      //if h.event_t0 < twindow {
482      //  rm_hits.push(h.paddle_id);
483      //  twindows.push(twindow);
484      //  continue;
485      //}
486      // this is the "lenient" strategy
487      if h.event_t0 + 2.0*t_err < twindow {
488        rm_hits.push(h.paddle_id);
489        twindows.push(twindow);
490        continue;
491      }
492      // this is the "aggressive" strategy
493      //if h.event_t0 - 2.0*t_err < twindow {
494      //  rm_hits.push(h.paddle_id);
495      //  continue;
496      //}
497      
498      // should we remove negative beta hits?
499      //if beta < 0.0 {
500      //  rm_hits.push(h.paddle_id);
501      //  continue;
502      //}
503      // update the prior hit only 
504      // when it is a good one. If it 
505      // is bad we continue to compare
506      // to the first hit
507      prior_hit = h.clone();
508      clean_hits.push(*h);
509    }
510    self.hits = clean_hits;
511    (rm_hits, twindows)
512  }
513
514
515  /// Non causal hits have hit times in ends A and be which 
516  /// are not compatible with the speed of light in the paddle, 
517  /// that is, the hit gets registered too early. If we look 
518  /// at a plot of the reconstructed position, these hits would 
519  /// correspond to positions outside of the paddle.
520  ///
521  /// #Returns:
522  ///   A vector of paddle ids with removed hits
523  ///
524  pub fn remove_non_causal_hits(&mut self) -> Vec<u8> {
525    let mut clean_hits = Vec::<TofHit>::new();
526    let mut removed_pids = Vec::<u8>::new();
527    for h in &self.hits {
528      if h.obeys_causality() {
529        clean_hits.push(*h);
530      } else {
531        removed_pids.push(h.paddle_id);
532      }
533    }
534    self.hits = clean_hits;
535    removed_pids
536  }
537
538  /// Revisit all hit times and apply all event-wide 
539  /// corrections (local, e.g. cable corrections) 
540  /// and global (phase difference, timing offsets) 
541  /// applied and then after, sort them by time.
542  ///
543  /// The order of functions calls needs to be 
544  /// 1) ev.set_timing_offsets 
545  /// 2) ev.normalize_hit_times
546  #[cfg(feature="database")]
547  pub fn normalize_hit_times(&mut self) {
548    if self.hits.len() == 0 {
549      return;
550    }
551    // check if hit times have already been normalized
552    if self.hits[0].event_t0 == 0.0 {
553      return;
554    }
555
556    let phase0 = self.hits[0].phase.to_f32();
557    // add the corrections here 
558    // 1) phase
559    // 2) second timing offsets 
560    for h in &mut self.hits {
561      let t0 = h.get_t0_uncorrected() + h.get_cable_delay();
562      // deal with the phase difference
563      let mut phase_diff = h.phase.to_f32() - phase0;
564      while phase_diff < - PI/2.0 {
565        phase_diff += 2.0*PI;
566      }
567      while phase_diff > PI/2.0 {
568        phase_diff -= 2.0*PI;
569      }
570      let t_shift = 50.0*phase_diff/(2.0*PI);
571      h.event_t0 = t0 + t_shift - h.timing_offset;
572    }
573    // start the first hit at 0
574    //self.hits.sort_by(|a,b| (a.event_t0).partial_cmp(&b.event_t0).unwrap_or(Ordering::Greater));
575    self.hits.sort_by(|a,b| (a.event_t0).total_cmp(&b.event_t0));
576    let t0_first_hit = self.hits[0].event_t0;
577    for h in self.hits.iter_mut() {
578      h.event_t0 -= t0_first_hit;
579    }
580  }
581
582  /// Certain quantitities can only be calculated if we know more 
583  /// about the paddle the hit was seen in. 
584  /// This meta information is not stored within the hit to save 
585  /// space for telemetry/storage reasons.
586  ///
587  /// The meta-information (e.g. paddle length) can be added by 
588  /// filling in the blanks from the specific paddles as obtained 
589  /// from the database shipped with gondola.
590  #[cfg(feature="database")]
591  pub fn set_paddles(&mut self, paddles : &HashMap<u8, TofPaddle>) {
592    let mut nerror = 0u8;
593    if paddles.is_empty() {
594      debug!("Unable to set paddles, empty map given!");
595      return;
596    }
597    if self.hits.len() == 0 {
598      for rbev  in &mut self.rb_events {
599        for h in &mut rbev.hits {
600          match paddles.get(&h.paddle_id) {
601            None => {
602              error!("Got paddle id {} which is not in given map!", h.paddle_id);
603              nerror += 1;
604              continue;
605            }
606            Some(pdl) => {
607              h.set_paddle(pdl);
608            }
609          }
610        }
611      }
612    } else {
613      for h in &mut self.hits {
614        match paddles.get(&h.paddle_id) {
615          None => {
616            error!("Got paddle id {} which is not in given map!", h.paddle_id);
617            nerror += 1;
618            continue;
619          }
620          Some(pdl) => {
621            h.set_paddle(pdl);
622          }
623        }
624      }
625    }
626    if nerror == 0 {
627      self.paddles_set = true;
628      //self.normalize_hit_times();
629    }
630  }
631
632  /// Get the pointcloud of this event, sorted by time. 
633  /// For this to work, the event times have to be 
634  /// normalized first, this means that the first hit 
635  /// in the event is at 0 and the hits have been 
636  /// sorted in time
637  /// 
638  /// # Returns
639  ///   (f32, f32, f32, f32, f32) : (x,y,z,t,edep)
640  pub fn get_pointcloud(&self) -> Option<Vec<(f32,f32,f32,f32,f32)>> {
641    let mut pc = Vec::<(f32,f32,f32,f32,f32)>::new();
642    if !self.paddles_set {
643      error!("Before getting the pointcloud, paddle information needs to be set for this event. Call TofEvent;:set_paddle");
644      return None;
645    }
646    for h in &self.hits {
647      let result = (h.x, h.y, h.z, h.event_t0, h.get_edep());
648      pc.push(result);
649    }
650    Some(pc)
651  }
652
653  /// Compare the MasterTriggerEvent::trigger_hits with 
654  /// the actual hits to determine from which paddles
655  /// we should have received HG hits (from waveforms)
656  /// but we did not get them
657  ///
658  /// WARNING: The current implementation of this is 
659  /// rather slow and not fit for production use
660  /// FIXME - rewrite as a closure
661  #[cfg(feature="database")]
662  pub fn get_missing_paddles_hg(&self, pid_map :   &DsiJChPidMapping) -> Vec<u8> {
663    let mut missing = Vec::<u8>::new();
664    for th in self.get_trigger_hits() {
665      if !pid_map.contains_key(&th.0) {
666        error!("Can't find {:?} in paddlemap!",th);
667        continue;
668      }
669      if !pid_map.get(&th.0).unwrap().contains_key(&th.1) {
670        error!("Can't find {:?} in paddlemap!",th);
671        continue;
672      }
673      if !pid_map.get(&th.0).unwrap().get(&th.1).unwrap().contains_key(&th.2.0) {
674        error!("Can't find {:?} in paddlemap!",th);
675        continue;
676      }
677      let pid = pid_map.get(&th.0).unwrap().get(&th.1).unwrap().get(&th.2.0).unwrap().0;
678      let mut found = false;
679      for h in &self.hits {
680        if h.paddle_id == pid {
681          found = true;
682          break
683        }
684      }
685      if !found {
686        missing.push(pid);
687      }
688    }
689    missing
690  }
691  
692  /// Get the triggered paddle ids
693  ///
694  /// Warning, this might be a bit slow
695  #[cfg(feature="database")]
696  pub fn get_triggered_paddles(&self, pid_map :   &DsiJChPidMapping) -> Vec<u8> {
697    let mut paddles = Vec::<u8>::with_capacity(3);
698    for th in &self.get_trigger_hits() {
699      let pid = pid_map.get(&th.0).unwrap().get(&th.1).unwrap().get(&th.2.0).unwrap().0;
700      paddles.push(pid);
701    }
702    paddles
703  }
704
705  /// Get the RB link IDs according to the mask
706  pub fn get_rb_link_ids(&self) -> Vec<u8> {
707    let mut links = Vec::<u8>::new();
708    for k in 0..64 {
709      if (self.mtb_link_mask >> k) as u64 & 0x1 == 1 {
710        links.push(k as u8);
711      }
712    }
713    links
714  }
715
716  /// Get the combination of triggered DSI/J/CH on 
717  /// the MTB which formed the trigger. This does 
718  /// not include further hits which fall into the 
719  /// integration window. For those, se rb_link_mask
720  ///
721  /// The returned values follow the TOF convention
722  /// to start with 1, so that we can use them to 
723  /// look up LTB ids in the db.
724  ///
725  /// # Returns
726  ///
727  ///   Vec<(hit)> where hit is (DSI, J, CH) 
728  pub fn get_trigger_hits(&self) -> Vec<(u8, u8, (u8, u8), LTBThreshold)> {
729    let mut hits = Vec::<(u8,u8,(u8,u8),LTBThreshold)>::with_capacity(5); 
730    //let n_masks_needed = self.dsi_j_mask.count_ones() / 2 + self.dsi_j_mask.count_ones() % 2;
731    let n_masks_needed = self.dsi_j_mask.count_ones();
732    if self.channel_mask.len() < n_masks_needed as usize {
733      error!("We need {} hit masks, but only have {}! This is bad!", n_masks_needed, self.channel_mask.len());
734      return hits;
735    }
736    let mut n_mask = 0;
737    trace!("Expecting {} hit masks", n_masks_needed);
738    trace!("ltb channels {:?}", self.dsi_j_mask);
739    trace!("hit masks {:?}", self.channel_mask); 
740    //println!("We see LTB Channels {:?} with Hit masks {:?} for {} masks requested by us!", self.dsi_j_mask, self.channel_mask, n_masks_needed);
741    
742    // one k here is for one ltb
743    for k in 0..32 {
744      if (self.dsi_j_mask >> k) as u32 & 0x1 == 1 {
745        let mut dsi = 0u8;
746        let mut j   = 0u8;
747        if k < 5 {
748          dsi = 1;
749          j   = k as u8 + 1;
750        } else if k < 10 {
751          dsi = 2;
752          j   = k as u8 - 5 + 1;
753        } else if k < 15 {
754          dsi = 3;
755          j   = k as u8- 10 + 1;
756        } else if k < 20 {
757          dsi = 4;
758          j   = k as u8- 15 + 1;
759        } else if k < 25 {
760          dsi = 5;
761          j   = k as u8 - 20 + 1;
762        } 
763        //let dsi = (k as f32 / 4.0).floor() as u8 + 1;       
764        //let j   = (k % 5) as u8 + 1;
765        //println!("n_mask {n_mask}");
766        let channels = self.channel_mask[n_mask]; 
767        for (i,ch) in LTB_CHANNELS.iter().enumerate() {
768          //let chn = *ch as u8 + 1;
769          let ph_chn = PHYSICAL_CHANNELS[i];
770          //let chn = i as u8 + 1;
771          //println!("i,ch {}, {}", i, ch);
772          let thresh_bits = ((channels & ch) >> (i*2)) as u8;
773          //println!("thresh_bits {}", thresh_bits);
774          if thresh_bits > 0 { // hit over threshold
775            hits.push((dsi, j, ph_chn, LTBThreshold::from(thresh_bits)));
776          }
777        }
778        n_mask += 1;
779      } // next ltb
780    }
781    hits
782  }
783  
784  /// Get the trigger sources from trigger source byte
785  pub fn get_trigger_sources(&self) -> Vec<TriggerType> {
786    TriggerType::transcode_trigger_sources(self.trigger_sources)
787  }
788  
789  pub fn get_timestamp48(&self) -> u64 {
790    // the first number here is a constant, which was not sent, it is basically 
791    // a time offset (redefinition of epoch) and gets added through the "|" operation
792    0x273000000000000 | (((self.timestamp16 as u64) << 32) | self.timestamp32 as u64)
793  }
794  
795  /// Ttotal energy depostion in the TOF - Umbrella
796  ///
797  /// Utilizes Philip's formula based on 
798  /// peak height
799  pub fn get_edep_umbrella(&self) -> f32 {
800    let mut tot_edep = 0.0f32;
801    for h in &self.hits {
802      if h.paddle_id < 61 || h.paddle_id > 108 {
803        continue;
804      }
805      tot_edep += h.get_edep();
806    }
807    tot_edep
808  }
809  
810  /// Ttotal energy depostion in the TOF - Umbrella
811  ///
812  /// Utilizes Philip's formula based on 
813  /// peak height
814  pub fn get_edep_cube(&self) -> f32 {
815    let mut tot_edep = 0.0f32;
816    for h in &self.hits {
817      if h.paddle_id > 60 {
818        continue;
819      }
820      tot_edep += h.get_edep();
821    }
822    tot_edep
823  }
824  
825  /// Ttotal energy depostion in the Cortina
826  ///
827  /// Utilizes Philip's formula based on 
828  /// peak height
829  pub fn get_edep_cortina(&self) -> f32 {
830    let mut tot_edep = 0.0f32;
831    for h in &self.hits {
832      if h.paddle_id < 109 {
833        continue;
834      }
835      tot_edep += h.get_edep();
836    }
837    tot_edep
838  }
839  
840  /// Ttotal energy depostion in the complete TOF
841  ///
842  /// Utilizes Philip's formula based on 
843  /// peak height
844  pub fn get_edep(&self) -> f32 {
845    let mut tot_edep = 0.0f32;
846    for h in &self.hits {
847      tot_edep += h.get_edep();
848    }
849    tot_edep
850  }
851
852  /// The number of hits in the umbrella (UMB)
853  pub fn get_nhits_umb(&self) -> usize {
854    let mut nhit = 0;
855    for h in &self.hits {
856      if h.paddle_id > 60 && h.paddle_id < 109 {
857        nhit += 1;
858      }
859    }
860    nhit
861  }
862  
863  /// The number of hits in the cube (CBE)
864  pub fn get_nhits_cbe(&self) -> usize {
865    let mut nhit = 0;
866    for h in &self.hits {
867      if h.paddle_id < 61  {
868        nhit += 1;
869      }
870    }
871    nhit
872  }
873  
874  pub fn get_nhits_cor(&self) -> usize {
875    let mut nhit = 0;
876    for h in &self.hits {
877      if h.paddle_id > 108  {
878        nhit += 1;
879      }
880    }
881    nhit
882  }
883
884  pub fn get_nhits(&self) -> usize {
885    self.hits.len()
886  }
887  
888  /// Check if th eassociated RBEvents have any of their
889  /// mangling stati set
890  pub fn has_any_mangling(&self) -> bool {
891    for rbev in &self.rb_events {
892      if rbev.status == EventStatus::CellAndChnSyncErrors 
893      || rbev.status == EventStatus::CellSyncErrors 
894      || rbev.status == EventStatus::ChnSyncErrors {
895        return true;
896      }
897    }
898    false
899  }
900  
901  /// Get all waveforms of all RBEvents in this event
902  /// ISSUE - Performance, Memory
903  /// FIXME - reimplement this things where this
904  ///         returns only a reference
905  pub fn get_waveforms(&self) -> Vec<RBWaveform> {
906    let mut wfs = Vec::<RBWaveform>::new();
907    for ev in &self.rb_events {
908      for wf in &ev.get_rbwaveforms() {
909        wfs.push(wf.clone());
910      }
911    }
912    wfs
913  }
914
915  /// Change the status version when the event is already 
916  /// packed. The status version is encoded in byte 2 
917  /// (starting from 0) in the payload of the TofPacket 
918  pub fn set_packed_status_version(pack : &mut TofPacket, version : ProtocolVersion) 
919    -> Result<(), SerializationError> {
920    if pack.packet_type != TofPacketType::TofEvent {
921      return Err(SerializationError::IncorrectPacketType);
922    }
923    let mut status_version = pack.payload[2];
924    // null the bytes relevant for the version 
925    status_version  = status_version & 0x3f;
926    // now or the bytes for the new version 
927    status_version  = status_version | version.to_u8();
928    pack.payload[2] = status_version; 
929    Ok(()) 
930  }
931
932  /// For events with ProtocolVersion == V3, 
933  /// we have the rbevents at the end and the 
934  /// packet contains the GCU variables.
935  ///
936  /// This can remove the rbevents from a packed bytestrem,
937  /// and will reset the ProtocolVersion to V2.
938  /// The result is an event which should be ready 
939  /// to be sent to the GCU.
940  pub fn strip_packed_rbevents_for_pv3(pack : &mut TofPacket)
941    -> Result<(), SerializationError> {
942    if pack.packet_type != TofPacketType::TofEvent {
943      return Err(SerializationError::IncorrectPacketType);
944    }
945    let status_version = pack.payload[2];
946    let mut version    = ProtocolVersion::from(status_version & 0xc0);
947    if version != ProtocolVersion::V3 {
948      error!("This operation can only be executed on {}, however, this is version {}!", ProtocolVersion::V3, version);
949      return Err(SerializationError::WrongProtocolVersion);
950    }
951    // jump to the start of RBEvents 
952    let mut pos = 0usize;
953    pos += 10; // header 
954    pos += 15; // gcu variables (protocolversion V1 & V3) 
955    pos += 15;
956    if pack.payload.len() >= pos {
957      return Err(SerializationError::StreamTooShort);
958    }
959    let nmasks = parse_u8(&pack.payload, &mut pos);
960    for _ in 0..nmasks {
961      pos += 2;
962    }
963    pos += 8;
964    let nhits  = parse_u16(&pack.payload,&mut pos);
965    // set back the version
966    for _ in 0..nhits {
967      pos += TofHit::SIZE;
968      // FIXME - if we don't be able to manage 
969      //         to have a consistent size for 
970      //         TofHit, we have to deserialize them 
971      //         here (or write a minimal deserializer
972    }
973    // the next byte is finally the number of RBEvents. 
974    // So we set that to 0, strip the rest of the paylod 
975    // and re-attach the TAIL
976    pack.payload.truncate(pack.payload.len() - pos);
977    pack.payload.extend_from_slice(&Self::TAIL.to_le_bytes());
978    version = ProtocolVersion::V2;
979    Self::set_packed_status_version(pack, version)?;
980    Ok(())
981  }
982}
983
984impl TofPackable for TofEvent {
985  // v0.11 TofPacketType::TofEventSummary -> TofPacketType::TofEvent
986  const TOF_PACKET_TYPE        : TofPacketType = TofPacketType::TofEvent;
987  const TOF_PACKET_TYPE_ALT    : TofPacketType = TofPacketType::TofEventDeprecated;
988}
989
990impl Serialization for TofEvent {
991  
992  const HEAD               : u16   = 43690; //0xAAAA
993  const TAIL               : u16   = 21845; //0x5555
994  
995  fn to_bytestream(&self) -> Vec<u8> {
996    let mut stream = Vec::<u8>::new();
997    stream.extend_from_slice(&Self::HEAD.to_le_bytes());
998    let status_version = self.status as u8 | self.version.to_u8();
999    stream.push(status_version);
1000    stream.extend_from_slice(&self.trigger_sources.to_le_bytes());
1001    stream.extend_from_slice(&self.n_trigger_paddles.to_le_bytes());
1002    stream.extend_from_slice(&self.event_id.to_le_bytes());
1003    // depending on the version, we send the fc event packet
1004    if self.version == ProtocolVersion::V1 
1005      || self.version == ProtocolVersion::V3 {
1006      stream.extend_from_slice(&self.n_hits_umb  .to_le_bytes()); 
1007      stream.extend_from_slice(&self.n_hits_cbe  .to_le_bytes()); 
1008      stream.extend_from_slice(&self.n_hits_cor  .to_le_bytes()); 
1009      stream.extend_from_slice(&self.tot_edep_umb.to_le_bytes()); 
1010      stream.extend_from_slice(&self.tot_edep_cbe.to_le_bytes()); 
1011      stream.extend_from_slice(&self.tot_edep_cor.to_le_bytes()); 
1012    }
1013    stream.extend_from_slice(&(self.quality as u8).to_le_bytes());
1014    stream.extend_from_slice(&self.timestamp32.to_le_bytes());
1015    stream.extend_from_slice(&self.timestamp16.to_le_bytes());
1016    stream.extend_from_slice(&self.run_id.to_le_bytes());
1017    stream.extend_from_slice(&self.drs_dead_lost_hits.to_le_bytes());
1018    stream.extend_from_slice(&self.dsi_j_mask.to_le_bytes());
1019    let n_channel_masks = self.channel_mask.len();
1020    stream.push(n_channel_masks as u8);
1021    for k in 0..n_channel_masks {
1022      stream.extend_from_slice(&self.channel_mask[k].to_le_bytes());
1023    }
1024    stream.extend_from_slice(&self.mtb_link_mask.to_le_bytes());
1025    let nhits = self.hits.len() as u16;
1026    stream.extend_from_slice(&nhits.to_le_bytes());
1027    for k in 0..self.hits.len() {
1028      stream.extend_from_slice(&self.hits[k].to_bytestream());
1029    }
1030    // for the new (>=v0.11) event, we will always write 
1031    // the rb events
1032    if self.version == ProtocolVersion::V2 
1033      || self.version == ProtocolVersion::V3 {
1034      stream.push(self.rb_events.len() as u8);
1035      for rbev in &self.rb_events {
1036        stream.extend_from_slice(&rbev.to_bytestream());
1037      }
1038    }
1039    stream.extend_from_slice(&Self::TAIL.to_le_bytes());
1040    stream
1041  }
1042  
1043  fn from_bytestream(stream    : &Vec<u8>, 
1044                     pos       : &mut usize) 
1045    -> Result<Self, SerializationError>{
1046    let mut event = Self::new();
1047    let head      = parse_u16(stream, pos);
1048    if head != Self::HEAD {
1049      error!("Decoding of HEAD failed! Got {} instead!", head);
1050      return Err(SerializationError::HeadInvalid);
1051    }
1052    
1053    let status_version_u8   = parse_u8(stream, pos);
1054    let status              = EventStatus::from(status_version_u8 & 0x3f);
1055    let version             = ProtocolVersion::from(status_version_u8 & 0xc0); 
1056    event.status            = status;
1057    event.version           = version;
1058    event.trigger_sources   = parse_u16(stream, pos);
1059    event.n_trigger_paddles = parse_u8(stream, pos);
1060    event.event_id          = parse_u32(stream, pos);
1061    if event.version == ProtocolVersion::V1
1062      || event.version == ProtocolVersion::V3 {
1063      event.n_hits_umb      = parse_u8(stream, pos); 
1064      event.n_hits_cbe      = parse_u8(stream, pos); 
1065      event.n_hits_cor      = parse_u8(stream, pos); 
1066      event.tot_edep_umb    = parse_f32(stream, pos); 
1067      event.tot_edep_cbe    = parse_f32(stream, pos); 
1068      event.tot_edep_cor    = parse_f32(stream, pos); 
1069    }
1070    event.quality            = EventQuality::from(parse_u8(stream, pos));
1071    event.timestamp32        = parse_u32(stream, pos);
1072    event.timestamp16        = parse_u16(stream, pos);
1073    event.run_id             = parse_u16(stream, pos);
1074    event.drs_dead_lost_hits = parse_u16(stream, pos);
1075    event.dsi_j_mask         = parse_u32(stream, pos);
1076    let n_channel_masks        = parse_u8(stream, pos);
1077    for _ in 0..n_channel_masks {
1078      event.channel_mask.push(parse_u16(stream, pos));
1079    }
1080    event.mtb_link_mask      = parse_u64(stream, pos);
1081    let nhits                = parse_u16(stream, pos);
1082    //println!("{}", event);
1083    if nhits > 160 {
1084      error!("There are an abnormous amount of hits in this event!");
1085      return Err(SerializationError::StreamTooLong);
1086    } 
1087    for _ in 0..nhits {
1088      event.hits.push(TofHit::from_bytestream(stream, pos)?);
1089    }
1090    if event.version == ProtocolVersion::V2 
1091      || event.version == ProtocolVersion::V3 {
1092      let n_rb_events = parse_u8(stream, pos);
1093      if n_rb_events > 0 {
1094        for _ in 0..n_rb_events {
1095          event.rb_events.push(RBEvent::from_bytestream(stream, pos)?);
1096        }
1097      }
1098    }
1099
1100    let tail = parse_u16(stream, pos);
1101    if tail != Self::TAIL {
1102      error!("Decoding of TAIL failed for version {}! Got {} instead!", version, tail);
1103      return Err(SerializationError::TailInvalid);
1104    }
1105    Ok(event)
1106  }
1107  
1108  /// Allows to get TofEvent from a packet 
1109  /// of the deprecate packet type TofEventDeprecated.
1110  /// This packet type was formerly known as TofEvent
1111  ///
1112  /// This will produce an event with rbevents & hits.
1113  fn from_bytestream_alt(stream    : &Vec<u8>, 
1114                         pos       : &mut usize) 
1115    -> Result<Self, SerializationError> {
1116    let head = parse_u16(stream, pos);
1117    if head != TofEvent::HEAD {
1118      return Err(SerializationError::HeadInvalid);
1119    }
1120    let mut te            = Self::new();
1121    // the compression level will always be 0 for old data
1122    let _compression_level = parse_u8(stream, pos);
1123
1124    te.quality            = EventQuality::from(parse_u8(stream, pos));
1125    // at this position is the serialized TofEventHeader. We don't have that anymore (>v0.11). 
1126    // However, the only information we need from it is the run id, the other fields are anyway
1127    // empty
1128    *pos += 2; // for TofEventHeader::HEAD
1129    // FIXME - potentially dangerous for u16 overflow!
1130    te.run_id             = parse_u32(stream, pos) as u16;
1131    *pos += 43 - 6;// rest of TofEventHeader 
1132    //let header         = TofEventHeader::from_bytestream(stream, &mut pos)?;
1133    // now parse the "old" MasterTriggerEvent
1134    *pos += 2; // MasterTriggerEvent::HEAD
1135    let event_status   = parse_u8 (stream, pos);
1136    te.status              = EventStatus::from(event_status);
1137    if te.has_any_mangling() {
1138      te.status = EventStatus::AnyDataMangling;
1139    }
1140    te.event_id        = parse_u32(stream, pos);
1141    let mtb_timestamp  = parse_u32(stream, pos);
1142    let tiu_timestamp  = parse_u32(stream, pos);
1143    let tiu_gps32      = parse_u32(stream, pos);
1144    let _tiu_gps16     = parse_u16(stream,pos);
1145    let _crc           = parse_u32(stream, pos);
1146    let mt_timestamp   = (mt_event_get_timestamp_abs48(mtb_timestamp, tiu_gps32, tiu_timestamp ) as f64/1000.0).floor()  as u64; 
1147    te.timestamp32      = (mt_timestamp  & 0x00000000ffffffff ) as u32;
1148    te.timestamp16      = ((mt_timestamp & 0x0000ffff00000000 ) >> 32) as u16;
1149    te.trigger_sources  = parse_u16(stream, pos);
1150    te.dsi_j_mask       = parse_u32(stream, pos);
1151    let n_channel_masks = parse_u8(stream, pos);
1152    for _ in 0..n_channel_masks {
1153      te.channel_mask.push(parse_u16(stream, pos));
1154    }
1155
1156    te.mtb_link_mask      = parse_u64(stream, pos);
1157    let mt_event_tail     = parse_u16(stream, pos);
1158    if mt_event_tail != Self::TAIL {
1159      // (tail for mt event was the same)
1160      error!("Parsed TAIL from MT event is incorrect! Got {} instead of {} at pos {}", mt_event_tail, Self::TAIL, pos);
1161    }
1162    //let mt_event      = MasterTriggerEvent::from_bytestream(stream, &mut pos)?;
1163    let v_sizes           = Self::decode_depr_tofevent_size_header(&parse_u32(stream, pos));
1164    //println!("TofEvent - rbevents,  {:?}", v_sizes);
1165    for _ in 0..v_sizes.0 {
1166      // we are getting all waveforms for now, but we can 
1167      // discard them later
1168      let next_rb_event = RBEvent::from_bytestream(stream, pos)?;
1169      //println!("{}", next_rb_event);
1170      te.rb_events.push(next_rb_event);
1171    }
1172    //println!("{}",te);
1173    
1174    // FIXME - this is slow, use Arc<> instead. However, then make 
1175    // sure to copy them in case we get rid of the rb evvnts 
1176    // (or does Arc take care of it) 
1177    for rbev in &te.rb_events {
1178      for h in &rbev.hits {
1179        te.hits.push(*h);
1180      }
1181    }
1182    let tail = parse_u16(stream, pos);
1183    if tail != Self::TAIL {
1184      error!("Decoding of TAIL failed! Got {} instead!", tail);
1185      return Err(SerializationError::TailInvalid);
1186    }
1187    return Ok(te);
1188  }
1189}
1190    
1191impl Default for TofEvent {
1192  fn default() -> Self {
1193    Self::new()
1194  }
1195}
1196
1197impl fmt::Display for TofEvent {
1198  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1199    let mut repr = format!("<TofEvent (version {})", self.version);
1200    repr += &(format!("\n  EventID          : {}", self.event_id));
1201    repr += &(format!("\n  RunID            : {}", self.run_id));
1202    repr += &(format!("\n  EventStatus      : {}", self.status));
1203    repr += &(format!("\n  TriggerSources   : {:?}", self.get_trigger_sources()));
1204    repr += &(format!("\n  NTrigPaddles     : {}", self.n_trigger_paddles));
1205    repr += &(format!("\n  DRS dead hits    : {}", self.drs_dead_lost_hits));
1206    repr += &(format!("\n  timestamp32      : {}", self.timestamp32)); 
1207    repr += &(format!("\n  timestamp16      : {}", self.timestamp16)); 
1208    repr += &(format!("\n   |-> timestamp48 : {}", self.get_timestamp48())); 
1209    repr += &(format!("\n   |->       UNIX  : {}", (1e-8) * self.get_timestamp48() as f64)); 
1210    repr += &(format!("\n   |->        UTC  : {}", get_utc_timestamp_from_unix((1e-8) * self.get_timestamp48() as f64).unwrap_or(String::from("INVALID")))); 
1211    //repr += &(format!("\n  mt_tiu_gps16     : {}", self.mt_tiu_gps16));
1212    //repr += &(format!("\n  mt_tiu_gps32     : {}", self.mt_tiu_gps32)); 
1213    //repr += &(format!("\n  mt_timestamp     : {}", self.mt_timestamp));
1214    //repr += &(format!("\n  mt_tiu_timestamp : {}", self.mt_tiu_timestamp));
1215    //repr += &(format!("\n  gps timestamp    : {}", self.get_mt_timestamp_abs()));
1216    //repr += &(format!("\n  PrimaryBeta      : {}", self.get_beta())); 
1217    //repr += &(format!("\n  PrimaryCharge    : {}", self.primary_charge));
1218    if self.version == ProtocolVersion::V1 {
1219      repr += "\n ---- V1 variables ----";
1220      repr += &(format!("\n n_hits_umb   : {}", self.n_hits_umb  )); 
1221      repr += &(format!("\n n_hits_cbe   : {}", self.n_hits_cbe  )); 
1222      repr += &(format!("\n n_hits_cor   : {}", self.n_hits_cor  )); 
1223      repr += &(format!("\n tot_edep_umb : {}", self.tot_edep_umb)); 
1224      repr += &(format!("\n tot_edep_cbe : {}", self.tot_edep_cbe)); 
1225      repr += &(format!("\n tot_edep_cor : {}", self.tot_edep_cor)); 
1226    }
1227    repr += &(format!("\n  ** ** TRIGGER HITS (DSI/J/CH) [{} LTBS] ** **", self.dsi_j_mask.count_ones()));
1228    for k in self.get_trigger_hits() {
1229      repr += &(format!("\n  => {}/{}/({},{}) ({}) ", k.0, k.1, k.2.0, k.2.1, k.3));
1230    }
1231    repr += "\n  ** ** MTB LINK IDs ** **";
1232    let mut mtblink_str = String::from("\n  => ");
1233    for k in self.get_rb_link_ids() {
1234      mtblink_str += &(format!("{} ", k))
1235    }
1236    repr += &mtblink_str;
1237    repr += &(format!("\n  == Trigger hits {}, expected RBEvents {}",
1238            self.get_trigger_hits().len(),
1239            self.get_rb_link_ids().len()));
1240    repr += &String::from("\n  ** ** ** HITS ** ** **");
1241    for h in &self.hits {
1242      repr += &(format!("\n  {}", h));
1243    }
1244    if self.rb_events.len() > 0 {
1245      repr += &format!("\n -- has {} RBEvents with waveforms!", self.rb_events.len());
1246      repr += "\n -- -- boards: ";
1247      for b in &self.rb_events {
1248        repr += &format!("{} ", b.header.rb_id);
1249      }
1250    }
1251    repr += ">";
1252    write!(f, "{}", repr)
1253  }
1254}
1255
1256#[cfg(feature="random")]
1257impl FromRandom for TofEvent {
1258
1259  fn from_random() -> Self {
1260    let mut event             = Self::new();
1261    let mut rng               = rand::rng();
1262    let status                = EventStatus::from_random();
1263    let version               = ProtocolVersion::from_random();
1264    if version == ProtocolVersion::V1 {
1265      event.n_hits_umb        = rng.random::<u8>();
1266      event.n_hits_cbe        = rng.random::<u8>();
1267      event.n_hits_cor        = rng.random::<u8>();
1268      event.tot_edep_umb      = rng.random::<f32>();
1269      event.tot_edep_cbe      = rng.random::<f32>();
1270      event.tot_edep_cor      = rng.random::<f32>();
1271      event.quality           = EventQuality::from_random();
1272    }
1273    event.status             = status;
1274    event.version            = version;
1275    // variable packet for the FC
1276    event.trigger_sources    = rng.random::<u16>();
1277    event.n_trigger_paddles  = rng.random::<u8>();
1278    event.event_id           = rng.random::<u32>();
1279    event.timestamp32        = rng.random::<u32>();
1280    event.timestamp16        = rng.random::<u16>();
1281    event.drs_dead_lost_hits = rng.random::<u16>();
1282    event.dsi_j_mask         = rng.random::<u32>();
1283    let n_channel_masks        = rng.random::<u8>();
1284    for _ in 0..n_channel_masks {
1285      event.channel_mask.push(rng.random::<u16>());
1286    }
1287    event.mtb_link_mask      = rng.random::<u64>();
1288    //let nhits                  = rng.random::<u8>();
1289    let nhits: u16 = rng.random_range(0..5);
1290    for _ in 0..nhits {
1291      event.hits.push(TofHit::from_random());
1292    }
1293    if event.version == ProtocolVersion::V2 {
1294      let n_rb_events = rng.random_range(0..4);
1295      for _ in 0..n_rb_events {
1296        event.rb_events.push(RBEvent::from_random());
1297      }
1298    }
1299    event
1300  }
1301}
1302
1303//---------------------------------------------------
1304
1305#[cfg(feature="pybindings")]
1306#[pymethods]
1307impl TofEvent {
1308
1309  #[getter]
1310  #[pyo3(name="tof")] 
1311  fn get_tof_py(&mut self) -> Option<(f32,f32,f32,f32,f32)> {
1312    self.get_tof()
1313  }
1314
1315  #[pyo3(name="strip_rbevents")]
1316  fn strip_rbevents_py(&mut self) {
1317    self.strip_rbevents()
1318  }
1319  
1320  /// Calculate the TOF part of the interesting events mechanism, whcih is
1321  /// NHIT (CBE, COR, UMB) and EDEP (CBE, COR, UMB)
1322  #[pyo3(name="calc_gcu_variables")]
1323  fn calc_gcu_variables_py(&mut self) {
1324    self.calc_gcu_variables()
1325  }
1326
1327  /// Emit a copy of self
1328  fn copy(&self) -> Self {
1329    self.clone()
1330  }
1331
1332  /// Set timing offsets on the hit times of the events' hits,
1333  /// which absorb any systematic besides the phase & cable length.
1334  ///
1335  /// This will NOT yet subtract these numbers. To 
1336  /// do so, the hit times have to be "normalized" by 
1337  /// calling ev.normalize_hit_times 
1338  ///
1339  /// # Arguments:
1340  ///   * offsets : a hashmap paddle id -> timing offset
1341  #[pyo3(name="set_timing_offsets")]
1342  pub fn set_timing_offsets_py(&mut self, timing_offsets : HashMap<u8, f32>) {
1343    self.set_timing_offsets(&timing_offsets);
1344  }
1345  
1346  /// Revisit all hit times and apply all event-wide 
1347  /// corrections (local, e.g. cable corrections) 
1348  /// and global (phase difference, timing offsets) 
1349  /// applied and then after, sort them by time.
1350  ///
1351  /// The order of functions calls needs to be 
1352  /// 1) ev.set_timing_offsets 
1353  /// 2) ev.normalize_hit_times
1354  #[pyo3(name="normalize_hit_times")]
1355  pub fn normalize_hit_times_py(&mut self) {
1356    self.normalize_hit_times();
1357  }
1358  
1359  /// Remove hits from the hitseries which can not 
1360  /// be caused by the same particle, which means 
1361  /// that for these two specific hits beta with 
1362  /// respect to the first hit in the event is 
1363  /// larger than one
1364  /// That this works, first hits need to be 
1365  /// "normalized" by calling normalize_hit_times
1366  #[pyo3(name="lightspeed_cleaning")]
1367  pub fn lightspeed_cleaning_py(&mut self, t_err : f32) -> (Vec<u16>, Vec<f32>) {
1368    // return Vec<u16> here so that python does not 
1369    // interpret it as a byte
1370    let mut pids = Vec::<u16>::new();
1371    let (pids_rm, twindows) = self.lightspeed_cleaning(t_err);
1372    for pid in pids_rm {
1373      pids.push(pid as u16);
1374    }
1375    (pids, twindows)
1376  }
1377 
1378  /// The run id 
1379  #[getter]
1380  fn get_run_id(&self) -> u16 { 
1381    self.run_id
1382  }
1383
1384  /// Remove all hits from the event's hit series which 
1385  /// do NOT obey causality. that is where the timings
1386  /// measured at ends A and B can not be correlated
1387  /// by the assumed speed of light in the paddle
1388  #[pyo3(name="remove_non_causal_hits")]
1389  fn remove_non_causal_hits_py(&mut self) -> Vec<u16> {
1390    // return Vec<u16> here so that python does not 
1391    // interpret it as a byte
1392    let mut pids = Vec::<u16>::new();
1393    for pid in self.remove_non_causal_hits() {
1394      pids.push(pid as u16);
1395    }
1396    pids
1397  }
1398  
1399  /// Get the pointcloud of this event, sorted by time. 
1400  /// For this to work, the event times have to be 
1401  /// normalized first, this means that the first hit 
1402  /// in the event is at 0 and the hits have been 
1403  /// sorted in time
1404  /// 
1405  /// # Returns
1406  ///   (f32, f32, f32, f32, f32) : (x,y,z,t,edep)
1407  ///   Will return None if information about the tof paddles 
1408  ///   as obtained from the gondola_db is not available! 
1409  #[getter]
1410  #[pyo3(name="pointcloud")]
1411  fn get_pointcloud_py(&self) -> Option<Vec<(f32,f32,f32,f32,f32)>> {
1412    self.get_pointcloud()
1413  }
1414
1415  #[getter]
1416  #[pyo3(name="has_any_mangling")]
1417  fn has_any_mangling_py(&self) -> bool {
1418    self.has_any_mangling() 
1419  }
1420
1421  #[getter]
1422  fn get_event_id(&self) -> u32 {
1423    self.event_id
1424  }
1425  
1426  #[getter]
1427  fn get_event_status(&self) -> EventStatus {
1428    self.status
1429  }
1430  
1431  /// Compare the hg hits of the event with the triggered paddles and 
1432  /// return the paddles which have at least a missing HG hit
1433  #[pyo3(name="get_missing_paddles_hg")]
1434  fn get_missing_paddles_hg_py(&self, mapping : DsiJChPidMapping) -> Vec<u8> {
1435    self.get_missing_paddles_hg(&mapping)
1436  }
1437
1438  /// Get all the paddle ids which have been triggered
1439  #[pyo3(name="get_triggered_paddles")]
1440  fn get_triggered_paddles_py(&self, mapping : DsiJChPidMapping) -> Vec<u8> {
1441    self.get_triggered_paddles(&mapping)
1442  }
1443
1444  /// The hits we were not able to read out because the DRS4 chip
1445  /// on the RBs was busy
1446  #[getter]
1447  fn lost_hits(&self) -> u16 {
1448    self.drs_dead_lost_hits
1449  }
1450
1451  /// RB Link IDS (not RB ids) which fall into the 
1452  /// trigger window
1453  #[getter]
1454  fn rb_link_ids(&self) -> Vec<u32> {
1455    self.get_rb_link_ids().into_iter().map(|byte| byte as u32).collect()
1456  }
1457
1458  /// The event might have RBEvents associated with it
1459  #[getter]
1460  fn get_rb_events(&self) -> Vec<RBEvent> {
1461    self.rb_events.clone()
1462  }
1463
1464  /// Hits which formed a trigger
1465  #[getter]
1466  pub fn trigger_hits(&self) -> PyResult<Vec<(u8, u8, (u8, u8), LTBThreshold)>> {
1467    Ok(self.get_trigger_hits())
1468  }
1469  
1470  /// The active triggers in this event. This can be more than one, 
1471  /// if multiple trigger conditions are satisfied.
1472  #[getter]
1473  pub fn trigger_sources(&self) -> Vec<TriggerType> {
1474    self.get_trigger_sources()
1475  } 
1476  
1477  /// The active triggers in this event. This can be more than one, 
1478  /// if multiple trigger conditions are satisfied.
1479  #[getter]
1480  #[pyo3(name="trigger_sources_bytes")]
1481  pub fn get_trigger_sources_bytes_py(&self) -> u16 {
1482    self.trigger_sources
1483  } 
1484
1485  #[pyo3(name="move_hits")]
1486  pub fn move_hits_py(&mut self) {
1487    self.move_hits()
1488  }
1489
1490  #[getter]
1491  #[pyo3(name="hits")]
1492  pub fn hits_py<'_py>(&self) -> Vec<TofHit> {
1493  //pub fn hits_py<'_py>(&self) -> PyResult<Bound<'_,Vec<TofHit>>> {
1494    //Bound::new(py, self.hits)
1495    //FIXMEFIXMEFIXME
1496    self.hits.clone()
1497  }
1498  
1499  #[getter]
1500  #[pyo3(name="hitmap")]
1501  pub fn hitmap<'_py>(&self) -> HashMap<u8,TofHit> {
1502  //pub fn hits_py<'_py>(&self) -> PyResult<Bound<'_,Vec<TofHit>>> {
1503    //Bound::new(py, self.hits)
1504    //FIXMEFIXMEFIXME
1505    let mut hitmap = HashMap::<u8, TofHit>::new();
1506    for h in &self.hits {
1507      hitmap.insert(h.paddle_id, *h);
1508    }
1509    hitmap
1510  }
1511  
1512  /// Total energy depostion in the Umbrella
1513  ///
1514  /// Utilizes Philip's formula based on 
1515  /// peak height
1516  #[getter]
1517  #[pyo3(name="edep_umb")]
1518  pub fn get_edep_umbrella_py(&self) -> f32 {
1519    self.get_edep_umbrella()
1520  }
1521  
1522  /// Total energy depostion in the Cube
1523  ///
1524  /// Utilizes Philip's formula based on 
1525  /// peak height
1526  #[getter]
1527  #[pyo3(name="edep_cbe")]
1528  pub fn get_edep_cube_py(&self) -> f32 {
1529    self.get_edep_cube()
1530  }
1531  
1532  /// Total energy depostion in the Cortina
1533  ///
1534  /// Utilizes Philip's formula based on 
1535  /// peak height
1536  #[getter]
1537  #[pyo3(name="edep_cor")]
1538  pub fn get_edep_cortina_py(&self) -> f32 {
1539    self.get_edep_cortina()
1540  }
1541
1542  /// Total energy depostion in the complete TOF
1543  ///
1544  /// Utilizes Philip's formula based on 
1545  /// peak height
1546  #[getter]
1547  #[pyo3(name="edep")]
1548  pub fn get_edep_py(&self) -> f32 {
1549    self.get_edep()
1550  }
1551
1552  #[getter]
1553  #[pyo3(name="nhits")]
1554  pub fn nhits_py(&self) -> usize {
1555    self.get_nhits()
1556  }
1557
1558  #[getter]
1559  #[pyo3(name="dsi_j_mask")]
1560  pub fn get_dsi_j_mask_py(&self) -> u32 {
1561    self.dsi_j_mask
1562  }
1563
1564  #[getter]
1565  #[pyo3(name="channel_masks")]
1566  pub fn get_channel_masks_py(&self) -> Vec<u16> {
1567    self.channel_mask.clone()
1568  }
1569
1570  #[getter]
1571  #[pyo3(name="nhits_umb")]
1572  pub fn nhits_umb_py(&self) -> usize {
1573    self.get_nhits_umb()
1574  }
1575
1576  #[getter]
1577  #[pyo3(name="nhits_cbe")]
1578  fn get_nhits_cbe_py(&self) -> usize {
1579    self.get_nhits_cbe()
1580  }
1581  
1582  #[getter]
1583  #[pyo3(name="nhits_cor")]
1584  fn get_nhits_cor_py(&self) -> usize {
1585    self.get_nhits_cor()
1586  }
1587
1588  #[getter]
1589  fn get_timestamp16(&self) -> u16 {
1590    self.timestamp16
1591  }
1592  
1593  #[getter]
1594  fn get_timestamp32(&self) -> u32 {
1595    self.timestamp32
1596  }
1597  
1598  #[getter]
1599  fn timestamp48(&self) -> u64 {
1600    self.get_timestamp48()
1601  }
1602  
1603  #[getter]
1604  fn get_status(&self) -> EventStatus {
1605    self.status
1606  }
1607
1608  #[getter]
1609  #[pyo3(name="waveforms")]
1610  fn get_waveforms_py(&self) -> Vec<RBWaveform> {
1611    self.get_waveforms()
1612  }
1613
1614  #[staticmethod]
1615  #[pyo3(name = "set_packed_status_version")]
1616  fn set_packed_status_version_py(pack : &mut TofPacket, version : ProtocolVersion) 
1617    -> PyResult<()> {
1618    match Self::set_packed_status_version(pack, version) {
1619      Err(err) => {
1620        let err_mesg = format!("Unable to set status version! {}", err);
1621        return Err(PyValueError::new_err(err_mesg));
1622      } 
1623      Ok(_) => {
1624        return Ok(());
1625      }
1626    }
1627  }
1628
1629  #[staticmethod]
1630  #[pyo3(name = "strip_packed_rbevents_for_pv3")]
1631  fn strip_packed_rbevents_for_pv3_py(pack : &mut TofPacket) 
1632    -> PyResult<()> {
1633    match Self::strip_packed_rbevents_for_pv3(pack) {
1634      Err(err) => {
1635        let err_msg = format!("Unable to strip packed rbevents{}", err);
1636        return Err(PyValueError::new_err(err_msg));
1637      } 
1638      Ok(_) => {
1639        return Ok(());
1640      }
1641    }
1642  }
1643  
1644  #[cfg(feature="database")]
1645  #[staticmethod]
1646  fn unpack(pack : &TofPacket) -> PyResult<Self> {
1647    if pack.packet_type != Self::TOF_PACKET_TYPE {
1648      let err_msg = format!("This is a packet of type {}, but we need type {}", pack.packet_type, Self::TOF_PACKET_TYPE);
1649      return Err(PyValueError::new_err(err_msg));
1650    }
1651    let mut pos = 0;
1652    let mut ev = Self::from_bytestream(&pack.payload,&mut pos)?; 
1653    ev.set_paddles(&pack.tof_paddles);
1654    Ok(ev)
1655  }
1656}
1657
1658#[cfg(feature="pybindings")]
1659pythonize_packable!(TofEvent);
1660
1661//---------------------------------------------------
1662
1663#[test]
1664#[cfg(feature="random")]
1665fn packable_tofeventv0() {
1666  for _ in 0..500 {
1667    let mut data = TofEvent::from_random();
1668    if data.version != ProtocolVersion::Unknown {
1669      continue;
1670    }
1671    let mut test : TofEvent = data.pack().unpack().unwrap();
1672    //println!("{}", data.hits[0]);
1673    //println!("{}", test.hits[0]);
1674    // Manually zero these fields, since comparison with nan will fail and 
1675    // from_random did not touch these
1676    //println!("{}", data);
1677    //println!("{}", test);
1678    let fix_time = Instant::now();
1679    test.creation_time = fix_time;
1680    data.creation_time = fix_time;
1681    for k in &mut data.rb_events {
1682      k.creation_time = None;
1683    }
1684    for k in &mut test.rb_events {
1685      k.creation_time = None;
1686    }
1687    for h in &mut test.hits {
1688      h.paddle_len       = 0.0; 
1689      h.coax_cable_time  = 0.0; 
1690      h.hart_cable_time  = 0.0; 
1691      h.x                = 0.0; 
1692      h.y                = 0.0; 
1693      h.z                = 0.0; 
1694      h.event_t0         = 0.0;
1695    }
1696    assert_eq!(data, test);
1697  }
1698}  
1699
1700#[test]
1701#[cfg(feature="random")]
1702fn packable_tofeventv1() {
1703  for _ in 0..500 {
1704    let mut data = TofEvent::from_random();
1705    if data.version != ProtocolVersion::V1 {
1706      continue;
1707    }
1708    let mut test : TofEvent = data.pack().unpack().unwrap();
1709    //println!("{}", data.hits[0]);
1710    //println!("{}", test.hits[0]);
1711    // Manually zero these fields, since comparison with nan will fail and 
1712    // from_random did not touch these
1713    let fix_time = Instant::now();
1714    test.creation_time = fix_time;
1715    data.creation_time = fix_time;
1716    for k in &mut data.rb_events {
1717      k.creation_time = None;
1718    }
1719    for k in &mut test.rb_events {
1720      k.creation_time = None;
1721    }
1722    for h in &mut test.hits {
1723      h.paddle_len       = 0.0; 
1724      h.coax_cable_time  = 0.0; 
1725      h.hart_cable_time  = 0.0; 
1726      h.x                = 0.0; 
1727      h.y                = 0.0; 
1728      h.z                = 0.0; 
1729      h.event_t0         = 0.0;
1730    }
1731    assert_eq!(data, test);
1732  }
1733}  
1734
1735#[test]
1736#[cfg(feature="random")]
1737fn packable_tofeventv2() {
1738  for _ in 0..500 {
1739    let mut data = TofEvent::from_random();
1740    if data.version != ProtocolVersion::V2 {
1741      continue;
1742    }
1743    let mut test : TofEvent = data.pack().unpack().unwrap();
1744    //println!("{}", data.hits[0]);
1745    //println!("{}", test.hits[0]);
1746    // Manually zero these fields, since comparison with nan will fail and 
1747    // from_random did not touch these
1748    let fix_time = Instant::now();
1749    test.creation_time = fix_time;
1750    data.creation_time = fix_time;
1751    for k in &mut data.rb_events {
1752      k.creation_time = None;
1753    }
1754    for k in &mut test.rb_events {
1755      k.creation_time = None;
1756    }
1757    for h in &mut test.hits {
1758      h.paddle_len       = 0.0; 
1759      h.coax_cable_time  = 0.0; 
1760      h.hart_cable_time  = 0.0; 
1761      h.x                = 0.0; 
1762      h.y                = 0.0; 
1763      h.z                = 0.0; 
1764      h.event_t0         = 0.0;
1765    }
1766    assert_eq!(data, test);
1767  }
1768}  
1769
1770#[test]
1771#[cfg(feature="random")]
1772fn packable_tofeventv3() {
1773  for _ in 0..500 {
1774    let mut data = TofEvent::from_random();
1775    if data.version != ProtocolVersion::V3 {
1776      continue;
1777    }
1778    let mut test : TofEvent = data.pack().unpack().unwrap();
1779    //println!("{}", data.hits[0]);
1780    //println!("{}", test.hits[0]);
1781    // Manually zero these fields, since comparison with nan will fail and 
1782    // from_random did not touch these
1783    let fix_time = Instant::now();
1784    test.creation_time = fix_time;
1785    data.creation_time = fix_time;
1786    for h in &mut test.hits {
1787      h.paddle_len       = 0.0; 
1788      h.coax_cable_time  = 0.0; 
1789      h.hart_cable_time  = 0.0; 
1790      h.x                = 0.0; 
1791      h.y                = 0.0; 
1792      h.z                = 0.0; 
1793      h.event_t0         = 0.0;
1794    }
1795    assert_eq!(data, test);
1796  }
1797}  
1798
1799#[test]
1800#[cfg(feature="random")]
1801fn tofevent_move_hits() {
1802  let mut event = TofEvent::from_random();
1803  let mut n_hits_exp = 0usize;
1804  for rb in &event.rb_events {
1805    n_hits_exp += rb.hits.len();
1806  }
1807  event.hits.clear();
1808  event.move_hits();
1809  for rb in &event.rb_events {
1810    assert_eq!(rb.hits.len(),0);
1811  }
1812  assert_eq!(n_hits_exp, event.hits.len());
1813
1814}
1815
1816#[test]
1817#[cfg(feature="random")] 
1818fn tofevent_striprbevents() {
1819  let mut event      = TofEvent::from_random();
1820  let mut n_hits_exp = 0usize;
1821  for rb in &event.rb_events {
1822    n_hits_exp += rb.hits.len();
1823  }
1824  event.hits.clear();
1825  event.strip_rbevents();
1826  assert_eq!(event.rb_events.len(),0);
1827  assert_eq!(n_hits_exp, event.hits.len());
1828}
1829
1830//#[test]
1831//#[cfg(feature = "random")]
1832//fn tofevent_sizes_header() {
1833//  for _ in 0..100 {
1834//    let data = TofEvent::from_random();
1835//    let mask = data.construct_sizes_header();
1836//    let size = TofEvent::decode_size_header(&mask);
1837//    assert_eq!(size.0, data.rb_events.len());
1838//    //assert_eq!(size.1, data.missing_hits.len());
1839//  }
1840//}
1841
1842//#[test]
1843//#[cfg(feature = "random")]
1844//fn packable_tofevent() {
1845//  for _ in 0..5 {
1846//    let data = TofEvent::from_random();
1847//    let test : TofEvent = data.pack().unpack().unwrap();
1848//    assert_eq!(data.header, test.header);
1849//    assert_eq!(data.compression_level, test.compression_level);
1850//    assert_eq!(data.quality, test.quality);
1851//    assert_eq!(data.mt_event, test.mt_event);
1852//    assert_eq!(data.rb_events.len(), test.rb_events.len());
1853//    //assert_eq!(data.missing_hits.len(), test.missing_hits.len());
1854//    //assert_eq!(data.missing_hits, test.missing_hits);
1855//    assert_eq!(data.rb_events, test.rb_events);
1856//    //assert_eq!(data, test);
1857//    //println!("{}", data);
1858//  }
1859//}
1860
1861
1862