1use crate::error::{IoError, Result};
48use scirs2_core::ndarray::{ArrayBase, ArrayD, ArrayView, ArrayViewMut, Dimension, IxDyn};
49use std::fs::{File, OpenOptions};
50use std::io::Write;
51use std::marker::PhantomData;
52use std::path::Path;
53
54pub struct MmapArray<T> {
56 mmap: memmap2::Mmap,
58 _file: File,
60 len: usize,
62 _phantom: PhantomData<T>,
64}
65
66pub struct MmapArrayMut<T> {
68 mmap: memmap2::MmapMut,
70 _file: File,
72 len: usize,
74 _phantom: PhantomData<T>,
76}
77
78pub struct MmapArrayBuilder<'a> {
80 path: &'a Path,
82 create: bool,
84 truncate: bool,
86 buffer_size: usize,
88}
89
90#[derive(Debug, Clone, Default)]
92pub struct MmapConfig {
93 pub prefetch: bool,
95 pub page_size: Option<usize>,
97 pub sequential: bool,
99 pub random: bool,
101}
102
103impl<'a> MmapArrayBuilder<'a> {
104 pub fn new<P: AsRef<Path>>(path: &'a P) -> Self {
106 Self {
107 path: path.as_ref(),
108 create: true,
109 truncate: false,
110 buffer_size: 64 * 1024, }
112 }
113
114 pub fn create(mut self, create: bool) -> Self {
116 self.create = create;
117 self
118 }
119
120 pub fn truncate(mut self, truncate: bool) -> Self {
122 self.truncate = truncate;
123 self
124 }
125
126 pub fn buffer_size(mut self, size: usize) -> Self {
128 self.buffer_size = size;
129 self
130 }
131
132 pub fn create_from_array<S, D, T>(&self, array: &ArrayBase<S, D>) -> Result<()>
134 where
135 S: scirs2_core::ndarray::Data<Elem = T>,
136 D: Dimension,
137 T: Clone + bytemuck::Pod,
138 {
139 let mut file = OpenOptions::new()
140 .write(true)
141 .create(self.create)
142 .truncate(self.truncate)
143 .open(self.path)
144 .map_err(|e| IoError::FileError(format!("Failed to create file: {}", e)))?;
145
146 let shape = array.shape();
148 let ndim = shape.len() as u64;
149 file.write_all(&ndim.to_le_bytes())
150 .map_err(|e| IoError::FileError(format!("Failed to write metadata: {}", e)))?;
151
152 for &dim in shape {
153 let dim = dim as u64;
154 file.write_all(&dim.to_le_bytes())
155 .map_err(|e| IoError::FileError(format!("Failed to write shape: {}", e)))?;
156 }
157
158 let element_size = std::mem::size_of::<T>() as u64;
160 file.write_all(&element_size.to_le_bytes())
161 .map_err(|e| IoError::FileError(format!("Failed to write element size: {}", e)))?;
162
163 if array.is_standard_layout() {
165 let data_slice = bytemuck::cast_slice(array.as_slice().expect("Operation failed"));
167 let mut written = 0;
168 while written < data_slice.len() {
169 let chunk_size = (data_slice.len() - written).min(self.buffer_size);
170 let chunk = &data_slice[written..written + chunk_size];
171 file.write_all(chunk)
172 .map_err(|e| IoError::FileError(format!("Failed to write data: {}", e)))?;
173 written += chunk_size;
174 }
175 } else {
176 let owned_array = array.to_owned();
178 let data_slice =
179 bytemuck::cast_slice(owned_array.as_slice().expect("Operation failed"));
180 let mut written = 0;
181 while written < data_slice.len() {
182 let chunk_size = (data_slice.len() - written).min(self.buffer_size);
183 let chunk = &data_slice[written..written + chunk_size];
184 file.write_all(chunk)
185 .map_err(|e| IoError::FileError(format!("Failed to write data: {}", e)))?;
186 written += chunk_size;
187 }
188 }
189
190 file.sync_all()
191 .map_err(|e| IoError::FileError(format!("Failed to sync file: {}", e)))?;
192
193 Ok(())
194 }
195
196 pub fn create_empty<T>(&self, shape: &[usize]) -> Result<()>
198 where
199 T: bytemuck::Pod,
200 {
201 let mut file = OpenOptions::new()
202 .write(true)
203 .create(self.create)
204 .truncate(self.truncate)
205 .open(self.path)
206 .map_err(|e| IoError::FileError(format!("Failed to create file: {}", e)))?;
207
208 let ndim = shape.len() as u64;
210 file.write_all(&ndim.to_le_bytes())
211 .map_err(|e| IoError::FileError(format!("Failed to write metadata: {}", e)))?;
212
213 for &dim in shape {
214 let dim = dim as u64;
215 file.write_all(&dim.to_le_bytes())
216 .map_err(|e| IoError::FileError(format!("Failed to write shape: {}", e)))?;
217 }
218
219 let element_size = std::mem::size_of::<T>() as u64;
220 file.write_all(&element_size.to_le_bytes())
221 .map_err(|e| IoError::FileError(format!("Failed to write element size: {}", e)))?;
222
223 let total_elements: usize = shape.iter().product();
225 let total_bytes = total_elements * std::mem::size_of::<T>();
226
227 let zero_buffer = vec![0u8; self.buffer_size.min(total_bytes)];
228 let mut remaining = total_bytes;
229
230 while remaining > 0 {
231 let chunk_size = remaining.min(zero_buffer.len());
232 file.write_all(&zero_buffer[..chunk_size])
233 .map_err(|e| IoError::FileError(format!("Failed to write zeros: {}", e)))?;
234 remaining -= chunk_size;
235 }
236
237 file.sync_all()
238 .map_err(|e| IoError::FileError(format!("Failed to sync file: {}", e)))?;
239
240 Ok(())
241 }
242}
243
244impl<T> MmapArray<T>
245where
246 T: bytemuck::Pod,
247{
248 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
250 let file = File::open(path.as_ref())
251 .map_err(|e| IoError::FileError(format!("Failed to open file: {}", e)))?;
252
253 let file_size = file
254 .metadata()
255 .map_err(|e| IoError::FileError(format!("Failed to get file size: {}", e)))?
256 .len();
257
258 if file_size < 8 {
259 return Err(IoError::FormatError(
260 "File too small to contain valid array".to_string(),
261 ));
262 }
263
264 let mmap = unsafe {
265 memmap2::Mmap::map(&file)
266 .map_err(|e| IoError::FileError(format!("Failed to create memory map: {}", e)))?
267 };
268
269 let (len_value, metadata_size) = Self::read_metadata(&mmap[..])?;
271
272 Ok(Self {
273 mmap,
274 _file: file,
275 len: len_value,
276 _phantom: PhantomData,
277 })
278 }
279
280 fn read_metadata(mmap: &[u8]) -> Result<(usize, usize)> {
282 if mmap.len() < 8 {
283 return Err(IoError::FormatError("Invalid file format".to_string()));
284 }
285
286 let mut offset = 0;
287
288 let ndim = u64::from_le_bytes(
290 mmap[offset..offset + 8]
291 .try_into()
292 .map_err(|_| IoError::FormatError("Failed to read ndim".to_string()))?,
293 ) as usize;
294 offset += 8;
295
296 if ndim == 0 || ndim > 32 {
297 return Err(IoError::FormatError(
298 "Invalid number of dimensions".to_string(),
299 ));
300 }
301
302 let mut total_elements = 1;
304 for _ in 0..ndim {
305 if offset + 8 > mmap.len() {
306 return Err(IoError::FormatError("Truncated shape data".to_string()));
307 }
308 let dim = u64::from_le_bytes(
309 mmap[offset..offset + 8]
310 .try_into()
311 .map_err(|_| IoError::FormatError("Failed to read dimension".to_string()))?,
312 ) as usize;
313 total_elements *= dim;
314 offset += 8;
315 }
316
317 if offset + 8 > mmap.len() {
319 return Err(IoError::FormatError(
320 "Truncated element size data".to_string(),
321 ));
322 }
323 let element_size = u64::from_le_bytes(
324 mmap[offset..offset + 8]
325 .try_into()
326 .map_err(|_| IoError::FormatError("Failed to read element size".to_string()))?,
327 ) as usize;
328 offset += 8;
329
330 if element_size != std::mem::size_of::<T>() {
331 return Err(IoError::FormatError("Element size mismatch".to_string()));
332 }
333
334 Ok((total_elements, offset))
335 }
336
337 pub fn shape(&self) -> Result<Vec<usize>> {
339 let mut offset = 0;
340
341 let ndim = u64::from_le_bytes(
343 self.mmap[offset..offset + 8]
344 .try_into()
345 .map_err(|_| IoError::FormatError("Failed to read ndim".to_string()))?,
346 ) as usize;
347 offset += 8;
348
349 let mut shape = Vec::with_capacity(ndim);
351 for _ in 0..ndim {
352 let dim = u64::from_le_bytes(
353 self.mmap[offset..offset + 8]
354 .try_into()
355 .map_err(|_| IoError::FormatError("Failed to read dimension".to_string()))?,
356 ) as usize;
357 shape.push(dim);
358 offset += 8;
359 }
360
361 Ok(shape)
362 }
363
364 fn data_offset(&self) -> Result<usize> {
366 let ndim = u64::from_le_bytes(
367 self.mmap[0..8]
368 .try_into()
369 .map_err(|_| IoError::FormatError("Failed to read ndim".to_string()))?,
370 ) as usize;
371
372 Ok(8 + ndim * 8 + 8)
374 }
375
376 pub fn as_slice(&self) -> Result<&[T]> {
378 let data_offset = self.data_offset()?;
379 let data_bytes = &self.mmap[data_offset..];
380
381 if data_bytes.len() < self.len * std::mem::size_of::<T>() {
382 return Err(IoError::FormatError(
383 "Insufficient data in file".to_string(),
384 ));
385 }
386
387 Ok(bytemuck::cast_slice(
388 &data_bytes[..self.len * std::mem::size_of::<T>()],
389 ))
390 }
391
392 pub fn as_array_view(&self, shape: &[usize]) -> Result<ArrayView<T, IxDyn>> {
394 let data_slice = self.as_slice()?;
395
396 let expected_len: usize = shape.iter().product();
397 if expected_len != self.len {
398 return Err(IoError::FormatError(format!(
399 "Shape mismatch: expected {} elements, got {}",
400 expected_len, self.len
401 )));
402 }
403
404 ArrayView::from_shape(IxDyn(shape), data_slice)
405 .map_err(|e| IoError::FormatError(format!("Failed to create array view: {}", e)))
406 }
407
408 pub fn len(&self) -> usize {
410 self.len
411 }
412
413 pub fn is_empty(&self) -> bool {
415 self.len == 0
416 }
417}
418
419impl<T> MmapArrayMut<T>
420where
421 T: bytemuck::Pod,
422{
423 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
425 let file = OpenOptions::new()
426 .read(true)
427 .write(true)
428 .open(path.as_ref())
429 .map_err(|e| IoError::FileError(format!("Failed to open file: {}", e)))?;
430
431 let file_size = file
432 .metadata()
433 .map_err(|e| IoError::FileError(format!("Failed to get file size: {}", e)))?
434 .len();
435
436 if file_size < 8 {
437 return Err(IoError::FormatError(
438 "File too small to contain valid array".to_string(),
439 ));
440 }
441
442 let mmap = unsafe {
443 memmap2::MmapMut::map_mut(&file)
444 .map_err(|e| IoError::FileError(format!("Failed to create memory map: {}", e)))?
445 };
446
447 let (len_value, metadata_size) = Self::read_metadata(&mmap)?;
449
450 Ok(Self {
451 mmap,
452 _file: file,
453 len: len_value,
454 _phantom: PhantomData,
455 })
456 }
457
458 fn read_metadata(mmap: &memmap2::MmapMut) -> Result<(usize, usize)> {
460 MmapArray::<T>::read_metadata(&mmap[..])
462 }
463
464 pub fn shape(&self) -> Result<Vec<usize>> {
466 let mut offset = 0;
467
468 let ndim = u64::from_le_bytes(
470 self.mmap[offset..offset + 8]
471 .try_into()
472 .map_err(|_| IoError::FormatError("Failed to read ndim".to_string()))?,
473 ) as usize;
474 offset += 8;
475
476 let mut shape = Vec::with_capacity(ndim);
478 for _ in 0..ndim {
479 let dim = u64::from_le_bytes(
480 self.mmap[offset..offset + 8]
481 .try_into()
482 .map_err(|_| IoError::FormatError("Failed to read dimension".to_string()))?,
483 ) as usize;
484 shape.push(dim);
485 offset += 8;
486 }
487
488 Ok(shape)
489 }
490
491 fn data_offset(&self) -> Result<usize> {
493 let ndim = u64::from_le_bytes(
494 self.mmap[0..8]
495 .try_into()
496 .map_err(|_| IoError::FormatError("Failed to read ndim".to_string()))?,
497 ) as usize;
498
499 Ok(8 + ndim * 8 + 8)
501 }
502
503 pub fn as_slice_mut(&mut self) -> Result<&mut [T]> {
505 let data_offset = self.data_offset()?;
506 let data_len = self.len * std::mem::size_of::<T>();
507
508 if self.mmap.len() < data_offset + data_len {
509 return Err(IoError::FormatError(
510 "Insufficient data in file".to_string(),
511 ));
512 }
513
514 let data_bytes = &mut self.mmap[data_offset..data_offset + data_len];
515 Ok(bytemuck::cast_slice_mut(data_bytes))
516 }
517
518 pub fn as_array_view_mut(&mut self, shape: &[usize]) -> Result<ArrayViewMut<T, IxDyn>> {
520 let expected_len: usize = shape.iter().product();
521 if expected_len != self.len {
522 return Err(IoError::FormatError(format!(
523 "Shape mismatch: expected {} elements, got {}",
524 expected_len, self.len
525 )));
526 }
527
528 let data_slice = self.as_slice_mut()?;
529
530 ArrayViewMut::from_shape(IxDyn(shape), data_slice)
531 .map_err(|e| IoError::FormatError(format!("Failed to create array view: {}", e)))
532 }
533
534 pub fn flush(&self) -> Result<()> {
536 self.mmap
537 .flush()
538 .map_err(|e| IoError::FileError(format!("Failed to flush memory map: {}", e)))
539 }
540
541 pub fn len(&self) -> usize {
543 self.len
544 }
545
546 pub fn is_empty(&self) -> bool {
548 self.len == 0
549 }
550}
551
552#[allow(dead_code)]
554pub fn create_mmap_array<P, S, D, T>(path: P, array: &ArrayBase<S, D>) -> Result<()>
555where
556 P: AsRef<Path>,
557 S: scirs2_core::ndarray::Data<Elem = T>,
558 D: Dimension,
559 T: Clone + bytemuck::Pod,
560{
561 MmapArrayBuilder::new(&path).create_from_array(array)
562}
563
564#[allow(dead_code)]
566pub fn read_mmap_array<P, T>(path: P) -> Result<ArrayD<T>>
567where
568 P: AsRef<Path>,
569 T: bytemuck::Pod + Clone,
570{
571 let mmap_array = MmapArray::open(path)?;
572 let shape = mmap_array.shape()?;
573 let array_view = mmap_array.as_array_view(&shape)?;
574 Ok(array_view.to_owned())
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use scirs2_core::ndarray::{array, Array1, Array2};
581 use tempfile::tempdir;
582
583 #[test]
584 fn test_mmap_array_1d() {
585 let temp_dir = tempdir().expect("Operation failed");
586 let file_path = temp_dir.path().join("test_1d.bin");
587
588 let data = Array1::from(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
590
591 create_mmap_array(&file_path, &data).expect("Operation failed");
593
594 let mmap_array: MmapArray<f64> = MmapArray::open(&file_path).expect("Operation failed");
596 let shape = mmap_array.shape().expect("Operation failed");
597 assert_eq!(shape, vec![5]);
598
599 let array_view = mmap_array.as_array_view(&shape).expect("Operation failed");
600 assert_eq!(array_view.len(), 5);
601
602 for (i, &value) in array_view.iter().enumerate() {
603 assert_eq!(value, data[i]);
604 }
605 }
606
607 #[test]
608 fn test_mmap_array_2d() {
609 let temp_dir = tempdir().expect("Operation failed");
610 let file_path = temp_dir.path().join("test_2d.bin");
611
612 let data = array![[1.0f64, 2.0, 3.0], [4.0, 5.0, 6.0]];
614
615 create_mmap_array(&file_path, &data).expect("Operation failed");
617
618 let mmap_array: MmapArray<f64> = MmapArray::open(&file_path).expect("Operation failed");
620 let shape = mmap_array.shape().expect("Operation failed");
621 assert_eq!(shape, vec![2, 3]);
622
623 let array_view = mmap_array.as_array_view(&shape).expect("Operation failed");
624 assert_eq!(array_view.shape(), &[2, 3]);
625
626 for i in 0..2 {
628 for j in 0..3 {
629 let linear_index = i * 3 + j;
630 assert_eq!(
631 array_view.as_slice().expect("Operation failed")[linear_index],
632 data[[i, j]]
633 );
634 }
635 }
636 }
637
638 #[test]
639 fn test_mmap_array_mutable() {
640 let temp_dir = tempdir().expect("Operation failed");
641 let file_path = temp_dir.path().join("test_mut.bin");
642
643 let data: Array2<f64> = Array2::zeros((10, 10));
645
646 create_mmap_array(&file_path, &data).expect("Operation failed");
648
649 let mut mmap_array: MmapArrayMut<f64> =
651 MmapArrayMut::open(&file_path).expect("Operation failed");
652 let shape = mmap_array.shape().expect("Operation failed");
653
654 {
655 let mut array_view = mmap_array
656 .as_array_view_mut(&shape)
657 .expect("Operation failed");
658 let slice = array_view.as_slice_mut().expect("Operation failed");
660 slice[5 * 10 + 5] = 42.0; slice[10 + 2] = 13.7; }
663
664 mmap_array.flush().expect("Operation failed");
666
667 let read_array: ArrayD<f64> = read_mmap_array(&file_path).expect("Operation failed");
669 let read_slice = read_array.as_slice().expect("Operation failed");
670 assert_eq!(read_slice[5 * 10 + 5], 42.0);
671 assert_eq!(read_slice[10 + 2], 13.7);
672 assert_eq!(read_slice[0], 0.0);
673 }
674
675 #[test]
676 fn test_convenience_functions() {
677 let temp_dir = tempdir().expect("Operation failed");
678 let file_path = temp_dir.path().join("test_convenience.bin");
679
680 let original = Array2::from_shape_fn((100, 50), |(i, j)| (i + j) as f64);
682
683 create_mmap_array(&file_path, &original).expect("Operation failed");
685
686 let read_back: ArrayD<f64> = read_mmap_array(&file_path).expect("Operation failed");
688
689 assert_eq!(original.shape(), read_back.shape());
690 for (orig, read) in original.iter().zip(read_back.iter()) {
691 assert_eq!(orig, read);
692 }
693 }
694
695 #[test]
696 fn test_empty_array_creation() {
697 let temp_dir = tempdir().expect("Operation failed");
698 let file_path = temp_dir.path().join("test_empty.bin");
699
700 let shape = vec![100, 200];
702 MmapArrayBuilder::new(&file_path)
703 .create_empty::<f64>(&shape)
704 .expect("Operation failed");
705
706 let mmap_array = MmapArray::<f64>::open(&file_path).expect("Operation failed");
708 let readshape = mmap_array.shape().expect("Operation failed");
709 assert_eq!(readshape, shape);
710 assert_eq!(mmap_array.len(), 100 * 200);
711
712 let array_view = mmap_array.as_array_view(&shape).expect("Operation failed");
713 for &value in array_view.iter() {
714 assert_eq!(value, 0.0);
715 }
716 }
717}