Skip to content

Commit

Permalink
Make common API for &str/DimensionIdentifier
Browse files Browse the repository at this point in the history
  • Loading branch information
magnusuMET committed Mar 1, 2024
1 parent 100da01 commit 3577bfa
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 195 deletions.
172 changes: 171 additions & 1 deletion netcdf/src/dimension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,118 @@ use netcdf_sys::*;

use super::error;

mod sealed {
pub trait Sealed {}
}

/// Types which can be used to distinguish dimensions
/// in a netCDF file. This can be `&str` (the normal use case)
/// or a special identifier when using nested groups.
///
/// This trait is not expected to be implemented elsewhere and is therefore sealed.
///
/// # Examples
/// (helper function to show consumption of type)
/// ```rust
/// # use netcdf::AsNcDimensions;
/// fn take(d: impl AsNcDimensions) {}
/// ```
/// Normally one uses the name of the dimension to specify the dimension
/// ```rust
/// # use netcdf::AsNcDimensions;
/// # fn take(d: impl AsNcDimensions) {}
/// take(()); // scalar
/// take("x"); // single dimension
/// take(["x", "y"]); // multiple dimensions
/// ```
/// When working with dimensions across groups, it might be necessary
/// to use dimension identifiers to get the correct group dimension
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use netcdf::AsNcDimensions;
/// # fn take(d: impl AsNcDimensions) {}
/// let file = netcdf::open("test.nc")?;
/// let dim = file.dimension("x").expect("File does not contain dimension");
/// let dimid = dim.identifier();
///
/// take(dimid); // from a dimension identifier
/// take([dimid, dimid]); // from multiple identifiers
/// # Ok(()) }
/// ```
pub trait AsNcDimensions: sealed::Sealed {
/// Convert from a slice of [`&str`]/[`DimensionIdentifier`] to concrete dimensions
/// which are guaranteed to exist in this file
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>>;
}

impl sealed::Sealed for &[&str] {}
impl AsNcDimensions for &[&str] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.iter()
.map(|&name| match dimension_from_name(ncid, name) {
Ok(Some(x)) => Ok(x),
Ok(None) => Err(format!("Dimension {name} not found").into()),
Err(e) => Err(e),
})
.collect()
}
}
impl<const N: usize> sealed::Sealed for [&str; N] {}
impl<const N: usize> AsNcDimensions for [&str; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}
impl<const N: usize> sealed::Sealed for &[&str; N] {}
impl<const N: usize> AsNcDimensions for &[&str; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}

impl sealed::Sealed for &[DimensionIdentifier] {}
impl AsNcDimensions for &[DimensionIdentifier] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.iter()
.map(|dimid| match dimension_from_identifier(ncid, *dimid) {
Ok(Some(x)) => Ok(x),
Ok(None) => Err("Dimension id does not exist".into()),
Err(e) => Err(e),
})
.collect()
}
}
impl<const N: usize> sealed::Sealed for [DimensionIdentifier; N] {}
impl<const N: usize> AsNcDimensions for [DimensionIdentifier; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}
impl<const N: usize> sealed::Sealed for &[DimensionIdentifier; N] {}
impl<const N: usize> AsNcDimensions for &[DimensionIdentifier; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}
impl sealed::Sealed for () {}
impl AsNcDimensions for () {
fn get_dimensions<'g>(&self, _ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
Ok(Vec::new())
}
}
impl sealed::Sealed for &str {}
impl AsNcDimensions for &str {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
([*self]).get_dimensions(ncid)
}
}
impl sealed::Sealed for DimensionIdentifier {}
impl AsNcDimensions for DimensionIdentifier {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
([*self]).get_dimensions(ncid)
}
}

/// Represents a netcdf dimension
#[derive(Debug, Clone)]
pub struct Dimension<'g> {
Expand All @@ -25,9 +137,23 @@ pub struct DimensionIdentifier {
pub(crate) dimid: nc_type,
}

