hdf5-pure-rust 0.3.2

Pure Rust implementation of the HDF5 file format
Documentation
use crate::error::{Error, Result};

pub fn vector_reduce_product(values: &[u64]) -> Result<u64> {
    values.iter().try_fold(1u64, |acc, &value| {
        acc.checked_mul(value)
            .ok_or_else(|| Error::InvalidFormat("vector product overflow".into()))
    })
}

pub fn vector_zerop_u(values: &[u64]) -> bool {
    values.iter().any(|&value| value == 0)
}

pub fn vector_zerop_s(values: &[i64]) -> bool {
    values.iter().any(|&value| value == 0)
}

pub fn vector_cmp_u(lhs: &[u64], rhs: &[u64]) -> std::cmp::Ordering {
    lhs.cmp(rhs)
}

pub fn vector_cmp_s(lhs: &[i64], rhs: &[i64]) -> std::cmp::Ordering {
    lhs.cmp(rhs)
}

pub fn vector_inc(values: &mut [u64], dims: &[u64]) -> bool {
    if values.len() != dims.len() {
        return false;
    }
    for index in (0..values.len()).rev() {
        values[index] += 1;
        if values[index] < dims[index] {
            return true;
        }
        values[index] = 0;
    }
    false
}

pub fn power2up(value: u64) -> Option<u64> {
    if value <= 1 {
        Some(1)
    } else {
        value.checked_next_power_of_two()
    }
}

pub fn bit_set(bytes: &mut [u8], bit: usize, value: bool) -> Result<()> {
    let byte = bytes
        .get_mut(bit / 8)
        .ok_or_else(|| Error::InvalidFormat("bit index out of bounds".into()))?;
    let mask = 0x80 >> (bit % 8);
    if value {
        *byte |= mask;
    } else {
        *byte &= !mask;
    }
    Ok(())
}

pub fn bit_get(bytes: &[u8], bit: usize) -> Result<bool> {
    let byte = bytes
        .get(bit / 8)
        .ok_or_else(|| Error::InvalidFormat("bit index out of bounds".into()))?;
    Ok((*byte & (0x80 >> (bit % 8))) != 0)
}

pub fn log2_of2(value: u64) -> Result<u32> {
    if value == 0 || !value.is_power_of_two() {
        return Err(Error::InvalidFormat("value is not a power of two".into()));
    }
    Ok(value.trailing_zeros())
}

pub fn log2_gen(value: u64) -> Result<u32> {
    if value == 0 {
        return Err(Error::InvalidFormat("log2 input is zero".into()));
    }
    Ok(u64::BITS - 1 - value.leading_zeros())
}

pub fn stride_optimize1(stride: usize, elem_size: usize) -> Result<usize> {
    stride
        .checked_mul(elem_size)
        .ok_or_else(|| Error::InvalidFormat("stride optimization overflow".into()))
}

pub fn stride_optimize2(stride: usize, count: usize, elem_size: usize) -> Result<usize> {
    stride
        .checked_mul(count)
        .and_then(|value| value.checked_mul(elem_size))
        .ok_or_else(|| Error::InvalidFormat("stride optimization overflow".into()))
}

pub fn hyper_stride(start: &[u64], stride: &[u64], index: &[u64]) -> Result<Vec<u64>> {
    if start.len() != stride.len() || start.len() != index.len() {
        return Err(Error::InvalidFormat("hyperslab rank mismatch".into()));
    }
    start
        .iter()
        .zip(stride)
        .zip(index)
        .map(|((&start, &stride), &index)| {
            index
                .checked_mul(stride)
                .and_then(|delta| start.checked_add(delta))
                .ok_or_else(|| Error::InvalidFormat("hyperslab coordinate overflow".into()))
        })
        .collect()
}

pub fn hyper_eq(start_a: &[u64], stride_a: &[u64], start_b: &[u64], stride_b: &[u64]) -> bool {
    start_a == start_b && stride_a == stride_b
}

pub fn hyper_fill<T: Clone>(out: &mut [T], value: T) {
    out.fill(value);
}

pub fn hyper_copy<T: Clone>(src: &[T], dst: &mut [T]) -> Result<()> {
    if dst.len() < src.len() {
        return Err(Error::InvalidFormat(
            "hyperslab destination too small".into(),
        ));
    }
    dst[..src.len()].clone_from_slice(src);
    Ok(())
}

pub fn stride_fill<T: Clone>(
    out: &mut [T],
    offset: usize,
    stride: usize,
    count: usize,
    value: T,
) -> Result<()> {
    let mut pos = offset;
    for _ in 0..count {
        let slot = out
            .get_mut(pos)
            .ok_or_else(|| Error::InvalidFormat("stride fill index out of bounds".into()))?;
        *slot = value.clone();
        pos = pos
            .checked_add(stride)
            .ok_or_else(|| Error::InvalidFormat("stride fill index overflow".into()))?;
    }
    Ok(())
}

pub fn stride_copy<T: Clone>(
    src: &[T],
    dst: &mut [T],
    src_stride: usize,
    dst_stride: usize,
    count: usize,
) -> Result<()> {
    stride_copy_s(src, dst, 0, 0, src_stride, dst_stride, count)
}

