use crate::{
error::QuantRS2Result, gate::multi::*, gate::single::*, gate::GateOp, qubit::QubitId,
variational::VariationalOptimizer,
};
use scirs2_core::ndarray::{Array1, Array2, Axis};
use scirs2_core::random::{rngs::StdRng, Rng, RngExt, SeedableRng};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QGANConfig {
pub generator_qubits: usize,
pub discriminator_qubits: usize,
pub latent_qubits: usize,
pub data_qubits: usize,
pub generator_lr: f64,
pub discriminator_lr: f64,
pub generator_depth: usize,
pub discriminator_depth: usize,
pub batch_size: usize,
pub max_iterations: usize,
pub generator_frequency: usize,
pub use_quantum_advantage: bool,
pub noise_type: NoiseType,
pub regularization: f64,
pub random_seed: Option<u64>,
}
impl Default for QGANConfig {
fn default() -> Self {
Self {
generator_qubits: 6,
discriminator_qubits: 6,
latent_qubits: 4,
data_qubits: 4,
generator_lr: 0.01,
discriminator_lr: 0.01,
generator_depth: 8,
discriminator_depth: 6,
batch_size: 16,
max_iterations: 1000,
generator_frequency: 1,
use_quantum_advantage: true,
noise_type: NoiseType::Gaussian,
regularization: 0.001,
random_seed: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NoiseType {
Gaussian,
Uniform,
QuantumSuperposition,
BasisStates,
}
pub struct QGAN {
config: QGANConfig,
generator: QuantumGenerator,
discriminator: QuantumDiscriminator,
training_stats: QGANTrainingStats,
rng: StdRng,
iteration: usize,
}
pub struct QuantumGenerator {
circuit: QuantumGeneratorCircuit,
parameters: Array1<f64>,
#[allow(dead_code)]
optimizer: VariationalOptimizer,
gradient_history: VecDeque<Array1<f64>>,
}
pub struct QuantumDiscriminator {
circuit: QuantumDiscriminatorCircuit,
parameters: Array1<f64>,
#[allow(dead_code)]
optimizer: VariationalOptimizer,
gradient_history: VecDeque<Array1<f64>>,
}
#[derive(Debug, Clone)]
pub struct QuantumGeneratorCircuit {
latent_qubits: usize,
data_qubits: usize,
depth: usize,
total_qubits: usize,
noise_type: NoiseType,
}
#[derive(Debug, Clone)]
pub struct QuantumDiscriminatorCircuit {
data_qubits: usize,
aux_qubits: usize,
depth: usize,
total_qubits: usize,
}
#[derive(Debug, Clone, Default)]
pub struct QGANTrainingStats {
pub generator_losses: Vec<f64>,
pub discriminator_losses: Vec<f64>,
pub fidelities: Vec<f64>,
pub iteration_times: Vec<f64>,
pub convergence_metrics: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct QGANIterationMetrics {
pub generator_loss: f64,
pub discriminator_loss: f64,
pub real_accuracy: f64,
pub fake_accuracy: f64,
pub fidelity: f64,
pub iteration: usize,
}
impl QGAN {
pub fn new(config: QGANConfig) -> QuantRS2Result<Self> {
let rng = match config.random_seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_seed([0; 32]), };
let generator = QuantumGenerator::new(&config)?;
let discriminator = QuantumDiscriminator::new(&config)?;
Ok(Self {
config,
generator,
discriminator,
training_stats: QGANTrainingStats::default(),
rng,
iteration: 0,
})
}
pub fn train(&mut self, real_data: &Array2<f64>) -> QuantRS2Result<QGANIterationMetrics> {
let batch_size = self.config.batch_size.min(real_data.nrows());
let real_batch = self.sample_real_data_batch(real_data, batch_size)?;
let fake_batch = self.generate_fake_data_batch(batch_size)?;
let (d_loss_real, d_loss_fake) = self.train_discriminator(&real_batch, &fake_batch)?;
let discriminator_loss = d_loss_real + d_loss_fake;
let generator_loss = if self.iteration % self.config.generator_frequency == 0 {
self.train_generator(batch_size)?
} else {
0.0
};
let real_accuracy = self.compute_discriminator_accuracy(&real_batch, true)?;
let fake_accuracy = self.compute_discriminator_accuracy(&fake_batch, false)?;
let fidelity = self.compute_fidelity(&real_batch, &fake_batch)?;
self.training_stats.generator_losses.push(generator_loss);
self.training_stats
.discriminator_losses
.push(discriminator_loss);
self.training_stats.fidelities.push(fidelity);
let metrics = QGANIterationMetrics {
generator_loss,
discriminator_loss,
real_accuracy,
fake_accuracy,
fidelity,
iteration: self.iteration,
};
self.iteration += 1;
Ok(metrics)
}
pub fn generate_data(&mut self, num_samples: usize) -> QuantRS2Result<Array2<f64>> {
self.generate_fake_data_batch(num_samples)
}
pub fn discriminate(&self, data: &Array2<f64>) -> QuantRS2Result<Array1<f64>> {
let num_samples = data.nrows();
let mut scores = Array1::zeros(num_samples);
for i in 0..num_samples {
let sample = data.row(i).to_owned();
scores[i] = self.discriminator.discriminate(&sample)?;
}
Ok(scores)
}
fn sample_real_data_batch(
&mut self,
real_data: &Array2<f64>,
batch_size: usize,
) -> QuantRS2Result<Array2<f64>> {
let num_samples = real_data.nrows();
let mut batch = Array2::zeros((batch_size, real_data.ncols()));
for i in 0..batch_size {
let idx = self.rng.random_range(0..num_samples);
batch.row_mut(i).assign(&real_data.row(idx));
}
Ok(batch)
}
fn generate_fake_data_batch(&mut self, batch_size: usize) -> QuantRS2Result<Array2<f64>> {
let mut fake_batch = Array2::zeros((batch_size, self.config.data_qubits));
for i in 0..batch_size {
let noise = self.sample_noise()?;
let generated_sample = self.generator.generate(&noise)?;
fake_batch.row_mut(i).assign(&generated_sample);
}
Ok(fake_batch)
}
fn sample_noise(&mut self) -> QuantRS2Result<Array1<f64>> {
let mut noise = Array1::zeros(self.config.latent_qubits);
match self.config.noise_type {
NoiseType::Gaussian => {
for i in 0..self.config.latent_qubits {
noise[i] = self.rng.random::<f64>().mul_add(2.0, -1.0); }
}
NoiseType::Uniform => {
for i in 0..self.config.latent_qubits {
noise[i] = (self.rng.random::<f64>() * 2.0)
.mul_add(std::f64::consts::PI, -std::f64::consts::PI);
}
}
NoiseType::QuantumSuperposition => {
for i in 0..self.config.latent_qubits {
noise[i] = std::f64::consts::PI / 2.0; }
}
NoiseType::BasisStates => {
let state = self.rng.random_range(0..(1 << self.config.latent_qubits));
for i in 0..self.config.latent_qubits {
noise[i] = if (state >> i) & 1 == 1 {
std::f64::consts::PI
} else {
0.0
};
}
}
}
Ok(noise)
}
fn train_discriminator(
&mut self,
real_batch: &Array2<f64>,
fake_batch: &Array2<f64>,
) -> QuantRS2Result<(f64, f64)> {
let d_loss_real = self
.discriminator
.train_batch(real_batch, &Array1::ones(real_batch.nrows()))?;
let d_loss_fake = self
.discriminator
.train_batch(fake_batch, &Array1::zeros(fake_batch.nrows()))?;
Ok((d_loss_real, d_loss_fake))
}
fn train_generator(&mut self, batch_size: usize) -> QuantRS2Result<f64> {
let fake_batch = self.generate_fake_data_batch(batch_size)?;
let discriminator_scores = self.discriminate(&fake_batch)?;
let targets = Array1::ones(batch_size);
let generator_loss =
self.generator
.train_adversarial(&fake_batch, &targets, &discriminator_scores)?;
Ok(generator_loss)
}
fn compute_discriminator_accuracy(
&self,
data: &Array2<f64>,
is_real: bool,
) -> QuantRS2Result<f64> {
let scores = self.discriminate(data)?;
let threshold = 0.5;
let _target = if is_real { 1.0 } else { 0.0 };
let correct = scores
.iter()
.map(|&score| {
if (score > threshold) == is_real {
1.0
} else {
0.0
}
})
.sum::<f64>();
Ok(correct / data.nrows() as f64)
}
fn compute_fidelity(
&self,
real_batch: &Array2<f64>,
fake_batch: &Array2<f64>,
) -> QuantRS2Result<f64> {
use crate::error::QuantRS2Error;
let real_mean = real_batch
.mean_axis(Axis(0))
.ok_or_else(|| QuantRS2Error::InvalidInput("Empty real batch".to_string()))?;
let fake_mean = fake_batch
.mean_axis(Axis(0))
.ok_or_else(|| QuantRS2Error::InvalidInput("Empty fake batch".to_string()))?;
let real_var = real_batch.var_axis(Axis(0), 0.0);
let fake_var = fake_batch.var_axis(Axis(0), 0.0);
let mean_diff = (&real_mean - &fake_mean).mapv(|x| x.powi(2)).sum().sqrt();
let var_diff = (&real_var - &fake_var).mapv(|x| x.powi(2)).sum().sqrt();
let fidelity = (-0.5 * (mean_diff + var_diff)).exp();
Ok(fidelity)
}
pub const fn get_training_stats(&self) -> &QGANTrainingStats {
&self.training_stats
}
pub fn has_converged(&self, tolerance: f64, window: usize) -> bool {
if self.training_stats.fidelities.len() < window {
return false;
}
let recent_fidelities =
&self.training_stats.fidelities[self.training_stats.fidelities.len() - window..];
let mean_fidelity = recent_fidelities.iter().sum::<f64>() / window as f64;
mean_fidelity > 1.0 - tolerance
}
}
impl QuantumGenerator {
fn new(config: &QGANConfig) -> QuantRS2Result<Self> {
let circuit = QuantumGeneratorCircuit::new(
config.latent_qubits,
config.data_qubits,
config.generator_depth,
config.noise_type,
)?;
let num_parameters = circuit.get_parameter_count();
let mut parameters = Array1::zeros(num_parameters);
let mut rng = match config.random_seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_seed([0; 32]),
};
for param in &mut parameters {
*param = rng.random_range(-std::f64::consts::PI..std::f64::consts::PI);
}
let optimizer = VariationalOptimizer::new(0.01, 0.9);
let gradient_history = VecDeque::with_capacity(10);
Ok(Self {
circuit,
parameters,
optimizer,
gradient_history,
})
}
fn generate(&self, noise: &Array1<f64>) -> QuantRS2Result<Array1<f64>> {
self.circuit.generate_data(noise, &self.parameters)
}
fn train_adversarial(
&mut self,
generated_data: &Array2<f64>,
targets: &Array1<f64>,
discriminator_scores: &Array1<f64>,
) -> QuantRS2Result<f64> {
let batch_size = generated_data.nrows();
let mut total_loss = 0.0;
for i in 0..batch_size {
let data_sample = generated_data.row(i).to_owned();
let target = targets[i];
let score = discriminator_scores[i];
let loss = (score - target).powi(2);
total_loss += loss;
let gradients = self.circuit.compute_adversarial_gradients(
&data_sample,
target,
score,
&self.parameters,
)?;
self.update_parameters(&gradients, 0.01)?; }
Ok(total_loss / batch_size as f64)
}
fn update_parameters(
&mut self,
gradients: &Array1<f64>,
learning_rate: f64,
) -> QuantRS2Result<()> {
let mut effective_gradients = gradients.clone();
if let Some(prev_gradients) = self.gradient_history.back() {
let momentum = 0.9;
effective_gradients = &effective_gradients + &(prev_gradients * momentum);
}
for (param, &grad) in self.parameters.iter_mut().zip(effective_gradients.iter()) {
*param -= learning_rate * grad;
}
self.gradient_history.push_back(effective_gradients);
if self.gradient_history.len() > 10 {
self.gradient_history.pop_front();
}
Ok(())
}
}
impl QuantumDiscriminator {
fn new(config: &QGANConfig) -> QuantRS2Result<Self> {
let circuit = QuantumDiscriminatorCircuit::new(
config.data_qubits,
config.discriminator_qubits - config.data_qubits,
config.discriminator_depth,
)?;
let num_parameters = circuit.get_parameter_count();
let mut parameters = Array1::zeros(num_parameters);
let mut rng = match config.random_seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_seed([0; 32]),
};
for param in &mut parameters {
*param = rng.random_range(-std::f64::consts::PI..std::f64::consts::PI);
}
let optimizer = VariationalOptimizer::new(0.01, 0.9);
let gradient_history = VecDeque::with_capacity(10);
Ok(Self {
circuit,
parameters,
optimizer,
gradient_history,
})
}
fn discriminate(&self, data: &Array1<f64>) -> QuantRS2Result<f64> {
self.circuit.discriminate_data(data, &self.parameters)
}
fn train_batch(
&mut self,
data_batch: &Array2<f64>,
targets: &Array1<f64>,
) -> QuantRS2Result<f64> {
let batch_size = data_batch.nrows();
let mut total_loss = 0.0;
for i in 0..batch_size {
let data_sample = data_batch.row(i).to_owned();
let target = targets[i];
let prediction = self.discriminate(&data_sample)?;
let loss = -target.mul_add(prediction.ln(), (1.0 - target) * (1.0 - prediction).ln());
total_loss += loss;
let gradients = self.circuit.compute_discriminator_gradients(
&data_sample,
target,
prediction,
&self.parameters,
)?;
self.update_parameters(&gradients, 0.01)?; }
Ok(total_loss / batch_size as f64)
}
fn update_parameters(
&mut self,
gradients: &Array1<f64>,
learning_rate: f64,
) -> QuantRS2Result<()> {
let mut effective_gradients = gradients.clone();
if let Some(prev_gradients) = self.gradient_history.back() {
let momentum = 0.9;
effective_gradients = &effective_gradients + &(prev_gradients * momentum);
}
for (param, &grad) in self.parameters.iter_mut().zip(effective_gradients.iter()) {
*param -= learning_rate * grad;
}
self.gradient_history.push_back(effective_gradients);
if self.gradient_history.len() > 10 {
self.gradient_history.pop_front();
}
Ok(())
}
}
impl QuantumGeneratorCircuit {
const fn new(
latent_qubits: usize,
data_qubits: usize,
depth: usize,
noise_type: NoiseType,
) -> QuantRS2Result<Self> {
let total_qubits = latent_qubits + data_qubits;
Ok(Self {
latent_qubits,
data_qubits,
depth,
total_qubits,
noise_type,
})
}
const fn get_parameter_count(&self) -> usize {
let total_qubits = self.latent_qubits + self.data_qubits;
let rotations_per_layer = total_qubits * 3;
let entangling_per_layer = total_qubits; self.depth * (rotations_per_layer + entangling_per_layer)
}
fn generate_data(
&self,
noise: &Array1<f64>,
parameters: &Array1<f64>,
) -> QuantRS2Result<Array1<f64>> {
let mut gates = Vec::new();
for i in 0..self.latent_qubits {
let noise_value = if i < noise.len() { noise[i] } else { 0.0 };
gates.push(Box::new(RotationY {
target: QubitId(i as u32),
theta: noise_value,
}) as Box<dyn GateOp>);
}
let mut param_idx = 0;
for _layer in 0..self.depth {
for qubit in 0..self.latent_qubits + self.data_qubits {
if param_idx + 2 < parameters.len() {
gates.push(Box::new(RotationX {
target: QubitId(qubit as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
gates.push(Box::new(RotationY {
target: QubitId(qubit as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
gates.push(Box::new(RotationZ {
target: QubitId(qubit as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
}
}
for qubit in 0..self.latent_qubits + self.data_qubits - 1 {
if param_idx < parameters.len() {
gates.push(Box::new(CRZ {
control: QubitId(qubit as u32),
target: QubitId((qubit + 1) as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
}
}
}
let generated_data = self.simulate_generation_circuit(&gates)?;
Ok(generated_data)
}
fn simulate_generation_circuit(
&self,
gates: &[Box<dyn GateOp>],
) -> QuantRS2Result<Array1<f64>> {
let mut data = Array1::zeros(self.data_qubits);
let mut hash_value = 0u64;
for gate in gates {
if let Ok(matrix) = gate.matrix() {
for complex in &matrix {
hash_value = hash_value.wrapping_add((complex.re * 1000.0) as u64);
}
}
}
for i in 0..self.data_qubits {
let qubit_hash = hash_value.wrapping_add(i as u64);
data[i] = ((qubit_hash % 1000) as f64 / 1000.0).mul_add(2.0, -1.0); }
Ok(data)
}
fn compute_adversarial_gradients(
&self,
_data_sample: &Array1<f64>,
target: f64,
score: f64,
parameters: &Array1<f64>,
) -> QuantRS2Result<Array1<f64>> {
let mut gradients = Array1::zeros(parameters.len());
let shift = std::f64::consts::PI / 2.0;
for i in 0..parameters.len() {
let mut params_plus = parameters.clone();
params_plus[i] += shift;
let data_plus = self.generate_data(&Array1::zeros(self.latent_qubits), ¶ms_plus)?;
let mut params_minus = parameters.clone();
params_minus[i] -= shift;
let data_minus =
self.generate_data(&Array1::zeros(self.latent_qubits), ¶ms_minus)?;
let loss_gradient = 2.0 * (score - target);
let data_diff = (&data_plus - &data_minus).sum() / 2.0;
gradients[i] = loss_gradient * data_diff;
}
Ok(gradients)
}
}
impl QuantumDiscriminatorCircuit {
const fn new(data_qubits: usize, aux_qubits: usize, depth: usize) -> QuantRS2Result<Self> {
let total_qubits = data_qubits + aux_qubits;
Ok(Self {
data_qubits,
aux_qubits,
depth,
total_qubits,
})
}
const fn get_parameter_count(&self) -> usize {
let total_qubits = self.data_qubits + self.aux_qubits;
let rotations_per_layer = total_qubits * 3;
let entangling_per_layer = total_qubits;
self.depth * (rotations_per_layer + entangling_per_layer)
}
fn discriminate_data(
&self,
data: &Array1<f64>,
parameters: &Array1<f64>,
) -> QuantRS2Result<f64> {
let mut gates = Vec::new();
for i in 0..self.data_qubits {
let data_value = if i < data.len() { data[i] } else { 0.0 };
gates.push(Box::new(RotationY {
target: QubitId(i as u32),
theta: data_value * std::f64::consts::PI,
}) as Box<dyn GateOp>);
}
let mut param_idx = 0;
for _layer in 0..self.depth {
for qubit in 0..self.data_qubits + self.aux_qubits {
if param_idx + 2 < parameters.len() {
gates.push(Box::new(RotationX {
target: QubitId(qubit as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
gates.push(Box::new(RotationY {
target: QubitId(qubit as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
gates.push(Box::new(RotationZ {
target: QubitId(qubit as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
}
}
for qubit in 0..self.data_qubits + self.aux_qubits - 1 {
if param_idx < parameters.len() {
gates.push(Box::new(CRZ {
control: QubitId(qubit as u32),
target: QubitId((qubit + 1) as u32),
theta: parameters[param_idx],
}) as Box<dyn GateOp>);
param_idx += 1;
}
}
}
let probability = self.simulate_discrimination_circuit(&gates)?;
Ok(probability)
}
fn simulate_discrimination_circuit(&self, gates: &[Box<dyn GateOp>]) -> QuantRS2Result<f64> {
let mut hash_value = 0u64;
for gate in gates {
if let Ok(matrix) = gate.matrix() {
for complex in &matrix {
hash_value = hash_value.wrapping_add((complex.re * 1000.0) as u64);
hash_value = hash_value.wrapping_add((complex.im * 1000.0) as u64);
}
}
}
let probability = ((hash_value % 1000) as f64) / 1000.0;
Ok(probability)
}
fn compute_discriminator_gradients(
&self,
data_sample: &Array1<f64>,
target: f64,
prediction: f64,
parameters: &Array1<f64>,
) -> QuantRS2Result<Array1<f64>> {
let mut gradients = Array1::zeros(parameters.len());
let shift = std::f64::consts::PI / 2.0;
for i in 0..parameters.len() {
let mut params_plus = parameters.clone();
params_plus[i] += shift;
let pred_plus = self.discriminate_data(data_sample, ¶ms_plus)?;
let mut params_minus = parameters.clone();
params_minus[i] -= shift;
let pred_minus = self.discriminate_data(data_sample, ¶ms_minus)?;
let pred_gradient = if prediction > 0.0 && prediction < 1.0 {
-target / prediction + (1.0 - target) / (1.0 - prediction)
} else {
0.0 };
gradients[i] = pred_gradient * (pred_plus - pred_minus) / 2.0;
}
Ok(gradients)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qgan_creation() {
let config = QGANConfig::default();
let qgan = QGAN::new(config).expect("failed to create QGAN");
assert_eq!(qgan.iteration, 0);
assert_eq!(qgan.training_stats.generator_losses.len(), 0);
}
#[test]
fn test_noise_generation() {
let config = QGANConfig::default();
let mut qgan = QGAN::new(config).expect("failed to create QGAN");
let noise = qgan.sample_noise().expect("failed to sample noise");
assert_eq!(noise.len(), qgan.config.latent_qubits);
qgan.config.noise_type = NoiseType::Uniform;
let uniform_noise = qgan.sample_noise().expect("failed to sample uniform noise");
assert_eq!(uniform_noise.len(), qgan.config.latent_qubits);
qgan.config.noise_type = NoiseType::QuantumSuperposition;
let quantum_noise = qgan.sample_noise().expect("failed to sample quantum noise");
assert_eq!(quantum_noise.len(), qgan.config.latent_qubits);
}
#[test]
fn test_data_generation() {
let config = QGANConfig::default();
let mut qgan = QGAN::new(config).expect("failed to create QGAN");
let generated_data = qgan.generate_data(5).expect("failed to generate data");
assert_eq!(generated_data.nrows(), 5);
assert_eq!(generated_data.ncols(), qgan.config.data_qubits);
}
#[test]
fn test_discrimination() {
let config = QGANConfig::default();
let qgan = QGAN::new(config).expect("failed to create QGAN");
let data = Array2::from_shape_fn((3, qgan.config.data_qubits), |(i, j)| {
(i as f64 + j as f64) / 10.0
});
let scores = qgan.discriminate(&data).expect("failed to discriminate");
assert_eq!(scores.len(), 3);
for &score in scores.iter() {
assert!(score >= 0.0 && score <= 1.0);
}
}
#[test]
fn test_qgan_training_step() {
let config = QGANConfig {
batch_size: 4,
..Default::default()
};
let mut qgan = QGAN::new(config).expect("failed to create QGAN");
let real_data = Array2::from_shape_fn((10, qgan.config.data_qubits), |(i, j)| {
((i + j) as f64).sin()
});
let metrics = qgan.train(&real_data).expect("failed to train QGAN");
assert_eq!(metrics.iteration, 0);
assert!(metrics.fidelity >= 0.0 && metrics.fidelity <= 1.0);
assert_eq!(qgan.iteration, 1);
assert_eq!(qgan.training_stats.generator_losses.len(), 1);
assert_eq!(qgan.training_stats.discriminator_losses.len(), 1);
}
#[test]
fn test_convergence_check() {
let config = QGANConfig::default();
let mut qgan = QGAN::new(config).expect("failed to create QGAN");
for _ in 0..10 {
qgan.training_stats.fidelities.push(0.95);
}
assert!(qgan.has_converged(0.1, 5)); assert!(!qgan.has_converged(0.01, 5)); }
#[test]
fn test_quantum_generator_circuit() {
let circuit = QuantumGeneratorCircuit::new(3, 2, 4, NoiseType::Gaussian)
.expect("failed to create generator circuit");
let param_count = circuit.get_parameter_count();
assert!(param_count > 0);
let noise = Array1::from_vec(vec![0.5, -0.5, 0.0]);
let parameters = Array1::zeros(param_count);
let generated_data = circuit
.generate_data(&noise, ¶meters)
.expect("failed to generate data");
assert_eq!(generated_data.len(), 2);
}
#[test]
fn test_quantum_discriminator_circuit() {
let circuit = QuantumDiscriminatorCircuit::new(3, 2, 4)
.expect("failed to create discriminator circuit");
let param_count = circuit.get_parameter_count();
assert!(param_count > 0);
let data = Array1::from_vec(vec![0.5, -0.5, 0.0]);
let parameters = Array1::zeros(param_count);
let score = circuit
.discriminate_data(&data, ¶meters)
.expect("failed to discriminate data");
assert!(score >= 0.0 && score <= 1.0);
}
#[test]
fn test_fidelity_computation() {
let config = QGANConfig::default();
let qgan = QGAN::new(config).expect("failed to create QGAN");
let data1 = Array2::ones((5, 3));
let data2 = Array2::ones((5, 3));
let fidelity = qgan
.compute_fidelity(&data1, &data2)
.expect("failed to compute fidelity");
assert!(fidelity > 0.9);
let data3 = Array2::zeros((5, 3));
let fidelity2 = qgan
.compute_fidelity(&data1, &data3)
.expect("failed to compute fidelity");
assert!(fidelity2 < fidelity);
}
}