1use crate::error::{MLError, Result};
8use scirs2_core::ndarray::{
9 Array, Array1, Array2, Array3, ArrayD, ArrayViewD, Axis, Dimension, Ix2, IxDyn,
10};
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13
14pub trait SciRS2Tensor {
16 fn shape(&self) -> &[usize];
18
19 fn view(&self) -> ArrayViewD<f64>;
21
22 fn to_scirs2(&self) -> Result<SciRS2Array>;
24
25 fn matmul(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array>;
27
28 fn add(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array>;
30 fn mul(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array>;
31 fn sub(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array>;
32
33 fn sum(&self, axis: Option<usize>) -> Result<SciRS2Array>;
35 fn mean(&self, axis: Option<usize>) -> Result<SciRS2Array>;
36 fn max(&self, axis: Option<usize>) -> Result<SciRS2Array>;
37 fn min(&self, axis: Option<usize>) -> Result<SciRS2Array>;
38}
39
40pub struct SciRS2Array {
42 pub data: ArrayD<f64>,
44 pub requires_grad: bool,
46 pub grad: Option<Arc<Mutex<ArrayD<f64>>>>,
55 pub grad_fn: Option<Box<dyn GradFunction>>,
57}
58
59impl std::fmt::Debug for SciRS2Array {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("SciRS2Array")
62 .field("data", &self.data)
63 .field("requires_grad", &self.requires_grad)
64 .field("grad", &self.grad)
65 .field("grad_fn", &"<gradient_function>")
66 .finish()
67 }
68}
69
70impl Clone for SciRS2Array {
71 fn clone(&self) -> Self {
72 Self {
73 data: self.data.clone(),
74 requires_grad: self.requires_grad,
75 grad: self.grad.clone(),
76 grad_fn: None, }
78 }
79}
80
81impl SciRS2Array {
82 pub fn new(data: ArrayD<f64>, requires_grad: bool) -> Self {
84 let grad = if requires_grad {
85 Some(Arc::new(Mutex::new(ArrayD::zeros(data.raw_dim()))))
86 } else {
87 None
88 };
89 Self {
90 data,
91 requires_grad,
92 grad,
93 grad_fn: None,
94 }
95 }
96
97 pub fn from_array<D: Dimension>(arr: Array<f64, D>) -> Self {
99 let data = arr.into_dyn();
100 Self::new(data, false)
101 }
102
103 pub fn with_grad<D: Dimension>(arr: Array<f64, D>) -> Self {
105 let data = arr.into_dyn();
106 Self::new(data, true)
107 }
108
109 pub fn zero_grad(&mut self) {
111 if let Some(ref grad) = self.grad {
112 lock_grad(grad).fill(0.0);
113 }
114 }
115
116 pub fn set_grad(&mut self, grad: ArrayD<f64>) {
119 match &self.grad {
120 Some(cell) => *lock_grad(cell) = grad,
121 None => self.grad = Some(Arc::new(Mutex::new(grad))),
122 }
123 }
124
125 pub fn backward(&mut self) -> Result<()> {
129 if let Some(grad_fn) = self.grad_fn.take() {
131 grad_fn.backward(self)?;
132 self.grad_fn = Some(grad_fn);
133 }
134 Ok(())
135 }
136
137 pub fn matmul(&self, other: &SciRS2Array) -> Result<SciRS2Array> {
139 let result_data = if self.data.ndim() == 2 && other.data.ndim() == 2 {
140 let self_2d = self
141 .data
142 .view()
143 .into_dimensionality::<Ix2>()
144 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
145 let other_2d = other
146 .data
147 .view()
148 .into_dimensionality::<Ix2>()
149 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
150 self_2d.dot(&other_2d).into_dyn()
151 } else {
152 return Err(MLError::InvalidConfiguration(
153 "Matrix multiplication requires 2D arrays".to_string(),
154 ));
155 };
156
157 let requires_grad = self.requires_grad || other.requires_grad;
158 let mut result = SciRS2Array::new(result_data, requires_grad);
159
160 if requires_grad {
161 result.grad_fn = Some(Box::new(MatmulGradFn {
162 left_grad: self.grad.clone(),
163 right_grad: other.grad.clone(),
164 left_data: self.data.clone(),
165 right_data: other.data.clone(),
166 }));
167 }
168
169 Ok(result)
170 }
171
172 pub fn add(&self, other: &SciRS2Array) -> Result<SciRS2Array> {
174 let result_data = &self.data + &other.data;
175 let requires_grad = self.requires_grad || other.requires_grad;
176 let mut result = SciRS2Array::new(result_data, requires_grad);
177
178 if requires_grad {
179 result.grad_fn = Some(Box::new(AddGradFn {
180 left_grad: self.grad.clone(),
181 right_grad: other.grad.clone(),
182 }));
183 }
184
185 Ok(result)
186 }
187
188 pub fn mul(&self, other: &SciRS2Array) -> Result<SciRS2Array> {
190 let result_data = &self.data * &other.data;
191 let requires_grad = self.requires_grad || other.requires_grad;
192 let mut result = SciRS2Array::new(result_data, requires_grad);
193
194 if requires_grad {
195 result.grad_fn = Some(Box::new(MulGradFn {
196 left_grad: self.grad.clone(),
197 right_grad: other.grad.clone(),
198 left_data: self.data.clone(),
199 right_data: other.data.clone(),
200 }));
201 }
202
203 Ok(result)
204 }
205
206 pub fn sum(&self, axis: Option<usize>) -> Result<SciRS2Array> {
208 let result_data = match axis {
209 Some(ax) => self
210 .data
211 .sum_axis(scirs2_core::ndarray::Axis(ax))
212 .into_dyn(),
213 None => {
214 let sum_val = self.data.sum();
215 ArrayD::from_elem(IxDyn(&[]), sum_val)
216 }
217 };
218
219 let mut result = SciRS2Array::new(result_data, self.requires_grad);
220
221 if self.requires_grad {
222 result.grad_fn = Some(Box::new(SumGradFn {
223 axis,
224 input_shape: self.data.raw_dim(),
225 input_grad: self.grad.clone(),
226 }));
227 }
228
229 Ok(result)
230 }
231}
232
233impl SciRS2Tensor for SciRS2Array {
234 fn shape(&self) -> &[usize] {
235 self.data.shape()
236 }
237
238 fn view(&self) -> ArrayViewD<f64> {
239 self.data.view()
240 }
241
242 fn to_scirs2(&self) -> Result<SciRS2Array> {
243 Ok(self.clone())
244 }
245
246 fn matmul(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array> {
247 let other_array = other.to_scirs2()?;
249 self.matmul(&other_array)
250 }
251
252 fn add(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array> {
253 let other_array = other.to_scirs2()?;
254 self.add(&other_array)
255 }
256
257 fn mul(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array> {
258 let other_array = other.to_scirs2()?;
259 self.mul(&other_array)
260 }
261
262 fn sub(&self, other: &dyn SciRS2Tensor) -> Result<SciRS2Array> {
263 let other_array = other.to_scirs2()?;
264 let result_data = &self.data - &other_array.data;
265 let requires_grad = self.requires_grad || other_array.requires_grad;
266 let mut result = SciRS2Array::new(result_data, requires_grad);
267
268 if requires_grad {
269 result.grad_fn = Some(Box::new(SubGradFn {
270 left_grad: self.grad.clone(),
271 right_grad: other_array.grad.clone(),
272 }));
273 }
274
275 Ok(result)
276 }
277
278 fn sum(&self, axis: Option<usize>) -> Result<SciRS2Array> {
279 self.sum(axis)
280 }
281
282 fn mean(&self, axis: Option<usize>) -> Result<SciRS2Array> {
283 let result_data = match axis {
284 Some(ax) => self
285 .data
286 .mean_axis(scirs2_core::ndarray::Axis(ax))
287 .ok_or_else(|| {
288 MLError::ComputationError("Empty axis for mean computation".to_string())
289 })?
290 .into_dyn(),
291 None => {
292 let mean_val = self.data.mean().ok_or_else(|| {
293 MLError::ComputationError("Empty array for mean computation".to_string())
294 })?;
295 ArrayD::from_elem(IxDyn(&[]), mean_val)
296 }
297 };
298 Ok(SciRS2Array::new(result_data, self.requires_grad))
299 }
300
301 fn max(&self, axis: Option<usize>) -> Result<SciRS2Array> {
302 let result_data = match axis {
303 Some(ax) => self
304 .data
305 .map_axis(scirs2_core::ndarray::Axis(ax), |view| {
306 *view
307 .iter()
308 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
309 .expect("map_axis guarantees non-empty view for valid axis")
310 })
311 .into_dyn(),
312 None => {
313 let max_val = *self
314 .data
315 .iter()
316 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
317 .ok_or_else(|| {
318 MLError::ComputationError("Empty array for max computation".to_string())
319 })?;
320 ArrayD::from_elem(IxDyn(&[]), max_val)
321 }
322 };
323 Ok(SciRS2Array::new(result_data, self.requires_grad))
324 }
325
326 fn min(&self, axis: Option<usize>) -> Result<SciRS2Array> {
327 let result_data = match axis {
328 Some(ax) => self
329 .data
330 .map_axis(scirs2_core::ndarray::Axis(ax), |view| {
331 *view
332 .iter()
333 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
334 .expect("map_axis guarantees non-empty view for valid axis")
335 })
336 .into_dyn(),
337 None => {
338 let min_val = *self
339 .data
340 .iter()
341 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
342 .ok_or_else(|| {
343 MLError::ComputationError("Empty array for min computation".to_string())
344 })?;
345 ArrayD::from_elem(IxDyn(&[]), min_val)
346 }
347 };
348 Ok(SciRS2Array::new(result_data, self.requires_grad))
349 }
350}
351
352fn lock_grad(cell: &Arc<Mutex<ArrayD<f64>>>) -> std::sync::MutexGuard<'_, ArrayD<f64>> {
356 cell.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
357}
358
359fn output_grad_or_ones(output: &SciRS2Array) -> ArrayD<f64> {
364 match &output.grad {
365 Some(cell) => lock_grad(cell).clone(),
366 None => ArrayD::ones(output.data.raw_dim()),
367 }
368}
369
370fn accumulate_grad(cell: &Option<Arc<Mutex<ArrayD<f64>>>>, contribution: &ArrayD<f64>) {
372 if let Some(cell) = cell {
373 let mut guard = lock_grad(cell);
374 *guard = &*guard + contribution;
375 }
376}
377
378pub trait GradFunction: Send + Sync {
380 fn backward(&self, output: &mut SciRS2Array) -> Result<()>;
381}
382
383#[derive(Debug)]
386struct MatmulGradFn {
387 left_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
388 right_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
389 left_data: ArrayD<f64>,
390 right_data: ArrayD<f64>,
391}
392
393impl GradFunction for MatmulGradFn {
394 fn backward(&self, output: &mut SciRS2Array) -> Result<()> {
395 let output_grad = output_grad_or_ones(output);
396 let grad_2d = output_grad
397 .view()
398 .into_dimensionality::<Ix2>()
399 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
400
401 if self.left_grad.is_some() {
402 let right_2d = self
403 .right_data
404 .view()
405 .into_dimensionality::<Ix2>()
406 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
407 let grad_left = grad_2d.dot(&right_2d.t()).into_dyn();
408 accumulate_grad(&self.left_grad, &grad_left);
409 }
410
411 if self.right_grad.is_some() {
412 let left_2d = self
413 .left_data
414 .view()
415 .into_dimensionality::<Ix2>()
416 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
417 let grad_right = left_2d.t().dot(&grad_2d).into_dyn();
418 accumulate_grad(&self.right_grad, &grad_right);
419 }
420
421 Ok(())
422 }
423}
424
425#[derive(Debug)]
429struct AddGradFn {
430 left_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
431 right_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
432}
433
434impl GradFunction for AddGradFn {
435 fn backward(&self, output: &mut SciRS2Array) -> Result<()> {
436 let output_grad = output_grad_or_ones(output);
437 accumulate_grad(&self.left_grad, &output_grad);
438 accumulate_grad(&self.right_grad, &output_grad);
439 Ok(())
440 }
441}
442
443#[derive(Debug)]
446struct SubGradFn {
447 left_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
448 right_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
449}
450
451impl GradFunction for SubGradFn {
452 fn backward(&self, output: &mut SciRS2Array) -> Result<()> {
453 let output_grad = output_grad_or_ones(output);
454 accumulate_grad(&self.left_grad, &output_grad);
455 let negated = output_grad.mapv(|x| -x);
456 accumulate_grad(&self.right_grad, &negated);
457 Ok(())
458 }
459}
460
461#[derive(Debug)]
465struct MulGradFn {
466 left_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
467 right_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
468 left_data: ArrayD<f64>,
469 right_data: ArrayD<f64>,
470}
471
472impl GradFunction for MulGradFn {
473 fn backward(&self, output: &mut SciRS2Array) -> Result<()> {
474 let output_grad = output_grad_or_ones(output);
475 let grad_left = &output_grad * &self.right_data;
476 let grad_right = &output_grad * &self.left_data;
477 accumulate_grad(&self.left_grad, &grad_left);
478 accumulate_grad(&self.right_grad, &grad_right);
479 Ok(())
480 }
481}
482
483#[derive(Debug)]
487struct SumGradFn {
488 axis: Option<usize>,
489 input_shape: IxDyn,
490 input_grad: Option<Arc<Mutex<ArrayD<f64>>>>,
491}
492
493impl GradFunction for SumGradFn {
494 fn backward(&self, output: &mut SciRS2Array) -> Result<()> {
495 let output_grad = output_grad_or_ones(output);
496
497 let broadcasted = match self.axis {
498 None => {
499 let scalar = output_grad.iter().next().copied().unwrap_or(0.0);
500 ArrayD::from_elem(self.input_shape.clone(), scalar)
501 }
502 Some(ax) => {
503 let expanded = output_grad.insert_axis(Axis(ax));
504 expanded
505 .broadcast(self.input_shape.clone())
506 .ok_or_else(|| {
507 MLError::ComputationError(
508 "Failed to broadcast sum gradient back to input shape".to_string(),
509 )
510 })?
511 .to_owned()
512 }
513 };
514
515 accumulate_grad(&self.input_grad, &broadcasted);
516 Ok(())
517 }
518}
519
520pub struct SciRS2Optimizer {
522 pub optimizer_type: String,
524 pub config: HashMap<String, f64>,
526 pub state: HashMap<String, ArrayD<f64>>,
528}
529
530impl SciRS2Optimizer {
531 pub fn new(optimizer_type: impl Into<String>) -> Self {
533 Self {
534 optimizer_type: optimizer_type.into(),
535 config: HashMap::new(),
536 state: HashMap::new(),
537 }
538 }
539
540 pub fn with_config(mut self, key: impl Into<String>, value: f64) -> Self {
542 self.config.insert(key.into(), value);
543 self
544 }
545
546 pub fn step(&mut self, params: &mut HashMap<String, SciRS2Array>) -> Result<()> {
548 match self.optimizer_type.as_str() {
549 "adam" => self.adam_step(params),
550 "sgd" => self.sgd_step(params),
551 "lbfgs" => self.lbfgs_step(params),
552 _ => Err(MLError::InvalidConfiguration(format!(
553 "Unknown optimizer type: {}",
554 self.optimizer_type
555 ))),
556 }
557 }
558
559 fn adam_step(&mut self, params: &mut HashMap<String, SciRS2Array>) -> Result<()> {
561 let learning_rate = self.config.get("learning_rate").unwrap_or(&0.001);
562 let beta1 = self.config.get("beta1").unwrap_or(&0.9);
563 let beta2 = self.config.get("beta2").unwrap_or(&0.999);
564 let epsilon = self.config.get("epsilon").unwrap_or(&1e-8);
565
566 for (name, param) in params.iter_mut() {
567 let grad_cell = match ¶m.grad {
568 Some(cell) => cell.clone(),
569 None => continue,
570 };
571 let grad = lock_grad(&grad_cell).clone();
572 {
573 let m_key = format!("{}_m", name);
575 let v_key = format!("{}_v", name);
576
577 if !self.state.contains_key(&m_key) {
578 self.state
579 .insert(m_key.clone(), ArrayD::zeros(grad.raw_dim()));
580 self.state
581 .insert(v_key.clone(), ArrayD::zeros(grad.raw_dim()));
582 }
583
584 {
586 let m = self
587 .state
588 .get_mut(&m_key)
589 .expect("m_key was just inserted if not present");
590 *m = *beta1 * &*m + (1.0 - *beta1) * &grad;
591 }
592
593 {
595 let v = self
596 .state
597 .get_mut(&v_key)
598 .expect("v_key was just inserted if not present");
599 *v = *beta2 * &*v + (1.0 - *beta2) * &grad * &grad;
600 }
601
602 let m_hat = self
604 .state
605 .get(&m_key)
606 .expect("m_key exists after update")
607 .clone();
608 let v_hat = self
609 .state
610 .get(&v_key)
611 .expect("v_key exists after update")
612 .clone();
613
614 param.data =
616 ¶m.data - *learning_rate * &m_hat / (v_hat.mapv(|x| x.sqrt()) + *epsilon);
617 }
618 }
619
620 Ok(())
621 }
622
623 fn sgd_step(&mut self, params: &mut HashMap<String, SciRS2Array>) -> Result<()> {
625 let learning_rate = self.config.get("learning_rate").unwrap_or(&0.01);
626 let momentum = self.config.get("momentum").unwrap_or(&0.0);
627
628 for (name, param) in params.iter_mut() {
629 let grad_cell = match ¶m.grad {
630 Some(cell) => cell.clone(),
631 None => continue,
632 };
633 let grad = lock_grad(&grad_cell).clone();
634 {
635 if *momentum > 0.0 {
636 let v_key = format!("{}_v", name);
637 if !self.state.contains_key(&v_key) {
638 self.state
639 .insert(v_key.clone(), ArrayD::zeros(grad.raw_dim()));
640 }
641
642 let v = self
643 .state
644 .get_mut(&v_key)
645 .expect("v_key was just inserted if not present");
646 *v = *momentum * &*v + *learning_rate * &grad;
647 param.data = ¶m.data - &*v;
648 } else {
649 param.data = ¶m.data - *learning_rate * &grad;
650 }
651 }
652 }
653
654 Ok(())
655 }
656
657 fn lbfgs_step(&mut self, _params: &mut HashMap<String, SciRS2Array>) -> Result<()> {
659 Ok(())
661 }
662}
663
664pub struct SciRS2DistributedTrainer {
666 pub world_size: usize,
668 pub rank: usize,
670 pub backend: String,
672}
673
674impl SciRS2DistributedTrainer {
675 pub fn new(world_size: usize, rank: usize) -> Self {
677 Self {
678 world_size,
679 rank,
680 backend: "nccl".to_string(),
681 }
682 }
683
684 pub fn all_reduce(&self, tensor: &mut SciRS2Array) -> Result<()> {
686 Ok(())
688 }
689
690 pub fn all_reduce_scalar(&self, value: f64) -> Result<f64> {
692 Ok(value)
695 }
696
697 pub fn broadcast(&self, tensor: &mut SciRS2Array, root: usize) -> Result<()> {
699 Ok(())
701 }
702
703 pub fn all_gather(&self, tensor: &SciRS2Array) -> Result<Vec<SciRS2Array>> {
705 Ok(vec![tensor.clone(); self.world_size])
707 }
708
709 pub fn wrap_model<T>(&self, model: T) -> Result<T> {
711 Ok(model)
714 }
715}
716
717pub struct SciRS2Serializer;
719
720impl SciRS2Serializer {
721 pub fn save_model(params: &HashMap<String, SciRS2Array>, path: &str) -> Result<()> {
723 Ok(())
725 }
726
727 pub fn load_model(path: &str) -> Result<HashMap<String, SciRS2Array>> {
729 Ok(HashMap::new())
731 }
732
733 pub fn save_checkpoint(
735 params: &HashMap<String, SciRS2Array>,
736 optimizer: &SciRS2Optimizer,
737 epoch: usize,
738 path: &str,
739 ) -> Result<()> {
740 Ok(())
742 }
743
744 pub fn load_checkpoint(
746 path: &str,
747 ) -> Result<(HashMap<String, SciRS2Array>, SciRS2Optimizer, usize)> {
748 Ok((HashMap::new(), SciRS2Optimizer::new("adam"), 0))
750 }
751}
752
753pub struct SciRS2Dataset {
755 pub data: ArrayD<f64>,
757 pub labels: ArrayD<f64>,
759 pub size: usize,
761}
762
763impl SciRS2Dataset {
764 pub fn new(data: ArrayD<f64>, labels: ArrayD<f64>) -> Result<Self> {
766 let size = data.shape()[0];
767 if labels.shape()[0] != size {
768 return Err(MLError::InvalidConfiguration(
769 "Data and labels must have same number of samples".to_string(),
770 ));
771 }
772
773 Ok(Self { data, labels, size })
774 }
775}
776
777pub struct SciRS2DataLoader {
779 pub dataset: SciRS2Dataset,
781 pub batch_size: usize,
783 pub current_index: usize,
785}
786
787impl SciRS2DataLoader {
788 pub fn new(dataset: SciRS2Dataset, batch_size: usize) -> Self {
790 Self {
791 dataset,
792 batch_size,
793 current_index: 0,
794 }
795 }
796
797 pub fn enumerate(&mut self) -> DataLoaderIterator {
799 DataLoaderIterator {
800 loader: self,
801 batch_idx: 0,
802 }
803 }
804}
805
806pub struct DataLoaderIterator<'a> {
808 loader: &'a mut SciRS2DataLoader,
809 batch_idx: usize,
810}
811
812impl<'a> Iterator for DataLoaderIterator<'a> {
813 type Item = (usize, (SciRS2Array, SciRS2Array));
814
815 fn next(&mut self) -> Option<Self::Item> {
816 if self.loader.current_index >= self.loader.dataset.size {
817 return None;
818 }
819
820 let start = self.loader.current_index;
821 let end = (start + self.loader.batch_size).min(self.loader.dataset.size);
822
823 let batch_data = self
825 .loader
826 .dataset
827 .data
828 .slice(scirs2_core::ndarray::s![start..end, ..])
829 .to_owned();
830 let batch_labels = self
831 .loader
832 .dataset
833 .labels
834 .slice(scirs2_core::ndarray::s![start..end, ..])
835 .to_owned();
836
837 let data_array = SciRS2Array::from_array(batch_data);
838 let label_array = SciRS2Array::from_array(batch_labels);
839
840 self.loader.current_index = end;
841 let batch_idx = self.batch_idx;
842 self.batch_idx += 1;
843
844 Some((batch_idx, (data_array, label_array)))
845 }
846}
847
848#[derive(Debug, Clone, Copy)]
850pub enum SciRS2Device {
851 CPU,
852 GPU,
853 Quantum,
854}
855
856impl SciRS2Array {
858 pub fn randn(shape: Vec<usize>, device: SciRS2Device) -> Result<Self> {
860 use scirs2_core::random::prelude::*;
861 let total_size = shape.iter().product();
862 let mut rng = thread_rng();
863 let data: Vec<f64> = (0..total_size)
864 .map(|_| rng.random_range(-1.0..1.0))
865 .collect();
866 let array = ArrayD::from_shape_vec(IxDyn(&shape), data)
867 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
868 Ok(Self::new(array, false))
869 }
870
871 pub fn ones_like(&self) -> Result<Self> {
873 let ones = ArrayD::ones(self.data.raw_dim());
874 Ok(Self::new(ones, false))
875 }
876
877 pub fn randint(low: i32, high: i32, shape: Vec<usize>, device: SciRS2Device) -> Result<Self> {
879 use scirs2_core::random::prelude::*;
880 let total_size = shape.iter().product();
881 let mut rng = thread_rng();
882 let data: Vec<f64> = (0..total_size)
883 .map(|_| rng.random_range(low..high) as f64)
884 .collect();
885 let array = ArrayD::from_shape_vec(IxDyn(&shape), data)
886 .map_err(|e| MLError::ComputationError(format!("Shape error: {}", e)))?;
887 Ok(Self::new(array, false))
888 }
889
890 pub fn quantum_observable(name: &str, num_qubits: usize) -> Result<Self> {
892 match name {
893 "pauli_z_all" => {
894 let size = 1 << num_qubits;
895 let mut data = ArrayD::zeros(IxDyn(&[size, size]));
896 for i in 0..size {
897 let parity = i.count_ones() % 2;
898 data[[i, i]] = if parity == 0 { 1.0 } else { -1.0 };
899 }
900 Ok(Self::new(data, false))
901 }
902 _ => Err(MLError::InvalidConfiguration(format!(
903 "Unknown observable: {}",
904 name
905 ))),
906 }
907 }
908}
909
910pub mod integration {
912 use super::*;
913
914 pub fn from_ndarray<D: Dimension>(arr: Array<f64, D>) -> SciRS2Array {
916 SciRS2Array::from_array(arr)
917 }
918
919 pub fn to_ndarray<D: Dimension>(arr: &SciRS2Array) -> Result<Array<f64, D>> {
921 arr.data
922 .view()
923 .into_dimensionality::<D>()
924 .map(|v| v.to_owned())
925 .map_err(|e| MLError::ComputationError(format!("Dimension error: {}", e)))
926 }
927
928 pub fn create_optimizer(optimizer_type: &str, config: HashMap<String, f64>) -> SciRS2Optimizer {
930 let mut optimizer = SciRS2Optimizer::new(optimizer_type);
931 for (key, value) in config {
932 optimizer = optimizer.with_config(key, value);
933 }
934 optimizer
935 }
936
937 pub fn setup_distributed(world_size: usize, rank: usize) -> SciRS2DistributedTrainer {
939 SciRS2DistributedTrainer::new(world_size, rank)
940 }
941}
942
943#[cfg(test)]
944mod tests {
945 use super::*;
946 use scirs2_core::ndarray::Array2;
947
948 #[test]
949 fn test_scirs2_array_creation() {
950 let arr = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
951 .expect("valid shape for 2x2 array");
952 let scirs2_arr = SciRS2Array::from_array(arr);
953
954 assert_eq!(scirs2_arr.data.shape(), &[2, 2]);
955 assert!(!scirs2_arr.requires_grad);
956 }
957
958 #[test]
959 fn test_scirs2_array_with_grad() {
960 let arr = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
961 .expect("valid shape for 2x2 array");
962 let scirs2_arr = SciRS2Array::with_grad(arr);
963
964 assert!(scirs2_arr.requires_grad);
965 assert!(scirs2_arr.grad.is_some());
966 }
967
968 #[test]
969 fn test_scirs2_matmul() {
970 let arr1 = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
971 .expect("valid shape for 2x3 array");
972 let arr2 = Array2::from_shape_vec((3, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
973 .expect("valid shape for 3x2 array");
974
975 let scirs2_arr1 = SciRS2Array::from_array(arr1);
976 let scirs2_arr2 = SciRS2Array::from_array(arr2);
977
978 let result = scirs2_arr1
979 .matmul(&scirs2_arr2)
980 .expect("matmul should succeed for compatible shapes");
981 assert_eq!(result.data.shape(), &[2, 2]);
982 }
983
984 #[test]
985 fn test_scirs2_optimizer() {
986 let mut optimizer = SciRS2Optimizer::new("adam")
987 .with_config("learning_rate", 0.001)
988 .with_config("beta1", 0.9);
989
990 let mut params = HashMap::new();
991 let param_arr = SciRS2Array::with_grad(Array1::from_vec(vec![1.0, 2.0, 3.0]));
992 params.insert("weight".to_string(), param_arr);
993
994 let result = optimizer.step(&mut params);
995 assert!(result.is_ok());
996 }
997
998 #[test]
1007 fn test_matmul_mul_sum_backward_are_real() {
1008 let a_arr = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
1011 .expect("valid shape for 2x2 array");
1012 let b_arr = Array2::from_shape_vec((2, 2), vec![5.0, 6.0, 7.0, 8.0])
1013 .expect("valid shape for 2x2 array");
1014 let a = SciRS2Array::with_grad(a_arr.clone());
1015 let b = SciRS2Array::with_grad(b_arr.clone());
1016
1017 let mut c = a.matmul(&b).expect("matmul should succeed");
1018 let seed = Array2::from_shape_vec((2, 2), vec![1.0, 0.0, 0.0, 1.0])
1019 .expect("valid shape for 2x2 array")
1020 .into_dyn();
1021 c.set_grad(seed.clone());
1022 c.backward().expect("matmul backward should succeed");
1023
1024 let seed_2d = seed.into_dimensionality::<Ix2>().expect("2d seed");
1027 let expected_grad_a = seed_2d.dot(&b_arr.t());
1028 let expected_grad_b = a_arr.t().dot(&seed_2d);
1029
1030 let grad_a = a
1031 .grad
1032 .as_ref()
1033 .expect("a should have a grad cell")
1034 .lock()
1035 .expect("grad lock should not be poisoned")
1036 .clone();
1037 let grad_b = b
1038 .grad
1039 .as_ref()
1040 .expect("b should have a grad cell")
1041 .lock()
1042 .expect("grad lock should not be poisoned")
1043 .clone();
1044
1045 for ((i, j), &expected) in expected_grad_a.indexed_iter() {
1046 assert!(
1047 (grad_a[[i, j]] - expected).abs() < 1e-9,
1048 "matmul dL/dA mismatch at ({i},{j}): got {}, expected {expected}",
1049 grad_a[[i, j]]
1050 );
1051 }
1052 for ((i, j), &expected) in expected_grad_b.indexed_iter() {
1053 assert!(
1054 (grad_b[[i, j]] - expected).abs() < 1e-9,
1055 "matmul dL/dB mismatch at ({i},{j}): got {}, expected {expected}",
1056 grad_b[[i, j]]
1057 );
1058 }
1059
1060 let x_arr = Array1::from_vec(vec![2.0, 3.0, 4.0]);
1062 let y_arr = Array1::from_vec(vec![10.0, 20.0, 30.0]);
1063 let x = SciRS2Array::with_grad(x_arr.clone());
1064 let y = SciRS2Array::with_grad(y_arr.clone());
1065
1066 let mut z = x.mul(&y).expect("mul should succeed");
1067 z.set_grad(ArrayD::from_elem(IxDyn(&[3]), 2.0));
1068 z.backward().expect("mul backward should succeed");
1069
1070 let grad_x = x
1071 .grad
1072 .as_ref()
1073 .expect("x should have a grad cell")
1074 .lock()
1075 .expect("grad lock should not be poisoned")
1076 .clone();
1077 let grad_y = y
1078 .grad
1079 .as_ref()
1080 .expect("y should have a grad cell")
1081 .lock()
1082 .expect("grad lock should not be poisoned")
1083 .clone();
1084
1085 for (i, (&gx, &yv)) in grad_x.iter().zip(y_arr.iter()).enumerate() {
1086 assert!(
1087 (gx - 2.0 * yv).abs() < 1e-9,
1088 "mul dL/dx mismatch at {i}: got {gx}, expected {}",
1089 2.0 * yv
1090 );
1091 }
1092 for (i, (&gy, &xv)) in grad_y.iter().zip(x_arr.iter()).enumerate() {
1093 assert!(
1094 (gy - 2.0 * xv).abs() < 1e-9,
1095 "mul dL/dy mismatch at {i}: got {gy}, expected {}",
1096 2.0 * xv
1097 );
1098 }
1099
1100 let s_arr = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
1102 let s = SciRS2Array::with_grad(s_arr);
1103 let mut total = s.sum(None).expect("sum should succeed");
1104 total.set_grad(ArrayD::from_elem(IxDyn(&[]), 3.5));
1105 total.backward().expect("sum backward should succeed");
1106
1107 let grad_s = s
1108 .grad
1109 .as_ref()
1110 .expect("s should have a grad cell")
1111 .lock()
1112 .expect("grad lock should not be poisoned")
1113 .clone();
1114 for &g in grad_s.iter() {
1115 assert!(
1116 (g - 3.5).abs() < 1e-9,
1117 "sum backward did not broadcast the seed gradient: got {g}, expected 3.5"
1118 );
1119 }
1120 }
1121
1122 #[test]
1123 fn test_integration_helpers() {
1124 let arr = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
1125 .expect("valid shape for 2x2 array");
1126 let scirs2_arr = integration::from_ndarray(arr.clone());
1127
1128 let back_to_ndarray: Array2<f64> = integration::to_ndarray(&scirs2_arr)
1129 .expect("conversion back to ndarray should succeed");
1130 assert_eq!(arr, back_to_ndarray);
1131 }
1132}