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