1use crate::circuit_integration::{QuantumLayer, QuantumMLExecutor};
7use crate::error::{MLError, Result};
8use crate::simulator_backends::{
9 BackendCapabilities, DynamicCircuit, Observable, SimulatorBackend, StatevectorBackend,
10};
11use quantrs2_circuit::prelude::*;
12use quantrs2_core::prelude::*;
13use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayD, Axis, IxDyn};
14use std::collections::HashMap;
15use std::sync::Arc;
16
17pub trait KerasLayer: Send + Sync {
19 fn build(&mut self, input_shape: &[usize]) -> Result<()>;
21
22 fn call(&self, inputs: &ArrayD<f64>) -> Result<ArrayD<f64>>;
24
25 fn compute_output_shape(&self, input_shape: &[usize]) -> Vec<usize>;
27
28 fn name(&self) -> &str;
30
31 fn get_weights(&self) -> Vec<ArrayD<f64>>;
33
34 fn set_weights(&mut self, weights: Vec<ArrayD<f64>>) -> Result<()>;
36
37 fn count_params(&self) -> usize {
39 self.get_weights().iter().map(|w| w.len()).sum()
40 }
41
42 fn built(&self) -> bool;
44}
45
46pub struct Dense {
48 units: usize,
50 activation: Option<ActivationFunction>,
52 use_bias: bool,
54 kernel_initializer: InitializerType,
56 bias_initializer: InitializerType,
58 name: String,
60 built: bool,
62 input_shape: Option<Vec<usize>>,
64 weights: Vec<ArrayD<f64>>,
66}
67
68impl Dense {
69 pub fn new(units: usize) -> Self {
71 Self {
72 units,
73 activation: None,
74 use_bias: true,
75 kernel_initializer: InitializerType::GlorotUniform,
76 bias_initializer: InitializerType::Zeros,
77 name: format!("dense_{}", fastrand::u32(..)),
78 built: false,
79 input_shape: None,
80 weights: Vec::new(),
81 }
82 }
83
84 pub fn activation(mut self, activation: ActivationFunction) -> Self {
86 self.activation = Some(activation);
87 self
88 }
89
90 pub fn use_bias(mut self, use_bias: bool) -> Self {
92 self.use_bias = use_bias;
93 self
94 }
95
96 pub fn name(mut self, name: impl Into<String>) -> Self {
98 self.name = name.into();
99 self
100 }
101
102 pub fn kernel_initializer(mut self, initializer: InitializerType) -> Self {
104 self.kernel_initializer = initializer;
105 self
106 }
107}
108
109impl KerasLayer for Dense {
110 fn build(&mut self, input_shape: &[usize]) -> Result<()> {
111 if input_shape.is_empty() {
112 return Err(MLError::InvalidConfiguration(
113 "Dense layer requires input shape".to_string(),
114 ));
115 }
116
117 let input_dim = input_shape[input_shape.len() - 1];
118 self.input_shape = Some(input_shape.to_vec());
119
120 let kernel = self.initialize_weights(&[input_dim, self.units], &self.kernel_initializer)?;
122 self.weights.push(kernel);
123
124 if self.use_bias {
126 let bias = self.initialize_weights(&[self.units], &self.bias_initializer)?;
127 self.weights.push(bias);
128 }
129
130 self.built = true;
131 Ok(())
132 }
133
134 fn call(&self, inputs: &ArrayD<f64>) -> Result<ArrayD<f64>> {
135 if !self.built {
136 return Err(MLError::InvalidConfiguration(
137 "Layer must be built before calling".to_string(),
138 ));
139 }
140
141 let kernel = &self.weights[0];
142 let outputs = match (inputs.ndim(), kernel.ndim()) {
144 (2, 2) => {
145 let inputs_2d = inputs
147 .clone()
148 .into_dimensionality::<scirs2_core::ndarray::Ix2>()
149 .map_err(|_| MLError::InvalidConfiguration("Input must be 2D".to_string()))?;
150 let kernel_2d = kernel
151 .clone()
152 .into_dimensionality::<scirs2_core::ndarray::Ix2>()
153 .map_err(|_| MLError::InvalidConfiguration("Kernel must be 2D".to_string()))?;
154 inputs_2d.dot(&kernel_2d).into_dyn()
155 }
156 _ => {
157 return Err(MLError::InvalidConfiguration(
158 "Unsupported array dimensions for matrix multiplication".to_string(),
159 ));
160 }
161 };
162 let mut outputs = outputs;
163
164 if self.use_bias && self.weights.len() > 1 {
166 let bias = &self.weights[1];
167 outputs = outputs + bias;
168 }
169
170 if let Some(ref activation) = self.activation {
172 outputs = self.apply_activation(&outputs, activation)?;
173 }
174
175 Ok(outputs)
176 }
177
178 fn compute_output_shape(&self, input_shape: &[usize]) -> Vec<usize> {
179 let mut output_shape = input_shape.to_vec();
180 let last_idx = output_shape.len() - 1;
181 output_shape[last_idx] = self.units;
182 output_shape
183 }
184
185 fn name(&self) -> &str {
186 &self.name
187 }
188
189 fn get_weights(&self) -> Vec<ArrayD<f64>> {
190 self.weights.clone()
191 }
192
193 fn set_weights(&mut self, weights: Vec<ArrayD<f64>>) -> Result<()> {
194 if weights.len() != self.weights.len() {
195 return Err(MLError::InvalidConfiguration(
196 "Number of weight arrays doesn't match layer structure".to_string(),
197 ));
198 }
199 self.weights = weights;
200 Ok(())
201 }
202
203 fn built(&self) -> bool {
204 self.built
205 }
206}
207
208impl Dense {
209 fn initialize_weights(
211 &self,
212 shape: &[usize],
213 initializer: &InitializerType,
214 ) -> Result<ArrayD<f64>> {
215 match initializer {
216 InitializerType::Zeros => Ok(ArrayD::zeros(shape)),
217 InitializerType::Ones => Ok(ArrayD::ones(shape)),
218 InitializerType::GlorotUniform => {
219 let fan_in = if shape.len() >= 2 { shape[0] } else { 1 };
220 let fan_out = if shape.len() >= 2 { shape[1] } else { shape[0] };
221 let limit = (6.0 / (fan_in + fan_out) as f64).sqrt();
222
223 Ok(ArrayD::from_shape_fn(shape, |_| {
224 fastrand::f64() * 2.0 * limit - limit
225 }))
226 }
227 InitializerType::GlorotNormal => {
228 let fan_in = if shape.len() >= 2 { shape[0] } else { 1 };
229 let fan_out = if shape.len() >= 2 { shape[1] } else { shape[0] };
230 let std = (2.0 / (fan_in + fan_out) as f64).sqrt();
231
232 Ok(ArrayD::from_shape_fn(shape, |_| {
233 let u1 = fastrand::f64();
235 let u2 = fastrand::f64();
236 let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
237 z * std
238 }))
239 }
240 InitializerType::HeUniform => {
241 let fan_in = if shape.len() >= 2 { shape[0] } else { 1 };
242 let limit = (6.0 / fan_in as f64).sqrt();
243
244 Ok(ArrayD::from_shape_fn(shape, |_| {
245 fastrand::f64() * 2.0 * limit - limit
246 }))
247 }
248 }
249 }
250
251 fn apply_activation(
253 &self,
254 inputs: &ArrayD<f64>,
255 activation: &ActivationFunction,
256 ) -> Result<ArrayD<f64>> {
257 Ok(match activation {
258 ActivationFunction::Linear => inputs.clone(),
259 ActivationFunction::ReLU => inputs.mapv(|x| x.max(0.0)),
260 ActivationFunction::Sigmoid => inputs.mapv(|x| 1.0 / (1.0 + (-x).exp())),
261 ActivationFunction::Tanh => inputs.mapv(|x| x.tanh()),
262 ActivationFunction::Softmax => {
263 let mut outputs = inputs.clone();
264 for mut row in outputs.axis_iter_mut(Axis(0)) {
265 let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
266 row.mapv_inplace(|x| (x - max_val).exp());
267 let sum = row.sum();
268 row /= sum;
269 }
270 outputs
271 }
272 ActivationFunction::LeakyReLU(alpha) => {
273 inputs.mapv(|x| if x > 0.0 { x } else { alpha * x })
274 }
275 ActivationFunction::ELU(alpha) => {
276 inputs.mapv(|x| if x > 0.0 { x } else { alpha * (x.exp() - 1.0) })
277 }
278 })
279 }
280}
281
282pub struct QuantumDense {
284 num_qubits: usize,
286 units: usize,
288 ansatz_type: QuantumAnsatzType,
290 num_layers: usize,
292 observable: Observable,
294 backend: Arc<dyn SimulatorBackend>,
296 name: String,
298 built: bool,
300 input_shape: Option<Vec<usize>>,
302 quantum_weights: Vec<ArrayD<f64>>,
304}
305
306#[derive(Debug, Clone)]
308pub enum QuantumAnsatzType {
309 HardwareEfficient,
311 RealAmplitudes,
313 StronglyEntangling,
315 Custom(DynamicCircuit),
317}
318
319impl QuantumDense {
320 pub fn new(num_qubits: usize, units: usize) -> Self {
322 Self {
323 num_qubits,
324 units,
325 ansatz_type: QuantumAnsatzType::HardwareEfficient,
326 num_layers: 1,
327 observable: Observable::PauliZ(vec![0]),
328 backend: Arc::new(StatevectorBackend::new(10)),
329 name: format!("quantum_dense_{}", fastrand::u32(..)),
330 built: false,
331 input_shape: None,
332 quantum_weights: Vec::new(),
333 }
334 }
335
336 pub fn ansatz_type(mut self, ansatz_type: QuantumAnsatzType) -> Self {
338 self.ansatz_type = ansatz_type;
339 self
340 }
341
342 pub fn num_layers(mut self, num_layers: usize) -> Self {
344 self.num_layers = num_layers;
345 self
346 }
347
348 pub fn observable(mut self, observable: Observable) -> Self {
350 self.observable = observable;
351 self
352 }
353
354 pub fn backend(mut self, backend: Arc<dyn SimulatorBackend>) -> Self {
356 self.backend = backend;
357 self
358 }
359
360 pub fn name(mut self, name: impl Into<String>) -> Self {
362 self.name = name.into();
363 self
364 }
365}
366
367impl KerasLayer for QuantumDense {
368 fn build(&mut self, input_shape: &[usize]) -> Result<()> {
369 self.input_shape = Some(input_shape.to_vec());
370
371 let num_params = match &self.ansatz_type {
373 QuantumAnsatzType::HardwareEfficient => {
374 self.num_qubits * 2 * self.num_layers
376 }
377 QuantumAnsatzType::RealAmplitudes => {
378 self.num_qubits * self.num_layers
380 }
381 QuantumAnsatzType::StronglyEntangling => {
382 self.num_qubits * 3 * self.num_layers
384 }
385 QuantumAnsatzType::Custom(_) => {
386 10 }
389 };
390
391 let params = ArrayD::from_shape_fn(IxDyn(&[self.units, num_params]), |_| {
393 fastrand::f64() * 2.0 * std::f64::consts::PI
394 });
395 self.quantum_weights.push(params);
396
397 self.built = true;
398 Ok(())
399 }
400
401 fn call(&self, inputs: &ArrayD<f64>) -> Result<ArrayD<f64>> {
402 if !self.built {
403 return Err(MLError::InvalidConfiguration(
404 "Layer must be built before calling".to_string(),
405 ));
406 }
407
408 let batch_size = inputs.shape()[0];
409 let mut outputs = ArrayD::zeros(IxDyn(&[batch_size, self.units]));
410
411 for batch_idx in 0..batch_size {
412 for unit_idx in 0..self.units {
413 let circuit = self.build_quantum_circuit()?;
415
416 let input_slice = inputs.slice(s![batch_idx, ..]);
418 let param_slice = self.quantum_weights[0].slice(s![unit_idx, ..]);
419
420 let combined_params: Vec<f64> = input_slice
422 .iter()
423 .chain(param_slice.iter())
424 .copied()
425 .collect();
426
427 let expectation =
429 self.backend
430 .expectation_value(&circuit, &combined_params, &self.observable)?;
431
432 outputs[[batch_idx, unit_idx]] = expectation;
433 }
434 }
435
436 Ok(outputs)
437 }
438
439 fn compute_output_shape(&self, input_shape: &[usize]) -> Vec<usize> {
440 let mut output_shape = input_shape.to_vec();
441 let last_idx = output_shape.len() - 1;
442 output_shape[last_idx] = self.units;
443 output_shape
444 }
445
446 fn name(&self) -> &str {
447 &self.name
448 }
449
450 fn get_weights(&self) -> Vec<ArrayD<f64>> {
451 self.quantum_weights.clone()
452 }
453
454 fn set_weights(&mut self, weights: Vec<ArrayD<f64>>) -> Result<()> {
455 if weights.len() != self.quantum_weights.len() {
456 return Err(MLError::InvalidConfiguration(
457 "Number of weight arrays doesn't match layer structure".to_string(),
458 ));
459 }
460 self.quantum_weights = weights;
461 Ok(())
462 }
463
464 fn built(&self) -> bool {
465 self.built
466 }
467}
468
469impl QuantumDense {
470 fn build_quantum_circuit(&self) -> Result<DynamicCircuit> {
472 let mut builder: Circuit<8> = Circuit::new();
473
474 match &self.ansatz_type {
475 QuantumAnsatzType::HardwareEfficient => {
476 for layer in 0..self.num_layers {
477 if layer == 0 {
479 for qubit in 0..self.num_qubits {
480 builder.ry(qubit, 0.0)?; }
482 }
483
484 for qubit in 0..self.num_qubits {
486 builder.ry(qubit, 0.0)?; builder.rz(qubit, 0.0)?; }
489
490 for qubit in 0..self.num_qubits - 1 {
492 builder.cnot(qubit, qubit + 1)?;
493 }
494 if self.num_qubits > 2 {
495 builder.cnot(self.num_qubits - 1, 0)?;
496 }
497 }
498 }
499 QuantumAnsatzType::RealAmplitudes => {
500 for layer in 0..self.num_layers {
501 if layer == 0 {
503 for qubit in 0..self.num_qubits {
504 builder.ry(qubit, 0.0)?; }
506 }
507
508 for qubit in 0..self.num_qubits {
510 builder.ry(qubit, 0.0)?; }
512
513 for qubit in 0..self.num_qubits - 1 {
515 builder.cnot(qubit, qubit + 1)?;
516 }
517 }
518 }
519 QuantumAnsatzType::StronglyEntangling => {
520 for layer in 0..self.num_layers {
521 if layer == 0 {
523 for qubit in 0..self.num_qubits {
524 builder.ry(qubit, 0.0)?; }
526 }
527
528 for qubit in 0..self.num_qubits {
530 builder.rx(qubit, 0.0)?; builder.ry(qubit, 0.0)?; builder.rz(qubit, 0.0)?; }
534
535 for qubit in 0..self.num_qubits - 1 {
537 builder.cnot(qubit, qubit + 1)?;
538 }
539 if self.num_qubits > 2 {
540 builder.cnot(self.num_qubits - 1, 0)?;
541 }
542 }
543 }
544 QuantumAnsatzType::Custom(circuit) => {
545 return Ok(circuit.clone());
546 }
547 }
548
549 let circuit = builder.build();
550 DynamicCircuit::from_circuit(circuit)
551 }
552}
553
554#[derive(Debug, Clone)]
556pub enum ActivationFunction {
557 Linear,
559 ReLU,
561 Sigmoid,
563 Tanh,
565 Softmax,
567 LeakyReLU(f64),
569 ELU(f64),
571}
572
573pub struct Activation {
575 function: ActivationFunction,
577 name: String,
579 built: bool,
581}
582
583impl Activation {
584 pub fn new(function: ActivationFunction) -> Self {
586 Self {
587 function,
588 name: format!("activation_{}", fastrand::u32(..)),
589 built: false,
590 }
591 }
592
593 pub fn name(mut self, name: impl Into<String>) -> Self {
595 self.name = name.into();
596 self
597 }
598}
599
600impl KerasLayer for Activation {
601 fn build(&mut self, _input_shape: &[usize]) -> Result<()> {
602 self.built = true;
603 Ok(())
604 }
605
606 fn call(&self, inputs: &ArrayD<f64>) -> Result<ArrayD<f64>> {
607 Ok(match &self.function {
608 ActivationFunction::Linear => inputs.clone(),
609 ActivationFunction::ReLU => inputs.mapv(|x| x.max(0.0)),
610 ActivationFunction::Sigmoid => inputs.mapv(|x| 1.0 / (1.0 + (-x).exp())),
611 ActivationFunction::Tanh => inputs.mapv(|x| x.tanh()),
612 ActivationFunction::Softmax => {
613 let mut outputs = inputs.clone();
614 for mut row in outputs.axis_iter_mut(Axis(0)) {
615 let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
616 row.mapv_inplace(|x| (x - max_val).exp());
617 let sum = row.sum();
618 row /= sum;
619 }
620 outputs
621 }
622 ActivationFunction::LeakyReLU(alpha) => {
623 inputs.mapv(|x| if x > 0.0 { x } else { alpha * x })
624 }
625 ActivationFunction::ELU(alpha) => {
626 inputs.mapv(|x| if x > 0.0 { x } else { alpha * (x.exp() - 1.0) })
627 }
628 })
629 }
630
631 fn compute_output_shape(&self, input_shape: &[usize]) -> Vec<usize> {
632 input_shape.to_vec()
633 }
634
635 fn name(&self) -> &str {
636 &self.name
637 }
638
639 fn get_weights(&self) -> Vec<ArrayD<f64>> {
640 Vec::new()
641 }
642
643 fn set_weights(&mut self, _weights: Vec<ArrayD<f64>>) -> Result<()> {
644 Ok(())
645 }
646
647 fn built(&self) -> bool {
648 self.built
649 }
650}
651
652#[derive(Debug, Clone)]
654pub enum InitializerType {
655 Zeros,
657 Ones,
659 GlorotUniform,
661 GlorotNormal,
663 HeUniform,
665}
666
667pub struct Sequential {
669 layers: Vec<Box<dyn KerasLayer>>,
671 name: String,
673 built: bool,
675 compiled: bool,
677 input_shape: Option<Vec<usize>>,
679 loss: Option<LossFunction>,
681 optimizer: Option<OptimizerType>,
683 metrics: Vec<MetricType>,
685}
686
687impl Sequential {
688 pub fn new() -> Self {
690 Self {
691 layers: Vec::new(),
692 name: format!("sequential_{}", fastrand::u32(..)),
693 built: false,
694 compiled: false,
695 input_shape: None,
696 loss: None,
697 optimizer: None,
698 metrics: Vec::new(),
699 }
700 }
701
702 pub fn name(mut self, name: impl Into<String>) -> Self {
704 self.name = name.into();
705 self
706 }
707
708 pub fn add(&mut self, layer: Box<dyn KerasLayer>) {
710 self.layers.push(layer);
711 self.built = false; }
713
714 pub fn build(&mut self, input_shape: Vec<usize>) -> Result<()> {
716 self.input_shape = Some(input_shape.clone());
717 let mut current_shape = input_shape;
718
719 for layer in &mut self.layers {
720 layer.build(¤t_shape)?;
721 current_shape = layer.compute_output_shape(¤t_shape);
722 }
723
724 self.built = true;
725 Ok(())
726 }
727
728 pub fn compile(
730 mut self,
731 loss: LossFunction,
732 optimizer: OptimizerType,
733 metrics: Vec<MetricType>,
734 ) -> Self {
735 self.loss = Some(loss);
736 self.optimizer = Some(optimizer);
737 self.metrics = metrics;
738 self.compiled = true;
739 self
740 }
741
742 pub fn summary(&self) -> ModelSummary {
744 let mut layers_info = Vec::new();
745 let mut total_params = 0;
746 let mut trainable_params = 0;
747
748 let mut current_shape = self.input_shape.clone().unwrap_or_default();
749
750 for layer in &self.layers {
751 let output_shape = layer.compute_output_shape(¤t_shape);
752 let params = layer.count_params();
753
754 layers_info.push(LayerInfo {
755 name: layer.name().to_string(),
756 layer_type: "Layer".to_string(), output_shape: output_shape.clone(),
758 param_count: params,
759 });
760
761 total_params += params;
762 trainable_params += params; current_shape = output_shape;
764 }
765
766 ModelSummary {
767 layers: layers_info,
768 total_params,
769 trainable_params,
770 non_trainable_params: 0,
771 }
772 }
773
774 pub fn predict(&self, inputs: &ArrayD<f64>) -> Result<ArrayD<f64>> {
776 if !self.built {
777 return Err(MLError::InvalidConfiguration(
778 "Model must be built before prediction".to_string(),
779 ));
780 }
781
782 let mut current = inputs.clone();
783
784 for layer in &self.layers {
785 current = layer.call(¤t)?;
786 }
787
788 Ok(current)
789 }
790
791 pub fn fit(
793 &mut self,
794 X: &ArrayD<f64>,
795 y: &ArrayD<f64>,
796 epochs: usize,
797 batch_size: Option<usize>,
798 validation_data: Option<(&ArrayD<f64>, &ArrayD<f64>)>,
799 callbacks: Vec<Box<dyn Callback>>,
800 ) -> Result<TrainingHistory> {
801 if !self.compiled {
802 return Err(MLError::InvalidConfiguration(
803 "Model must be compiled before training".to_string(),
804 ));
805 }
806
807 let batch_size = batch_size.unwrap_or(32);
808 let n_samples = X.shape()[0];
809 let n_batches = (n_samples + batch_size - 1) / batch_size;
810
811 let mut history = TrainingHistory::new();
812
813 for epoch in 0..epochs {
814 let mut epoch_loss = 0.0;
815 let mut epoch_metrics: HashMap<String, f64> = HashMap::new();
816
817 for metric in &self.metrics {
819 epoch_metrics.insert(metric.name(), 0.0);
820 }
821
822 for batch_idx in 0..n_batches {
824 let start_idx = batch_idx * batch_size;
825 let end_idx = ((batch_idx + 1) * batch_size).min(n_samples);
826
827 let X_batch = X.slice(s![start_idx..end_idx, ..]);
829 let y_batch = y.slice(s![start_idx..end_idx, ..]);
830
831 let predictions = self.predict(&X_batch.to_owned().into_dyn())?;
833
834 let loss = self.compute_loss(&predictions, &y_batch.to_owned().into_dyn())?;
836 epoch_loss += loss;
837
838 self.backward_pass(&predictions, &y_batch.to_owned().into_dyn())?;
840
841 for metric in &self.metrics {
843 let metric_value =
844 metric.compute(&predictions, &y_batch.to_owned().into_dyn())?;
845 *epoch_metrics.entry(metric.name()).or_insert(0.0) += metric_value;
846 }
847 }
848
849 epoch_loss /= n_batches as f64;
851 for value in epoch_metrics.values_mut() {
852 *value /= n_batches as f64;
853 }
854
855 let (val_loss, val_metrics) = if let Some((X_val, y_val)) = validation_data {
857 let val_predictions = self.predict(X_val)?;
858 let val_loss = self.compute_loss(&val_predictions, y_val)?;
859
860 let mut val_metrics = HashMap::new();
861 for metric in &self.metrics {
862 let metric_value = metric.compute(&val_predictions, y_val)?;
863 val_metrics.insert(format!("val_{}", metric.name()), metric_value);
864 }
865
866 (Some(val_loss), val_metrics)
867 } else {
868 (None, HashMap::new())
869 };
870
871 history.add_epoch(epoch_loss, epoch_metrics, val_loss, val_metrics);
873
874 for callback in &callbacks {
876 callback.on_epoch_end(epoch, &history)?;
877 }
878
879 println!("Epoch {}/{} - loss: {:.4}", epoch + 1, epochs, epoch_loss);
880 }
881
882 Ok(history)
883 }
884
885 pub fn evaluate(
887 &self,
888 X: &ArrayD<f64>,
889 y: &ArrayD<f64>,
890 batch_size: Option<usize>,
891 ) -> Result<HashMap<String, f64>> {
892 let predictions = self.predict(X)?;
893 let loss = self.compute_loss(&predictions, y)?;
894
895 let mut results = HashMap::new();
896 results.insert("loss".to_string(), loss);
897
898 for metric in &self.metrics {
899 let metric_value = metric.compute(&predictions, y)?;
900 results.insert(metric.name(), metric_value);
901 }
902
903 Ok(results)
904 }
905
906 fn compute_loss(&self, predictions: &ArrayD<f64>, targets: &ArrayD<f64>) -> Result<f64> {
908 if let Some(ref loss_fn) = self.loss {
909 loss_fn.compute(predictions, targets)
910 } else {
911 Err(MLError::InvalidConfiguration(
912 "Loss function not specified".to_string(),
913 ))
914 }
915 }
916
917 fn backward_pass(&mut self, _predictions: &ArrayD<f64>, _targets: &ArrayD<f64>) -> Result<()> {
919 Ok(())
922 }
923}
924
925#[derive(Debug, Clone)]
927pub enum LossFunction {
928 MeanSquaredError,
930 BinaryCrossentropy,
932 CategoricalCrossentropy,
934 SparseCategoricalCrossentropy,
936 MeanAbsoluteError,
938 Huber(f64),
940}
941
942impl LossFunction {
943 pub fn compute(&self, predictions: &ArrayD<f64>, targets: &ArrayD<f64>) -> Result<f64> {
945 match self {
946 LossFunction::MeanSquaredError => {
947 let diff = predictions - targets;
948 diff.mapv(|x| x * x).mean().ok_or_else(|| {
949 MLError::ComputationError("Failed to compute mean of empty array".to_string())
950 })
951 }
952 LossFunction::BinaryCrossentropy => {
953 let epsilon = 1e-15;
954 let clipped_preds = predictions.mapv(|x| x.max(epsilon).min(1.0 - epsilon));
955 let loss = targets * clipped_preds.mapv(|x| x.ln())
956 + (1.0 - targets) * clipped_preds.mapv(|x| (1.0 - x).ln());
957 loss.mean().map(|m| -m).ok_or_else(|| {
958 MLError::ComputationError("Failed to compute mean of empty array".to_string())
959 })
960 }
961 LossFunction::MeanAbsoluteError => {
962 let diff = predictions - targets;
963 diff.mapv(|x| x.abs()).mean().ok_or_else(|| {
964 MLError::ComputationError("Failed to compute mean of empty array".to_string())
965 })
966 }
967 _ => Err(MLError::InvalidConfiguration(
968 "Loss function not implemented".to_string(),
969 )),
970 }
971 }
972}
973
974#[derive(Debug, Clone)]
976pub enum OptimizerType {
977 SGD { learning_rate: f64, momentum: f64 },
979 Adam {
981 learning_rate: f64,
982 beta1: f64,
983 beta2: f64,
984 epsilon: f64,
985 },
986 RMSprop {
988 learning_rate: f64,
989 rho: f64,
990 epsilon: f64,
991 },
992 AdaGrad { learning_rate: f64, epsilon: f64 },
994}
995
996#[derive(Debug, Clone)]
998pub enum MetricType {
999 Accuracy,
1001 Precision,
1003 Recall,
1005 F1Score,
1007 MeanAbsoluteError,
1009 MeanSquaredError,
1011}
1012
1013impl MetricType {
1014 pub fn name(&self) -> String {
1016 match self {
1017 MetricType::Accuracy => "accuracy".to_string(),
1018 MetricType::Precision => "precision".to_string(),
1019 MetricType::Recall => "recall".to_string(),
1020 MetricType::F1Score => "f1_score".to_string(),
1021 MetricType::MeanAbsoluteError => "mean_absolute_error".to_string(),
1022 MetricType::MeanSquaredError => "mean_squared_error".to_string(),
1023 }
1024 }
1025
1026 pub fn compute(&self, predictions: &ArrayD<f64>, targets: &ArrayD<f64>) -> Result<f64> {
1028 match self {
1029 MetricType::Accuracy => {
1030 let pred_classes = predictions.mapv(|x| if x > 0.5 { 1.0 } else { 0.0 });
1031 let correct = pred_classes
1032 .iter()
1033 .zip(targets.iter())
1034 .filter(|(&pred, &target)| (pred - target).abs() < 1e-6)
1035 .count();
1036 Ok(correct as f64 / targets.len() as f64)
1037 }
1038 MetricType::MeanAbsoluteError => {
1039 let diff = predictions - targets;
1040 diff.mapv(|x| x.abs()).mean().ok_or_else(|| {
1041 MLError::ComputationError("Failed to compute mean of empty array".to_string())
1042 })
1043 }
1044 MetricType::MeanSquaredError => {
1045 let diff = predictions - targets;
1046 diff.mapv(|x| x * x).mean().ok_or_else(|| {
1047 MLError::ComputationError("Failed to compute mean of empty array".to_string())
1048 })
1049 }
1050 _ => Err(MLError::InvalidConfiguration(
1051 "Metric not implemented".to_string(),
1052 )),
1053 }
1054 }
1055}
1056
1057pub trait Callback: Send + Sync {
1059 fn on_epoch_end(&self, epoch: usize, history: &TrainingHistory) -> Result<()>;
1061}
1062
1063pub struct EarlyStopping {
1065 monitor: String,
1067 min_delta: f64,
1069 patience: usize,
1071 best: f64,
1073 wait: usize,
1075 stopped: bool,
1077}
1078
1079impl EarlyStopping {
1080 pub fn new(monitor: String, min_delta: f64, patience: usize) -> Self {
1082 Self {
1083 monitor,
1084 min_delta,
1085 patience,
1086 best: f64::INFINITY,
1087 wait: 0,
1088 stopped: false,
1089 }
1090 }
1091}
1092
1093impl Callback for EarlyStopping {
1094 fn on_epoch_end(&self, _epoch: usize, _history: &TrainingHistory) -> Result<()> {
1095 Ok(())
1097 }
1098}
1099
1100#[derive(Debug, Clone)]
1102pub struct TrainingHistory {
1103 pub loss: Vec<f64>,
1105 pub metrics: Vec<HashMap<String, f64>>,
1107 pub val_loss: Vec<f64>,
1109 pub val_metrics: Vec<HashMap<String, f64>>,
1111}
1112
1113impl TrainingHistory {
1114 pub fn new() -> Self {
1116 Self {
1117 loss: Vec::new(),
1118 metrics: Vec::new(),
1119 val_loss: Vec::new(),
1120 val_metrics: Vec::new(),
1121 }
1122 }
1123
1124 pub fn add_epoch(
1126 &mut self,
1127 loss: f64,
1128 metrics: HashMap<String, f64>,
1129 val_loss: Option<f64>,
1130 val_metrics: HashMap<String, f64>,
1131 ) {
1132 self.loss.push(loss);
1133 self.metrics.push(metrics);
1134
1135 if let Some(val_loss) = val_loss {
1136 self.val_loss.push(val_loss);
1137 }
1138 self.val_metrics.push(val_metrics);
1139 }
1140}
1141
1142#[derive(Debug)]
1144pub struct ModelSummary {
1145 pub layers: Vec<LayerInfo>,
1147 pub total_params: usize,
1149 pub trainable_params: usize,
1151 pub non_trainable_params: usize,
1153}
1154
1155#[derive(Debug)]
1157pub struct LayerInfo {
1158 pub name: String,
1160 pub layer_type: String,
1162 pub output_shape: Vec<usize>,
1164 pub param_count: usize,
1166}
1167
1168pub struct Input {
1170 pub shape: Vec<usize>,
1172 pub name: Option<String>,
1174 pub dtype: DataType,
1176}
1177
1178impl Input {
1179 pub fn new(shape: Vec<usize>) -> Self {
1181 Self {
1182 shape,
1183 name: None,
1184 dtype: DataType::Float64,
1185 }
1186 }
1187
1188 pub fn name(mut self, name: impl Into<String>) -> Self {
1190 self.name = Some(name.into());
1191 self
1192 }
1193
1194 pub fn dtype(mut self, dtype: DataType) -> Self {
1196 self.dtype = dtype;
1197 self
1198 }
1199}
1200
1201#[derive(Debug, Clone)]
1203pub enum DataType {
1204 Float32,
1206 Float64,
1208 Int32,
1210 Int64,
1212}
1213
1214pub mod utils {
1216 use super::*;
1217
1218 pub fn create_classification_model(
1220 input_dim: usize,
1221 num_classes: usize,
1222 hidden_layers: Vec<usize>,
1223 ) -> Sequential {
1224 let mut model = Sequential::new();
1225
1226 for (i, &units) in hidden_layers.iter().enumerate() {
1228 model.add(Box::new(
1229 Dense::new(units)
1230 .activation(ActivationFunction::ReLU)
1231 .name(format!("dense_{}", i)),
1232 ));
1233 }
1234
1235 let output_activation = if num_classes == 2 {
1237 ActivationFunction::Sigmoid
1238 } else {
1239 ActivationFunction::Softmax
1240 };
1241
1242 model.add(Box::new(
1243 Dense::new(num_classes)
1244 .activation(output_activation)
1245 .name("output"),
1246 ));
1247
1248 model
1249 }
1250
1251 pub fn create_quantum_model(
1253 num_qubits: usize,
1254 num_classes: usize,
1255 num_layers: usize,
1256 ) -> Sequential {
1257 let mut model = Sequential::new();
1258
1259 model.add(Box::new(
1261 QuantumDense::new(num_qubits, num_classes)
1262 .num_layers(num_layers)
1263 .ansatz_type(QuantumAnsatzType::HardwareEfficient)
1264 .name("quantum_layer"),
1265 ));
1266
1267 if num_classes > 1 {
1269 model.add(Box::new(
1270 Activation::new(ActivationFunction::Softmax).name("softmax"),
1271 ));
1272 }
1273
1274 model
1275 }
1276
1277 pub fn create_hybrid_model(
1279 input_dim: usize,
1280 num_qubits: usize,
1281 num_classes: usize,
1282 classical_hidden: Vec<usize>,
1283 ) -> Sequential {
1284 let mut model = Sequential::new();
1285
1286 for (i, &units) in classical_hidden.iter().enumerate() {
1288 model.add(Box::new(
1289 Dense::new(units)
1290 .activation(ActivationFunction::ReLU)
1291 .name(format!("classical_{}", i)),
1292 ));
1293 }
1294
1295 model.add(Box::new(
1297 QuantumDense::new(num_qubits, 64)
1298 .num_layers(2)
1299 .ansatz_type(QuantumAnsatzType::HardwareEfficient)
1300 .name("quantum_layer"),
1301 ));
1302
1303 model.add(Box::new(
1305 Dense::new(num_classes)
1306 .activation(if num_classes == 2 {
1307 ActivationFunction::Sigmoid
1308 } else {
1309 ActivationFunction::Softmax
1310 })
1311 .name("output"),
1312 ));
1313
1314 model
1315 }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320 use super::*;
1321 use scirs2_core::ndarray::Array;
1322
1323 #[test]
1324 fn test_dense_layer() {
1325 let mut dense = Dense::new(10)
1326 .activation(ActivationFunction::ReLU)
1327 .name("test_dense");
1328
1329 assert!(!dense.built());
1330
1331 dense.build(&[5]).expect("Failed to build dense layer");
1332 assert!(dense.built());
1333 assert_eq!(dense.compute_output_shape(&[32, 5]), vec![32, 10]);
1334
1335 let input = ArrayD::from_shape_vec(
1336 vec![2, 5],
1337 vec![1.0, 2.0, 3.0, 4.0, 5.0, 0.5, 1.5, 2.5, 3.5, 4.5],
1338 )
1339 .expect("Failed to create input array");
1340
1341 let output = dense.call(&input);
1342 assert!(output.is_ok());
1343 assert_eq!(output.expect("Forward pass failed").shape(), &[2, 10]);
1344 }
1345
1346 #[test]
1347 fn test_activation_layer() {
1348 let mut activation = Activation::new(ActivationFunction::ReLU);
1349 activation
1350 .build(&[10])
1351 .expect("Failed to build activation layer");
1352
1353 let input = ArrayD::from_shape_vec(vec![2, 3], vec![-1.0, 0.0, 1.0, -2.0, 0.5, 2.0])
1354 .expect("Failed to create input array");
1355
1356 let output = activation
1357 .call(&input)
1358 .expect("Activation forward pass failed");
1359 let expected = ArrayD::from_shape_vec(vec![2, 3], vec![0.0, 0.0, 1.0, 0.0, 0.5, 2.0])
1360 .expect("Failed to create expected array");
1361
1362 assert_eq!(output.shape(), expected.shape());
1363 }
1364
1365 #[test]
1366 fn test_sequential_model() {
1367 let mut model = Sequential::new();
1368
1369 model.add(Box::new(
1370 Dense::new(10).activation(ActivationFunction::ReLU),
1371 ));
1372 model.add(Box::new(
1373 Dense::new(1).activation(ActivationFunction::Sigmoid),
1374 ));
1375
1376 model
1377 .build(vec![5])
1378 .expect("Failed to build sequential model");
1379 assert!(model.built);
1380
1381 let summary = model.summary();
1382 assert_eq!(summary.layers.len(), 2);
1383
1384 let input = ArrayD::from_shape_vec(
1385 vec![2, 5],
1386 vec![1.0, 2.0, 3.0, 4.0, 5.0, 0.5, 1.5, 2.5, 3.5, 4.5],
1387 )
1388 .expect("Failed to create input array");
1389
1390 let output = model.predict(&input);
1391 assert!(output.is_ok());
1392 assert_eq!(output.expect("Prediction failed").shape(), &[2, 1]);
1393 }
1394
1395 #[test]
1396 fn test_loss_functions() {
1397 let predictions = ArrayD::from_shape_vec(vec![2, 1], vec![0.8, 0.3])
1398 .expect("Failed to create predictions array");
1399 let targets = ArrayD::from_shape_vec(vec![2, 1], vec![1.0, 0.0])
1400 .expect("Failed to create targets array");
1401
1402 let mse = LossFunction::MeanSquaredError;
1403 let loss = mse
1404 .compute(&predictions, &targets)
1405 .expect("MSE computation failed");
1406 assert!(loss > 0.0);
1407
1408 let bce = LossFunction::BinaryCrossentropy;
1409 let loss = bce
1410 .compute(&predictions, &targets)
1411 .expect("BCE computation failed");
1412 assert!(loss > 0.0);
1413 }
1414
1415 #[test]
1416 fn test_metrics() {
1417 let predictions = ArrayD::from_shape_vec(vec![4, 1], vec![0.8, 0.3, 0.9, 0.1])
1418 .expect("Failed to create predictions array");
1419 let targets = ArrayD::from_shape_vec(vec![4, 1], vec![1.0, 0.0, 1.0, 0.0])
1420 .expect("Failed to create targets array");
1421
1422 let accuracy = MetricType::Accuracy;
1423 let acc_value = accuracy
1424 .compute(&predictions, &targets)
1425 .expect("Accuracy computation failed");
1426 assert!(acc_value >= 0.0 && acc_value <= 1.0);
1427 }
1428
1429 #[test]
1430 #[ignore]
1431 fn test_model_utils() {
1432 let model = utils::create_classification_model(10, 3, vec![20, 15]);
1433 let summary = model.summary();
1434 assert_eq!(summary.layers.len(), 3); let quantum_model = utils::create_quantum_model(4, 2, 2);
1437 let summary = quantum_model.summary();
1438 assert!(summary.layers.len() >= 1);
1439 }
1440}