Skip to main content

mzdata_bindata/
encodings.rs

1use bytemuck::{self, Pod};
2
3use std::{
4    fmt::Display,
5    io,
6    ops::{AddAssign, Mul},
7};
8
9use thiserror::{self, Error};
10use num_traits::{Num, ToBytes};
11
12#[cfg(feature = "numpress")]
13use numpress;
14
15use mzdata_param::{curie, Param, ControlledVocabulary, ParamCow, Unit, CURIE, ValueRef};
16
17pub type Bytes = Vec<u8>;
18
19/// Convert the values in `data` into an owned buffer of little-endian bytes
20///
21/// # Examples
22/// ```
23/// use mzdata_bindata::to_bytes;
24///
25/// let values: [f64; 3] = [1.0, 2.5, -3.25];
26/// let bytes = to_bytes(&values);
27/// assert_eq!(bytes.len(), 3 * 8);
28/// assert_eq!(&bytes[0..8], &1.0f64.to_le_bytes());
29/// ```
30pub fn to_bytes<T: Pod + ToBytes>(data: &[T]) -> Bytes {
31    let n = data.len();
32    let mut buf = Vec::with_capacity(n * size_of::<T>());
33    for v in data {
34        buf.extend_from_slice(v.to_le_bytes().as_ref());
35    }
36    buf
37}
38
39/// Reinterpret a slice of a [`bytemuck::Pod`] type as raw bytes without copying or
40/// converting endianness.
41///
42/// Unlike [`to_bytes`], this does not convert byte order: the returned bytes are in the
43/// host's native byte order, which only matches [`to_bytes`]'s little-endian output on a
44/// little-endian host.
45///
46/// # Examples
47/// ```
48/// use mzdata_bindata::as_bytes;
49///
50/// let values: [i32; 2] = [7, -1];
51/// let bytes = as_bytes(&values);
52/// assert_eq!(bytes.len(), 8);
53/// ```
54pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
55    bytemuck::cast_slice(data)
56}
57
58/// Convert an owned [`Vec`] of a [`bytemuck::Pod`] type into an owned buffer of bytes.
59///
60/// Unlike [`to_bytes`], this does not convert byte order: the returned bytes are in the
61/// host's native byte order, which only matches [`to_bytes`]'s little-endian output on a
62/// little-endian host.
63///
64/// # Examples
65/// ```
66/// use mzdata_bindata::vec_as_bytes;
67///
68/// let values: Vec<f32> = vec![1.0, 2.0, 3.0];
69/// let bytes = vec_as_bytes(values);
70/// assert_eq!(bytes.len(), 3 * 4);
71/// ```
72pub fn vec_as_bytes<T: Pod>(data: Vec<T>) -> Bytes {
73    let mut buf = Bytes::with_capacity(data.len() * std::mem::size_of::<T>());
74    for val in data {
75        buf.extend_from_slice(bytemuck::bytes_of(&val));
76    }
77    buf
78}
79
80mod byte_rotation {
81    use super::*;
82
83    pub fn transpose_bytes_into<T: Pod, const N: usize>(data: &[T], buffer: &mut Vec<u8>) {
84        assert_eq!(core::mem::size_of::<T>(), N);
85        let bytes = bytemuck::cast_slice::<T, [u8; N]>(data);
86        buffer.clear();
87        let delta = (data.len() * N).saturating_sub(buffer.capacity());
88        if delta > 0 {
89            buffer.reserve(delta);
90        }
91
92        #[cfg(target_endian = "little")]
93        {
94            for i in 0..N {
95                buffer.extend(bytes.iter().map(|b| b[i]))
96            }
97        }
98        #[cfg(target_endian = "big")]
99        {
100            for i in (0..N).rev() {
101                buffer.extend(bytes.iter().map(|b| b[i]))
102            }
103        }
104    }
105
106    pub fn transpose_bytes<T: Pod, const N: usize>(data: &[T]) -> Bytes {
107        let mut result = Bytes::with_capacity(data.len() * N);
108        transpose_bytes_into::<T, N>(data, &mut result);
109        result
110    }
111
112    pub fn transpose_4bytes<T: Pod>(data: &[T]) -> Bytes {
113        assert_eq!(std::mem::size_of::<T>(), 4);
114        transpose_bytes::<_, 4>(data)
115    }
116
117    pub fn transpose_8bytes<T: Pod>(data: &[T]) -> Bytes {
118        assert_eq!(std::mem::size_of::<T>(), 8);
119        transpose_bytes::<_, 8>(data)
120    }
121
122    pub fn transpose_i32(data: &[i32]) -> Bytes {
123        transpose_4bytes(data)
124    }
125
126    pub fn transpose_f32(data: &[f32]) -> Bytes {
127        transpose_4bytes(data)
128    }
129
130    pub fn transpose_i64(data: &[i64]) -> Bytes {
131        transpose_8bytes(data)
132    }
133
134    pub fn transpose_f64(data: &[f64]) -> Bytes {
135        transpose_8bytes(data)
136    }
137
138    pub fn reverse_transpose_bytes_into<const N: usize>(data: &[u8], buffer: &mut Vec<u8>) {
139        let rem = data.len() % N;
140        assert_eq!(rem, 0);
141        let n_entries = data.len() / N;
142        buffer.clear();
143        buffer.resize(data.len(), 0);
144
145        #[cfg(target_endian = "little")]
146        {
147            for (i, band) in data.chunks_exact(n_entries).enumerate() {
148                for (j, byte) in band.iter().copied().enumerate() {
149                    bytemuck::cast_slice_mut::<_, [u8; N]>(buffer)[j][i] = byte;
150                }
151            }
152        }
153        #[cfg(target_endian = "big")]
154        {
155            for (i, band) in data.chunks_exact(n_entries).enumerate() {
156                for (j, byte) in band.iter().copied().enumerate() {
157                    bytemuck::cast_slice_mut::<_, [u8; N]>(buffer)[j][(N - 1) - i] = byte;
158                }
159            }
160        }
161    }
162
163    pub fn reverse_transpose_bytes<const N: usize>(data: &[u8]) -> Bytes {
164        let mut result: Bytes = vec![0; data.len()];
165        reverse_transpose_bytes_into::<N>(data, &mut result);
166        result
167    }
168
169    pub fn reverse_transpose_4bytes<T: Pod>(data: &[u8]) -> Bytes {
170        assert_eq!(std::mem::size_of::<T>(), 4);
171        reverse_transpose_bytes::<4>(data)
172    }
173
174    pub fn reverse_transpose_8bytes<T: Pod>(data: &[u8]) -> Bytes {
175        assert_eq!(std::mem::size_of::<T>(), 8);
176        reverse_transpose_bytes::<8>(data)
177    }
178
179    pub fn reverse_transpose_i32(data: &[u8]) -> Vec<u8> {
180        reverse_transpose_4bytes::<i32>(data)
181    }
182
183    pub fn reverse_transpose_f32(data: &[u8]) -> Vec<u8> {
184        reverse_transpose_4bytes::<f32>(data)
185    }
186
187    pub fn reverse_transpose_i64(data: &[u8]) -> Vec<u8> {
188        reverse_transpose_8bytes::<i64>(data)
189    }
190
191    pub fn reverse_transpose_f64(data: &[u8]) -> Vec<u8> {
192        reverse_transpose_8bytes::<f64>(data)
193    }
194}
195
196mod dictionary_encoding {
197    use super::*;
198    use io::prelude::*;
199    use num_traits::ops::bytes::{FromBytes, ToBytes};
200    use std::{
201        borrow::Cow,
202        collections::{HashMap, HashSet},
203        hash::Hash,
204        io::BufWriter,
205    };
206
207    trait DictValue<const W: usize>:
208        Pod + ToBytes<Bytes = [u8; W]> + Hash + Eq + Ord + FromBytes<Bytes = [u8; W]>
209    {
210    }
211
212    macro_rules! impl_dict_value {
213        ($val:ty, $size:literal) => {
214            impl DictValue<$size> for $val {}
215        };
216    }
217
218    impl_dict_value!(u8, 1);
219    impl_dict_value!(u16, 2);
220    impl_dict_value!(u32, 4);
221    impl_dict_value!(u64, 8);
222
223    trait DictIndex<const W: usize>:
224        Pod + ToBytes<Bytes = [u8; W]> + FromBytes<Bytes = [u8; W]>
225    {
226        fn from_usize(index: usize) -> Self;
227        fn to_usize(&self) -> usize;
228    }
229
230    macro_rules! impl_dict_index {
231        ($idx:ty, $size:literal) => {
232            impl DictIndex<$size> for $idx {
233                fn from_usize(index: usize) -> Self {
234                    index as Self
235                }
236
237                fn to_usize(&self) -> usize {
238                    *self as usize
239                }
240            }
241        };
242    }
243
244    impl_dict_index!(u8, 1);
245    impl_dict_index!(u16, 2);
246    impl_dict_index!(u32, 4);
247    impl_dict_index!(u64, 8);
248
249    #[derive(Default, Debug)]
250    pub struct DictionaryEncoder {
251        shuffle: bool,
252        buffer: Vec<u8>,
253    }
254
255    impl DictionaryEncoder {
256        pub fn new(shuffle: bool) -> Self {
257            Self {
258                shuffle,
259                buffer: Vec::new(),
260            }
261        }
262
263        fn build_value_map<T: Pod, const W1: usize, V: DictValue<W1>>(
264            &self,
265            data: &[T],
266        ) -> (Vec<V>, HashMap<V, usize>) {
267            debug_assert_eq!(core::mem::size_of::<T>(), core::mem::size_of::<V>());
268            debug_assert_eq!(core::mem::size_of::<T>(), W1);
269            let mut value_codes = HashSet::new();
270            for v in data {
271                let k: V = *bytemuck::from_bytes(bytemuck::bytes_of(v));
272                value_codes.insert(k);
273            }
274
275            let mut value_codes: Vec<_> = value_codes.into_iter().collect();
276            value_codes.sort();
277
278            let byte_map: HashMap<V, usize> = value_codes
279                .iter()
280                .enumerate()
281                .map(|(i, k)| (*k, i))
282                .collect();
283            (value_codes, byte_map)
284        }
285
286        fn create_writer<
287            T: Pod,
288            const W1: usize,
289            V: Pod + Ord + Hash + ToBytes<Bytes = [u8; W1]> + Eq,
290            const W2: usize,
291            K: DictIndex<W2>,
292        >(
293            &self,
294            data: &[T],
295            value_codes: &[V],
296        ) -> BufWriter<Vec<u8>> {
297            let data_offset = 16 + std::mem::size_of_val(value_codes);
298            let dict_buffer: Vec<u8> =
299                Vec::with_capacity(data_offset + data.len() * core::mem::size_of::<K>());
300            BufWriter::new(dict_buffer)
301        }
302
303        fn encode_dict_indices<
304            T: Pod,
305            const W1: usize,
306            V: Pod + Ord + Hash + ToBytes<Bytes = [u8; W1]> + Eq,
307            const W2: usize,
308            K: DictIndex<W2>,
309        >(
310            &mut self,
311            data: &[T],
312            value_codes: &[V],
313            byte_map: HashMap<V, usize>,
314        ) -> io::Result<Vec<u8>> {
315            let data_offset = 16 + std::mem::size_of_val(value_codes);
316            let mut writer = self.create_writer::<T, W1, V, W2, K>(data, value_codes);
317            writer.write_all(&(data_offset as u64).to_le_bytes())?;
318            writer.write_all(&(value_codes.len() as u64).to_le_bytes())?;
319
320            if self.shuffle {
321                // This isn't endian-correct yet, see note in `transpose_bytes_into`
322                byte_rotation::transpose_bytes_into::<V, W1>(value_codes, &mut self.buffer);
323                writer.write_all(&self.buffer)?;
324                // for v in self.buffer.chunks(W1) {
325                //     writer.write_all(v)?;
326                // }
327            } else {
328                for v in value_codes.iter() {
329                    let bts = v.to_le_bytes();
330                    writer.write_all(&bts)?;
331                }
332            }
333
334            if self.shuffle {
335                let mut buf = Vec::with_capacity(data.len());
336                for v in data {
337                    let i = *byte_map
338                        .get(bytemuck::from_bytes(bytemuck::bytes_of(v)))
339                        .unwrap();
340                    let ik: K = K::from_usize(i);
341                    buf.push(ik);
342                }
343                // This isn't endian-correct
344                byte_rotation::transpose_bytes_into::<K, W2>(&buf, &mut self.buffer);
345                writer.write_all(&self.buffer)?;
346            } else {
347                for v in data {
348                    let i = *byte_map
349                        .get(bytemuck::from_bytes(bytemuck::bytes_of(v)))
350                        .unwrap();
351                    let ik: K = K::from_usize(i);
352                    writer.write_all(&ik.to_le_bytes())?;
353                }
354            }
355
356            writer.flush()?;
357            let val = writer.into_inner().unwrap();
358            Ok(val)
359        }
360
361        fn encode_values<T: Pod, const W1: usize, V: DictValue<W1>>(
362            &mut self,
363            data: &[T],
364        ) -> Result<Vec<u8>, io::Error> {
365            let (value_codes, byte_map) = self.build_value_map(data);
366            let n_value_codes = value_codes.len();
367
368            if n_value_codes <= 2usize.pow(8) {
369                self.encode_dict_indices::<T, W1, V, 1, u8>(data, &value_codes, byte_map)
370            } else if n_value_codes <= 2usize.pow(16) {
371                self.encode_dict_indices::<T, W1, V, 2, u16>(data, &value_codes, byte_map)
372            } else if n_value_codes <= 2usize.pow(32) {
373                self.encode_dict_indices::<T, W1, V, 4, u32>(data, &value_codes, byte_map)
374            } else if n_value_codes <= 2usize.pow(64) {
375                self.encode_dict_indices::<T, W1, V, 8, u64>(data, &value_codes, byte_map)
376            } else {
377                Err(io::Error::new(
378                    io::ErrorKind::Unsupported,
379                    "Cannot encode a dictionary with more than 2 ** 64 values",
380                ))
381            }
382        }
383
384        pub fn encode<T: Pod>(&mut self, data: &[T]) -> io::Result<Bytes> {
385            if data.is_empty() {
386                return Ok(Vec::new());
387            }
388            let z_val = core::mem::size_of::<T>();
389            if z_val <= 1 {
390                self.encode_values::<T, 1, u8>(data)
391            } else if z_val <= 2 {
392                self.encode_values::<T, 2, u16>(data)
393            } else if z_val <= 4 {
394                self.encode_values::<T, 4, u32>(data)
395            } else if z_val <= 8 {
396                self.encode_values::<T, 8, u64>(data)
397            } else {
398                Err(io::Error::new(
399                    io::ErrorKind::Unsupported,
400                    "Cannot encode a dictionary with more than 2 ** 64 keys",
401                ))
402            }
403        }
404    }
405
406    #[derive(Default, Debug)]
407    pub struct DictionaryDecoder {
408        shuffle: bool,
409        buffer: Vec<u8>,
410    }
411
412    impl DictionaryDecoder {
413        pub fn new(shuffle: bool) -> Self {
414            Self {
415                shuffle,
416                buffer: Default::default(),
417            }
418        }
419
420        fn make_reader<'a>(&self, buffer: &'a [u8]) -> io::BufReader<&'a [u8]> {
421            io::BufReader::new(buffer)
422        }
423
424        fn decode_value_buffer<T: Pod, const W1: usize, V: DictValue<W1>>(
425            &mut self,
426            buffer: &[u8],
427            n_values: usize,
428        ) -> Vec<T> {
429            macro_rules! decode_chunk {
430                ($chunk:ident) => {{
431                    let chunk_a: [u8; W1] = $chunk.try_into().unwrap();
432                    let val = V::from_le_bytes(&chunk_a);
433                    let val: T = *bytemuck::from_bytes(bytemuck::bytes_of(&val));
434                    val
435                }};
436            }
437            let mut value_buffer = Vec::with_capacity(n_values);
438            if self.shuffle {
439                let blocks = match core::mem::size_of::<T>() {
440                    1 => Cow::Borrowed(buffer),
441                    2 => {
442                        byte_rotation::reverse_transpose_bytes_into::<2>(buffer, &mut self.buffer);
443                        Cow::Borrowed(self.buffer.as_slice())
444                    }
445                    4 => {
446                        byte_rotation::reverse_transpose_bytes_into::<4>(buffer, &mut self.buffer);
447                        Cow::Borrowed(self.buffer.as_slice())
448                    }
449                    8 => {
450                        byte_rotation::reverse_transpose_bytes_into::<8>(buffer, &mut self.buffer);
451                        Cow::Borrowed(self.buffer.as_slice())
452                    }
453                    x => {
454                        panic!("Unsupported size {x}");
455                    }
456                };
457                for chunk in blocks.chunks_exact(W1) {
458                    let val = decode_chunk!(chunk);
459                    value_buffer.push(val);
460                }
461            } else {
462                for chunk in buffer.chunks_exact(W1) {
463                    let val = decode_chunk!(chunk);
464                    value_buffer.push(val);
465                }
466            };
467            value_buffer
468        }
469
470        fn decode_index_buffer<T: Pod, const W2: usize, K: DictIndex<W2>>(
471            &mut self,
472            value_codes: &[T],
473            index_buffer: &[u8],
474        ) -> Vec<T> {
475            let mut result = Vec::with_capacity(index_buffer.len() / W2);
476            if self.shuffle {
477                byte_rotation::reverse_transpose_bytes_into::<W2>(index_buffer, &mut self.buffer);
478                for chunk in self.buffer.chunks_exact(W2) {
479                    let b: [u8; W2] = chunk.try_into().unwrap();
480                    let k: usize = K::from_le_bytes(&b).to_usize();
481                    result.push(value_codes[k])
482                }
483            } else {
484                for chunk in index_buffer.chunks_exact(W2) {
485                    let b: [u8; W2] = chunk.try_into().unwrap();
486                    let k: usize = K::from_le_bytes(&b).to_usize();
487                    result.push(value_codes[k])
488                }
489            }
490            result
491        }
492
493        pub fn decode<T: Pod>(&mut self, buffer: &[u8]) -> io::Result<Vec<T>> {
494            if buffer.is_empty() {
495                return Ok(Vec::new());
496            }
497            let mut reader = self.make_reader(buffer);
498            let mut z_buf = [0u8; 8];
499            reader.read_exact(&mut z_buf)?;
500            let data_offset = u64::from_le_bytes(z_buf);
501            if data_offset == 0 {
502                return Ok(Vec::new());
503            }
504            let mut z_buf = [0u8; 8];
505            reader.read_exact(&mut z_buf)?;
506            let n_value_codes = u64::from_le_bytes(z_buf);
507            if n_value_codes == 0 {
508                return Ok(Vec::new());
509            }
510            let value_buffer = &buffer[16..(data_offset as usize)];
511            let value_width = (data_offset - 16) / n_value_codes;
512            let index_buffer = &buffer[data_offset as usize..];
513
514            let n_value_codes = n_value_codes as usize;
515
516            macro_rules! decode_indices {
517                ($values:ident) => {
518                    if n_value_codes <= 2usize.pow(8) {
519                        self.decode_index_buffer::<T, 1, u8>(&$values, index_buffer)
520                    } else if n_value_codes <= 2usize.pow(16) {
521                        self.decode_index_buffer::<T, 2, u16>(&$values, index_buffer)
522                    } else if n_value_codes <= 2usize.pow(32) {
523                        self.decode_index_buffer::<T, 4, u32>(&$values, index_buffer)
524                    } else if n_value_codes <= 2usize.pow(64) {
525                        self.decode_index_buffer::<T, 8, u64>(&$values, index_buffer)
526                    } else {
527                        return Err(io::Error::new(
528                            io::ErrorKind::Unsupported,
529                            "Cannot decode a dictionary with more than 2 ** 64 indices",
530                        ));
531                    }
532                };
533            }
534
535            let values = if value_width <= 1 {
536                let values = self.decode_value_buffer::<T, 1, u8>(value_buffer, n_value_codes);
537                decode_indices!(values)
538            } else if value_width <= 2 {
539                let values = self.decode_value_buffer::<T, 2, u16>(value_buffer, n_value_codes);
540                decode_indices!(values)
541            } else if value_width <= 4 {
542                let values = self.decode_value_buffer::<T, 4, u32>(value_buffer, n_value_codes);
543                decode_indices!(values)
544            } else if value_width <= 8 {
545                let values = self.decode_value_buffer::<T, 8, u64>(value_buffer, n_value_codes);
546                decode_indices!(values)
547            } else {
548                return Err(io::Error::new(
549                    io::ErrorKind::Unsupported,
550                    "Cannot decode dictionary with value byte width greater than 8",
551                ));
552            };
553
554            Ok(values)
555        }
556    }
557
558    pub fn dictionary_encoding<T: Pod>(data: &[T]) -> Result<Vec<u8>, io::Error> {
559        let mut encoder = DictionaryEncoder::new(true);
560        encoder.encode(data)
561    }
562
563    pub fn dictionary_decoding<T: Pod>(buffer: &[u8]) -> io::Result<Vec<T>> {
564        let mut decoder = DictionaryDecoder::new(true);
565        decoder.decode(buffer)
566    }
567}
568
569#[allow(unused)]
570pub use byte_rotation::*;
571
572#[allow(unused)]
573pub use dictionary_encoding::{dictionary_decoding, dictionary_encoding};
574
575/// The kinds of data arrays found in mass spectrometry data files governed
576/// by the PSI-MS controlled vocabulary.
577#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Default)]
578#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
579pub enum ArrayType {
580    /// No information is given about this array, not even a name
581    #[default]
582    Unknown,
583    /// The m/z array, the coordinate values of a mass spectrum. The default unit is [`Unit::MZ`].
584    MZArray,
585    /// The intensity or abundance of a signal at a given point.
586    /// The default unit is [`Unit::DetectorCounts`].
587    IntensityArray,
588    /// The deconvolved charge state of an ion
589    ChargeArray,
590    /// The estimated signal to noise ratio of a point observation
591    SignalToNoiseArray,
592    /// The time coordinate an observation occured such as for a feature, trace, or chromatgam.
593    /// The default unit is [`Unit::Minute`]
594    TimeArray,
595    /// The wavelength coordinate of an observation in a photospectrometry measurement.
596    /// The default unit is [`Unit::Nanometer`].
597    WavelengthArray,
598
599    /// A basic ion mobility array that contains no additional information about the unit
600    /// or level of processing or aggregation. **Note: It has no implicit unit information!**
601    IonMobilityArray,
602
603    /// The mean ion mobility measurement of an observation. This is the centroid of an ion mobility
604    /// measurement, distinct from a profile array view. **Note: It has no implicit unit information!**
605    MeanIonMobilityArray,
606    /// The mean drift time of an observation. This is the centroid of an ion mobility measurement
607    /// expressed in terms of drift time. The default unit is [`Unit::Millisecond`].
608    MeanDriftTimeArray,
609    /// The mean drift time of an observation. This is the centroid of an ion mobility measurement
610    /// expressed in terms of $V*s/cm^2$. The default unit is [`Unit::VoltSecondPerSquareCentimeter`].
611    MeanInverseReducedIonMobilityArray,
612
613    /// The raw ion mobility measurement of an observation. This is unprocessed and may be profile
614    /// or centroid ion mobility measurements.
615    RawIonMobilityArray,
616    /// The raw drift time of an observation. This is may be a profile or centroid representation
617    /// of an ion mobility measurement expressed in terms of drift time. The default unit is [`Unit::Millisecond`].
618    RawDriftTimeArray,
619    /// The raw drift time of an observation. This is may be a profile or centroid representation
620    /// of an ion mobility measurement expressed in terms of $V*s/cm^2$. The default unit is [`Unit::VoltSecondPerSquareCentimeter`].
621    RawInverseReducedIonMobilityArray,
622
623    /// The mean ion mobility measurement of an observation after deconvolution of cross-cutting properties like
624    /// charge state, adduct, or some other relevant property. This is the centroid of an ion mobility
625    /// measurement, distinct from a profile array view. **Note: It has no implicit unit information!**
626    DeconvolutedIonMobilityArray,
627    /// The mean ion mobility drift time measurement of an observation expressed in drift time after deconvolution
628    /// of cross-cutting properties like charge state, adduct, or some other relevant property. This is the centroid of
629    /// an ion mobility measurement. The default unit is [`Unit::Millisecond`].
630    DeconvolutedDriftTimeArray,
631    /// The mean ion mobility drift time measurement of an observation expressed in $V*s/cm^2$ after deconvolution
632    /// of cross-cutting properties like charge state, adduct, or some other relevant property. This is the centroid of
633    /// an ion mobility measurement. The default unit is [`Unit::Millisecond`].
634    DeconvolutedInverseReducedIonMobilityArray,
635
636    /// Array of m/z values representing the lower bound m/z of the quadrupole position at each point in the spectrum.
637    /// The default unit is [`Unit::MZ`]
638    ScanningQuadrupolePositionLowerBoundMZ,
639    /// Array of m/z values representing the upper bound m/z of the quadrupole position at each point in the spectrum.
640    /// The default unit is [`Unit::MZ`]
641    ScanningQuadrupolePositionUpperBoundMZ,
642
643    /// A data array whose values are index values for another collection or array.
644    IndexArray,
645
646    /// A data array of signal baseline values (the signal in the absence of analytes).
647    BaselineArray,
648    /// A data array of resolution values, which influence the accuracy of measurements.
649    ResolutionArray,
650    /// A data array of pressure measurements.
651    PressureArray,
652    /// A data array of temperature measurements.
653    TemperatureArray,
654    /// A data array of flow rate measurements.
655    FlowRateArray,
656
657    /// A data array that is not covered by any of the existing types, but given a distinct name.
658    /// This is used for user- or application-defined arrays.
659    NonStandardDataArray {
660        name: Box<String>,
661    },
662}
663
664impl Display for ArrayType {
665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
666        write!(f, "{:?}", self)
667    }
668}
669
670impl ArrayType {
671    /// Get the data type that the array is compatible with in the
672    /// `mzdata` type expectations.
673    ///
674    /// By default, the m/z array is encoded using `Float64`,
675    /// the charge state array is encoded using `Int32`, and
676    /// all other arrays are encoded using `Float32`.
677    ///
678    /// # Examples
679    /// ```
680    /// use mzdata_bindata::{ArrayType, BinaryDataArrayType};
681    ///
682    /// assert_eq!(ArrayType::MZArray.preferred_dtype(), BinaryDataArrayType::Float64);
683    /// assert_eq!(ArrayType::IntensityArray.preferred_dtype(), BinaryDataArrayType::Float32);
684    /// assert_eq!(ArrayType::ChargeArray.preferred_dtype(), BinaryDataArrayType::Int32);
685    /// ```
686    pub const fn preferred_dtype(&self) -> BinaryDataArrayType {
687        match self {
688            ArrayType::MZArray => BinaryDataArrayType::Float64,
689            ArrayType::IntensityArray => BinaryDataArrayType::Float32,
690            ArrayType::ChargeArray => BinaryDataArrayType::Int32,
691            ArrayType::IndexArray => BinaryDataArrayType::Int64,
692            _ => BinaryDataArrayType::Float32,
693        }
694    }
695
696    /// Convert an ion mobility array to its mean variant
697    ///
698    /// # Examples
699    /// ```
700    /// use mzdata_bindata::ArrayType;
701    ///
702    /// assert_eq!(
703    ///     ArrayType::RawDriftTimeArray.as_mean_ion_mobility(),
704    ///     Some(ArrayType::MeanDriftTimeArray)
705    /// );
706    /// assert_eq!(ArrayType::MZArray.as_mean_ion_mobility(), None);
707    /// ```
708    pub const fn as_mean_ion_mobility(&self) -> Option<ArrayType> {
709        Some(match self {
710            Self::RawDriftTimeArray
711            | Self::DeconvolutedDriftTimeArray
712            | Self::MeanDriftTimeArray => Self::MeanDriftTimeArray,
713            Self::RawInverseReducedIonMobilityArray
714            | Self::DeconvolutedInverseReducedIonMobilityArray
715            | Self::MeanInverseReducedIonMobilityArray => Self::MeanInverseReducedIonMobilityArray,
716            Self::RawIonMobilityArray
717            | Self::DeconvolutedIonMobilityArray
718            | Self::MeanIonMobilityArray => Self::MeanIonMobilityArray,
719            _ => return None,
720        })
721    }
722
723    /// Convert an ion mobility array to its raw variant
724    pub const fn as_raw_ion_mobility(&self) -> Option<ArrayType> {
725        Some(match self {
726            Self::RawDriftTimeArray
727            | Self::DeconvolutedDriftTimeArray
728            | Self::MeanDriftTimeArray => Self::RawDriftTimeArray,
729            Self::RawInverseReducedIonMobilityArray
730            | Self::DeconvolutedInverseReducedIonMobilityArray
731            | Self::MeanInverseReducedIonMobilityArray => Self::RawInverseReducedIonMobilityArray,
732            Self::RawIonMobilityArray
733            | Self::DeconvolutedIonMobilityArray
734            | Self::MeanIonMobilityArray => Self::RawIonMobilityArray,
735            _ => return None,
736        })
737    }
738
739    /// Convert an ion mobility array to its deconvoluted variant
740    pub const fn as_deconvoluted_ion_mobility(&self) -> Option<ArrayType> {
741        Some(match self {
742            Self::RawDriftTimeArray
743            | Self::DeconvolutedDriftTimeArray
744            | Self::MeanDriftTimeArray => Self::DeconvolutedDriftTimeArray,
745            Self::RawInverseReducedIonMobilityArray
746            | Self::DeconvolutedInverseReducedIonMobilityArray
747            | Self::MeanInverseReducedIonMobilityArray => {
748                Self::DeconvolutedInverseReducedIonMobilityArray
749            }
750            Self::RawIonMobilityArray
751            | Self::DeconvolutedIonMobilityArray
752            | Self::MeanIonMobilityArray => Self::DeconvolutedIonMobilityArray,
753            _ => return None,
754        })
755    }
756
757    /// Create a [`ArrayType::NonStandardDataArray`] with the provided name.
758    pub fn nonstandard<S: ToString>(name: S) -> ArrayType {
759        ArrayType::NonStandardDataArray {
760            name: name.to_string().into(),
761        }
762    }
763
764    /// The preferred unit for this array type.
765    pub const fn preferred_unit(&self) -> Option<Unit> {
766        let unit = match self {
767            Self::MZArray | Self::ScanningQuadrupolePositionLowerBoundMZ | Self::ScanningQuadrupolePositionUpperBoundMZ => Unit::MZ,
768            Self::IntensityArray => Unit::DetectorCounts,
769            Self::WavelengthArray => Unit::Nanometer,
770            Self::TimeArray | Self::FlowRateArray => Unit::Minute,
771            Self::PressureArray => Unit::Pascal,
772            Self::RawDriftTimeArray | Self::MeanDriftTimeArray | Self::DeconvolutedDriftTimeArray => Unit::Millisecond,
773            Self::RawInverseReducedIonMobilityArray | Self::MeanInverseReducedIonMobilityArray | Self::DeconvolutedInverseReducedIonMobilityArray => Unit::VoltSecondPerSquareCentimeter,
774            _ => return None
775        };
776        Some(unit)
777    }
778
779    /// Test if the the array describes an ion mobility quantity.
780    ///
781    /// # Examples
782    /// ```
783    /// use mzdata_bindata::ArrayType;
784    ///
785    /// assert!(!ArrayType::MZArray.is_ion_mobility());
786    /// assert!(ArrayType::MeanInverseReducedIonMobilityArray.is_ion_mobility());
787    /// ```
788    pub const fn is_ion_mobility(&self) -> bool {
789        matches!(
790            self,
791            Self::IonMobilityArray
792                | Self::MeanIonMobilityArray
793                | Self::MeanDriftTimeArray
794                | Self::MeanInverseReducedIonMobilityArray
795                | Self::DeconvolutedIonMobilityArray
796                | Self::DeconvolutedDriftTimeArray
797                | Self::DeconvolutedInverseReducedIonMobilityArray
798                | Self::RawIonMobilityArray
799                | Self::RawDriftTimeArray
800                | Self::RawInverseReducedIonMobilityArray,
801        )
802    }
803
804    /// Create a [`Param`] for this array type.
805    ///
806    /// If a unit is provided, that unit will be specified, otherwise a default unit may
807    /// be used instead.
808    ///
809    /// Prefer this to [`Self::as_param_const`] when you do not need to worry about const-ness
810    /// and the
811    pub fn as_param(&self, unit: Option<Unit>) -> Param {
812        const CV: ControlledVocabulary = ControlledVocabulary::MS;
813        let unit = unit.or_else(|| self.preferred_unit());
814
815        if let Self::NonStandardDataArray { name } = self {
816            let mut p = CV.param_val(
817                1000786,
818                "non-standard data array",
819                name.to_string()
820            );
821            p.unit = unit.unwrap_or_default();
822            p
823        }
824        else {
825            match unit {
826                Some(unit) => self.as_param_with_unit_const(unit).into(),
827                None => self.as_param_const().into()
828            }
829        }
830
831        // match self {
832        //     ArrayType::MZArray => CV
833        //         .const_param_ident_unit("m/z array", 1000514, unit.unwrap_or_default())
834        //         .into(),
835        //     ArrayType::IntensityArray => CV
836        //         .const_param_ident_unit(
837        //             "intensity array",
838        //             1000515,
839        //             unit.unwrap_or_default(),
840        //         )
841        //         .into(),
842        //     ArrayType::ChargeArray => CV.const_param_ident("charge array", 1000516).into(),
843        //     ArrayType::TimeArray => CV
844        //         .const_param_ident_unit("time array", 1000595, unit.unwrap_or_default())
845        //         .into(),
846        //     ArrayType::WavelengthArray => CV
847        //         .const_param_ident_unit("wavelength array", 1000617, unit.unwrap_or_default())
848        //         .into(),
849        //     ArrayType::SignalToNoiseArray => CV
850        //         .const_param_ident("signal to noise array", 1000517)
851        //         .into(),
852        //     ArrayType::IonMobilityArray => CV
853        //         .const_param_ident_unit(
854        //             "ion mobility array", 1002893, unit.unwrap_or_default())
855        //         .into(),
856
857        //     ArrayType::RawDriftTimeArray => CV
858        //         .const_param_ident_unit(
859        //             "raw ion mobility drift time array",
860        //             1003153,
861        //             unit.unwrap_or_default(),
862        //         )
863        //         .into(),
864        //     ArrayType::RawInverseReducedIonMobilityArray => CV
865        //         .const_param_ident_unit(
866        //             "raw inverse reduced ion mobility array",
867        //             1003008,
868        //             unit.unwrap_or_default(),
869        //         )
870        //         .into(),
871        //     ArrayType::RawIonMobilityArray => CV
872        //         .const_param_ident_unit("raw ion mobility array", 1003007, unit.unwrap_or_default())
873        //         .into(),
874
875        //     ArrayType::MeanIonMobilityArray => CV
876        //         .const_param_ident_unit(
877        //             "mean ion mobility array",
878        //             1002816,
879        //             unit.unwrap_or_default(),
880        //         )
881        //         .into(),
882        //     ArrayType::MeanDriftTimeArray => CV
883        //         .const_param_ident_unit(
884        //             "mean ion mobility drift time array",
885        //             1002477,
886        //             unit.unwrap_or_default(),
887        //         )
888        //         .into(),
889        //     ArrayType::MeanInverseReducedIonMobilityArray => CV
890        //         .const_param_ident_unit(
891        //             "mean inverse reduced ion mobility array",
892        //             1003006,
893        //             unit.unwrap_or_default(),
894        //         )
895        //         .into(),
896
897        //     ArrayType::DeconvolutedIonMobilityArray => CV
898        //         .const_param_ident_unit(
899        //             "deconvoluted ion mobility array",
900        //             1003154,
901        //             unit.unwrap_or_default(),
902        //         )
903        //         .into(),
904        //     ArrayType::DeconvolutedDriftTimeArray => CV
905        //         .const_param_ident_unit(
906        //             "deconvoluted ion mobility drift time array",
907        //             1003156,
908        //             unit.unwrap_or_default(),
909        //         )
910        //         .into(),
911        //     ArrayType::DeconvolutedInverseReducedIonMobilityArray => CV
912        //         .const_param_ident_unit(
913        //             "deconvoluted inverse reduced ion mobility array",
914        //             1003155,
915        //             unit.unwrap_or_default(),
916        //         )
917        //         .into(),
918
919        //     ArrayType::NonStandardDataArray { name } => {
920        //         let mut p = CV.param_val(1000786, "non-standard data array", name.to_string());
921        //         p.unit = unit.unwrap_or_default();
922        //         p
923        //     }
924        //     ArrayType::BaselineArray => CV.const_param_ident("baseline array", 1002530).into(),
925        //     ArrayType::ResolutionArray => CV.const_param_ident("resolution array", 1002529).into(),
926        //     ArrayType::PressureArray => {
927        //         let mut p = CV.const_param_ident("pressure array", 1000821);
928        //         p.unit = unit.unwrap_or_default();
929        //         p.into()
930        //     }
931        //     ArrayType::TemperatureArray => {
932        //         let mut p = CV.const_param_ident("temperature array", 1000822);
933        //         p.unit = unit.unwrap_or_default();
934        //         p.into()
935        //     }
936        //     ArrayType::FlowRateArray => {
937        //         let mut p = CV.const_param_ident("flow rate array", 1000820);
938        //         p.unit = unit.unwrap_or_default();
939        //         p.into()
940        //     }
941        //     ArrayType::ScanningQuadrupolePositionLowerBoundMZ => {
942        //         let mut p = CV.const_param_ident("scanning quadrupole position lower bound m/z array", 1003157);
943        //         p.unit = unit.unwrap_or_default();
944        //         p.into()
945        //     }
946        //     ArrayType::ScanningQuadrupolePositionUpperBoundMZ => {
947        //         let mut p = CV.const_param_ident("scanning quadrupole position upper bound m/z array", 1003158);
948        //         p.unit = unit.unwrap_or_default();
949        //         p.into()
950        //     }
951        //     ArrayType::IndexArray => {
952        //         CV.const_param_ident("index array", 1003870).into()
953        //     }
954        //     _ => {
955        //         panic!("Could not determine how to name for array {}", self);
956        //     }
957        // }
958    }
959
960    /// Create a [`ParamCow`] for this array type in a `const` context using the default unit.
961    ///
962    /// **NOTE**: If this is a [`Self::NonStandardDataArray`], this function *will* panic as
963    /// this variant requires allocation.
964    ///
965    /// Prefer this method over [`Self::as_param_with_unit_const`] when the default unit is
966    /// acceptable, and you are confident you are not dealing with a non-standard array.
967    ///
968    /// # Panics
969    /// When `self` is either [`Self::NonStandardDataArray`] or an unimplemented variant.
970    pub const fn as_param_const(&self) -> ParamCow<'static> {
971        const CV: ControlledVocabulary = ControlledVocabulary::MS;
972
973        match self {
974            ArrayType::MZArray => CV.const_param_ident_unit("m/z array", 1000514, self.preferred_unit().unwrap()),
975            ArrayType::IntensityArray => {
976                CV.const_param_ident_unit("intensity array", 1000515, self.preferred_unit().unwrap())
977            }
978            ArrayType::ChargeArray => CV.const_param_ident("charge array", 1000516),
979            ArrayType::TimeArray => CV.const_param_ident_unit("time array", 1000595, self.preferred_unit().unwrap()),
980            ArrayType::WavelengthArray => {
981                CV.const_param_ident_unit("wavelength array", 1000617, self.preferred_unit().unwrap())
982            }
983            ArrayType::SignalToNoiseArray => CV.const_param_ident("signal to noise array", 1000517),
984            ArrayType::IonMobilityArray => CV.const_param_ident("ion mobility array", 1002893),
985            ArrayType::RawIonMobilityArray => {
986                CV.const_param_ident("raw ion mobility array", 1003007)
987            }
988            ArrayType::MeanIonMobilityArray => {
989                CV.const_param_ident("mean ion mobility array", 1002816)
990            }
991            ArrayType::DeconvolutedIonMobilityArray => {
992                CV.const_param_ident("deconvoluted ion mobility array", 1003154)
993            }
994            ArrayType::RawDriftTimeArray => CV.const_param_ident_unit(
995                "raw ion mobility drift time array",
996                1003153,
997                self.preferred_unit().unwrap(),
998            ),
999            ArrayType::RawInverseReducedIonMobilityArray => CV.const_param_ident_unit(
1000                "raw inverse reduced ion mobility array",
1001                1003008,
1002                self.preferred_unit().unwrap(),
1003            ),
1004
1005            ArrayType::MeanDriftTimeArray => CV.const_param_ident_unit(
1006                "mean ion mobility drift time array",
1007                1002477,
1008                self.preferred_unit().unwrap(),
1009            ),
1010            ArrayType::MeanInverseReducedIonMobilityArray => CV.const_param_ident_unit(
1011                "mean inverse reduced ion mobility array",
1012                1003006,
1013                self.preferred_unit().unwrap(),
1014            ),
1015
1016            ArrayType::DeconvolutedDriftTimeArray => CV.const_param_ident_unit(
1017                "deconvoluted ion mobility drift time array",
1018                1003156,
1019                self.preferred_unit().unwrap(),
1020            ),
1021            ArrayType::DeconvolutedInverseReducedIonMobilityArray => CV.const_param_ident_unit(
1022                "deconvoluted inverse reduced ion mobility array",
1023                1003155,
1024                self.preferred_unit().unwrap(),
1025            ),
1026
1027            ArrayType::NonStandardDataArray { name: _name } => {
1028                panic!(
1029                    "Cannot format NonStandardDataArray in a const context, please use `as_param`"
1030                );
1031            }
1032            ArrayType::BaselineArray => CV.const_param_ident("baseline array", 1002530),
1033            ArrayType::ResolutionArray => CV.const_param_ident("resolution array", 1002529),
1034            ArrayType::PressureArray => CV.const_param_ident_unit("pressure array", 1000821, self.preferred_unit().unwrap()),
1035            ArrayType::TemperatureArray => CV.const_param_ident("temperature array", 1000822),
1036            ArrayType::FlowRateArray => CV.const_param_ident_unit("flow rate array", 1000820, self.preferred_unit().unwrap()),
1037            ArrayType::ScanningQuadrupolePositionLowerBoundMZ => {
1038                let mut p = CV.const_param_ident("scanning quadrupole position lower bound m/z array", 1003157);
1039                p.unit = self.preferred_unit().unwrap();
1040                p
1041            }
1042            ArrayType::IndexArray => {
1043                CV.const_param_ident("index array", 1003870)
1044            }
1045            ArrayType::ScanningQuadrupolePositionUpperBoundMZ => {
1046                let mut p = CV.const_param_ident("scanning quadrupole position upper bound m/z array", 1003158);
1047                p.unit = self.preferred_unit().unwrap();
1048                p
1049            }
1050            _ => {
1051                panic!("Could not determine how to name for array");
1052            }
1053        }
1054    }
1055
1056    /// Create a [`ParamCow`] for this array type in a `const` context using the provided unit.
1057    ///
1058    /// **NOTE**: If this is a [`Self::NonStandardDataArray`], this function *will* panic as
1059    /// this variant requires allocation.
1060    ///
1061    /// Prefer this method over [`Self::as_param_const`] when you want to control the unit
1062    /// directly rather than accepting the default unit and possibly updating it in the calling
1063    /// code.
1064    ///
1065    /// # Panics
1066    /// When `self` is either [`Self::NonStandardDataArray`] or an unimplemented variant.
1067    pub const fn as_param_with_unit_const(&self, unit: Unit) -> ParamCow<'static> {
1068        let mut inner = self.as_param_const();
1069        inner.unit = unit;
1070        inner
1071    }
1072
1073    /// Translate from [`CURIE`] to an [`ArrayType`]
1074    pub fn from_accession(x: CURIE) -> Option<Self> {
1075        let tp = if x == Self::MZArray.as_param_const().curie().unwrap() {
1076            Self::MZArray
1077        } else if x == Self::IntensityArray.as_param_const().curie().unwrap() {
1078            Self::IntensityArray
1079        } else if x == Self::ChargeArray.as_param_const().curie().unwrap() {
1080            Self::ChargeArray
1081        } else if x == Self::SignalToNoiseArray.as_param_const().curie().unwrap() {
1082            Self::SignalToNoiseArray
1083        } else if x == Self::TimeArray.as_param_const().curie().unwrap() {
1084            Self::TimeArray
1085        } else if x == Self::WavelengthArray.as_param_const().curie().unwrap() {
1086            Self::WavelengthArray
1087        } else if x == Self::IonMobilityArray.as_param_const().curie().unwrap() {
1088            Self::IonMobilityArray
1089        } else if x == Self::MeanIonMobilityArray.as_param_const().curie().unwrap() {
1090            Self::MeanIonMobilityArray
1091        } else if x == Self::MeanDriftTimeArray.as_param_const().curie().unwrap() {
1092            Self::MeanDriftTimeArray
1093        } else if x
1094            == Self::MeanInverseReducedIonMobilityArray
1095                .as_param_const()
1096                .curie()
1097                .unwrap()
1098        {
1099            Self::MeanInverseReducedIonMobilityArray
1100        } else if x == Self::RawIonMobilityArray.as_param_const().curie().unwrap() {
1101            Self::RawIonMobilityArray
1102        } else if x == Self::RawDriftTimeArray.as_param_const().curie().unwrap() {
1103            Self::RawDriftTimeArray
1104        } else if x
1105            == Self::RawInverseReducedIonMobilityArray
1106                .as_param_const()
1107                .curie()
1108                .unwrap()
1109        {
1110            Self::RawInverseReducedIonMobilityArray
1111        } else if x
1112            == Self::DeconvolutedIonMobilityArray
1113                .as_param_const()
1114                .curie()
1115                .unwrap()
1116        {
1117            Self::DeconvolutedIonMobilityArray
1118        } else if x
1119            == Self::DeconvolutedDriftTimeArray
1120                .as_param_const()
1121                .curie()
1122                .unwrap()
1123        {
1124            Self::DeconvolutedDriftTimeArray
1125        } else if x
1126            == Self::DeconvolutedInverseReducedIonMobilityArray
1127                .as_param_const()
1128                .curie()
1129                .unwrap()
1130        {
1131            Self::DeconvolutedInverseReducedIonMobilityArray
1132        } else if x == Self::BaselineArray.as_param_const().curie().unwrap() {
1133            Self::BaselineArray
1134        } else if x == Self::ResolutionArray.as_param_const().curie().unwrap() {
1135            Self::ResolutionArray
1136        } else if x == Self::PressureArray.as_param_const().curie().unwrap() {
1137            Self::PressureArray
1138        } else if x == Self::TemperatureArray.as_param_const().curie().unwrap() {
1139            Self::TemperatureArray
1140        } else if x == Self::FlowRateArray.as_param_const().curie().unwrap() {
1141            Self::FlowRateArray
1142        } else if x == Self::ScanningQuadrupolePositionLowerBoundMZ.as_param_const().curie().unwrap() {
1143            Self::ScanningQuadrupolePositionLowerBoundMZ
1144        } else if x == Self::ScanningQuadrupolePositionUpperBoundMZ.as_param_const().curie().unwrap() {
1145            Self::ScanningQuadrupolePositionUpperBoundMZ
1146        } else if x == Self::IndexArray.as_param_const().curie().unwrap() {
1147            Self::IndexArray
1148        }
1149        else if x
1150            == (Self::NonStandardDataArray {
1151                name: "".to_string().into(),
1152            })
1153            .as_param(None)
1154            .curie()
1155            .unwrap()
1156        {
1157            Self::NonStandardDataArray {
1158                name: "".to_string().into(),
1159            }
1160        } else {
1161            return None;
1162        };
1163        Some(tp)
1164    }
1165}
1166
1167/// The canonical primitive data types found in MS data file formats
1168/// supported by the PSI-MS controlled vocabulary
1169#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, Default)]
1170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1171pub enum BinaryDataArrayType {
1172    /// Unspecified, opaque data
1173    #[default]
1174    Unknown,
1175    /// 64-bit floating point numbers
1176    Float64,
1177    /// 32-bit floating point numbers
1178    Float32,
1179    /// 64-bit signed integers
1180    Int64,
1181    /// 32-bit signed integers
1182    Int32,
1183    /// Null-terminated ASCII strings
1184    ASCII,
1185}
1186
1187impl Display for BinaryDataArrayType {
1188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1189        write!(f, "{:?}", self)
1190    }
1191}
1192
1193impl BinaryDataArrayType {
1194    /// Get the size in bytes of a single value of this type
1195    ///
1196    /// # Examples
1197    /// ```rust
1198    /// use mzdata_bindata::BinaryDataArrayType;
1199    ///
1200    /// assert_eq!(BinaryDataArrayType::Float32.size_of(), 4);
1201    /// assert_eq!(BinaryDataArrayType::Float64.size_of(), 8);
1202    /// assert_eq!(BinaryDataArrayType::ASCII.size_of(), 1);
1203    /// ```
1204    pub const fn size_of(&self) -> usize {
1205        match self {
1206            BinaryDataArrayType::Unknown | BinaryDataArrayType::ASCII => 1,
1207            BinaryDataArrayType::Float32 | BinaryDataArrayType::Int32 => 4,
1208            BinaryDataArrayType::Float64 | BinaryDataArrayType::Int64 => 8,
1209        }
1210    }
1211
1212    /// Convert the data type to a [`ParamCow`] if possible.
1213    pub const fn as_param_const(&self) -> Option<ParamCow<'static>> {
1214        let name = match self {
1215            BinaryDataArrayType::Unknown => return None,
1216            BinaryDataArrayType::Float64 => "64-bit float",
1217            BinaryDataArrayType::Float32 => "32-bit float",
1218            BinaryDataArrayType::Int64 => "64-bit integer",
1219            BinaryDataArrayType::Int32 => "32-bit integer",
1220            BinaryDataArrayType::ASCII => "null-terminated ASCII string",
1221        };
1222        if let Some(curie) = self.curie() {
1223            Some(ParamCow::const_new(
1224                name,
1225                ValueRef::Empty,
1226                Some(curie.accession),
1227                Some(curie.controlled_vocabulary),
1228                Unit::Unknown,
1229            ))
1230        } else {
1231            None
1232        }
1233    }
1234
1235    /// Convert the data type to a [`CURIE`] if one exists for it
1236    pub const fn curie(&self) -> Option<CURIE> {
1237        match self {
1238            Self::Float32 => Some(curie!(MS:1000521)),
1239            Self::Float64 => Some(curie!(MS:1000523)),
1240            Self::Int32 => Some(curie!(MS:1000519)),
1241            Self::Int64 => Some(curie!(MS:1000522)),
1242            Self::ASCII => Some(curie!(MS:1001479)),
1243            _ => None,
1244        }
1245    }
1246
1247    pub fn from_accession(accession: CURIE) -> Option<Self> {
1248        match accession {
1249            x if Some(x) == Self::Float32.curie() => Some(Self::Float32),
1250            x if Some(x) == Self::Float64.curie() => Some(Self::Float64),
1251            x if Some(x) == Self::Int32.curie() => Some(Self::Int32),
1252            x if Some(x) == Self::Int64.curie() => Some(Self::Int64),
1253            x if Some(x) == Self::ASCII.curie() => Some(Self::ASCII),
1254            _ => None,
1255        }
1256    }
1257
1258    /// Byte-order swap the data stored in `data`, treating it as a sequence of
1259    /// fixed-width elements of `self`'s size.
1260    ///
1261    /// # Examples
1262    /// ```rust
1263    /// use mzdata_bindata::BinaryDataArrayType;
1264    ///
1265    /// let mut bytes = 42u32.to_le_bytes();
1266    /// BinaryDataArrayType::Int32.swap_bytes(&mut bytes).unwrap();
1267    /// assert_eq!(u32::from_be_bytes(bytes), 42);
1268    /// ```
1269    pub fn swap_bytes(&self, data: &mut [u8]) -> Result<(), ArrayRetrievalError> {
1270        let z = self.size_of();
1271        if !(data.len() % z == 0) {
1272            return Err(ArrayRetrievalError::DataTypeSizeMismatch)
1273        }
1274        match z {
1275            1 => {
1276                data.reverse();
1277            }
1278            4 => {
1279                data.as_chunks_mut::<4>().0.into_iter().for_each(|c| {
1280                    *c = u32::from_ne_bytes(*c).swap_bytes().to_ne_bytes();
1281                });
1282            }
1283            8 => {
1284                data.as_chunks_mut::<8>().0.into_iter().for_each(|c| {
1285                    *c = u64::from_ne_bytes(*c).swap_bytes().to_ne_bytes();
1286                });
1287                // data.chunks_exact_mut(4).for_each(|c| {
1288                //     c.swap(0, 7);
1289                //     c.swap(1, 6);
1290                //     c.swap(2, 5);
1291                //     c.swap(3, 4);
1292                // });
1293            }
1294            x => {
1295                data.chunks_exact_mut(x).for_each(|c| c.reverse());
1296            }
1297        }
1298        Ok(())
1299    }
1300}
1301
1302/// The range of compression and encoding states that a raw byte buffer
1303/// might be in during different stages of decoding. Other than `Decoded`,
1304/// these states may or may not include intermediate base64 encoding.
1305#[derive(Debug, Clone, Copy, PartialEq, Hash, Default)]
1306#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1307pub enum BinaryCompressionType {
1308    /// The data are not compressed, but are base64 encoded and not ready for native use.
1309    #[default]
1310    NoCompression,
1311
1312    /// A well known, widely available general-purpose compressor codec with many implementations. Always
1313    /// available, but several backends with differing speed and size tradeoffs are available under different
1314    /// features.
1315    Zlib,
1316
1317    /// **Warning: Lossy** A combination simple linear model fit and byte packing codec that stores the model
1318    /// coefficients as float64 and the residuals as unsigned 16 or 32-bit integers. With an optimal fixed point, this will produce a median loss of accuracy below
1319    /// of about 2e-8 absolute units, but may vary widely from dataset to dataset. This codec **MUST** be used with sorted data only, and is only suitable for
1320    /// float64 data. It may be combined with `zlib` [`BinaryCompressionType::NumpressLinearZlib`] or `zstd` [`BinaryCompressionType::NumpressLinearZstd`] for even
1321    /// greater space savings. See details at <https://doi.org/10.1074/mcp.O114.037879>
1322    ///
1323    /// Requires feature `numpress`
1324    NumpressLinear,
1325
1326    /// **Warning: Lossy** A lossy numerical approximation that log transforms and scales values to make them very
1327    /// close to 16-bit integers. This codec is lossier the larger the value is, and is only appropriate for count data like intensities. It can also combine with
1328    /// `zlib` [`BinaryCompressionType::NumpressSLOFZlib`] and `zstd` [`BinaryCompressionType::NumpressSLOFZstd`]. See details at <https://doi.org/10.1074/mcp.O114.037879>
1329    ///
1330    /// Requires feature `numpress`
1331    NumpressSLOF,
1332
1333    /// **Warning: Lossy** This simply rounds a floating point value and then packs it
1334    /// into a truncated integer. May be suitable for intensity values, but not consistently better than [`BinaryCompressionType::NumpressSLOF`].
1335    /// See details at <https://doi.org/10.1074/mcp.O114.037879>
1336    ///
1337    /// Requires feature `numpress`
1338    NumpressPIC,
1339    /// **Warning: Lossy** Combines [`Self::Zlib`] with [`Self::NumpressLinear`]
1340    NumpressLinearZlib,
1341    /// **Warning: Lossy** Combines [`Self::Zlib`] with [`Self::NumpressSLOF`]
1342    NumpressSLOFZlib,
1343    /// **Warning: Lossy** Combines [`Self::Zlib`] with [`Self::NumpressPIC`]
1344    NumpressPICZlib,
1345    /// **Warning: Lossy** The same concept as [`BinaryCompressionType::NumpressLinear`] without the truncation
1346    /// All of the same concerns apply though it loses less precision. This does not perform any form of compression. It was uses as part of mzMLb which applies
1347    /// container-level compression. Only suitable for float64 data.
1348    LinearPrediction,
1349    /// **Warning: Lossy** This computes first order difference of a sorted array of values and stores the difference
1350    /// and the starting point. It is sometimes more accurate than [`BinaryCompressionType::LinearPrediction`]. Like [`BinaryCompressionType::LinearPrediction`],
1351    /// it does not do any actual compression. It was meant for use with mzMLb, which compresses the entire container. Only suitable for float64 data.
1352    DeltaPrediction,
1353    /// The data are completely decoded in native byte order
1354    Decoded,
1355    /// A newer general-purpose codec that is not widely used in mzML but very popular in other applications.
1356    /// Much faster and often better compression compared to `zlib`.
1357    ///
1358    /// Unlike [`Self::Zlib`], [`Self::Zstd`]'s default compression level is controlled by [`zstd::DEFAULT_COMPRESSION_LEVEL`],
1359    /// but the `MZDATA_ZSTD_LEVEL` environment variable can be used to raise or lower it as desired.
1360    ///
1361    /// Requires feature `zstd`
1362    Zstd,
1363    /// This first applies a byte shuffle transform to the data to compress
1364    /// the `n`th byte of each value contiguously, and then apply [`Self::Zstd`]. This makes sorted data compress more effectively.
1365    ///
1366    /// Requires feature `zstd`
1367    ShuffleZstd,
1368    /// **Warning: Lossy** This computes first order difference of a sorted array of values and stores the difference
1369    /// and the starting point. It then byte shuffles the differences and [`Self::Zstd`] compresses them.
1370    /// Only suitable for sorted float64 data.
1371    ///
1372    /// Requires feature `zstd`
1373    DeltaShuffleZstd,
1374    /// Another elaboration of `byte-shuffled zstd` where the values are first dictionary-encoded
1375    /// so that repeated values are denoted by indices rather than the full size value itself. The dictionary is sorted and then both the dictionary and the
1376    /// indices are byte shuffle encoded and then [`Self::Zstd`] compressed. This is even more effective than previous codecs for data with repeated, semi-sorted values
1377    /// like m/z and ion mobility.
1378    ///
1379    /// Requires feature `zstd`
1380    ZstdDict,
1381    /// **Warning: Lossy** Combines [`Self::Zstd`] with [`Self::NumpressLinear`]
1382    NumpressLinearZstd,
1383    /// **Warning: Lossy** Combines [`Self::Zstd`] with [`Self::NumpressSLOF`]
1384    NumpressSLOFZstd,
1385    /// **Warning: Lossy** Combines [`Self::Zstd`] with [`Self::NumpressPIC`]
1386    NumpressPICZstd,
1387}
1388
1389impl BinaryCompressionType {
1390    /// All compression methods in an array
1391    pub const COMPRESSION_METHODS: &[Self] = &[
1392        Self::NoCompression,
1393        Self::Zlib,
1394
1395        #[cfg(feature = "numpress")]
1396        Self::NumpressLinear,
1397        #[cfg(feature = "numpress")]
1398        Self::NumpressLinearZlib,
1399        #[cfg(feature = "numpress")]
1400        Self::NumpressSLOF,
1401        #[cfg(feature = "numpress")]
1402        Self::NumpressSLOFZlib,
1403
1404        #[cfg(feature = "zstd")]
1405        Self::Zstd,
1406        #[cfg(feature = "zstd")]
1407        Self::ShuffleZstd,
1408        #[cfg(feature = "zstd")]
1409        Self::DeltaShuffleZstd,
1410        #[cfg(feature = "zstd")]
1411        Self::ZstdDict,
1412
1413        #[cfg(all(feature = "zstd", feature = "numpress"))]
1414        Self::NumpressLinearZstd,
1415        #[cfg(all(feature = "zstd", feature = "numpress"))]
1416        Self::NumpressSLOFZstd,
1417    ];
1418
1419    /// Generate a user-understandable message about why a compression conversion operation failed
1420    pub fn unsupported_msg(&self, context: Option<&str>) -> String {
1421        match context {
1422            Some(ctx) => format!("Cannot decode array compressed with {:?} ({})", self, ctx),
1423            None => format!("Cannot decode array compressed with {:?}", self),
1424        }
1425    }
1426
1427    /// Check if the compressor handles endianess during decoding. Otherwise the data needs to be
1428    /// byteswapped to little endianess on big endian systems before use
1429    pub const fn is_endian_aware(&self) -> bool {
1430        match self {
1431            BinaryCompressionType::Zlib |
1432            BinaryCompressionType::NoCompression |
1433            BinaryCompressionType::DeltaPrediction |
1434            BinaryCompressionType::LinearPrediction |
1435            BinaryCompressionType::Zstd => false,
1436            _ => true,
1437
1438        }
1439    }
1440
1441    /// Get the accession number in the [`ControlledVocabulary::MS`] vocabulary.
1442    pub const fn accession(&self) -> Option<u32> {
1443        let acc = match self {
1444            BinaryCompressionType::NoCompression => 1000576,
1445            BinaryCompressionType::Zlib => 1000574,
1446            BinaryCompressionType::NumpressLinear => 1002312,
1447            BinaryCompressionType::NumpressSLOF => 1002314,
1448            BinaryCompressionType::NumpressPIC => 1002313,
1449            BinaryCompressionType::NumpressLinearZlib => 1002746,
1450            BinaryCompressionType::NumpressSLOFZlib => 1002748,
1451            BinaryCompressionType::NumpressPICZlib => 1002747,
1452            BinaryCompressionType::DeltaPrediction => 1003089,
1453            BinaryCompressionType::LinearPrediction => 1003090,
1454            BinaryCompressionType::NumpressSLOFZstd => 1003785,
1455            BinaryCompressionType::NumpressLinearZstd => 1003783,
1456            BinaryCompressionType::NumpressPICZstd => 1003784,
1457            BinaryCompressionType::ZstdDict => 1003782,
1458            BinaryCompressionType::Zstd => 1003780,
1459            BinaryCompressionType::ShuffleZstd => 1003781,
1460            BinaryCompressionType::DeltaShuffleZstd => 9999999,
1461            BinaryCompressionType::Decoded => return None,
1462        };
1463        Some(acc)
1464    }
1465
1466    /// Convert a [`CURIE`] into a compression method, if one exists.
1467    pub fn from_accession(accession: CURIE) -> Option<Self> {
1468        match accession {
1469            CURIE {
1470                controlled_vocabulary: ControlledVocabulary::MS,
1471                accession: 1000576,
1472            } => Some(Self::NoCompression),
1473            CURIE {
1474                controlled_vocabulary: ControlledVocabulary::MS,
1475                accession: 1000574,
1476            } => Some(BinaryCompressionType::Zlib),
1477            CURIE {
1478                controlled_vocabulary: ControlledVocabulary::MS,
1479                accession: 1002312,
1480            } => Some(BinaryCompressionType::NumpressLinear),
1481            CURIE {
1482                controlled_vocabulary: ControlledVocabulary::MS,
1483                accession: 1002314,
1484            } => Some(BinaryCompressionType::NumpressSLOF),
1485            CURIE {
1486                controlled_vocabulary: ControlledVocabulary::MS,
1487                accession: 1002313,
1488            } => Some(BinaryCompressionType::NumpressPIC),
1489            CURIE {
1490                controlled_vocabulary: ControlledVocabulary::MS,
1491                accession: 1002746,
1492            } => Some(BinaryCompressionType::NumpressLinearZlib),
1493            CURIE {
1494                controlled_vocabulary: ControlledVocabulary::MS,
1495                accession: 1002748,
1496            } => Some(BinaryCompressionType::NumpressSLOFZlib),
1497            CURIE {
1498                controlled_vocabulary: ControlledVocabulary::MS,
1499                accession: 1002747,
1500            } => Some(BinaryCompressionType::NumpressPICZlib),
1501            CURIE {
1502                controlled_vocabulary: ControlledVocabulary::MS,
1503                accession: 1003089,
1504            } => Some(BinaryCompressionType::DeltaPrediction),
1505            CURIE {
1506                controlled_vocabulary: ControlledVocabulary::MS,
1507                accession: 1003090,
1508            } => Some(BinaryCompressionType::LinearPrediction),
1509            x if x
1510                == CURIE {
1511                    controlled_vocabulary: ControlledVocabulary::MS,
1512                    accession: BinaryCompressionType::NumpressSLOFZstd.accession().unwrap(),
1513                } =>
1514            {
1515                Some(BinaryCompressionType::NumpressSLOFZstd)
1516            }
1517            x if x
1518                == CURIE {
1519                    controlled_vocabulary: ControlledVocabulary::MS,
1520                    accession: BinaryCompressionType::NumpressPICZstd.accession().unwrap(),
1521                } =>
1522            {
1523                Some(BinaryCompressionType::NumpressPICZstd)
1524            }
1525            x if x
1526                == CURIE {
1527                    controlled_vocabulary: ControlledVocabulary::MS,
1528                    accession: BinaryCompressionType::NumpressLinearZstd
1529                        .accession()
1530                        .unwrap(),
1531                } =>
1532            {
1533                Some(BinaryCompressionType::NumpressLinearZstd)
1534            }
1535            x if x
1536                == CURIE {
1537                    controlled_vocabulary: ControlledVocabulary::MS,
1538                    accession: BinaryCompressionType::ZstdDict.accession().unwrap(),
1539                } =>
1540            {
1541                Some(BinaryCompressionType::ZstdDict)
1542            }
1543            x if x
1544                == CURIE {
1545                    controlled_vocabulary: ControlledVocabulary::MS,
1546                    accession: BinaryCompressionType::Zstd.accession().unwrap(),
1547                } =>
1548            {
1549                Some(BinaryCompressionType::Zstd)
1550            }
1551            x if x
1552                == CURIE {
1553                    controlled_vocabulary: ControlledVocabulary::MS,
1554                    accession: BinaryCompressionType::ShuffleZstd.accession().unwrap(),
1555                } =>
1556            {
1557                Some(BinaryCompressionType::ShuffleZstd)
1558            }
1559            x if x
1560                == CURIE {
1561                    controlled_vocabulary: ControlledVocabulary::MS,
1562                    accession: BinaryCompressionType::DeltaShuffleZstd.accession().unwrap(),
1563                } =>
1564            {
1565                Some(BinaryCompressionType::DeltaShuffleZstd)
1566            }
1567            _ => None,
1568        }
1569    }
1570
1571    /// Convert the compression type to a [`ParamCow`].
1572    ///
1573    /// Most compression methods have a controlled vocabulary
1574    /// term.
1575    pub const fn as_param(&self) -> Option<ParamCow<'static>> {
1576        let (name, accession) = match self {
1577            BinaryCompressionType::Decoded => return None,
1578            BinaryCompressionType::NoCompression => ("no compression", self.accession()),
1579            BinaryCompressionType::Zlib => ("zlib compression", self.accession()),
1580            BinaryCompressionType::NumpressLinear => (
1581                "MS-Numpress linear prediction compression",
1582                self.accession(),
1583            ),
1584            BinaryCompressionType::NumpressSLOF => (
1585                "MS-Numpress short logged float compression",
1586                self.accession(),
1587            ),
1588            BinaryCompressionType::NumpressPIC => {
1589                ("MS-Numpress positive integer compression", self.accession())
1590            }
1591            BinaryCompressionType::NumpressLinearZlib => (
1592                "MS-Numpress linear prediction compression followed by zlib compression",
1593                self.accession(),
1594            ),
1595            BinaryCompressionType::NumpressSLOFZlib => (
1596                "MS-Numpress short logged float compression followed by zlib compression",
1597                self.accession(),
1598            ),
1599            BinaryCompressionType::NumpressPICZlib => (
1600                "MS-Numpress positive integer compression followed by zlib compression",
1601                self.accession(),
1602            ),
1603            BinaryCompressionType::DeltaPrediction => (
1604                "truncation, delta prediction and zlib compression",
1605                self.accession(),
1606            ),
1607            BinaryCompressionType::LinearPrediction => (
1608                "truncation, linear prediction and zlib compression",
1609                self.accession(),
1610            ),
1611            BinaryCompressionType::NumpressSLOFZstd => {
1612                return Some(ParamCow::const_new(
1613                    "MS-Numpress short logged float compression followed by zstd compression",
1614                    ValueRef::Empty,
1615                    self.accession(),
1616                    Some(ControlledVocabulary::MS),
1617                    Unit::Unknown,
1618                ))
1619            }
1620            BinaryCompressionType::NumpressLinearZstd => {
1621                return Some(ParamCow::const_new(
1622                    "MS-Numpress linear prediction compression followed by zstd compression",
1623                    ValueRef::Empty,
1624                    self.accession(),
1625                    Some(ControlledVocabulary::MS),
1626                    Unit::Unknown,
1627                ))
1628            }
1629            BinaryCompressionType::NumpressPICZstd => {
1630                return Some(ParamCow::const_new(
1631                    "MS-Numpress positive integer compression followed by zstd compression",
1632                    ValueRef::Empty,
1633                    self.accession(),
1634                    Some(ControlledVocabulary::MS),
1635                    Unit::Unknown,
1636                ))
1637            }
1638            BinaryCompressionType::ZstdDict => {
1639                return Some(ParamCow::const_new(
1640                    "dict-zstd compression",
1641                    ValueRef::Empty,
1642                    self.accession(),
1643                    Some(ControlledVocabulary::MS),
1644                    Unit::Unknown,
1645                ))
1646            }
1647            BinaryCompressionType::Zstd => {
1648                return Some(ParamCow::const_new(
1649                    "zstd compression",
1650                    ValueRef::Empty,
1651                    self.accession(),
1652                    Some(ControlledVocabulary::MS),
1653                    Unit::Unknown,
1654                ))
1655            }
1656            BinaryCompressionType::ShuffleZstd => {
1657                return Some(ParamCow::const_new(
1658                    "byte-shuffle-zstd compression",
1659                    ValueRef::Empty,
1660                    self.accession(),
1661                    Some(ControlledVocabulary::MS),
1662                    Unit::Unknown,
1663                ))
1664            }
1665            BinaryCompressionType::DeltaShuffleZstd => {
1666                return Some(ParamCow::const_new(
1667                    "delta-byte-shuffle-zstd compression",
1668                    ValueRef::Empty,
1669                    self.accession(),
1670                    Some(ControlledVocabulary::MS),
1671                    Unit::Unknown,
1672                ))
1673            }
1674        };
1675        Some(
1676            ControlledVocabulary::MS
1677                .const_param_ident(name, unsafe { accession.unwrap_unchecked() }),
1678        )
1679    }
1680}
1681
1682impl Display for BinaryCompressionType {
1683    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1684        write!(f, "{:?}", self)
1685    }
1686}
1687
1688/// A high level set of failure modes that an operation to retrieve a typed memory buffer
1689/// from a [`BinaryArrayMap`](crate::BinaryArrayMap) might encounter. May also be used to represented conversion
1690/// during reading or writing.
1691#[derive(Debug, Clone, Error, PartialEq)]
1692pub enum ArrayRetrievalError {
1693    #[error("Array type {0:?} not found")]
1694    NotFound(ArrayType),
1695    #[error("An error occurred while decompressing: {0}")]
1696    DecompressionError(String),
1697    #[error("The requested data type does not match the number of bytes available in the buffer, or the conversion is unsupported")]
1698    DataTypeSizeMismatch,
1699    #[error("The content was not properly Base64 encoded")]
1700    MalformedBase64Encoding,
1701}
1702
1703impl From<bytemuck::PodCastError> for ArrayRetrievalError {
1704    fn from(value: bytemuck::PodCastError) -> Self {
1705        match value {
1706            bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => {
1707                Self::DataTypeSizeMismatch
1708            }
1709            bytemuck::PodCastError::OutputSliceWouldHaveSlop => Self::DataTypeSizeMismatch,
1710            bytemuck::PodCastError::SizeMismatch => Self::DataTypeSizeMismatch,
1711            bytemuck::PodCastError::AlignmentMismatch => Self::DataTypeSizeMismatch,
1712        }
1713    }
1714}
1715
1716impl From<ArrayRetrievalError> for io::Error {
1717    fn from(value: ArrayRetrievalError) -> Self {
1718        match value {
1719            ArrayRetrievalError::NotFound(_) => io::Error::new(io::ErrorKind::NotFound, value),
1720            ArrayRetrievalError::DecompressionError(e) => {
1721                io::Error::new(io::ErrorKind::InvalidData, e)
1722            }
1723            ArrayRetrievalError::DataTypeSizeMismatch => {
1724                io::Error::new(io::ErrorKind::InvalidData, value)
1725            },
1726            ArrayRetrievalError::MalformedBase64Encoding => {
1727                io::Error::new(io::ErrorKind::InvalidData, value)
1728            }
1729        }
1730    }
1731}
1732
1733#[cfg(feature = "numpress")]
1734impl From<numpress::Error> for ArrayRetrievalError {
1735    fn from(value: numpress::Error) -> Self {
1736        ArrayRetrievalError::DecompressionError(value.to_string())
1737    }
1738}
1739
1740/// The decoder for [`BinaryCompressionType::LinearPrediction`].
1741pub fn linear_prediction_decoding<F: Num + Copy + Mul + AddAssign>(values: &mut [F]) -> &mut [F] {
1742    if values.len() < 2 {
1743        return values;
1744    }
1745
1746    let two = F::one() + F::one();
1747
1748    let prev2 = values[1];
1749    let prev1 = values[2];
1750    let offset = values[1];
1751
1752    values
1753        .iter_mut()
1754        .skip(2)
1755        .fold((prev1, prev2), |(prev1, prev2), current| {
1756            let tmp = *current + two * prev1 - prev2 - offset;
1757            let prev1 = *current;
1758            let prev2 = prev1;
1759            *current = tmp;
1760            (prev1, prev2)
1761        });
1762
1763    for i in 0..values.len() {
1764        if i < 2 {
1765            continue;
1766        }
1767        let v = values[i] + two * values[i - 1] - values[i - 2] - values[1];
1768        values[i] = v;
1769    }
1770    values
1771}
1772
1773/// The encoder for [`BinaryCompressionType::LinearPrediction`].
1774pub fn linear_prediction_encoding<F: Num + Copy + Mul<F> + AddAssign>(
1775    values: &mut [F],
1776) -> &mut [F] {
1777    let n = values.len();
1778    if n < 3 {
1779        return values;
1780    }
1781    let offset = values[1];
1782    let prev2 = values[0];
1783    let prev1 = values[1];
1784    let two = F::one() + F::one();
1785
1786    values
1787        .iter_mut()
1788        .fold((prev1, prev2), |(prev1, prev2), val| {
1789            *val += offset - two * prev1 + prev2;
1790            let tmp = prev1;
1791            let prev1 = *val + two * prev1 - prev2 - offset;
1792            let prev2 = tmp;
1793            (prev1, prev2)
1794        });
1795    values
1796}
1797
1798/// The decoder for [`BinaryCompressionType::DeltaPrediction`].
1799pub fn delta_decoding<F: Num + Copy + Mul + AddAssign>(values: &mut [F]) -> &mut [F] {
1800    if values.len() < 2 {
1801        return values;
1802    }
1803
1804    let offset = values[0];
1805    let prev = values[1];
1806
1807    values.iter_mut().skip(2).fold(prev, |prev, current| {
1808        *current += prev - offset;
1809        *current
1810    });
1811    values
1812}
1813
1814/// The encoder for [`BinaryCompressionType::DeltaPrediction`].
1815pub fn delta_encoding<F: Num + Copy + Mul + AddAssign>(values: &mut [F]) -> &mut [F] {
1816    let n = values.len();
1817    if n < 2 {
1818        return values;
1819    }
1820    let prev = values[0];
1821    let offset = values[0];
1822
1823    let it = values.iter_mut();
1824    it.skip(1).fold(prev, |prev, current| {
1825        let tmp = *current;
1826        *current += offset - prev;
1827        tmp
1828    });
1829    values
1830}
1831
1832#[cfg(test)]
1833mod test {
1834    use super::*;
1835
1836    #[test]
1837    fn test_dtype_size() {
1838        assert_eq!(BinaryDataArrayType::ASCII.size_of(), 1);
1839        assert_eq!(BinaryDataArrayType::Float32.size_of(), 4);
1840        assert_eq!(BinaryDataArrayType::Int32.size_of(), 4);
1841        assert_eq!(BinaryDataArrayType::Float64.size_of(), 8);
1842        assert_eq!(BinaryDataArrayType::Int64.size_of(), 8);
1843    }
1844
1845    #[test]
1846    fn test_array_type_param() {
1847        let array_types = [
1848            ArrayType::MZArray,
1849            ArrayType::IntensityArray,
1850            ArrayType::ChargeArray,
1851            ArrayType::SignalToNoiseArray,
1852            ArrayType::TimeArray,
1853            ArrayType::WavelengthArray,
1854            ArrayType::IonMobilityArray,
1855            ArrayType::MeanIonMobilityArray,
1856            ArrayType::RawIonMobilityArray,
1857            ArrayType::DeconvolutedIonMobilityArray,
1858        ];
1859
1860        for at in array_types {
1861            assert_eq!(at.as_param_const().name, at.as_param(None).name)
1862        }
1863    }
1864
1865    #[test]
1866    fn test_binary_encoding_conv() {
1867        let encodings = [
1868            BinaryCompressionType::Decoded,
1869            BinaryCompressionType::NoCompression,
1870            BinaryCompressionType::NumpressLinear,
1871            BinaryCompressionType::NumpressLinearZlib,
1872            BinaryCompressionType::NumpressPIC,
1873            BinaryCompressionType::NumpressPICZlib,
1874            BinaryCompressionType::NumpressSLOF,
1875            BinaryCompressionType::NumpressSLOFZlib,
1876            BinaryCompressionType::Zlib,
1877        ];
1878
1879        for enc in encodings {
1880            let reps = match enc {
1881                BinaryCompressionType::NoCompression => ("no compression", 1000576),
1882                BinaryCompressionType::Zlib => ("zlib compression", 1000574),
1883                BinaryCompressionType::NumpressLinear => {
1884                    ("MS-Numpress linear prediction compression", 1002312)
1885                }
1886                BinaryCompressionType::NumpressSLOF => {
1887                    ("MS-Numpress short logged float compression", 1002314)
1888                }
1889                BinaryCompressionType::NumpressPIC => {
1890                    ("MS-Numpress positive integer compression", 1002313)
1891                }
1892                BinaryCompressionType::NumpressLinearZlib => (
1893                    "MS-Numpress linear prediction compression followed by zlib compression",
1894                    1002746,
1895                ),
1896                BinaryCompressionType::NumpressPICZlib => (
1897                    "MS-Numpress positive integer compression followed by zlib compression",
1898                    1002747,
1899                ),
1900                BinaryCompressionType::NumpressSLOFZlib => (
1901                    "MS-Numpress short logged float compression followed by zlib compression",
1902                    1002748,
1903                ),
1904                _ => ("", 0),
1905            };
1906            if let Some(p) = enc.as_param() {
1907                assert_eq!(p.name, reps.0);
1908                assert_eq!(p.accession.unwrap(), reps.1);
1909            }
1910        }
1911    }
1912
1913    #[test]
1914    fn test_transpose() {
1915        let data: Vec<_> = (0..128i32).map(|i| i.pow(2u32) as f64).collect();
1916        let flip = transpose_f64(&data);
1917        let rev = reverse_transpose_f64(&flip);
1918        let rev_cast: &[f64] = bytemuck::cast_slice(&rev);
1919        assert_eq!(data, rev_cast);
1920    }
1921
1922    #[test]
1923    fn test_dict() {
1924        let data: Vec<_> = (0..127i32).map(|i| i.pow(2u32) as f64).collect();
1925        let encoded = dictionary_encoding(&data).unwrap();
1926        let decoded: Vec<f64> = dictionary_decoding(&encoded).unwrap();
1927
1928        assert_eq!(data, decoded);
1929    }
1930
1931    #[test]
1932    fn test_byteswap() {
1933        let x = 42u32;
1934        let mut bytes_of = x.to_le_bytes();
1935        BinaryDataArrayType::Int32.swap_bytes(&mut bytes_of).unwrap();
1936        let y1 = u32::from_le_bytes(bytes_of);
1937        let y2 = x.swap_bytes();
1938        assert_eq!(y1, y2);
1939
1940        bytes_of = x.to_be_bytes();
1941        BinaryDataArrayType::Int32.swap_bytes(&mut bytes_of).unwrap();
1942        let y1 = u32::from_le_bytes(bytes_of);
1943        assert_eq!(x, y1);
1944    }
1945}