1use crate::error::{ScirsError, ScirsResult};
8use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
9use scirs2_core::random::RngExt;
10use scirs2_core::Rng;
11use statrs::statistics::Statistics;
12
13pub trait MPIInterface {
15 fn rank(&self) -> i32;
17
18 fn size(&self) -> i32;
20
21 fn broadcast<T>(&self, data: &mut [T], root: i32) -> ScirsResult<()>
23 where
24 T: Clone + Send + Sync;
25
26 fn gather<T>(&self, send_data: &[T], recv_data: Option<&mut [T]>, root: i32) -> ScirsResult<()>
28 where
29 T: Clone + Send + Sync;
30
31 fn allreduce<T>(
33 &self,
34 send_data: &[T],
35 recv_data: &mut [T],
36 op: ReductionOp,
37 ) -> ScirsResult<()>
38 where
39 T: Clone + Send + Sync + std::ops::Add<Output = T> + PartialOrd;
40
41 fn barrier(&self) -> ScirsResult<()>;
43
44 fn send<T>(&self, data: &[T], dest: i32, tag: i32) -> ScirsResult<()>
46 where
47 T: Clone + Send + Sync;
48
49 fn recv<T>(&self, data: &mut [T], source: i32, tag: i32) -> ScirsResult<()>
51 where
52 T: Clone + Send + Sync;
53}
54
55#[derive(Debug, Clone, Copy)]
57pub enum ReductionOp {
58 Sum,
59 Min,
60 Max,
61 Prod,
62}
63
64#[derive(Debug, Clone)]
66pub struct DistributedConfig {
67 pub distribution_strategy: DistributionStrategy,
69 pub load_balancing: LoadBalancingConfig,
71 pub communication: CommunicationConfig,
73 pub fault_tolerance: FaultToleranceConfig,
75}
76
77impl Default for DistributedConfig {
78 fn default() -> Self {
79 Self {
80 distribution_strategy: DistributionStrategy::DataParallel,
81 load_balancing: LoadBalancingConfig::default(),
82 communication: CommunicationConfig::default(),
83 fault_tolerance: FaultToleranceConfig::default(),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq)]
90pub enum DistributionStrategy {
91 DataParallel,
93 ModelParallel,
95 Hybrid,
97 MasterWorker,
99}
100
101#[derive(Debug, Clone)]
103pub struct LoadBalancingConfig {
104 pub dynamic: bool,
106 pub imbalance_threshold: f64,
108 pub rebalance_interval: usize,
110}
111
112impl Default for LoadBalancingConfig {
113 fn default() -> Self {
114 Self {
115 dynamic: true,
116 imbalance_threshold: 0.2,
117 rebalance_interval: 100,
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
124pub struct CommunicationConfig {
125 pub async_communication: bool,
127 pub buffer_size: usize,
129 pub use_compression: bool,
131 pub overlap_computation: bool,
133}
134
135impl Default for CommunicationConfig {
136 fn default() -> Self {
137 Self {
138 async_communication: true,
139 buffer_size: 1024 * 1024, use_compression: false,
141 overlap_computation: true,
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct FaultToleranceConfig {
149 pub checkpointing: bool,
151 pub checkpoint_interval: usize,
153 pub max_retries: usize,
155 pub timeout: f64,
157}
158
159impl Default for FaultToleranceConfig {
160 fn default() -> Self {
161 Self {
162 checkpointing: false,
163 checkpoint_interval: 1000,
164 max_retries: 3,
165 timeout: 30.0,
166 }
167 }
168}
169
170pub struct DistributedOptimizationContext<M: MPIInterface> {
172 mpi: M,
173 config: DistributedConfig,
174 rank: i32,
175 size: i32,
176 work_distribution: WorkDistribution,
177 performance_stats: DistributedStats,
178}
179
180impl<M: MPIInterface> DistributedOptimizationContext<M> {
181 pub fn new(mpi: M, config: DistributedConfig) -> Self {
183 let rank = mpi.rank();
184 let size = mpi.size();
185 let work_distribution = WorkDistribution::new(rank, size, config.distribution_strategy);
186
187 Self {
188 mpi,
189 config,
190 rank,
191 size,
192 work_distribution,
193 performance_stats: DistributedStats::new(),
194 }
195 }
196
197 pub fn rank(&self) -> i32 {
199 self.rank
200 }
201
202 pub fn size(&self) -> i32 {
204 self.size
205 }
206
207 pub fn is_master(&self) -> bool {
209 self.rank == 0
210 }
211
212 pub fn distribute_work(&mut self, total_work: usize) -> WorkAssignment {
214 self.work_distribution.assign_work(total_work)
215 }
216
217 pub fn synchronize(&self) -> ScirsResult<()> {
219 self.mpi.barrier()
220 }
221
222 pub fn broadcast_parameters(&self, params: &mut Array1<f64>) -> ScirsResult<()> {
224 let data = params.as_slice_mut().expect("Operation failed");
225 self.mpi.broadcast(data, 0)
226 }
227
228 pub fn gather_results(&self, local_result: &Array1<f64>) -> ScirsResult<Option<Array2<f64>>> {
230 if self.is_master() {
231 let total_size = local_result.len() * self.size as usize;
232 let mut gathered_data = vec![0.0; total_size];
233 self.mpi.gather(
234 local_result.as_slice().expect("Operation failed"),
235 Some(&mut gathered_data),
236 0,
237 )?;
238
239 let result =
241 Array2::from_shape_vec((self.size as usize, local_result.len()), gathered_data)
242 .map_err(|e| {
243 ScirsError::InvalidInput(scirs2_core::error::ErrorContext::new(format!(
244 "Failed to reshape gathered data: {}",
245 e
246 )))
247 })?;
248 Ok(Some(result))
249 } else {
250 self.mpi
251 .gather(local_result.as_slice().expect("Operation failed"), None, 0)?;
252 Ok(None)
253 }
254 }
255
256 pub fn allreduce_sum(&self, local_data: &Array1<f64>) -> ScirsResult<Array1<f64>> {
258 let mut result = Array1::zeros(local_data.len());
259 self.mpi.allreduce(
260 local_data.as_slice().expect("Operation failed"),
261 result.as_slice_mut().expect("Operation failed"),
262 ReductionOp::Sum,
263 )?;
264 Ok(result)
265 }
266
267 pub fn allreduce_min(&self, local_data: &Array1<f64>) -> ScirsResult<Array1<f64>> {
269 let mut result = Array1::zeros(local_data.len());
270 self.mpi.allreduce(
271 local_data.as_slice().expect("Operation failed"),
272 result.as_slice_mut().expect("Operation failed"),
273 ReductionOp::Min,
274 )?;
275 Ok(result)
276 }
277
278 pub fn broadcast_from(&self, params: &mut Array1<f64>, root: i32) -> ScirsResult<()> {
280 let data = params.as_slice_mut().expect("Operation failed");
281 self.mpi.broadcast(data, root)
282 }
283
284 pub fn select_global_best(
292 &self,
293 local_best: &Array1<f64>,
294 local_value: f64,
295 ) -> ScirsResult<(Array1<f64>, f64)> {
296 if self.size <= 1 {
297 return Ok((local_best.to_owned(), local_value));
298 }
299
300 let value_buf = Array1::from_elem(1, local_value);
302 let global_value = self.allreduce_min(&value_buf)?[0];
303
304 let owner_proposal = if local_value <= global_value {
308 self.rank as f64
309 } else {
310 self.size as f64
311 };
312 let owner_buf = Array1::from_elem(1, owner_proposal);
313 let owner_rank = self.allreduce_min(&owner_buf)?[0] as i32;
314
315 let mut winner = local_best.to_owned();
317 self.broadcast_from(&mut winner, owner_rank)?;
318
319 Ok((winner, global_value))
320 }
321
322 pub fn ring_exchange(&self, outgoing: &Array1<f64>) -> ScirsResult<Option<Array1<f64>>> {
330 if self.size <= 1 {
331 return Ok(None);
332 }
333
334 let next = (self.rank + 1).rem_euclid(self.size);
335 let prev = (self.rank - 1).rem_euclid(self.size);
336 let tag = 0;
337 let send = outgoing.as_slice().expect("Operation failed");
338 let mut incoming = vec![0.0_f64; outgoing.len()];
339
340 if self.rank == 0 {
341 self.mpi.recv(&mut incoming, prev, tag)?;
343 self.mpi.send(send, next, tag)?;
344 } else {
345 self.mpi.send(send, next, tag)?;
346 self.mpi.recv(&mut incoming, prev, tag)?;
347 }
348
349 Ok(Some(Array1::from_vec(incoming)))
350 }
351
352 pub fn stats(&self) -> &DistributedStats {
354 &self.performance_stats
355 }
356}
357
358struct WorkDistribution {
360 rank: i32,
361 size: i32,
362 strategy: DistributionStrategy,
363}
364
365impl WorkDistribution {
366 fn new(rank: i32, size: i32, strategy: DistributionStrategy) -> Self {
367 Self {
368 rank,
369 size,
370 strategy,
371 }
372 }
373
374 fn assign_work(&self, total_work: usize) -> WorkAssignment {
375 match self.strategy {
376 DistributionStrategy::DataParallel => self.data_parallel_assignment(total_work),
377 DistributionStrategy::ModelParallel => self.model_parallel_assignment(total_work),
378 DistributionStrategy::Hybrid => self.hybrid_assignment(total_work),
379 DistributionStrategy::MasterWorker => self.master_worker_assignment(total_work),
380 }
381 }
382
383 fn data_parallel_assignment(&self, total_work: usize) -> WorkAssignment {
384 let work_per_process = total_work / self.size as usize;
385 let remainder = total_work % self.size as usize;
386
387 let start = self.rank as usize * work_per_process + (self.rank as usize).min(remainder);
388 let extra = if (self.rank as usize) < remainder {
389 1
390 } else {
391 0
392 };
393 let count = work_per_process + extra;
394
395 WorkAssignment {
396 start_index: start,
397 count,
398 strategy: DistributionStrategy::DataParallel,
399 }
400 }
401
402 fn model_parallel_assignment(&self, total_work: usize) -> WorkAssignment {
403 WorkAssignment {
405 start_index: 0,
406 count: total_work, strategy: DistributionStrategy::ModelParallel,
408 }
409 }
410
411 fn hybrid_assignment(&self, total_work: usize) -> WorkAssignment {
412 let size = (self.size.max(1)) as usize;
421
422 let mut model_groups = 1usize;
425 let mut divisor = 2usize;
426 while divisor * divisor <= size {
427 if size % divisor == 0 {
428 model_groups = divisor;
429 }
430 divisor += 1;
431 }
432 let data_groups = size / model_groups;
433
434 let rank = (self.rank.max(0)) as usize;
436 let data_coord = (rank / model_groups).min(data_groups.saturating_sub(1));
437
438 let work_per_group = total_work / data_groups;
440 let remainder = total_work % data_groups;
441 let start = data_coord * work_per_group + data_coord.min(remainder);
442 let extra = if data_coord < remainder { 1 } else { 0 };
443 let count = work_per_group + extra;
444
445 WorkAssignment {
446 start_index: start,
447 count,
448 strategy: DistributionStrategy::Hybrid,
449 }
450 }
451
452 fn master_worker_assignment(&self, total_work: usize) -> WorkAssignment {
453 if self.rank == 0 {
454 WorkAssignment {
456 start_index: 0,
457 count: 0,
458 strategy: DistributionStrategy::MasterWorker,
459 }
460 } else {
461 let worker_count = self.size - 1;
463 let work_per_worker = total_work / worker_count as usize;
464 let remainder = total_work % worker_count as usize;
465 let worker_rank = self.rank - 1;
466
467 let start =
468 worker_rank as usize * work_per_worker + (worker_rank as usize).min(remainder);
469 let extra = if (worker_rank as usize) < remainder {
470 1
471 } else {
472 0
473 };
474 let count = work_per_worker + extra;
475
476 WorkAssignment {
477 start_index: start,
478 count,
479 strategy: DistributionStrategy::MasterWorker,
480 }
481 }
482 }
483}
484
485#[derive(Debug, Clone)]
487pub struct WorkAssignment {
488 pub start_index: usize,
490 pub count: usize,
492 pub strategy: DistributionStrategy,
494}
495
496impl WorkAssignment {
497 pub fn range(&self) -> std::ops::Range<usize> {
499 self.start_index..(self.start_index + self.count)
500 }
501
502 pub fn is_empty(&self) -> bool {
504 self.count == 0
505 }
506}
507
508pub mod algorithms {
510 use super::*;
511 use crate::result::OptimizeResults;
512
513 pub struct DistributedDifferentialEvolution<M: MPIInterface> {
515 context: DistributedOptimizationContext<M>,
516 population_size: usize,
517 max_nit: usize,
518 f_scale: f64,
519 crossover_rate: f64,
520 }
521
522 impl<M: MPIInterface> DistributedDifferentialEvolution<M> {
523 pub fn new(
525 context: DistributedOptimizationContext<M>,
526 population_size: usize,
527 max_nit: usize,
528 ) -> Self {
529 Self {
530 context,
531 population_size,
532 max_nit,
533 f_scale: 0.8,
534 crossover_rate: 0.7,
535 }
536 }
537
538 pub fn with_parameters(mut self, f_scale: f64, crossover_rate: f64) -> Self {
540 self.f_scale = f_scale;
541 self.crossover_rate = crossover_rate;
542 self
543 }
544
545 pub fn optimize<F>(
547 &mut self,
548 function: F,
549 bounds: &[(f64, f64)],
550 ) -> ScirsResult<OptimizeResults<f64>>
551 where
552 F: Fn(&ArrayView1<f64>) -> f64 + Clone + Send + Sync,
553 {
554 let dims = bounds.len();
555
556 let local_pop_size = self.population_size / self.context.size() as usize;
558 let mut local_population = self.initialize_local_population(local_pop_size, bounds)?;
559 let mut local_fitness = self.evaluate_local_population(&function, &local_population)?;
560
561 let mut global_best = self.find_global_best(&local_population, &local_fitness)?;
563 let mut global_best_fitness = global_best.1;
564
565 let mut total_evaluations = self.population_size;
566
567 for iteration in 0..self.max_nit {
568 let trial_population = self.generate_trial_population(&local_population)?;
570 let trial_fitness = self.evaluate_local_population(&function, &trial_population)?;
571
572 self.selection(
574 &mut local_population,
575 &mut local_fitness,
576 &trial_population,
577 &trial_fitness,
578 );
579
580 total_evaluations += local_pop_size;
581
582 if iteration % 10 == 0 {
584 let new_global_best =
585 self.find_global_best(&local_population, &local_fitness)?;
586 if new_global_best.1 < global_best_fitness {
587 global_best = new_global_best;
588 global_best_fitness = global_best.1;
589 }
590
591 self.migrate_individuals(&mut local_population, &mut local_fitness)?;
593 }
594
595 if iteration % 50 == 0 {
597 let convergence = self.check_convergence(&local_fitness)?;
598 if convergence {
599 break;
600 }
601 }
602 }
603
604 let final_best = self.find_global_best(&local_population, &local_fitness)?;
606 if final_best.1 < global_best_fitness {
607 global_best = final_best.clone();
608 global_best_fitness = final_best.1;
609 }
610
611 Ok(OptimizeResults::<f64> {
612 x: global_best.0,
613 fun: global_best_fitness,
614 success: true,
615 message: "Distributed differential evolution completed".to_string(),
616 nit: self.max_nit,
617 nfev: total_evaluations,
618 ..OptimizeResults::default()
619 })
620 }
621
622 fn initialize_local_population(
623 &self,
624 local_size: usize,
625 bounds: &[(f64, f64)],
626 ) -> ScirsResult<Array2<f64>> {
627 let mut rng = scirs2_core::random::rng();
628
629 let dims = bounds.len();
630 let mut population = Array2::zeros((local_size, dims));
631
632 for i in 0..local_size {
633 for j in 0..dims {
634 let (low, high) = bounds[j];
635 population[[i, j]] = rng.random_range(low..=high);
636 }
637 }
638
639 Ok(population)
640 }
641
642 fn evaluate_local_population<F>(
643 &self,
644 function: &F,
645 population: &Array2<f64>,
646 ) -> ScirsResult<Array1<f64>>
647 where
648 F: Fn(&ArrayView1<f64>) -> f64,
649 {
650 let mut fitness = Array1::zeros(population.nrows());
651
652 for i in 0..population.nrows() {
653 let individual = population.row(i);
654 fitness[i] = function(&individual);
655 }
656
657 Ok(fitness)
658 }
659
660 fn find_global_best(
661 &mut self,
662 local_population: &Array2<f64>,
663 local_fitness: &Array1<f64>,
664 ) -> ScirsResult<(Array1<f64>, f64)> {
665 let mut best_idx = 0;
667 let mut best_fitness = local_fitness[0];
668 for (i, &fitness) in local_fitness.iter().enumerate() {
669 if fitness < best_fitness {
670 best_fitness = fitness;
671 best_idx = i;
672 }
673 }
674
675 let local_best = local_population.row(best_idx).to_owned();
676
677 self.context.select_global_best(&local_best, best_fitness)
680 }
681
682 fn generate_trial_population(&self, population: &Array2<f64>) -> ScirsResult<Array2<f64>> {
683 let mut rng = scirs2_core::random::rng();
684
685 let (pop_size, dims) = population.dim();
686 let mut trial_population = Array2::zeros((pop_size, dims));
687
688 for i in 0..pop_size {
689 let mut indices = Vec::new();
691 while indices.len() < 3 {
692 let idx = rng.random_range(0..pop_size);
693 if idx != i && !indices.contains(&idx) {
694 indices.push(idx);
695 }
696 }
697
698 let a = indices[0];
699 let b = indices[1];
700 let c = indices[2];
701
702 let j_rand = rng.random_range(0..dims);
704 for j in 0..dims {
705 if rng.random::<f64>() < self.crossover_rate || j == j_rand {
706 trial_population[[i, j]] = population[[a, j]]
707 + self.f_scale * (population[[b, j]] - population[[c, j]]);
708 } else {
709 trial_population[[i, j]] = population[[i, j]];
710 }
711 }
712 }
713
714 Ok(trial_population)
715 }
716
717 fn selection(
718 &self,
719 population: &mut Array2<f64>,
720 fitness: &mut Array1<f64>,
721 trial_population: &Array2<f64>,
722 trial_fitness: &Array1<f64>,
723 ) {
724 for i in 0..population.nrows() {
725 if trial_fitness[i] <= fitness[i] {
726 for j in 0..population.ncols() {
727 population[[i, j]] = trial_population[[i, j]];
728 }
729 fitness[i] = trial_fitness[i];
730 }
731 }
732 }
733
734 fn migrate_individuals(
735 &mut self,
736 population: &mut Array2<f64>,
737 fitness: &mut Array1<f64>,
738 ) -> ScirsResult<()> {
739 if self.context.size() <= 1 {
744 return Ok(());
745 }
746
747 let best_idx = fitness
748 .iter()
749 .enumerate()
750 .min_by(|(_, a), (_, b)| a.partial_cmp(b).expect("Operation failed"))
751 .map(|(i, _)| i)
752 .unwrap_or(0);
753
754 let dims = population.ncols();
758 let mut payload = population.row(best_idx).to_vec();
759 payload.push(fitness[best_idx]);
760 let payload = Array1::from_vec(payload);
761
762 if let Some(received) = self.context.ring_exchange(&payload)? {
763 let migrant_fitness = received[dims];
764
765 let worst_idx = fitness
767 .iter()
768 .enumerate()
769 .max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("Operation failed"))
770 .map(|(i, _)| i)
771 .unwrap_or(0);
772
773 if migrant_fitness < fitness[worst_idx] {
774 for j in 0..dims {
775 population[[worst_idx, j]] = received[j];
776 }
777 fitness[worst_idx] = migrant_fitness;
778 }
779 }
780
781 Ok(())
782 }
783
784 fn check_convergence(&mut self, local_fitness: &Array1<f64>) -> ScirsResult<bool> {
785 let mean = local_fitness.view().mean();
786 let variance = local_fitness
787 .iter()
788 .map(|&x| (x - mean).powi(2))
789 .sum::<f64>()
790 / local_fitness.len() as f64;
791
792 let std_dev = variance.sqrt();
793
794 Ok(std_dev < 1e-12)
796 }
797 }
798
799 pub struct DistributedParticleSwarm<M: MPIInterface> {
801 context: DistributedOptimizationContext<M>,
802 swarm_size: usize,
803 max_nit: usize,
804 w: f64, c1: f64, c2: f64, }
808
809 impl<M: MPIInterface> DistributedParticleSwarm<M> {
810 pub fn new(
812 context: DistributedOptimizationContext<M>,
813 swarm_size: usize,
814 max_nit: usize,
815 ) -> Self {
816 Self {
817 context,
818 swarm_size,
819 max_nit,
820 w: 0.729,
821 c1: 1.49445,
822 c2: 1.49445,
823 }
824 }
825
826 pub fn with_parameters(mut self, w: f64, c1: f64, c2: f64) -> Self {
828 self.w = w;
829 self.c1 = c1;
830 self.c2 = c2;
831 self
832 }
833
834 pub fn optimize<F>(
836 &mut self,
837 function: F,
838 bounds: &[(f64, f64)],
839 ) -> ScirsResult<OptimizeResults<f64>>
840 where
841 F: Fn(&ArrayView1<f64>) -> f64 + Clone + Send + Sync,
842 {
843 let dims = bounds.len();
844 let local_swarm_size = self.swarm_size / self.context.size() as usize;
845
846 let mut positions = self.initialize_positions(local_swarm_size, bounds)?;
848 let mut velocities = Array2::zeros((local_swarm_size, dims));
849 let mut personal_best = positions.clone();
850 let mut personal_best_fitness = self.evaluate_swarm(&function, &positions)?;
851
852 let mut global_best = self.find_global_best(&personal_best, &personal_best_fitness)?;
854 let mut global_best_fitness = global_best.1;
855
856 let mut function_evaluations = local_swarm_size;
857
858 for iteration in 0..self.max_nit {
859 self.update_swarm(
861 &mut positions,
862 &mut velocities,
863 &personal_best,
864 &global_best.0,
865 bounds,
866 )?;
867
868 let fitness = self.evaluate_swarm(&function, &positions)?;
870 function_evaluations += local_swarm_size;
871
872 for i in 0..local_swarm_size {
874 if fitness[i] < personal_best_fitness[i] {
875 personal_best_fitness[i] = fitness[i];
876 for j in 0..dims {
877 personal_best[[i, j]] = positions[[i, j]];
878 }
879 }
880 }
881
882 if iteration % 10 == 0 {
884 let new_global_best =
885 self.find_global_best(&personal_best, &personal_best_fitness)?;
886 if new_global_best.1 < global_best_fitness {
887 global_best = new_global_best;
888 global_best_fitness = global_best.1;
889 }
890 }
891 }
892
893 Ok(OptimizeResults::<f64> {
894 x: global_best.0,
895 fun: global_best_fitness,
896 success: true,
897 message: "Distributed particle swarm optimization completed".to_string(),
898 nit: self.max_nit,
899 nfev: function_evaluations,
900 ..OptimizeResults::default()
901 })
902 }
903
904 fn initialize_positions(
905 &self,
906 local_size: usize,
907 bounds: &[(f64, f64)],
908 ) -> ScirsResult<Array2<f64>> {
909 let mut rng = scirs2_core::random::rng();
910
911 let dims = bounds.len();
912 let mut positions = Array2::zeros((local_size, dims));
913
914 for i in 0..local_size {
915 for j in 0..dims {
916 let (low, high) = bounds[j];
917 positions[[i, j]] = rng.random_range(low..=high);
918 }
919 }
920
921 Ok(positions)
922 }
923
924 fn evaluate_swarm<F>(
925 &self,
926 function: &F,
927 positions: &Array2<f64>,
928 ) -> ScirsResult<Array1<f64>>
929 where
930 F: Fn(&ArrayView1<f64>) -> f64,
931 {
932 let mut fitness = Array1::zeros(positions.nrows());
933
934 for i in 0..positions.nrows() {
935 let particle = positions.row(i);
936 fitness[i] = function(&particle);
937 }
938
939 Ok(fitness)
940 }
941
942 fn find_global_best(
943 &mut self,
944 positions: &Array2<f64>,
945 fitness: &Array1<f64>,
946 ) -> ScirsResult<(Array1<f64>, f64)> {
947 let mut best_idx = 0;
949 let mut best_fitness = fitness[0];
950 for (i, &f) in fitness.iter().enumerate() {
951 if f < best_fitness {
952 best_fitness = f;
953 best_idx = i;
954 }
955 }
956
957 let local_best = positions.row(best_idx).to_owned();
958
959 self.context.select_global_best(&local_best, best_fitness)
962 }
963
964 fn update_swarm(
965 &self,
966 positions: &mut Array2<f64>,
967 velocities: &mut Array2<f64>,
968 personal_best: &Array2<f64>,
969 global_best: &Array1<f64>,
970 bounds: &[(f64, f64)],
971 ) -> ScirsResult<()> {
972 let mut rng = scirs2_core::random::rng();
973
974 let (swarm_size, dims) = positions.dim();
975
976 for i in 0..swarm_size {
977 for j in 0..dims {
978 let r1: f64 = rng.random();
979 let r2: f64 = rng.random();
980
981 velocities[[i, j]] = self.w * velocities[[i, j]]
983 + self.c1 * r1 * (personal_best[[i, j]] - positions[[i, j]])
984 + self.c2 * r2 * (global_best[j] - positions[[i, j]]);
985
986 positions[[i, j]] += velocities[[i, j]];
988
989 let (low, high) = bounds[j];
991 if positions[[i, j]] < low {
992 positions[[i, j]] = low;
993 velocities[[i, j]] = 0.0;
994 } else if positions[[i, j]] > high {
995 positions[[i, j]] = high;
996 velocities[[i, j]] = 0.0;
997 }
998 }
999 }
1000
1001 Ok(())
1002 }
1003 }
1004}
1005
1006#[derive(Debug, Clone)]
1008pub struct DistributedStats {
1009 pub communication_time: f64,
1011 pub computation_time: f64,
1013 pub load_balance_ratio: f64,
1015 pub synchronizations: usize,
1017 pub bytes_transferred: usize,
1019}
1020
1021impl DistributedStats {
1022 fn new() -> Self {
1023 Self {
1024 communication_time: 0.0,
1025 computation_time: 0.0,
1026 load_balance_ratio: 1.0,
1027 synchronizations: 0,
1028 bytes_transferred: 0,
1029 }
1030 }
1031
1032 pub fn parallel_efficiency(&self) -> f64 {
1034 if self.communication_time + self.computation_time == 0.0 {
1035 1.0
1036 } else {
1037 self.computation_time / (self.communication_time + self.computation_time)
1038 }
1039 }
1040
1041 pub fn generate_report(&self) -> String {
1043 format!(
1044 "Distributed Optimization Performance Report\n\
1045 ==========================================\n\
1046 Computation Time: {:.3}s\n\
1047 Communication Time: {:.3}s\n\
1048 Parallel Efficiency: {:.1}%\n\
1049 Load Balance Ratio: {:.3}\n\
1050 Synchronizations: {}\n\
1051 Data Transferred: {} bytes\n",
1052 self.computation_time,
1053 self.communication_time,
1054 self.parallel_efficiency() * 100.0,
1055 self.load_balance_ratio,
1056 self.synchronizations,
1057 self.bytes_transferred
1058 )
1059 }
1060}
1061
1062#[cfg(test)]
1064pub struct MockMPI {
1065 rank: i32,
1066 size: i32,
1067}
1068
1069#[cfg(test)]
1070impl MockMPI {
1071 pub fn new(rank: i32, size: i32) -> Self {
1072 Self { rank, size }
1073 }
1074}
1075
1076#[cfg(test)]
1077impl MPIInterface for MockMPI {
1078 fn rank(&self) -> i32 {
1079 self.rank
1080 }
1081 fn size(&self) -> i32 {
1082 self.size
1083 }
1084
1085 fn broadcast<T>(&self, data: &mut [T], root: i32) -> ScirsResult<()>
1086 where
1087 T: Clone + Send + Sync,
1088 {
1089 Ok(())
1090 }
1091
1092 fn gather<T>(
1093 &self,
1094 _send_data: &[T],
1095 _recv_data: Option<&mut [T]>,
1096 _root: i32,
1097 ) -> ScirsResult<()>
1098 where
1099 T: Clone + Send + Sync,
1100 {
1101 Ok(())
1102 }
1103
1104 fn allreduce<T>(
1105 &self,
1106 send_data: &[T],
1107 recv_data: &mut [T],
1108 _op: ReductionOp,
1109 ) -> ScirsResult<()>
1110 where
1111 T: Clone + Send + Sync + std::ops::Add<Output = T> + PartialOrd,
1112 {
1113 for (i, item) in send_data.iter().enumerate() {
1114 if i < recv_data.len() {
1115 recv_data[i] = item.clone();
1116 }
1117 }
1118 Ok(())
1119 }
1120
1121 fn barrier(&self) -> ScirsResult<()> {
1122 Ok(())
1123 }
1124 fn send<T>(&self, _data: &[T], _dest: i32, tag: i32) -> ScirsResult<()>
1125 where
1126 T: Clone + Send + Sync,
1127 {
1128 Ok(())
1129 }
1130 fn recv<T>(&self, _data: &mut [T], _source: i32, tag: i32) -> ScirsResult<()>
1131 where
1132 T: Clone + Send + Sync,
1133 {
1134 Ok(())
1135 }
1136}
1137
1138#[cfg(test)]
1139mod tests {
1140 use super::*;
1141
1142 #[test]
1143 fn test_work_distribution() {
1144 let distribution = WorkDistribution::new(0, 4, DistributionStrategy::DataParallel);
1145 let assignment = distribution.assign_work(100);
1146
1147 assert_eq!(assignment.count, 25);
1148 assert_eq!(assignment.start_index, 0);
1149 assert_eq!(assignment.range(), 0..25);
1150 }
1151
1152 #[test]
1153 fn test_work_assignment_remainder() {
1154 let distribution = WorkDistribution::new(3, 4, DistributionStrategy::DataParallel);
1155 let assignment = distribution.assign_work(10);
1156
1157 assert_eq!(assignment.count, 2);
1159 assert_eq!(assignment.start_index, 8);
1160 }
1161
1162 #[test]
1163 fn test_master_worker_distribution() {
1164 let master_distribution = WorkDistribution::new(0, 4, DistributionStrategy::MasterWorker);
1165 let master_assignment = master_distribution.assign_work(100);
1166
1167 assert_eq!(master_assignment.count, 0); let worker_distribution = WorkDistribution::new(1, 4, DistributionStrategy::MasterWorker);
1170 let worker_assignment = worker_distribution.assign_work(100);
1171
1172 assert!(worker_assignment.count > 0); }
1174
1175 #[test]
1176 fn test_distributed_context() {
1177 let mpi = MockMPI::new(0, 4);
1178 let config = DistributedConfig::default();
1179 let context = DistributedOptimizationContext::new(mpi, config);
1180
1181 assert_eq!(context.rank(), 0);
1182 assert_eq!(context.size(), 4);
1183 assert!(context.is_master());
1184 }
1185
1186 #[test]
1187 fn test_distributed_stats() {
1188 let mut stats = DistributedStats::new();
1189 stats.computation_time = 80.0;
1190 stats.communication_time = 20.0;
1191
1192 assert_eq!(stats.parallel_efficiency(), 0.8);
1193 }
1194}