1extern crate zstd_sys;
19
20use tensogram_sz3_sys::SZ3_Config;
21
22#[derive(Clone, Debug, Copy)]
28#[non_exhaustive]
29pub enum CompressionAlgorithm {
30 Interpolation,
31 InterpolationLorenzo,
32 LorenzoRegression {
33 lorenzo: bool,
34 lorenzo_second_order: bool,
35 regression: bool,
36 },
37 BiologyMolecularData,
38 BiologyMolecularDataGromacsXtc,
39 NoPrediction,
40 Lossless,
41}
42
43impl CompressionAlgorithm {
44 fn decode(config: SZ3_Config) -> Result<Self> {
45 match config.cmprAlgo as u32 {
46 tensogram_sz3_sys::SZ3::ALGO_ALGO_INTERP => Ok(Self::Interpolation),
47 tensogram_sz3_sys::SZ3::ALGO_ALGO_INTERP_LORENZO => Ok(Self::InterpolationLorenzo),
48 tensogram_sz3_sys::SZ3::ALGO_ALGO_LORENZO_REG => Ok(Self::LorenzoRegression {
49 lorenzo: config.lorenzo,
50 lorenzo_second_order: config.lorenzo2,
51 regression: config.regression,
52 }),
53 tensogram_sz3_sys::SZ3::ALGO_ALGO_BIOMD => Ok(Self::BiologyMolecularData),
54 tensogram_sz3_sys::SZ3::ALGO_ALGO_BIOMDXTC => Ok(Self::BiologyMolecularDataGromacsXtc),
55 tensogram_sz3_sys::SZ3::ALGO_ALGO_NOPRED => Ok(Self::NoPrediction),
56 tensogram_sz3_sys::SZ3::ALGO_ALGO_LOSSLESS => Ok(Self::Lossless),
57 algo => Err(SZ3Error::UnsupportedAlgorithm(algo)),
58 }
59 }
60
61 fn code(&self) -> u8 {
62 (match self {
63 Self::Interpolation => tensogram_sz3_sys::SZ3::ALGO_ALGO_INTERP,
64 Self::InterpolationLorenzo => tensogram_sz3_sys::SZ3::ALGO_ALGO_INTERP_LORENZO,
65 Self::LorenzoRegression { .. } => tensogram_sz3_sys::SZ3::ALGO_ALGO_LORENZO_REG,
66 Self::BiologyMolecularData => tensogram_sz3_sys::SZ3::ALGO_ALGO_BIOMD,
67 Self::BiologyMolecularDataGromacsXtc => tensogram_sz3_sys::SZ3::ALGO_ALGO_BIOMDXTC,
68 Self::NoPrediction => tensogram_sz3_sys::SZ3::ALGO_ALGO_NOPRED,
69 Self::Lossless => tensogram_sz3_sys::SZ3::ALGO_ALGO_LOSSLESS,
70 }) as _
71 }
72
73 fn lorenzo(&self) -> bool {
74 match self {
75 Self::LorenzoRegression { lorenzo, .. } => *lorenzo,
76 _ => true,
77 }
78 }
79
80 fn lorenzo_second_order(&self) -> bool {
81 match self {
82 Self::LorenzoRegression {
83 lorenzo_second_order,
84 ..
85 } => *lorenzo_second_order,
86 _ => true,
87 }
88 }
89
90 fn regression(&self) -> bool {
91 match self {
92 Self::LorenzoRegression { regression, .. } => *regression,
93 _ => true,
94 }
95 }
96
97 pub fn interpolation() -> Self {
99 Self::Interpolation
100 }
101
102 pub fn interpolation_lorenzo() -> Self {
104 Self::InterpolationLorenzo
105 }
106
107 pub fn lorenzo_regression() -> Self {
109 Self::LorenzoRegression {
110 lorenzo: true,
111 lorenzo_second_order: false,
112 regression: true,
113 }
114 }
115
116 pub fn lorenzo_regression_custom(
118 lorenzo: Option<bool>,
119 lorenzo_second_order: Option<bool>,
120 regression: Option<bool>,
121 ) -> Self {
122 Self::LorenzoRegression {
123 lorenzo: lorenzo.unwrap_or(true),
124 lorenzo_second_order: lorenzo_second_order.unwrap_or(false),
125 regression: regression.unwrap_or(true),
126 }
127 }
128
129 pub fn biology_molecular_data() -> Self {
131 Self::BiologyMolecularData
132 }
133
134 pub fn biology_molecular_data_gromacs_xtc() -> Self {
136 Self::BiologyMolecularDataGromacsXtc
137 }
138
139 pub fn no_prediction() -> Self {
141 Self::NoPrediction
142 }
143
144 pub fn lossless() -> Self {
146 Self::Lossless
147 }
148}
149
150impl Default for CompressionAlgorithm {
151 fn default() -> Self {
152 Self::interpolation_lorenzo()
153 }
154}
155
156#[derive(Clone, Debug, Copy)]
163#[non_exhaustive]
164pub enum ErrorBound {
165 Absolute(f64),
166 Relative(f64),
167 PSNR(f64),
168 L2Norm(f64),
169 AbsoluteAndRelative {
170 absolute_bound: f64,
171 relative_bound: f64,
172 },
173 AbsoluteOrRelative {
174 absolute_bound: f64,
175 relative_bound: f64,
176 },
177}
178
179impl ErrorBound {
180 fn decode(config: SZ3_Config) -> Result<Self> {
181 match config.errorBoundMode as u32 {
182 tensogram_sz3_sys::SZ3::EB_EB_ABS => Ok(Self::Absolute(config.absErrorBound)),
183 tensogram_sz3_sys::SZ3::EB_EB_REL => Ok(Self::Relative(config.relErrorBound)),
184 tensogram_sz3_sys::SZ3::EB_EB_PSNR => Ok(Self::PSNR(config.psnrErrorBound)),
185 tensogram_sz3_sys::SZ3::EB_EB_L2NORM => Ok(Self::L2Norm(config.l2normErrorBound)),
186 tensogram_sz3_sys::SZ3::EB_EB_ABS_OR_REL => Ok(Self::AbsoluteOrRelative {
187 absolute_bound: config.absErrorBound,
188 relative_bound: config.relErrorBound,
189 }),
190 tensogram_sz3_sys::SZ3::EB_EB_ABS_AND_REL => Ok(Self::AbsoluteAndRelative {
191 absolute_bound: config.absErrorBound,
192 relative_bound: config.relErrorBound,
193 }),
194 mode => Err(SZ3Error::UnsupportedErrorBound(mode)),
195 }
196 }
197
198 fn code(&self) -> u8 {
199 (match self {
200 Self::Absolute(_) => tensogram_sz3_sys::SZ3::EB_EB_ABS,
201 Self::Relative(_) => tensogram_sz3_sys::SZ3::EB_EB_REL,
202 Self::PSNR(_) => tensogram_sz3_sys::SZ3::EB_EB_PSNR,
203 Self::L2Norm(_) => tensogram_sz3_sys::SZ3::EB_EB_L2NORM,
204 Self::AbsoluteAndRelative { .. } => tensogram_sz3_sys::SZ3::EB_EB_ABS_AND_REL,
205 Self::AbsoluteOrRelative { .. } => tensogram_sz3_sys::SZ3::EB_EB_ABS_OR_REL,
206 }) as _
207 }
208
209 fn abs_bound(&self) -> f64 {
210 match self {
211 Self::Absolute(bound) => *bound,
212 Self::AbsoluteOrRelative { absolute_bound, .. }
213 | Self::AbsoluteAndRelative { absolute_bound, .. } => *absolute_bound,
214 _ => 0.0,
215 }
216 }
217
218 fn rel_bound(&self) -> f64 {
219 match self {
220 Self::Relative(bound) => *bound,
221 Self::AbsoluteOrRelative { relative_bound, .. }
222 | Self::AbsoluteAndRelative { relative_bound, .. } => *relative_bound,
223 _ => 0.0,
224 }
225 }
226
227 fn l2norm_bound(&self) -> f64 {
228 match self {
229 Self::L2Norm(bound) => *bound,
230 _ => 0.0,
231 }
232 }
233
234 fn psnr_bound(&self) -> f64 {
235 match self {
236 Self::PSNR(bound) => *bound,
237 _ => 0.0,
238 }
239 }
240}
241
242#[derive(Clone, Debug)]
251pub struct Config {
252 compression_algorithm: CompressionAlgorithm,
253 error_bound: ErrorBound,
254 openmp: bool,
255 quantization_bincount: u32,
256 block_size: Option<u32>,
257}
258
259impl Config {
260 pub fn new(error_bound: ErrorBound) -> Self {
263 Self {
264 compression_algorithm: CompressionAlgorithm::default(),
265 error_bound,
266 openmp: false,
267 quantization_bincount: 65536,
268 block_size: None,
269 }
270 }
271
272 fn from_decompressed(config: SZ3_Config) -> Result<Self> {
273 Ok(Self {
274 compression_algorithm: CompressionAlgorithm::decode(config)?,
275 error_bound: ErrorBound::decode(config)?,
276 openmp: config.openmp,
277 quantization_bincount: config.quantbinCnt as _,
278 block_size: Some(config.blockSize as _),
279 })
280 }
281
282 pub fn compression_algorithm(mut self, compression_algorithm: CompressionAlgorithm) -> Self {
284 self.compression_algorithm = compression_algorithm;
285 self
286 }
287
288 pub fn error_bound(mut self, error_bound: ErrorBound) -> Self {
290 self.error_bound = error_bound;
291 self
292 }
293
294 #[cfg(feature = "openmp")]
296 pub fn openmp(mut self, openmp: bool) -> Self {
297 self.openmp = openmp;
298 self
299 }
300
301 pub fn quantization_bincount(mut self, quantization_bincount: u32) -> Self {
303 self.quantization_bincount = quantization_bincount;
304 self
305 }
306
307 pub fn block_size(mut self, block_size: u32) -> Self {
309 self.block_size = Some(block_size);
310 self
311 }
312
313 pub fn automatic_block_size(mut self) -> Self {
315 self.block_size = None;
316 self
317 }
318}
319
320pub trait SZ3Compressible: private::Sealed + Sized {}
330impl SZ3Compressible for f32 {}
331impl SZ3Compressible for f64 {}
332impl SZ3Compressible for u8 {}
333impl SZ3Compressible for i8 {}
334impl SZ3Compressible for u16 {}
335impl SZ3Compressible for i16 {}
336impl SZ3Compressible for u32 {}
337impl SZ3Compressible for i32 {}
338impl SZ3Compressible for u64 {}
339impl SZ3Compressible for i64 {}
340
341mod private {
342 pub trait Sealed: Copy {
343 const SZ_DATA_TYPE: u8;
344
345 unsafe fn compress_size_bound(config: tensogram_sz3_sys::SZ3_Config) -> usize;
346
347 unsafe fn compress(
348 config: tensogram_sz3_sys::SZ3_Config,
349 data: *const Self,
350 compressed_data: *mut u8,
351 compressed_capacity: usize,
352 ) -> usize;
353
354 unsafe fn decompress(
355 compressed_data: *const u8,
356 compressed_len: usize,
357 decompressed_data: *mut Self,
358 );
359 }
360
361 macro_rules! impl_sealed {
362 ($($impl_mod:ident),*) => {
363 $(impl Sealed for tensogram_sz3_sys::$impl_mod::ty {
364 const SZ_DATA_TYPE: u8 = tensogram_sz3_sys::$impl_mod::DATA_TYPE_TYPE;
365
366 unsafe fn compress_size_bound(config: tensogram_sz3_sys::SZ3_Config) -> usize {
367 unsafe { tensogram_sz3_sys::$impl_mod::compress_size_bound(config) }
368 }
369
370 unsafe fn compress(
371 config: tensogram_sz3_sys::SZ3_Config,
372 data: *const Self,
373 compressed_data: *mut u8,
374 compressed_capacity: usize,
375 ) -> usize {
376 unsafe {
377 tensogram_sz3_sys::$impl_mod::compress(
378 config,
379 data,
380 compressed_data.cast(),
381 compressed_capacity,
382 )
383 }
384 }
385
386 unsafe fn decompress(
387 compressed_data: *const u8,
388 compressed_len: usize,
389 decompressed_data: *mut Self,
390 ) {
391 unsafe {
392 tensogram_sz3_sys::$impl_mod::decompress(
393 compressed_data.cast(),
394 compressed_len,
395 decompressed_data,
396 )
397 }
398 }
399 })*
400 }
401 }
402
403 impl_sealed!(
404 impl_f32, impl_f64, impl_u8, impl_i8, impl_u16, impl_i16, impl_u32, impl_i32, impl_u64,
405 impl_i64
406 );
407}
408
409#[derive(Clone, Debug)]
419pub struct DimensionedData<V: SZ3Compressible, T: std::ops::Deref<Target = [V]>> {
420 data: T,
421 dims: Vec<usize>,
422}
423
424#[derive(Clone, Debug)]
426pub struct DimensionedDataBuilder<'a, V> {
427 data: &'a [V],
428 dims: Vec<usize>,
429 remainder: usize,
430}
431
432#[derive(Debug)]
434pub struct DimensionedDataBuilderMut<'a, V> {
435 data: &'a mut [V],
436 dims: Vec<usize>,
437 remainder: usize,
438}
439
440impl<V: SZ3Compressible, T: std::ops::Deref<Target = [V]>> DimensionedData<V, T> {
441 pub fn build<'a>(data: &'a T) -> DimensionedDataBuilder<'a, V> {
443 DimensionedDataBuilder {
444 data,
445 dims: vec![],
446 remainder: data.len(),
447 }
448 }
449
450 pub fn data(&self) -> &[V] {
452 &self.data
453 }
454
455 pub fn into_data(self) -> T {
457 self.data
458 }
459
460 pub fn dims(&self) -> &[usize] {
462 &self.dims
463 }
464
465 fn len(&self) -> usize {
466 self.data.len()
467 }
468
469 fn as_ptr(&self) -> *const V {
470 self.data.as_ptr()
471 }
472}
473
474impl<V: SZ3Compressible, T: std::ops::DerefMut<Target = [V]>> DimensionedData<V, T> {
475 pub fn build_mut<'a>(data: &'a mut T) -> DimensionedDataBuilderMut<'a, V> {
477 DimensionedDataBuilderMut {
478 remainder: data.len(),
479 data,
480 dims: vec![],
481 }
482 }
483
484 pub fn data_mut(&mut self) -> &mut [V] {
486 &mut self.data
487 }
488}
489
490#[derive(thiserror::Error, Debug)]
502#[non_exhaustive]
503pub enum SZ3Error {
504 #[error(
505 "invalid dimension specification for data of length {len}: already specified dimensions \
506 {dims:?}, and wanted to add dimension with length {wanted}, but this does not divide \
507 {remainder} cleanly"
508 )]
509 InvalidDimensionSize {
510 dims: Vec<usize>,
511 len: usize,
512 wanted: usize,
513 remainder: usize,
514 },
515 #[error("dimension with size one has no use")]
516 OneSizedDimension,
517 #[error(
518 "dimension specification {dims:?} for data of length {len} does not cover whole space, \
519 missing a dimension of {remainder}"
520 )]
521 UnderSpecifiedDimensions {
522 dims: Vec<usize>,
523 len: usize,
524 remainder: usize,
525 },
526 #[error("cannot decompress to array with a different data type")]
527 DecompressedDataTypeMismatch,
528 #[error("unsupported SZ3 compression algorithm code: {0}")]
529 UnsupportedAlgorithm(u32),
530 #[error("unsupported SZ3 error bound mode: {0}")]
531 UnsupportedErrorBound(u32),
532 #[error(
533 "cannot decompress array with dimensions {found:?} to array with different dimensions {expected:?}"
534 )]
535 DecompressedDimsMismatch {
536 found: Vec<usize>,
537 expected: Vec<usize>,
538 },
539 #[error("malformed or truncated SZ3 compressed stream")]
544 MalformedCompressedStream,
545}
546
547type Result<T> = std::result::Result<T, SZ3Error>;
548
549macro_rules! impl_dimensioned_data_builder {
554 ($($builder:ident => $data:ty),*) => {
555 $(impl<'a, V: SZ3Compressible> $builder<'a, V> {
556 pub fn dim(mut self, length: usize) -> Result<Self> {
561 if length == 1 {
562 if self.dims.is_empty() && self.remainder == 1 {
563 self.dims.push(1);
564 Ok(self)
565 } else {
566 Err(SZ3Error::OneSizedDimension)
567 }
568 } else if self.remainder % length != 0 {
569 Err(SZ3Error::InvalidDimensionSize {
570 dims: self.dims,
571 len: self.data.len(),
572 wanted: length,
573 remainder: self.remainder,
574 })
575 } else {
576 self.dims.push(length);
577 self.remainder /= length;
578 Ok(self)
579 }
580 }
581
582 pub fn remainder_dim(self) -> Result<$data> {
584 let remainder = self.remainder;
585 self.dim(remainder)?.finish()
586 }
587
588 pub fn finish(self) -> Result<$data> {
593 if self.remainder != 1 {
594 Err(SZ3Error::UnderSpecifiedDimensions {
595 dims: self.dims,
596 len: self.data.len(),
597 remainder: self.remainder,
598 })
599 } else {
600 Ok(DimensionedData {
601 data: self.data,
602 dims: self.dims,
603 })
604 }
605 }
606 })*
607 };
608}
609
610impl_dimensioned_data_builder! {
611 DimensionedDataBuilder => DimensionedData<V, &'a [V]>,
612 DimensionedDataBuilderMut => DimensionedData<V, &'a mut [V]>
613}
614
615struct ParsedConfig {
620 config: Config,
621 len: usize,
622 dims: Vec<usize>,
623 data_type: u8,
624}
625
626impl ParsedConfig {
627 fn from_compressed(compressed_data: &[u8]) -> Result<Self> {
628 let raw = unsafe {
629 tensogram_sz3_sys::sz3_decompress_config(
630 compressed_data.as_ptr().cast(),
631 compressed_data.len(),
632 )
633 };
634 if raw.N == 0 || raw.dims.is_null() {
640 return Err(SZ3Error::MalformedCompressedStream);
642 }
643 let dims: Vec<usize> = (0..raw.N)
644 .map(|i| unsafe { std::ptr::read(raw.dims.add(i as usize)) })
645 .collect();
646 unsafe {
647 tensogram_sz3_sys::sz3_dealloc_size_t(raw.dims);
648 }
649 let SZ3_Config {
650 num: len,
651 dataType: data_type,
652 ..
653 } = raw;
654 let config = Config::from_decompressed(raw)?;
655 Ok(Self {
656 config,
657 len,
658 dims,
659 data_type,
660 })
661 }
662}
663
664pub fn compress<V: SZ3Compressible, T: std::ops::Deref<Target = [V]>>(
670 data: &DimensionedData<V, T>,
671 error_bound: ErrorBound,
672) -> Result<Vec<u8>> {
673 let config = Config::new(error_bound);
674 compress_with_config(data, &config)
675}
676
677pub fn compress_with_config<V: SZ3Compressible, T: std::ops::Deref<Target = [V]>>(
679 data: &DimensionedData<V, T>,
680 config: &Config,
681) -> Result<Vec<u8>> {
682 let mut compressed_data = Vec::new();
683 compress_into_with_config(data, config, &mut compressed_data)?;
684 Ok(compressed_data)
685}
686
687pub fn compress_into<V: SZ3Compressible, T: std::ops::Deref<Target = [V]>>(
689 data: &DimensionedData<V, T>,
690 error_bound: ErrorBound,
691 compressed_data: &mut Vec<u8>,
692) -> Result<()> {
693 let config = Config::new(error_bound);
694 compress_into_with_config(data, &config, compressed_data)
695}
696
697pub fn compress_into_with_config<V: SZ3Compressible, T: std::ops::Deref<Target = [V]>>(
700 data: &DimensionedData<V, T>,
701 config: &Config,
702 compressed_data: &mut Vec<u8>,
703) -> Result<()> {
704 let block_size = config.block_size.unwrap_or(match data.dims().len() {
705 1 => 128,
706 2 => 16,
707 _ => 6,
708 });
709
710 let raw_config = SZ3_Config {
711 N: data.dims().len() as _,
712 dims: data.dims.as_ptr() as _,
713 num: data.len() as _,
714 errorBoundMode: config.error_bound.code(),
715 absErrorBound: config.error_bound.abs_bound(),
716 relErrorBound: config.error_bound.rel_bound(),
717 l2normErrorBound: config.error_bound.l2norm_bound(),
718 psnrErrorBound: config.error_bound.psnr_bound(),
719 cmprAlgo: config.compression_algorithm.code(),
720 lorenzo: config.compression_algorithm.lorenzo(),
721 lorenzo2: config.compression_algorithm.lorenzo_second_order(),
722 regression: config.compression_algorithm.regression(),
723 openmp: config.openmp,
724 dataType: V::SZ_DATA_TYPE as _,
725 blockSize: block_size as _,
726 quantbinCnt: config.quantization_bincount as _,
727 };
728
729 let capacity: usize = unsafe { V::compress_size_bound(raw_config) };
730 compressed_data.reserve(capacity);
731
732 let len = unsafe {
733 V::compress(
734 raw_config,
735 data.as_ptr(),
736 compressed_data
737 .spare_capacity_mut()
738 .as_mut_ptr()
739 .cast::<u8>(),
740 capacity,
741 )
742 };
743 unsafe { compressed_data.set_len(compressed_data.len() + len) };
744
745 Ok(())
746}
747
748pub fn decompress<V: SZ3Compressible, T: std::ops::Deref<Target = [u8]>>(
751 compressed_data: T,
752) -> Result<(Config, DimensionedData<V, Vec<V>>)> {
753 let ParsedConfig {
754 config,
755 len,
756 dims,
757 data_type,
758 } = ParsedConfig::from_compressed(&compressed_data)?;
759
760 if data_type != V::SZ_DATA_TYPE {
761 return Err(SZ3Error::DecompressedDataTypeMismatch);
762 }
763
764 if len == 0 {
772 return Err(SZ3Error::MalformedCompressedStream);
773 }
774
775 let mut decompressed_data: Vec<V> = Vec::new();
781 decompressed_data
782 .try_reserve_exact(len)
783 .map_err(|_| SZ3Error::MalformedCompressedStream)?;
784
785 let decompressed_data = unsafe {
786 V::decompress(
787 compressed_data.as_ptr(),
788 compressed_data.len(),
789 decompressed_data
790 .spare_capacity_mut()
791 .as_mut_ptr()
792 .cast::<V>(),
793 );
794
795 decompressed_data.set_len(len);
796 decompressed_data
797 };
798
799 Ok((
800 config,
801 DimensionedData {
802 data: decompressed_data,
803 dims,
804 },
805 ))
806}
807
808pub fn decompress_into_dimensioned<
813 V: SZ3Compressible,
814 C: std::ops::Deref<Target = [u8]>,
815 D: std::ops::DerefMut<Target = [V]>,
816>(
817 compressed_data: C,
818 decompressed_data: &mut DimensionedData<V, D>,
819) -> Result<Config> {
820 let ParsedConfig {
821 config,
822 len,
823 dims,
824 data_type,
825 } = ParsedConfig::from_compressed(&compressed_data)?;
826
827 if data_type != V::SZ_DATA_TYPE {
828 return Err(SZ3Error::DecompressedDataTypeMismatch);
829 }
830
831 if decompressed_data.dims() != dims.as_slice() {
832 return Err(SZ3Error::DecompressedDimsMismatch {
833 found: dims,
834 expected: decompressed_data.dims.clone(),
835 });
836 }
837
838 assert_eq!(decompressed_data.len(), len);
839
840 unsafe {
841 V::decompress(
842 compressed_data.as_ptr(),
843 compressed_data.len(),
844 decompressed_data.data.as_mut_ptr(),
845 );
846 }
847
848 Ok(config)
849}
850
851#[cfg(test)]
856mod tests {
857 use super::*;
858
859 #[test]
860 fn round_trip_f64() {
861 let data: Vec<f64> = (0..256)
862 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin())
863 .collect();
864 let dimensioned = DimensionedData::<f64, _>::build(&data)
865 .dim(256)
866 .unwrap()
867 .finish()
868 .unwrap();
869 let compressed = compress(&dimensioned, ErrorBound::Absolute(1e-6)).unwrap();
870 let (_config, decompressed) = decompress::<f64, _>(&*compressed).unwrap();
871 assert_eq!(decompressed.data().len(), data.len());
872 for (orig, dec) in data.iter().zip(decompressed.data()) {
873 assert!(
874 (orig - dec).abs() <= 1e-6,
875 "orig={orig}, dec={dec}, diff={}",
876 (orig - dec).abs()
877 );
878 }
879 }
880
881 #[test]
882 fn round_trip_f32() {
883 let data: Vec<f32> = (0..256)
884 .map(|i| (i as f32 / 256.0 * std::f32::consts::PI).sin())
885 .collect();
886 let dimensioned = DimensionedData::<f32, _>::build(&data)
887 .dim(256)
888 .unwrap()
889 .finish()
890 .unwrap();
891 let compressed = compress(&dimensioned, ErrorBound::Absolute(1e-4)).unwrap();
892 let (_config, decompressed) = decompress::<f32, _>(&*compressed).unwrap();
893 assert_eq!(decompressed.data().len(), data.len());
894 for (orig, dec) in data.iter().zip(decompressed.data()) {
895 assert!(
896 (orig - dec).abs() <= 1e-4,
897 "orig={orig}, dec={dec}, diff={}",
898 (orig - dec).abs()
899 );
900 }
901 }
902
903 #[test]
904 fn dimension_errors() {
905 let data: Vec<f64> = vec![1.0; 100];
906 let err = DimensionedData::<f64, _>::build(&data).dim(1);
907 assert!(matches!(err.unwrap_err(), SZ3Error::OneSizedDimension));
908 let err = DimensionedData::<f64, _>::build(&data).dim(7);
909 assert!(matches!(
910 err.unwrap_err(),
911 SZ3Error::InvalidDimensionSize { .. }
912 ));
913 let err = DimensionedData::<f64, _>::build(&data)
914 .dim(10)
915 .unwrap()
916 .finish();
917 assert!(matches!(
918 err.unwrap_err(),
919 SZ3Error::UnderSpecifiedDimensions { .. }
920 ));
921 }
922
923 #[test]
924 fn round_trip_u8() {
925 let data: Vec<u8> = (0..256).map(|i| i as u8).collect();
926 let d = DimensionedData::<u8, _>::build(&data)
927 .dim(256)
928 .unwrap()
929 .finish()
930 .unwrap();
931 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
932 let (_, dec) = decompress::<u8, _>(&*compressed).unwrap();
933 assert_eq!(dec.data(), data.as_slice());
934 }
935
936 #[test]
937 fn round_trip_i8() {
938 let data: Vec<i8> = (-128..127).map(|i| i as i8).collect();
939 let d = DimensionedData::<i8, _>::build(&data)
940 .dim(data.len())
941 .unwrap()
942 .finish()
943 .unwrap();
944 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
945 let (_, dec) = decompress::<i8, _>(&*compressed).unwrap();
946 assert_eq!(dec.data(), data.as_slice());
947 }
948
949 #[test]
950 fn round_trip_u16() {
951 let data: Vec<u16> = (0..256).map(|i| i as u16 * 100).collect();
952 let d = DimensionedData::<u16, _>::build(&data)
953 .dim(256)
954 .unwrap()
955 .finish()
956 .unwrap();
957 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
958 let (_, dec) = decompress::<u16, _>(&*compressed).unwrap();
959 assert_eq!(dec.data(), data.as_slice());
960 }
961
962 #[test]
963 fn round_trip_i16() {
964 let data: Vec<i16> = (-128..128).map(|i| i as i16 * 50).collect();
965 let d = DimensionedData::<i16, _>::build(&data)
966 .dim(256)
967 .unwrap()
968 .finish()
969 .unwrap();
970 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
971 let (_, dec) = decompress::<i16, _>(&*compressed).unwrap();
972 assert_eq!(dec.data(), data.as_slice());
973 }
974
975 #[test]
976 fn round_trip_u32() {
977 let data: Vec<u32> = (0..256).map(|i| i as u32 * 1000).collect();
978 let d = DimensionedData::<u32, _>::build(&data)
979 .dim(256)
980 .unwrap()
981 .finish()
982 .unwrap();
983 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
984 let (_, dec) = decompress::<u32, _>(&*compressed).unwrap();
985 assert_eq!(dec.data(), data.as_slice());
986 }
987
988 #[test]
989 fn round_trip_i32() {
990 let data: Vec<i32> = (-128..128).map(|i: i32| i * 1000).collect();
991 let d = DimensionedData::<i32, _>::build(&data)
992 .dim(256)
993 .unwrap()
994 .finish()
995 .unwrap();
996 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
997 let (_, dec) = decompress::<i32, _>(&*compressed).unwrap();
998 assert_eq!(dec.data(), data.as_slice());
999 }
1000
1001 #[test]
1002 fn round_trip_u64() {
1003 let data: Vec<u64> = (0..256).map(|i| i as u64 * 10000).collect();
1004 let d = DimensionedData::<u64, _>::build(&data)
1005 .dim(256)
1006 .unwrap()
1007 .finish()
1008 .unwrap();
1009 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
1010 let (_, dec) = decompress::<u64, _>(&*compressed).unwrap();
1011 assert_eq!(dec.data(), data.as_slice());
1012 }
1013
1014 #[test]
1015 fn round_trip_i64() {
1016 let data: Vec<i64> = (-128..128).map(|i| i as i64 * 10000).collect();
1017 let d = DimensionedData::<i64, _>::build(&data)
1018 .dim(256)
1019 .unwrap()
1020 .finish()
1021 .unwrap();
1022 let compressed = compress(&d, ErrorBound::Absolute(0.0)).unwrap();
1023 let (_, dec) = decompress::<i64, _>(&*compressed).unwrap();
1024 assert_eq!(dec.data(), data.as_slice());
1025 }
1026
1027 #[test]
1028 fn error_bound_relative() {
1029 let data: Vec<f64> = (0..256)
1030 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin() + 2.0)
1031 .collect();
1032 let d = DimensionedData::<f64, _>::build(&data)
1033 .dim(256)
1034 .unwrap()
1035 .finish()
1036 .unwrap();
1037 let cfg = Config::new(ErrorBound::Relative(1e-4));
1038 let c = compress_with_config(&d, &cfg).unwrap();
1039 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1040 assert_eq!(dec.data().len(), data.len());
1041 }
1042
1043 #[test]
1044 fn error_bound_psnr() {
1045 let data: Vec<f64> = (0..256)
1046 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin())
1047 .collect();
1048 let d = DimensionedData::<f64, _>::build(&data)
1049 .dim(256)
1050 .unwrap()
1051 .finish()
1052 .unwrap();
1053 let cfg = Config::new(ErrorBound::PSNR(80.0));
1054 let c = compress_with_config(&d, &cfg).unwrap();
1055 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1056 assert_eq!(dec.data().len(), data.len());
1057 }
1058
1059 #[test]
1060 fn error_bound_l2norm() {
1061 let data: Vec<f64> = (0..256)
1062 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin())
1063 .collect();
1064 let d = DimensionedData::<f64, _>::build(&data)
1065 .dim(256)
1066 .unwrap()
1067 .finish()
1068 .unwrap();
1069 let cfg = Config::new(ErrorBound::L2Norm(1e-3));
1070 let c = compress_with_config(&d, &cfg).unwrap();
1071 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1072 assert_eq!(dec.data().len(), data.len());
1073 }
1074
1075 #[test]
1076 fn error_bound_abs_and_rel() {
1077 let data: Vec<f64> = (0..256)
1078 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin() + 2.0)
1079 .collect();
1080 let d = DimensionedData::<f64, _>::build(&data)
1081 .dim(256)
1082 .unwrap()
1083 .finish()
1084 .unwrap();
1085 let cfg = Config::new(ErrorBound::AbsoluteAndRelative {
1086 absolute_bound: 1e-4,
1087 relative_bound: 1e-3,
1088 });
1089 let c = compress_with_config(&d, &cfg).unwrap();
1090 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1091 assert_eq!(dec.data().len(), data.len());
1092 }
1093
1094 #[test]
1095 fn error_bound_abs_or_rel() {
1096 let data: Vec<f64> = (0..256)
1097 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin() + 2.0)
1098 .collect();
1099 let d = DimensionedData::<f64, _>::build(&data)
1100 .dim(256)
1101 .unwrap()
1102 .finish()
1103 .unwrap();
1104 let cfg = Config::new(ErrorBound::AbsoluteOrRelative {
1105 absolute_bound: 1e-4,
1106 relative_bound: 1e-3,
1107 });
1108 let c = compress_with_config(&d, &cfg).unwrap();
1109 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1110 assert_eq!(dec.data().len(), data.len());
1111 }
1112
1113 #[test]
1114 fn algo_interpolation() {
1115 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1116 let d = DimensionedData::<f64, _>::build(&data)
1117 .dim(256)
1118 .unwrap()
1119 .finish()
1120 .unwrap();
1121 let cfg = Config::new(ErrorBound::Absolute(1e-6))
1122 .compression_algorithm(CompressionAlgorithm::interpolation());
1123 let c = compress_with_config(&d, &cfg).unwrap();
1124 let (dc, dec) = decompress::<f64, _>(&*c).unwrap();
1125 assert_eq!(dec.data().len(), data.len());
1126 assert!(matches!(
1127 dc.compression_algorithm,
1128 CompressionAlgorithm::Interpolation
1129 ));
1130 }
1131
1132 #[test]
1133 fn algo_lorenzo_regression() {
1134 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1135 let d = DimensionedData::<f64, _>::build(&data)
1136 .dim(256)
1137 .unwrap()
1138 .finish()
1139 .unwrap();
1140 let cfg = Config::new(ErrorBound::Absolute(1e-6))
1141 .compression_algorithm(CompressionAlgorithm::lorenzo_regression());
1142 let c = compress_with_config(&d, &cfg).unwrap();
1143 let (dc, dec) = decompress::<f64, _>(&*c).unwrap();
1144 assert_eq!(dec.data().len(), data.len());
1145 assert!(matches!(
1146 dc.compression_algorithm,
1147 CompressionAlgorithm::LorenzoRegression { .. }
1148 ));
1149 }
1150
1151 #[test]
1152 fn algo_lossless() {
1153 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1154 let d = DimensionedData::<f64, _>::build(&data)
1155 .dim(256)
1156 .unwrap()
1157 .finish()
1158 .unwrap();
1159 let cfg = Config::new(ErrorBound::Absolute(0.0))
1160 .compression_algorithm(CompressionAlgorithm::lossless());
1161 let c = compress_with_config(&d, &cfg).unwrap();
1162 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1163 assert_eq!(dec.data(), data.as_slice());
1164 }
1165
1166 #[test]
1167 fn config_quantization_bincount() {
1168 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1169 let d = DimensionedData::<f64, _>::build(&data)
1170 .dim(256)
1171 .unwrap()
1172 .finish()
1173 .unwrap();
1174 let cfg = Config::new(ErrorBound::Absolute(1e-6)).quantization_bincount(1024);
1175 let c = compress_with_config(&d, &cfg).unwrap();
1176 let (dc, _) = decompress::<f64, _>(&*c).unwrap();
1177 assert_eq!(dc.quantization_bincount, 1024);
1178 }
1179
1180 #[test]
1181 fn config_block_size() {
1182 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1183 let d = DimensionedData::<f64, _>::build(&data)
1184 .dim(256)
1185 .unwrap()
1186 .finish()
1187 .unwrap();
1188 let cfg = Config::new(ErrorBound::Absolute(1e-6)).block_size(64);
1189 let c = compress_with_config(&d, &cfg).unwrap();
1190 let (dc, _) = decompress::<f64, _>(&*c).unwrap();
1191 assert_eq!(dc.block_size, Some(64));
1192 }
1193
1194 #[test]
1195 fn config_automatic_block_size() {
1196 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1197 let d = DimensionedData::<f64, _>::build(&data)
1198 .dim(256)
1199 .unwrap()
1200 .finish()
1201 .unwrap();
1202 let cfg = Config::new(ErrorBound::Absolute(1e-6))
1203 .block_size(64)
1204 .automatic_block_size();
1205 assert!(cfg.block_size.is_none());
1206 let c = compress_with_config(&d, &cfg).unwrap();
1207 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1208 assert_eq!(dec.data().len(), data.len());
1209 }
1210
1211 #[test]
1212 fn config_error_bound_setter() {
1213 let cfg = Config::new(ErrorBound::Absolute(1.0)).error_bound(ErrorBound::Relative(0.5));
1214 assert!(matches!(cfg.error_bound, ErrorBound::Relative(_)));
1215 }
1216
1217 #[test]
1218 fn dimensioned_data_accessors() {
1219 let data: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1220 let dd = DimensionedData::<f64, _>::build(&data)
1221 .dim(2)
1222 .unwrap()
1223 .dim(3)
1224 .unwrap()
1225 .finish()
1226 .unwrap();
1227 assert_eq!(dd.dims(), &[2, 3]);
1228 assert_eq!(dd.data(), &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1229 let owned = dd.into_data();
1230 assert_eq!(owned, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1231 }
1232
1233 #[test]
1234 fn dimensioned_data_build_mut() {
1235 let mut data: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0];
1236 let mut dd = DimensionedData::<f64, _>::build_mut(&mut data)
1237 .dim(4)
1238 .unwrap()
1239 .finish()
1240 .unwrap();
1241 assert_eq!(dd.data(), &[1.0, 2.0, 3.0, 4.0]);
1242 dd.data_mut()[0] = 99.0;
1243 assert_eq!(dd.data()[0], 99.0);
1244 }
1245
1246 #[test]
1247 fn dimensioned_data_remainder_dim() {
1248 let data: Vec<f64> = vec![1.0; 120];
1249 let dd = DimensionedData::<f64, _>::build(&data)
1250 .dim(10)
1251 .unwrap()
1252 .remainder_dim()
1253 .unwrap();
1254 assert_eq!(dd.dims(), &[10, 12]);
1255 assert_eq!(dd.data().len(), 120);
1256 }
1257
1258 #[test]
1259 fn dimensioned_data_singleton() {
1260 let data: Vec<f64> = vec![42.0];
1261 let dd = DimensionedData::<f64, _>::build(&data)
1262 .dim(1)
1263 .unwrap()
1264 .finish()
1265 .unwrap();
1266 assert_eq!(dd.dims(), &[1]);
1267 assert_eq!(dd.data(), &[42.0]);
1268 }
1269
1270 #[test]
1271 fn decompress_into_dimensioned_round_trip() {
1272 let data: Vec<f64> = (0..256)
1273 .map(|i| (i as f64 / 256.0 * std::f64::consts::PI).sin())
1274 .collect();
1275 let d = DimensionedData::<f64, _>::build(&data)
1276 .dim(256)
1277 .unwrap()
1278 .finish()
1279 .unwrap();
1280 let compressed = compress(&d, ErrorBound::Absolute(1e-6)).unwrap();
1281 let mut output = vec![0.0f64; 256];
1282 let mut out_dim = DimensionedData::<f64, _>::build_mut(&mut output)
1283 .dim(256)
1284 .unwrap()
1285 .finish()
1286 .unwrap();
1287 let _cfg = decompress_into_dimensioned(&*compressed, &mut out_dim).unwrap();
1288 for (orig, dec) in data.iter().zip(out_dim.data()) {
1289 assert!((orig - dec).abs() <= 1e-6);
1290 }
1291 }
1292
1293 #[test]
1294 fn decompress_data_type_mismatch() {
1295 let data: Vec<f32> = (0..256).map(|i| i as f32).collect();
1296 let d = DimensionedData::<f32, _>::build(&data)
1297 .dim(256)
1298 .unwrap()
1299 .finish()
1300 .unwrap();
1301 let compressed = compress(&d, ErrorBound::Absolute(1e-4)).unwrap();
1302 let result = decompress::<f64, _>(&*compressed);
1303 assert!(matches!(
1304 result.unwrap_err(),
1305 SZ3Error::DecompressedDataTypeMismatch
1306 ));
1307 }
1308
1309 #[test]
1310 fn decompress_into_dims_mismatch() {
1311 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1312 let d = DimensionedData::<f64, _>::build(&data)
1313 .dim(256)
1314 .unwrap()
1315 .finish()
1316 .unwrap();
1317 let compressed = compress(&d, ErrorBound::Absolute(1e-6)).unwrap();
1318 let mut output = vec![0.0f64; 256];
1319 let mut out_dim = DimensionedData::<f64, _>::build_mut(&mut output)
1320 .dim(16)
1321 .unwrap()
1322 .dim(16)
1323 .unwrap()
1324 .finish()
1325 .unwrap();
1326 let result = decompress_into_dimensioned(&*compressed, &mut out_dim);
1327 assert!(matches!(
1328 result.unwrap_err(),
1329 SZ3Error::DecompressedDimsMismatch { .. }
1330 ));
1331 }
1332
1333 #[test]
1334 fn decompress_into_type_mismatch() {
1335 let data: Vec<f32> = (0..256).map(|i| i as f32).collect();
1336 let d = DimensionedData::<f32, _>::build(&data)
1337 .dim(256)
1338 .unwrap()
1339 .finish()
1340 .unwrap();
1341 let compressed = compress(&d, ErrorBound::Absolute(1e-4)).unwrap();
1342 let mut output = vec![0.0f64; 256];
1343 let mut out_dim = DimensionedData::<f64, _>::build_mut(&mut output)
1344 .dim(256)
1345 .unwrap()
1346 .finish()
1347 .unwrap();
1348 let result = decompress_into_dimensioned(&*compressed, &mut out_dim);
1349 assert!(matches!(
1350 result.unwrap_err(),
1351 SZ3Error::DecompressedDataTypeMismatch
1352 ));
1353 }
1354
1355 #[test]
1356 fn compress_into_appends() {
1357 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1358 let d = DimensionedData::<f64, _>::build(&data)
1359 .dim(256)
1360 .unwrap()
1361 .finish()
1362 .unwrap();
1363 let mut buf = vec![0xAA, 0xBB, 0xCC];
1364 compress_into(&d, ErrorBound::Absolute(1e-6), &mut buf).unwrap();
1365 assert_eq!(&buf[..3], &[0xAA, 0xBB, 0xCC]);
1366 assert!(buf.len() > 3);
1367 let (_, dec) = decompress::<f64, _>(&buf[3..]).unwrap();
1368 assert_eq!(dec.data().len(), data.len());
1369 }
1370
1371 #[test]
1372 fn compress_into_with_config_appends() {
1373 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1374 let d = DimensionedData::<f64, _>::build(&data)
1375 .dim(256)
1376 .unwrap()
1377 .finish()
1378 .unwrap();
1379 let cfg = Config::new(ErrorBound::Absolute(1e-6))
1380 .compression_algorithm(CompressionAlgorithm::lossless());
1381 let mut buf = vec![0xDD, 0xEE];
1382 compress_into_with_config(&d, &cfg, &mut buf).unwrap();
1383 assert_eq!(&buf[..2], &[0xDD, 0xEE]);
1384 let (_, dec) = decompress::<f64, _>(&buf[2..]).unwrap();
1385 assert_eq!(dec.data(), data.as_slice());
1386 }
1387
1388 #[test]
1389 fn round_trip_2d() {
1390 let data: Vec<f64> = (0..256).map(|i| i as f64).collect();
1391 let d = DimensionedData::<f64, _>::build(&data)
1392 .dim(16)
1393 .unwrap()
1394 .dim(16)
1395 .unwrap()
1396 .finish()
1397 .unwrap();
1398 let c = compress(&d, ErrorBound::Absolute(1e-6)).unwrap();
1399 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1400 assert_eq!(dec.dims(), &[16, 16]);
1401 }
1402
1403 #[test]
1404 fn round_trip_3d() {
1405 let data: Vec<f64> = (0..216).map(|i| i as f64).collect();
1406 let d = DimensionedData::<f64, _>::build(&data)
1407 .dim(6)
1408 .unwrap()
1409 .dim(6)
1410 .unwrap()
1411 .dim(6)
1412 .unwrap()
1413 .finish()
1414 .unwrap();
1415 let c = compress(&d, ErrorBound::Absolute(1e-6)).unwrap();
1416 let (_, dec) = decompress::<f64, _>(&*c).unwrap();
1417 assert_eq!(dec.dims(), &[6, 6, 6]);
1418 }
1419
1420 #[test]
1421 fn algo_constructors() {
1422 assert!(matches!(
1423 CompressionAlgorithm::interpolation(),
1424 CompressionAlgorithm::Interpolation
1425 ));
1426 assert!(matches!(
1427 CompressionAlgorithm::interpolation_lorenzo(),
1428 CompressionAlgorithm::InterpolationLorenzo
1429 ));
1430 assert!(matches!(
1431 CompressionAlgorithm::lorenzo_regression(),
1432 CompressionAlgorithm::LorenzoRegression {
1433 lorenzo: true,
1434 lorenzo_second_order: false,
1435 regression: true
1436 }
1437 ));
1438 let a =
1439 CompressionAlgorithm::lorenzo_regression_custom(Some(false), Some(true), Some(false));
1440 assert!(matches!(
1441 a,
1442 CompressionAlgorithm::LorenzoRegression {
1443 lorenzo: false,
1444 lorenzo_second_order: true,
1445 regression: false
1446 }
1447 ));
1448 let a = CompressionAlgorithm::lorenzo_regression_custom(None, None, None);
1449 assert!(matches!(
1450 a,
1451 CompressionAlgorithm::LorenzoRegression {
1452 lorenzo: true,
1453 lorenzo_second_order: false,
1454 regression: true
1455 }
1456 ));
1457 assert!(matches!(
1458 CompressionAlgorithm::biology_molecular_data(),
1459 CompressionAlgorithm::BiologyMolecularData
1460 ));
1461 assert!(matches!(
1462 CompressionAlgorithm::biology_molecular_data_gromacs_xtc(),
1463 CompressionAlgorithm::BiologyMolecularDataGromacsXtc
1464 ));
1465 assert!(matches!(
1466 CompressionAlgorithm::no_prediction(),
1467 CompressionAlgorithm::NoPrediction
1468 ));
1469 assert!(matches!(
1470 CompressionAlgorithm::lossless(),
1471 CompressionAlgorithm::Lossless
1472 ));
1473 assert!(matches!(
1474 CompressionAlgorithm::default(),
1475 CompressionAlgorithm::InterpolationLorenzo
1476 ));
1477 }
1478
1479 #[test]
1480 fn error_display_messages() {
1481 let e = SZ3Error::OneSizedDimension;
1482 assert!(format!("{e}").contains("size one"));
1483 let e = SZ3Error::DecompressedDataTypeMismatch;
1484 assert!(format!("{e}").contains("different data type"));
1485 let e = SZ3Error::DecompressedDimsMismatch {
1486 found: vec![256],
1487 expected: vec![16, 16],
1488 };
1489 assert!(format!("{e}").contains("[256]"));
1490 let e = SZ3Error::InvalidDimensionSize {
1491 dims: vec![10],
1492 len: 100,
1493 wanted: 7,
1494 remainder: 10,
1495 };
1496 assert!(format!("{e}").contains("7"));
1497 let e = SZ3Error::UnderSpecifiedDimensions {
1498 dims: vec![10],
1499 len: 100,
1500 remainder: 10,
1501 };
1502 assert!(format!("{e}").contains("10"));
1503 }
1504}