HDF5 2.0 native complex as a first-class type on read; Python libver=
- Datatype::parse returns Datatype::Complex for class 11 (also inside
compounds, arrays and VL types) instead of the {r, i} compound view.
- Facade: DType::Complex(Box<DType>); read_complex_f32/f64 accept it.
- h5rs dump/ls/diff print native complex as h5dump/h5ls/h5diff 2.2.0 do
(checked against a fixture written by h5py 3.16 / libhdf5 2.0.0);
dump --json keeps the {r, i} compound (hdf5-json has no complex class).
- clawhdf5-wasm reads native complex datasets as [re, im] pairs.
- Python: clawhdf5.File(path, 'w', libver=...) with h5py's values,
mapped to FileBuilder::libver_bounds; 'v108' output opens in HDF5 1.8.23.
- Docs: known-issues entry moved to Fixed (history), CHANGELOG, READMEs.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -312,11 +312,14 @@ impl Reader {
|
||||
let base = array_base(dt);
|
||||
let is_array = !std::ptr::eq(base, dt);
|
||||
Ok(match base {
|
||||
// Read as the part type: an array or complex element is its
|
||||
// parts stored one after another (a complex number's real, then
|
||||
// imaginary part).
|
||||
Datatype::FloatingPoint { size, .. } if *size <= 4 => {
|
||||
Data::F32(data_read::read_as_f32(raw, dt).map_err(err)?)
|
||||
Data::F32(data_read::read_as_f32(raw, base).map_err(err)?)
|
||||
}
|
||||
Datatype::FloatingPoint { .. } => {
|
||||
Data::F64(data_read::read_as_f64(raw, dt).map_err(err)?)
|
||||
Data::F64(data_read::read_as_f64(raw, base).map_err(err)?)
|
||||
}
|
||||
Datatype::FixedPoint { size, signed, .. } => {
|
||||
let signed_ints = || data_read::read_as_i64(raw, dt).map_err(err);
|
||||
@@ -393,10 +396,13 @@ fn narrow<S: Copy + std::fmt::Display, T: TryFrom<S>>(v: Vec<S>) -> Result<Vec<T
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Innermost element type of (possibly nested) array datatypes.
|
||||
/// Innermost element type of (possibly nested) array datatypes; for a
|
||||
/// native complex type, its part type (each element holds two).
|
||||
fn array_base(dt: &Datatype) -> &Datatype {
|
||||
match dt {
|
||||
Datatype::Array { base_type, .. } => array_base(base_type),
|
||||
Datatype::Array { base_type, .. } | Datatype::Complex { base_type, .. } => {
|
||||
array_base(base_type)
|
||||
}
|
||||
_ => dt,
|
||||
}
|
||||
}
|
||||
@@ -411,6 +417,12 @@ fn element_shape(dt: &Datatype) -> Vec<u64> {
|
||||
dims.extend(element_shape(base_type));
|
||||
dims
|
||||
}
|
||||
// `[re, im]`: a complex element reads as its two parts.
|
||||
Datatype::Complex { base_type, .. } => {
|
||||
let mut dims = vec![2];
|
||||
dims.extend(element_shape(base_type));
|
||||
dims
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -487,9 +499,7 @@ pub fn describe(dt: &Datatype) -> String {
|
||||
}
|
||||
}
|
||||
match dt {
|
||||
Datatype::Complex { size, base_type } => {
|
||||
describe(&Datatype::complex_as_compound(*size, base_type))
|
||||
}
|
||||
Datatype::Complex { base_type, .. } => format!("complex<{}>", describe(base_type)),
|
||||
Datatype::FixedPoint {
|
||||
size,
|
||||
signed,
|
||||
@@ -681,6 +691,74 @@ mod tests {
|
||||
assert!(e.contains("not supported"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_complex_reads_as_re_im_pairs() {
|
||||
let z = [[1.0f64, -2.0], [0.5, 0.0], [-0.0, 3.25], [f64::MAX, 1e-300]];
|
||||
let mut b = FileBuilder::new();
|
||||
b.create_dataset("z")
|
||||
.with_native_complex_f64_data(&z)
|
||||
.with_shape(&[2, 2]);
|
||||
b.create_dataset("z32")
|
||||
.with_native_complex_f32_data(&[[1.5f32, -2.5]]);
|
||||
let r = Reader::open(b.finish().unwrap()).unwrap();
|
||||
let i = r.info("z").unwrap();
|
||||
assert_eq!(
|
||||
(i.shape, i.dtype, i.element_shape),
|
||||
(vec![2, 2], "complex<f64>".to_string(), vec![2])
|
||||
);
|
||||
let all = r.read("z", None).unwrap();
|
||||
assert_eq!(all.shape, vec![2, 2, 2]);
|
||||
assert_eq!(all.data, Data::F64(z.iter().flatten().copied().collect()));
|
||||
let slab = Hyperslab {
|
||||
start: vec![1, 0],
|
||||
count: vec![1, 2],
|
||||
stride: None,
|
||||
block: None,
|
||||
};
|
||||
let part = r.read("z", Some(&slab)).unwrap();
|
||||
assert_eq!(part.shape, vec![1, 2, 2]);
|
||||
assert_eq!(part.data, Data::F64(vec![-0.0, 3.25, f64::MAX, 1e-300]));
|
||||
let z32 = r.read("z32", None).unwrap();
|
||||
assert_eq!(
|
||||
(z32.shape, z32.data),
|
||||
(vec![1, 2], Data::F32(vec![1.5, -2.5]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_complex_written_by_libhdf5_2() {
|
||||
// Written by h5py 3.16 / libhdf5 2.0.0 (gen_native_complex.py).
|
||||
let path = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../clawhdf5/tests/fixtures/native_complex_hdf5_2.h5"
|
||||
);
|
||||
let r = Reader::open(std::fs::read(path).unwrap()).unwrap();
|
||||
let be = r.read("c128be", None).unwrap();
|
||||
assert_eq!(be.shape, vec![2, 3, 2]);
|
||||
assert_eq!(be.data, r.read("c128", None).unwrap().data);
|
||||
assert_eq!(r.info("c128be").unwrap().dtype, "complex<f64 (big-endian)>");
|
||||
// binary16 parts widen to f32.
|
||||
assert_eq!(
|
||||
r.read("c32", None).unwrap().data,
|
||||
Data::F32(vec![1.0, -2.0, 0.5, 65504.0, 0.0, -0.0])
|
||||
);
|
||||
// An array of complex: array dimensions, then the two parts.
|
||||
let i = r.info("array").unwrap();
|
||||
assert_eq!(
|
||||
(i.dtype.as_str(), i.element_shape),
|
||||
("array[2]<complex<f32>>", vec![2, 2])
|
||||
);
|
||||
assert_eq!(
|
||||
r.read("array", None).unwrap().data,
|
||||
Data::F32(vec![1.0, 2.0, 3.0, 4.0, -1.0, -1.0, 0.0, 0.0])
|
||||
);
|
||||
let s = r.read("scalar", None).unwrap();
|
||||
assert_eq!((s.shape, s.data), (vec![2], Data::F64(vec![2.5, -0.5])));
|
||||
// A compound holding a complex member is still refused.
|
||||
let e = r.read("compound", None).unwrap_err();
|
||||
assert!(e.contains("compound{z: complex<f64>, k: i64}"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn garbage_is_an_error() {
|
||||
assert!(Reader::open(vec![0u8; 64]).is_err());
|
||||
|
||||
Reference in New Issue
Block a user