1use crate::circuit_integration::{QuantumLayer, QuantumMLExecutor};
7use crate::error::{MLError, Result};
8use crate::simulator_backends::{
9 BackendCapabilities, DynamicCircuit, Observable, SimulatorBackend, StatevectorBackend,
10};
11use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayD, Axis, IxDyn};
12use quantrs2_circuit::prelude::*;
13use quantrs2_core::prelude::*;
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.get_mut(&metric.name()).unwrap() += 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 Ok(diff.mapv(|x| x * x).mean().unwrap())
949 }
950 LossFunction::BinaryCrossentropy => {
951 let epsilon = 1e-15;
952 let clipped_preds = predictions.mapv(|x| x.max(epsilon).min(1.0 - epsilon));
953 let loss = targets * clipped_preds.mapv(|x| x.ln())
954 + (1.0 - targets) * clipped_preds.mapv(|x| (1.0 - x).ln());
955 Ok(-loss.mean().unwrap())
956 }
957 LossFunction::MeanAbsoluteError => {
958 let diff = predictions - targets;
959 Ok(diff.mapv(|x| x.abs()).mean().unwrap())
960 }
961 _ => Err(MLError::InvalidConfiguration(
962 "Loss function not implemented".to_string(),
963 )),
964 }
965 }
966}
967
968#[derive(Debug, Clone)]
970pub enum OptimizerType {
971 SGD { learning_rate: f64, momentum: f64 },
973 Adam {
975 learning_rate: f64,
976 beta1: f64,
977 beta2: f64,
978 epsilon: f64,
979 },
980 RMSprop {
982 learning_rate: f64,
983 rho: f64,
984 epsilon: f64,
985 },
986 AdaGrad { learning_rate: f64, epsilon: f64 },
988}
989
990#[derive(Debug, Clone)]
992pub enum MetricType {
993 Accuracy,
995 Precision,
997 Recall,
999 F1Score,
1001 MeanAbsoluteError,
1003 MeanSquaredError,
1005}
1006
1007impl MetricType {
1008 pub fn name(&self) -> String {
1010 match self {
1011 MetricType::Accuracy => "accuracy".to_string(),
1012 MetricType::Precision => "precision".to_string(),
1013 MetricType::Recall => "recall".to_string(),
1014 MetricType::F1Score => "f1_score".to_string(),
1015 MetricType::MeanAbsoluteError => "mean_absolute_error".to_string(),
1016 MetricType::MeanSquaredError => "mean_squared_error".to_string(),
1017 }
1018 }
1019
1020 pub fn compute(&self, predictions: &ArrayD<f64>, targets: &ArrayD<f64>) -> Result<f64> {
1022 match self {
1023 MetricType::Accuracy => {
1024 let pred_classes = predictions.mapv(|x| if x > 0.5 { 1.0 } else { 0.0 });
1025 let correct = pred_classes
1026 .iter()
1027 .zip(targets.iter())
1028 .filter(|(&pred, &target)| (pred - target).abs() < 1e-6)
1029 .count();
1030 Ok(correct as f64 / targets.len() as f64)
1031 }
1032 MetricType::MeanAbsoluteError => {
1033 let diff = predictions - targets;
1034 Ok(diff.mapv(|x| x.abs()).mean().unwrap())
1035 }
1036 MetricType::MeanSquaredError => {
1037 let diff = predictions - targets;
1038 Ok(diff.mapv(|x| x * x).mean().unwrap())
1039 }
1040 _ => Err(MLError::InvalidConfiguration(
1041 "Metric not implemented".to_string(),
1042 )),
1043 }
1044 }
1045}
1046
1047pub trait Callback: Send + Sync {
1049 fn on_epoch_end(&self, epoch: usize, history: &TrainingHistory) -> Result<()>;
1051}
1052
1053pub struct EarlyStopping {
1055 monitor: String,
1057 min_delta: f64,
1059 patience: usize,
1061 best: f64,
1063 wait: usize,
1065 stopped: bool,
1067}
1068
1069impl EarlyStopping {
1070 pub fn new(monitor: String, min_delta: f64, patience: usize) -> Self {
1072 Self {
1073 monitor,
1074 min_delta,
1075 patience,
1076 best: f64::INFINITY,
1077 wait: 0,
1078 stopped: false,
1079 }
1080 }
1081}
1082
1083impl Callback for EarlyStopping {
1084 fn on_epoch_end(&self, _epoch: usize, _history: &TrainingHistory) -> Result<()> {
1085 Ok(())
1087 }
1088}
1089
1090#[derive(Debug, Clone)]
1092pub struct TrainingHistory {
1093 pub loss: Vec<f64>,
1095 pub metrics: Vec<HashMap<String, f64>>,
1097 pub val_loss: Vec<f64>,
1099 pub val_metrics: Vec<HashMap<String, f64>>,
1101}
1102
1103impl TrainingHistory {
1104 pub fn new() -> Self {
1106 Self {
1107 loss: Vec::new(),
1108 metrics: Vec::new(),
1109 val_loss: Vec::new(),
1110 val_metrics: Vec::new(),
1111 }
1112 }
1113
1114 pub fn add_epoch(
1116 &mut self,
1117 loss: f64,
1118 metrics: HashMap<String, f64>,
1119 val_loss: Option<f64>,
1120 val_metrics: HashMap<String, f64>,
1121 ) {
1122 self.loss.push(loss);
1123 self.metrics.push(metrics);
1124
1125 if let Some(val_loss) = val_loss {
1126 self.val_loss.push(val_loss);
1127 }
1128 self.val_metrics.push(val_metrics);
1129 }
1130}
1131
1132#[derive(Debug)]
1134pub struct ModelSummary {
1135 pub layers: Vec<LayerInfo>,
1137 pub total_params: usize,
1139 pub trainable_params: usize,
1141 pub non_trainable_params: usize,
1143}
1144
1145#[derive(Debug)]
1147pub struct LayerInfo {
1148 pub name: String,
1150 pub layer_type: String,
1152 pub output_shape: Vec<usize>,
1154 pub param_count: usize,
1156}
1157
1158pub struct Input {
1160 pub shape: Vec<usize>,
1162 pub name: Option<String>,
1164 pub dtype: DataType,
1166}
1167
1168impl Input {
1169 pub fn new(shape: Vec<usize>) -> Self {
1171 Self {
1172 shape,
1173 name: None,
1174 dtype: DataType::Float64,
1175 }
1176 }
1177
1178 pub fn name(mut self, name: impl Into<String>) -> Self {
1180 self.name = Some(name.into());
1181 self
1182 }
1183
1184 pub fn dtype(mut self, dtype: DataType) -> Self {
1186 self.dtype = dtype;
1187 self
1188 }
1189}
1190
1191#[derive(Debug, Clone)]
1193pub enum DataType {
1194 Float32,
1196 Float64,
1198 Int32,
1200 Int64,
1202}
1203
1204pub mod utils {
1206 use super::*;
1207
1208 pub fn create_classification_model(
1210 input_dim: usize,
1211 num_classes: usize,
1212 hidden_layers: Vec<usize>,
1213 ) -> Sequential {
1214 let mut model = Sequential::new();
1215
1216 for (i, &units) in hidden_layers.iter().enumerate() {
1218 model.add(Box::new(
1219 Dense::new(units)
1220 .activation(ActivationFunction::ReLU)
1221 .name(format!("dense_{}", i)),
1222 ));
1223 }
1224
1225 let output_activation = if num_classes == 2 {
1227 ActivationFunction::Sigmoid
1228 } else {
1229 ActivationFunction::Softmax
1230 };
1231
1232 model.add(Box::new(
1233 Dense::new(num_classes)
1234 .activation(output_activation)
1235 .name("output"),
1236 ));
1237
1238 model
1239 }
1240
1241 pub fn create_quantum_model(
1243 num_qubits: usize,
1244 num_classes: usize,
1245 num_layers: usize,
1246 ) -> Sequential {
1247 let mut model = Sequential::new();
1248
1249 model.add(Box::new(
1251 QuantumDense::new(num_qubits, num_classes)
1252 .num_layers(num_layers)
1253 .ansatz_type(QuantumAnsatzType::HardwareEfficient)
1254 .name("quantum_layer"),
1255 ));
1256
1257 if num_classes > 1 {
1259 model.add(Box::new(
1260 Activation::new(ActivationFunction::Softmax).name("softmax"),
1261 ));
1262 }
1263
1264 model
1265 }
1266
1267 pub fn create_hybrid_model(
1269 input_dim: usize,
1270 num_qubits: usize,
1271 num_classes: usize,
1272 classical_hidden: Vec<usize>,
1273 ) -> Sequential {
1274 let mut model = Sequential::new();
1275
1276 for (i, &units) in classical_hidden.iter().enumerate() {
1278 model.add(Box::new(
1279 Dense::new(units)
1280 .activation(ActivationFunction::ReLU)
1281 .name(format!("classical_{}", i)),
1282 ));
1283 }
1284
1285 model.add(Box::new(
1287 QuantumDense::new(num_qubits, 64)
1288 .num_layers(2)
1289 .ansatz_type(QuantumAnsatzType::HardwareEfficient)
1290 .name("quantum_layer"),
1291 ));
1292
1293 model.add(Box::new(
1295 Dense::new(num_classes)
1296 .activation(if num_classes == 2 {
1297 ActivationFunction::Sigmoid
1298 } else {
1299 ActivationFunction::Softmax
1300 })
1301 .name("output"),
1302 ));
1303
1304 model
1305 }
1306}
1307
1308#[cfg(test)]
1309mod tests {
1310 use super::*;
1311 use scirs2_core::ndarray::Array;
1312
1313 #[test]
1314 fn test_dense_layer() {
1315 let mut dense = Dense::new(10)
1316 .activation(ActivationFunction::ReLU)
1317 .name("test_dense");
1318
1319 assert!(!dense.built());
1320
1321 dense.build(&[5]).unwrap();
1322 assert!(dense.built());
1323 assert_eq!(dense.compute_output_shape(&[32, 5]), vec![32, 10]);
1324
1325 let input = ArrayD::from_shape_vec(
1326 vec![2, 5],
1327 vec![1.0, 2.0, 3.0, 4.0, 5.0, 0.5, 1.5, 2.5, 3.5, 4.5],
1328 )
1329 .unwrap();
1330
1331 let output = dense.call(&input);
1332 assert!(output.is_ok());
1333 assert_eq!(output.unwrap().shape(), &[2, 10]);
1334 }
1335
1336 #[test]
1337 fn test_activation_layer() {
1338 let mut activation = Activation::new(ActivationFunction::ReLU);
1339 activation.build(&[10]).unwrap();
1340
1341 let input =
1342 ArrayD::from_shape_vec(vec![2, 3], vec![-1.0, 0.0, 1.0, -2.0, 0.5, 2.0]).unwrap();
1343
1344 let output = activation.call(&input).unwrap();
1345 let expected =
1346 ArrayD::from_shape_vec(vec![2, 3], vec![0.0, 0.0, 1.0, 0.0, 0.5, 2.0]).unwrap();
1347
1348 assert_eq!(output.shape(), expected.shape());
1349 }
1350
1351 #[test]
1352 fn test_sequential_model() {
1353 let mut model = Sequential::new();
1354
1355 model.add(Box::new(
1356 Dense::new(10).activation(ActivationFunction::ReLU),
1357 ));
1358 model.add(Box::new(
1359 Dense::new(1).activation(ActivationFunction::Sigmoid),
1360 ));
1361
1362 model.build(vec![5]).unwrap();
1363 assert!(model.built);
1364
1365 let summary = model.summary();
1366 assert_eq!(summary.layers.len(), 2);
1367
1368 let input = ArrayD::from_shape_vec(
1369 vec![2, 5],
1370 vec![1.0, 2.0, 3.0, 4.0, 5.0, 0.5, 1.5, 2.5, 3.5, 4.5],
1371 )
1372 .unwrap();
1373
1374 let output = model.predict(&input);
1375 assert!(output.is_ok());
1376 assert_eq!(output.unwrap().shape(), &[2, 1]);
1377 }
1378
1379 #[test]
1380 fn test_loss_functions() {
1381 let predictions = ArrayD::from_shape_vec(vec![2, 1], vec![0.8, 0.3]).unwrap();
1382 let targets = ArrayD::from_shape_vec(vec![2, 1], vec![1.0, 0.0]).unwrap();
1383
1384 let mse = LossFunction::MeanSquaredError;
1385 let loss = mse.compute(&predictions, &targets).unwrap();
1386 assert!(loss > 0.0);
1387
1388 let bce = LossFunction::BinaryCrossentropy;
1389 let loss = bce.compute(&predictions, &targets).unwrap();
1390 assert!(loss > 0.0);
1391 }
1392
1393 #[test]
1394 fn test_metrics() {
1395 let predictions = ArrayD::from_shape_vec(vec![4, 1], vec![0.8, 0.3, 0.9, 0.1]).unwrap();
1396 let targets = ArrayD::from_shape_vec(vec![4, 1], vec![1.0, 0.0, 1.0, 0.0]).unwrap();
1397
1398 let accuracy = MetricType::Accuracy;
1399 let acc_value = accuracy.compute(&predictions, &targets).unwrap();
1400 assert!(acc_value >= 0.0 && acc_value <= 1.0);
1401 }
1402
1403 #[test]
1404 #[ignore]
1405 fn test_model_utils() {
1406 let model = utils::create_classification_model(10, 3, vec![20, 15]);
1407 let summary = model.summary();
1408 assert_eq!(summary.layers.len(), 3); let quantum_model = utils::create_quantum_model(4, 2, 2);
1411 let summary = quantum_model.summary();
1412 assert!(summary.layers.len() >= 1);
1413 }
1414}