1use super::*;
7use crate::{CircuitResult, DeviceError, DeviceResult, QuantumDevice};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13pub trait QuantumNeuralNetwork: Send + Sync {
15 fn forward(&self, input: &[f64]) -> DeviceResult<Vec<f64>>;
17
18 fn parameters(&self) -> &[f64];
20
21 fn set_parameters(&mut self, params: Vec<f64>) -> DeviceResult<()>;
23
24 fn parameter_count(&self) -> usize;
26
27 fn architecture(&self) -> QNNArchitecture;
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct QNNArchitecture {
34 pub network_type: QNNType,
35 pub num_qubits: usize,
36 pub num_layers: usize,
37 pub num_parameters: usize,
38 pub input_encoding: InputEncoding,
39 pub output_decoding: OutputDecoding,
40 pub entangling_strategy: EntanglingStrategy,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub enum QNNType {
46 PQC,
48 QCNN,
50 VQC,
52 QGAN,
54 HybridCQN,
56 QRNN,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub enum InputEncoding {
63 Amplitude,
65 Angle,
67 Basis,
69 CoherentState,
71 Displacement,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub enum OutputDecoding {
78 PauliExpectation,
80 Probabilities,
82 Fidelity,
84 CoherentMeasurement,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub enum EntanglingStrategy {
91 Linear,
92 Circular,
93 AllToAll,
94 Random,
95 Hardware,
96 Custom(Vec<(usize, usize)>),
97}
98
99pub struct PQCNetwork {
101 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
102 num_qubits: usize,
103 num_layers: usize,
104 parameters: Vec<f64>,
105 input_encoding: InputEncoding,
106 output_decoding: OutputDecoding,
107 entangling_strategy: EntanglingStrategy,
108 measurement_operators: Vec<PauliOperator>,
109}
110
111impl PQCNetwork {
112 pub fn new(
114 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
115 num_qubits: usize,
116 num_layers: usize,
117 input_encoding: InputEncoding,
118 output_decoding: OutputDecoding,
119 entangling_strategy: EntanglingStrategy,
120 ) -> Self {
121 let parameter_count = Self::calculate_parameter_count(num_qubits, num_layers);
122 let parameters = (0..parameter_count)
123 .map(|_| fastrand::f64() * 2.0 * std::f64::consts::PI)
124 .collect();
125
126 let measurement_operators = (0..num_qubits).map(|_| PauliOperator::Z).collect();
127
128 Self {
129 device,
130 num_qubits,
131 num_layers,
132 parameters,
133 input_encoding,
134 output_decoding,
135 entangling_strategy,
136 measurement_operators,
137 }
138 }
139
140 const fn calculate_parameter_count(num_qubits: usize, num_layers: usize) -> usize {
141 3 * num_qubits * num_layers
143 }
144
145 pub async fn build_circuit(&self, input: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit> {
147 let mut circuit = ParameterizedQuantumCircuit::new(self.num_qubits);
148
149 self.encode_input(&mut circuit, input).await?;
151
152 let mut param_idx = 0;
154 for layer in 0..self.num_layers {
155 for qubit in 0..self.num_qubits {
157 circuit.add_rx_gate(qubit, self.parameters[param_idx])?;
158 param_idx += 1;
159 circuit.add_ry_gate(qubit, self.parameters[param_idx])?;
160 param_idx += 1;
161 circuit.add_rz_gate(qubit, self.parameters[param_idx])?;
162 param_idx += 1;
163 }
164
165 self.add_entangling_gates(&mut circuit, layer).await?;
167 }
168
169 Ok(circuit)
170 }
171
172 async fn encode_input(
173 &self,
174 circuit: &mut ParameterizedQuantumCircuit,
175 input: &[f64],
176 ) -> DeviceResult<()> {
177 match self.input_encoding {
178 InputEncoding::Angle => {
179 let padded_input = self.pad_input(input, self.num_qubits);
181 for (qubit, &value) in padded_input.iter().enumerate() {
182 circuit.add_ry_gate(qubit, value)?;
183 }
184 }
185 InputEncoding::Amplitude => {
186 for qubit in 0..self.num_qubits {
189 circuit.add_h_gate(qubit)?;
190 }
191 }
193 InputEncoding::Basis => {
194 let binary_input = self.convert_to_binary(input);
196 for (qubit, &bit) in binary_input.iter().enumerate() {
197 if bit == 1 {
198 circuit.add_x_gate(qubit)?;
199 }
200 }
201 }
202 _ => {
203 return Err(DeviceError::InvalidInput(format!(
204 "Input encoding {:?} not implemented for PQC",
205 self.input_encoding
206 )));
207 }
208 }
209 Ok(())
210 }
211
212 async fn add_entangling_gates(
213 &self,
214 circuit: &mut ParameterizedQuantumCircuit,
215 _layer: usize,
216 ) -> DeviceResult<()> {
217 match &self.entangling_strategy {
218 EntanglingStrategy::Linear => {
219 for qubit in 0..self.num_qubits - 1 {
220 circuit.add_cnot_gate(qubit, qubit + 1)?;
221 }
222 }
223 EntanglingStrategy::Circular => {
224 for qubit in 0..self.num_qubits - 1 {
225 circuit.add_cnot_gate(qubit, qubit + 1)?;
226 }
227 if self.num_qubits > 2 {
228 circuit.add_cnot_gate(self.num_qubits - 1, 0)?;
229 }
230 }
231 EntanglingStrategy::AllToAll => {
232 for i in 0..self.num_qubits {
233 for j in i + 1..self.num_qubits {
234 circuit.add_cnot_gate(i, j)?;
235 }
236 }
237 }
238 EntanglingStrategy::Custom(connections) => {
239 for &(control, target) in connections {
240 if control < self.num_qubits && target < self.num_qubits {
241 circuit.add_cnot_gate(control, target)?;
242 }
243 }
244 }
245 _ => {
246 for qubit in 0..self.num_qubits - 1 {
248 circuit.add_cnot_gate(qubit, qubit + 1)?;
249 }
250 }
251 }
252 Ok(())
253 }
254
255 fn pad_input(&self, input: &[f64], target_size: usize) -> Vec<f64> {
256 let mut padded = input.to_vec();
257 while padded.len() < target_size {
258 padded.push(0.0);
259 }
260 padded.truncate(target_size);
261 padded
262 }
263
264 fn convert_to_binary(&self, input: &[f64]) -> Vec<u8> {
265 let mut binary = Vec::new();
266 for &value in input {
267 let int_value = (value * 255.0) as u8;
268 for i in 0..8 {
269 binary.push((int_value >> i) & 1);
270 if binary.len() >= self.num_qubits {
271 break;
272 }
273 }
274 if binary.len() >= self.num_qubits {
275 break;
276 }
277 }
278 while binary.len() < self.num_qubits {
279 binary.push(0);
280 }
281 binary.truncate(self.num_qubits);
282 binary
283 }
284
285 async fn decode_output(&self, circuit_result: &CircuitResult) -> DeviceResult<Vec<f64>> {
286 match self.output_decoding {
287 OutputDecoding::PauliExpectation => {
288 let mut expectations = Vec::new();
290 for (qubit, pauli_op) in self.measurement_operators.iter().enumerate() {
291 let expectation =
292 self.compute_pauli_expectation(circuit_result, qubit, pauli_op)?;
293 expectations.push(expectation);
294 }
295 Ok(expectations)
296 }
297 OutputDecoding::Probabilities => {
298 let total_shots = circuit_result.shots as f64;
300 let mut probs = Vec::new();
301
302 for i in 0..self.num_qubits {
303 let mut prob_one = 0.0;
304 for (bitstring, count) in &circuit_result.counts {
305 if let Some(bit_char) = bitstring.chars().nth(i) {
306 if bit_char == '1' {
307 prob_one += *count as f64 / total_shots;
308 }
309 }
310 }
311 probs.push(prob_one);
312 }
313 Ok(probs)
314 }
315 _ => Err(DeviceError::InvalidInput(format!(
316 "Output decoding {:?} not implemented",
317 self.output_decoding
318 ))),
319 }
320 }
321
322 fn compute_pauli_expectation(
323 &self,
324 circuit_result: &CircuitResult,
325 qubit: usize,
326 pauli_op: &PauliOperator,
327 ) -> DeviceResult<f64> {
328 let mut expectation = 0.0;
329 let total_shots = circuit_result.shots as f64;
330
331 for (bitstring, count) in &circuit_result.counts {
332 let probability = *count as f64 / total_shots;
333
334 let eigenvalue = if let Some(bit_char) = bitstring.chars().nth(qubit) {
335 match pauli_op {
336 PauliOperator::Z => {
337 if bit_char == '0' {
338 1.0
339 } else {
340 -1.0
341 }
342 }
343 PauliOperator::X | PauliOperator::Y => {
344 return Err(DeviceError::InvalidInput(
346 "X and Y Pauli measurements require basis rotation".to_string(),
347 ));
348 }
349 PauliOperator::I => 1.0,
350 }
351 } else {
352 0.0
353 };
354
355 expectation += probability * eigenvalue;
356 }
357
358 Ok(expectation)
359 }
360
361 async fn execute_circuit_helper(
363 _device: &(dyn QuantumDevice + Send + Sync),
364 circuit: &ParameterizedQuantumCircuit,
365 shots: usize,
366 ) -> DeviceResult<CircuitResult> {
367 crate::quantum_ml::circuit_simulation::simulate_and_sample(circuit, shots)
371 }
372}
373
374impl QuantumNeuralNetwork for PQCNetwork {
375 fn forward(&self, input: &[f64]) -> DeviceResult<Vec<f64>> {
376 let rt = tokio::runtime::Runtime::new().map_err(|e| {
378 DeviceError::ExecutionFailed(format!("Failed to create tokio runtime: {e}"))
379 })?;
380 rt.block_on(async {
381 let circuit = self.build_circuit(input).await?;
382 let device = self.device.read().await;
383 let result = Self::execute_circuit_helper(&*device, &circuit, 1024).await?;
384 self.decode_output(&result).await
385 })
386 }
387
388 fn parameters(&self) -> &[f64] {
389 &self.parameters
390 }
391
392 fn set_parameters(&mut self, params: Vec<f64>) -> DeviceResult<()> {
393 if params.len() != self.parameters.len() {
394 return Err(DeviceError::InvalidInput(format!(
395 "Expected {} parameters, got {}",
396 self.parameters.len(),
397 params.len()
398 )));
399 }
400 self.parameters = params;
401 Ok(())
402 }
403
404 fn parameter_count(&self) -> usize {
405 self.parameters.len()
406 }
407
408 fn architecture(&self) -> QNNArchitecture {
409 QNNArchitecture {
410 network_type: QNNType::PQC,
411 num_qubits: self.num_qubits,
412 num_layers: self.num_layers,
413 num_parameters: self.parameters.len(),
414 input_encoding: self.input_encoding.clone(),
415 output_decoding: self.output_decoding.clone(),
416 entangling_strategy: self.entangling_strategy.clone(),
417 }
418 }
419}
420
421pub struct QCNN {
423 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
424 num_qubits: usize,
425 conv_layers: Vec<QConvLayer>,
426 pooling_layers: Vec<QPoolingLayer>,
427 parameters: Vec<f64>,
428 input_encoding: InputEncoding,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct QConvLayer {
434 pub kernel_size: usize,
435 pub stride: usize,
436 pub num_filters: usize,
437 pub parameter_indices: Vec<usize>,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct QPoolingLayer {
443 pub pool_size: usize,
444 pub pool_type: QPoolingType,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
448pub enum QPoolingType {
449 Max,
450 Average,
451 Measurement,
452}
453
454impl QCNN {
455 pub fn new(
456 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
457 num_qubits: usize,
458 conv_layers: Vec<QConvLayer>,
459 pooling_layers: Vec<QPoolingLayer>,
460 input_encoding: InputEncoding,
461 ) -> Self {
462 let total_params = conv_layers.iter()
463 .map(|layer| layer.num_filters * layer.kernel_size * 3) .sum();
465
466 let parameters = (0..total_params)
467 .map(|_| fastrand::f64() * 2.0 * std::f64::consts::PI)
468 .collect();
469
470 Self {
471 device,
472 num_qubits,
473 conv_layers,
474 pooling_layers,
475 parameters,
476 input_encoding,
477 }
478 }
479
480 pub async fn build_circuit(&self, input: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit> {
481 let mut circuit = ParameterizedQuantumCircuit::new(self.num_qubits);
482
483 self.encode_input(&mut circuit, input).await?;
485
486 let mut current_qubits = self.num_qubits;
487
488 for (conv_layer, pool_layer) in self.conv_layers.iter().zip(self.pooling_layers.iter()) {
490 self.apply_conv_layer(&mut circuit, conv_layer, current_qubits)
492 .await?;
493
494 current_qubits = self
496 .apply_pooling_layer(&mut circuit, pool_layer, current_qubits)
497 .await?;
498 }
499
500 Ok(circuit)
501 }
502
503 async fn encode_input(
504 &self,
505 circuit: &mut ParameterizedQuantumCircuit,
506 input: &[f64],
507 ) -> DeviceResult<()> {
508 match self.input_encoding {
509 InputEncoding::Angle => {
510 let padded_input = self.pad_input(input, self.num_qubits);
511 for (qubit, &value) in padded_input.iter().enumerate() {
512 circuit.add_ry_gate(qubit, value)?;
513 }
514 }
515 InputEncoding::Amplitude => {
516 for qubit in 0..self.num_qubits {
518 circuit.add_h_gate(qubit)?;
519 }
520 }
521 _ => {
522 return Err(DeviceError::InvalidInput(format!(
523 "Input encoding {:?} not implemented for QCNN",
524 self.input_encoding
525 )));
526 }
527 }
528 Ok(())
529 }
530
531 async fn apply_conv_layer(
532 &self,
533 circuit: &mut ParameterizedQuantumCircuit,
534 layer: &QConvLayer,
535 num_active_qubits: usize,
536 ) -> DeviceResult<()> {
537 let num_windows = (num_active_qubits - layer.kernel_size) / layer.stride + 1;
538
539 for window in 0..num_windows {
540 let start_qubit = window * layer.stride;
541
542 for filter in 0..layer.num_filters {
543 let param_offset = filter * layer.kernel_size * 3;
544
545 for i in 0..layer.kernel_size {
547 let qubit = start_qubit + i;
548 let param_base = param_offset + i * 3;
549
550 if param_base + 2 < self.parameters.len() {
551 circuit.add_rx_gate(qubit, self.parameters[param_base])?;
552 circuit.add_ry_gate(qubit, self.parameters[param_base + 1])?;
553 circuit.add_rz_gate(qubit, self.parameters[param_base + 2])?;
554 }
555 }
556
557 for i in 0..layer.kernel_size - 1 {
559 let control = start_qubit + i;
560 let target = start_qubit + i + 1;
561 circuit.add_cnot_gate(control, target)?;
562 }
563 }
564 }
565
566 Ok(())
567 }
568
569 async fn apply_pooling_layer(
570 &self,
571 circuit: &mut ParameterizedQuantumCircuit,
572 layer: &QPoolingLayer,
573 num_active_qubits: usize,
574 ) -> DeviceResult<usize> {
575 let num_pools = num_active_qubits / layer.pool_size;
576
577 match layer.pool_type {
578 QPoolingType::Measurement => {
579 Ok(num_pools)
582 }
583 QPoolingType::Max | QPoolingType::Average => {
584 for pool in 0..num_pools {
586 let start_qubit = pool * layer.pool_size;
587
588 for i in 0..layer.pool_size - 1 {
590 let qubit1 = start_qubit + i;
591 let qubit2 = start_qubit + i + 1;
592 circuit.add_cnot_gate(qubit1, qubit2)?;
593 }
594 }
595 Ok(num_pools)
596 }
597 }
598 }
599
600 fn pad_input(&self, input: &[f64], target_size: usize) -> Vec<f64> {
601 let mut padded = input.to_vec();
602 while padded.len() < target_size {
603 padded.push(0.0);
604 }
605 padded.truncate(target_size);
606 padded
607 }
608}
609
610impl QuantumNeuralNetwork for QCNN {
611 fn forward(&self, input: &[f64]) -> DeviceResult<Vec<f64>> {
612 let rt = tokio::runtime::Runtime::new().map_err(|e| {
613 DeviceError::ExecutionFailed(format!("Failed to create tokio runtime: {e}"))
614 })?;
615 rt.block_on(async {
616 let circuit = self.build_circuit(input).await?;
617 let device = self.device.read().await;
618 let result = Self::execute_circuit_helper(&*device, &circuit, 1024).await?;
619
620 let mut output = Vec::new();
622 let total_shots = result.shots as f64;
623
624 for i in 0..self.num_qubits.min(8) {
625 let mut prob_one = 0.0;
627 for (bitstring, count) in &result.counts {
628 if let Some(bit_char) = bitstring.chars().nth(i) {
629 if bit_char == '1' {
630 prob_one += *count as f64 / total_shots;
631 }
632 }
633 }
634 output.push(prob_one);
635 }
636
637 Ok(output)
638 })
639 }
640
641 fn parameters(&self) -> &[f64] {
642 &self.parameters
643 }
644
645 fn set_parameters(&mut self, params: Vec<f64>) -> DeviceResult<()> {
646 if params.len() != self.parameters.len() {
647 return Err(DeviceError::InvalidInput(format!(
648 "Expected {} parameters, got {}",
649 self.parameters.len(),
650 params.len()
651 )));
652 }
653 self.parameters = params;
654 Ok(())
655 }
656
657 fn parameter_count(&self) -> usize {
658 self.parameters.len()
659 }
660
661 fn architecture(&self) -> QNNArchitecture {
662 QNNArchitecture {
663 network_type: QNNType::QCNN,
664 num_qubits: self.num_qubits,
665 num_layers: self.conv_layers.len(),
666 num_parameters: self.parameters.len(),
667 input_encoding: self.input_encoding.clone(),
668 output_decoding: OutputDecoding::Probabilities,
669 entangling_strategy: EntanglingStrategy::Linear,
670 }
671 }
672}
673
674impl QCNN {
675 async fn execute_circuit_helper(
677 _device: &(dyn QuantumDevice + Send + Sync),
678 circuit: &ParameterizedQuantumCircuit,
679 shots: usize,
680 ) -> DeviceResult<CircuitResult> {
681 crate::quantum_ml::circuit_simulation::simulate_and_sample(circuit, shots)
685 }
686}
687
688pub struct VQC {
690 pqc_network: PQCNetwork,
691 class_mapping: HashMap<usize, String>,
692}
693
694impl VQC {
695 pub fn new(
696 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
697 num_qubits: usize,
698 num_layers: usize,
699 num_classes: usize,
700 ) -> Self {
701 let pqc_network = PQCNetwork::new(
702 device,
703 num_qubits,
704 num_layers,
705 InputEncoding::Angle,
706 OutputDecoding::PauliExpectation,
707 EntanglingStrategy::Linear,
708 );
709
710 let class_mapping = (0..num_classes)
711 .map(|i| (i, format!("class_{i}")))
712 .collect();
713
714 Self {
715 pqc_network,
716 class_mapping,
717 }
718 }
719
720 pub fn classify(&self, input: &[f64]) -> DeviceResult<ClassificationResult> {
721 let raw_output = self.pqc_network.forward(input)?;
722
723 let class_probs = self.softmax(&raw_output);
725
726 let (predicted_class, confidence) = class_probs
728 .iter()
729 .enumerate()
730 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
731 .map_or((0, 0.0), |(idx, &prob)| (idx, prob));
732
733 let class_name = self
734 .class_mapping
735 .get(&predicted_class)
736 .cloned()
737 .unwrap_or_else(|| "unknown".to_string());
738
739 Ok(ClassificationResult {
740 predicted_class,
741 class_name,
742 confidence,
743 class_probabilities: class_probs,
744 })
745 }
746
747 fn softmax(&self, values: &[f64]) -> Vec<f64> {
748 let max_val = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
749 let exp_values: Vec<f64> = values.iter().map(|&x| (x - max_val).exp()).collect();
750 let sum_exp: f64 = exp_values.iter().sum();
751 exp_values.iter().map(|&x| x / sum_exp).collect()
752 }
753}
754
755impl QuantumNeuralNetwork for VQC {
756 fn forward(&self, input: &[f64]) -> DeviceResult<Vec<f64>> {
757 self.pqc_network.forward(input)
758 }
759
760 fn parameters(&self) -> &[f64] {
761 self.pqc_network.parameters()
762 }
763
764 fn set_parameters(&mut self, params: Vec<f64>) -> DeviceResult<()> {
765 self.pqc_network.set_parameters(params)
766 }
767
768 fn parameter_count(&self) -> usize {
769 self.pqc_network.parameter_count()
770 }
771
772 fn architecture(&self) -> QNNArchitecture {
773 let mut arch = self.pqc_network.architecture();
774 arch.network_type = QNNType::VQC;
775 arch
776 }
777}
778
779#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct ClassificationResult {
782 pub predicted_class: usize,
783 pub class_name: String,
784 pub confidence: f64,
785 pub class_probabilities: Vec<f64>,
786}
787
788pub fn create_pqc_classifier(
790 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
791 num_features: usize,
792 num_classes: usize,
793 num_layers: usize,
794) -> DeviceResult<VQC> {
795 let num_qubits =
796 (num_features as f64).log2().ceil() as usize + (num_classes as f64).log2().ceil() as usize;
797 Ok(VQC::new(device, num_qubits, num_layers, num_classes))
798}
799
800pub fn create_qcnn_classifier(
802 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
803 image_size: usize,
804) -> DeviceResult<QCNN> {
805 let num_qubits = (image_size as f64).log2().ceil() as usize;
806
807 let conv_layers = vec![
808 QConvLayer {
809 kernel_size: 2,
810 stride: 1,
811 num_filters: 2,
812 parameter_indices: (0..12).collect(), },
814 QConvLayer {
815 kernel_size: 2,
816 stride: 1,
817 num_filters: 1,
818 parameter_indices: (12..18).collect(), },
820 ];
821
822 let pooling_layers = vec![
823 QPoolingLayer {
824 pool_size: 2,
825 pool_type: QPoolingType::Measurement,
826 },
827 QPoolingLayer {
828 pool_size: 2,
829 pool_type: QPoolingType::Measurement,
830 },
831 ];
832
833 Ok(QCNN::new(
834 device,
835 num_qubits,
836 conv_layers,
837 pooling_layers,
838 InputEncoding::Angle,
839 ))
840}
841
842#[cfg(test)]
843mod tests {
844 use super::*;
845 use crate::test_utils::create_mock_quantum_device;
846
847 #[test]
848 fn test_pqc_network_creation() {
849 let device = create_mock_quantum_device();
850 let network = PQCNetwork::new(
851 device,
852 4,
853 2,
854 InputEncoding::Angle,
855 OutputDecoding::PauliExpectation,
856 EntanglingStrategy::Linear,
857 );
858
859 assert_eq!(network.num_qubits, 4);
860 assert_eq!(network.num_layers, 2);
861 assert_eq!(network.parameter_count(), 24); }
863
864 #[test]
865 fn test_vqc_creation() {
866 let device = create_mock_quantum_device();
867 let classifier = VQC::new(device, 4, 2, 3);
868
869 assert_eq!(classifier.class_mapping.len(), 3);
870 assert_eq!(classifier.parameter_count(), 24);
871 }
872
873 #[test]
874 fn test_qcnn_creation() {
875 let device = create_mock_quantum_device();
876 let conv_layers = vec![QConvLayer {
877 kernel_size: 2,
878 stride: 1,
879 num_filters: 1,
880 parameter_indices: (0..6).collect(),
881 }];
882 let pooling_layers = vec![QPoolingLayer {
883 pool_size: 2,
884 pool_type: QPoolingType::Max,
885 }];
886
887 let qcnn = QCNN::new(device, 4, conv_layers, pooling_layers, InputEncoding::Angle);
888
889 assert_eq!(qcnn.num_qubits, 4);
890 assert_eq!(qcnn.parameter_count(), 6);
891 }
892
893 #[test]
894 fn test_softmax() {
895 let classifier = {
896 let device = create_mock_quantum_device();
897 VQC::new(device, 4, 2, 3)
898 };
899
900 let input = vec![1.0, 2.0, 3.0];
901 let output = classifier.softmax(&input);
902
903 assert_eq!(output.len(), 3);
904 assert!((output.iter().sum::<f64>() - 1.0).abs() < 1e-10);
905 assert!(output[2] > output[1]);
906 assert!(output[1] > output[0]);
907 }
908
909 #[test]
910 fn test_parameter_operations() {
911 let device = create_mock_quantum_device();
912 let mut network = PQCNetwork::new(
913 device,
914 4,
915 2,
916 InputEncoding::Angle,
917 OutputDecoding::PauliExpectation,
918 EntanglingStrategy::Linear,
919 );
920
921 let original_params = network.parameters().to_vec();
922 let new_params = vec![0.0; network.parameter_count()];
923
924 network
925 .set_parameters(new_params.clone())
926 .expect("Setting parameters should succeed");
927 assert_eq!(network.parameters(), &new_params);
928 assert_ne!(network.parameters(), &original_params);
929
930 let invalid_params = vec![0.0; 5];
932 assert!(network.set_parameters(invalid_params).is_err());
933 }
934}