clawhdf5-netcdf4: variables' dimensions come from the file
Variables got the first unused dimension of equal size, so a variable on an unlimited dimension with fewer records got an anonymous dim_<n>, and dimensions of one size could be swapped. Resolve them as netCDF-C does (libhdf5/hdf5open.c): _Netcdf4Coordinates ids, else the scales DIMENSION_LIST references (the last one attached to an axis), searched in the variable's group and its parents; a coordinate variable is on its own scale. Size matching remains only for axes the file names nothing for. variables()/variable_names() leave out dimension scales that are only dimensions, and _nc4_non_coord_<name> is the variable <name>. Variable::shape is the netCDF shape (an unlimited dimension's length) and the reads pad unwritten records with the fill value (_FillValue, else NC_FILL_*; NaN from read_f64); Variable::stored_shape is the HDF5 extent. New NetCDF4File::variable_names. Tests compare with netCDF4-python variable by variable: the known-issues reproducer, equal sizes, (p, p), scalars, inherited dimensions, unwritten records, h5py dimension scales, h5netcdf and xarray files. CI installs h5netcdf. known-issues entry moved to Fixed (history); stale open-table row for the unlimited-size fix removed. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -754,3 +754,53 @@ fn test_dimension_struct_equality() {
|
||||
};
|
||||
assert_ne!(d1, d3);
|
||||
}
|
||||
|
||||
/// A dimension scale that is only a dimension (netCDF-C's `NAME`) is not a
|
||||
/// variable, and `_nc4_non_coord_<name>` is the variable `<name>`, found in
|
||||
/// place of the scale of the same name.
|
||||
#[test]
|
||||
fn test_pure_dimensions_hidden_and_non_coord_names() {
|
||||
let pure = "This is a netCDF dimension but not a netCDF variable. 2";
|
||||
let mut b = FileBuilder::new();
|
||||
b.create_dataset("x")
|
||||
.with_f32_data(&[0.0, 0.0])
|
||||
.with_shape(&[2])
|
||||
.set_attr("CLASS", AttrValue::String("DIMENSION_SCALE".into()))
|
||||
.set_attr("NAME", AttrValue::String(pure.into()))
|
||||
.set_attr("_Netcdf4Dimid", AttrValue::I64(0));
|
||||
b.create_dataset("_nc4_non_coord_x")
|
||||
.with_f64_data(&[1.0, 2.0, 3.0])
|
||||
.with_shape(&[3]);
|
||||
b.create_dataset("v")
|
||||
.with_f64_data(&[5.0, 6.0])
|
||||
.with_shape(&[2]);
|
||||
let file = NetCDF4File::from_bytes(b.finish().unwrap()).unwrap();
|
||||
|
||||
let dims = file.dimensions().unwrap();
|
||||
assert_eq!(dims.len(), 1);
|
||||
assert_eq!(dims[0].name, "x");
|
||||
let mut names = file.variable_names().unwrap();
|
||||
names.sort();
|
||||
assert_eq!(names, ["v", "x"]);
|
||||
let x = file.variable("x").unwrap();
|
||||
assert_eq!(x.name(), "x");
|
||||
assert_eq!(x.read_raw_f64().unwrap(), [1.0, 2.0, 3.0]);
|
||||
assert!(!x.is_coordinate());
|
||||
// No DIMENSION_LIST: `v` gets `x` by size, as before.
|
||||
assert_eq!(file.variable("v").unwrap().dimensions()[0].name, "x");
|
||||
}
|
||||
|
||||
/// `variable` still takes a path relative to the group, as it did when it
|
||||
/// opened the dataset by path.
|
||||
#[test]
|
||||
fn test_variable_by_path() {
|
||||
let file = NetCDF4File::from_bytes(make_grouped_netcdf4()).unwrap();
|
||||
let pressure = file.variable("surface/pressure").unwrap();
|
||||
assert_eq!(pressure.name(), "pressure");
|
||||
assert_eq!(pressure.read_raw_f64().unwrap(), [1013.25, 1012.0, 1011.5]);
|
||||
assert!(file.variable("/time").is_ok());
|
||||
assert!(matches!(
|
||||
file.variable("nowhere/pressure"),
|
||||
Err(clawhdf5_netcdf4::Error::VariableNotFound(_))
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user