1use std::num::NonZeroUsize;
24
25use ndarray::{ArrayView3, ArrayViewMut3};
26
27#[derive(Copy, Clone, PartialEq, Debug)]
28#[non_exhaustive]
29pub enum CompressionMode<'a> {
31 SymbolicQuantityOfInterest {
33 qoi: &'a str,
35 qoi_block_size: (NonZeroUsize, NonZeroUsize, NonZeroUsize),
38 qoi_pwe: f64,
41 data_pwe: Option<f64>,
43 qoi_k: f64,
45 high_prec: bool,
47 },
48}
49
50#[derive(Debug, thiserror::Error)]
51pub enum Error {
53 #[error("one or more parameters is invalid")]
55 InvalidParameter,
56 #[error("cannot decompress to an array with a different shape")]
58 DecompressShapeMismatch,
59 #[error("other error")]
61 Other,
62}
63
64#[allow(clippy::missing_panics_doc)]
75pub fn compress_3d<T: Element>(
76 src: ArrayView3<T>,
77 mode: CompressionMode,
78 chunks: (usize, usize, usize),
79) -> Result<Vec<u8>, Error> {
80 let src = src.as_standard_layout();
81
82 let mut dst = std::ptr::null_mut();
83 let mut dst_len = 0;
84
85 let CompressionMode::SymbolicQuantityOfInterest {
86 qoi,
87 qoi_block_size,
88 qoi_pwe,
89 data_pwe,
90 qoi_k,
91 high_prec,
92 } = mode;
93
94 let mut qoi = Vec::from(qoi.as_bytes());
95 qoi.push(b'\0');
96 let qoi = qoi;
97
98 #[allow(unsafe_code)] let res = unsafe {
100 qpet_sperr_sys::qpet_sperr_comp_3d(
101 src.as_ptr().cast(),
102 T::IS_FLOAT.into(),
103 src.dim().2,
104 src.dim().1,
105 src.dim().0,
106 chunks.2,
107 chunks.1,
108 chunks.0,
109 data_pwe.unwrap_or(f64::MAX),
110 0,
111 std::ptr::addr_of_mut!(dst),
112 std::ptr::addr_of_mut!(dst_len),
113 qoi.as_ptr().cast(),
114 qoi_pwe,
115 qoi_block_size.2.get(),
116 qoi_block_size.1.get(),
117 qoi_block_size.0.get(),
118 qoi_k,
119 high_prec,
120 )
121 };
122
123 match res {
124 0 => (), #[allow(clippy::unreachable)]
126 1 => unreachable!("qpet_sperr_comp_3d: dst is not pointing to a NULL pointer"),
127 2 => return Err(Error::InvalidParameter),
128 -1 => return Err(Error::Other),
129 #[allow(clippy::panic)]
130 _ => panic!("qpet_sperr_comp_3d: unknown error kind {res}"),
131 }
132
133 #[allow(unsafe_code)] let compressed =
135 Vec::from(unsafe { std::slice::from_raw_parts(dst.cast_const().cast::<u8>(), dst_len) });
136
137 #[allow(unsafe_code)] unsafe {
139 qpet_sperr_sys::free_dst(dst);
140 }
141
142 Ok(compressed)
143}
144
145#[allow(clippy::missing_panics_doc)]
155pub fn decompress_into_3d<T: Element>(
156 compressed: &[u8],
157 mut decompressed: ArrayViewMut3<T>,
158) -> Result<(), Error> {
159 let mut dim_x = 0;
160 let mut dim_y = 0;
161 let mut dim_z = 0;
162 let mut is_float = 0;
163
164 #[allow(unsafe_code)] unsafe {
166 qpet_sperr_sys::sperr_parse_header(
167 compressed.as_ptr().cast(),
168 std::ptr::addr_of_mut!(dim_x),
169 std::ptr::addr_of_mut!(dim_y),
170 std::ptr::addr_of_mut!(dim_z),
171 std::ptr::addr_of_mut!(is_float),
172 );
173 }
174
175 if (dim_z, dim_y, dim_x)
176 != (
177 decompressed.dim().0,
178 decompressed.dim().1,
179 decompressed.dim().2,
180 )
181 {
182 return Err(Error::DecompressShapeMismatch);
183 }
184
185 let mut dst = std::ptr::null_mut();
186
187 #[allow(unsafe_code)] let res = unsafe {
189 qpet_sperr_sys::sperr_decomp_3d(
190 compressed.as_ptr().cast(),
191 compressed.len(),
192 T::IS_FLOAT.into(),
193 0,
194 std::ptr::addr_of_mut!(dim_x),
195 std::ptr::addr_of_mut!(dim_y),
196 std::ptr::addr_of_mut!(dim_z),
197 std::ptr::addr_of_mut!(dst),
198 )
199 };
200
201 match res {
202 0 => (), #[allow(clippy::unreachable)]
204 1 => unreachable!("sperr_decomp_3d: dst is not pointing to a NULL pointer"),
205 -1 => return Err(Error::Other),
206 #[allow(clippy::panic)]
207 _ => panic!("sperr_decomp_3d: unknown error kind {res}"),
208 }
209
210 #[allow(unsafe_code)] let dec =
212 unsafe { ArrayView3::from_shape_ptr(decompressed.dim(), dst.cast_const().cast::<T>()) };
213 decompressed.assign(&dec);
214
215 #[allow(unsafe_code)] unsafe {
217 qpet_sperr_sys::free_dst(dst);
218 }
219
220 Ok(())
221}
222
223pub trait Element: sealed::Element {}
225
226impl Element for f32 {}
227impl sealed::Element for f32 {
228 const IS_FLOAT: bool = true;
229}
230
231impl Element for f64 {}
232impl sealed::Element for f64 {
233 const IS_FLOAT: bool = false;
234}
235
236mod sealed {
237 pub trait Element: Copy {
238 const IS_FLOAT: bool;
239 }
240}
241
242#[cfg(test)]
243#[allow(clippy::expect_used)]
244mod tests {
245 use ndarray::{linspace, logspace, Array1, Array3};
246
247 use super::*;
248
249 const ONE: NonZeroUsize = NonZeroUsize::MIN;
250 const THREE: NonZeroUsize = ONE.saturating_add(2);
251
252 fn compress_decompress(mode: CompressionMode) {
253 let data = linspace(1.0, 10.0, 128 * 128 * 128).collect::<Array1<f64>>()
254 + logspace(2.0, 0.0, 5.0, 128 * 128 * 128)
255 .rev()
256 .collect::<Array1<f64>>();
257 let data: Array3<f64> = data
258 .into_shape_clone((128, 128, 128))
259 .expect("create test data array");
260
261 let compressed =
262 compress_3d(data.view(), mode, (64, 64, 64)).expect("compression should not fail");
263
264 let mut decompressed = Array3::<f64>::zeros(data.dim());
265 decompress_into_3d(compressed.as_slice(), decompressed.view_mut())
266 .expect("decompression should not fail");
267
268 let data: Array3<f64> = Array3::zeros((64, 64, 1));
269
270 let compressed =
271 compress_3d(data.view(), mode, (256, 256, 256)).expect("compression should not fail");
272
273 let mut decompressed = Array3::<f64>::zeros(data.dim());
274 decompress_into_3d(compressed.as_slice(), decompressed.view_mut())
275 .expect("decompression should not fail");
276 }
277
278 #[test]
279 fn compress_decompress_square() {
280 compress_decompress(CompressionMode::SymbolicQuantityOfInterest {
281 qoi: "x^2",
282 qoi_block_size: (ONE, ONE, ONE),
283 qoi_pwe: 0.1,
284 data_pwe: None,
285 qoi_k: 3.0,
286 high_prec: false,
287 });
288
289 }
298
299 #[test]
300 fn compress_decompress_log10() {
301 compress_decompress(CompressionMode::SymbolicQuantityOfInterest {
302 qoi: "log(x,10)",
303 qoi_block_size: (ONE, ONE, ONE),
304 qoi_pwe: 0.1,
305 data_pwe: None,
306 qoi_k: 3.0,
307 high_prec: true,
308 });
309
310 }
319}