Skip to main content

fpzip_rs/
compressor.rs

1use crate::codec::range_decoder::RangeDecoder;
2use crate::codec::range_encoder::RangeEncoder;
3use crate::decoder;
4use crate::encoder;
5use crate::error::{FpZipError, Result};
6use crate::header::{FpZipHeader, FpZipType};
7
8extern crate alloc;
9use alloc::vec;
10use alloc::vec::Vec;
11
12// --- Core compression/decompression with precision ---
13
14fn compress_f32_prec(
15    data: &[f32],
16    nx: u32,
17    ny: u32,
18    nz: u32,
19    nf: u32,
20    prec: u32,
21) -> Result<Vec<u8>> {
22    validate_dimensions(data.len(), nx, ny, nz, nf)?;
23
24    let mut header = FpZipHeader::new(FpZipType::Float, nx, ny, nz, nf);
25    // C++ convention: prec=0 means full precision, but the header stores the actual bits.
26    // C++ sets stream->prec from the user, and compress4d reads it:
27    //   int bits = stream->prec ? stream->prec : (int)(CHAR_BIT * sizeof(T));
28    let bits = if prec == 0 { 32 } else { prec };
29    header.prec = prec;
30    let mut enc = RangeEncoder::with_capacity(data.len() * 4);
31    header.write_to_encoder(&mut enc);
32    encoder::compress_4d_float(
33        &mut enc, data, nx as i32, ny as i32, nz as i32, nf as i32, bits,
34    );
35    Ok(enc.finish())
36}
37
38fn compress_f64_prec(
39    data: &[f64],
40    nx: u32,
41    ny: u32,
42    nz: u32,
43    nf: u32,
44    prec: u32,
45) -> Result<Vec<u8>> {
46    validate_dimensions(data.len(), nx, ny, nz, nf)?;
47
48    let mut header = FpZipHeader::new(FpZipType::Double, nx, ny, nz, nf);
49    let bits = if prec == 0 { 64 } else { prec };
50    header.prec = prec;
51    let mut enc = RangeEncoder::with_capacity(data.len() * 8);
52    header.write_to_encoder(&mut enc);
53    encoder::compress_4d_double(
54        &mut enc, data, nx as i32, ny as i32, nz as i32, nf as i32, bits,
55    );
56    Ok(enc.finish())
57}
58
59fn decompress_f32_impl(data: &[u8]) -> Result<(FpZipHeader, Vec<f32>)> {
60    let mut dec = RangeDecoder::new(data);
61    dec.init();
62
63    let header = FpZipHeader::read_from_decoder(&mut dec)?;
64    if header.data_type != FpZipType::Float {
65        return Err(FpZipError::TypeMismatch {
66            expected: FpZipType::Float,
67            actual: header.data_type,
68        });
69    }
70
71    let bits = if header.prec == 0 { 32 } else { header.prec };
72    let total = header.total_elements() as usize;
73    let mut result = vec![0.0f32; total];
74    decoder::decompress_4d_float(
75        &mut dec,
76        &mut result,
77        header.nx as i32,
78        header.ny as i32,
79        header.nz as i32,
80        header.nf as i32,
81        bits,
82    );
83    Ok((header, result))
84}
85
86fn decompress_f64_impl(data: &[u8]) -> Result<(FpZipHeader, Vec<f64>)> {
87    let mut dec = RangeDecoder::new(data);
88    dec.init();
89
90    let header = FpZipHeader::read_from_decoder(&mut dec)?;
91    if header.data_type != FpZipType::Double {
92        return Err(FpZipError::TypeMismatch {
93            expected: FpZipType::Double,
94            actual: header.data_type,
95        });
96    }
97
98    let bits = if header.prec == 0 { 64 } else { header.prec };
99    let total = header.total_elements() as usize;
100    let mut result = vec![0.0f64; total];
101    decoder::decompress_4d_double(
102        &mut dec,
103        &mut result,
104        header.nx as i32,
105        header.ny as i32,
106        header.nz as i32,
107        header.nf as i32,
108        bits,
109    );
110    Ok((header, result))
111}
112
113// --- Public API ---
114
115/// Compresses a float slice at full 32-bit precision (lossless).
116///
117/// For lossy compression with reduced precision, use [`FpZipCompressor`] with
118/// [`prec`](FpZipCompressor::prec).
119///
120/// # Arguments
121/// * `data` - The float array to compress.
122/// * `nx`, `ny`, `nz`, `nf` - Array dimensions. `data.len()` must equal `nx * ny * nz * nf`.
123///
124/// # Errors
125/// Returns [`FpZipError::DimensionMismatch`] if the data length does not match the dimensions.
126pub fn compress_f32(data: &[f32], nx: u32, ny: u32, nz: u32, nf: u32) -> Result<Vec<u8>> {
127    compress_f32_prec(data, nx, ny, nz, nf, 32)
128}
129
130/// Compresses a double slice at full 64-bit precision (lossless).
131///
132/// See [`compress_f32`] for details; this is the `f64` equivalent.
133pub fn compress_f64(data: &[f64], nx: u32, ny: u32, nz: u32, nf: u32) -> Result<Vec<u8>> {
134    compress_f64_prec(data, nx, ny, nz, nf, 64)
135}
136
137/// Decompresses compressed data to a float vector.
138///
139/// The precision and dimensions are read from the embedded header.
140///
141/// # Errors
142/// Returns [`FpZipError::TypeMismatch`] if the compressed data contains doubles.
143pub fn decompress_f32(data: &[u8]) -> Result<Vec<f32>> {
144    decompress_f32_impl(data).map(|(_, v)| v)
145}
146
147/// Decompresses compressed data to a double vector.
148///
149/// See [`decompress_f32`] for details; this is the `f64` equivalent.
150pub fn decompress_f64(data: &[u8]) -> Result<Vec<f64>> {
151    decompress_f64_impl(data).map(|(_, v)| v)
152}
153
154/// Decompresses float data into a pre-allocated buffer. Returns the header.
155///
156/// # Errors
157/// Returns [`FpZipError::BufferTooSmall`] if `output` is smaller than the decompressed array.
158pub fn decompress_f32_into(data: &[u8], output: &mut [f32]) -> Result<FpZipHeader> {
159    let mut dec = RangeDecoder::new(data);
160    dec.init();
161
162    let header = FpZipHeader::read_from_decoder(&mut dec)?;
163    if header.data_type != FpZipType::Float {
164        return Err(FpZipError::TypeMismatch {
165            expected: FpZipType::Float,
166            actual: header.data_type,
167        });
168    }
169
170    let bits = if header.prec == 0 { 32 } else { header.prec };
171    let total = header.total_elements() as usize;
172    if output.len() < total {
173        return Err(FpZipError::BufferTooSmall {
174            needed: total,
175            available: output.len(),
176        });
177    }
178    decoder::decompress_4d_float(
179        &mut dec,
180        output,
181        header.nx as i32,
182        header.ny as i32,
183        header.nz as i32,
184        header.nf as i32,
185        bits,
186    );
187    Ok(header)
188}
189
190/// Decompresses double data into a pre-allocated buffer. Returns the header.
191///
192/// See [`decompress_f32_into`] for details; this is the `f64` equivalent.
193pub fn decompress_f64_into(data: &[u8], output: &mut [f64]) -> Result<FpZipHeader> {
194    let mut dec = RangeDecoder::new(data);
195    dec.init();
196
197    let header = FpZipHeader::read_from_decoder(&mut dec)?;
198    if header.data_type != FpZipType::Double {
199        return Err(FpZipError::TypeMismatch {
200            expected: FpZipType::Double,
201            actual: header.data_type,
202        });
203    }
204
205    let bits = if header.prec == 0 { 64 } else { header.prec };
206    let total = header.total_elements() as usize;
207    if output.len() < total {
208        return Err(FpZipError::BufferTooSmall {
209            needed: total,
210            available: output.len(),
211        });
212    }
213    decoder::decompress_4d_double(
214        &mut dec,
215        output,
216        header.nx as i32,
217        header.ny as i32,
218        header.nz as i32,
219        header.nf as i32,
220        bits,
221    );
222    Ok(header)
223}
224
225/// Compresses a float slice at full precision into a pre-allocated byte buffer.
226///
227/// Returns the number of bytes written on success.
228///
229/// # Errors
230/// Returns [`FpZipError::BufferTooSmall`] if `destination` cannot hold the compressed output.
231pub fn compress_f32_into(
232    data: &[f32],
233    destination: &mut [u8],
234    nx: u32,
235    ny: u32,
236    nz: u32,
237    nf: u32,
238) -> Result<usize> {
239    let compressed = compress_f32(data, nx, ny, nz, nf)?;
240    if compressed.len() > destination.len() {
241        return Err(FpZipError::BufferTooSmall {
242            needed: compressed.len(),
243            available: destination.len(),
244        });
245    }
246    destination[..compressed.len()].copy_from_slice(&compressed);
247    Ok(compressed.len())
248}
249
250/// Compresses a double slice at full precision into a pre-allocated byte buffer.
251///
252/// See [`compress_f32_into`] for details; this is the `f64` equivalent.
253pub fn compress_f64_into(
254    data: &[f64],
255    destination: &mut [u8],
256    nx: u32,
257    ny: u32,
258    nz: u32,
259    nf: u32,
260) -> Result<usize> {
261    let compressed = compress_f64(data, nx, ny, nz, nf)?;
262    if compressed.len() > destination.len() {
263        return Err(FpZipError::BufferTooSmall {
264            needed: compressed.len(),
265            available: destination.len(),
266        });
267    }
268    destination[..compressed.len()].copy_from_slice(&compressed);
269    Ok(compressed.len())
270}
271
272/// Reads the header from compressed data without decompressing the array.
273///
274/// Useful for inspecting dimensions and type before allocating output buffers.
275pub fn read_header(data: &[u8]) -> Result<FpZipHeader> {
276    let mut dec = RangeDecoder::new(data);
277    dec.init();
278    FpZipHeader::read_from_decoder(&mut dec)
279}
280
281/// Returns an upper bound on the compressed size for the given element count.
282///
283/// Use this to allocate a buffer for [`compress_f32_into`] or [`compress_f64_into`].
284pub fn max_compressed_size(element_count: usize, data_type: FpZipType) -> usize {
285    let element_size = match data_type {
286        FpZipType::Float => 4,
287        FpZipType::Double => 8,
288    };
289    let data_size = element_count * element_size;
290    data_size + (data_size / 20) + 128
291}
292
293/// Builder for configuring and executing fpzip compression.
294///
295/// Provides a fluent API for setting dimensions and precision before compressing.
296/// Defaults to 1D (`ny=1, nz=1, nf=1`) at full precision (lossless).
297///
298/// # Example
299///
300/// ```
301/// use fpzip_rs::{FpZipCompressor, decompress_f32};
302///
303/// let data: Vec<f32> = (0..64).map(|i| i as f32).collect();
304/// let compressed = FpZipCompressor::new(4)
305///     .ny(4)
306///     .nz(4)
307///     .compress_f32(&data)
308///     .unwrap();
309///
310/// let decompressed = decompress_f32(&compressed).unwrap();
311/// assert_eq!(data, decompressed);
312/// ```
313pub struct FpZipCompressor {
314    nx: u32,
315    ny: u32,
316    nz: u32,
317    nf: u32,
318    prec: u32,
319}
320
321impl FpZipCompressor {
322    /// Creates a new compressor for an array with X dimension `nx`.
323    pub fn new(nx: u32) -> Self {
324        Self {
325            nx,
326            ny: 1,
327            nz: 1,
328            nf: 1,
329            prec: 0,
330        }
331    }
332
333    /// Sets the Y dimension (default: 1).
334    pub fn ny(mut self, ny: u32) -> Self {
335        self.ny = ny;
336        self
337    }
338
339    /// Sets the Z dimension (default: 1).
340    pub fn nz(mut self, nz: u32) -> Self {
341        self.nz = nz;
342        self
343    }
344
345    /// Sets the number of fields / 4th dimension (default: 1).
346    pub fn nf(mut self, nf: u32) -> Self {
347        self.nf = nf;
348        self
349    }
350
351    /// Sets the bit precision for lossy compression.
352    ///
353    /// - `0` or full type width (32 for float, 64 for double) = lossless.
354    /// - For float: valid range is 2..=32.
355    /// - For double: valid range is 4..=64 (even values only).
356    /// - Lower precision gives better compression ratios at the cost of accuracy.
357    pub fn prec(mut self, prec: u32) -> Self {
358        self.prec = prec;
359        self
360    }
361
362    /// Compresses a float slice with the configured dimensions and precision.
363    pub fn compress_f32(&self, data: &[f32]) -> Result<Vec<u8>> {
364        compress_f32_prec(data, self.nx, self.ny, self.nz, self.nf, self.prec)
365    }
366
367    /// Compresses a double slice with the configured dimensions and precision.
368    pub fn compress_f64(&self, data: &[f64]) -> Result<Vec<u8>> {
369        compress_f64_prec(data, self.nx, self.ny, self.nz, self.nf, self.prec)
370    }
371
372    /// Compresses a float slice into a pre-allocated buffer.
373    pub fn compress_f32_into(&self, data: &[f32], dest: &mut [u8]) -> Result<usize> {
374        let compressed = self.compress_f32(data)?;
375        if compressed.len() > dest.len() {
376            return Err(FpZipError::BufferTooSmall {
377                needed: compressed.len(),
378                available: dest.len(),
379            });
380        }
381        dest[..compressed.len()].copy_from_slice(&compressed);
382        Ok(compressed.len())
383    }
384
385    /// Compresses a double slice into a pre-allocated buffer.
386    pub fn compress_f64_into(&self, data: &[f64], dest: &mut [u8]) -> Result<usize> {
387        let compressed = self.compress_f64(data)?;
388        if compressed.len() > dest.len() {
389            return Err(FpZipError::BufferTooSmall {
390                needed: compressed.len(),
391                available: dest.len(),
392            });
393        }
394        dest[..compressed.len()].copy_from_slice(&compressed);
395        Ok(compressed.len())
396    }
397}
398
399// --- Stream-based APIs ---
400
401/// Compresses a float slice and writes the output to a writer.
402///
403/// Returns the number of bytes written.
404#[cfg(feature = "std")]
405pub fn compress_f32_to_writer<W: std::io::Write>(
406    data: &[f32],
407    writer: &mut W,
408    nx: u32,
409    ny: u32,
410    nz: u32,
411    nf: u32,
412) -> Result<u64> {
413    let compressed = compress_f32(data, nx, ny, nz, nf)?;
414    writer.write_all(&compressed)?;
415    Ok(compressed.len() as u64)
416}
417
418/// Compresses a double slice and writes the output to a writer.
419///
420/// See [`compress_f32_to_writer`] for details; this is the `f64` equivalent.
421#[cfg(feature = "std")]
422pub fn compress_f64_to_writer<W: std::io::Write>(
423    data: &[f64],
424    writer: &mut W,
425    nx: u32,
426    ny: u32,
427    nz: u32,
428    nf: u32,
429) -> Result<u64> {
430    let compressed = compress_f64(data, nx, ny, nz, nf)?;
431    writer.write_all(&compressed)?;
432    Ok(compressed.len() as u64)
433}
434
435/// Reads and decompresses float data from a reader.
436///
437/// Returns the header and decompressed data.
438#[cfg(feature = "std")]
439pub fn decompress_f32_from_reader<R: std::io::Read>(
440    reader: &mut R,
441) -> Result<(FpZipHeader, Vec<f32>)> {
442    let mut data = Vec::new();
443    reader.read_to_end(&mut data)?;
444    decompress_f32_impl(&data)
445}
446
447/// Reads and decompresses double data from a reader.
448///
449/// See [`decompress_f32_from_reader`] for details; this is the `f64` equivalent.
450#[cfg(feature = "std")]
451pub fn decompress_f64_from_reader<R: std::io::Read>(
452    reader: &mut R,
453) -> Result<(FpZipHeader, Vec<f64>)> {
454    let mut data = Vec::new();
455    reader.read_to_end(&mut data)?;
456    decompress_f64_impl(&data)
457}
458
459fn validate_dimensions(data_length: usize, nx: u32, ny: u32, nz: u32, nf: u32) -> Result<()> {
460    let expected = nx as u64 * ny as u64 * nz as u64 * nf as u64;
461    if data_length as u64 != expected {
462        return Err(FpZipError::DimensionMismatch {
463            actual: data_length,
464            expected,
465            nx,
466            ny,
467            nz,
468            nf,
469        });
470    }
471    Ok(())
472}