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  pub fn accept(&mut self, ev : &mut TofEvent) -> bool {
398    if self.is_void() {
399      return true;
400    }
401    // The order of events is important. Hit cleaning 
402    // comes before the application of cuts.
403    let nhits        = ev.get_nhits() as u64;
404    self.hits_total += nhits;
405    self.nevents    += 1;
406    // we need to make sure the times are 
407    // calculated.properly. If they are, 
408    // this won't do anything
409    ev.normalize_hit_times();
410    if self.only_causal_hits {
411      let rm_pids = ev.remove_non_causal_hits();
412      self.hits_rmvd_csl  += rm_pids.len() as u64;
413    }
414    if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
415      // FIXME - change type of ls_cleaning_t_err to f32
416      let rm_pids_ls       = ev.lightspeed_cleaning(self.ls_cleaning_t_err as f32);
417      self.hits_rmvd_ls   += rm_pids_ls.0.len() as u64;
418    }
419    // get number of cbe/umb/cor hits - only for valid hits
420    let nhits_cbe   = ev.get_nhits_cbe() as u64;
421    let nhits_umb   = ev.get_nhits_umb() as u64;
422    let nhits_cor   = ev.get_nhits_cor() as u64;
423    let clean_nhits = nhits_cbe + nhits_umb + nhits_cor;
424    // check for min/max hits on cbe, umb, cor
425    // these cuts are combined with AND
426    if !((self.min_hit_all as u64 <= clean_nhits) && (clean_nhits <= self.max_hit_all as u64)) {
427      return false;
428    } else { 
429      self.hit_all_acc += 1;
430    }
431    if !((self.min_hit_cbe as u64<= nhits_cbe) && (nhits_cbe <= self.max_hit_cbe as u64)) {
432      return false;
433    } else {
434      self.hit_cbe_acc += 1;
435    }
436    if !((self.min_hit_umb as u64 <= nhits_umb) && (nhits_umb <= self.max_hit_umb as u64)) {
437      return false;
438    } else {
439      self.hit_umb_acc += 1;
440    }
441    if !((self.min_hit_cor as u64 <= nhits_cor) && (nhits_cor <= self.max_hit_cor as u64)) {
442      return false;
443    } else {
444      self.hit_cor_acc += 1;
445    }
446    //# at this point, it can still be that we don't have any TOF hits at all
447    //# the following set of cuts can only be calculated if there are hits
448    //#no_cos_possible = False 
449    if self.fh_must_be_umb 
450      || self.thru_going 
451      || self.fhi_not_bot 
452      || (self.min_cos_theta != 0.0) 
453      || (self.max_cos_theta != 1.0) 
454      || self.fho_must_panel7 
455      || self.lh_must_panel2 
456      || self.hit_high_edep {
457      // in this casese, we need inner and outer hits and have them sorted 
458          ev.hits.sort_by(|a,b| a.event_t0.partial_cmp(&b.event_t0).unwrap_or(Ordering::Greater));
459          let hits_sorted = &ev.hits;
460          if hits_sorted.len() == 0 {
461            //if we don't have hits, we also don't fulfill any of these conditions. simple.
462            return false;
463          }
464          let first_pid  = hits_sorted[0].paddle_id; 
465          let last_pid   = hits_sorted.last().expect("No HITS!").paddle_id;
466          let hits_inner: Vec<&TofHit> = hits_sorted.iter()
467                                         .filter(|k| k.paddle_id < 61)
468                                         .collect();
469          let hits_outer: Vec<&TofHit> = hits_sorted.iter()
470                                         .filter(|k| k.paddle_id > 60)
471                                         .collect();
472      //# now we are sure that there are hits
473      if self.fh_must_be_umb {
474        if  (first_pid < 61) || (first_pid > 108) {
475          return false;
476        } else {
477          self.fh_umb_acc += 1;
478        }
479      } else {
480        self.fh_umb_acc += 1;
481      }
482      if self.thru_going {
483        //if  (last_pid in range(13,25) or 108 < last_pid):
484        if (last_pid >= 13 && last_pid < 25) || 108 < last_pid {
485          self.thru_going_acc += 1;
486        } else {
487          return false;
488        }
489      } else {
490        self.thru_going_acc += 1;
491      } 
492      if self.fhi_not_bot {
493        if hits_inner.len() == 0 {
494            self.fhi_not_bot_acc += 1;
495        } else if (12 < hits_inner[0].paddle_id) && (hits_inner[0].paddle_id < 25) {
496          return false;
497        } else {
498          self.fhi_not_bot_acc += 1;
499        }
500      } else {
501        self.fhi_not_bot_acc += 1;
502      } 
503      if self.min_cos_theta != 0.0 || self.max_cos_theta != 1.0 {
504        let dist = hits_inner[0].distance(hits_outer[0])/1000.0;
505        let cos_theta = f32::abs(hits_inner[0].z - hits_outer[0].z)/(1000.0*dist);  
506        if !((self.min_cos_theta <= cos_theta) && (cos_theta <= self.max_cos_theta)) {
507          return false;
508        } else {
509          self.cos_theta_acc += 1;
510        }
511        self.cos_theta_acc += 1;
512      }
513      if self.fho_must_panel7 {
514        //if first_pid not in range(61, 73):
515        if first_pid < 61 || first_pid >= 72 {
516          return false; 
517        } else {
518          self.fho_must_panel7_acc += 1;
519        }
520      }
521      if self.lh_must_panel2 {
522        if last_pid < 13 || last_pid > 24 {
523          return false; 
524        } else {
525          self.lh_must_panel2_acc += 1;
526        }
527      }
528      if self.hit_high_edep {
529        let mut found = false; 
530        for h in hits_sorted {
531          if h.get_edep() > 20.0 {
532            self.lh_must_panel2_acc += 1;
533            found = true;
534            break;
535          }
536        }
537        if !found {
538          return false;
539        }
540      }
541    }
542    // if we arrive here, we passed everything
543    true
544  }
545
546  /// Print out nicely formatted efficiencies
547  pub fn pretty_print_efficiency(&self) -> String {
548    let mut repr =  String::from("-- -- -- -- -- -- -- -- -- -- --");
549    repr += &(format!("\n  TOTAL EVENTS : {}", self.nevents));
550    repr += &(format!("\n    {} <= NHit(UMB) <= {} : {:.2} %", self.min_hit_umb, self.max_hit_umb, 100.0*self.get_acc_frac_hit_umb())); 
551    repr += &(format!("\n    {} <= NHit(CBE) <= {} : {:.2} %", self.min_hit_cbe, self.max_hit_cbe, 100.0*self.get_acc_frac_hit_cbe())); 
552    repr += &(format!("\n    {} <= NHit(COR) <= {} : {:.2} %", self.min_hit_cor, self.max_hit_cor, 100.0*self.get_acc_frac_hit_cor())); 
553    repr += &(format!("\n    {} <= NHit(TOF) <= {} : {:.2} %", self.min_hit_all, self.max_hit_all, 100.0*self.get_acc_frac_hit_all())); 
554    repr += &(format!("\n    {} <= COS(THET) <= {} : {:.2} %", self.min_cos_theta, self.max_cos_theta, self.get_acc_frac_cos_theta()));  
555    if self.only_causal_hits {
556      if self.hits_total > 0 {
557        repr += &(format!("\n Removed {:.2} % of hits due to causality cut!", 100.0*self.hits_rmvd_csl as f64/self.hits_total as f64));
558      }
559    }
560    if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
561      if self.hits_total > 0 {
562        repr += &(format!("\n Removed {:.2} % of hits due to lightspeed cut!", 100.0*(self.hits_rmvd_ls as f64)/self.hits_total as f64));
563      }
564    }
565    if self.fh_must_be_umb {
566      repr += "\n First hit must be on UMB!";
567      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_fh_must_be_umb()));
568    }
569    if self.thru_going {
570      repr += "\n Require through-going track!";
571      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_thru_going()));
572    }
573    if self.fhi_not_bot {
574      repr += "\n Require first hit on the inner TOF can not be on the Bottom 12PP";
575      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_fhi_not_bot()));
576    }
577    if self.fho_must_panel7 {
578      repr += "\n Require first hit on the outer TOF must be on panel7";
579      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_fho_must_panel7()));
580    }
581    if self.lh_must_panel2 {
582      repr += "\n Require last hit must be on the bottom CBE panel";
583      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_lh_must_panel2()));
584    }
585    if self.hit_high_edep {
586      repr += "\n Require that one hit has an edep > 20MeV";
587      repr += &(format!("\n   -- Accepted {:.2} %", 100.0*self.get_acc_frac_hit_high_edep()));
588    }
589    repr +=  "\n-- -- -- -- -- -- -- -- -- -- --";
590    //println!("{}",repr);
591    repr
592  }
593}
594
595impl Default for TofCuts {
596  fn default() -> Self {
597    Self::new()
598  }
599}
600
601impl fmt::Display for TofCuts {
602  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603    let mut repr = String::from("<TofCuts:");
604    if self.is_void() {
605      repr += " (void)>";
606    } else {
607      if self.only_causal_hits {
608        repr += &(format!("\n -- removes non-causal hits!"));
609      }
610      // FIXME - 1e9 is a magic value
611      if self.ls_cleaning_t_err != NO_LIGHTSPEED_CUTS {
612        repr += "\n -- removes hits which are not correlated with the first hit!";
613        repr += &(format!("\n --   assumed timing error {}", self.ls_cleaning_t_err));
614      }
615      if self.fh_must_be_umb {
616        repr += "\n -- first hit must be on UMB";
617      }
618      if self.thru_going {
619        repr += "\n -- require last hit on CBE BOT or COR (thru-going tracks)";
620      }
621      if self.fhi_not_bot {
622        repr += "\n -- require that the first hit on the inner TOF is not on CBE BOT";
623      }
624      if self.fho_must_panel7 {
625        repr += "\n -- require that the first hit on the outer TOF is on panel7";
626      }
627      if self.lh_must_panel2 {
628        repr += "\n -- require that the last hit on the inner TOF is on CBE BOT";
629      }
630      if self.hit_high_edep {
631        repr += "\n -- require that at least one hit has an edep of > 29MeV";
632      }
633      repr += &(format!("\n  {} <= NHit(UMB) <= {}", self.min_hit_umb, self.max_hit_umb)); 
634      repr += &(format!("\n  {} <= NHit(CBE) <= {}", self.min_hit_cbe, self.max_hit_cbe)); 
635      repr += &(format!("\n  {} <= NHit(COR) <= {}", self.min_hit_cor, self.max_hit_cor)); 
636      repr += &(format!("\n  {} <= NHit(TOF) <= {}", self.min_hit_all, self.max_hit_all)); 
637      repr += &(format!("\n  {} <= COS(THET) <= {}", self.min_cos_theta, self.max_cos_theta)); 
638      repr += ">";
639    }
640    write!(f, "{}", repr)
641  }
642}
643
644impl AddAssign for TofCuts {
645  fn add_assign(&mut self, other : Self) {
646    if !self.is_compatible(&other) {
647      // not sure if that should raise panic?
648      panic!("Cuts are not compatible!");
649    }
650    self.nevents             += other.nevents;
651    self.hit_cbe_acc         += other.hit_cbe_acc; 
652    self.hit_umb_acc         += other.hit_umb_acc; 
653    self.hit_cor_acc         += other.hit_cor_acc;
654    self.hit_all_acc         += other.hit_all_acc;
655    self.cos_theta_acc       += other.cos_theta_acc; 
656    self.hits_total          += other.hits_total;
657    self.hits_rmvd_csl       += other.hits_rmvd_csl;
658    self.hits_rmvd_ls        += other.hits_rmvd_ls;
659    self.fh_umb_acc          += other.fh_umb_acc;
660    self.thru_going_acc      += other.thru_going_acc;
661    self.fhi_not_bot_acc     += other.fhi_not_bot_acc;
662    self.fho_must_panel7_acc += other.fho_must_panel7_acc; 
663    self.lh_must_panel2_acc  += other.lh_must_panel2_acc;
664    self.hit_high_edep_acc   += other.hit_high_edep_acc;
665  }
666}
667
668impl Add for TofCuts {
669
670  type Output = TofCuts;
671
672  fn add(self, other: Self) -> Self::Output {
673    let mut output = self.clone();
674    output += other;
675    return output;
676  }
677}
678
679#[cfg(feature="pybindings")]
680#[pymethods]
681impl TofCuts {
682 
683  /// Return a literal full deep copy 
684  /// of the instance
685  fn copy(&self) -> Self {
686    self.clone()
687  }
688
689  #[pyo3(name="is_compatible")]
690  fn is_compatible_py(&self, other : &Self) -> bool {
691    self.is_compatible(other)
692  }
693
694  #[pyo3(name = "clear_stats")]
695  fn clear_stats_py(&mut self) {
696    self.clear_stats();
697  }
698
699  #[pyo3(name="accept")]
700  fn accept_py(&mut self, event : &mut TofEvent) -> bool {
701    self.accept(event)
702  }
703
704  #[getter]
705  fn get_min_hit_cor        (&self) -> u8   {
706    self.min_hit_cor
707  }
708
709  #[setter]
710  fn set_min_hit_cor(&mut self, value : u8) -> PyResult<()> {
711    self.min_hit_cor = value;
712    Ok(())
713  }
714
715  fn __iadd__(&mut self, other : &TofCuts) {
716    self.add_assign(*other);
717  }
718
719  fn __add__(&self, other : &TofCuts) -> TofCuts {
720    let other_c = other.clone();
721    self.add(other_c)
722  }
723 
724  #[pyo3(name="to_toml")]
725  fn to_toml_py(&self, filename : String) {
726    self.to_toml(filename); 
727  }
728  
729  #[staticmethod]
730  #[pyo3(name="from_toml")]
731  fn from_toml_py(filename : String) -> PyResult<Self> {
732    match Self::from_toml(&filename) {
733      Err(err)       => {
734        return Err(PyValueError::new_err(err.to_string()));
735      }
736      Ok(cuts_)  => {
737        return Ok(cuts_);
738      }
739    }
740  }
741
742  #[getter]
743  fn void(&self) -> bool {
744    self.is_void()
745  }
746
747  /// Return a prettily formated string with 
748  /// the efficiency information for all the 
749  /// individual cuts
750  #[pyo3(name="pretty_print_efficiency")]
751  fn pretty_print_efficiency_py(&self) -> String {
752    self.pretty_print_efficiency()
753  }
754
755  #[getter]
756  fn get_min_hit_cbe        (&self) -> u8   {
757    self.min_hit_cbe
758  }
759  
760  #[setter]
761  fn set_min_hit_cbe(&mut self, value : u8) -> PyResult<()> {
762    self.min_hit_cbe = value;
763    Ok(())
764  }
765
766  #[getter]
767  fn get_min_hit_umb        (&self) -> u8   {
768    self.min_hit_umb
769  }
770  
771  #[setter]
772  fn set_min_hit_umb(&mut self, value : u8) -> PyResult<()> {
773    self.min_hit_umb = value;
774    Ok(())
775  }
776
777  #[getter]
778  fn get_max_hit_cor        (&self) -> u8   {
779    self.max_hit_cor
780  }
781  
782  #[setter]
783  fn set_max_hit_cor(&mut self, value : u8) -> PyResult<()> {
784    self.max_hit_cor = value;
785    Ok(())
786  }
787
788  #[getter]
789  fn get_max_hit_cbe        (&self) -> u8   {
790    self.max_hit_cbe
791  }
792  
793  #[setter]
794  fn set_max_hit_cbe(&mut self, value : u8) -> PyResult<()> {
795    self.max_hit_cbe = value;
796    Ok(())
797  }
798
799  #[getter]
800  fn get_max_hit_umb        (&self) -> u8   {
801    self.max_hit_umb
802  }
803  
804  #[setter]
805  fn set_max_hit_umb(&mut self, value : u8) -> PyResult<()> {
806    self.max_hit_umb = value;
807    Ok(())
808  }
809
810  #[getter]
811  fn get_min_hit_all        (&self) -> u8   {
812    self.min_hit_all
813  }
814  
815  #[setter]
816  fn set_min_hit_all(&mut self, value : u8) -> PyResult<()> {
817    self.min_hit_all = value;
818    Ok(())
819  }
820
821  #[getter]
822  fn get_max_hit_all        (&self) -> u8   {
823    self.max_hit_all
824  }
825  
826  #[setter]
827  fn set_max_hit_all(&mut self, value : u8) -> PyResult<()> {
828    self.max_hit_all = value;
829    Ok(())
830  }
831
832  #[getter]
833  fn get_min_cos_theta      (&self) -> f32  {
834    self.min_cos_theta
835  }
836  
837  #[setter]
838  fn set_min_cos_theta(&mut self, value : f32) -> PyResult<()> {
839    self.min_cos_theta = value;
840    Ok(())
841  }
842
843  #[getter]
844  fn get_max_cos_theta      (&self) -> f32  {
845    self.max_cos_theta
846  }
847  
848  #[setter]
849  fn set_max_cos_theta(&mut self, value : f32) -> PyResult<()> {
850    self.max_cos_theta = value;
851    Ok(())
852  }
853
854  #[getter]
855  fn get_only_causal_hits   (&self) -> bool {
856    self.only_causal_hits
857  }
858  
859  #[setter]
860  fn set_only_causal_hits(&mut self, value : bool) -> PyResult<()> {
861    self.only_causal_hits = value;
862    Ok(())
863  }
864
865  #[getter]
866  fn get_hit_cbe_acc        (&self) -> u64  {
867    self.hit_cbe_acc
868  }
869
870  #[getter]
871  fn get_hit_umb_acc        (&self) -> u64  {
872    self.hit_umb_acc
873  }
874
875  #[getter]
876  fn get_hit_cor_acc        (&self) -> u64  {
877    self.hit_cor_acc
878  }
879
880  #[getter]
881  fn get_hit_all_acc        (&self) -> u64  {
882    self.hit_all_acc
883  }
884
885  #[getter]
886  fn get_cos_theta_acc      (&self) -> u64  {
887    self.cos_theta_acc
888  }
889
890  #[getter]
891  fn get_nevents            (&self) -> u64  {
892    self.nevents
893  }
894
895  #[getter]
896  fn get_hits_total         (&self) -> u64  {
897    self.hits_total
898  }
899
900  #[getter]
901  fn get_hits_rmvd_csl      (&self) -> u64  {
902    self.hits_rmvd_csl
903  }
904
905  #[getter]
906  fn get_hits_rmvd_ls       (&self) -> u64  {
907    self.hits_rmvd_ls 
908  }
909
910  #[getter]
911  fn get_fh_must_be_umb     (&self) -> bool {
912    self.fh_must_be_umb
913  }
914  
915  #[setter]
916  fn set_fh_must_be_umb(&mut self, value : bool) -> PyResult<()> {
917    self.fh_must_be_umb = value;
918    Ok(())
919  }
920
921  #[getter]
922  fn get_fh_umb_acc         (&self) -> u64  {
923    self.fh_umb_acc
924  }
925
926  #[getter]
927  fn get_ls_cleaning_t_err  (&self) -> f64  {
928    self.ls_cleaning_t_err
929  }
930  
931  #[setter]
932  fn set_ls_cleaning_t_err(&mut self, value : f64) -> PyResult<()> {
933    self.ls_cleaning_t_err = value;
934    Ok(())
935  }
936
937  #[getter]
938  fn get_thru_going         (&self) -> bool {
939    self.thru_going
940  }
941  
942  #[setter]
943  fn set_thru_going(&mut self, value : bool) -> PyResult<()> {
944    self.thru_going = value;
945    Ok(())
946  }
947
948  #[getter]
949  fn get_thru_going_acc     (&self) -> u64  {
950    self.thru_going_acc
951  }
952
953  #[getter]
954  fn get_fhi_not_bot        (&self) -> bool {
955    self.fhi_not_bot
956  }
957  
958  #[setter]
959  fn set_fhi_not_bot(&mut self, value : bool) -> PyResult<()> {
960    self.fhi_not_bot = value;
961    Ok(())
962  }
963
964  #[getter]
965  fn get_fhi_not_bot_acc    (&self) -> u64  {
966    self.fhi_not_bot_acc
967  }
968
969  #[getter]
970  fn get_fho_must_panel7    (&self) -> bool {
971    self.fho_must_panel7
972  }
973  
974  #[setter]
975  fn set_fho_must_panel7(&mut self, value : bool) -> PyResult<()> {
976    self.fho_must_panel7 = value;
977    Ok(())
978  }
979
980  #[getter]
981  fn get_fho_must_panel7_acc(&self) -> u64  {
982    self.fho_must_panel7_acc
983  }
984
985  #[getter]
986  fn get_lh_must_panel2     (&self) -> bool {
987    self.lh_must_panel2
988  }
989  
990  #[setter]
991  fn set_lh_must_panel2(&mut self, value : bool) -> PyResult<()> {
992    self.lh_must_panel2 = value;
993    Ok(())
994  }
995
996  #[getter]
997  fn get_lh_must_panel2_acc (&self) -> u64  {
998    self.lh_must_panel2_acc
999  }
1000
1001  #[getter]
1002  fn get_hit_high_edep      (&self) -> bool {
1003    self.hit_high_edep 
1004  }
1005  
1006  #[setter]
1007  fn set_hit_high_edep(&mut self, value : bool) -> PyResult<()> {
1008    self.hit_high_edep = value;
1009    Ok(())
1010  }
1011
1012  #[getter]
1013  #[pyo3(name="acc_frac_hit_umb")]
1014  fn get_acc_frac_hit_umb_py(&self) -> f64 {
1015    self.get_acc_frac_hit_umb()
1016  } 
1017  
1018  #[getter]
1019  #[pyo3(name="acc_frac_hit_cbe")]
1020  fn get_acc_frac_hit_cbe_py(&self) -> f64 {
1021    self.get_acc_frac_hit_cbe()
1022  }
1023  
1024  #[getter]
1025  #[pyo3(name="acc_frac_hit_cor")]
1026  fn get_acc_frac_hit_cor_py(&self) -> f64 {
1027    self.get_acc_frac_hit_cor()
1028  }
1029  
1030  #[getter]
1031  #[pyo3(name="acc_frac_hit_all")]
1032  fn get_acc_frac_hit_all_py(&self) -> f64 {
1033    self.get_acc_frac_hit_all()
1034  }
1035  
1036  #[getter]
1037  #[pyo3(name="acc_frac_cos_theta")]
1038  fn get_acc_frac_cos_theta_py(&self) -> f64 {
1039    self.get_acc_frac_cos_theta()
1040  }
1041  
1042  #[getter]
1043  #[pyo3(name="acc_frac_fh_must_be_umb")]
1044  fn get_acc_frac_fh_must_be_umb_py(&self)  -> f64 {
1045    self.get_acc_frac_fh_must_be_umb()
1046  }
1047
1048  #[getter]
1049  #[pyo3(name="get_frac_thru_going")]
1050  fn get_acc_frac_thru_going_py(&self)  -> f64 {
1051    self.get_acc_frac_thru_going()
1052  }
1053
1054  #[getter]
1055  #[pyo3(name="acc_frac_fhi_not_bot")]
1056  fn get_acc_frac_fhi_not_bot_py(&self) -> f64 {
1057    self.get_acc_frac_fhi_not_bot()
1058  }
1059
1060  #[getter]
1061  #[pyo3(name="acc_frac_fho_must_panel7")]
1062  fn get_acc_frac_fho_must_panel7_py(&self) -> f64 {
1063    self.get_acc_frac_fho_must_panel7()
1064  }
1065  
1066  #[getter]
1067  #[pyo3(name="acc_frac_lh_must_panel2")]
1068  fn get_acc_frac_lh_must_panel2_py(&self) -> f64 {
1069    self.get_acc_frac_lh_must_panel2()
1070  }
1071
1072  #[getter]
1073  fn get_hit_high_edep_acc  (&self) -> u64  {
1074    self.hit_high_edep_acc
1075  }
1076}
1077
1078#[cfg(feature="pybindings")]
1079pythonize!(TofCuts);
1080