1use std::any::{Any, TypeId};
13use std::collections::HashMap;
14use std::fmt::Debug;
15
16use crate::array_protocol::{ArrayFunction, ArrayProtocol, DistributedArray, NotImplemented};
17use crate::error::CoreResult;
18use ::ndarray::{Array, Axis, Dimension, Slice};
19
20#[derive(Debug, Clone, Default)]
22pub struct DistributedConfig {
23 pub chunks: usize,
25
26 pub balance: bool,
28
29 pub strategy: DistributionStrategy,
31
32 pub backend: DistributedBackend,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum DistributionStrategy {
39 RowWise,
41
42 ColumnWise,
44
45 Blocks,
47
48 Auto,
50}
51
52impl Default for DistributionStrategy {
53 fn default() -> Self {
54 Self::Auto
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum DistributedBackend {
61 Threaded,
63
64 MPI,
66
67 TCP,
69}
70
71impl Default for DistributedBackend {
72 fn default() -> Self {
73 Self::Threaded
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct ArrayChunk<T, D>
80where
81 T: Clone + 'static,
82 D: Dimension + 'static,
83{
84 pub data: Array<T, D>,
86
87 pub global_index: Vec<usize>,
89
90 pub nodeid: usize,
92}
93
94pub struct DistributedNdarray<T, D>
96where
97 T: Clone + 'static,
98 D: Dimension + 'static,
99{
100 pub config: DistributedConfig,
102
103 chunks: Vec<ArrayChunk<T, D>>,
105
106 shape: Vec<usize>,
108
109 id: String,
111}
112
113impl<T, D> Debug for DistributedNdarray<T, D>
114where
115 T: Clone + Debug + 'static,
116 D: Dimension + Debug + 'static,
117{
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("DistributedNdarray")
120 .field("config", &self.config)
121 .field("chunks", &self.chunks.len())
122 .field("shape", &self.shape)
123 .field("id", &self.id)
124 .finish()
125 }
126}
127
128impl<T, D> DistributedNdarray<T, D>
129where
130 T: Clone + Send + Sync + 'static + num_traits::Zero + std::ops::Div<f64, Output = T> + Default,
131 D: Dimension + Clone + Send + Sync + 'static + crate::ndarray::RemoveAxis,
132{
133 #[must_use]
135 pub fn new(
136 chunks: Vec<ArrayChunk<T, D>>,
137 shape: Vec<usize>,
138 config: DistributedConfig,
139 ) -> Self {
140 let uuid = uuid::Uuid::new_v4();
141 let id = format!("uuid_{uuid}");
142 Self {
143 config,
144 chunks,
145 shape,
146 id,
147 }
148 }
149
150 #[must_use]
160 pub fn from_array(array: &Array<T, D>, config: DistributedConfig) -> Self
161 where
162 T: Clone,
163 {
164 let shape = array.shape().to_vec();
165 let num_chunks = config.chunks.max(1);
166 let axis0_len = shape.first().copied().unwrap_or(1).max(1);
167 let rows_per_chunk = axis0_len.div_ceil(num_chunks).max(1);
168
169 let mut chunks = Vec::new();
170 let mut start = 0usize;
171 let mut nodeid = 0usize;
172 while start < axis0_len {
173 let end = (start + rows_per_chunk).min(axis0_len);
174 let chunk_data = array
175 .slice_axis(Axis(0), Slice::from(start..end))
176 .to_owned();
177
178 chunks.push(ArrayChunk {
179 data: chunk_data,
180 global_index: vec![start],
181 nodeid: nodeid % 3, });
183
184 start = end;
185 nodeid += 1;
186 }
187
188 Self::new(chunks, shape, config)
189 }
190
191 #[must_use]
193 pub fn num_chunks(&self) -> usize {
194 self.chunks.len()
195 }
196
197 #[must_use]
199 pub fn shape(&self) -> &[usize] {
200 &self.shape
201 }
202
203 #[must_use]
205 pub fn chunks(&self) -> &[ArrayChunk<T, D>] {
206 &self.chunks
207 }
208
209 pub fn to_array(&self) -> CoreResult<Array<T, crate::ndarray::IxDyn>> {
221 let mut result =
223 Array::<T, crate::ndarray::IxDyn>::zeros(crate::ndarray::IxDyn(&self.shape));
224
225 for chunk in &self.chunks {
226 let start = chunk.global_index.first().copied().unwrap_or(0);
227 let chunk_view = chunk.data.view().into_dyn();
228 let len = chunk_view.shape().first().copied().unwrap_or(0);
229 if len == 0 {
230 continue;
231 }
232 let mut dest = result.slice_axis_mut(Axis(0), Slice::from(start..start + len));
233 dest.assign(&chunk_view);
234 }
235
236 Ok(result)
237 }
238
239 #[must_use]
241 pub fn map<F, R>(&self, f: F) -> Vec<R>
242 where
243 F: Fn(&ArrayChunk<T, D>) -> R + Send + Sync,
244 R: Send + 'static,
245 {
246 self.chunks.iter().map(f).collect()
249 }
250
251 #[must_use]
257 pub fn map_reduce<F, R, G>(&self, map_fn: F, reducefn: G) -> R
258 where
259 F: Fn(&ArrayChunk<T, D>) -> R + Send + Sync,
260 G: Fn(R, R) -> R + Send + Sync,
261 R: Send + Clone + 'static,
262 {
263 let results = self.map(map_fn);
265
266 results
269 .into_iter()
270 .reduce(reducefn)
271 .expect("Operation failed")
272 }
273}
274
275impl<T, D> ArrayProtocol for DistributedNdarray<T, D>
276where
277 T: Clone
278 + Send
279 + Sync
280 + 'static
281 + num_traits::Zero
282 + std::ops::Div<f64, Output = T>
283 + Default
284 + std::ops::Add<Output = T>
285 + std::ops::Mul<Output = T>,
286 D: Dimension + Clone + Send + Sync + 'static + crate::ndarray::RemoveAxis,
287{
288 fn array_function(
289 &self,
290 func: &ArrayFunction,
291 _types: &[TypeId],
292 args: &[Box<dyn Any>],
293 kwargs: &HashMap<String, Box<dyn Any>>,
294 ) -> Result<Box<dyn Any>, NotImplemented> {
295 match func.name {
296 "scirs2::array_protocol::operations::sum" => {
297 let axis = kwargs.get("axis").and_then(|a| a.downcast_ref::<usize>());
299
300 if let Some(&ax) = axis {
301 let dummy_array = self.chunks[0].data.clone();
304 let sum_array = dummy_array.sum_axis(crate::ndarray::Axis(ax));
305
306 Ok(Box::new(super::NdarrayWrapper::new(sum_array)))
308 } else {
309 let sum = self.map_reduce(|chunk| chunk.data.sum(), |a, b| a + b);
311 Ok(Box::new(sum))
312 }
313 }
314 "scirs2::array_protocol::operations::mean" => {
315 let sum = self.map_reduce(|chunk| chunk.data.sum(), |a, b| a + b);
318
319 #[allow(clippy::cast_precision_loss)]
321 let count = self.shape.iter().product::<usize>() as f64;
322
323 let mean = sum / count;
325
326 Ok(Box::new(mean))
327 }
328 "scirs2::array_protocol::operations::add" => {
329 if args.len() < 2 {
331 return Err(NotImplemented);
332 }
333
334 if let Some(other) = args[1].downcast_ref::<Self>() {
336 if self.shape() != other.shape() {
338 return Err(NotImplemented);
339 }
340
341 let mut new_chunks = Vec::with_capacity(self.chunks.len());
343
344 for (self_chunk, other_chunk) in self.chunks.iter().zip(other.chunks.iter()) {
347 let result_data = &self_chunk.data + &other_chunk.data;
348 new_chunks.push(ArrayChunk {
349 data: result_data,
350 global_index: self_chunk.global_index.clone(),
351 nodeid: self_chunk.nodeid,
352 });
353 }
354
355 let result = Self::new(new_chunks, self.shape.clone(), self.config.clone());
356
357 return Ok(Box::new(result));
358 }
359
360 Err(NotImplemented)
361 }
362 "scirs2::array_protocol::operations::multiply" => {
363 if args.len() < 2 {
365 return Err(NotImplemented);
366 }
367
368 if let Some(other) = args[1].downcast_ref::<Self>() {
370 if self.shape() != other.shape() {
372 return Err(NotImplemented);
373 }
374
375 let mut new_chunks = Vec::with_capacity(self.chunks.len());
377
378 for (self_chunk, other_chunk) in self.chunks.iter().zip(other.chunks.iter()) {
381 let result_data = &self_chunk.data * &other_chunk.data;
382 new_chunks.push(ArrayChunk {
383 data: result_data,
384 global_index: self_chunk.global_index.clone(),
385 nodeid: self_chunk.nodeid,
386 });
387 }
388
389 let result = Self::new(new_chunks, self.shape.clone(), self.config.clone());
390
391 return Ok(Box::new(result));
392 }
393
394 Err(NotImplemented)
395 }
396 "scirs2::array_protocol::operations::matmul" => {
397 if args.len() < 2 {
399 return Err(NotImplemented);
400 }
401
402 if self.shape.len() != 2 {
404 return Err(NotImplemented);
405 }
406
407 if let Some(other) = args[1].downcast_ref::<Self>() {
409 if self.shape.len() != 2
411 || other.shape.len() != 2
412 || self.shape[1] != other.shape[0]
413 {
414 return Err(NotImplemented);
415 }
416
417 let resultshape = vec![self.shape[0], other.shape[1]];
421
422 let dummyshape = crate::ndarray::IxDyn(&resultshape);
425 let dummy_array = Array::<T, crate::ndarray::IxDyn>::zeros(dummyshape);
426
427 let chunk = ArrayChunk {
429 data: dummy_array,
430 global_index: vec![0],
431 nodeid: 0,
432 };
433
434 let result =
435 DistributedNdarray::new(vec![chunk], resultshape, self.config.clone());
436
437 return Ok(Box::new(result));
438 }
439
440 Err(NotImplemented)
441 }
442 "scirs2::array_protocol::operations::transpose" => {
443 if self.shape.len() != 2 {
445 return Err(NotImplemented);
446 }
447
448 let transposedshape = vec![self.shape[1], self.shape[0]];
450
451 let dummyshape = crate::ndarray::IxDyn(&transposedshape);
458 let dummy_array = Array::<T, crate::ndarray::IxDyn>::zeros(dummyshape);
459
460 let chunk = ArrayChunk {
462 data: dummy_array,
463 global_index: vec![0],
464 nodeid: 0,
465 };
466
467 let result =
468 DistributedNdarray::new(vec![chunk], transposedshape, self.config.clone());
469
470 Ok(Box::new(result))
471 }
472 "scirs2::array_protocol::operations::reshape" => {
473 if let Some(shape) = kwargs
475 .get("shape")
476 .and_then(|s| s.downcast_ref::<Vec<usize>>())
477 {
478 let old_size: usize = self.shape.iter().product();
480 let new_size: usize = shape.iter().product();
481
482 if old_size != new_size {
483 return Err(NotImplemented);
484 }
485
486 let dummyshape = crate::ndarray::IxDyn(shape);
492 let dummy_array = Array::<T, crate::ndarray::IxDyn>::zeros(dummyshape);
493
494 let chunk = ArrayChunk {
496 data: dummy_array,
497 global_index: vec![0],
498 nodeid: 0,
499 };
500
501 let result =
502 DistributedNdarray::new(vec![chunk], shape.clone(), self.config.clone());
503
504 return Ok(Box::new(result));
505 }
506
507 Err(NotImplemented)
508 }
509 _ => Err(NotImplemented),
510 }
511 }
512
513 fn as_any(&self) -> &dyn Any {
514 self
515 }
516
517 fn shape(&self) -> &[usize] {
518 &self.shape
519 }
520
521 fn box_clone(&self) -> Box<dyn ArrayProtocol> {
522 Box::new(Self {
523 config: self.config.clone(),
524 chunks: self.chunks.clone(),
525 shape: self.shape.clone(),
526 id: self.id.clone(),
527 })
528 }
529}
530
531impl<T, D> DistributedArray for DistributedNdarray<T, D>
532where
533 T: Clone
534 + Send
535 + Sync
536 + 'static
537 + num_traits::Zero
538 + std::ops::Div<f64, Output = T>
539 + Default
540 + num_traits::One,
541 D: Dimension + Clone + Send + Sync + 'static + crate::ndarray::RemoveAxis,
542{
543 fn distribution_info(&self) -> HashMap<String, String> {
544 let mut info = HashMap::new();
545 info.insert("type".to_string(), "distributed_ndarray".to_string());
546 info.insert("chunks".to_string(), self.chunks.len().to_string());
547 info.insert("shape".to_string(), format!("{:?}", self.shape));
548 info.insert("id".to_string(), self.id.clone());
549 info.insert(
550 "strategy".to_string(),
551 format!("{:?}", self.config.strategy),
552 );
553 info.insert("backend".to_string(), format!("{:?}", self.config.backend));
554 info
555 }
556
557 fn gather(&self) -> CoreResult<Box<dyn ArrayProtocol>>
560 where
561 D: crate::ndarray::RemoveAxis,
562 T: Default + Clone + num_traits::One,
563 {
564 let array_dyn = self.to_array()?;
567
568 Ok(Box::new(super::NdarrayWrapper::new(array_dyn)))
570 }
571
572 fn scatter(&self, chunks: usize) -> CoreResult<Box<dyn DistributedArray>> {
575 let mut config = self.config.clone();
580 config.chunks = chunks;
581
582 let new_dist_array = Self {
585 config,
586 chunks: self.chunks.clone(),
587 shape: self.shape.clone(),
588 id: {
589 let uuid = uuid::Uuid::new_v4();
590 format!("uuid_{uuid}")
591 },
592 };
593
594 Ok(Box::new(new_dist_array))
595 }
596
597 fn is_distributed(&self) -> bool {
598 true
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605 use ::ndarray::Array2;
606
607 #[test]
608 fn test_distributed_ndarray_creation() {
609 let array = Array2::<f64>::ones((10, 5));
610 let config = DistributedConfig {
611 chunks: 3,
612 ..Default::default()
613 };
614
615 let dist_array = DistributedNdarray::from_array(&array, config);
616
617 assert_eq!(dist_array.num_chunks(), 3);
619 assert_eq!(dist_array.shape(), &[10, 5]);
620
621 let total_elements: usize = dist_array
625 .chunks()
626 .iter()
627 .map(|chunk| chunk.data.len())
628 .sum();
629 assert_eq!(total_elements, array.len());
630 }
631
632 #[test]
633 fn test_distributed_ndarray_to_array() {
634 let array = Array2::from_shape_fn((10, 5), |(i, j)| (i * 5 + j) as f64);
639 let config = DistributedConfig {
640 chunks: 3,
641 ..Default::default()
642 };
643
644 let dist_array = DistributedNdarray::from_array(&array, config);
645
646 let result = dist_array.to_array().expect("Operation failed");
648
649 assert_eq!(result.shape(), array.shape());
651 assert_eq!(result, array.into_dyn());
652 }
653
654 #[test]
655 fn test_distributed_ndarray_map_reduce() {
656 let array = Array2::<f64>::ones((10, 5));
657 let config = DistributedConfig {
658 chunks: 3,
659 ..Default::default()
660 };
661
662 let dist_array = DistributedNdarray::from_array(&array, config);
663
664 let expected_sum = array.sum();
668
669 let sum = dist_array.map_reduce(|chunk| chunk.data.sum(), |a, b| a + b);
671
672 assert_eq!(sum, expected_sum);
673 }
674}