1use byteorder::{ByteOrder, LittleEndian};
40
41use crate::codec::{ChannelParams, CodecId, ColumnCodec, EncodeStats};
42use crate::error::{Error, Result};
43
44const HEADER_BYTES: usize = 4 + 8 + 4 + 1 + 1; #[derive(Debug, Clone, Default)]
48pub struct AdcBitpack;
49
50impl ColumnCodec for AdcBitpack {
51 fn id(&self) -> CodecId {
52 CodecId::AdcBitpack
53 }
54
55 fn encode_chunk(
56 &self,
57 input: &[f32],
58 params: &ChannelParams,
59 out: &mut Vec<u8>,
60 ) -> Result<EncodeStats> {
61 if input.is_empty() {
62 return Err(Error::InvalidParams("AdcBitpack: empty chunk"));
63 }
64 if params.range == 0 {
65 return Err(Error::InvalidParams(
66 "AdcBitpack: $PnR must be > 0 (use Mode A for unbounded data)",
67 ));
68 }
69
70 let adc_bits = effective_adc_bits(params)?;
71 let scale = scale_from(params.range, adc_bits);
72 if !scale.is_finite() || scale == 0.0 {
73 return Err(Error::InvalidParams(
74 "AdcBitpack: derived scale is non-finite or zero",
75 ));
76 }
77
78 let mut quantized: Vec<i64> = Vec::with_capacity(input.len());
80 let mut q_min = i64::MAX;
81 let mut q_max = i64::MIN;
82 for &x in input {
83 if !x.is_finite() {
84 return Err(Error::InvalidParams(
85 "AdcBitpack: encountered NaN or infinite value",
86 ));
87 }
88 let q = (x as f64 / scale).round() as i64;
89 q_min = q_min.min(q);
90 q_max = q_max.max(q);
91 quantized.push(q);
92 }
93
94 let span = (q_max - q_min) as u128;
96 let bits_used = if span == 0 {
97 0u8
98 } else {
99 ((128 - (span).leading_zeros()) as u8).clamp(1, 32)
101 };
102
103 let header_start = out.len();
104 out.resize(header_start + HEADER_BYTES, 0);
105 {
106 let h = &mut out[header_start..header_start + HEADER_BYTES];
107 LittleEndian::write_f32(&mut h[0..4], scale as f32);
108 LittleEndian::write_i64(&mut h[4..12], q_min);
109 LittleEndian::write_u32(&mut h[12..16], input.len() as u32);
110 h[16] = bits_used;
111 h[17] = 0; }
113
114 if bits_used > 0 {
115 let mask = if bits_used == 32 {
116 u32::MAX
117 } else {
118 (1u32 << bits_used) - 1
119 };
120 let mut staged: Vec<u32> = Vec::with_capacity(quantized.len());
124 for q in &quantized {
125 staged.push((*q - q_min) as u32 & mask);
126 }
127 pack_bits_fast(&staged, bits_used, out);
128 }
129
130 let written = out.len() - header_start;
131 Ok(EncodeStats {
132 input_events: input.len() as u32,
133 input_bytes: (input.len() * 4) as u64,
134 output_bytes: written as u64,
135 })
136 }
137
138 fn decode_chunk(
139 &self,
140 payload: &[u8],
141 _params: &ChannelParams,
142 out: &mut [f32],
143 ) -> Result<()> {
144 if payload.len() < HEADER_BYTES {
145 return Err(Error::Truncated {
146 needed: HEADER_BYTES,
147 have: payload.len(),
148 });
149 }
150 let scale = LittleEndian::read_f32(&payload[0..4]) as f64;
151 let offset = LittleEndian::read_i64(&payload[4..12]);
152 let n_values = LittleEndian::read_u32(&payload[12..16]) as usize;
153 let bits_used = payload[16];
154
155 if out.len() != n_values {
156 return Err(Error::LengthMismatch {
157 expected: n_values,
158 actual: out.len(),
159 });
160 }
161
162 if bits_used == 0 {
163 let v = (offset as f64 * scale) as f32;
165 for slot in out.iter_mut() {
166 *slot = v;
167 }
168 return Ok(());
169 }
170 if bits_used > 32 {
171 return Err(Error::InvalidParams(
172 "AdcBitpack: bits_used > 32 is invalid",
173 ));
174 }
175
176 let total_bits = n_values * bits_used as usize;
177 let needed = HEADER_BYTES + total_bits.div_ceil(8);
178 if payload.len() < needed {
179 return Err(Error::Truncated {
180 needed,
181 have: payload.len(),
182 });
183 }
184 let packed = &payload[HEADER_BYTES..];
185 let mut staging: Vec<u32> = vec![0; n_values];
189 unpack_bits_fast(packed, bits_used, n_values, &mut staging);
190 for (slot, q_biased) in out.iter_mut().zip(staging.iter()) {
191 let q = *q_biased as i64 + offset;
192 *slot = (q as f64 * scale) as f32;
193 }
194 Ok(())
195 }
196}
197
198fn effective_adc_bits(params: &ChannelParams) -> Result<u8> {
199 if let Some(b) = params.adc_bits {
200 if !(1..=32).contains(&b) {
201 return Err(Error::InvalidParams(
202 "AdcBitpack: adc_bits must be in 1..=32",
203 ));
204 }
205 return Ok(b);
206 }
207 let range = params.range as u64;
209 if range <= 1 {
210 return Ok(1);
211 }
212 let bits = 64 - (range - 1).leading_zeros();
213 Ok(bits.clamp(1, 32) as u8)
214}
215
216fn scale_from(range: u32, adc_bits: u8) -> f64 {
217 let denom = if adc_bits >= 64 {
220 f64::INFINITY
221 } else {
222 (1u64 << adc_bits) as f64
223 };
224 range as f64 / denom
225}
226
227fn pack_bits_fast(values: &[u32], width: u8, dst: &mut Vec<u8>) {
235 if width == 0 {
236 return;
237 }
238 let mask = if width >= 32 { u32::MAX } else { (1u32 << width) - 1 };
239 let mut buf: u64 = 0;
240 let mut buf_bits: u32 = 0;
241 for &v in values {
242 let masked = (v & mask) as u64;
243 buf |= masked << buf_bits;
244 buf_bits += width as u32;
245 if buf_bits >= 32 {
246 let four = (buf & 0xFFFF_FFFF) as u32;
248 dst.extend_from_slice(&four.to_le_bytes());
249 buf >>= 32;
250 buf_bits -= 32;
251 }
252 }
253 while buf_bits >= 8 {
254 dst.push((buf & 0xFF) as u8);
255 buf >>= 8;
256 buf_bits -= 8;
257 }
258 if buf_bits > 0 {
259 dst.push((buf & 0xFF) as u8);
260 }
261}
262
263#[inline]
267fn unpack_bits_fast(src: &[u8], width: u8, n: usize, out: &mut [u32]) {
268 if width == 0 {
269 for slot in out.iter_mut().take(n) {
270 *slot = 0;
271 }
272 return;
273 }
274 let mask = if width >= 32 {
275 u32::MAX as u64
276 } else {
277 (1u64 << width) - 1
278 };
279 let mut buf: u64 = 0;
280 let mut buf_bits: u32 = 0;
281 let mut src_pos = 0usize;
282 let bytes_avail = src.len();
283 for slot in out.iter_mut().take(n) {
284 while buf_bits < width as u32 {
286 if src_pos + 4 <= bytes_avail && buf_bits + 32 <= 64 {
290 let four = u32::from_le_bytes([
291 src[src_pos],
292 src[src_pos + 1],
293 src[src_pos + 2],
294 src[src_pos + 3],
295 ]);
296 buf |= (four as u64) << buf_bits;
297 buf_bits += 32;
298 src_pos += 4;
299 } else if src_pos < bytes_avail {
300 buf |= (src[src_pos] as u64) << buf_bits;
301 buf_bits += 8;
302 src_pos += 1;
303 } else {
304 break;
305 }
306 }
307 *slot = (buf & mask) as u32;
308 buf >>= width;
309 buf_bits = buf_bits.saturating_sub(width as u32);
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 fn integer_channel(range: u32, adc_bits: u8, n: usize, seed: u64) -> Vec<f32> {
318 let scale = scale_from(range, adc_bits);
320 let mut v = Vec::with_capacity(n);
321 let mut s = seed;
322 for _ in 0..n {
323 s = s
324 .wrapping_mul(6364136223846793005)
325 .wrapping_add(1442695040888963407);
326 let q = (s as u64) % (1u64 << adc_bits);
327 v.push((q as f64 * scale) as f32);
328 }
329 v
330 }
331
332 fn signed_integer_channel(range: u32, adc_bits: u8, n: usize, seed: u64) -> Vec<f32> {
333 let scale = scale_from(range, adc_bits);
335 let mut v = Vec::with_capacity(n);
336 let mut s = seed;
337 for _ in 0..n {
338 s = s
339 .wrapping_mul(6364136223846793005)
340 .wrapping_add(1442695040888963407);
341 let q = ((s as i64) % (1i64 << adc_bits)) - (1i64 << (adc_bits - 1));
342 v.push((q as f64 * scale) as f32);
343 }
344 v
345 }
346
347 fn params(range: u32, adc_bits: u8, signed: bool) -> ChannelParams {
348 ChannelParams {
349 name: "test".into(),
350 stored_bits: 32,
351 range,
352 log_decades: (0.0, 0.0),
353 adc_bits: Some(adc_bits),
354 signed,
355 }
356 }
357
358 #[test]
359 fn round_trips_22_bit_unsigned() {
360 let p = params(1 << 22, 22, false);
361 let input = integer_channel(p.range, 22, 4096, 42);
362 let codec = AdcBitpack::default();
363
364 let mut payload = Vec::new();
365 let stats = codec.encode_chunk(&input, &p, &mut payload).unwrap();
366 assert_eq!(stats.input_events, 4096);
367
368 let mut out = vec![0.0f32; input.len()];
369 codec.decode_chunk(&payload, &p, &mut out).unwrap();
370 for (a, b) in input.iter().zip(out.iter()) {
372 assert_eq!(a.to_bits(), b.to_bits(), "lossless ADC round-trip violated");
373 }
374 }
375
376 #[test]
377 fn round_trips_18_bit_signed_with_negatives() {
378 let p = params(1 << 18, 18, true);
379 let input = signed_integer_channel(p.range, 18, 2048, 7);
380 assert!(input.iter().any(|x| *x < 0.0));
382
383 let codec = AdcBitpack::default();
384 let mut payload = Vec::new();
385 codec.encode_chunk(&input, &p, &mut payload).unwrap();
386 let mut out = vec![0.0f32; input.len()];
387 codec.decode_chunk(&payload, &p, &mut out).unwrap();
388 for (a, b) in input.iter().zip(out.iter()) {
389 assert_eq!(a.to_bits(), b.to_bits());
390 }
391 }
392
393 #[test]
394 fn constant_chunk_uses_zero_bits() {
395 let p = params(262_144, 18, false);
396 let input = vec![1024.0f32; 1024];
397
398 let codec = AdcBitpack::default();
399 let mut payload = Vec::new();
400 codec.encode_chunk(&input, &p, &mut payload).unwrap();
401 assert_eq!(payload.len(), HEADER_BYTES);
403 assert_eq!(payload[16], 0);
404
405 let mut out = vec![0.0f32; input.len()];
406 codec.decode_chunk(&payload, &p, &mut out).unwrap();
407 for (a, b) in input.iter().zip(out.iter()) {
408 assert_eq!(a.to_bits(), b.to_bits());
409 }
410 }
411
412 #[test]
413 fn ratio_beats_raw_on_22_bit_data() {
414 let p = params(1 << 22, 22, false);
415 let input = integer_channel(p.range, 22, 65536, 99);
416
417 let codec = AdcBitpack::default();
418 let mut adc_payload = Vec::new();
419 codec.encode_chunk(&input, &p, &mut adc_payload).unwrap();
420
421 let raw_bytes = input.len() * 4;
423 assert!(
424 adc_payload.len() < raw_bytes,
425 "Mode B ({}) failed to beat raw f32 ({})",
426 adc_payload.len(),
427 raw_bytes
428 );
429 assert!(
431 adc_payload.len() * 4 < raw_bytes * 3,
432 "Mode B compression unexpectedly poor: {} vs raw {}",
433 adc_payload.len(),
434 raw_bytes
435 );
436 }
437
438 #[test]
439 fn rejects_nan() {
440 let p = params(1 << 22, 22, false);
441 let mut input = integer_channel(p.range, 22, 16, 1);
442 input[3] = f32::NAN;
443 let codec = AdcBitpack::default();
444 let mut out = Vec::new();
445 let err = codec.encode_chunk(&input, &p, &mut out).unwrap_err();
446 assert!(matches!(err, Error::InvalidParams(_)));
447 }
448
449 #[test]
450 fn truncated_payload_detected() {
451 let p = params(1 << 22, 22, false);
452 let input = integer_channel(p.range, 22, 256, 5);
453 let codec = AdcBitpack::default();
454 let mut payload = Vec::new();
455 codec.encode_chunk(&input, &p, &mut payload).unwrap();
456 payload.truncate(HEADER_BYTES + 1);
457 let mut out = vec![0.0f32; input.len()];
458 let err = codec.decode_chunk(&payload, &p, &mut out).unwrap_err();
459 assert!(matches!(err, Error::Truncated { .. }));
460 }
461}