use rustpix_core::soa::HitBatch;
use rustpix_tpx::ordering::TimeOrderedStream;
use rustpix_tpx::section::discover_sections;
use rustpix_tpx::DetectorConfig;
use rustpix_tpx::Tpx3Packet;
fn make_header(chip_id: u8) -> u64 {
Tpx3Packet::TPX3_HEADER_MAGIC | (u64::from(chip_id) << 32)
}
fn make_tdc(timestamp: u32) -> u64 {
0x6F00_0000_0000_0000 | (u64::from(timestamp) << 12)
}
fn make_hit(timestamp: u32, tot: u16, addr: u16) -> u64 {
let toa = u16::try_from(timestamp & 0x3FFF).unwrap();
let spidr = u16::try_from((timestamp >> 14) & 0xFFFF).unwrap();
0xB000_0000_0000_0000
| (u64::from(toa) << 30)
| (u64::from(tot) << 20)
| (u64::from(addr) << 44)
| u64::from(spidr)
}
fn collect_batches<D>(stream: TimeOrderedStream<D>) -> HitBatch
where
D: AsRef<[u8]> + Clone,
{
let mut batch = HitBatch::default();
for pulse_batch in stream {
batch.append(&pulse_batch);
}
batch
}
#[test]
fn test_interleaved_ordering() {
let mut data = Vec::new();
data.extend_from_slice(&make_header(0).to_le_bytes());
data.extend_from_slice(&make_tdc(1000).to_le_bytes());
data.extend_from_slice(&make_hit(1100, 10, 0).to_le_bytes());
data.extend_from_slice(&make_tdc(2000).to_le_bytes());
data.extend_from_slice(&make_hit(2100, 10, 0).to_le_bytes());
data.extend_from_slice(&make_tdc(3000).to_le_bytes());
data.extend_from_slice(&make_header(1).to_le_bytes());
data.extend_from_slice(&make_tdc(1000).to_le_bytes());
data.extend_from_slice(&make_hit(1200, 10, 0).to_le_bytes());
data.extend_from_slice(&make_tdc(2000).to_le_bytes());
data.extend_from_slice(&make_hit(2050, 10, 0).to_le_bytes());
data.extend_from_slice(&make_tdc(3000).to_le_bytes());
let sections = discover_sections(&data);
assert_eq!(sections.len(), 2);
let config = DetectorConfig::default();
let stream = TimeOrderedStream::new(&data, §ions, &config);
let hits = collect_batches(stream);
assert_eq!(hits.len(), 4);
assert_eq!(hits.tof[0], 100);
assert_eq!(hits.chip_id[0], 0);
assert_eq!(hits.tof[1], 200);
assert_eq!(hits.chip_id[1], 1);
assert_eq!(hits.tof[2], 50);
assert_eq!(hits.chip_id[2], 1);
assert_eq!(hits.tof[3], 100);
assert_eq!(hits.chip_id[3], 0);
}
#[test]
fn test_independent_rollover() {
let mut data = Vec::new();
let tdc_val = 0x3FFF_F000;
let hit_val = 0x0001;
data.extend_from_slice(&make_header(0).to_le_bytes());
data.extend_from_slice(&make_tdc(tdc_val).to_le_bytes());
let raw_hit = make_hit(hit_val, 10, 0);
data.extend_from_slice(&raw_hit.to_le_bytes());
data.extend_from_slice(&make_tdc(tdc_val + 10000).to_le_bytes());
let sections = discover_sections(&data);
let config = DetectorConfig::default();
let stream = TimeOrderedStream::new(&data, §ions, &config);
let hits = collect_batches(stream);
assert_eq!(hits.len(), 1);
assert_eq!(hits.tof[0], 4097);
}
#[test]
fn test_late_hit_boundary() {
let mut data = Vec::new();
data.extend_from_slice(&make_header(0).to_le_bytes());
data.extend_from_slice(&make_tdc(1000).to_le_bytes());
data.extend_from_slice(&make_hit(1100, 10, 0).to_le_bytes());
data.extend_from_slice(&make_tdc(2000).to_le_bytes());
data.extend_from_slice(&make_hit(1950, 10, 1).to_le_bytes());
data.extend_from_slice(&make_hit(2100, 10, 2).to_le_bytes());
data.extend_from_slice(&make_tdc(3000).to_le_bytes());
let sections = discover_sections(&data);
let config = DetectorConfig::default();
let stream = TimeOrderedStream::new(&data, §ions, &config);
let hits = collect_batches(stream);
assert_eq!(hits.len(), 3);
assert_eq!(hits.tof[0], 100);
assert_eq!(hits.tof[1], 950);
assert_eq!(hits.tof[2], 100);
}
#[test]
fn test_tdc_rollover_ordering() {
let mut data = Vec::new();
let tdc_pre_a = 0x3FFF_F000;
let tdc_pre_b = 0x3FFF_F100;
let tdc_post = 0x0000_1000;
data.extend_from_slice(&make_header(0).to_le_bytes());
data.extend_from_slice(&make_tdc(tdc_pre_a).to_le_bytes());
data.extend_from_slice(&make_hit(tdc_pre_a + 10, 10, 0).to_le_bytes());
data.extend_from_slice(&make_tdc(tdc_post).to_le_bytes());
data.extend_from_slice(&make_hit(tdc_post + 30, 10, 1).to_le_bytes());
data.extend_from_slice(&make_header(1).to_le_bytes());
data.extend_from_slice(&make_tdc(tdc_pre_b).to_le_bytes());
data.extend_from_slice(&make_hit(tdc_pre_b + 20, 10, 2).to_le_bytes());
let sections = discover_sections(&data);
let config = DetectorConfig::default();
let stream = TimeOrderedStream::new(&data, §ions, &config);
let hits = collect_batches(stream);
assert_eq!(hits.len(), 3);
assert_eq!(hits.chip_id[0], 0);
assert_eq!(hits.tof[0], 10);
assert_eq!(hits.chip_id[1], 1);
assert_eq!(hits.tof[1], 20);
assert_eq!(hits.chip_id[2], 0);
assert_eq!(hits.tof[2], 30);
}
#[test]
#[ignore = "Run with `cargo test -- --ignored` to benchmark"]
fn test_performance_synthetic() {
let mut data = Vec::with_capacity(100 * 1024 * 1024);
let num_pulses: u32 = 1000;
let hits_per_pulse: u32 = 250;
for chip in 0u8..4 {
data.extend_from_slice(&make_header(chip).to_le_bytes());
for p in 0..num_pulses {
data.extend_from_slice(&make_tdc(p * 10_000).to_le_bytes());
for i in 0..hits_per_pulse {
let tof = i * 10;
let ts = (p * 10_000) + tof;
data.extend_from_slice(&make_hit(ts, 1, 0).to_le_bytes());
}
}
}
let sections = discover_sections(&data);
let config = DetectorConfig::default();
let start = std::time::Instant::now();
let stream = TimeOrderedStream::new(&data, §ions, &config);
let count: u64 = stream
.map(|batch| u64::try_from(batch.len()).unwrap())
.sum();
let elapsed = start.elapsed();
println!("Processed {count} hits in {elapsed:.2?}");
let expected_count = u64::from(num_pulses) * u64::from(hits_per_pulse) * 4;
assert_eq!(count, expected_count);
let hits_per_sec = f64::from(u32::try_from(count).unwrap()) / elapsed.as_secs_f64();
println!("Throughput: {:.2} M hits/s", hits_per_sec / 1e6);
}