impl DimensionIdentifier {
// Internal netcdf detail, the top 16 bits gives the corresponding
// file handle. This to ensure dimensions are not added from another
// file which is unrelated to self
pub(crate) fn belongs_to(&self, ncid: nc_type) -> bool {
self.ncid >> 16 == ncid >> 16
}
}

#[allow(clippy::len_without_is_empty)]
impl<'g> Dimension<'g> {
/// Get current length of this dimension
///
/// ## Note
/// A dimension can be unlimited (growable) and changes size
/// if putting values to a variable which uses this
/// dimension
pub fn len(&self) -> usize {
if let Some(x) = self.len {
x.get()
Expand Down Expand Up @@ -66,7 +192,10 @@ impl<'g> Dimension<'g> {
}

/// Grabs the unique identifier for this dimension, which
/// can be used in `add_variable_from_identifiers`
/// can be used in [`add_variable`](crate::FileMut::add_variable).
///
/// This is useful when working with nested groups and need
/// to distinguish between names at different levels.
pub fn identifier(&self) -> DimensionIdentifier {
self.id
}
Expand Down Expand Up @@ -272,6 +401,47 @@ pub(crate) fn dimension_from_name<'f>(
}))
}

pub(crate) fn dimension_from_identifier<'f>(
ncid: nc_type,
dimid: DimensionIdentifier,
) -> error::Result<Option<Dimension<'f>>> {
if !dimid.belongs_to(ncid) {
return Err(error::Error::WrongDataset);
}
let dimid = dimid.dimid;

let mut dimlen = 0;
unsafe {
error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap();
}
if dimlen != 0 {
// Have to check if this dimension is unlimited
let mut nunlim = 0;
unsafe {
error::checked(super::with_lock(|| {
nc_inq_unlimdims(ncid, &mut nunlim, std::ptr::null_mut())
}))?;
}
if nunlim != 0 {
let mut unlimdims = Vec::with_capacity(nunlim.try_into()?);
unsafe {
error::checked(super::with_lock(|| {
nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr())
}))?;
}
unsafe { unlimdims.set_len(nunlim.try_into()?) }
if unlimdims.contains(&dimid) {
dimlen = 0;
}
}
}
Ok(Some(Dimension {
len: core::num::NonZeroUsize::new(dimlen),
id: super::dimension::DimensionIdentifier { ncid, dimid },
_group: PhantomData,
}))
}

pub(crate) fn add_dimension_at<'f>(
ncid: nc_type,
name: &str,
Expand Down
57 changes: 37 additions & 20 deletions netcdf/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::path;
use netcdf_sys::*;

use super::attribute::{Attribute, AttributeValue};
use super::dimension::{self, Dimension};
use super::dimension::{AsNcDimensions, Dimension};
use super::error;
use super::group::{Group, GroupMut};
use super::variable::{NcPutGet, Variable, VariableMut};
Expand Down Expand Up @@ -338,20 +338,50 @@ impl FileMut {
))
}

/// Create a Variable into the dataset, with no data written into it
/// Create a variable in the dataset, with no data written into it
///
/// Dimensions are identified using the name of the dimension, and will recurse upwards
/// if not found in the current group.
pub fn add_variable<'f, T>(
/// # Examples
/// ## Adding variables with different types
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut file = netcdf::create("file.nc")?;
/// file.add_dimension("x", 10)?;
/// file.add_dimension("y", 11)?;
/// file.add_variable::<u8, _>("var_u8", &["y", "x"])?;
/// file.add_variable::<i8, _>("var_i8", &["y", "x"])?;
/// file.add_variable::<u64, _>("var_u64", &["y", "x"])?;
/// file.add_variable::<f64, _>("var_f64", &["y", "x"])?;
/// # Ok(())}
/// ```
/// ## Adding a scalar variable
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut file = netcdf::create("scalar.nc")?;
/// file.add_variable::<u8, _>("var2", ())?;
/// # Ok(())}
/// ```
/// ## Using dimension identifiers in place of names
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut file = netcdf::create("dimids.nc")?;
/// let dimx = file.dimension("x").unwrap().identifier();
/// let dimy = file.dimension("y").unwrap().identifier();
/// file.add_variable::<u8, _>("var2", &[dimy, dimx])?;
/// # Ok(())}
///```
///
/// See [`AsNcDimensions`] for how to specify dimensions.
pub fn add_variable<'f, T, D>(
&'f mut self,
name: &str,
dims: &[&str],
dims: D,
) -> error::Result<VariableMut<'f>>
where
T: NcPutGet,
D: AsNcDimensions,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
}

