gondola_core/tof/
cuts.rs

1// This file is part of gaps-online-software and published 
2// under the GPLv3 license
3
4use std::ops::AddAssign; 
5use std::ops::Add;
6use std::cmp::Ordering;
7use crate::prelude::*;
8
9/// A large number for the lightspeed cut.
10/// This can be used for the error, which is 
11/// in % of the lightspeed so everything 
12/// > 1 is non-sensical
13const NO_LIGHTSPEED_CUTS : f64 = 42e9;
14
15/// Sets of cuts which can be imposed on 
16/// TofEvents 
17///
18///
19//FIXME - it is addmitedly a bit of a mess, since
20//        it will perform hit cleanings 
21#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
22#[cfg_attr(feature="pybindings", pyclass)]
23pub struct TofCuts {
24  /// the number of events which the cut instance
25  /// has seen. This is all events, both rejected
26  /// and passed.
27  #[serde(skip_serializing)]
28  pub nevents             : u64 ,
29  /// require at least N COR hits
30  pub min_hit_cor         : u8  ,
31  /// require at least N CBE hits
32  pub min_hit_cbe         : u8  ,
33  /// require at least N UMB hits
34  pub min_hit_umb         : u8  ,
35  pub max_hit_cor         : u8  ,
36  pub max_hit_cbe         : u8  ,
37  pub max_hit_umb         : u8  ,
38  pub min_hit_all         : u8  ,
39  pub max_hit_all         : u8  ,
40  pub min_cos_theta       : f32 ,
41  pub max_cos_theta       : f32 ,
42  pub only_causal_hits    : bool,
43  #[serde(skip_serializing)]
44  pub hit_cbe_acc         : u64 ,
45  #[serde(skip_serializing)]
46  pub hit_umb_acc         : u64 ,
47  #[serde(skip_serializing)]
48  pub hit_cor_acc         : u64 ,
49  #[serde(skip_serializing)]
50  pub hit_all_acc         : u64 ,
51  #[serde(skip_serializing)]
52  pub fh_umb_acc          : u64 ,
53  #[serde(skip_serializing)]
54  pub cos_theta_acc       : u64 ,
55  #[serde(skip_serializing)]
56  pub hits_total          : u64 ,
57  #[serde(skip_serializing)]
58  pub hits_rmvd_csl       : u64 ,
59  #[serde(skip_serializing)]
60  pub hits_rmvd_ls        : u64 ,
61  pub fh_must_be_umb      : bool,
62  #[serde(skip_serializing)]
63  pub ls_cleaning_t_err   : f64 ,
64  pub thru_going          : bool,
65  #[serde(skip_serializing)]
66  pub thru_going_acc      : u64 ,
67  pub fhi_not_bot         : bool,
68  #[serde(skip_serializing)]
69  pub fhi_not_bot_acc     : u64 ,
70  pub fho_must_panel7     : bool,
71  #[serde(skip_serializing)]
72  pub fho_must_panel7_acc : u64 ,
73  pub lh_must_panel2      : bool, 
74  #[serde(skip_serializing)]
75  pub lh_must_panel2_acc  : u64 ,
76  pub hit_high_edep       : bool,
77  #[serde(skip_serializing)]
78  pub hit_high_edep_acc   : u64 , 
79}
80
81impl TofCuts {
82
83  pub fn new() -> Self {
84    Self {
85      min_hit_cor         : 0  ,
86      min_hit_cbe         : 0  ,
87      min_hit_umb         : 0  ,
88      max_hit_cor         : 161,
89      max_hit_cbe         : 161,
90      max_hit_umb         : 161,
91      min_hit_all         : 0  ,
92      max_hit_all         : 161,
93      min_cos_theta       : 0.0,
94      max_cos_theta       : 1.0,
95      only_causal_hits    : false,
96      hit_cbe_acc         : 0,
97      hit_umb_acc         : 0,
98      hit_cor_acc         : 0,
99      hit_all_acc         : 0,
100      cos_theta_acc       : 0,
101      nevents             : 0,
102      hits_total          : 0,
103      hits_rmvd_csl       : 0,
104      hits_rmvd_ls        : 0,
105      fh_must_be_umb      : false,
106      fh_umb_acc          : 0 ,
107      ls_cleaning_t_err   : NO_LIGHTSPEED_CUTS ,
108      thru_going          : false,
109      thru_going_acc      : 0 ,
110      fhi_not_bot         : false,
111      fhi_not_bot_acc     : 0 ,
112      fho_must_panel7     : false,
113      fho_must_panel7_acc : 0 ,
114      lh_must_panel2      : false, 
115      lh_must_panel2_acc  : 0 ,
116      hit_high_edep       : false,
117      hit_high_edep_acc   : 0 , 
118    }
119  }
120  
121  /// Write the settings to a toml file
122  pub fn to_toml(&self, mut filename : String) {
123    if !filename.ends_with(".toml") {
124      filename += ".toml";
125    }
126    info!("Will write to file {}!", filename);
127    match File::create(&filename) {
128      Err(err) => {
129        error!("Unable to open file {}! {}", filename, err);
130      }
131      Ok(mut file) => {
132        match toml::to_string_pretty(&self) {
133          Err(err) => {
134            error!("Unable to serialize toml! {err}");
135          }
136          Ok(toml_string) => {
137            match file.write_all(toml_string.as_bytes()) {
138              Err(err) => error!("Unable to write to file {}! {}", filename, err),
139              Ok(_)    => debug!("Wrote settings to {}!", filename)
140            }
141          }
142        }
143      }
144    }
145  }
146  
147  pub fn from_toml(filename : &str) -> Result<Self, SerializationError> {
148    match File::open(filename) {
149      Err(err) => {
150        error!("Unable to open {}! {}", filename, err);
151        return Err(SerializationError::TomlDecodingError);
152      }
153      Ok(mut file) => {
154        let mut toml_string = String::from("");
155        match file.read_to_string(&mut toml_string) {
156          Err(err) => {
157            error!("Unable to read {}! {}", filename, err);
158            return Err(SerializationError::TomlDecodingError);
159          }
160          Ok(_) => {
161            match toml::from_str(&toml_string) {
162              Err(err) => {
163                error!("Can't interpret toml! {}", err);
164                return Err(SerializationError::TomlDecodingError);
165              }
166              Ok(cuts) => {
167                return Ok(cuts);
168              }
169            }
170          }
171        }
172      }
173    }
174  }
175
176  /// Can two cut instances be added? 
177  ///
178  /// Void cuts will automatically be comptaible
179  pub fn is_compatible(&self, other : &TofCuts) -> bool {
180    if self.only_causal_hits != other.only_causal_hits {
181      return false;
182    }
183    if self.min_hit_cor  != other.min_hit_cor {
184      return false;
185    }
186    if self.min_hit_cbe  != other.min_hit_cbe {
187      return false;
188    }
189    if self.min_hit_umb  != other.min_hit_umb {
190      return false;
191    }
192    if self.max_hit_cor  != other.max_hit_cor {
193      return false;
194    }
195    if self.max_hit_cbe  != other.max_hit_cbe {
196      return false;
197    }
198    if self.max_hit_umb  != other.max_hit_umb {
199      return false;
200    }
201    if self.min_hit_all  != other.min_hit_all {
202      return false;
203    }
204    if self.max_hit_all  != other.max_hit_all {
205      return false;
206    }
207    if self.ls_cleaning_t_err != other.ls_cleaning_t_err {
208      return false;
209    }
210    if self.fh_must_be_umb != other.fh_must_be_umb {
211      return false;
212    } 
213    if self.thru_going != other.thru_going {
214      return false;
215    }
216    if self.fhi_not_bot != other.fhi_not_bot {
217      return false;
218    }
219    if self.min_cos_theta != other.min_cos_theta {
220      return false; 
221    }
222    if self.max_cos_theta != other.max_cos_theta {
223      return false; 
224    }
225    if self.fho_must_panel7 != other.fho_must_panel7 {
226      return false; 
227    }
228    if self.lh_must_panel2 != other.lh_must_panel2 {
229      return false; 
230    }
231    if self.hit_high_edep != other.hit_high_edep { 
232      return false;
233    }
234    true
235  }
236
237
238  /// Zero out the event counter variables
239  pub fn clear_stats(&mut self) {
240    self.hit_cbe_acc         = 0; 
241    self.hit_umb_acc         = 0; 
242    self.hit_cor_acc         = 0;
243    self.hit_all_acc         = 0; 
244    self.cos_theta_acc       = 0;
245    self.nevents             = 0;
246    self.hits_total          = 0;
247    self.hits_rmvd_csl       = 0;
248    self.hits_rmvd_ls        = 0;
249    self.fh_umb_acc          = 0;
250    self.thru_going_acc      = 0;
251    self.fhi_not_bot_acc     = 0;
252    self.fho_must_panel7_acc = 0; 
253    self.lh_must_panel2_acc  = 0; 
254    self.hit_high_edep_acc   = 0;
255  }
256    
257  pub fn is_void(&self) -> bool {
258    if self.min_hit_cor      != 0 {
259      return false;
260    }
261    if self.min_hit_cbe      != 0 {
262      return false;
263    }
264    if self.min_hit_umb      != 0 {
265      return false;
266    }
267    if self.max_hit_cor      != 161 {
268      return false;
269    }
270    if self.max_hit_cbe      != 161 {
271      return false;
272    }
273    if self.max_hit_umb      != 161 {
274      return false;  
275    }
276    if self.min_hit_all      != 0 {
277      return false;
278    }
279    if self.max_hit_all      != 161 {
280      return false;
281    }
282    if self.only_causal_hits {
283      return false;
284    }
285    if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
286      return false;
287    }
288    if self.fh_must_be_umb != false {
289      return false;
290    }
291    if self.thru_going != false {
292      return false;
293    }
294    if self.fhi_not_bot != false {
295      return false;
296    }
297    if self.min_cos_theta != 0.0 {
298      return false; 
299    }
300    if self.max_cos_theta != 1.0 {
301      return false; 
302    }
303    if self.fho_must_panel7 {
304      return false; 
305    }
306    if self.lh_must_panel2 {
307      return false; 
308    }
309    if self.hit_high_edep {
310      return false;
311    }
312    return true;
313  }
314
315  pub fn get_acc_frac_hit_umb(&self) -> f64 {
316    if self.nevents == 0 {
317      return 0.0;
318    }
319    self.hit_umb_acc as f64/(self.nevents as f64)
320  }
321  
322  pub fn get_acc_frac_hit_cbe(&self) -> f64 {
323    if self.nevents == 0 {
324      return 0.0;
325    }
326    self.hit_cbe_acc as f64/(self.nevents as f64)
327  }
328  
329  pub fn get_acc_frac_hit_cor(&self) -> f64 {
330    if self.nevents == 0 {
331      return 0.0;
332    }
333    self.hit_cor_acc as f64/(self.nevents as f64)
334  }
335  
336  pub fn get_acc_frac_hit_all(&self) -> f64 {
337    if self.nevents == 0 {
338      return 0.0;
339    }
340    self.hit_all_acc as f64/(self.nevents as f64)
341  }
342  
343  pub fn get_acc_frac_cos_theta(&self) -> f64 {
344    if self.nevents == 0 {
345      return 0.0;
346    }
347    self.cos_theta_acc as f64/(self.nevents as f64)
348  }
349  
350  pub fn get_acc_frac_fh_must_be_umb(&self)  -> f64 {
351    if self.nevents == 0 {
352      return 0.0;
353    }
354    self.fh_umb_acc as f64/(self.nevents as f64)
355  }
356
357  pub fn get_acc_frac_thru_going(&self)  -> f64 {
358    if self.nevents == 0 {
359      return 0.0;
360    }
361    self.thru_going_acc as f64/(self.nevents as f64)
362  }
363
364  pub fn get_acc_frac_fhi_not_bot(&self) -> f64 {
365    if self.nevents == 0 {
366      return 0.0;
367    }
368    self.fhi_not_bot_acc as f64/(self.nevents as f64) 
369  }
370
371  pub fn get_acc_frac_fho_must_panel7(&self) -> f64 {
372    if self.nevents == 0 {
373      return 0.0;
374    }
375    self.fho_must_panel7_acc as f64/(self.nevents as f64)
376  }
377  
378  pub fn get_acc_frac_lh_must_panel2(&self) -> f64 {
379    if self.nevents == 0 {
380      return 0.0;
381    }
382    self.lh_must_panel2_acc as f64/(self.nevents as f64)
383  }
384  
385  pub fn get_acc_frac_hit_high_edep(&self) -> f64 {
386    if self.nevents == 0 {
387      return 0.0;
388    }
389    self.hit_high_edep_acc as f64/(self.nevents as f64)
390  }
391
392  
393  /// Check if an event passes the selection
394  /// and update the counters. 
395  /// If cleanings are enabled, this will 
396  /// change the event in-place!
397  #[cfg(feature="database")]
398  pub fn accept(&mut self, ev : &mut TofEvent) -> bool {
399    if self.is_void() {
400      return true;
401    }
402    // The order of events is important. Hit cleaning 
403    // comes before the application of cuts.
404    let nhits        = ev.get_nhits() as u64;
405    self.hits_total += nhits;
406    self.nevents    += 1;
407    // we need to make sure the times are 
408    // calculated.properly. If they are, 
409    // this won't do anything
410    ev.normalize_hit_times();
411    if self.only_causal_hits {
412      let rm_pids = ev.remove_non_causal_hits();
413      self.hits_rmvd_csl  += rm_pids.len() as u64;
414    }
415    if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
416      // FIXME - change type of ls_cleaning_t_err to f32
417      let rm_pids_ls       = ev.lightspeed_cleaning(self.ls_cleaning_t_err as f32);
418      self.hits_rmvd_ls   += rm_pids_ls.0.len() as u64;
419    }
420    // get number of cbe/umb/cor hits - only for valid hits
421    let nhits_cbe   = ev.get_nhits_cbe() as u64;
422    let nhits_umb   = ev.get_nhits_umb() as u64;
423    let nhits_cor   = ev.get_nhits_cor() as u64;
424    let clean_nhits = nhits_cbe + nhits_umb + nhits_cor;
425    // check for min/max hits on cbe, umb, cor
426    // these cuts are combined with AND
427    if !((self.min_hit_all as u64 <= clean_nhits) && (clean_nhits <= self.max_hit_all as u64)) {
428      return false;
429    } else { 
430      self.hit_all_acc += 1;
431    }
432    if !((self.min_hit_cbe as u64<= nhits_cbe) && (nhits_cbe <= self.max_hit_cbe as u64)) {
433      return false;
434    } else {
435      self.hit_cbe_acc += 1;
436    }
437    if !((self.min_hit_umb as u64 <= nhits_umb) && (nhits_umb <= self.max_hit_umb as u64)) {
438      return false;
439    } else {
440      self.hit_umb_acc += 1;
441    }
442    if !((self.min_hit_cor as u64 <= nhits_cor) && (nhits_cor <= self.max_hit_cor as u64)) {
443      return false;
444    } else {
445      self.hit_cor_acc += 1;
446    }
447    //# at this point, it can still be that we don't have any TOF hits at all
448    //# the following set of cuts can only be calculated if there are hits
449    //#no_cos_possible = False 
450    if self.fh_must_be_umb 
451      || self.thru_going 
452      || self.fhi_not_bot 
453      || (self.min_cos_theta != 0.0) 
454      || (self.max_cos_theta != 1.0) 
455      || self.fho_must_panel7 
456      || self.lh_must_panel2 
457      || self.hit_high_edep {
458      // in this casese, we need inner and outer hits and have them sorted 
459          ev.hits.sort_by(|a,b| a.event_t0.partial_cmp(&b.event_t0).unwrap_or(Ordering::Greater));
460          let hits_sorted = &ev.hits;
461          if hits_sorted.len() == 0 {
462            //if we don't have hits, we also don't fulfill any of these conditions. simple.
463            return false;
464          }
465          let first_pid  = hits_sorted[0].paddle_id; 
466          let last_pid   = hits_sorted.last().expect("No HITS!").paddle_id;
467          let hits_inner: Vec<&TofHit> = hits_sorted.iter()
468                                         .filter(|k| k.paddle_id < 61)
469                                         .collect();
470          let hits_outer: Vec<&TofHit> = hits_sorted.iter()
471                                         .filter(|k| k.paddle_id > 60)
472                                         .collect();
473      //# now we are sure that there are hits
474      if self.fh_must_be_umb {
475        if  (first_pid < 61) || (first_pid > 108) {
476          return false;
477        } else {
478          self.fh_umb_acc += 1;
479        }
480      } else {
481        self.fh_umb_acc += 1;
482      }
483      if self.thru_going {
484        //if  (last_pid in range(13,25) or 108 < last_pid):
485        if (last_pid >= 13 && last_pid < 25) || 108 < last_pid {
486          self.thru_going_acc += 1;
487        } else {
488          return false;
489        }
490      } else {
491        self.thru_going_acc += 1;
492      } 
493      if self.fhi_not_bot {
494        if hits_inner.len() == 0 {
495            self.fhi_not_bot_acc += 1;
496        } else if (12 < hits_inner[0].paddle_id) && (hits_inner[0].paddle_id < 25) {
497          return false;
498        } else {
499          self.fhi_not_bot_acc += 1;
500        }
501      } else {
502        self.fhi_not_bot_acc += 1;
503      } 
504      if self.min_cos_theta != 0.0 || self.max_cos_theta != 1.0 {
505        let dist = hits_inner[0].distance(hits_outer[0])/1000.0;
506        let cos_theta = f32::abs(hits_inner[0].z - hits_outer[0].z)/(1000.0*dist);  
507        if !((self.min_cos_theta <= cos_theta) && (cos_theta <= self.max_cos_theta)) {
508          return false;
509        } else {
510          self.cos_theta_acc += 1;
511        }
512        self.cos_theta_acc += 1;
513      }
514      if self.fho_must_panel7 {
515        //if first_pid not in range(61, 73):
516        if first_pid < 61 || first_pid >= 72 {
517          return false; 
518        } else {
519          self.fho_must_panel7_acc += 1;
520        }
521      }
522      if self.lh_must_panel2 {
523        if last_pid < 13 || last_pid > 24 {
524          return false; 
525        } else {
526          self.lh_must_panel2_acc += 1;
527        }
528      }
529      if self.hit_high_edep {
530        let mut found = false; 
531        for h in hits_sorted {
532          if h.get_edep() > 20.0 {
533            self.lh_must_panel2_acc += 1;
534            found = true;
535            break;
536          }
537        }
538        if !found {
539          return false;
540        }
541      }
542    }
543    // if we arrive here, we passed everything
544    true
545  }
546
547  /// Print out nicely formatted efficiencies
548  pub fn pretty_print_efficiency(&self) -> String {
549    let mut repr =  String::from("-- -- -- -- -- -- -- -- -- -- --");
550    repr += &(format!("\n  TOTAL EVENTS : {}", self.nevents));
551    repr += &(format!("\n    {} <= NHit(UMB) <= {} : {:.2} %", self.min_hit_umb, self.max_hit_umb, 100.0*self.get_acc_frac_hit_umb())); 
552    repr += &(format!("\n    {} <= NHit(CBE) <= {} : {:.2} %", self.min_hit_cbe, self.max_hit_cbe, 100.0*self.get_acc_frac_hit_cbe())); 
553    repr += &(format!("\n    {} <= NHit(COR) <= {} : {:.2} %", self.min_hit_cor, self.max_hit_cor, 100.0*self.get_acc_frac_hit_cor())); 
554    repr += &(format!("\n    {} <= NHit(TOF) <= {} : {:.2} %", self.min_hit_all, self.max_hit_all, 100.0*self.get_acc_frac_hit_all())); 
555    repr += &(format!("\n    {} <= COS(THET) <= {} : {:.2} %", self.min_cos_theta, self.max_cos_theta, self.get_acc_frac_cos_theta()));  
556    if self.only_causal_hits {
557      if self.hits_total > 0 {
558        repr += &(format!("\n Removed {:.2} % of hits due to causality cut!", 100.0*self.hits_rmvd_csl as f64/self.hits_total as f64));
559      }
560    }
561    if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
562      if self.hits_total > 0 {
563        repr += &(format!("\n Removed {:.2} % of hits due to lightspeed cut!", 100.0*(self.hits_rmvd_ls as f64)/self.hits_total as f64));
564      }
565    }
566    if self.fh_must_be_umb {
567      repr += "\n First hit must be on UMB!";
568      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_fh_must_be_umb()));
569    }
570    if self.thru_going {
571      repr += "\n Require through-going track!";
572      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_thru_going()));
573    }
574    if self.fhi_not_bot {
575      repr += "\n Require first hit on the inner TOF can not be on the Bottom 12PP";
576      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_fhi_not_bot()));
577    }
578    if self.fho_must_panel7 {
579      repr += "\n Require first hit on the outer TOF must be on panel7";
580      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_fho_must_panel7()));
581    }
582    if self.lh_must_panel2 {
583      repr += "\n Require last hit must be on the bottom CBE panel";
584      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_lh_must_panel2()));
585    }
586    if self.hit_high_edep {
587      repr += "\n Require that one hit has an edep > 20MeV";
588      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_hit_high_edep()));
589    }
590    repr +=  "\n-- -- -- -- -- -- -- -- -- -- --";
591    //println!("{}",repr);
592    repr
593  }
594}
595
596impl Default for TofCuts {
597  fn default() -> Self {
598    Self::new()
599  }
600}
601
602impl fmt::Display for TofCuts {
603  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
604    let mut repr = String::from("<TofCuts:");
605    if self.is_void() {
606      repr += " (void)>";
607    } else {
608      if self.only_causal_hits {
609        repr += &(format!("\n -- removes non-causal hits!"));
610      }
611      // FIXME - 1e9 is a magic value
612      if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
613        repr += "\n -- removes hits which are not correlated with the first hit!";
614        repr += &(format!("\n --   assumed timing error {}", self.ls_cleaning_t_err));
615      }
616      if self.fh_must_be_umb {
617        repr += "\n -- first hit must be on UMB";
618      }
619      if self.thru_going {
620        repr += "\n -- require last hit on CBE BOT or COR (thru-going tracks)";
621      }
622      if self.fhi_not_bot {
623        repr += "\n -- require that the first hit on the inner TOF is not on CBE BOT";
624      }
625      if self.fho_must_panel7 {
626        repr += "\n -- require that the first hit on the outer TOF is on panel7";
627      }
628      if self.lh_must_panel2 {
629        repr += "\n -- require that the last hit on the inner TOF is on CBE BOT";
630      }
631      if self.hit_high_edep {
632        repr += "\n -- require that at least one hit has an edep of > 29MeV";
633      }
634      repr += &(format!("\n  {} <= NHit(UMB) <= {}", self.min_hit_umb, self.max_hit_umb)); 
635      repr += &(format!("\n  {} <= NHit(CBE) <= {}", self.min_hit_cbe, self.max_hit_cbe)); 
636      repr += &(format!("\n  {} <= NHit(COR) <= {}", self.min_hit_cor, self.max_hit_cor)); 
637      repr += &(format!("\n  {} <= NHit(TOF) <= {}", self.min_hit_all, self.max_hit_all)); 
638      repr += &(format!("\n  {} <= COS(THET) <= {}", self.min_cos_theta, self.max_cos_theta)); 
639      repr += ">";
640    }
641    write!(f, "{}", repr)
642  }
643}
644
645impl AddAssign for TofCuts {
646  fn add_assign(&mut self, other : Self) {
647    if !self.is_compatible(&other) {
648      // not sure if that should raise panic?
649      panic!("Cuts are not compatible!");
650    }
651    self.nevents             += other.nevents;
652    self.hit_cbe_acc         += other.hit_cbe_acc; 
653    self.hit_umb_acc         += other.hit_umb_acc; 
654    self.hit_cor_acc         += other.hit_cor_acc;
655    self.hit_all_acc         += other.hit_all_acc;
656    self.cos_theta_acc       += other.cos_theta_acc; 
657    self.hits_total          += other.hits_total;
658    self.hits_rmvd_csl       += other.hits_rmvd_csl;
659    self.hits_rmvd_ls        += other.hits_rmvd_ls;
660    self.fh_umb_acc          += other.fh_umb_acc;
661    self.thru_going_acc      += other.thru_going_acc;
662    self.fhi_not_bot_acc     += other.fhi_not_bot_acc;
663    self.fho_must_panel7_acc += other.fho_must_panel7_acc; 
664    self.lh_must_panel2_acc  += other.lh_must_panel2_acc;
665    self.hit_high_edep_acc   += other.hit_high_edep_acc;
666  }
667}
668
669impl Add for TofCuts {
670
671  type Output = TofCuts;
672
673  fn add(self, other: Self) -> Self::Output {
674    let mut output = self.clone();
675    output += other;
676    return output;
677  }
678}
679
680#[cfg(feature="pybindings")]
681#[pymethods]
682impl TofCuts {
683 
684  /// Return a literal full deep copy 
685  /// of the instance
686  fn copy(&self) -> Self {
687    self.clone()
688  }
689
690  #[pyo3(name="is_compatible")]
691  fn is_compatible_py(&self, other : &Self) -> bool {
692    self.is_compatible(other)
693  }
694
695  #[pyo3(name = "clear_stats")]
696  fn clear_stats_py(&mut self) {
697    self.clear_stats();
698  }
699
700  #[cfg(feature="database")]
701  #[pyo3(name="accept")]
702  fn accept_py(&mut self, event : &mut TofEvent) -> bool {
703    self.accept(event)
704  }
705
706  #[getter]
707  fn get_min_hit_cor        (&self) -> u8   {
708    self.min_hit_cor
709  }
710
711  #[setter]
712  fn set_min_hit_cor(&mut self, value : u8) -> PyResult<()> {
713    self.min_hit_cor = value;
714    Ok(())
715  }
716
717  fn __iadd__(&mut self, other : &TofCuts) {
718    self.add_assign(*other);
719  }
720
721  fn __add__(&self, other : &TofCuts) -> TofCuts {
722    let other_c = other.clone();
723    self.add(other_c)
724  }
725 
726  #[pyo3(name="to_toml")]
727  fn to_toml_py(&self, filename : String) {
728    self.to_toml(filename); 
729  }
730  
731  #[staticmethod]
732  #[pyo3(name="from_toml")]
733  fn from_toml_py(filename : String) -> PyResult<Self> {
734    match Self::from_toml(&filename) {
735      Err(err)       => {
736        return Err(PyValueError::new_err(err.to_string()));
737      }
738      Ok(cuts_)  => {
739        return Ok(cuts_);
740      }
741    }
742  }
743
744  #[getter]
745  fn void(&self) -> bool {
746    self.is_void()
747  }
748
749  /// Return a prettily formated string with 
750  /// the efficiency information for all the 
751  /// individual cuts
752  #[pyo3(name="pretty_print_efficiency")]
753  fn pretty_print_efficiency_py(&self) -> String {
754    self.pretty_print_efficiency()
755  }
756
757  #[getter]
758  fn get_min_hit_cbe        (&self) -> u8   {
759    self.min_hit_cbe
760  }
761  
762  #[setter]
763  fn set_min_hit_cbe(&mut self, value : u8) -> PyResult<()> {
764    self.min_hit_cbe = value;
765    Ok(())
766  }
767
768  #[getter]
769  fn get_min_hit_umb        (&self) -> u8   {
770    self.min_hit_umb
771  }
772  
773  #[setter]
774  fn set_min_hit_umb(&mut self, value : u8) -> PyResult<()> {
775    self.min_hit_umb = value;
776    Ok(())
777  }
778
779  #[getter]
780  fn get_max_hit_cor        (&self) -> u8   {
781    self.max_hit_cor
782  }
783  
784  #[setter]
785  fn set_max_hit_cor(&mut self, value : u8) -> PyResult<()> {
786    self.max_hit_cor = value;
787    Ok(())
788  }
789
790  #[getter]
791  fn get_max_hit_cbe        (&self) -> u8   {
792    self.max_hit_cbe
793  }
794  
795  #[setter]
796  fn set_max_hit_cbe(&mut self, value : u8) -> PyResult<()> {
797    self.max_hit_cbe = value;
798    Ok(())
799  }
800
801  #[getter]
802  fn get_max_hit_umb        (&self) -> u8   {
803    self.max_hit_umb
804  }
805  
806  #[setter]
807  fn set_max_hit_umb(&mut self, value : u8) -> PyResult<()> {
808    self.max_hit_umb = value;
809    Ok(())
810  }
811
812  #[getter]
813  fn get_min_hit_all        (&self) -> u8   {
814    self.min_hit_all
815  }
816  
817  #[setter]
818  fn set_min_hit_all(&mut self, value : u8) -> PyResult<()> {
819    self.min_hit_all = value;
820    Ok(())
821  }
822
823  #[getter]
824  fn get_max_hit_all        (&self) -> u8   {
825    self.max_hit_all
826  }
827  
828  #[setter]
829  fn set_max_hit_all(&mut self, value : u8) -> PyResult<()> {
830    self.max_hit_all = value;
831    Ok(())
832  }
833
834  #[getter]
835  fn get_min_cos_theta      (&self) -> f32  {
836    self.min_cos_theta
837  }
838  
839  #[setter]
840  fn set_min_cos_theta(&mut self, value : f32) -> PyResult<()> {
841    self.min_cos_theta = value;
842    Ok(())
843  }
844
845  #[getter]
846  fn get_max_cos_theta      (&self) -> f32  {
847    self.max_cos_theta
848  }
849  
850  #[setter]
851  fn set_max_cos_theta(&mut self, value : f32) -> PyResult<()> {
852    self.max_cos_theta = value;
853    Ok(())
854  }
855
856  #[getter]
857  fn get_only_causal_hits   (&self) -> bool {
858    self.only_causal_hits
859  }
860  
861  #[setter]
862  fn set_only_causal_hits(&mut self, value : bool) -> PyResult<()> {
863    self.only_causal_hits = value;
864    Ok(())
865  }
866
867  #[getter]
868  fn get_hit_cbe_acc        (&self) -> u64  {
869    self.hit_cbe_acc
870  }
871
872  #[getter]
873  fn get_hit_umb_acc        (&self) -> u64  {
874    self.hit_umb_acc
875  }
876
877  #[getter]
878  fn get_hit_cor_acc        (&self) -> u64  {
879    self.hit_cor_acc
880  }
881
882  #[getter]
883  fn get_hit_all_acc        (&self) -> u64  {
884    self.hit_all_acc
885  }
886
887  #[getter]
888  fn get_cos_theta_acc      (&self) -> u64  {
889    self.cos_theta_acc
890  }
891
892  #[getter]
893  fn get_nevents            (&self) -> u64  {
894    self.nevents
895  }
896
897  #[getter]
898  fn get_hits_total         (&self) -> u64  {
899    self.hits_total
900  }
901
902  #[getter]
903  fn get_hits_rmvd_csl      (&self) -> u64  {
904    self.hits_rmvd_csl
905  }
906
907  #[getter]
908  fn get_hits_rmvd_ls       (&self) -> u64  {
909    self.hits_rmvd_ls 
910  }
911
912  #[getter]
913  fn get_fh_must_be_umb     (&self) -> bool {
914    self.fh_must_be_umb
915  }
916  
917  #[setter]
918  fn set_fh_must_be_umb(&mut self, value : bool) -> PyResult<()> {
919    self.fh_must_be_umb = value;
920    Ok(())
921  }
922
923  #[getter]
924  fn get_fh_umb_acc         (&self) -> u64  {
925    self.fh_umb_acc
926  }
927
928  #[getter]
929  fn get_ls_cleaning_t_err  (&self) -> f64  {
930    self.ls_cleaning_t_err
931  }
932  
933  #[setter]
934  fn set_ls_cleaning_t_err(&mut self, value : f64) -> PyResult<()> {
935    self.ls_cleaning_t_err = value;
936    Ok(())
937  }
938
939  #[getter]
940  fn get_thru_going         (&self) -> bool {
941    self.thru_going
942  }
943  
944  #[setter]
945  fn set_thru_going(&mut self, value : bool) -> PyResult<()> {
946    self.thru_going = value;
947    Ok(())
948  }
949
950  #[getter]
951  fn get_thru_going_acc     (&self) -> u64  {
952    self.thru_going_acc
953  }
954
955  #[getter]
956  fn get_fhi_not_bot        (&self) -> bool {
957    self.fhi_not_bot
958  }
959  
960  #[setter]
961  fn set_fhi_not_bot(&mut self, value : bool) -> PyResult<()> {
962    self.fhi_not_bot = value;
963    Ok(())
964  }
965
966  #[getter]
967  fn get_fhi_not_bot_acc    (&self) -> u64  {
968    self.fhi_not_bot_acc
969  }
970
971  #[getter]
972  fn get_fho_must_panel7    (&self) -> bool {
973    self.fho_must_panel7
974  }
975  
976  #[setter]
977  fn set_fho_must_panel7(&mut self, value : bool) -> PyResult<()> {
978    self.fho_must_panel7 = value;
979    Ok(())
980  }
981
982  #[getter]
983  fn get_fho_must_panel7_acc(&self) -> u64  {
984    self.fho_must_panel7_acc
985  }
986
987  #[getter]
988  fn get_lh_must_panel2     (&self) -> bool {
989    self.lh_must_panel2
990  }
991  
992  #[setter]
993  fn set_lh_must_panel2(&mut self, value : bool) -> PyResult<()> {
994    self.lh_must_panel2 = value;
995    Ok(())
996  }
997
998  #[getter]
999  fn get_lh_must_panel2_acc (&self) -> u64  {
1000    self.lh_must_panel2_acc
1001  }
1002
1003  #[getter]
1004  fn get_hit_high_edep      (&self) -> bool {
1005    self.hit_high_edep 
1006  }
1007  
1008  #[setter]
1009  fn set_hit_high_edep(&mut self, value : bool) -> PyResult<()> {
1010    self.hit_high_edep = value;
1011    Ok(())
1012  }
1013
1014  #[getter]
1015  #[pyo3(name="acc_frac_hit_umb")]
1016  fn get_acc_frac_hit_umb_py(&self) -> f64 {
1017    self.get_acc_frac_hit_umb()
1018  } 
1019  
1020  #[getter]
1021  #[pyo3(name="acc_frac_hit_cbe")]
1022  fn get_acc_frac_hit_cbe_py(&self) -> f64 {
1023    self.get_acc_frac_hit_cbe()
1024  }
1025  
1026  #[getter]
1027  #[pyo3(name="acc_frac_hit_cor")]
1028  fn get_acc_frac_hit_cor_py(&self) -> f64 {
1029    self.get_acc_frac_hit_cor()
1030  }
1031  
1032  #[getter]
1033  #[pyo3(name="acc_frac_hit_all")]
1034  fn get_acc_frac_hit_all_py(&self) -> f64 {
1035    self.get_acc_frac_hit_all()
1036  }
1037  
1038  #[getter]
1039  #[pyo3(name="acc_frac_cos_theta")]
1040  fn get_acc_frac_cos_theta_py(&self) -> f64 {
1041    self.get_acc_frac_cos_theta()
1042  }
1043  
1044  #[getter]
1045  #[pyo3(name="acc_frac_fh_must_be_umb")]
1046  fn get_acc_frac_fh_must_be_umb_py(&self)  -> f64 {
1047    self.get_acc_frac_fh_must_be_umb()
1048  }
1049
1050  #[getter]
1051  #[pyo3(name="get_frac_thru_going")]
1052  fn get_acc_frac_thru_going_py(&self)  -> f64 {
1053    self.get_acc_frac_thru_going()
1054  }
1055
1056  #[getter]
1057  #[pyo3(name="acc_frac_fhi_not_bot")]
1058  fn get_acc_frac_fhi_not_bot_py(&self) -> f64 {
1059    self.get_acc_frac_fhi_not_bot()
1060  }
1061
1062  #[getter]
1063  #[pyo3(name="acc_frac_fho_must_panel7")]
1064  fn get_acc_frac_fho_must_panel7_py(&self) -> f64 {
1065    self.get_acc_frac_fho_must_panel7()
1066  }
1067  
1068  #[getter]
1069  #[pyo3(name="acc_frac_lh_must_panel2")]
1070  fn get_acc_frac_lh_must_panel2_py(&self) -> f64 {
1071    self.get_acc_frac_lh_must_panel2()
1072  }
1073
1074  #[getter]
1075  fn get_hit_high_edep_acc  (&self) -> u64  {
1076    self.hit_high_edep_acc
1077  }
1078}
1079
1080#[cfg(feature="pybindings")]
1081pythonize!(TofCuts);
1082