1use std::num::NonZeroUsize;
2
3use ndarray::{Array, ArrayView, Dimension, IxDyn};
4
5#[derive(Clone, Debug)]
6pub enum CompressionMode<'a> {
7 SymbolicQuantityOfInterest {
9 qoi: &'a str,
11 qoi_region_size: NonZeroUsize,
14 qoi_error_bound: QoIErrorBound,
16 data_error_bound: DataErrorBound,
18 qoi_c: f64,
20 },
21}
22
23#[derive(Clone, Debug, Copy)]
24pub enum DataErrorBound {
25 Absolute(f64),
26 Relative(f64),
27 PSNR(f64),
28 L2Norm(f64),
29 AbsoluteAndRelative {
30 absolute_bound: f64,
31 relative_bound: f64,
32 },
33 AbsoluteOrRelative {
34 absolute_bound: f64,
35 relative_bound: f64,
36 },
37}
38
39#[derive(Clone, Debug, Copy)]
40pub enum QoIErrorBound {
41 Absolute(f64),
43 Relative(f64),
45}
46
47impl DataErrorBound {
48 const fn code(&self) -> u8 {
49 #[allow(clippy::cast_possible_truncation)]
50 match self {
51 Self::Absolute(_) => const { qpet_sz_sys::QoZ_EB_EB_ABS as u8 },
52 Self::Relative(_) => const { qpet_sz_sys::QoZ_EB_EB_REL as u8 },
53 Self::PSNR(_) => const { qpet_sz_sys::QoZ_EB_EB_PSNR as u8 },
54 Self::L2Norm(_) => const { qpet_sz_sys::QoZ_EB_EB_L2NORM as u8 },
55 Self::AbsoluteAndRelative { .. } => const { qpet_sz_sys::QoZ_EB_EB_ABS_AND_REL as u8 },
56 Self::AbsoluteOrRelative { .. } => const { qpet_sz_sys::QoZ_EB_EB_ABS_OR_REL as u8 },
57 }
58 }
59
60 const fn abs_bound(&self) -> f64 {
61 match self {
62 Self::Absolute(bound) => *bound,
63 Self::AbsoluteOrRelative { absolute_bound, .. }
64 | Self::AbsoluteAndRelative { absolute_bound, .. } => *absolute_bound,
65 _ => 0.0,
66 }
67 }
68
69 const fn rel_bound(&self) -> f64 {
70 match self {
71 Self::Relative(bound) => *bound,
72 Self::AbsoluteOrRelative { relative_bound, .. }
73 | Self::AbsoluteAndRelative { relative_bound, .. } => *relative_bound,
74 _ => 0.0,
75 }
76 }
77
78 const fn l2norm_bound(&self) -> f64 {
79 match self {
80 Self::L2Norm(bound) => *bound,
81 _ => 0.0,
82 }
83 }
84
85 const fn psnr_bound(&self) -> f64 {
86 match self {
87 Self::PSNR(bound) => *bound,
88 _ => 0.0,
89 }
90 }
91}
92
93impl QoIErrorBound {
94 const fn code(&self) -> u8 {
95 #[allow(clippy::cast_possible_truncation)]
96 match self {
97 Self::Absolute(_) => const { qpet_sz_sys::QoZ_EB_EB_ABS as u8 },
98 Self::Relative(_) => const { qpet_sz_sys::QoZ_EB_EB_REL as u8 },
99 }
100 }
101
102 const fn bound(&self) -> f64 {
103 match self {
104 Self::Absolute(bound) | Self::Relative(bound) => *bound,
105 }
106 }
107}
108
109#[derive(Clone, Debug)]
110pub struct Config<'a> {
111 mode: CompressionMode<'a>,
112 openmp: bool,
113}
114
115pub trait Element: private::Element {}
116impl Element for f32 {}
117impl Element for f64 {}
118
119mod private {
120 pub trait Element: Copy {
121 unsafe fn compress(
122 config: qpet_sz_sys::QoZ_Config,
123 data: *const Self,
124 len: *mut usize,
125 ) -> *mut u8;
126
127 unsafe fn decompress(
128 compressed_data: *const u8,
129 compressed_len: usize,
130 decompressed_data: *mut *mut Self,
131 ) -> qpet_sz_sys::QoZ_Config;
132
133 unsafe fn dealloc(data: *mut Self);
134 }
135
136 impl Element for f32 {
137 unsafe fn compress(
138 config: qpet_sz_sys::QoZ_Config,
139 data: *const Self,
140 len: *mut usize,
141 ) -> *mut u8 {
142 qpet_sz_sys::compress_float(config, data, len).cast()
143 }
144
145 unsafe fn decompress(
146 compressed_data: *const u8,
147 compressed_len: usize,
148 decompressed_data: *mut *mut Self,
149 ) -> qpet_sz_sys::QoZ_Config {
150 qpet_sz_sys::decompress_float(compressed_data.cast(), compressed_len, decompressed_data)
151 }
152
153 unsafe fn dealloc(data: *mut Self) {
154 qpet_sz_sys::dealloc_float(data);
155 }
156 }
157
158 impl Element for f64 {
159 unsafe fn compress(
160 config: qpet_sz_sys::QoZ_Config,
161 data: *const Self,
162 len: *mut usize,
163 ) -> *mut u8 {
164 qpet_sz_sys::compress_double(config, data, len).cast()
165 }
166
167 unsafe fn decompress(
168 compressed_data: *const u8,
169 compressed_len: usize,
170 decompressed_data: *mut *mut Self,
171 ) -> qpet_sz_sys::QoZ_Config {
172 qpet_sz_sys::decompress_double(
173 compressed_data.cast(),
174 compressed_len,
175 decompressed_data,
176 )
177 }
178
179 unsafe fn dealloc(data: *mut Self) {
180 qpet_sz_sys::dealloc_double(data);
181 }
182 }
183}
184
185impl<'a> Config<'a> {
186 #[must_use]
187 pub const fn new(mode: CompressionMode<'a>) -> Self {
188 Self {
189 mode,
190 openmp: false,
191 }
192 }
193
194 #[cfg(feature = "openmp")]
195 #[must_use]
196 pub const fn openmp(mut self, openmp: bool) -> Self {
197 self.openmp = openmp;
198 self
199 }
200}
201
202#[allow(clippy::needless_pass_by_value)]
203#[allow(clippy::missing_panics_doc)] pub fn compress<T: Element, D: Dimension>(data: ArrayView<T, D>, config: &Config) -> Vec<u8> {
205 let Config {
206 mode:
207 CompressionMode::SymbolicQuantityOfInterest {
208 qoi,
209 qoi_region_size,
210 qoi_error_bound,
211 data_error_bound,
212 qoi_c,
213 },
214 openmp,
215 } = config;
216
217 let mut qoi_string = Vec::from(qoi.as_bytes());
218 qoi_string.push(b'\0');
219
220 #[allow(clippy::unwrap_used)] let raw_config = qpet_sz_sys::QoZ_Config {
222 N: data.ndim().try_into().unwrap(),
223 dims: data.shape().as_ptr().cast_mut(),
224 num: data.len(),
225 errorBoundMode: data_error_bound.code(),
226 absErrorBound: data_error_bound.abs_bound(),
227 relErrorBound: data_error_bound.rel_bound(),
228 l2normErrorBound: data_error_bound.l2norm_bound(),
229 psnrErrorBound: data_error_bound.psnr_bound(),
230 openmp: *openmp,
231 qoi: 14, qoiEB: qoi_error_bound.bound(),
233 qoiEBMode: qoi_error_bound.code(),
234 qoi_string: qoi_string.as_mut_ptr().cast(),
235 qoiRegionSize: qoi_region_size.get().try_into().unwrap(),
236 qoiRegionMode: match *qoi_region_size {
237 NonZeroUsize::MIN => 0, _ => 1, },
240 error_std_rate: *qoi_c,
241 analytical: true,
242 };
243
244 eprintln!(
246 "compress config N={} num={} shape={:?}",
247 raw_config.N,
248 raw_config.num,
249 data.shape()
250 );
251
252 let mut len: usize = 0;
253
254 let compressed_ptr = unsafe { T::compress(raw_config, data.as_ptr(), &raw mut len) };
255
256 let compressed_data = unsafe { std::slice::from_raw_parts(compressed_ptr, len) };
257 let compressed_data = Vec::from(compressed_data);
258
259 unsafe { qpet_sz_sys::dealloc_char(compressed_ptr.cast()) };
260
261 compressed_data
262}
263
264#[must_use]
265#[allow(clippy::missing_panics_doc)] pub fn decompress<T: Element>(compressed_data: &[u8]) -> Array<T, IxDyn> {
267 let mut decompressed_ptr = std::ptr::null_mut();
268
269 let config = unsafe {
270 T::decompress(
271 compressed_data.as_ptr(),
272 compressed_data.len(),
273 &raw mut decompressed_ptr,
274 )
275 };
276
277 let decompressed_data = unsafe { std::slice::from_raw_parts(decompressed_ptr, config.num) };
278 let decompressed_data = Vec::from(decompressed_data);
279
280 unsafe { T::dealloc(decompressed_ptr) };
281
282 #[allow(clippy::unwrap_used)] let shape: Vec<usize> = (0..config.N)
284 .map(|i| unsafe { std::ptr::read(config.dims.add(i.try_into().unwrap())) })
285 .collect();
286
287 eprintln!(
289 "decompress config N={} num={} shape={:?}",
290 config.N, config.num, shape
291 );
292
293 unsafe {
294 qpet_sz_sys::dealloc_size_t(config.dims);
295 }
296
297 unsafe {
298 qpet_sz_sys::dealloc_char(config.qoi_string);
299 }
300
301 #[allow(clippy::unwrap_used)] let decompressed_data = Array::from_shape_vec(shape, decompressed_data).unwrap();
303
304 decompressed_data
305}
306
307#[cfg(test)]
308#[allow(clippy::unwrap_used)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn log10_decode() {
314 let encoded = compress(
315 Array::<f32, _>::logspace(2.0, 0.0, 100.0, 721 * 1440)
316 .into_shape_clone((721, 1440))
317 .unwrap()
318 .view(),
319 &Config::new(CompressionMode::SymbolicQuantityOfInterest {
320 qoi: "log(x, 10)",
321 qoi_region_size: NonZeroUsize::MIN,
322 qoi_error_bound: QoIErrorBound::Absolute(0.25),
323 data_error_bound: DataErrorBound::Absolute(f64::MAX),
324 qoi_c: 2.0,
325 }),
326 );
327 let decoded = decompress::<f32>(&encoded);
328
329 assert_eq!(decoded.len(), 721 * 1440);
330 assert_eq!(decoded.shape(), &[721, 1440]);
331 }
332}