Skip to main content

scirs2_io/
mmap.rs

1//! Memory-mapped file I/O for large arrays
2//!
3//! This module provides memory-mapped file operations for efficient handling of large arrays
4//! without loading them entirely into memory. Memory mapping is particularly useful for:
5//!
6//! - Processing arrays larger than available RAM
7//! - Random access patterns across large datasets
8//! - Sharing data between multiple processes
9//! - Minimizing memory usage for read-only operations
10//! - Fast startup times for large files
11//!
12//! ## Features
13//!
14//! - **Memory-Mapped Arrays**: Read arrays from files using memory mapping
15//! - **Multi-dimensional Support**: Handle 1D, 2D, and N-dimensional arrays
16//! - **Type Safety**: Generic support for different numeric types
17//! - **Cross-platform**: Works on Unix and Windows systems
18//! - **Performance Optimized**: Minimal memory overhead and fast access
19//! - **Error Handling**: Comprehensive error handling for I/O operations
20//!
21//! ## Examples
22//!
23//! ```rust,no_run
24//! use scirs2_io::mmap::{MmapArray, create_mmap_array};
25//! use scirs2_core::ndarray::Array2;
26//! use std::path::Path;
27//!
28//! // Create a large array file
29//! let data = Array2::from_shape_fn((1000, 1000), |(i, j)| (i + j) as f64);
30//! let file_path = Path::new("large_array.bin");
31//!
32//! // Write array to file
33//! create_mmap_array(file_path, &data)?;
34//!
35//! // Memory-map the array for reading
36//! let mmap_array: MmapArray<f64> = MmapArray::open(file_path)?;
37//! let shape = mmap_array.shape()?;
38//! let array_view = mmap_array.as_array_view(&shape)?;
39//!
40//! // Access data without loading entire file into memory
41//! let slice = mmap_array.as_slice()?;
42//! let value = slice[500 * 1000 + 500]; // Access element at (500, 500)
43//! println!("Value at (500, 500): {}", value);
44//! # Ok::<(), scirs2_io::error::IoError>(())
45//! ```
46
47use 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
54/// Memory-mapped array that provides efficient access to large datasets
55pub struct MmapArray<T> {
56    /// Memory-mapped region
57    mmap: memmap2::Mmap,
58    /// File handle
59    _file: File,
60    /// Total number of elements
61    len: usize,
62    /// Phantom data for type safety
63    _phantom: PhantomData<T>,
64}
65
66/// Mutable memory-mapped array for read-write access
67pub struct MmapArrayMut<T> {
68    /// Mutable memory-mapped region
69    mmap: memmap2::MmapMut,
70    /// File handle
71    _file: File,
72    /// Total number of elements
73    len: usize,
74    /// Phantom data for type safety
75    _phantom: PhantomData<T>,
76}
77
78/// Builder for creating memory-mapped arrays
79pub struct MmapArrayBuilder<'a> {
80    /// Path to the file
81    path: &'a Path,
82    /// Whether to create the file if it doesn't exist
83    create: bool,
84    /// Whether to truncate the file if it exists
85    truncate: bool,
86    /// Buffer size for I/O operations
87    buffer_size: usize,
88}
89
90/// Configuration for memory-mapped array operations
91#[derive(Debug, Clone, Default)]
92pub struct MmapConfig {
93    /// Enable read-ahead prefetching
94    pub prefetch: bool,
95    /// Page size for memory mapping (None for system default)
96    pub page_size: Option<usize>,
97    /// Whether to use sequential access pattern hints
98    pub sequential: bool,
99    /// Whether to use random access pattern hints
100    pub random: bool,
101}
102
103impl<'a> MmapArrayBuilder<'a> {
104    /// Create a new builder for the specified file path
105    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, // 64KB default buffer
111        }
112    }
113
114    /// Set whether to create the file if it doesn't exist
115    pub fn create(mut self, create: bool) -> Self {
116        self.create = create;
117        self
118    }
119
120    /// Set whether to truncate the file if it exists
121    pub fn truncate(mut self, truncate: bool) -> Self {
122        self.truncate = truncate;
123        self
124    }
125
126    /// Set the buffer size for I/O operations
127    pub fn buffer_size(mut self, size: usize) -> Self {
128        self.buffer_size = size;
129        self
130    }
131
132    /// Create a memory-mapped array from an existing ndarray
133    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        // Write array metadata (shape, stride, element count)
147        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        // Write element size
159        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        // Write array data in chunks to avoid memory pressure
164        if array.is_standard_layout() {
165            // For contiguous arrays, we can write directly
166            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            // For non-contiguous arrays, we need to copy to a contiguous buffer
177            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    /// Create an empty memory-mapped array with the specified shape
197    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        // Write metadata
209        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        // Write zeros for the data
224        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    /// Open an existing memory-mapped array file for reading
249    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        // Read metadata to determine array size
270        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    /// Read metadata from the memory-mapped file
281    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        // Read number of dimensions
289        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        // Read shape
303        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        // Read element size
318        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    /// Get the shape of the array from the file metadata
338    pub fn shape(&self) -> Result<Vec<usize>> {
339        let mut offset = 0;
340
341        // Read number of dimensions
342        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        // Read shape
350        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    /// Get the data offset in the file (after metadata)
365    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        // 8 bytes for ndim + 8 bytes per dimension + 8 bytes for element size
373        Ok(8 + ndim * 8 + 8)
374    }
375
376    /// Get a slice view of the raw data
377    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    /// Create an ndarray view of the memory-mapped data
393    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    /// Get the number of elements in the array
409    pub fn len(&self) -> usize {
410        self.len
411    }
412
413    /// Check if the array is empty
414    pub fn is_empty(&self) -> bool {
415        self.len == 0
416    }
417}
418
419impl<T> MmapArrayMut<T>
420where
421    T: bytemuck::Pod,
422{
423    /// Open an existing memory-mapped array file for read-write access
424    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        // Read metadata to determine array size
448        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    /// Read metadata from the memory-mapped file
459    fn read_metadata(mmap: &memmap2::MmapMut) -> Result<(usize, usize)> {
460        // Similar to read-only version
461        MmapArray::<T>::read_metadata(&mmap[..])
462    }
463
464    /// Get the shape of the array from the file metadata
465    pub fn shape(&self) -> Result<Vec<usize>> {
466        let mut offset = 0;
467
468        // Read number of dimensions
469        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        // Read shape
477        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    /// Get the data offset in the file (after metadata)
492    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        // 8 bytes for ndim + 8 bytes per dimension + 8 bytes for element size
500        Ok(8 + ndim * 8 + 8)
501    }
502
503    /// Get a mutable slice view of the raw data
504    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    /// Create a mutable ndarray view of the memory-mapped data
519    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    /// Flush changes to disk
535    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    /// Get the number of elements in the array
542    pub fn len(&self) -> usize {
543        self.len
544    }
545
546    /// Check if the array is empty
547    pub fn is_empty(&self) -> bool {
548        self.len == 0
549    }
550}
551
552/// Convenience function to create a memory-mapped array from an ndarray
553#[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/// Convenience function to read a memory-mapped array as an ndarray
565#[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        // Create test data
589        let data = Array1::from(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
590
591        // Write to file
592        create_mmap_array(&file_path, &data).expect("Operation failed");
593
594        // Read back
595        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        // Create test data
613        let data = array![[1.0f64, 2.0, 3.0], [4.0, 5.0, 6.0]];
614
615        // Write to file
616        create_mmap_array(&file_path, &data).expect("Operation failed");
617
618        // Read back
619        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        // Access individual elements using linear indexing
627        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        // Create test data
644        let data: Array2<f64> = Array2::zeros((10, 10));
645
646        // Write to file
647        create_mmap_array(&file_path, &data).expect("Operation failed");
648
649        // Open for writing
650        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            // Modify some values using linear indexing
659            let slice = array_view.as_slice_mut().expect("Operation failed");
660            slice[5 * 10 + 5] = 42.0; // (5, 5) in row-major order
661            slice[10 + 2] = 13.7; // (1, 2) in row-major order
662        }
663
664        // Flush changes
665        mmap_array.flush().expect("Operation failed");
666
667        // Read back and verify
668        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        // Create test data
681        let original = Array2::from_shape_fn((100, 50), |(i, j)| (i + j) as f64);
682
683        // Write using convenience function
684        create_mmap_array(&file_path, &original).expect("Operation failed");
685
686        // Read using convenience function
687        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        // Create empty array
701        let shape = vec![100, 200];
702        MmapArrayBuilder::new(&file_path)
703            .create_empty::<f64>(&shape)
704            .expect("Operation failed");
705
706        // Verify it was created correctly
707        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}