/// Create a variable with the specified type
Expand Down Expand Up @@ -410,19 +440,6 @@ impl FileMut {
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
VariableMut::add_from_str(ncid, NC_STRING, name, dims)
}
/// Adds a variable from a set of unique identifiers, recursing upwards
/// from the current group if necessary.
pub fn add_variable_from_identifiers<'f, T>(
&'f mut self,
name: &str,
dims: &[dimension::DimensionIdentifier],
) -> error::Result<VariableMut<'f>>
where
T: NcPutGet,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
}
}

#[cfg(feature = "has-mmap")]
Expand Down
28 changes: 10 additions & 18 deletions netcdf/src/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::marker::PhantomData;
use netcdf_sys::*;

use super::attribute::{Attribute, AttributeValue};
use super::dimension::Dimension;
use super::dimension::{AsNcDimensions, Dimension};
use super::error;
use super::variable::{NcPutGet, Variable, VariableMut};

Expand Down Expand Up @@ -244,18 +244,23 @@ impl<'f> GroupMut<'f> {
/// Create a Variable into the dataset, with no data written into it
///
/// Dimensions are identified using the name of the dimension, and will recurse upwards
/// if not found in the current group.
pub fn add_variable<'g, T>(
/// if not found in the current group. If the name is shadowed one can get an
/// [`DimensionIdentifier`](crate::DimensionIdentifier) to ensure the right dimension
/// is used
///
/// See [`AsNcDimensions`] for how to specify dimensions.
pub fn add_variable<'g, T, D>(
&'g mut self,
name: &str,
dims: &[&str],
dims: D,
) -> error::Result<VariableMut<'g>>
where
T: NcPutGet,
'f: 'g,
D: AsNcDimensions,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
}
/// Adds a variable with a basic type of string
pub fn add_string_variable<'g>(
Expand All @@ -266,19 +271,6 @@ impl<'f> GroupMut<'f> {
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
VariableMut::add_from_str(ncid, NC_STRING, name, dims)
}
/// Adds a variable from a set of unique identifiers, recursing upwards
/// from the current group if necessary.
pub fn add_variable_from_identifiers<'g, T>(
&'g mut self,
name: &str,
dims: &[super::dimension::DimensionIdentifier],
) -> error::Result<VariableMut<'g>>
where
T: NcPutGet,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
}

/// Create a variable with the specified type
pub fn add_variable_with_type(
Expand Down
4 changes: 2 additions & 2 deletions netcdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
//! file.add_unlimited_dimension("time")?;
//!
//! // A variable can now be declared, and must be created from the dimension names.
//! let mut var = file.add_variable::<i32>(
//! let mut var = file.add_variable::<i32, _>(
//! "crab_coolness_level",
//! &["time", "ncrabs"],
//! )?;
Expand Down Expand Up @@ -114,7 +114,7 @@ pub mod types;
pub(crate) mod variable;

pub use attribute::{Attribute, AttributeValue};
pub use dimension::{Dimension, DimensionIdentifier};
pub use dimension::{AsNcDimensions, Dimension, DimensionIdentifier};
pub use error::{Error, Result};
pub use extent::{Extent, Extents};
#[cfg(feature = "has-mmap")]
Expand Down
Loading

0 comments on commit 3577bfa

Please sign in to comment.