pub fn stride_copy_s<T: Clone>(
    src: &[T],
    dst: &mut [T],
    mut src_pos: usize,
    mut dst_pos: usize,
    src_stride: usize,
    dst_stride: usize,
    count: usize,
) -> Result<()> {
    for _ in 0..count {
        let value = src
            .get(src_pos)
            .ok_or_else(|| Error::InvalidFormat("stride copy source out of bounds".into()))?
            .clone();
        let slot = dst
            .get_mut(dst_pos)
            .ok_or_else(|| Error::InvalidFormat("stride copy destination out of bounds".into()))?;
        *slot = value;
        src_pos = src_pos
            .checked_add(src_stride)
            .ok_or_else(|| Error::InvalidFormat("stride copy source overflow".into()))?;
        dst_pos = dst_pos
            .checked_add(dst_stride)
            .ok_or_else(|| Error::InvalidFormat("stride copy destination overflow".into()))?;
    }
    Ok(())
}

pub fn array_down(coords: &[u64], dims: &[u64]) -> Result<Vec<u64>> {
    if coords.len() != dims.len() {
        return Err(Error::InvalidFormat("array rank mismatch".into()));
    }
    coords
        .iter()
        .zip(dims)
        .map(|(&coord, &dim)| {
            if dim == 0 {
                Err(Error::InvalidFormat("array dimension is zero".into()))
            } else {
                Ok(coord % dim)
            }
        })
        .collect()
}

pub fn array_offset_pre(dims: &[u64]) -> Result<Vec<u64>> {
    let mut strides = vec![1u64; dims.len()];
    if dims.len() > 1 {
        for index in (0..dims.len() - 1).rev() {
            strides[index] = strides[index + 1]
                .checked_mul(dims[index + 1])
                .ok_or_else(|| Error::InvalidFormat("array stride overflow".into()))?;
        }
    }
    Ok(strides)
}

pub fn array_offset(coords: &[u64], dims: &[u64]) -> Result<u64> {
    if coords.len() != dims.len() {
        return Err(Error::InvalidFormat("array rank mismatch".into()));
    }
    let strides = array_offset_pre(dims)?;
    coords
        .iter()
        .zip(dims)
        .zip(strides)
        .try_fold(0u64, |acc, ((&coord, &dim), stride)| {
            if coord >= dim {
                return Err(Error::InvalidFormat(
                    "array coordinate out of bounds".into(),
                ));
            }
            let term = coord
                .checked_mul(stride)
                .ok_or_else(|| Error::InvalidFormat("array offset overflow".into()))?;
            acc.checked_add(term)
                .ok_or_else(|| Error::InvalidFormat("array offset overflow".into()))
        })
}

pub fn chunk_index(coords: &[u64], dims: &[u64]) -> Result<u64> {
    array_offset(coords, dims)
}

pub fn chunk_scaled(coords: &[u64], chunk_dims: &[u64]) -> Result<Vec<u64>> {
    if coords.len() != chunk_dims.len() {
        return Err(Error::InvalidFormat("chunk rank mismatch".into()));
    }
    coords
        .iter()
        .zip(chunk_dims)
        .map(|(&coord, &chunk)| {
            if chunk == 0 {
                Err(Error::InvalidFormat("chunk dimension is zero".into()))
            } else {
                Ok(coord / chunk)
            }
        })
        .collect()
}

pub fn chunk_index_scaled(coords: &[u64], dims: &[u64], chunk_dims: &[u64]) -> Result<u64> {
    let scaled = chunk_scaled(coords, chunk_dims)?;
    let scaled_dims = chunk_scaled(dims, chunk_dims)?;
    chunk_index(&scaled, &scaled_dims)
}

pub fn opvv<T: Copy, F: Fn(T, T) -> T>(lhs: &[T], rhs: &[T], out: &mut [T], op: F) -> Result<()> {
    if lhs.len() != rhs.len() || lhs.len() > out.len() {
        return Err(Error::InvalidFormat(
            "vector operation length mismatch".into(),
        ));
    }
    for ((out, &lhs), &rhs) in out.iter_mut().zip(lhs).zip(rhs) {
        *out = op(lhs, rhs);
    }
    Ok(())
}

pub fn memcpyvv<T: Copy>(
    dst: &mut [T],
    dst_offsets: &[usize],
    src: &[T],
    src_offsets: &[usize],
    lens: &[usize],
) -> Result<()> {
    if dst_offsets.len() != src_offsets.len() || dst_offsets.len() != lens.len() {
        return Err(Error::InvalidFormat(
            "vector copy segment length mismatch".into(),
        ));
    }
    for ((&dst_offset, &src_offset), &len) in dst_offsets.iter().zip(src_offsets).zip(lens) {
        let src_end = src_offset
            .checked_add(len)
            .ok_or_else(|| Error::InvalidFormat("vector copy source overflow".into()))?;
        let dst_end = dst_offset
            .checked_add(len)
            .ok_or_else(|| Error::InvalidFormat("vector copy destination overflow".into()))?;
        let src_window = src
            .get(src_offset..src_end)
            .ok_or_else(|| Error::InvalidFormat("vector copy source out of bounds".into()))?;
        let dst_window = dst
            .get_mut(dst_offset..dst_end)
            .ok_or_else(|| Error::InvalidFormat("vector copy destination out of bounds".into()))?;
        dst_window.copy_from_slice(src_window);
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn chunk_scaling_and_indexing_work() {
        assert_eq!(chunk_scaled(&[9, 5], &[4, 2]).unwrap(), vec![2, 2]);
        assert_eq!(chunk_index(&[2, 1], &[4, 4]).unwrap(), 9);
    }

    #[test]
    fn vector_copy_segments_work() {
        let src = [1, 2, 3, 4, 5];
        let mut dst = [0; 5];
        memcpyvv(&mut dst, &[0, 3], &src, &[1, 4], &[2, 1]).unwrap();
        assert_eq!(dst, [2, 3, 0, 5, 0]);
    }
}