1#![allow(dead_code)]
8
9use crate::sampler::SamplerError;
10use scirs2_core::ndarray::{s, Array1, Array2};
11use scirs2_core::random::prelude::*;
12use scirs2_core::SliceRandomExt;
13use std::collections::HashMap;
14use std::f64::consts::PI;
15
16pub struct QuantumNeuralNetwork {
18 pub architecture: QNNArchitecture,
20 pub layers: Vec<QuantumLayer>,
22 pub classical_layers: Vec<ClassicalLayer>,
24 pub training_config: QNNTrainingConfig,
26 pub parameters: QNNParameters,
28 pub training_history: Vec<TrainingEpoch>,
30 pub metrics: QNNMetrics,
32}
33
34#[derive(Debug, Clone)]
36pub struct QNNArchitecture {
37 pub input_dim: usize,
39 pub output_dim: usize,
41 pub num_qubits: usize,
43 pub circuit_depth: usize,
45 pub entanglement_pattern: EntanglementPattern,
47 pub measurement_scheme: MeasurementScheme,
49 pub postprocessing: PostprocessingScheme,
51}
52
53#[derive(Debug, Clone, PartialEq)]
55pub enum EntanglementPattern {
56 Linear,
58 Full,
60 Circular,
62 Random { connectivity: f64 },
64 HardwareEfficient,
66 ProblemAdapted,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum MeasurementScheme {
73 Computational,
75 Pauli { bases: Vec<PauliBasis> },
77 Bell,
79 Adaptive,
81 ShadowTomography { num_shadows: usize },
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum PauliBasis {
88 X,
89 Y,
90 Z,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum PostprocessingScheme {
96 None,
98 Linear,
100 NonlinearNN { hidden_dims: Vec<usize> },
102 Attention,
104 GraphNN,
106}
107
108#[derive(Debug, Clone)]
110pub struct QuantumLayer {
111 pub layer_id: usize,
113 pub num_qubits: usize,
115 pub gates: Vec<QuantumGate>,
117 pub parametrized_gates: Vec<ParametrizedGate>,
119 pub layer_type: QuantumLayerType,
121 pub skip_connections: Vec<usize>,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum QuantumLayerType {
128 Variational,
130 Entangling,
132 Measurement,
134 ErrorCorrection,
136 Adaptive,
138}
139
140#[derive(Debug, Clone)]
142pub enum QuantumGate {
143 RX {
145 qubit: usize,
146 angle: f64,
147 },
148 RY {
149 qubit: usize,
150 angle: f64,
151 },
152 RZ {
153 qubit: usize,
154 angle: f64,
155 },
156 CNOT {
158 control: usize,
159 target: usize,
160 },
161 CZ {
162 control: usize,
163 target: usize,
164 },
165 Toffoli {
167 controls: Vec<usize>,
168 target: usize,
169 },
170 Custom {
172 name: String,
173 qubits: Vec<usize>,
174 matrix: Array2<f64>,
175 },
176}
177
178#[derive(Debug, Clone)]
180pub struct ParametrizedGate {
181 pub gate_type: ParametrizedGateType,
183 pub qubits: Vec<usize>,
185 pub parameter_indices: Vec<usize>,
187 pub gate_function: GateFunction,
189}
190
191#[derive(Debug, Clone, PartialEq)]
193pub enum ParametrizedGateType {
194 Rotation { axis: RotationAxis },
196 Entangling { gate_name: String },
198 GeneralizedRotation,
200 ProblemSpecific { problem_type: String },
202}
203
204#[derive(Debug, Clone, PartialEq)]
206pub enum RotationAxis {
207 X,
208 Y,
209 Z,
210 Arbitrary { nx: f64, ny: f64, nz: f64 },
211}
212
213#[derive(Debug, Clone)]
215pub enum GateFunction {
216 StandardRotation,
218 ControlledRotation,
220 MultiControlledRotation,
222 Custom { function_name: String },
224}
225
226#[derive(Debug, Clone)]
228pub struct ClassicalLayer {
229 pub layer_type: ClassicalLayerType,
231 pub input_dim: usize,
233 pub output_dim: usize,
235 pub weights: Array2<f64>,
237 pub biases: Array1<f64>,
239 pub activation: ActivationFunction,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum ClassicalLayerType {
246 Dense,
248 Convolutional,
250 Attention,
252 Normalization,
254 Embedding,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
260pub enum ActivationFunction {
261 ReLU,
262 Sigmoid,
263 Tanh,
264 Linear,
265 Softmax,
266 Swish,
267 GELU,
268}
269
270#[derive(Debug, Clone)]
272pub struct QNNTrainingConfig {
273 pub learning_rate: f64,
275 pub batch_size: usize,
277 pub num_epochs: usize,
279 pub optimizer: OptimizerType,
281 pub loss_function: LossFunction,
283 pub regularization: RegularizationConfig,
285 pub early_stopping: EarlyStoppingConfig,
287 pub gradient_estimation: GradientEstimationMethod,
289}
290
291#[derive(Debug, Clone, PartialEq)]
293pub enum OptimizerType {
294 SGD {
295 momentum: f64,
296 },
297 Adam {
298 beta1: f64,
299 beta2: f64,
300 epsilon: f64,
301 },
302 AdaGrad {
303 epsilon: f64,
304 },
305 RMSprop {
306 decay: f64,
307 epsilon: f64,
308 },
309 SPSA {
310 a: f64,
311 c: f64,
312 },
313 QuantumNaturalGradient,
314}
315
316#[derive(Debug, Clone, PartialEq)]
318pub enum LossFunction {
319 MeanSquaredError,
320 CrossEntropy,
321 HuberLoss { delta: f64 },
322 QuantumFisherInformation,
323 ExpectationValueLoss,
324 Custom { name: String },
325}
326
327#[derive(Debug, Clone)]
329pub struct RegularizationConfig {
330 pub l1_strength: f64,
332 pub l2_strength: f64,
334 pub dropout_prob: f64,
336 pub parameter_noise: f64,
338 pub quantum_noise: QuantumNoiseConfig,
340}
341
342#[derive(Debug, Clone)]
344pub struct QuantumNoiseConfig {
345 pub enable_noise: bool,
347 pub depolarizing_strength: f64,
349 pub amplitude_damping: f64,
351 pub phase_damping: f64,
353 pub gate_error_rates: HashMap<String, f64>,
355}
356
357#[derive(Debug, Clone)]
359pub struct EarlyStoppingConfig {
360 pub enabled: bool,
362 pub patience: usize,
364 pub min_improvement: f64,
366 pub monitor_metric: String,
368}
369
370#[derive(Debug, Clone, PartialEq)]
372pub enum GradientEstimationMethod {
373 ParameterShift,
375 FiniteDifferences { epsilon: f64 },
377 StochasticParameterShift,
379 NaturalGradient,
381 QuantumFisherInformation,
383}
384
385#[derive(Debug, Clone)]
387pub struct QNNParameters {
388 pub quantum_params: Array1<f64>,
390 pub classical_params: Vec<Array2<f64>>,
392 pub bias_params: Vec<Array1<f64>>,
394 pub parameter_bounds: Vec<(f64, f64)>,
396 pub initialization_scheme: ParameterInitializationScheme,
398}
399
400#[derive(Debug, Clone, PartialEq)]
402pub enum ParameterInitializationScheme {
403 RandomUniform { min: f64, max: f64 },
405 RandomNormal { mean: f64, std: f64 },
407 Xavier,
409 He,
411 ProblemSpecific,
413 WarmStart,
415}
416
417#[derive(Debug, Clone)]
419pub struct TrainingEpoch {
420 pub epoch: usize,
422 pub training_loss: f64,
424 pub validation_loss: Option<f64>,
426 pub training_accuracy: f64,
428 pub validation_accuracy: Option<f64>,
430 pub learning_rate: f64,
432 pub training_time: f64,
434 pub gradient_norms: Vec<f64>,
436 pub parameter_stats: ParameterStatistics,
438}
439
440#[derive(Debug, Clone)]
442pub struct ParameterStatistics {
443 pub mean_values: Array1<f64>,
445 pub std_values: Array1<f64>,
447 pub ranges: Vec<(f64, f64)>,
449 pub correlations: Array2<f64>,
451}
452
453#[derive(Debug, Clone)]
455pub struct QNNMetrics {
456 pub training_metrics: TrainingMetrics,
458 pub validation_metrics: ValidationMetrics,
460 pub quantum_metrics: QuantumMetrics,
462 pub computational_metrics: ComputationalMetrics,
464}
465
466#[derive(Debug, Clone)]
468pub struct TrainingMetrics {
469 pub final_training_loss: f64,
471 pub convergence_rate: f64,
473 pub epochs_to_convergence: usize,
475 pub training_stability: f64,
477 pub overfitting_measure: f64,
479}
480
481#[derive(Debug, Clone)]
483pub struct ValidationMetrics {
484 pub best_validation_loss: f64,
486 pub validation_accuracy: f64,
488 pub generalization_gap: f64,
490 pub cv_scores: Vec<f64>,
492 pub confidence_intervals: Vec<(f64, f64)>,
494}
495
496#[derive(Debug, Clone)]
498pub struct QuantumMetrics {
499 pub quantum_volume: f64,
501 pub entanglement_measures: Vec<f64>,
503 pub quantum_advantage: f64,
505 pub fidelity_measures: Vec<f64>,
507 pub coherence_utilization: f64,
509}
510
511#[derive(Debug, Clone)]
513pub struct ComputationalMetrics {
514 pub training_time_per_epoch: f64,
516 pub inference_time: f64,
518 pub memory_usage: f64,
520 pub quantum_execution_time: f64,
522 pub classical_computation_time: f64,
524}
525
526impl QuantumNeuralNetwork {
527 pub fn new(
529 architecture: QNNArchitecture,
530 training_config: QNNTrainingConfig,
531 ) -> Result<Self, SamplerError> {
532 let num_quantum_params = Self::calculate_quantum_params(&architecture);
533 let _num_classical_params = Self::calculate_classical_params(&architecture);
534
535 let parameters = QNNParameters {
536 quantum_params: Array1::zeros(num_quantum_params),
537 classical_params: vec![],
538 bias_params: vec![],
539 parameter_bounds: vec![(-PI, PI); num_quantum_params],
540 initialization_scheme: ParameterInitializationScheme::RandomUniform {
541 min: -PI,
542 max: PI,
543 },
544 };
545
546 let layers = Self::build_quantum_layers(&architecture)?;
547 let classical_layers = Self::build_classical_layers(&architecture)?;
548
549 Ok(Self {
550 architecture,
551 layers,
552 classical_layers,
553 training_config,
554 parameters,
555 training_history: Vec::new(),
556 metrics: QNNMetrics::default(),
557 })
558 }
559
560 pub fn train(
562 &mut self,
563 training_data: &[(Array1<f64>, Array1<f64>)],
564 ) -> Result<TrainingMetrics, SamplerError> {
565 println!("Starting QNN training with {} samples", training_data.len());
566
567 self.initialize_parameters()?;
569
570 let mut best_loss = f64::INFINITY;
571 let mut patience_counter = 0;
572
573 for epoch in 0..self.training_config.num_epochs {
574 let epoch_start = std::time::Instant::now();
575
576 let mut shuffled_data = training_data.to_vec();
578 shuffled_data.shuffle(&mut thread_rng());
579
580 let mut epoch_loss = 0.0;
581 let mut epoch_accuracy = 0.0;
582 let batch_count = shuffled_data
583 .len()
584 .div_ceil(self.training_config.batch_size);
585
586 for batch_idx in 0..batch_count {
587 let batch_start = batch_idx * self.training_config.batch_size;
588 let batch_end = std::cmp::min(
589 batch_start + self.training_config.batch_size,
590 shuffled_data.len(),
591 );
592 let batch = &shuffled_data[batch_start..batch_end];
593
594 let batch_predictions = self.forward_batch(batch)?;
596
597 let (batch_loss, gradients) =
599 self.compute_loss_and_gradients(batch, &batch_predictions)?;
600
601 self.update_parameters(&gradients)?;
603
604 epoch_loss += batch_loss;
605 epoch_accuracy += self.compute_batch_accuracy(batch, &batch_predictions);
606 }
607
608 epoch_loss /= batch_count as f64;
609 epoch_accuracy /= batch_count as f64;
610
611 let epoch_time = epoch_start.elapsed().as_secs_f64();
612
613 let validation_loss = self.compute_validation_loss(training_data)?;
615
616 let epoch_record = TrainingEpoch {
618 epoch,
619 training_loss: epoch_loss,
620 validation_loss: Some(validation_loss),
621 training_accuracy: epoch_accuracy,
622 validation_accuracy: Some(epoch_accuracy), learning_rate: self.training_config.learning_rate,
624 training_time: epoch_time,
625 gradient_norms: vec![0.1], parameter_stats: self.compute_parameter_statistics(),
627 };
628
629 self.training_history.push(epoch_record);
630
631 if self.training_config.early_stopping.enabled {
633 if validation_loss < best_loss - self.training_config.early_stopping.min_improvement
634 {
635 best_loss = validation_loss;
636 patience_counter = 0;
637 } else {
638 patience_counter += 1;
639 if patience_counter >= self.training_config.early_stopping.patience {
640 println!("Early stopping at epoch {epoch} due to no improvement");
641 break;
642 }
643 }
644 }
645
646 if epoch % 10 == 0 {
647 println!(
648 "Epoch {epoch}: Loss = {epoch_loss:.6}, Accuracy = {epoch_accuracy:.3}, Val Loss = {validation_loss:.6}"
649 );
650 }
651 }
652
653 let final_loss = self
655 .training_history
656 .last()
657 .map(|epoch| epoch.training_loss)
658 .unwrap_or(f64::INFINITY);
659
660 let training_metrics = TrainingMetrics {
661 final_training_loss: final_loss,
662 convergence_rate: self.compute_convergence_rate(),
663 epochs_to_convergence: self.training_history.len(),
664 training_stability: self.compute_training_stability(),
665 overfitting_measure: self.compute_overfitting_measure(),
666 };
667
668 self.metrics.training_metrics = training_metrics.clone();
669
670 println!(
671 "QNN training completed. Final loss: {:.6}",
672 training_metrics.final_training_loss
673 );
674 Ok(training_metrics)
675 }
676
677 pub fn forward(&self, input: &Array1<f64>) -> Result<Array1<f64>, SamplerError> {
679 let mut current_state = input.clone();
681 for layer in &self.classical_layers {
682 current_state = self.apply_classical_layer(¤t_state, layer)?;
683 }
684
685 let quantum_output = self.apply_quantum_layers(¤t_state)?;
687
688 self.apply_postprocessing(&quantum_output)
690 }
691
692 fn apply_quantum_layers(&self, input: &Array1<f64>) -> Result<Array1<f64>, SamplerError> {
694 let mut quantum_state = self.initialize_quantum_state(input)?;
696
697 for layer in &self.layers {
699 quantum_state = self.apply_quantum_layer(&quantum_state, layer)?;
700 }
701
702 self.measure_quantum_state(&quantum_state)
704 }
705
706 fn initialize_quantum_state(&self, input: &Array1<f64>) -> Result<Array1<f64>, SamplerError> {
708 let num_qubits = self.architecture.num_qubits;
710 let state_dim = 1 << num_qubits;
711 let mut quantum_state = Array1::zeros(state_dim);
712
713 if input.len() <= state_dim {
715 for (i, &val) in input.iter().enumerate() {
716 if i < state_dim {
717 quantum_state[i] = val;
718 }
719 }
720 let norm = quantum_state.dot(&quantum_state).sqrt();
722 if norm > 1e-10 {
723 quantum_state /= norm;
724 } else {
725 quantum_state[0] = 1.0; }
727 } else {
728 for i in 0..std::cmp::min(input.len(), num_qubits) {
730 let angle = input[i] * PI;
731 quantum_state[0] = angle.cos();
733 quantum_state[1] = angle.sin();
734 }
735 }
736
737 Ok(quantum_state)
738 }
739
740 fn apply_quantum_layer(
742 &self,
743 state: &Array1<f64>,
744 layer: &QuantumLayer,
745 ) -> Result<Array1<f64>, SamplerError> {
746 let mut current_state = state.clone();
747
748 for gate in &layer.parametrized_gates {
750 current_state = self.apply_parametrized_gate(¤t_state, gate)?;
751 }
752
753 for gate in &layer.gates {
755 current_state = self.apply_quantum_gate(¤t_state, gate)?;
756 }
757
758 Ok(current_state)
759 }
760
761 fn apply_parametrized_gate(
763 &self,
764 state: &Array1<f64>,
765 gate: &ParametrizedGate,
766 ) -> Result<Array1<f64>, SamplerError> {
767 match &gate.gate_type {
768 ParametrizedGateType::Rotation { axis } => {
769 if gate.parameter_indices.is_empty() || gate.qubits.is_empty() {
770 return Ok(state.clone());
771 }
772
773 let param_idx = gate.parameter_indices[0];
774 let qubit = gate.qubits[0];
775
776 if param_idx >= self.parameters.quantum_params.len() {
777 return Ok(state.clone());
778 }
779
780 let angle = self.parameters.quantum_params[param_idx];
781
782 match axis {
783 RotationAxis::X => self.apply_rx_gate(state, qubit, angle),
784 RotationAxis::Y => self.apply_ry_gate(state, qubit, angle),
785 RotationAxis::Z => self.apply_rz_gate(state, qubit, angle),
786 RotationAxis::Arbitrary { nx, ny, nz } => {
787 self.apply_arbitrary_rotation(state, qubit, angle, *nx, *ny, *nz)
788 }
789 }
790 }
791 ParametrizedGateType::Entangling { gate_name } => {
792 self.apply_entangling_gate(state, &gate.qubits, gate_name)
793 }
794 _ => Ok(state.clone()), }
796 }
797
798 fn apply_rx_gate(
800 &self,
801 state: &Array1<f64>,
802 qubit: usize,
803 angle: f64,
804 ) -> Result<Array1<f64>, SamplerError> {
805 let num_qubits = self.architecture.num_qubits;
806 if qubit >= num_qubits {
807 return Ok(state.clone());
808 }
809
810 let mut new_state = state.clone();
811 let state_dim = state.len();
812
813 for i in 0..state_dim {
814 let bit_pos = num_qubits - 1 - qubit;
816 if (i & (1 << bit_pos)) == 0 {
817 let j = i | (1 << bit_pos);
818 if j < state_dim {
819 let cos_half = (angle / 2.0).cos();
820 let sin_half = (angle / 2.0).sin();
821
822 let state_i = state[i];
823 let state_j = state[j];
824
825 new_state[i] = cos_half * state_i + sin_half * state_j;
826 new_state[j] = sin_half * state_i + cos_half * state_j;
827 }
828 }
829 }
830
831 Ok(new_state)
832 }
833
834 fn apply_ry_gate(
836 &self,
837 state: &Array1<f64>,
838 qubit: usize,
839 angle: f64,
840 ) -> Result<Array1<f64>, SamplerError> {
841 let num_qubits = self.architecture.num_qubits;
842 if qubit >= num_qubits {
843 return Ok(state.clone());
844 }
845
846 let mut new_state = state.clone();
847 let state_dim = state.len();
848
849 for i in 0..state_dim {
850 let bit_pos = num_qubits - 1 - qubit;
852 if (i & (1 << bit_pos)) == 0 {
853 let j = i | (1 << bit_pos);
854 if j < state_dim {
855 let cos_half = (angle / 2.0).cos();
856 let sin_half = (angle / 2.0).sin();
857
858 let state_i = state[i];
859 let state_j = state[j];
860
861 new_state[i] = cos_half * state_i + sin_half * state_j;
862 new_state[j] = (-sin_half).mul_add(state_i, cos_half * state_j);
863 }
864 }
865 }
866
867 Ok(new_state)
868 }
869
870 fn apply_rz_gate(
872 &self,
873 state: &Array1<f64>,
874 qubit: usize,
875 angle: f64,
876 ) -> Result<Array1<f64>, SamplerError> {
877 let num_qubits = self.architecture.num_qubits;
878 if qubit >= num_qubits {
879 return Ok(state.clone());
880 }
881
882 let mut new_state = state.clone();
883 let exp_neg = (-angle / 2.0).exp();
884 let exp_pos = (angle / 2.0).exp();
885
886 for i in 0..state.len() {
887 let bit_pos = num_qubits - 1 - qubit;
889 if (i & (1 << bit_pos)) == 0 {
890 new_state[i] = state[i] * exp_neg;
891 } else {
892 new_state[i] = state[i] * exp_pos;
893 }
894 }
895
896 Ok(new_state)
897 }
898
899 fn apply_arbitrary_rotation(
901 &self,
902 state: &Array1<f64>,
903 qubit: usize,
904 angle: f64,
905 nx: f64,
906 ny: f64,
907 nz: f64,
908 ) -> Result<Array1<f64>, SamplerError> {
909 let norm = nz.mul_add(nz, nx.mul_add(nx, ny * ny)).sqrt();
911 if norm < 1e-10 {
912 return Ok(state.clone());
913 }
914 let (_nx, _ny, nz) = (nx / norm, ny / norm, nz / norm);
915
916 let _cos_half = (angle / 2.0).cos();
918 let _sin_half = (angle / 2.0).sin();
919
920 self.apply_rz_gate(state, qubit, angle * nz)
922 }
923
924 fn apply_entangling_gate(
926 &self,
927 state: &Array1<f64>,
928 qubits: &[usize],
929 gate_name: &str,
930 ) -> Result<Array1<f64>, SamplerError> {
931 if qubits.len() < 2 {
932 return Ok(state.clone());
933 }
934
935 match gate_name {
936 "CNOT" => self.apply_cnot_gate(state, qubits[0], qubits[1]),
937 "CZ" => self.apply_cz_gate(state, qubits[0], qubits[1]),
938 _ => Ok(state.clone()),
939 }
940 }
941
942 fn apply_cnot_gate(
944 &self,
945 state: &Array1<f64>,
946 control: usize,
947 target: usize,
948 ) -> Result<Array1<f64>, SamplerError> {
949 let num_qubits = self.architecture.num_qubits;
950 if control >= num_qubits || target >= num_qubits {
951 return Ok(state.clone());
952 }
953
954 let mut new_state = state.clone();
955
956 for i in 0..state.len() {
957 if (i & (1 << control)) != 0 {
958 let j = i ^ (1 << target);
959 new_state[i] = state[j];
960 }
961 }
962
963 Ok(new_state)
964 }
965
966 fn apply_cz_gate(
968 &self,
969 state: &Array1<f64>,
970 control: usize,
971 target: usize,
972 ) -> Result<Array1<f64>, SamplerError> {
973 let num_qubits = self.architecture.num_qubits;
974 if control >= num_qubits || target >= num_qubits {
975 return Ok(state.clone());
976 }
977
978 let mut new_state = state.clone();
979
980 for i in 0..state.len() {
981 if (i & (1 << control)) != 0 && (i & (1 << target)) != 0 {
982 new_state[i] = -state[i];
983 }
984 }
985
986 Ok(new_state)
987 }
988
989 fn apply_quantum_gate(
991 &self,
992 state: &Array1<f64>,
993 gate: &QuantumGate,
994 ) -> Result<Array1<f64>, SamplerError> {
995 match gate {
996 QuantumGate::RX { qubit, angle } => self.apply_rx_gate(state, *qubit, *angle),
997 QuantumGate::RY { qubit, angle } => self.apply_ry_gate(state, *qubit, *angle),
998 QuantumGate::RZ { qubit, angle } => self.apply_rz_gate(state, *qubit, *angle),
999 QuantumGate::CNOT { control, target } => self.apply_cnot_gate(state, *control, *target),
1000 QuantumGate::CZ { control, target } => self.apply_cz_gate(state, *control, *target),
1001 _ => Ok(state.clone()), }
1003 }
1004
1005 fn measure_quantum_state(&self, state: &Array1<f64>) -> Result<Array1<f64>, SamplerError> {
1007 match &self.architecture.measurement_scheme {
1008 MeasurementScheme::Computational => {
1009 let probabilities: Array1<f64> = state.mapv(|x| x * x);
1011 Ok(probabilities)
1012 }
1013 MeasurementScheme::Pauli { bases } => {
1014 let mut expectations = Array1::zeros(bases.len());
1016 for (i, basis) in bases.iter().enumerate() {
1017 expectations[i] = self.compute_pauli_expectation(state, basis)?;
1018 }
1019 Ok(expectations)
1020 }
1021 _ => {
1022 let probabilities: Array1<f64> = state.mapv(|x| x * x);
1024 Ok(probabilities)
1025 }
1026 }
1027 }
1028
1029 fn compute_pauli_expectation(
1031 &self,
1032 state: &Array1<f64>,
1033 basis: &PauliBasis,
1034 ) -> Result<f64, SamplerError> {
1035 match basis {
1036 PauliBasis::Z => {
1037 let mut expectation = 0.0;
1038 for i in 0..state.len() {
1039 let parity = (i.count_ones() % 2) as f64;
1040 expectation += state[i] * state[i] * 2.0f64.mul_add(-parity, 1.0);
1041 }
1042 Ok(expectation)
1043 }
1044 PauliBasis::X => {
1045 let mut expectation = 0.0;
1047 for i in 0..state.len() {
1048 let j = i ^ 1; if j < state.len() {
1050 expectation += state[i] * state[j];
1051 }
1052 }
1053 Ok(expectation * 2.0)
1054 }
1055 PauliBasis::Y => {
1056 Ok(0.0) }
1059 }
1060 }
1061
1062 fn apply_classical_layer(
1064 &self,
1065 input: &Array1<f64>,
1066 layer: &ClassicalLayer,
1067 ) -> Result<Array1<f64>, SamplerError> {
1068 let output = layer.weights.dot(input) + &layer.biases;
1069
1070 let activated_output = match layer.activation {
1071 ActivationFunction::ReLU => output.mapv(|x| x.max(0.0)),
1072 ActivationFunction::Sigmoid => output.mapv(|x| 1.0 / (1.0 + (-x).exp())),
1073 ActivationFunction::Tanh => output.mapv(|x| x.tanh()),
1074 ActivationFunction::Linear => output,
1075 ActivationFunction::Softmax => {
1076 let exp_vals = output.mapv(|x| x.exp());
1077 let sum = exp_vals.sum();
1078 exp_vals / sum
1079 }
1080 _ => output, };
1082
1083 Ok(activated_output)
1084 }
1085
1086 fn apply_postprocessing(
1088 &self,
1089 quantum_output: &Array1<f64>,
1090 ) -> Result<Array1<f64>, SamplerError> {
1091 match &self.architecture.postprocessing {
1092 PostprocessingScheme::None => Ok(quantum_output.clone()),
1093 PostprocessingScheme::Linear => {
1094 let transformed = quantum_output.slice(s![..self.architecture.output_dim]);
1096 Ok(transformed.to_owned())
1097 }
1098 _ => Ok(quantum_output.clone()), }
1100 }
1101
1102 fn forward_batch(
1104 &self,
1105 batch: &[(Array1<f64>, Array1<f64>)],
1106 ) -> Result<Vec<Array1<f64>>, SamplerError> {
1107 let mut predictions = Vec::new();
1108 for (input, _) in batch {
1109 predictions.push(self.forward(input)?);
1110 }
1111 Ok(predictions)
1112 }
1113
1114 fn compute_loss_and_gradients(
1116 &self,
1117 batch: &[(Array1<f64>, Array1<f64>)],
1118 predictions: &[Array1<f64>],
1119 ) -> Result<(f64, Array1<f64>), SamplerError> {
1120 let mut total_loss = 0.0;
1121 let mut gradients = Array1::zeros(self.parameters.quantum_params.len());
1122
1123 for ((_, target), prediction) in batch.iter().zip(predictions.iter()) {
1124 let loss = self.compute_loss(prediction, target)?;
1125 total_loss += loss;
1126
1127 let param_gradients = self.compute_parameter_gradients(prediction, target)?;
1129 gradients += ¶m_gradients;
1130 }
1131
1132 total_loss /= batch.len() as f64;
1133 gradients /= batch.len() as f64;
1134
1135 Ok((total_loss, gradients))
1136 }
1137
1138 fn compute_loss(
1140 &self,
1141 prediction: &Array1<f64>,
1142 target: &Array1<f64>,
1143 ) -> Result<f64, SamplerError> {
1144 match &self.training_config.loss_function {
1145 LossFunction::MeanSquaredError => {
1146 let diff = prediction - target;
1147 Ok(diff.dot(&diff) / (2.0 * prediction.len() as f64))
1148 }
1149 LossFunction::CrossEntropy => {
1150 let mut loss = 0.0;
1151 for (pred, targ) in prediction.iter().zip(target.iter()) {
1152 loss -= targ * pred.ln();
1153 }
1154 Ok(loss)
1155 }
1156 _ => {
1157 let diff = prediction - target;
1159 Ok(diff.dot(&diff) / (2.0 * prediction.len() as f64))
1160 }
1161 }
1162 }
1163
1164 fn compute_parameter_gradients(
1166 &self,
1167 prediction: &Array1<f64>,
1168 target: &Array1<f64>,
1169 ) -> Result<Array1<f64>, SamplerError> {
1170 let mut gradients = Array1::zeros(self.parameters.quantum_params.len());
1171 let shift = PI / 2.0;
1172
1173 for i in 0..self.parameters.quantum_params.len() {
1174 let mut params_plus = self.parameters.quantum_params.clone();
1176 params_plus[i] += shift;
1177 let prediction_plus = self.forward_with_params(prediction, ¶ms_plus)?;
1178 let loss_plus = self.compute_loss(&prediction_plus, target)?;
1179
1180 let mut params_minus = self.parameters.quantum_params.clone();
1182 params_minus[i] -= shift;
1183 let prediction_minus = self.forward_with_params(prediction, ¶ms_plus)?;
1184 let loss_minus = self.compute_loss(&prediction_minus, target)?;
1185
1186 gradients[i] = (loss_plus - loss_minus) / (2.0 * shift);
1188 }
1189
1190 Ok(gradients)
1191 }
1192
1193 fn forward_with_params(
1195 &self,
1196 input: &Array1<f64>,
1197 _params: &Array1<f64>,
1198 ) -> Result<Array1<f64>, SamplerError> {
1199 self.forward(input)
1201 }
1202
1203 fn update_parameters(&mut self, gradients: &Array1<f64>) -> Result<(), SamplerError> {
1205 match &self.training_config.optimizer {
1206 OptimizerType::SGD { momentum: _ } => {
1207 let lr = self.training_config.learning_rate;
1209 self.parameters.quantum_params =
1210 &self.parameters.quantum_params - &(gradients * lr);
1211 }
1212 OptimizerType::Adam {
1213 beta1: _,
1214 beta2: _,
1215 epsilon: _,
1216 } => {
1217 let lr = self.training_config.learning_rate;
1219 self.parameters.quantum_params =
1220 &self.parameters.quantum_params - &(gradients * lr);
1221 }
1222 _ => {
1223 let lr = self.training_config.learning_rate;
1225 self.parameters.quantum_params =
1226 &self.parameters.quantum_params - &(gradients * lr);
1227 }
1228 }
1229
1230 let clipped_params: Vec<f64> = self
1232 .parameters
1233 .quantum_params
1234 .iter()
1235 .enumerate()
1236 .map(|(i, ¶m)| {
1237 if i < self.parameters.parameter_bounds.len() {
1238 let (min_val, max_val) = self.parameters.parameter_bounds[i];
1239 param.max(min_val).min(max_val)
1240 } else {
1241 param
1242 }
1243 })
1244 .collect();
1245
1246 for (i, value) in clipped_params.into_iter().enumerate() {
1247 self.parameters.quantum_params[i] = value;
1248 }
1249
1250 Ok(())
1251 }
1252
1253 fn compute_batch_accuracy(
1255 &self,
1256 batch: &[(Array1<f64>, Array1<f64>)],
1257 predictions: &[Array1<f64>],
1258 ) -> f64 {
1259 let mut correct = 0;
1260 for ((_, target), prediction) in batch.iter().zip(predictions.iter()) {
1261 if self.is_prediction_correct(prediction, target) {
1262 correct += 1;
1263 }
1264 }
1265 correct as f64 / batch.len() as f64
1266 }
1267
1268 fn is_prediction_correct(&self, prediction: &Array1<f64>, target: &Array1<f64>) -> bool {
1270 if prediction.len() > 1 {
1272 let pred_class = prediction
1273 .iter()
1274 .enumerate()
1275 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
1276 .map(|(idx, _)| idx)
1277 .unwrap_or(0);
1278 let target_class = target
1279 .iter()
1280 .enumerate()
1281 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
1282 .map(|(idx, _)| idx)
1283 .unwrap_or(0);
1284 pred_class == target_class
1285 } else if !prediction.is_empty() && !target.is_empty() {
1286 (prediction[0] - target[0]).abs() < 0.1
1288 } else {
1289 false
1290 }
1291 }
1292
1293 fn compute_validation_loss(
1295 &self,
1296 data: &[(Array1<f64>, Array1<f64>)],
1297 ) -> Result<f64, SamplerError> {
1298 let mut total_loss = 0.0;
1299 for (input, target) in data.iter().take(10) {
1300 let prediction = self.forward(input)?;
1302 total_loss += self.compute_loss(&prediction, target)?;
1303 }
1304 Ok(total_loss / 10.0)
1305 }
1306
1307 fn initialize_parameters(&mut self) -> Result<(), SamplerError> {
1309 match &self.parameters.initialization_scheme {
1310 ParameterInitializationScheme::RandomUniform { min, max } => {
1311 let mut rng = thread_rng();
1312 for param in &mut self.parameters.quantum_params {
1313 *param = rng.gen_range(*min..*max);
1314 }
1315 }
1316 ParameterInitializationScheme::RandomNormal { mean, std } => {
1317 let mut rng = thread_rng();
1318 for param in &mut self.parameters.quantum_params {
1319 *param = rng.gen::<f64>() * std + mean;
1320 }
1321 }
1322 _ => {
1323 let mut rng = thread_rng();
1325 for param in &mut self.parameters.quantum_params {
1326 *param = rng.gen_range(-PI..PI);
1327 }
1328 }
1329 }
1330 Ok(())
1331 }
1332
1333 fn compute_parameter_statistics(&self) -> ParameterStatistics {
1335 let params = &self.parameters.quantum_params;
1336 let mean = params.mean().unwrap_or(0.0);
1337 let std = params.std(0.0);
1338
1339 let ranges = vec![(
1340 *params
1341 .iter()
1342 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1343 .unwrap_or(&0.0),
1344 *params
1345 .iter()
1346 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1347 .unwrap_or(&0.0),
1348 )];
1349 let correlations = Array2::eye(params.len());
1350
1351 ParameterStatistics {
1352 mean_values: Array1::from_elem(params.len(), mean),
1353 std_values: Array1::from_elem(params.len(), std),
1354 ranges,
1355 correlations,
1356 }
1357 }
1358
1359 fn compute_convergence_rate(&self) -> f64 {
1361 if self.training_history.len() < 2 {
1362 return 0.0;
1363 }
1364
1365 let initial_loss = self.training_history[0].training_loss;
1366 let final_loss = self
1367 .training_history
1368 .last()
1369 .expect("training_history verified to have at least 2 elements")
1370 .training_loss;
1371
1372 if initial_loss > 0.0 {
1373 (initial_loss - final_loss) / initial_loss
1374 } else {
1375 0.0
1376 }
1377 }
1378
1379 fn compute_training_stability(&self) -> f64 {
1381 if self.training_history.len() < 10 {
1382 return 1.0;
1383 }
1384
1385 let losses: Vec<f64> = self
1386 .training_history
1387 .iter()
1388 .map(|epoch| epoch.training_loss)
1389 .collect();
1390 let mean_loss = losses.iter().sum::<f64>() / losses.len() as f64;
1391 let variance = losses
1392 .iter()
1393 .map(|loss| (loss - mean_loss).powi(2))
1394 .sum::<f64>()
1395 / losses.len() as f64;
1396
1397 1.0 / (1.0 + variance.sqrt())
1398 }
1399
1400 fn compute_overfitting_measure(&self) -> f64 {
1402 if self.training_history.is_empty() {
1403 return 0.0;
1404 }
1405
1406 let last_epoch = self
1407 .training_history
1408 .last()
1409 .expect("training_history verified to be non-empty");
1410 if let Some(val_loss) = last_epoch.validation_loss {
1411 (val_loss - last_epoch.training_loss).max(0.0)
1412 } else {
1413 0.0
1414 }
1415 }
1416
1417 const fn calculate_quantum_params(architecture: &QNNArchitecture) -> usize {
1419 architecture.num_qubits * architecture.circuit_depth * 3
1421 }
1422
1423 const fn calculate_classical_params(architecture: &QNNArchitecture) -> usize {
1425 architecture.input_dim * architecture.output_dim
1427 }
1428
1429 fn build_quantum_layers(
1431 architecture: &QNNArchitecture,
1432 ) -> Result<Vec<QuantumLayer>, SamplerError> {
1433 let mut layers = Vec::new();
1434
1435 for layer_id in 0..architecture.circuit_depth {
1436 let mut gates = Vec::new();
1437 let mut parametrized_gates = Vec::new();
1438
1439 for qubit in 0..architecture.num_qubits {
1441 parametrized_gates.push(ParametrizedGate {
1442 gate_type: ParametrizedGateType::Rotation {
1443 axis: RotationAxis::Y,
1444 },
1445 qubits: vec![qubit],
1446 parameter_indices: vec![layer_id * architecture.num_qubits + qubit],
1447 gate_function: GateFunction::StandardRotation,
1448 });
1449 }
1450
1451 match &architecture.entanglement_pattern {
1453 EntanglementPattern::Linear => {
1454 for qubit in 0..architecture.num_qubits - 1 {
1455 gates.push(QuantumGate::CNOT {
1456 control: qubit,
1457 target: qubit + 1,
1458 });
1459 }
1460 }
1461 EntanglementPattern::Circular => {
1462 for qubit in 0..architecture.num_qubits {
1463 let target = (qubit + 1) % architecture.num_qubits;
1464 gates.push(QuantumGate::CNOT {
1465 control: qubit,
1466 target,
1467 });
1468 }
1469 }
1470 _ => {
1471 for qubit in 0..architecture.num_qubits - 1 {
1473 gates.push(QuantumGate::CNOT {
1474 control: qubit,
1475 target: qubit + 1,
1476 });
1477 }
1478 }
1479 }
1480
1481 layers.push(QuantumLayer {
1482 layer_id,
1483 num_qubits: architecture.num_qubits,
1484 gates,
1485 parametrized_gates,
1486 layer_type: QuantumLayerType::Variational,
1487 skip_connections: Vec::new(),
1488 });
1489 }
1490
1491 Ok(layers)
1492 }
1493
1494 fn build_classical_layers(
1496 architecture: &QNNArchitecture,
1497 ) -> Result<Vec<ClassicalLayer>, SamplerError> {
1498 let mut layers = Vec::new();
1499
1500 if architecture.input_dim != architecture.num_qubits {
1502 let weights = Array2::zeros((architecture.num_qubits, architecture.input_dim));
1503 let biases = Array1::zeros(architecture.num_qubits);
1504
1505 layers.push(ClassicalLayer {
1506 layer_type: ClassicalLayerType::Dense,
1507 input_dim: architecture.input_dim,
1508 output_dim: architecture.num_qubits,
1509 weights,
1510 biases,
1511 activation: ActivationFunction::Tanh,
1512 });
1513 }
1514
1515 Ok(layers)
1516 }
1517}
1518
1519impl Default for QNNMetrics {
1521 fn default() -> Self {
1522 Self {
1523 training_metrics: TrainingMetrics {
1524 final_training_loss: 0.0,
1525 convergence_rate: 0.0,
1526 epochs_to_convergence: 0,
1527 training_stability: 0.0,
1528 overfitting_measure: 0.0,
1529 },
1530 validation_metrics: ValidationMetrics {
1531 best_validation_loss: 0.0,
1532 validation_accuracy: 0.0,
1533 generalization_gap: 0.0,
1534 cv_scores: Vec::new(),
1535 confidence_intervals: Vec::new(),
1536 },
1537 quantum_metrics: QuantumMetrics {
1538 quantum_volume: 0.0,
1539 entanglement_measures: Vec::new(),
1540 quantum_advantage: 0.0,
1541 fidelity_measures: Vec::new(),
1542 coherence_utilization: 0.0,
1543 },
1544 computational_metrics: ComputationalMetrics {
1545 training_time_per_epoch: 0.0,
1546 inference_time: 0.0,
1547 memory_usage: 0.0,
1548 quantum_execution_time: 0.0,
1549 classical_computation_time: 0.0,
1550 },
1551 }
1552 }
1553}
1554
1555pub fn create_binary_classification_qnn(
1557 num_qubits: usize,
1558) -> Result<QuantumNeuralNetwork, SamplerError> {
1559 let architecture = QNNArchitecture {
1560 input_dim: num_qubits,
1561 output_dim: 1,
1562 num_qubits,
1563 circuit_depth: 3,
1564 entanglement_pattern: EntanglementPattern::Linear,
1565 measurement_scheme: MeasurementScheme::Computational,
1566 postprocessing: PostprocessingScheme::Linear,
1567 };
1568
1569 let training_config = QNNTrainingConfig {
1570 learning_rate: 0.01,
1571 batch_size: 32,
1572 num_epochs: 100,
1573 optimizer: OptimizerType::Adam {
1574 beta1: 0.9,
1575 beta2: 0.999,
1576 epsilon: 1e-8,
1577 },
1578 loss_function: LossFunction::MeanSquaredError,
1579 regularization: RegularizationConfig {
1580 l1_strength: 0.0,
1581 l2_strength: 0.001,
1582 dropout_prob: 0.0,
1583 parameter_noise: 0.0,
1584 quantum_noise: QuantumNoiseConfig {
1585 enable_noise: false,
1586 depolarizing_strength: 0.0,
1587 amplitude_damping: 0.0,
1588 phase_damping: 0.0,
1589 gate_error_rates: HashMap::new(),
1590 },
1591 },
1592 early_stopping: EarlyStoppingConfig {
1593 enabled: true,
1594 patience: 10,
1595 min_improvement: 1e-4,
1596 monitor_metric: "validation_loss".to_string(),
1597 },
1598 gradient_estimation: GradientEstimationMethod::ParameterShift,
1599 };
1600
1601 QuantumNeuralNetwork::new(architecture, training_config)
1602}
1603
1604pub fn create_optimization_qnn(problem_size: usize) -> Result<QuantumNeuralNetwork, SamplerError> {
1606 let num_qubits = (problem_size as f64).log2().ceil() as usize;
1607
1608 let architecture = QNNArchitecture {
1609 input_dim: problem_size,
1610 output_dim: problem_size,
1611 num_qubits,
1612 circuit_depth: 5,
1613 entanglement_pattern: EntanglementPattern::HardwareEfficient,
1614 measurement_scheme: MeasurementScheme::Pauli {
1615 bases: vec![PauliBasis::Z, PauliBasis::X, PauliBasis::Y],
1616 },
1617 postprocessing: PostprocessingScheme::NonlinearNN {
1618 hidden_dims: vec![64, 32],
1619 },
1620 };
1621
1622 let training_config = QNNTrainingConfig {
1623 learning_rate: 0.005,
1624 batch_size: 16,
1625 num_epochs: 200,
1626 optimizer: OptimizerType::QuantumNaturalGradient,
1627 loss_function: LossFunction::ExpectationValueLoss,
1628 regularization: RegularizationConfig {
1629 l1_strength: 0.001,
1630 l2_strength: 0.01,
1631 dropout_prob: 0.1,
1632 parameter_noise: 0.01,
1633 quantum_noise: QuantumNoiseConfig {
1634 enable_noise: true,
1635 depolarizing_strength: 0.01,
1636 amplitude_damping: 0.001,
1637 phase_damping: 0.001,
1638 gate_error_rates: HashMap::new(),
1639 },
1640 },
1641 early_stopping: EarlyStoppingConfig {
1642 enabled: true,
1643 patience: 20,
1644 min_improvement: 1e-5,
1645 monitor_metric: "validation_loss".to_string(),
1646 },
1647 gradient_estimation: GradientEstimationMethod::QuantumFisherInformation,
1648 };
1649
1650 QuantumNeuralNetwork::new(architecture, training_config)
1651}
1652
1653#[cfg(test)]
1654mod tests {
1655 use super::*;
1656
1657 #[test]
1658 fn test_qnn_creation() {
1659 let qnn = create_binary_classification_qnn(4)
1660 .expect("Failed to create binary classification QNN with 4 qubits");
1661 assert_eq!(qnn.architecture.num_qubits, 4);
1662 assert_eq!(qnn.architecture.circuit_depth, 3);
1663 assert_eq!(qnn.layers.len(), 3);
1664 }
1665
1666 #[test]
1667 fn test_qnn_forward_pass() {
1668 let qnn = create_binary_classification_qnn(2)
1669 .expect("Failed to create binary classification QNN with 2 qubits");
1670 let input = Array1::from_vec(vec![0.5, 0.7]);
1671 let output = qnn.forward(&input);
1672 assert!(output.is_ok());
1673 }
1674
1675 #[test]
1676 fn test_optimization_qnn_creation() {
1677 let qnn = create_optimization_qnn(8)
1678 .expect("Failed to create optimization QNN with problem size 8");
1679 assert_eq!(qnn.architecture.input_dim, 8);
1680 assert_eq!(qnn.architecture.output_dim, 8);
1681 assert!(qnn.architecture.num_qubits >= 3); }
1683
1684 #[test]
1685 fn test_parameter_initialization() {
1686 let mut qnn = create_binary_classification_qnn(3)
1687 .expect("Failed to create binary classification QNN with 3 qubits");
1688 qnn.initialize_parameters()
1689 .expect("Failed to initialize QNN parameters");
1690
1691 for ¶m in &qnn.parameters.quantum_params {
1693 assert!((-PI..=PI).contains(¶m));
1694 }
1695 }
1696
1697 #[test]
1698 fn test_quantum_gate_application() {
1699 let qnn = create_binary_classification_qnn(2)
1700 .expect("Failed to create binary classification QNN with 2 qubits");
1701 let state = Array1::from_vec(vec![1.0, 0.0, 0.0, 0.0]); let new_state = qnn
1704 .apply_rx_gate(&state, 0, PI / 2.0)
1705 .expect("Failed to apply RX gate");
1706
1707 assert!((new_state[0] - 1.0 / 2.0_f64.sqrt()).abs() < 1e-10);
1709 assert!((new_state[2] - 1.0 / 2.0_f64.sqrt()).abs() < 1e-10);
1710 }
1711}