Skip to main content

gondola_core/events/
tracker_hit.rs

1//! Per-strip event information for the GAPS tracker
2// This file is part of gaps-online-software and published 
3// under the GPLv3 license
4
5use crate::prelude::*;
6
7#[cfg(feature="pybindings")]
8use pyo3::basic::CompareOp;
9
10use std::cmp::Ordering;
11
12/// Hit on a tracker strip
13#[derive(Debug, Copy, Clone)]
14#[cfg_attr(feature="pybindings", pyclass)]
15pub struct TrackerHit {
16  pub layer           : u8,
17  pub row             : u8,
18  pub module          : u8,
19  pub channel         : u8,
20  pub adc             : u16,
21  pub oscillator      : u64,
22  /// In BFSW, there are two versions of the tracker hit, 
23  /// tracker_hit and tracker::hit. The latter has 
24  /// an extra ASIC event code field. Let's unify those here
25  /// good hits should have asic event code 0 or 2
26  pub asic_event_code : u8,
27
28  // not getting serialized
29  /// calibrated energy
30  pub energy          : f32, 
31  pub x               : f32,
32  pub y               : f32,
33  pub z               : f32,
34  pub has_coordinates : bool,
35  pub adc_pedestal    : u16,
36}
37
38impl TrackerHit {
39  //const SIZE : usize = 18;
40  
41  pub fn new() -> Self {
42    Self {
43      layer           : 0,
44      row             : 0,
45      module          : 0,
46      channel         : 0,
47      adc             : 0,
48      oscillator      : 0,
49      asic_event_code : 0,
50      energy          : 0.0,
51      x               : 0.0,
52      y               : 0.0,
53      z               : 0.0,
54      has_coordinates : false,
55      adc_pedestal    : 0,
56    }
57  }
58
59  #[inline]
60  pub fn identity(&self) -> u64 {
61      // We shift each byte into its own position in the 64-bit integer
62      ((self.layer as u64)   << 40) |
63      ((self.row as u64)     << 32) |
64      ((self.module as u64)  << 24) |
65      ((self.channel as u64) << 16) |
66      (self.adc as u64)
67  } 
68  
69  /// Calculate the strip id from layer, module, row and channel
70  pub fn get_stripid(&self) -> u32 {
71    crate::events::strip_id(self.layer  , 
72                            self.row    ,
73                            self.module ,
74                            self.channel)
75  }
76
77 #[cfg(feature="database")]
78 pub fn set_coordinates(&mut self, strip_map : &HashMap<u32, TrackerStrip>) {
79   match strip_map.get(&self.get_stripid()) {
80     None  => debug!("Can not get strip for strip id {}" , self.get_stripid()),
81     Some(strip) => { 
82       self.x = strip.global_pos_x_l0;
83       self.y = strip.global_pos_y_l0;
84       self.z = strip.global_pos_z_l0;
85       self.has_coordinates = true
86     }
87   }
88 }
89}
90
91impl PartialEq for TrackerHit {
92  fn eq(&self, other: &TrackerHit) -> bool {
93    // we can only compare fields here which 
94    // are always set, merged events do 
95    // not have the asic event code 
96    // populated
97    self.identity() == other.identity()
98    //self.layer              ==  other.layer           
99    //&& self.row             ==  other.row            
100    //&& self.module          ==  other.module         
101    //&& self.channel         ==  other.channel        
102    //&& self.adc             ==  other.adc            
103    // FIXME - let's excluded the oscillator for now
104    //&& self.oscillator      ==  other.oscillator     
105    //&& self.asic_event_code ==  other.asic_event_code
106  }
107}
108
109//----------------------------------------
110
111impl Eq for TrackerHit {
112}
113
114//----------------------------------------
115
116impl Ord for TrackerHit {
117  fn cmp(&self, other: &Self) -> Ordering {
118    other.identity().cmp(&self.identity())
119  }
120}
121
122//----------------------------------------
123
124impl PartialOrd for TrackerHit {
125  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
126    Some(self.cmp(other))
127  }
128}
129
130
131
132impl fmt::Display for TrackerHit {
133  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134    let mut repr = String::from("<TrackerHit:");
135    repr += &(format!("\n  Layer, Row, Module, Channel : {} {} {} {}" ,self.layer, self.row, self.module, self.channel));
136    repr += &(format!("\n  ADC           : {}" ,self.adc));
137    repr += &(format!("\n  Oscillator    : {}" ,self.oscillator));
138    repr += &(format!("\n  ASIC Evt code : {}" ,self.asic_event_code)); 
139    if self.has_coordinates {
140      repr += &(format!("\n -- coordinates x : {} , y : {} , z {}", self.x, self.y, self.z));
141    } else {
142      repr += "\n -- [no coordinates set]";
143    }
144    repr += &(format!("\n  Cali. energy  : {}>", self.energy));
145    write!(f, "{}", repr)
146  }
147}
148
149#[cfg(feature="pybindings")]
150#[pymethods]
151impl TrackerHit {
152
153    // This allows Python's hash() function to work
154    fn __hash__(&self) -> isize {
155        let h = self.identity(); 
156        // Python hashes are signed integers. On 64-bit systems, 
157        // we can usually just cast. We use wrapping to be safe.
158        h as isize
159    }
160
161  /// Change the ADC value, e.g. if the 
162  /// pedestal should be subtracted
163  fn subtract_pedestal(&mut self, pedestal : u16) {
164    self.adc -= pedestal;
165  }
166
167  #[getter]
168  fn get_strip_id(&self) -> u32 {
169    self.get_stripid()
170  }
171
172  #[getter]
173  fn get_layer(&self) -> u8 {
174    self.layer
175  }
176
177  #[getter]
178  fn get_row(&self) -> u8 {
179    self.row
180  }
181
182  #[getter]
183  fn get_module(&self) -> u8 {
184    self.module
185  }
186
187  #[getter]
188  fn get_channel(&self) -> u8 {
189    self.channel
190  }
191
192  #[getter]
193  fn get_adc(&self) -> u16 {
194    self.adc
195  }
196
197  #[getter]
198  fn get_oscillator(&self) -> u64 {
199    self.oscillator
200  }
201 
202  #[getter] 
203  fn get_asic_event_code(&self) -> u8 {
204    self.asic_event_code
205  }
206
207  #[getter]
208  fn get_energy(&self) -> f32 {
209    self.energy
210  }
211
212  #[getter]
213  fn get_x(&self) -> f32 {
214    self.x
215  }
216  
217  #[getter]
218  fn get_y(&self) -> f32 {
219    self.y
220  }
221  
222  #[getter]
223  fn get_z(&self) -> f32 {
224    self.z
225  }
226    
227  // This handles Python's == and != (and others if you wish)
228  fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
229    match op {
230      CompareOp::Eq => Ok(self == other),
231      CompareOp::Ne => Ok(self != other),
232      _ => Ok(false), // Or return an error for unsupported ops like < or >
233    }
234  }
235}
236
237#[cfg(feature="random")]
238impl FromRandom for TrackerHit {
239  fn from_random() -> Self {
240    let mut rng       = rand::rng();
241    Self {
242      layer           : rng.random_range(0..9),
243      row             : rng.random_range(0..6),
244      module          : rng.random_range(0..6),
245      channel         : rng.random_range(0..32),
246      adc             : rng.random::<u16>() & 0x7ff,
247      oscillator      : rng.random::<u64>(),
248      asic_event_code : rng.random_range(0..4),
249      energy          : 0.0, 
250      x               : 0.0,
251      y               : 0.0,
252      z               : 0.0,
253      has_coordinates : false,
254      adc_pedestal    : 0,
255    }
256  }
257}
258
259#[test]
260fn test_tracker_hit_equality() {
261  // 1. Identity: A hit must equal itself
262  let hit_a = TrackerHit::from_random();
263  let hit_b = hit_a; // Copy trait makes this a bitwise clone
264  assert_eq!(hit_a, hit_b, "Identical hits must be equal");
265
266  // 2. Inequality: Changing one field must result in hit_a != hit_c
267  let mut hit_c = hit_a;
268  // Increment the channel (or wrap if at 255) to guarantee it's different
269  hit_c.channel = hit_a.channel.wrapping_add(1); 
270  assert_ne!(hit_a, hit_c, "Hits with different channels must not be equal");
271
272  // 3. Bit-packing verification: Test the ADC (16-bit) specifically
273  let mut hit_d = hit_a;
274  hit_d.adc = hit_a.adc.wrapping_add(1);
275  assert_ne!(hit_a, hit_d, "Hits with different ADC values must not be equal");
276}
277
278
279#[cfg(feature="pybindings")]
280pythonize!(TrackerHit);
281