use crate::ndarray::compat::ArrayStatCompat;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::mem;
use std::path::{Path, PathBuf};
use std::slice;
use ::ndarray::{Array, Dimension};
use memmap2::MmapOptions;
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use super::memmap::{AccessMode, MemoryMappedArray};
use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
pub trait ZeroCopySerializable: Sized + Clone + Copy + 'static + Send + Sync {
unsafe fn from_bytes(bytes: &[u8]) -> CoreResult<Self>;
unsafe fn as_bytes(&self) -> &[u8];
fn validate_bytes(bytes: &[u8]) -> bool {
bytes.len() == mem::size_of::<Self>()
}
fn byte_size() -> usize {
mem::size_of::<Self>()
}
fn type_identifier() -> &'static str {
std::any::type_name::<Self>()
}
}
macro_rules! impl_zerocopy_serializable {
($type:ty, $bytesize:expr, $name:expr) => {
impl ZeroCopySerializable for $type {
unsafe fn from_bytes(bytes: &[u8]) -> CoreResult<Self> {
if !Self::validate_bytes(bytes) {
return Err(CoreError::ValidationError(
ErrorContext::new(format!(
"Invalid byte length for {}: expected {} got {}",
$name,
mem::size_of::<Self>(),
bytes.len()
))
.with_location(ErrorLocation::new(file!(), line!())),
));
}
let mut value = [0u8; $bytesize];
value.copy_from_slice(bytes);
Ok(<$type>::from_ne_bytes(value))
}
unsafe fn as_bytes(&self) -> &[u8] {
let ptr = self as *const Self as *const u8;
slice::from_raw_parts(ptr, mem::size_of::<Self>())
}
}
};
}
#[cfg(feature = "float32")]
impl_zerocopy_serializable!(f32, 4, "f32");
#[cfg(feature = "float64")]
impl_zerocopy_serializable!(f64, 8, "f64");
#[cfg(all(not(feature = "float32"), not(feature = "float64")))]
mod default_float_impls {
use super::*;
impl_zerocopy_serializable!(f32, 4, "f32");
impl_zerocopy_serializable!(f64, 8, "f64");
}
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(i8, 1, "i8");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(i16, 2, "i16");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(i32, 4, "i32");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(i64, 8, "i64");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(u8, 1, "u8");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(u16, 2, "u16");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(u32, 4, "u32");
#[cfg(feature = "all_ints")]
impl_zerocopy_serializable!(u64, 8, "u64");
#[cfg(all(not(feature = "all_ints"), feature = "int32"))]
impl_zerocopy_serializable!(i32, 4, "i32");
#[cfg(all(not(feature = "all_ints"), feature = "uint32"))]
impl_zerocopy_serializable!(u32, 4, "u32");
#[cfg(all(not(feature = "all_ints"), feature = "int64"))]
impl_zerocopy_serializable!(i64, 8, "i64");
#[cfg(all(not(feature = "all_ints"), feature = "uint64"))]
impl_zerocopy_serializable!(u64, 8, "u64");
#[cfg(all(
not(feature = "all_ints"),
not(feature = "int32"),
not(feature = "uint32"),
not(feature = "int64"),
not(feature = "uint64")
))]
mod default_int_impls {
use super::*;
impl_zerocopy_serializable!(i32, 4, "i32");
impl_zerocopy_serializable!(u32, 4, "u32");
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct ZeroCopyHeader {
pub type_name: String,
pub type_identifier: String,
pub element_size: usize,
pub shape: Vec<usize>,
pub total_elements: usize,
pub metadata_json: Option<String>,
}
pub trait ZeroCopySerialization<A: ZeroCopySerializable> {
fn save_zero_copy(
&self,
path: impl AsRef<Path>,
metadata: Option<serde_json::Value>,
) -> CoreResult<()>;
fn load_zero_copy(path: impl AsRef<Path>, mode: AccessMode)
-> CoreResult<MemoryMappedArray<A>>;
fn as_bytes_slice(&self) -> CoreResult<&[u8]>;
fn as_bytes_slice_mut(&mut self) -> CoreResult<&mut [u8]>;
}
impl<A: ZeroCopySerializable> ZeroCopySerialization<A> for MemoryMappedArray<A> {
fn save_zero_copy(
&self,
path: impl AsRef<Path>,
metadata: Option<serde_json::Value>,
) -> CoreResult<()> {
let path = path.as_ref();
let metadata_json = metadata
.map(|m| serde_json::to_string(&m))
.transpose()
.map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?;
let header = ZeroCopyHeader {
type_name: std::any::type_name::<A>().to_string(),
type_identifier: A::type_identifier().to_string(),
element_size: mem::size_of::<A>(),
shape: self.shape.clone(),
total_elements: self.size,
metadata_json,
};
let cfg = oxicode::config::standard();
let header_bytes = oxicode::serde::encode_to_vec(&header, cfg).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?;
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)?;
let header_len = header_bytes.len() as u64;
file.write_all(&header_len.to_ne_bytes())?;
file.write_all(&header_bytes)?;
let current_pos = 8 + header_len as usize; let alignment = std::mem::align_of::<A>();
let padding_needed = if current_pos % alignment == 0 {
0
} else {
alignment - (current_pos % alignment)
};
if padding_needed > 0 {
let padding = vec![0u8; padding_needed];
file.write_all(&padding)?;
}
let array_bytes = self.as_bytes_slice()?;
file.write_all(array_bytes)?;
Ok(())
}
fn load_zero_copy(
path: impl AsRef<Path>,
mode: AccessMode,
) -> CoreResult<MemoryMappedArray<A>> {
let path = path.as_ref();
let mut file = File::open(path)?;
let mut header_len_bytes = [0u8; 8]; file.read_exact(&mut header_len_bytes)?;
let header_len = u64::from_ne_bytes(header_len_bytes) as usize;
let mut header_bytes = vec![0u8; header_len];
file.read_exact(&mut header_bytes)?;
let cfg = oxicode::config::standard();
let (header, _len): (ZeroCopyHeader, usize) =
oxicode::serde::decode_owned_from_slice(&header_bytes, cfg).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?;
if header.element_size != mem::size_of::<A>() {
return Err(CoreError::ValidationError(
ErrorContext::new(format!(
"Element size mismatch: expected {} got {}",
mem::size_of::<A>(),
header.element_size
))
.with_location(ErrorLocation::new(file!(), line!())),
));
}
if header.type_identifier != A::type_identifier() {
return Err(CoreError::ValidationError(
ErrorContext::new(format!(
"Type identifier mismatch: expected '{}' got '{}'",
A::type_identifier(),
header.type_identifier
))
.with_location(ErrorLocation::new(file!(), line!())),
));
}
let base_offset = 8 + header_len;
let alignment = std::mem::align_of::<A>();
let padding_needed = if base_offset % alignment == 0 {
0
} else {
alignment - (base_offset % alignment)
};
let data_offset = base_offset + padding_needed;
match mode {
AccessMode::ReadOnly => {
let file = File::open(path)?;
let mmap = unsafe { MmapOptions::new().offset(data_offset as u64).map(&file)? };
Ok(MemoryMappedArray {
shape: header.shape,
file_path: path.to_path_buf(),
mode,
offset: data_offset,
size: header.total_elements,
mmap_view: Some(mmap),
mmap_view_mut: None,
is_temp: false,
phantom: PhantomData,
})
}
AccessMode::ReadWrite => {
let file = OpenOptions::new().read(true).write(true).open(path)?;
let mmap = unsafe {
MmapOptions::new()
.offset(data_offset as u64)
.map_mut(&file)?
};
Ok(MemoryMappedArray {
shape: header.shape,
file_path: path.to_path_buf(),
mode,
offset: data_offset,
size: header.total_elements,
mmap_view: None,
mmap_view_mut: Some(mmap),
is_temp: false,
phantom: PhantomData,
})
}
AccessMode::CopyOnWrite => {
let file = File::open(path)?;
let mmap = unsafe {
MmapOptions::new()
.offset(data_offset as u64)
.map_copy(&file)?
};
Ok(MemoryMappedArray {
shape: header.shape,
file_path: path.to_path_buf(),
mode,
offset: data_offset,
size: header.total_elements,
mmap_view: None,
mmap_view_mut: Some(mmap),
is_temp: false,
phantom: PhantomData,
})
}
AccessMode::Write => {
return Err(CoreError::ValidationError(
ErrorContext::new("Cannot use Write mode with load_zero_copy".to_string())
.with_location(ErrorLocation::new(file!(), line!())),
));
}
}
}
fn as_bytes_slice(&self) -> CoreResult<&[u8]> {
self.as_bytes()
}
fn as_bytes_slice_mut(&mut self) -> CoreResult<&mut [u8]> {
self.as_bytes_mut()
}
}
impl<A: ZeroCopySerializable> MemoryMappedArray<A> {
pub fn save_array<S, D>(
data: &crate::ndarray::ArrayBase<S, D>,
file_path: impl AsRef<Path>,
metadata: Option<serde_json::Value>,
) -> CoreResult<Self>
where
S: crate::ndarray::Data<Elem = A>,
D: Dimension,
{
let mmap = super::memmap::create_temp_mmap(data, AccessMode::ReadWrite, 0)?;
mmap.save_zero_copy(&file_path, metadata)?;
Self::load_zero_copy(&file_path, AccessMode::ReadWrite)
}
pub fn open_zero_copy(filepath: impl AsRef<Path>, mode: AccessMode) -> CoreResult<Self> {
Self::load_zero_copy(filepath, mode)
}
pub fn read_metadata(filepath: impl AsRef<Path>) -> CoreResult<serde_json::Value> {
let path = filepath.as_ref();
let mut file = File::open(path)?;
let mut header_len_bytes = [0u8; 8]; file.read_exact(&mut header_len_bytes)?;
let header_len = u64::from_ne_bytes(header_len_bytes) as usize;
let mut header_bytes = vec![0u8; header_len];
file.read_exact(&mut header_bytes)?;
let cfg = oxicode::config::standard();
let (header, _len): (ZeroCopyHeader, usize) =
oxicode::serde::decode_owned_from_slice(&header_bytes, cfg).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?;
match header.metadata_json {
Some(json_str) => serde_json::from_str(&json_str).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
}),
None => Ok(serde_json::json!({})),
}
}
pub fn readonlyarray<D>(&self) -> CoreResult<Array<A, D>>
where
D: Dimension,
{
self.as_array::<D>()
}
pub fn update_metadata(
file_path: impl AsRef<Path>,
metadata: serde_json::Value,
) -> CoreResult<()> {
let path = file_path.as_ref();
let mut file = OpenOptions::new().read(true).write(true).open(path)?;
let mut header_len_bytes = [0u8; 8]; file.read_exact(&mut header_len_bytes)?;
let header_len = u64::from_ne_bytes(header_len_bytes) as usize;
let mut header_bytes = vec![0u8; header_len];
file.read_exact(&mut header_bytes)?;
let cfg = oxicode::config::standard();
let (mut header, _len): (ZeroCopyHeader, usize) =
oxicode::serde::decode_owned_from_slice(&header_bytes, cfg).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?;
header.metadata_json = Some(serde_json::to_string(&metadata).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?);
let cfg = oxicode::config::standard();
let new_header_bytes = oxicode::serde::encode_to_vec(&header, cfg).map_err(|e| {
CoreError::ValidationError(
ErrorContext::new(format!("{e}"))
.with_location(ErrorLocation::new(file!(), line!())),
)
})?;
if new_header_bytes.len() <= header_len {
file.seek(SeekFrom::Start(8))?;
file.write_all(&new_header_bytes)?;
if new_header_bytes.len() < header_len {
let padding = vec![0u8; header_len - new_header_bytes.len()];
file.write_all(&padding)?;
}
Ok(())
} else {
let array = MemoryMappedArray::<A>::load_zero_copy(path, AccessMode::ReadOnly)?;
let temppath = PathBuf::from(format!("{}.temp", path.display()));
array.save_zero_copy(&temppath, Some(metadata.clone()))?;
std::fs::rename(&temppath, path)?;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{Array, Array1, Array2, Array3, IxDyn};
use tempfile::tempdir;
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
struct Complex64 {
real: f64,
imag: f64,
}
impl Complex64 {
fn new(real: f64, imag: f64) -> Self {
Self { real, imag }
}
#[allow(dead_code)]
fn magnitude(&self) -> f64 {
(self.real * self.real + self.imag * self.imag).sqrt()
}
}
impl ZeroCopySerializable for Complex64 {
unsafe fn from_bytes(bytes: &[u8]) -> CoreResult<Self> {
if !Self::validate_bytes(bytes) {
return Err(CoreError::ValidationError(
ErrorContext::new(format!(
"Invalid byte length for Complex64: expected {} got {}",
mem::size_of::<Self>(),
bytes.len()
))
.with_location(ErrorLocation::new(file!(), line!())),
));
}
let ptr = bytes.as_ptr() as *const Self;
Ok(*ptr)
}
unsafe fn as_bytes(&self) -> &[u8] {
let ptr = self as *const Self as *const u8;
slice::from_raw_parts(ptr, mem::size_of::<Self>())
}
fn type_identifier() -> &'static str {
"Complex64"
}
}
#[test]
fn test_custom_complex_type() {
let complex = Complex64::new(3.5, 2.7);
unsafe {
let bytes = complex.as_bytes();
assert_eq!(bytes.len(), 16);
let deserialized = Complex64::from_bytes(bytes).expect("Operation failed");
assert_eq!(complex.real, deserialized.real);
assert_eq!(complex.imag, deserialized.imag);
}
}
#[test]
fn test_save_and_load_complex_array() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("complex_array.bin");
let data =
Array2::<Complex64>::from_shape_fn((5, 5), |(i, j)| Complex64::new(i as f64, j as f64));
let metadata = serde_json::json!({
"description": "Complex number array",
"type": "Complex64",
"shape": [5, 5]
});
let array =
MemoryMappedArray::<Complex64>::save_array(&data, &filepath, Some(metadata.clone()))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
assert_eq!(array.size, data.len());
let loaded =
MemoryMappedArray::<Complex64>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
assert_eq!(loaded.shape.as_slice(), data.shape());
assert_eq!(loaded.size, data.len());
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix2>()
.expect("Operation failed");
for i in 0..5 {
for j in 0..5 {
let original = data[[0, j]];
let loaded = loaded_array[[0, j]];
assert_eq!(original.real, loaded.real);
assert_eq!(original.imag, loaded.imag);
}
}
let loaded_metadata =
MemoryMappedArray::<Complex64>::read_metadata(&filepath).expect("Operation failed");
assert_eq!(loaded_metadata, metadata);
}
#[test]
#[cfg(feature = "float32")]
fn test_zero_copy_serializable_f32() {
let value: f32 = 3.5;
let bytes = value.to_ne_bytes();
assert_eq!(bytes.len(), 4);
let deserialized = f32::from_ne_bytes(bytes);
assert_eq!(value, deserialized);
}
#[test]
fn test_zero_copy_serializable_i32() {
let value: i32 = -42;
unsafe {
let bytes = value.as_bytes();
assert_eq!(bytes.len(), 4);
let deserialized = i32::from_bytes(bytes).expect("Operation failed");
assert_eq!(value, deserialized);
}
}
#[test]
fn test_save_and_load_array_1d() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_array.bin");
let data = Array1::<f64>::linspace(0.0, 9.9, 100);
let metadata = serde_json::json!({
"description": "Test 1D array",
"created": "2023-05-20",
});
let array = MemoryMappedArray::<f64>::save_array(&data, &filepath, Some(metadata.clone()))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
assert_eq!(array.size, data.len());
let loaded = MemoryMappedArray::<f64>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
assert_eq!(loaded.shape.as_slice(), data.shape());
assert_eq!(loaded.size, data.len());
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix1>()
.expect("Operation failed");
assert_eq!(loaded_array.shape(), data.shape());
for (i, &val) in loaded_array.iter().enumerate() {
assert_eq!(val, data[i]);
}
let loaded_metadata =
MemoryMappedArray::<f64>::read_metadata(&filepath).expect("Operation failed");
assert_eq!(loaded_metadata, metadata);
}
#[test]
#[cfg(feature = "float32")]
fn test_save_and_load_array_2d() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_array_2d.bin");
let data = Array2::<f32>::from_shape_fn((10, 20), |(i, j)| (i * 20 + j) as f32);
let array =
MemoryMappedArray::<f32>::save_array(&data, &filepath, None).expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
assert_eq!(array.size, data.len());
let loaded = MemoryMappedArray::<f32>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
assert_eq!(loaded.shape.as_slice(), data.shape());
assert_eq!(loaded.size, data.len());
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix2>()
.expect("Operation failed");
assert_eq!(loaded_array.shape(), data.shape());
for i in 0..10 {
for j in 0..20 {
assert_eq!(loaded_array[[0, j]], data[[0, j]]);
}
}
}
#[test]
fn test_save_and_load_array_3d() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_array_3d.bin");
let data = Array3::<i32>::from_shape_fn((5, 5, 5), |(i, j, k)| (i * 25 + j * 5 + k) as i32);
let metadata = serde_json::json!({
"description": "Test 3D array",
"dimensions": {
"x": 5,
"y": 5,
"z": 5
}
});
let array = MemoryMappedArray::<i32>::save_array(&data, &filepath, Some(metadata))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
assert_eq!(array.size, data.len());
let loaded = MemoryMappedArray::<i32>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
assert_eq!(loaded.shape.as_slice(), data.shape());
assert_eq!(loaded.size, data.len());
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix3>()
.expect("Operation failed");
assert_eq!(loaded_array.shape(), data.shape());
for i in 0..5 {
for j in 0..5 {
for k in 0..5 {
assert_eq!(loaded_array[[0, j, k]], data[[0, j, k]]);
}
}
}
}
#[test]
fn test_save_and_load_array_dynamic() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_array_dyn.bin");
let shape = IxDyn(&[3, 4, 2, 5]);
let data = Array::from_shape_fn(shape, |idx| {
let mut val = 0;
let mut factor = 1;
for &dim in idx.slice().iter().rev() {
val += dim * factor;
factor *= 10;
}
val as f64
});
let metadata = serde_json::json!({
"description": "Test dynamic 4D array",
"dimensions": {
"dim1": 3,
"dim2": 4,
"dim3": 2,
"dim4": 5
},
"created": "2023-05-20",
"format_version": "1.0"
});
let array = MemoryMappedArray::<f64>::save_array(&data, &filepath, Some(metadata))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
assert_eq!(array.size, data.len());
let loaded = MemoryMappedArray::<f64>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
assert_eq!(loaded.shape.as_slice(), data.shape());
assert_eq!(loaded.size, data.len());
let loaded_array = loaded.readonlyarray::<IxDyn>().expect("Operation failed");
assert_eq!(loaded_array.shape(), data.shape());
let test_indices = vec![
IxDyn(&[0, 0, 0, 0]),
IxDyn(&[1, 2, 1, 3]),
IxDyn(&[2, 3, 1, 4]),
IxDyn(&[2, 0, 0, 2]),
];
for idx in test_indices {
assert_eq!(loaded_array[&idx], data[&idx]);
}
let loaded_slice = loaded.as_slice();
let data_standard = data.as_standard_layout();
let data_slice = data_standard.as_slice().expect("Operation failed");
assert_eq!(loaded_slice.len(), data_slice.len());
for i in 0..data_slice.len() {
assert_eq!(loaded_slice[0], data_slice[0]);
}
}
#[test]
#[cfg(feature = "float32")]
fn test_save_and_load_array_mixed_types() {
let dir = tempdir().expect("Operation failed");
{
let filename = "u32_1d.bin";
let filepath = dir.path().join(filename);
let data = Array1::<u32>::from_shape_fn(100, |_| 0 as u32);
let metadata = serde_json::json!({
"array_type": "u32",
"dimensions": data.ndim(),
"shape": data.shape().to_vec()
});
let array =
MemoryMappedArray::<u32>::save_array(&data, &filepath, Some(metadata.clone()))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
let loaded = MemoryMappedArray::<u32>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix1>()
.expect("Operation failed");
for i in 0..data.len() {
assert_eq!(loaded_array[0], data[0]);
}
let loaded_metadata =
MemoryMappedArray::<u32>::read_metadata(&filepath).expect("Operation failed");
assert_eq!(loaded_metadata, metadata);
}
{
let filename = "i64_2d.bin";
let filepath = dir.path().join(filename);
let data = Array2::<i64>::from_shape_fn((5, 10), |(i, j)| (i * 10 + j) as i64);
let metadata = serde_json::json!({
"array_type": "i64",
"dimensions": data.ndim(),
"shape": data.shape().to_vec()
});
let array =
MemoryMappedArray::<i64>::save_array(&data, &filepath, Some(metadata.clone()))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
let loaded = MemoryMappedArray::<i64>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix2>()
.expect("Operation failed");
for i in 0..data.shape()[0] {
for j in 0..data.shape()[1] {
assert_eq!(loaded_array[[0, j]], data[[0, j]]);
}
}
let loaded_metadata =
MemoryMappedArray::<i64>::read_metadata(&filepath).expect("Operation failed");
assert_eq!(loaded_metadata, metadata);
}
{
let filename = "f32_3d.bin";
let filepath = dir.path().join(filename);
let data =
Array3::<f32>::from_shape_fn((3, 4, 5), |(i, j, k)| (i * 20 + j * 5 + k) as f32);
let metadata = serde_json::json!({
"array_type": "f32",
"dimensions": data.ndim(),
"shape": data.shape().to_vec()
});
let array =
MemoryMappedArray::<f32>::save_array(&data, &filepath, Some(metadata.clone()))
.expect("Operation failed");
assert_eq!(array.shape.as_slice(), data.shape());
let loaded = MemoryMappedArray::<f32>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix3>()
.expect("Operation failed");
for i in 0..data.shape()[0] {
for j in 0..data.shape()[1] {
for k in 0..data.shape()[2] {
assert_eq!(loaded_array[[0, j, k]], data[[0, j, k]]);
}
}
}
let loaded_metadata =
MemoryMappedArray::<f32>::read_metadata(&filepath).expect("Operation failed");
assert_eq!(loaded_metadata, metadata);
}
}
#[test]
fn test_update_metadata() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_metadata_update.bin");
let data = Array1::<f64>::linspace(0.0, 9.9, 100);
let initial_metadata = serde_json::json!({
"description": "Initial metadata",
"_version": "1.0"
});
MemoryMappedArray::<f64>::save_array(&data, &filepath, Some(initial_metadata))
.expect("Operation failed");
let updated_metadata = serde_json::json!({
"description": "Updated metadata",
"_version": "2.0",
"updated": true
});
MemoryMappedArray::<f64>::update_metadata(&filepath, updated_metadata.clone())
.expect("Operation failed");
let loaded_metadata =
MemoryMappedArray::<f64>::read_metadata(&filepath).expect("Operation failed");
assert_eq!(loaded_metadata, updated_metadata);
let loaded = MemoryMappedArray::<f64>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix1>()
.expect("Operation failed");
for (i, &val) in loaded_array.iter().enumerate() {
assert_eq!(val, data[i]);
}
}
#[test]
#[cfg(feature = "float32")]
fn test_modify_array() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_modify.bin");
let data = Array2::<f32>::from_shape_fn((5, 5), |(i, j)| (i * 5 + j) as f32);
MemoryMappedArray::<f32>::save_array(&data, &filepath, None).expect("Operation failed");
let mut mmap = MemoryMappedArray::<f32>::open_zero_copy(&filepath, AccessMode::ReadWrite)
.expect("Operation failed");
{
let mut array = mmap
.as_array_mut::<crate::ndarray::Ix2>()
.expect("Operation failed");
array[[2, 2]] = 999.0;
}
mmap.flush().expect("Operation failed");
let loaded = MemoryMappedArray::<f32>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
let loaded_array = loaded
.readonlyarray::<crate::ndarray::Ix2>()
.expect("Operation failed");
for i in 0..5 {
for j in 0..5 {
if i == 2 && j == 2 {
assert_eq!(loaded_array[[i, j]], 999.0);
} else {
assert_eq!(loaded_array[[i, j]], data[[i, j]]);
}
}
}
}
#[test]
fn test_copy_on_write_mode() {
let dir = tempdir().expect("Operation failed");
let filepath = dir.path().join("test_cow.bin");
let data = Array2::<f64>::from_shape_fn((10, 10), |(i, j)| (i * 10 + j) as f64);
MemoryMappedArray::<f64>::save_array(&data, &filepath, None).expect("Operation failed");
let mut cow_mmap =
MemoryMappedArray::<f64>::open_zero_copy(&filepath, AccessMode::CopyOnWrite)
.expect("Operation failed");
{
let mut array_view = cow_mmap
.as_array_mut::<crate::ndarray::Ix2>()
.expect("Operation failed");
for i in 0..10 {
array_view[[i, i]] = 100.0;
}
}
let original = MemoryMappedArray::<f64>::open_zero_copy(&filepath, AccessMode::ReadOnly)
.expect("Operation failed");
let original_array = original
.readonlyarray::<crate::ndarray::Ix2>()
.expect("Operation failed");
for i in 0..10 {
for j in 0..10 {
assert_eq!(original_array[[i, j]], data[[i, j]]);
}
}
let cow_array = cow_mmap
.as_array::<crate::ndarray::Ix2>()
.expect("Operation failed");
for i in 0..10 {
for j in 0..10 {
if i == j {
assert_eq!(cow_array[[i, j]], 100.0);
} else {
assert_eq!(cow_array[[i, j]], data[[i, j]]);
}
}
}
}
}