1use super::chunked::ChunkingStrategy;
9use super::memmap::{AccessMode, MemoryMappedArray};
10use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
11use crate::ndarray;
13use num_traits::Zero;
14use std::ops::{Add, Div, Mul, Sub};
15
16pub trait ZeroCopyOps<A: Clone + Copy + 'static + Send + Sync> {
23 fn map_zero_copy<F>(&self, f: F) -> CoreResult<MemoryMappedArray<A>>
45 where
46 F: Fn(A) -> A + Send + Sync;
47
48 fn reduce_zero_copy<F>(&self, init: A, f: F) -> CoreResult<A>
71 where
72 F: Fn(A, A) -> A + Send + Sync;
73
74 fn combine_zero_copy<F>(&self, other: &Self, f: F) -> CoreResult<MemoryMappedArray<A>>
98 where
99 F: Fn(A, A) -> A + Send + Sync;
100
101 fn filter_zero_copy<F>(&self, predicate: F) -> CoreResult<Vec<A>>
122 where
123 F: Fn(&A) -> bool + Send + Sync;
124
125 fn max_zero_copy(&self) -> CoreResult<A>
139 where
140 A: PartialOrd;
141
142 fn min_zero_copy(&self) -> CoreResult<A>
156 where
157 A: PartialOrd;
158
159 fn sum_zero_copy(&self) -> CoreResult<A>
173 where
174 A: Add<Output = A> + From<u8>;
175
176 fn product_zero_copy(&self) -> CoreResult<A>
190 where
191 A: Mul<Output = A> + From<u8>;
192
193 fn mean_zero_copy(&self) -> CoreResult<A>
207 where
208 A: Add<Output = A> + Div<Output = A> + From<u8> + From<usize>;
209}
210
211impl<A: Clone + Copy + 'static + Send + Sync + Send + Sync + Zero> ZeroCopyOps<A>
212 for MemoryMappedArray<A>
213{
214 fn map_zero_copy<F>(&self, f: F) -> CoreResult<MemoryMappedArray<A>>
215 where
216 F: Fn(A) -> A + Send + Sync,
217 {
218 let temp_file = tempfile::NamedTempFile::new()?;
220 let temp_path = temp_file.path().to_path_buf();
221
222 let shape = &self.shape;
224 let element_size = std::mem::size_of::<A>();
225 let file_size = self.size * element_size;
226
227 temp_file.as_file().set_len(file_size as u64)?;
229 drop(temp_file); let zeros = crate::ndarray::ArrayD::zeros(crate::ndarray::IxDyn(&self.shape));
234 {
235 let _ = MemoryMappedArray::<A>::new::<
236 crate::ndarray::OwnedRepr<A>,
237 crate::ndarray::IxDyn,
238 >(Some(&zeros), &temp_path, AccessMode::Write, 0)?;
239 }
240
241 let mut output = MemoryMappedArray::<A>::new::<
243 crate::ndarray::OwnedRepr<A>,
244 crate::ndarray::IxDyn,
245 >(None, &temp_path, AccessMode::ReadWrite, 0)?;
246
247 #[cfg(feature = "parallel")]
249 {
250 let chunk_size = (self.size / rayon::current_num_threads()).max(1024);
254
255 let num_chunks = self.size.div_ceil(chunk_size);
257
258 let array = self.as_array::<crate::ndarray::IxDyn>()?;
260 let mut out_array = output.as_array_mut::<crate::ndarray::IxDyn>()?;
261
262 for chunk_idx in 0..num_chunks {
263 let start = chunk_idx * chunk_size;
265 let end = (start + chunk_size).min(self.size);
266
267 let slice = array.as_slice().ok_or_else(|| {
269 CoreError::ValidationError(
270 ErrorContext::new("Array is not contiguous in memory".to_string())
271 .with_location(ErrorLocation::new(file!(), line!())),
272 )
273 })?;
274 let chunk = &slice[start..end];
275
276 let mapped_chunk: Vec<A> = chunk.iter().map(|&x| f(x)).collect();
278
279 let out_slice_full = out_array.as_slice_mut().ok_or_else(|| {
281 CoreError::ValidationError(
282 ErrorContext::new("Output array is not contiguous in memory".to_string())
283 .with_location(ErrorLocation::new(file!(), line!())),
284 )
285 })?;
286 let out_slice = &mut out_slice_full[start..end];
287
288 out_slice.copy_from_slice(&mapped_chunk);
290 }
291 }
292
293 #[cfg(not(feature = "parallel"))]
294 {
295 let chunk_size = 1024 * 1024; let strategy = ChunkingStrategy::Fixed(chunk_size);
299
300 for chunk_idx in 0..self.size.div_ceil(chunk_size) {
302 let start = chunk_idx * chunk_size;
304 let end = (start + chunk_size).min(self.size);
305
306 let array = self.as_array::<crate::ndarray::IxDyn>()?;
308 let slice = array.as_slice().ok_or_else(|| {
309 CoreError::ValidationError(
310 ErrorContext::new("Array is not contiguous in memory".to_string())
311 .with_location(ErrorLocation::new(file!(), line!())),
312 )
313 })?;
314 let chunk = &slice[start..end];
315
316 let mapped_chunk: Vec<A> = chunk.iter().map(|&x| f(x)).collect();
318
319 let mut out_array = output.as_array_mut::<crate::ndarray::IxDyn>()?;
322 let out_slice_full = out_array.as_slice_mut().ok_or_else(|| {
323 CoreError::ValidationError(
324 ErrorContext::new("Output array is not contiguous in memory".to_string())
325 .with_location(ErrorLocation::new(file!(), line!())),
326 )
327 })?;
328 let out_slice = &mut out_slice_full[start..end];
329
330 out_slice.copy_from_slice(&mapped_chunk);
332 }
333 }
334
335 Ok(output)
336 }
337
338 fn reduce_zero_copy<F>(&self, init: A, f: F) -> CoreResult<A>
339 where
340 F: Fn(A, A) -> A + Send + Sync,
341 {
342 let chunk_size = 1024 * 1024; let strategy = ChunkingStrategy::Fixed(chunk_size);
345
346 let num_chunks = self.size.div_ceil(chunk_size);
348 let mut chunk_results = Vec::with_capacity(num_chunks);
349
350 for chunk_idx in 0..num_chunks {
352 let start = chunk_idx * chunk_size;
354 let end = (start + chunk_size).min(self.size);
355
356 let array = self.as_array::<crate::ndarray::IxDyn>()?;
358 let slice = array.as_slice().ok_or_else(|| {
359 CoreError::ValidationError(
360 ErrorContext::new("Array is not contiguous in memory".to_string())
361 .with_location(ErrorLocation::new(file!(), line!())),
362 )
363 })?;
364 let chunk = &slice[start..end];
365
366 let chunk_result = chunk.iter().fold(init, |acc, &x| f(acc, x));
368 chunk_results.push(chunk_result);
369 }
370
371 let final_result = chunk_results.into_iter().fold(init, f);
373
374 Ok(final_result)
375 }
376
377 fn combine_zero_copy<F>(&self, other: &Self, f: F) -> CoreResult<MemoryMappedArray<A>>
378 where
379 F: Fn(A, A) -> A + Send + Sync,
380 {
381 if self.shape != other.shape {
383 return Err(CoreError::ShapeError(ErrorContext::new(format!(
384 "Arrays have different shapes: {:?} vs {:?}",
385 self.shape, other.shape
386 ))));
387 }
388
389 let temp_file = tempfile::NamedTempFile::new()?;
391 let temp_path = temp_file.path().to_path_buf();
392
393 let shape = &self.shape;
395 let element_size = std::mem::size_of::<A>();
396 let file_size = self.size * element_size;
397
398 temp_file.as_file().set_len(file_size as u64)?;
400 drop(temp_file); let zeros = crate::ndarray::ArrayD::zeros(crate::ndarray::IxDyn(&self.shape));
405 {
406 let _ = MemoryMappedArray::<A>::new::<
407 crate::ndarray::OwnedRepr<A>,
408 crate::ndarray::IxDyn,
409 >(Some(&zeros), &temp_path, AccessMode::Write, 0)?;
410 }
411
412 let mut output = MemoryMappedArray::<A>::new::<
414 crate::ndarray::OwnedRepr<A>,
415 crate::ndarray::IxDyn,
416 >(None, &temp_path, AccessMode::ReadWrite, 0)?;
417
418 let chunk_size = 1024 * 1024; let strategy = ChunkingStrategy::Fixed(chunk_size);
421
422 let num_chunks = self.size.div_ceil(chunk_size);
424
425 for chunk_idx in 0..num_chunks {
427 let start = chunk_idx * chunk_size;
429 let end = (start + chunk_size).min(self.size);
430 let len = end - start;
431
432 let self_array = self.as_array::<crate::ndarray::IxDyn>()?;
434 let other_array = other.as_array::<crate::ndarray::IxDyn>()?;
435
436 let self_slice = self_array.as_slice().ok_or_else(|| {
437 CoreError::ValidationError(
438 ErrorContext::new("Self array is not contiguous in memory".to_string())
439 .with_location(ErrorLocation::new(file!(), line!())),
440 )
441 })?;
442 let other_slice = other_array.as_slice().ok_or_else(|| {
443 CoreError::ValidationError(
444 ErrorContext::new("Other array is not contiguous in memory".to_string())
445 .with_location(ErrorLocation::new(file!(), line!())),
446 )
447 })?;
448 let self_chunk = &self_slice[start..end];
449 let other_chunk = &other_slice[start..end];
450
451 let mut result_chunk = Vec::with_capacity(len);
453 for i in 0..len {
454 result_chunk.push(f(self_chunk[i], other_chunk[i]));
455 }
456
457 let mut out_array = output.as_array_mut::<crate::ndarray::IxDyn>()?;
459 let out_slice_full = out_array.as_slice_mut().ok_or_else(|| {
460 CoreError::ValidationError(
461 ErrorContext::new("Output array is not contiguous in memory".to_string())
462 .with_location(ErrorLocation::new(file!(), line!())),
463 )
464 })?;
465 let out_slice = &mut out_slice_full[start..end];
466 out_slice.copy_from_slice(&result_chunk);
467 }
468
469 Ok(output)
470 }
471
472 fn filter_zero_copy<F>(&self, predicate: F) -> CoreResult<Vec<A>>
473 where
474 F: Fn(&A) -> bool + Send + Sync,
475 {
476 let chunk_size = 1024 * 1024; let num_chunks = self.size.div_ceil(chunk_size);
479 let mut result = Vec::new();
480
481 for chunk_idx in 0..num_chunks {
483 let start = chunk_idx * chunk_size;
485 let end = (start + chunk_size).min(self.size);
486
487 let array = self.as_array::<crate::ndarray::IxDyn>()?;
489 let array_slice = array.as_slice().ok_or_else(|| {
490 CoreError::ValidationError(
491 ErrorContext::new("Array is not contiguous in memory".to_string())
492 .with_location(ErrorLocation::new(file!(), line!())),
493 )
494 })?;
495 let slice = &array_slice[start..end];
496
497 let filtered_chunk = slice
499 .iter()
500 .filter(|&x| predicate(x))
501 .cloned()
502 .collect::<Vec<A>>();
503
504 result.extend(filtered_chunk);
506 }
507
508 Ok(result)
509 }
510
511 fn max_zero_copy(&self) -> CoreResult<A>
512 where
513 A: PartialOrd,
514 {
515 if self.size == 0 {
517 return Err(CoreError::ValueError(ErrorContext::new(
518 "Array is empty".to_string(),
519 )));
520 }
521
522 let first_element = {
524 let array = self.as_array::<crate::ndarray::IxDyn>()?;
525 let slice = array.as_slice().ok_or_else(|| {
526 CoreError::ValidationError(
527 ErrorContext::new("Array is not contiguous in memory".to_string())
528 .with_location(ErrorLocation::new(file!(), line!())),
529 )
530 })?;
531 slice[0]
532 };
533
534 self.reduce_zero_copy(first_element, |acc, x| if x > acc { x } else { acc })
536 }
537
538 fn min_zero_copy(&self) -> CoreResult<A>
539 where
540 A: PartialOrd,
541 {
542 if self.size == 0 {
544 return Err(CoreError::ValueError(ErrorContext::new(
545 "Array is empty".to_string(),
546 )));
547 }
548
549 let first_element = {
551 let array = self.as_array::<crate::ndarray::IxDyn>()?;
552 let slice = array.as_slice().ok_or_else(|| {
553 CoreError::ValidationError(
554 ErrorContext::new("Array is not contiguous in memory".to_string())
555 .with_location(ErrorLocation::new(file!(), line!())),
556 )
557 })?;
558 slice[0]
559 };
560
561 self.reduce_zero_copy(first_element, |acc, x| if x < acc { x } else { acc })
563 }
564
565 fn sum_zero_copy(&self) -> CoreResult<A>
566 where
567 A: Add<Output = A> + From<u8>,
568 {
569 let zero = A::from(0u8);
571
572 self.reduce_zero_copy(zero, |acc, x| acc + x)
574 }
575
576 fn product_zero_copy(&self) -> CoreResult<A>
577 where
578 A: Mul<Output = A> + From<u8>,
579 {
580 if self.size == 0 {
582 return Err(CoreError::ValueError(ErrorContext::new(
583 "Array is empty".to_string(),
584 )));
585 }
586
587 let one = A::from(1u8);
589
590 self.reduce_zero_copy(one, |acc, x| acc * x)
592 }
593
594 fn mean_zero_copy(&self) -> CoreResult<A>
595 where
596 A: Add<Output = A> + Div<Output = A> + From<u8> + From<usize>,
597 {
598 if self.size == 0 {
600 return Err(CoreError::ValueError(ErrorContext::new(
601 "Array is empty".to_string(),
602 )));
603 }
604
605 let sum = self.sum_zero_copy()?;
607
608 let count = A::from(self.size);
610
611 Ok(sum / count)
612 }
613}
614
615pub trait BroadcastOps<A: Clone + Copy + 'static + Send + Sync> {
620 fn broadcast_op<F>(&self, other: &Self, f: F) -> CoreResult<MemoryMappedArray<A>>
647 where
648 F: Fn(A, A) -> A + Send + Sync;
649}
650
651impl<A: Clone + Copy + 'static + Send + Sync + Send + Sync + Zero> BroadcastOps<A>
652 for MemoryMappedArray<A>
653{
654 fn broadcast_op<F>(&self, other: &Self, f: F) -> CoreResult<MemoryMappedArray<A>>
655 where
656 F: Fn(A, A) -> A + Send + Sync,
657 {
658 let selfshape = &self.shape;
660 let othershape = &other.shape;
661
662 let self_ndim = selfshape.len();
664 let other_ndim = othershape.len();
665 let output_ndim = std::cmp::max(self_ndim, other_ndim);
666
667 let mut self_dims = Vec::with_capacity(output_ndim);
669 let mut other_dims = Vec::with_capacity(output_ndim);
670
671 self_dims.resize(output_ndim - self_ndim, 1);
673 for dim in selfshape.iter() {
674 self_dims.push(*dim);
675 }
676
677 other_dims.resize(output_ndim - other_ndim, 1);
678 for dim in othershape.iter() {
679 other_dims.push(*dim);
680 }
681
682 let mut outputshape = Vec::with_capacity(output_ndim);
684 for i in 0..output_ndim {
685 #[allow(clippy::if_same_then_else)]
686 if self_dims[i] == 1 {
687 outputshape.push(other_dims[i]);
688 } else if other_dims[i] == 1 {
689 outputshape.push(self_dims[i]);
690 } else if self_dims[i] == other_dims[i] {
691 outputshape.push(self_dims[i]);
692 } else {
693 return Err(CoreError::ValueError(ErrorContext::new(format!(
694 "Arrays cannot be broadcast together with shapes {selfshape:?} and {othershape:?}"
695 ))));
696 }
697 }
698
699 let temp_file = tempfile::NamedTempFile::new()?;
701 let temp_path = temp_file.path().to_path_buf();
702
703 let output_size = outputshape.iter().product::<usize>();
705 let element_size = std::mem::size_of::<A>();
706 let file_size = output_size * element_size;
707
708 temp_file.as_file().set_len(file_size as u64)?;
710 drop(temp_file); let zeros = crate::ndarray::ArrayD::zeros(crate::ndarray::IxDyn(&outputshape));
715 {
716 let _ = MemoryMappedArray::<A>::new::<
717 crate::ndarray::OwnedRepr<A>,
718 crate::ndarray::IxDyn,
719 >(Some(&zeros), &temp_path, AccessMode::Write, 0)?;
720 }
721
722 let mut output = MemoryMappedArray::<A>::new::<
724 crate::ndarray::OwnedRepr<A>,
725 crate::ndarray::IxDyn,
726 >(None, &temp_path, AccessMode::ReadWrite, 0)?;
727
728 let self_array = self.as_array::<crate::ndarray::IxDyn>()?;
730 let other_array = other.as_array::<crate::ndarray::IxDyn>()?;
731
732 let self_view = self_array.view();
734 let other_view = other_array.view();
735
736 let mut output_array = output.as_array_mut::<crate::ndarray::IxDyn>()?;
738
739 crate::ndarray::Zip::from(&mut output_array)
741 .and_broadcast(&self_view)
742 .and_broadcast(&other_view)
743 .for_each(|out, &a, &b| {
744 *out = f(a, b);
745 });
746
747 Ok(output)
748 }
749}
750
751pub trait ArithmeticOps<A: Clone + Copy + 'static + Send + Sync> {
757 fn add(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
767 where
768 A: Add<Output = A>;
769
770 fn sub(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
780 where
781 A: Sub<Output = A>;
782
783 fn mul(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
793 where
794 A: Mul<Output = A>;
795
796 fn div(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
806 where
807 A: Div<Output = A>;
808}
809
810impl<A: Clone + Copy + 'static + Send + Sync + Send + Sync + Zero> ArithmeticOps<A>
811 for MemoryMappedArray<A>
812{
813 fn add(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
814 where
815 A: Add<Output = A>,
816 {
817 self.combine_zero_copy(other, |a, b| a + b)
818 }
819
820 fn sub(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
821 where
822 A: Sub<Output = A>,
823 {
824 self.combine_zero_copy(other, |a, b| a - b)
825 }
826
827 fn mul(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
828 where
829 A: Mul<Output = A>,
830 {
831 self.combine_zero_copy(other, |a, b| a * b)
832 }
833
834 fn div(&self, other: &Self) -> CoreResult<MemoryMappedArray<A>>
835 where
836 A: Div<Output = A>,
837 {
838 self.combine_zero_copy(other, |a, b| a / b)
839 }
840}
841
842#[cfg(test)]
843mod tests {
844 use super::*;
845 use ::ndarray::Array2;
846 use std::fs::File;
847 use std::io::Write;
848 use tempfile::tempdir;
849
850 #[test]
851 fn test_map_zero_copy() {
852 let dir = tempdir().expect("Operation failed");
854 let file_path = dir.path().join("test_map.bin");
855
856 let data = crate::ndarray::Array1::from_vec((0..1000).map(|i| i as f64).collect());
858 MemoryMappedArray::<f64>::save_array(&data, &file_path, None).expect("Operation failed");
859
860 let mmap = MemoryMappedArray::<f64>::open_zero_copy(&file_path, AccessMode::ReadOnly)
862 .expect("Operation failed");
863
864 let result = mmap.map_zero_copy(|x| x * 2.0).expect("Operation failed");
866
867 let result_array = result
869 .readonlyarray::<crate::ndarray::Ix1>()
870 .expect("Operation failed");
871 for i in 0..1000 {
872 assert_eq!(result_array[i], (i as f64) * 2.0);
873 }
874 }
875
876 #[test]
877 fn test_reduce_zero_copy() {
878 let dir = tempdir().expect("Operation failed");
880 let file_path = dir.path().join("test_reduce.bin");
881
882 let data: Vec<f64> = (0..1000).map(|i| i as f64).collect();
884 let mut file = File::create(&file_path).expect("Operation failed");
885 for val in &data {
886 file.write_all(&val.to_ne_bytes())
887 .expect("Operation failed");
888 }
889 drop(file);
890
891 let mmap = MemoryMappedArray::<f64>::path(&file_path, &[1000]).expect("Operation failed");
893
894 let sum = mmap
896 .reduce_zero_copy(0.0, |acc, x| acc + x)
897 .expect("Operation failed");
898
899 assert_eq!(sum, 499500.0);
901 }
902
903 #[test]
904 fn test_combine_zero_copy() {
905 let dir = tempdir().expect("Operation failed");
907 let file_path1 = dir.path().join("test_combine1.bin");
908 let file_path2 = dir.path().join("test_combine2.bin");
909
910 let data1 = crate::ndarray::Array1::from_vec((0..1000).map(|i| i as f64).collect());
912 let data2 = crate::ndarray::Array1::from_vec((0..1000).map(|i| (i * 2) as f64).collect());
913
914 MemoryMappedArray::<f64>::save_array(&data1, &file_path1, None).expect("Operation failed");
915 MemoryMappedArray::<f64>::save_array(&data2, &file_path2, None).expect("Operation failed");
916
917 let mmap1 = MemoryMappedArray::<f64>::open_zero_copy(&file_path1, AccessMode::ReadOnly)
919 .expect("Operation failed");
920 let mmap2 = MemoryMappedArray::<f64>::open_zero_copy(&file_path2, AccessMode::ReadOnly)
921 .expect("Operation failed");
922
923 let result = mmap1
925 .combine_zero_copy(&mmap2, |a, b| a + b)
926 .expect("Operation failed");
927
928 let result_array = result
930 .readonlyarray::<crate::ndarray::Ix1>()
931 .expect("Operation failed");
932 for i in 0..1000 {
933 assert_eq!(result_array[i], (i as f64) * 3.0);
934 }
935 }
936
937 #[test]
938 fn test_filter_zero_copy() {
939 let dir = tempdir().expect("Operation failed");
941 let file_path = dir.path().join("test_filter.bin");
942
943 let data: Vec<f64> = (0..1000).map(|i| i as f64).collect();
945 let mut file = File::create(&file_path).expect("Operation failed");
946 for val in &data {
947 file.write_all(&val.to_ne_bytes())
948 .expect("Operation failed");
949 }
950 drop(file);
951
952 let mmap = MemoryMappedArray::<f64>::path(&file_path, &[1000]).expect("Operation failed");
954
955 let even_numbers = mmap
957 .filter_zero_copy(|&x| (x as usize) % 2 == 0)
958 .expect("Operation failed");
959
960 assert_eq!(even_numbers.len(), 500);
962 for (i, val) in even_numbers.iter().enumerate() {
963 assert_eq!(*val, (i * 2) as f64);
964 }
965 }
966
967 #[test]
968 fn test_arithmetic_ops() {
969 let dir = tempdir().expect("Operation failed");
971 let file_path1 = dir.path().join("test_arithmetic1.bin");
972 let file_path2 = dir.path().join("test_arithmetic2.bin");
973
974 let data1 = crate::ndarray::Array1::from_vec((0..100).map(|i| i as f64).collect());
976 let data2 = crate::ndarray::Array1::from_vec((0..100).map(|i| (i + 5) as f64).collect());
977
978 MemoryMappedArray::<f64>::save_array(&data1, &file_path1, None).expect("Operation failed");
979 MemoryMappedArray::<f64>::save_array(&data2, &file_path2, None).expect("Operation failed");
980
981 let mmap1 = MemoryMappedArray::<f64>::open_zero_copy(&file_path1, AccessMode::ReadOnly)
983 .expect("Operation failed");
984 let mmap2 = MemoryMappedArray::<f64>::open_zero_copy(&file_path2, AccessMode::ReadOnly)
985 .expect("Operation failed");
986
987 let add_result = mmap1.add(&mmap2).expect("Operation failed");
989 let add_array = add_result
990 .readonlyarray::<crate::ndarray::Ix1>()
991 .expect("Operation failed");
992 for i in 0..100 {
993 assert_eq!(add_array[i], (i as f64) + ((i + 5) as f64));
994 }
995
996 let sub_result = mmap1.sub(&mmap2).expect("Operation failed");
998 let sub_array = sub_result
999 .readonlyarray::<crate::ndarray::Ix1>()
1000 .expect("Operation failed");
1001 for i in 0..100 {
1002 assert_eq!(sub_array[i], (i as f64) - ((i + 5) as f64));
1003 }
1004
1005 let mul_result = mmap1.mul(&mmap2).expect("Operation failed");
1007 let mul_array = mul_result
1008 .readonlyarray::<crate::ndarray::Ix1>()
1009 .expect("Operation failed");
1010 for i in 0..100 {
1011 assert_eq!(mul_array[i], (i as f64) * ((i + 5) as f64));
1012 }
1013
1014 let div_result = mmap2
1016 .div(&mmap1.map_zero_copy(|x| x + 1.0).expect("Operation failed"))
1017 .expect("Test: operation failed");
1018 let div_array = div_result
1019 .readonlyarray::<crate::ndarray::Ix1>()
1020 .expect("Operation failed");
1021 for i in 0..100 {
1022 assert_eq!(div_array[i], ((i + 5) as f64) / ((i + 1) as f64));
1023 }
1024 }
1025
1026 #[test]
1027 fn test_broadcast_op() {
1028 let dir = tempdir().expect("Operation failed");
1030 let file_path1 = dir.path().join("test_broadcast1.bin");
1031 let file_path2 = dir.path().join("test_broadcast2.bin");
1032
1033 let data1 = Array2::<f64>::from_shape_fn((3, 4), |(i, j)| (i * 4 + j) as f64);
1035 let data2 = crate::ndarray::Array1::from_vec((0..4).map(|i| (i + 1) as f64).collect());
1036
1037 MemoryMappedArray::<f64>::save_array(&data1, &file_path1, None).expect("Operation failed");
1039 MemoryMappedArray::<f64>::save_array(&data2, &file_path2, None).expect("Operation failed");
1040
1041 let mmap1 = MemoryMappedArray::<f64>::open_zero_copy(&file_path1, AccessMode::ReadOnly)
1043 .expect("Operation failed");
1044 let mmap2 = MemoryMappedArray::<f64>::open_zero_copy(&file_path2, AccessMode::ReadOnly)
1045 .expect("Operation failed");
1046
1047 let result = mmap1
1049 .broadcast_op(&mmap2, |a, b| a * b)
1050 .expect("Operation failed");
1051
1052 let result_array = result
1054 .readonlyarray::<crate::ndarray::Ix2>()
1055 .expect("Operation failed");
1056 assert_eq!(result_array.shape(), &[3, 4]);
1057
1058 for i in 0..3 {
1059 for j in 0..4 {
1060 let expected = (i * 4 + j) as f64 * (j + 1) as f64;
1061 assert_eq!(result_array[[i, j]], expected);
1062 }
1063 }
1064 }
1065}