From a9e0a9a6f4c1f5a533ac82ff06ae7d3177845ebe Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Dec 2023 22:27:52 +0100 Subject: [PATCH 1/6] Fix doc-test on no-default-features --- netcdf/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netcdf/src/lib.rs b/netcdf/src/lib.rs index 045fbe8..5929b04 100644 --- a/netcdf/src/lib.rs +++ b/netcdf/src/lib.rs @@ -38,6 +38,7 @@ //! let data_i32 = var.value::((40, 0, 0))?; //! //! // You can use `values_arr()` to get all the data from the variable. +//! // This requires the `ndarray` feature //! // Passing `..` will give you the entire slice //! # #[cfg(feature = "ndarray")] //! let data = var.values_arr::(..)?; @@ -46,6 +47,7 @@ //! // `(40, 0, 0)` and get a dataset of size `100, 100` from this //! # #[cfg(feature = "ndarray")] //! let data = var.values_arr::(([40, 0 ,0], [1, 100, 100]))?; +//! # #[cfg(feature = "ndarray")] //! let data = var.values_arr::((40, ..100, ..100))?; //! # Ok(()) } //! ``` From ec9e181d4311b40a98565887e843414049746aa4 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Dec 2023 22:27:52 +0100 Subject: [PATCH 2/6] Set resolver version --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ebb4509..7b874c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,4 @@ members = [ "netcdf-src", ] default-members = ["netcdf", "netcdf-sys"] +resolver = "2" From b41721acff81ba08171b49bd677e56ecd4607097 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Dec 2023 22:27:52 +0100 Subject: [PATCH 3/6] Make indexing reduce dimensionality --- netcdf/src/extent.rs | 125 ++++++++++++++++++++++++++++++----------- netcdf/src/variable.rs | 16 +++++- netcdf/tests/lib.rs | 34 +++++++++++ 3 files changed, 141 insertions(+), 34 deletions(-) diff --git a/netcdf/src/extent.rs b/netcdf/src/extent.rs index c95fffc..0749c04 100644 --- a/netcdf/src/extent.rs +++ b/netcdf/src/extent.rs @@ -206,6 +206,7 @@ impl From for Extent { impl_for_ref!(RangeFull: Extent); impl Extent { + #[allow(unused)] const fn stride(&self) -> Option { match *self { Self::Slice { start: _, stride } @@ -460,19 +461,100 @@ impl From<()> for Extents { pub(crate) type StartCountStride = (Vec, Vec, Vec); +pub(crate) struct StartCountStrideIterItem { + pub(crate) start: usize, + pub(crate) count: usize, + pub(crate) stride: isize, + pub(crate) is_an_index: bool, +} + +enum StartCountStrideIter<'a> { + All(std::slice::Iter<'a, Dimension<'a>>), + Extent(std::iter::Zip, std::slice::Iter<'a, Dimension<'a>>>), +} + +impl<'a> Iterator for StartCountStrideIter<'a> { + type Item = StartCountStrideIterItem; + fn next(&mut self) -> Option { + match self { + Self::All(iter) => iter.next().map(|dim| Self::Item { + start: 0, + count: dim.len(), + stride: 1, + is_an_index: false, + }), + Self::Extent(iter) => iter.next().map(|(extent, dim)| match *extent { + Extent::Index(start) => Self::Item { + start, + count: 1, + stride: 1, + is_an_index: true, + }, + Extent::Slice { start, stride } => stride.try_into().map_or_else( + |_| Self::Item { + start, + count: 0, + stride, // negative stride is not used + is_an_index: false, + }, + |stride| Self::Item { + start, + count: (start..dim.len()).step_by(stride).count(), + stride: stride as isize, + is_an_index: false, + }, + ), + Extent::SliceCount { + start, + count, + stride, + } => Self::Item { + start, + count, + stride, + is_an_index: false, + }, + Extent::SliceEnd { start, end, stride } => stride.try_into().map_or_else( + |_| Self::Item { + start, + count: 0, + stride, // negative stride is not used + is_an_index: false, + }, + |stride| Self::Item { + start, + count: (start..end).step_by(stride).count(), + stride: stride as isize, + is_an_index: false, + }, + ), + }), + } + } +} + impl Extents { pub(crate) fn get_start_count_stride( &self, dims: &[Dimension], ) -> Result { - let (start, count, stride): StartCountStride = match self { - Self::All => { - let start = dims.iter().map(|_| 0).collect(); - let counts = dims.iter().map(Dimension::len).collect(); - let stride = dims.iter().map(|_| 1).collect(); + let mut start = vec![]; + let mut count = vec![]; + let mut stride = vec![]; + for item in self.iter_with_dims(dims)? { + start.push(item.start); + count.push(item.count); + stride.push(item.stride); + } + Ok((start, count, stride)) + } - (start, counts, stride) - } + pub(crate) fn iter_with_dims<'a>( + &'a self, + dims: &'a [Dimension], + ) -> Result + 'a, error::Error> { + match self { + Self::All => Ok(StartCountStrideIter::All(dims.iter())), Self::Extent(extents) => { if extents.len() != dims.len() { return Err(error::Error::DimensionMismatch { @@ -480,32 +562,11 @@ impl Extents { actual: extents.len(), }); } - let (start, count) = dims - .iter() - .zip(extents) - .map(|(d, &e)| match e { - Extent::Index(start) => (start, 1), - Extent::Slice { start, stride } => usize::try_from(stride).map_or_else( - |_| (start, 0), - |stride| (start, (start..d.len()).step_by(stride).count()), - ), - Extent::SliceCount { - start, - count, - stride: _, - } => (start, count), - Extent::SliceEnd { start, end, stride } => usize::try_from(stride) - .map_or_else( - |_| (start, 0), - |stride| (start, (start..end).step_by(stride).count()), - ), - }) - .unzip(); - let stride = extents.iter().map(|e| e.stride().unwrap_or(1)).collect(); - (start, count, stride) + Ok(StartCountStrideIter::Extent( + extents.iter().zip(dims.iter()), + )) } - }; - Ok((start, count, stride)) + } } } diff --git a/netcdf/src/variable.rs b/netcdf/src/variable.rs index c94ac30..83301d6 100644 --- a/netcdf/src/variable.rs +++ b/netcdf/src/variable.rs @@ -820,7 +820,19 @@ impl<'g> Variable<'g> { /// Fetches variable fn values_arr_mono(&self, extents: &Extents) -> error::Result> { let dims = self.dimensions(); - let (start, count, stride) = extents.get_start_count_stride(dims)?; + let mut start = vec![]; + let mut count = vec![]; + let mut stride = vec![]; + let mut shape = vec![]; + + for item in extents.iter_with_dims(dims)? { + start.push(item.start); + count.push(item.count); + stride.push(item.stride); + if !item.is_an_index { + shape.push(item.count); + } + } let number_of_elements = count.iter().copied().fold(1_usize, usize::saturating_mul); let mut values = Vec::with_capacity(number_of_elements); @@ -829,7 +841,7 @@ impl<'g> Variable<'g> { values.set_len(number_of_elements); }; - Ok(ArrayD::from_shape_vec(count, values).unwrap()) + Ok(ArrayD::from_shape_vec(shape, values).unwrap()) } #[cfg(feature = "ndarray")] diff --git a/netcdf/tests/lib.rs b/netcdf/tests/lib.rs index 67ced50..db7e614 100644 --- a/netcdf/tests/lib.rs +++ b/netcdf/tests/lib.rs @@ -1585,3 +1585,37 @@ fn invalid_utf8_as_path() { let retrieved_path = file.path().unwrap(); assert_eq!(fullpath, retrieved_path); } + +#[test] +#[cfg(feature = "ndarray")] +fn drop_dim_on_simple_indices() { + let d = tempfile::tempdir().unwrap(); + let path = d.path().join("read_write_netcdf"); + + let mut f = netcdf::create(path).unwrap(); + f.add_dimension("d1", 10).unwrap(); + f.add_dimension("d2", 11).unwrap(); + f.add_dimension("d3", 12).unwrap(); + f.add_dimension("d4", 13).unwrap(); + + let var = f + .add_variable::("v", &["d1", "d2", "d3", "d4"]) + .unwrap(); + + let values = var.values_arr::(..).unwrap(); + assert_eq!(values.shape(), &[10, 11, 12, 13]); + let values = var.values_arr::((.., .., .., 1)).unwrap(); + assert_eq!(values.shape(), &[10, 11, 12]); + let values = var.values_arr::((.., 9, .., 1)).unwrap(); + assert_eq!(values.shape(), &[10, 12]); + + let values = var.values_arr::((.., 9, .., 1..=1)).unwrap(); + assert_eq!(values.shape(), &[10, 12, 1]); + + let values = var.values_arr::((2, 9, 3, 1)).unwrap(); + assert_eq!(values.shape(), &[]); + // Really awkward to get this one value... + let value = values.into_dimensionality().unwrap().into_scalar(); + let fill_value = var.fill_value().unwrap().unwrap(); + assert_eq!(value, fill_value); +} From b93b775e44a3e8f337a64cbf53ed3154ce1db516 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Dec 2023 22:27:52 +0100 Subject: [PATCH 4/6] Add put for ndarrays --- netcdf/src/extent.rs | 20 +++++++++++ netcdf/src/variable.rs | 76 +++++++++++++++++++++++++++++++++++++++++ netcdf/tests/lib.rs | 77 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) diff --git a/netcdf/src/extent.rs b/netcdf/src/extent.rs index 0749c04..b6bb3bd 100644 --- a/netcdf/src/extent.rs +++ b/netcdf/src/extent.rs @@ -461,11 +461,17 @@ impl From<()> for Extents { pub(crate) type StartCountStride = (Vec, Vec, Vec); +#[allow(dead_code)] pub(crate) struct StartCountStrideIterItem { pub(crate) start: usize, pub(crate) count: usize, pub(crate) stride: isize, + /// Extent is an index pub(crate) is_an_index: bool, + /// The dimension can increase + pub(crate) is_growable: bool, + /// The extent has an upper bound + pub(crate) is_upwards_limited: bool, } enum StartCountStrideIter<'a> { @@ -482,6 +488,8 @@ impl<'a> Iterator for StartCountStrideIter<'a> { count: dim.len(), stride: 1, is_an_index: false, + is_growable: dim.is_unlimited(), + is_upwards_limited: false, }), Self::Extent(iter) => iter.next().map(|(extent, dim)| match *extent { Extent::Index(start) => Self::Item { @@ -489,6 +497,8 @@ impl<'a> Iterator for StartCountStrideIter<'a> { count: 1, stride: 1, is_an_index: true, + is_growable: dim.is_unlimited(), + is_upwards_limited: true, }, Extent::Slice { start, stride } => stride.try_into().map_or_else( |_| Self::Item { @@ -496,12 +506,16 @@ impl<'a> Iterator for StartCountStrideIter<'a> { count: 0, stride, // negative stride is not used is_an_index: false, + is_growable: dim.is_unlimited(), + is_upwards_limited: false, }, |stride| Self::Item { start, count: (start..dim.len()).step_by(stride).count(), stride: stride as isize, is_an_index: false, + is_growable: dim.is_unlimited(), + is_upwards_limited: false, }, ), Extent::SliceCount { @@ -513,6 +527,8 @@ impl<'a> Iterator for StartCountStrideIter<'a> { count, stride, is_an_index: false, + is_growable: dim.is_unlimited(), + is_upwards_limited: true, }, Extent::SliceEnd { start, end, stride } => stride.try_into().map_or_else( |_| Self::Item { @@ -520,12 +536,16 @@ impl<'a> Iterator for StartCountStrideIter<'a> { count: 0, stride, // negative stride is not used is_an_index: false, + is_growable: dim.is_unlimited(), + is_upwards_limited: true, }, |stride| Self::Item { start, count: (start..end).step_by(stride).count(), stride: stride as isize, is_an_index: false, + is_growable: dim.is_unlimited(), + is_upwards_limited: true, }, ), }), diff --git a/netcdf/src/variable.rs b/netcdf/src/variable.rs index 83301d6..12be3ca 100644 --- a/netcdf/src/variable.rs +++ b/netcdf/src/variable.rs @@ -1268,6 +1268,82 @@ impl<'g> VariableMut<'g> { let extent = indices.try_into().map_err(Into::into)?; self.put_vlen_mono(vec, &extent) } + + #[cfg(feature = "ndarray")] + /// Put values in an ndarray into the variable + pub fn put_values_arr( + &mut self, + extent: E, + arr: ndarray::ArrayView, + ) -> error::Result<()> + where + E: TryInto, + E::Error: Into, + D: ndarray::Dimension, + { + let extent = extent.try_into().map_err(|e| e.into())?; + + let slice = if let Some(slice) = arr.as_slice() { + slice + } else { + return Err( + "Slice is not contiguous or in c-order, you might want to use `as_standard_layout`" + .into(), + ); + }; + + let dimlen = self.dimensions.len(); + let mut start = Vec::with_capacity(dimlen); + let mut count = Vec::with_capacity(dimlen); + let mut stride = Vec::with_capacity(dimlen); + + let mut remaining_arrshape = arr.shape(); + for (pos, item) in extent.iter_with_dims(self.dimensions())?.enumerate() { + if item.is_an_index { + start.push(item.start); + count.push(item.count); + stride.push(item.stride); + continue; + } + let arr_len = if let Some((&head, rest)) = remaining_arrshape.split_first() { + remaining_arrshape = rest; + head + } else { + return Err("Extents have greater dimensionality than the input array".into()); + }; + + start.push(item.start); + if arr_len != item.count { + if arr_len > item.count && item.is_growable && !item.is_upwards_limited { + // Item is allowed to grow to accomodate the + // extra values in the array + } else { + return Err(format!( + "Variable dimension (at position {pos}) has length {}, but input array has a size of {arr_len}", + item.count, + ) + .into()); + } + } + count.push(arr_len); + stride.push(item.stride); + } + if !remaining_arrshape.is_empty() { + return Err("Extents have lesser dimensionality than the input array".into()); + } + + assert_eq!( + arr.len(), + count.iter().copied().fold(1, usize::saturating_mul), + "Mismatch between the number of elements in array and the calculated `count`s" + ); + + // Safety: + // Dimensionality matches (always pushing in for loop) + // slice is valid pointer since we assert the size above + // slice is valid pointer since memory order is standard_layout (C) + unsafe { T::put_vars(self, &start, &count, &stride, slice.as_ptr()) } + } } impl<'g> VariableMut<'g> { diff --git a/netcdf/tests/lib.rs b/netcdf/tests/lib.rs index db7e614..3265918 100644 --- a/netcdf/tests/lib.rs +++ b/netcdf/tests/lib.rs @@ -1619,3 +1619,80 @@ fn drop_dim_on_simple_indices() { let fill_value = var.fill_value().unwrap().unwrap(); assert_eq!(value, fill_value); } + +#[test] +#[cfg(feature = "ndarray")] +fn ndarray_put() { + use ndarray::s; + + let d = tempfile::tempdir().unwrap(); + let path = d.path().join("stuff.nc"); + + let mut f = netcdf::create(path).unwrap(); + f.add_dimension("d1", 10).unwrap(); + f.add_dimension("d2", 11).unwrap(); + f.add_dimension("d3", 12).unwrap(); + f.add_dimension("d4", 13).unwrap(); + f.add_unlimited_dimension("grow").unwrap(); + + let values = ndarray::Array::::zeros((10, 11, 12, 13)); + + let mut var = f + .add_variable::("var", &["d1", "d2", "d3", "d4"]) + .unwrap(); + + macro_rules! put_values { + ($var:expr, $values:expr, $extent:expr) => { + put_values!($var, $extent, $values, $extent) + }; + ($var:expr, $extent:expr, $values:expr, $slice:expr) => { + $var.put_values_arr($extent, $values.slice($slice).as_standard_layout().view()) + .unwrap() + }; + ($var:expr, $extent:expr, $values:expr, $slice:expr, Failure) => { + $var.put_values_arr($extent, $values.slice($slice).as_standard_layout().view()) + .unwrap_err() + }; + } + + var.put_values_arr(.., values.view()).unwrap(); + put_values!(var, values, s![3, .., .., ..]); + put_values!(var, values, s![5, .., 2, 3]); + put_values!(var, values, s![5, .., 2, 3..5]); + + put_values!(var, .., values, s![.., .., .., ..]); + put_values!(var, (4, 6, .., ..), values, s![4, 6, .., ..]); + + put_values!(var, (4, 6, 2, ..), values, s![4, 6, 2, ..]); + put_values!(var, (4, 6, .., 4), values, s![4, 6, .., 4]); + + put_values!(var, values, s![4..;3, 6, .., 4]); + put_values!(var, .., values, s![.., .., .., 1], Failure); + put_values!(var, (.., .., .., 1), values, s![.., .., .., ..], Failure); + + std::mem::drop(var); + let mut var = f.add_variable::("grow", &["grow", "d2", "d3"]).unwrap(); + + // let values = ndarray::Array::::zeros((10, 11, 12, 13)); + put_values!(var, .., values, s![.., .., .., ..], Failure); + put_values!(var, .., values, s![..0, .., .., 0]); + put_values!(var, .., values, s![..1, .., .., 0]); + put_values!(var, .., values, s![.., .., .., 0]); + // Should fail since we don't know where to put the value (front? back?) + put_values!(var, .., values, s![..1, .., .., 0], Failure); + + put_values!(var, (1, 4, 3), values, s![1, 1, 0, 0]); + + put_values!(var, (1, 3..4, 3), values, s![1, 1, 5..6, 0]); + put_values!(var, (0, 0, 0), values, s![0, 0, .., ..], Failure); + // And weird implementation makes it such that we can append from a position which + // is not at the end, maybe this should be an error? + put_values!(var, (6.., .., ..), values, s![.., .., .., 0]); + // Can put at the same spot + put_values!(var, (6.., .., ..), values, s![.., .., .., 0]); + // But not if in the middle + put_values!(var, (5.., .., ..), values, s![.., .., .., 0], Failure); + // If the number of items is specified we can't put more items + put_values!(var, (5..6, .., ..), values, s![.., .., .., 0], Failure); + put_values!(var, (5..15, .., ..), values, s![.., .., .., 0]); +} From 0a6d9e403b0f47b0e630659e257559a2fdc839c4 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Dec 2023 22:27:52 +0100 Subject: [PATCH 5/6] Add put_into for ndarray --- netcdf/src/variable.rs | 63 ++++++++++++++++++++++++++++++++++++++++++ netcdf/tests/lib.rs | 46 ++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/netcdf/src/variable.rs b/netcdf/src/variable.rs index 12be3ca..8b5b753 100644 --- a/netcdf/src/variable.rs +++ b/netcdf/src/variable.rs @@ -855,6 +855,69 @@ impl<'g> Variable<'g> { self.values_arr_mono(&extents) } + #[cfg(feature = "ndarray")] + /// Get values into an ndarray + pub fn values_arr_into( + &self, + extents: E, + mut out: ndarray::ArrayViewMut, + ) -> error::Result<()> + where + D: ndarray::Dimension, + E: TryInto, + E::Error: Into, + { + let extents = extents.try_into().map_err(|e| e.into())?; + + let dims = self.dimensions(); + let mut start = Vec::with_capacity(dims.len()); + let mut count = Vec::with_capacity(dims.len()); + let mut stride = Vec::with_capacity(dims.len()); + + let mut rem_outshape = out.shape(); + + for (pos, item) in extents.iter_with_dims(dims)?.enumerate() { + start.push(item.start); + count.push(item.count); + stride.push(item.stride); + if !item.is_an_index { + let cur_dim_len = if let Some((&head, rest)) = rem_outshape.split_first() { + rem_outshape = rest; + head + } else { + return Err(("Output array dimensionality is less than extents").into()); + }; + if item.count != cur_dim_len { + return Err(format!("Item count (position {pos}) as {} but expected in output was {cur_dim_len}", item.count).into()); + } + } + } + if !rem_outshape.is_empty() { + return Err(("Output array dimensionality is larger than extents").into()); + } + + let slice = if let Some(slice) = out.as_slice_mut() { + slice + } else { + return Err("Output array must be in standard layout".into()); + }; + + assert_eq!( + slice.len(), + count.iter().copied().fold(1, usize::saturating_mul), + "Output size and number of elements to get are not compatible" + ); + + // Safety: + // start, count, stride are correct length + // slice is valid pointer, with enough space to hold all elements + unsafe { + T::get_vars(self, &start, &count, &stride, slice.as_mut_ptr())?; + } + + Ok(()) + } + /// Get the fill value of a variable pub fn fill_value(&self) -> error::Result> { if T::NCTYPE != self.vartype { diff --git a/netcdf/tests/lib.rs b/netcdf/tests/lib.rs index 3265918..ce2d4b0 100644 --- a/netcdf/tests/lib.rs +++ b/netcdf/tests/lib.rs @@ -1696,3 +1696,49 @@ fn ndarray_put() { put_values!(var, (5..6, .., ..), values, s![.., .., .., 0], Failure); put_values!(var, (5..15, .., ..), values, s![.., .., .., 0]); } + +#[test] +#[cfg(feature = "ndarray")] +fn ndarray_get_into() { + use ndarray::s; + + let d = tempfile::tempdir().unwrap(); + let path = d.path().join("get_into.nc"); + + let mut f = netcdf::create(path).unwrap(); + f.add_dimension("d1", 4).unwrap(); + f.add_dimension("d2", 5).unwrap(); + f.add_dimension("d3", 6).unwrap(); + + let values = ndarray::Array::::from_shape_fn((4, 5, 6), |(k, j, i)| { + (100 * k + 10 * j + i).try_into().unwrap() + }); + + let mut var = f.add_variable::("var", &["d1", "d2", "d3"]).unwrap(); + + var.put_values_arr(.., values.view()).unwrap(); + + let mut outarray = ndarray::Array::::zeros((4, 5, 6)); + + var.values_arr_into(.., outarray.view_mut()).unwrap(); + assert_eq!(values, outarray); + outarray.fill(0); + + var.values_arr_into((1, .., ..), outarray.slice_mut(s![0, .., ..])) + .unwrap(); + assert_eq!(values.slice(s![1, .., ..]), outarray.slice(s![0, .., ..])); + outarray.fill(0); + + var.values_arr_into((3, 1, ..), outarray.slice_mut(s![0, 0, ..])) + .unwrap(); + assert_eq!(values.slice(s![3, 1, ..]), outarray.slice(s![0, 0, ..])); + outarray.fill(0); + + var.values_arr_into((.., .., 1), outarray.slice_mut(s![.., .., 1])) + .unwrap_err(); + + let mut outarray = ndarray::Array::::zeros((3, 4, 5, 6)); + var.values_arr_into((.., .., ..), outarray.slice_mut(s![0, .., .., ..])) + .unwrap(); + assert_eq!(values, outarray.slice(s![0, .., .., ..])); +} From 057e97cc47b7e58bac7814907fdb5e634f9d9395 Mon Sep 17 00:00:00 2001 From: Magnus Ulimoen Date: Fri, 1 Dec 2023 22:27:52 +0100 Subject: [PATCH 6/6] Show put/get ndarray in docs --- netcdf/src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/netcdf/src/lib.rs b/netcdf/src/lib.rs index 5929b04..486bb11 100644 --- a/netcdf/src/lib.rs +++ b/netcdf/src/lib.rs @@ -49,6 +49,12 @@ //! let data = var.values_arr::(([40, 0 ,0], [1, 100, 100]))?; //! # #[cfg(feature = "ndarray")] //! let data = var.values_arr::((40, ..100, ..100))?; +//! +//! // You can read into an ndarray to reuse an allocation +//! # #[cfg(feature = "ndarray")] +//! let mut data = ndarray::Array::::zeros((100, 100)); +//! # #[cfg(feature = "ndarray")] +//! var.values_arr_into((0, .., ..), data.view_mut())?; //! # Ok(()) } //! ``` //! @@ -69,7 +75,8 @@ //! "crab_coolness_level", //! &["time", "ncrabs"], //! )?; -//! // Metadata can be added to the variable +//! // Metadata can be added to the variable, but will not be used when +//! // writing or reading data //! var.add_attribute("units", "Kelvin")?; //! var.add_attribute("add_offset", 273.15_f32)?; //! @@ -80,6 +87,12 @@ //! // Values can be added along the unlimited dimension, which //! // resizes along the `time` axis //! var.put_values(&data, (11, ..))?; +//! +//! // Using the ndarray feature you can also use +//! # #[cfg(feature = "ndarray")] +//! let values = ndarray::Array::from_shape_fn((5, 10), |(j, i)| (j * 10 + i) as f32); +//! # #[cfg(feature = "ndarray")] +//! var.put_values_arr((11.., ..), values.view())?; //! # Ok(()) } //! ```