fix(py): index lists of padded compounds no longer return uninitialised padding
np.concatenate copies structured dtypes field by field into np.empty, so the padding of ds[[0, 3, 6]] held process memory. The runs' bytes are joined in Rust, whole elements at a time, before anything becomes numpy: the padding is the file's bytes (h5py's) and the result is still a view of the Rust buffer. The h5py comparisons now compare every byte of structured values; the new test failed on the padding before. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -82,70 +82,60 @@ impl PyDataset {
|
||||
conv.empty(py, &out_shape)?
|
||||
} else {
|
||||
let (reads, list_axis) = plan.reads(dims);
|
||||
let read_shape = plan.read_shape();
|
||||
let file = &*self.file;
|
||||
let path = self.path.as_str();
|
||||
let (vl, elem_size, unit) = (conv.is_vl(), conv.elem_size, conv.vl_unit);
|
||||
// Everything below touches only Rust data: release the GIL.
|
||||
let read = || -> Result<_, ReadError> {
|
||||
let read = || -> Result<Elements, ReadError> {
|
||||
let ds = file.dataset(path)?;
|
||||
let sb = file.superblock();
|
||||
let mut blocks = Vec::with_capacity(reads.len());
|
||||
for (sel, shape) in reads {
|
||||
let raw = ds.read_selection(&sel)?;
|
||||
let n: usize = shape.iter().product();
|
||||
let data = if vl {
|
||||
if raw.len() != n * elem_size {
|
||||
return Err(ReadError::Other(format!(
|
||||
"read {} bytes of variable-length references, expected {}",
|
||||
raw.len(),
|
||||
n * elem_size
|
||||
)));
|
||||
}
|
||||
Elements::Vl(
|
||||
resolve_vl(
|
||||
file.as_bytes(),
|
||||
&raw,
|
||||
n,
|
||||
sb.offset_size,
|
||||
sb.length_size,
|
||||
unit,
|
||||
)
|
||||
.map_err(ReadError::Other)?,
|
||||
)
|
||||
} else {
|
||||
Elements::Bytes(raw)
|
||||
};
|
||||
blocks.push((data, shape));
|
||||
let want = shape.iter().product::<usize>() * elem_size;
|
||||
if raw.len() != want {
|
||||
return Err(ReadError::Other(format!(
|
||||
"read {} bytes, expected {want}",
|
||||
raw.len()
|
||||
)));
|
||||
}
|
||||
blocks.push((raw, shape));
|
||||
}
|
||||
Ok(blocks)
|
||||
// Several blocks only for a list index: join their bytes
|
||||
// (every byte of every element, padding included) along
|
||||
// that axis before anything becomes numpy.
|
||||
let raw = match (blocks.len(), list_axis) {
|
||||
(1, _) => blocks.pop().expect("one block").0,
|
||||
(_, Some(axis)) => select::join_along(&blocks, axis, elem_size),
|
||||
_ => {
|
||||
return Err(ReadError::Other(
|
||||
"several reads without an index list".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
if !vl {
|
||||
return Ok(Elements::Bytes(raw));
|
||||
}
|
||||
let sb = file.superblock();
|
||||
let n = read_shape.iter().product();
|
||||
resolve_vl(
|
||||
file.as_bytes(),
|
||||
&raw,
|
||||
n,
|
||||
sb.offset_size,
|
||||
sb.length_size,
|
||||
unit,
|
||||
)
|
||||
.map(Elements::Vl)
|
||||
.map_err(ReadError::Other)
|
||||
};
|
||||
let blocks: Vec<(Elements, Vec<usize>)> = py
|
||||
let data = py
|
||||
.detach(|| {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(read))
|
||||
.unwrap_or_else(|p| Err(ReadError::Panic(crate::panic_text(&*p))))
|
||||
})
|
||||
.map_err(|e| e.into_py(&self.path))?;
|
||||
|
||||
let mut arrays = Vec::with_capacity(blocks.len());
|
||||
for (data, shape) in blocks {
|
||||
arrays.push(conv.to_array(py, data, &shape, false)?);
|
||||
}
|
||||
let joined = if arrays.len() == 1 {
|
||||
arrays.pop().expect("one block")
|
||||
} else {
|
||||
let axis = list_axis.expect("several reads only for a list index");
|
||||
// Name the dtype: left to itself numpy canonicalises a
|
||||
// structured dtype here (drops padding, native byte order).
|
||||
let kwargs = pyo3::types::PyDict::new(py);
|
||||
kwargs.set_item("axis", axis)?;
|
||||
kwargs.set_item("dtype", arrays[0].getattr("dtype")?)?;
|
||||
kwargs.set_item("casting", "no")?;
|
||||
py.import("numpy")?.call_method(
|
||||
"concatenate",
|
||||
(PyList::new(py, arrays)?,),
|
||||
Some(&kwargs),
|
||||
)?
|
||||
};
|
||||
let joined = conv.to_array(py, data, &read_shape, false)?;
|
||||
// Drop the axes indexed by an integer (length 1 in the blocks).
|
||||
let mut shape = out_shape.clone();
|
||||
if let crate::convert::Layout::Subarray(sub) = &conv.layout {
|
||||
|
||||
Reference in New Issue
Block a user