1use crate::error::{MLError, Result};
8use crate::qnn::QuantumNeuralNetwork;
9use quantrs2_circuit::prelude::Circuit;
10use quantrs2_sim::statevector::StateVectorSimulator;
11use scirs2_core::ndarray::{Array1, Array2};
12use scirs2_core::random::prelude::*;
13use std::fmt;
14
15#[derive(Debug, Clone, Copy)]
17pub enum GeneratorType {
18 Classical,
20
21 QuantumOnly,
23
24 HybridClassicalQuantum,
26}
27
28#[derive(Debug, Clone, Copy)]
30pub enum DiscriminatorType {
31 Classical,
33
34 QuantumOnly,
36
37 HybridQuantumFeatures,
39
40 HybridQuantumDecision,
42}
43
44#[derive(Debug, Clone)]
46pub struct GANTrainingHistory {
47 pub gen_losses: Vec<f64>,
49
50 pub disc_losses: Vec<f64>,
52}
53
54#[derive(Debug, Clone)]
56pub struct GANEvaluationMetrics {
57 pub real_accuracy: f64,
59
60 pub fake_accuracy: f64,
62
63 pub overall_accuracy: f64,
65
66 pub js_divergence: f64,
68}
69
70pub trait Generator {
72 fn generate(&self, num_samples: usize) -> Result<Array2<f64>>;
74
75 fn generate_conditional(
77 &self,
78 num_samples: usize,
79 conditions: &[(usize, f64)],
80 ) -> Result<Array2<f64>>;
81
82 fn update(
84 &mut self,
85 latent_vectors: &Array2<f64>,
86 discriminator_outputs: &Array1<f64>,
87 learning_rate: f64,
88 ) -> Result<f64>;
89}
90
91pub trait Discriminator {
93 fn discriminate(&self, samples: &Array2<f64>) -> Result<Array1<f64>>;
95
96 fn predict_batch(&self, samples: &Array2<f64>) -> Result<Array1<f64>> {
98 self.discriminate(samples)
99 }
100
101 fn update(
103 &mut self,
104 real_samples: &Array2<f64>,
105 generated_samples: &Array2<f64>,
106 learning_rate: f64,
107 ) -> Result<f64>;
108}
109
110pub mod physics_gan {
112 use super::*;
113
114 pub struct ParticleGAN {
116 pub gan: QuantumGAN,
118
119 pub physics_params: PhysicsParameters,
121 }
122
123 #[derive(Debug, Clone)]
125 pub struct PhysicsParameters {
126 pub energy_scale: f64,
128
129 pub momentum_conservation: f64,
131
132 pub quantum_effects: bool,
134 }
135
136 impl ParticleGAN {
137 pub fn new(
139 num_qubits_gen: usize,
140 num_qubits_disc: usize,
141 latent_dim: usize,
142 data_dim: usize,
143 ) -> Result<Self> {
144 let gan = QuantumGAN::new(
146 num_qubits_gen,
147 num_qubits_disc,
148 latent_dim,
149 data_dim,
150 GeneratorType::HybridClassicalQuantum,
151 DiscriminatorType::HybridQuantumFeatures,
152 )?;
153
154 let physics_params = PhysicsParameters {
156 energy_scale: 100.0, momentum_conservation: 0.99,
158 quantum_effects: true,
159 };
160
161 Ok(ParticleGAN {
162 gan,
163 physics_params,
164 })
165 }
166
167 pub fn train(
169 &mut self,
170 particle_data: &Array2<f64>,
171 epochs: usize,
172 ) -> Result<&GANTrainingHistory> {
173 self.gan.train(
175 particle_data,
176 epochs,
177 32, 0.01, 0.01, 1, )
182 }
183
184 pub fn generate_particles(&self, num_particles: usize) -> Result<Array2<f64>> {
186 let raw_data = self.gan.generate(num_particles)?;
188
189 Ok(raw_data)
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
199pub struct QuantumGenerator {
200 num_qubits: usize,
202
203 latent_dim: usize,
205
206 data_dim: usize,
208
209 generator_type: GeneratorType,
211
212 qnn: QuantumNeuralNetwork,
214}
215
216impl QuantumGenerator {
217 pub fn new(
219 num_qubits: usize,
220 latent_dim: usize,
221 data_dim: usize,
222 generator_type: GeneratorType,
223 ) -> Result<Self> {
224 let layers = vec![
226 crate::qnn::QNNLayerType::EncodingLayer {
227 num_features: latent_dim,
228 },
229 crate::qnn::QNNLayerType::VariationalLayer {
230 num_params: 2 * num_qubits,
231 },
232 crate::qnn::QNNLayerType::EntanglementLayer {
233 connectivity: "full".to_string(),
234 },
235 crate::qnn::QNNLayerType::VariationalLayer {
236 num_params: 2 * num_qubits,
237 },
238 crate::qnn::QNNLayerType::MeasurementLayer {
239 measurement_basis: "computational".to_string(),
240 },
241 ];
242
243 let qnn = QuantumNeuralNetwork::new(layers, num_qubits, latent_dim, data_dim)?;
244
245 Ok(QuantumGenerator {
246 num_qubits,
247 latent_dim,
248 data_dim,
249 generator_type,
250 qnn,
251 })
252 }
253}
254
255impl QuantumGenerator {
256 fn generate_from_latent(&self, latent_vectors: &Array2<f64>) -> Result<Array2<f64>> {
263 let num_samples = latent_vectors.nrows();
264 let mut samples = Array2::zeros((num_samples, self.data_dim));
265 for i in 0..num_samples {
266 let latent = latent_vectors.row(i).to_owned();
267 let output = self.qnn.forward(&latent)?;
268 for j in 0..self.data_dim {
269 let expectation = if j < output.len() { output[j] } else { 0.0 };
270 samples[[i, j]] = (expectation + 1.0) * 0.5;
271 }
272 }
273 Ok(samples)
274 }
275
276 fn adversarial_loss(
279 &self,
280 latent_vectors: &Array2<f64>,
281 discriminator: &QuantumDiscriminator,
282 ) -> Result<f64> {
283 let samples = self.generate_from_latent(latent_vectors)?;
284 let outputs = discriminator.discriminate(&samples)?;
285 let n = outputs.len();
286 if n == 0 {
287 return Ok(0.0);
288 }
289 let loss = outputs.iter().map(|&d| (d - 1.0) * (d - 1.0)).sum::<f64>();
290 Ok(loss / n as f64)
291 }
292
293 pub fn adversarial_update(
301 &mut self,
302 latent_vectors: &Array2<f64>,
303 discriminator: &QuantumDiscriminator,
304 learning_rate: f64,
305 ) -> Result<f64> {
306 if latent_vectors.nrows() == 0 {
307 return Err(MLError::DataError(
308 "adversarial update received an empty latent batch".to_string(),
309 ));
310 }
311 let num_params = self.qnn.parameters.len();
312 let epsilon = 1e-3;
313 let base_loss = self.adversarial_loss(latent_vectors, discriminator)?;
314 let original = self.qnn.parameters.clone();
315
316 let mut gradient = Array1::<f64>::zeros(num_params);
317 for j in 0..num_params {
318 self.qnn.parameters[j] = original[j] + epsilon;
319 let loss_plus = self.adversarial_loss(latent_vectors, discriminator)?;
320 self.qnn.parameters[j] = original[j] - epsilon;
321 let loss_minus = self.adversarial_loss(latent_vectors, discriminator)?;
322 self.qnn.parameters[j] = original[j];
323 gradient[j] = (loss_plus - loss_minus) / (2.0 * epsilon);
324 }
325
326 for j in 0..num_params {
327 self.qnn.parameters[j] = original[j] - learning_rate * gradient[j];
328 }
329 Ok(base_loss)
330 }
331
332 fn feature_matching_loss(
335 &self,
336 latent_vectors: &Array2<f64>,
337 prototype: &Array1<f64>,
338 ) -> Result<f64> {
339 let samples = self.generate_from_latent(latent_vectors)?;
340 let n = samples.nrows();
341 if n == 0 {
342 return Ok(0.0);
343 }
344 let mut total = 0.0;
345 for i in 0..n {
346 for j in 0..self.data_dim {
347 let diff = samples[[i, j]] - prototype[j];
348 total += diff * diff;
349 }
350 }
351 Ok(total / n as f64)
352 }
353}
354
355impl Generator for QuantumGenerator {
356 fn generate(&self, num_samples: usize) -> Result<Array2<f64>> {
357 let mut latent_vectors = Array2::zeros((num_samples, self.latent_dim));
360 for i in 0..num_samples {
361 for j in 0..self.latent_dim {
362 latent_vectors[[i, j]] = thread_rng().random::<f64>() * 2.0 - 1.0;
363 }
364 }
365 self.generate_from_latent(&latent_vectors)
366 }
367
368 fn generate_conditional(
369 &self,
370 num_samples: usize,
371 conditions: &[(usize, f64)],
372 ) -> Result<Array2<f64>> {
373 let mut samples = self.generate(num_samples)?;
375
376 for &(feature_idx, value) in conditions {
378 if feature_idx < self.data_dim {
379 for i in 0..num_samples {
380 samples[[i, feature_idx]] = value;
381 }
382 }
383 }
384
385 Ok(samples)
386 }
387
388 fn update(
389 &mut self,
390 latent_vectors: &Array2<f64>,
391 discriminator_outputs: &Array1<f64>,
392 learning_rate: f64,
393 ) -> Result<f64> {
394 let n = latent_vectors.nrows();
405 if n == 0 {
406 return Err(MLError::DataError(
407 "generator update received an empty latent batch".to_string(),
408 ));
409 }
410
411 let samples = self.generate_from_latent(latent_vectors)?;
412 let weight_sum: f64 = discriminator_outputs.iter().map(|&d| d.max(0.0)).sum();
413
414 let mut prototype = Array1::zeros(self.data_dim);
415 if weight_sum > 1e-12 {
416 for i in 0..n.min(discriminator_outputs.len()) {
417 let weight = discriminator_outputs[i].max(0.0) / weight_sum;
418 for j in 0..self.data_dim {
419 prototype[j] += weight * samples[[i, j]];
420 }
421 }
422 } else {
423 for i in 0..n {
424 for j in 0..self.data_dim {
425 prototype[j] += samples[[i, j]] / n as f64;
426 }
427 }
428 }
429
430 let num_params = self.qnn.parameters.len();
431 let epsilon = 1e-3;
432 let base_loss = self.feature_matching_loss(latent_vectors, &prototype)?;
433 let original = self.qnn.parameters.clone();
434
435 let mut gradient = Array1::<f64>::zeros(num_params);
436 for j in 0..num_params {
437 self.qnn.parameters[j] = original[j] + epsilon;
438 let loss_plus = self.feature_matching_loss(latent_vectors, &prototype)?;
439 self.qnn.parameters[j] = original[j] - epsilon;
440 let loss_minus = self.feature_matching_loss(latent_vectors, &prototype)?;
441 self.qnn.parameters[j] = original[j];
442 gradient[j] = (loss_plus - loss_minus) / (2.0 * epsilon);
443 }
444
445 for j in 0..num_params {
446 self.qnn.parameters[j] = original[j] - learning_rate * gradient[j];
447 }
448 Ok(base_loss)
449 }
450}
451
452#[derive(Debug, Clone)]
454pub struct QuantumDiscriminator {
455 num_qubits: usize,
457
458 data_dim: usize,
460
461 discriminator_type: DiscriminatorType,
463
464 qnn: QuantumNeuralNetwork,
466}
467
468impl QuantumDiscriminator {
469 pub fn new(
471 num_qubits: usize,
472 data_dim: usize,
473 discriminator_type: DiscriminatorType,
474 ) -> Result<Self> {
475 let layers = vec![
477 crate::qnn::QNNLayerType::EncodingLayer {
478 num_features: data_dim,
479 },
480 crate::qnn::QNNLayerType::VariationalLayer {
481 num_params: 2 * num_qubits,
482 },
483 crate::qnn::QNNLayerType::EntanglementLayer {
484 connectivity: "full".to_string(),
485 },
486 crate::qnn::QNNLayerType::VariationalLayer {
487 num_params: 2 * num_qubits,
488 },
489 crate::qnn::QNNLayerType::MeasurementLayer {
490 measurement_basis: "computational".to_string(),
491 },
492 ];
493
494 let qnn = QuantumNeuralNetwork::new(
495 layers, num_qubits, data_dim, 1, )?;
497
498 Ok(QuantumDiscriminator {
499 num_qubits,
500 data_dim,
501 discriminator_type,
502 qnn,
503 })
504 }
505}
506
507impl QuantumDiscriminator {
508 fn discriminate_one(&self, sample: &Array1<f64>) -> Result<f64> {
515 let output = self.qnn.forward(sample)?;
516 if output.is_empty() {
517 return Err(MLError::MLOperationError(
518 "discriminator QNN produced an empty output".to_string(),
519 ));
520 }
521 Ok((output[0] + 1.0) * 0.5)
522 }
523
524 fn discrimination_loss(
527 &self,
528 real_samples: &Array2<f64>,
529 generated_samples: &Array2<f64>,
530 ) -> Result<f64> {
531 let n_real = real_samples.nrows();
532 let n_fake = generated_samples.nrows();
533
534 let mut real_loss = 0.0;
535 for i in 0..n_real {
536 let d = self.discriminate_one(&real_samples.row(i).to_owned())?;
537 real_loss += (d - 1.0) * (d - 1.0);
538 }
539 let mut fake_loss = 0.0;
540 for i in 0..n_fake {
541 let d = self.discriminate_one(&generated_samples.row(i).to_owned())?;
542 fake_loss += d * d;
543 }
544
545 let mut loss = 0.0;
546 if n_real > 0 {
547 loss += real_loss / n_real as f64;
548 }
549 if n_fake > 0 {
550 loss += fake_loss / n_fake as f64;
551 }
552 Ok(loss)
553 }
554}
555
556impl Discriminator for QuantumDiscriminator {
557 fn discriminate(&self, samples: &Array2<f64>) -> Result<Array1<f64>> {
558 let num_samples = samples.nrows();
559 let mut outputs = Array1::zeros(num_samples);
560 for i in 0..num_samples {
561 outputs[i] = self.discriminate_one(&samples.row(i).to_owned())?;
562 }
563 Ok(outputs)
564 }
565
566 fn update(
567 &mut self,
568 real_samples: &Array2<f64>,
569 generated_samples: &Array2<f64>,
570 learning_rate: f64,
571 ) -> Result<f64> {
572 let n_real = real_samples.nrows();
579 let n_fake = generated_samples.nrows();
580 let num_params = self.qnn.parameters.len();
581 let mut gradient = Array1::<f64>::zeros(num_params);
582
583 for i in 0..n_real {
584 let x = real_samples.row(i).to_owned();
585 let d = self.discriminate_one(&x)?;
586 let d_expectation = self.qnn.output_component_gradient(&x, 0)?;
587 let coeff = (d - 1.0) / n_real as f64;
588 for j in 0..num_params {
589 gradient[j] += coeff * d_expectation[j];
590 }
591 }
592 for i in 0..n_fake {
593 let x = generated_samples.row(i).to_owned();
594 let d = self.discriminate_one(&x)?;
595 let d_expectation = self.qnn.output_component_gradient(&x, 0)?;
596 let coeff = d / n_fake as f64;
597 for j in 0..num_params {
598 gradient[j] += coeff * d_expectation[j];
599 }
600 }
601
602 for j in 0..num_params {
603 self.qnn.parameters[j] -= learning_rate * gradient[j];
604 }
605
606 self.discrimination_loss(real_samples, generated_samples)
609 }
610}
611
612#[derive(Debug, Clone)]
614pub struct QuantumGAN {
615 pub generator: QuantumGenerator,
617
618 pub discriminator: QuantumDiscriminator,
620
621 pub training_history: GANTrainingHistory,
623}
624
625impl QuantumGAN {
626 pub fn new(
628 num_qubits_gen: usize,
629 num_qubits_disc: usize,
630 latent_dim: usize,
631 data_dim: usize,
632 generator_type: GeneratorType,
633 discriminator_type: DiscriminatorType,
634 ) -> Result<Self> {
635 let generator =
636 QuantumGenerator::new(num_qubits_gen, latent_dim, data_dim, generator_type)?;
637
638 let discriminator =
639 QuantumDiscriminator::new(num_qubits_disc, data_dim, discriminator_type)?;
640
641 let training_history = GANTrainingHistory {
642 gen_losses: Vec::new(),
643 disc_losses: Vec::new(),
644 };
645
646 Ok(QuantumGAN {
647 generator,
648 discriminator,
649 training_history,
650 })
651 }
652
653 pub fn train(
655 &mut self,
656 real_data: &Array2<f64>,
657 epochs: usize,
658 batch_size: usize,
659 gen_learning_rate: f64,
660 disc_learning_rate: f64,
661 disc_steps: usize,
662 ) -> Result<&GANTrainingHistory> {
663 let mut gen_losses = Vec::with_capacity(epochs);
664 let mut disc_losses = Vec::with_capacity(epochs);
665
666 for _epoch in 0..epochs {
667 let mut disc_loss_sum = 0.0;
669 for _step in 0..disc_steps {
670 let fake_samples = self.generator.generate(batch_size)?;
672
673 let real_batch = sample_batch(real_data, batch_size)?;
675
676 let disc_loss =
678 self.discriminator
679 .update(&real_batch, &fake_samples, disc_learning_rate)?;
680 disc_loss_sum += disc_loss;
681 }
682 let avg_disc_loss = disc_loss_sum / disc_steps as f64;
683
684 let latent_dim = self.generator.latent_dim;
687 let mut latent_vectors = Array2::zeros((batch_size, latent_dim));
688 for i in 0..batch_size {
689 for j in 0..latent_dim {
690 latent_vectors[[i, j]] = thread_rng().random::<f64>() * 2.0 - 1.0;
691 }
692 }
693 let gen_loss = self.generator.adversarial_update(
694 &latent_vectors,
695 &self.discriminator,
696 gen_learning_rate,
697 )?;
698
699 gen_losses.push(gen_loss);
701 disc_losses.push(avg_disc_loss);
702 }
703
704 self.training_history = GANTrainingHistory {
705 gen_losses,
706 disc_losses,
707 };
708
709 Ok(&self.training_history)
710 }
711
712 pub fn generate(&self, num_samples: usize) -> Result<Array2<f64>> {
714 self.generator.generate(num_samples)
715 }
716
717 pub fn generate_conditional(
719 &self,
720 num_samples: usize,
721 conditions: &[(usize, f64)],
722 ) -> Result<Array2<f64>> {
723 self.generator.generate_conditional(num_samples, conditions)
724 }
725
726 pub fn evaluate(
728 &self,
729 real_data: &Array2<f64>,
730 num_samples: usize,
731 ) -> Result<GANEvaluationMetrics> {
732 let fake_samples = self.generate(num_samples)?;
734
735 let real_preds = self.discriminator.predict_batch(real_data)?;
737 let real_correct = real_preds.iter().filter(|&&p| p > 0.5).count();
738 let real_accuracy = real_correct as f64 / real_preds.len() as f64;
739
740 let fake_preds = self.discriminator.predict_batch(&fake_samples)?;
742 let fake_correct = fake_preds.iter().filter(|&&p| p < 0.5).count();
743 let fake_accuracy = fake_correct as f64 / fake_preds.len() as f64;
744
745 let overall_correct = real_correct + fake_correct;
747 let overall_total = real_preds.len() + fake_preds.len();
748 let overall_accuracy = overall_correct as f64 / overall_total as f64;
749
750 let js_divergence = calculate_js_divergence(real_data, &fake_samples)?;
753
754 Ok(GANEvaluationMetrics {
755 real_accuracy,
756 fake_accuracy,
757 overall_accuracy,
758 js_divergence,
759 })
760 }
761}
762
763fn calculate_js_divergence(data1: &Array2<f64>, data2: &Array2<f64>) -> Result<f64> {
769 if data1.ncols() == 0 || data1.nrows() == 0 || data2.nrows() == 0 {
770 return Ok(0.0);
771 }
772
773 let n_bins: usize = 20;
774 let n_cols = data1.ncols().min(data2.ncols());
775 let mut total_js = 0.0;
776
777 for col in 0..n_cols {
778 let col1: Vec<f64> = data1.column(col).to_vec();
779 let col2: Vec<f64> = data2.column(col).to_vec();
780
781 let min_val = col1
782 .iter()
783 .chain(col2.iter())
784 .cloned()
785 .fold(f64::INFINITY, f64::min);
786 let max_val = col1
787 .iter()
788 .chain(col2.iter())
789 .cloned()
790 .fold(f64::NEG_INFINITY, f64::max);
791
792 if (max_val - min_val).abs() < 1e-14 {
793 continue;
795 }
796
797 let bin_width = (max_val - min_val) / n_bins as f64;
798 let mut hist1 = vec![0.0f64; n_bins];
799 let mut hist2 = vec![0.0f64; n_bins];
800
801 for &v in &col1 {
802 let bin = ((v - min_val) / bin_width) as usize;
803 let bin = bin.min(n_bins - 1);
804 hist1[bin] += 1.0;
805 }
806 for &v in &col2 {
807 let bin = ((v - min_val) / bin_width) as usize;
808 let bin = bin.min(n_bins - 1);
809 hist2[bin] += 1.0;
810 }
811
812 let n1 = col1.len() as f64;
813 let n2 = col2.len() as f64;
814 for i in 0..n_bins {
815 hist1[i] /= n1;
816 hist2[i] /= n2;
817 }
818
819 let mut js = 0.0f64;
821 for i in 0..n_bins {
822 let p = hist1[i];
823 let q = hist2[i];
824 let m = (p + q) * 0.5;
825 if m > 1e-14 {
826 if p > 1e-14 {
827 js += 0.5 * p * (p / m).ln();
828 }
829 if q > 1e-14 {
830 js += 0.5 * q * (q / m).ln();
831 }
832 }
833 }
834 total_js += js;
835 }
836
837 Ok(if n_cols > 0 {
838 total_js / n_cols as f64
839 } else {
840 0.0
841 })
842}
843
844fn sample_batch(data: &Array2<f64>, batch_size: usize) -> Result<Array2<f64>> {
846 let num_samples = data.nrows();
847 let mut batch = Array2::zeros((batch_size.min(num_samples), data.ncols()));
848
849 for i in 0..batch_size.min(num_samples) {
850 let idx = fastrand::usize(0..num_samples);
851 batch.row_mut(i).assign(&data.row(idx));
852 }
853
854 Ok(batch)
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860 use scirs2_core::ndarray::Array2;
861
862 #[test]
863 fn test_js_divergence_identical() {
864 let data = Array2::from_shape_vec((4, 2), vec![0.0, 1.0, 0.5, 0.5, 0.2, 0.8, 0.7, 0.3])
865 .expect("array creation failed");
866 let js = calculate_js_divergence(&data, &data).expect("divergence failed");
867 assert!(js < 0.01, "JS(p,p) should be ≈0, got {js}");
868 }
869
870 #[test]
871 fn test_js_divergence_bounded() {
872 let data1 =
873 Array2::from_shape_vec((4, 1), vec![0.0, 0.0, 0.0, 0.0]).expect("array creation");
874 let data2 =
875 Array2::from_shape_vec((4, 1), vec![1.0, 1.0, 1.0, 1.0]).expect("array creation");
876 let js = calculate_js_divergence(&data1, &data2).expect("divergence failed");
877 assert!(js >= 0.0 && js <= 1.0, "JS should be in [0, 1], got {js}");
878 }
879}
880
881impl fmt::Display for GeneratorType {
882 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
883 match self {
884 GeneratorType::Classical => write!(f, "Classical"),
885 GeneratorType::QuantumOnly => write!(f, "Quantum Only"),
886 GeneratorType::HybridClassicalQuantum => write!(f, "Hybrid Classical-Quantum"),
887 }
888 }
889}
890
891impl fmt::Display for DiscriminatorType {
892 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
893 match self {
894 DiscriminatorType::Classical => write!(f, "Classical"),
895 DiscriminatorType::QuantumOnly => write!(f, "Quantum Only"),
896 DiscriminatorType::HybridQuantumFeatures => write!(f, "Hybrid with Quantum Features"),
897 DiscriminatorType::HybridQuantumDecision => write!(f, "Hybrid with Quantum Decision"),
898 }
899 }
900}