use scirs2_core::random::prelude::*;
use scirs2_core::random::ChaCha8Rng;
use scirs2_core::random::{Rng, SeedableRng};
use scirs2_core::RngExt;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use thiserror::Error;
use crate::ising::{IsingError, IsingModel};
#[derive(Error, Debug)]
pub enum CimError {
#[error("Ising error: {0}")]
IsingError(#[from] IsingError),
#[error("Invalid optical parameters: {0}")]
InvalidOpticalParameters(String),
#[error("Simulation error: {0}")]
SimulationError(String),
#[error("Network topology error: {0}")]
TopologyError(String),
#[error("Convergence error: {0}")]
ConvergenceError(String),
}
pub type CimResult<T> = Result<T, CimError>;
#[derive(Debug, Clone)]
pub struct OpticalParametricOscillator {
pub index: usize,
pub amplitude: Complex,
pub gain: f64,
pub loss: f64,
pub saturation: f64,
pub injection: Complex,
pub threshold: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Complex {
pub re: f64,
pub im: f64,
}
impl Complex {
#[must_use]
pub const fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
#[must_use]
pub fn from_polar(magnitude: f64, phase: f64) -> Self {
Self {
re: magnitude * phase.cos(),
im: magnitude * phase.sin(),
}
}
#[must_use]
pub fn magnitude_squared(&self) -> f64 {
self.re.mul_add(self.re, self.im * self.im)
}
#[must_use]
pub fn magnitude(&self) -> f64 {
self.magnitude_squared().sqrt()
}
#[must_use]
pub fn phase(&self) -> f64 {
self.im.atan2(self.re)
}
#[must_use]
pub fn conjugate(&self) -> Self {
Self {
re: self.re,
im: -self.im,
}
}
}
impl std::ops::Add for Complex {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
re: self.re + other.re,
im: self.im + other.im,
}
}
}
impl std::ops::Sub for Complex {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
re: self.re - other.re,
im: self.im - other.im,
}
}
}
impl std::ops::Mul for Complex {
type Output = Self;
fn mul(self, other: Self) -> Self {
Self {
re: self.re.mul_add(other.re, -(self.im * other.im)),
im: self.re.mul_add(other.im, self.im * other.re),
}
}
}
impl std::ops::Mul<f64> for Complex {
type Output = Self;
fn mul(self, scalar: f64) -> Self {
Self {
re: self.re * scalar,
im: self.im * scalar,
}
}
}
#[derive(Debug, Clone)]
pub struct OpticalCoupling {
pub from: usize,
pub to: usize,
pub strength: Complex,
pub delay: f64,
}
#[derive(Debug, Clone)]
pub struct CimConfig {
pub num_oscillators: usize,
pub dt: f64,
pub total_time: f64,
pub pump_schedule: PumpSchedule,
pub topology: NetworkTopology,
pub noise_config: NoiseConfig,
pub measurement_config: MeasurementConfig,
pub convergence_config: ConvergenceConfig,
pub seed: Option<u64>,
pub detailed_logging: bool,
pub output_file: Option<String>,
}
impl Default for CimConfig {
fn default() -> Self {
Self {
num_oscillators: 4,
dt: 0.001,
total_time: 10.0,
pump_schedule: PumpSchedule::Linear {
initial_power: 0.5,
final_power: 2.0,
},
topology: NetworkTopology::FullyConnected,
noise_config: NoiseConfig::default(),
measurement_config: MeasurementConfig::default(),
convergence_config: ConvergenceConfig::default(),
seed: None,
detailed_logging: false,
output_file: None,
}
}
}
pub enum PumpSchedule {
Linear {
initial_power: f64,
final_power: f64,
},
Exponential {
initial_power: f64,
final_power: f64,
time_constant: f64,
},
Sigmoid {
initial_power: f64,
final_power: f64,
steepness: f64,
midpoint: f64,
},
Custom {
power_function: Box<dyn Fn(f64) -> f64 + Send + Sync>,
},
}
impl std::fmt::Debug for PumpSchedule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Linear {
initial_power,
final_power,
} => f
.debug_struct("Linear")
.field("initial_power", initial_power)
.field("final_power", final_power)
.finish(),
Self::Exponential {
initial_power,
final_power,
time_constant,
} => f
.debug_struct("Exponential")
.field("initial_power", initial_power)
.field("final_power", final_power)
.field("time_constant", time_constant)
.finish(),
Self::Sigmoid {
initial_power,
final_power,
steepness,
midpoint,
} => f
.debug_struct("Sigmoid")
.field("initial_power", initial_power)
.field("final_power", final_power)
.field("steepness", steepness)
.field("midpoint", midpoint)
.finish(),
Self::Custom { .. } => f
.debug_struct("Custom")
.field("power_function", &"<function>")
.finish(),
}
}
}
impl Clone for PumpSchedule {
fn clone(&self) -> Self {
match self {
Self::Linear {
initial_power,
final_power,
} => Self::Linear {
initial_power: *initial_power,
final_power: *final_power,
},
Self::Exponential {
initial_power,
final_power,
time_constant,
} => Self::Exponential {
initial_power: *initial_power,
final_power: *final_power,
time_constant: *time_constant,
},
Self::Sigmoid {
initial_power,
final_power,
steepness,
midpoint,
} => Self::Sigmoid {
initial_power: *initial_power,
final_power: *final_power,
steepness: *steepness,
midpoint: *midpoint,
},
Self::Custom { .. } => {
Self::Linear {
initial_power: 0.0,
final_power: 1.0,
}
}
}
}
}
#[derive(Debug, Clone)]
pub enum NetworkTopology {
FullyConnected,
Ring,
Lattice2D { width: usize, height: usize },
Random { connectivity: f64 },
SmallWorld {
ring_connectivity: usize,
rewiring_probability: f64,
},
Custom { couplings: Vec<OpticalCoupling> },
}
#[derive(Debug, Clone)]
pub struct NoiseConfig {
pub quantum_noise: f64,
pub phase_noise: f64,
pub amplitude_noise: f64,
pub temperature: f64,
pub decoherence_rate: f64,
}
impl Default for NoiseConfig {
fn default() -> Self {
Self {
quantum_noise: 0.01,
phase_noise: 0.001,
amplitude_noise: 0.001,
temperature: 0.01,
decoherence_rate: 0.001,
}
}
}
#[derive(Debug, Clone)]
pub struct MeasurementConfig {
pub detection_efficiency: f64,
pub measurement_window: f64,
pub reference_phase: f64,
pub measurement_repetitions: usize,
pub decision_threshold: f64,
}
impl Default for MeasurementConfig {
fn default() -> Self {
Self {
detection_efficiency: 0.95,
measurement_window: 1.0,
reference_phase: 0.0,
measurement_repetitions: 100,
decision_threshold: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct ConvergenceConfig {
pub energy_tolerance: f64,
pub stagnation_time: f64,
pub oscillation_threshold: f64,
pub phase_stability: f64,
}
impl Default for ConvergenceConfig {
fn default() -> Self {
Self {
energy_tolerance: 1e-6,
stagnation_time: 1.0,
oscillation_threshold: 0.1,
phase_stability: 0.01,
}
}
}
#[derive(Debug, Clone)]
pub struct CimResults {
pub best_solution: Vec<i8>,
pub best_energy: f64,
pub final_optical_state: Vec<Complex>,
pub energy_history: Vec<f64>,
pub optical_evolution: Vec<Vec<Complex>>,
pub time_points: Vec<f64>,
pub converged: bool,
pub convergence_time: f64,
pub total_simulation_time: Duration,
pub optical_stats: OpticalStatistics,
pub performance_metrics: CimPerformanceMetrics,
}
#[derive(Debug, Clone)]
pub struct OpticalStatistics {
pub average_power: f64,
pub power_variance: f64,
pub phase_coherence: Vec<f64>,
pub cross_correlations: Vec<Vec<f64>>,
pub oscillation_frequencies: Vec<f64>,
pub pump_efficiency: f64,
}
#[derive(Debug, Clone)]
pub struct CimPerformanceMetrics {
pub solution_quality: f64,
pub time_to_convergence: f64,
pub phase_transitions: usize,
pub power_efficiency: f64,
pub noise_resilience: f64,
}
pub struct CoherentIsingMachine {
config: CimConfig,
oscillators: Vec<OpticalParametricOscillator>,
couplings: Vec<OpticalCoupling>,
rng: ChaCha8Rng,
current_time: f64,
evolution_history: Vec<(f64, Vec<Complex>)>,
energy_history: Vec<f64>,
}
impl CoherentIsingMachine {
pub fn new(config: CimConfig) -> CimResult<Self> {
let rng = match config.seed {
Some(seed) => ChaCha8Rng::seed_from_u64(seed),
None => {
let seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| {
d.subsec_nanos() as u64 ^ (d.as_secs().wrapping_mul(0x9e37_79b9_7f4a_7c15))
})
.unwrap_or(0x1234_5678_9abc_def0);
ChaCha8Rng::seed_from_u64(seed)
}
};
let mut cim = Self {
oscillators: Vec::new(),
couplings: Vec::new(),
rng,
current_time: 0.0,
evolution_history: Vec::new(),
energy_history: Vec::new(),
config,
};
cim.initialize_system()?;
Ok(cim)
}
fn initialize_system(&mut self) -> CimResult<()> {
for i in 0..self.config.num_oscillators {
let osc = OpticalParametricOscillator {
index: i,
amplitude: Complex::new(
self.rng.random_range(-0.1..0.1),
self.rng.random_range(-0.1..0.1),
),
gain: 1.0,
loss: 0.1,
saturation: 1.0,
injection: Complex::new(0.0, 0.0),
threshold: 1.0,
};
self.oscillators.push(osc);
}
self.initialize_topology()?;
Ok(())
}
fn initialize_topology(&mut self) -> CimResult<()> {
self.couplings.clear();
let topology = self.config.topology.clone();
match topology {
NetworkTopology::FullyConnected => {
for i in 0..self.config.num_oscillators {
for j in (i + 1)..self.config.num_oscillators {
self.couplings.push(OpticalCoupling {
from: i,
to: j,
strength: Complex::new(0.1, 0.0),
delay: 0.0,
});
self.couplings.push(OpticalCoupling {
from: j,
to: i,
strength: Complex::new(0.1, 0.0),
delay: 0.0,
});
}
}
}
NetworkTopology::Ring => {
for i in 0..self.config.num_oscillators {
let j = (i + 1) % self.config.num_oscillators;
self.couplings.push(OpticalCoupling {
from: i,
to: j,
strength: Complex::new(0.2, 0.0),
delay: 0.0,
});
self.couplings.push(OpticalCoupling {
from: j,
to: i,
strength: Complex::new(0.2, 0.0),
delay: 0.0,
});
}
}
NetworkTopology::Lattice2D { width, height } => {
if width * height != self.config.num_oscillators {
return Err(CimError::TopologyError(
"Lattice dimensions don't match number of oscillators".to_string(),
));
}
for i in 0..width {
for j in 0..height {
let idx = i * height + j;
if i + 1 < width {
let neighbor = (i + 1) * height + j;
self.add_bidirectional_coupling(idx, neighbor, 0.15);
}
if j + 1 < height {
let neighbor = i * height + (j + 1);
self.add_bidirectional_coupling(idx, neighbor, 0.15);
}
}
}
}
NetworkTopology::Random { connectivity } => {
let num_possible_edges =
self.config.num_oscillators * (self.config.num_oscillators - 1) / 2;
let num_edges = (num_possible_edges as f64 * connectivity) as usize;
let mut added_edges = std::collections::HashSet::new();
while added_edges.len() < num_edges {
let i = self.rng.random_range(0..self.config.num_oscillators);
let j = self.rng.random_range(0..self.config.num_oscillators);
if i != j {
let edge = if i < j { (i, j) } else { (j, i) };
if added_edges.insert(edge) {
let strength = self.rng.random_range(0.05..0.25);
self.add_bidirectional_coupling(edge.0, edge.1, strength);
}
}
}
}
NetworkTopology::SmallWorld {
ring_connectivity,
rewiring_probability,
} => {
for i in 0..self.config.num_oscillators {
for k in 1..=ring_connectivity {
let j = (i + k) % self.config.num_oscillators;
if self.rng.random_bool(rewiring_probability) {
let new_target = self.rng.random_range(0..self.config.num_oscillators);
if new_target != i {
self.add_bidirectional_coupling(i, new_target, 0.1);
}
} else {
self.add_bidirectional_coupling(i, j, 0.1);
}
}
}
}
NetworkTopology::Custom { couplings } => {
self.couplings = couplings;
}
}
Ok(())
}
fn add_bidirectional_coupling(&mut self, i: usize, j: usize, strength: f64) {
self.couplings.push(OpticalCoupling {
from: i,
to: j,
strength: Complex::new(strength, 0.0),
delay: 0.0,
});
self.couplings.push(OpticalCoupling {
from: j,
to: i,
strength: Complex::new(strength, 0.0),
delay: 0.0,
});
}
pub fn solve(&mut self, problem: &IsingModel) -> CimResult<CimResults> {
if problem.num_qubits != self.config.num_oscillators {
return Err(CimError::SimulationError(format!(
"Problem size {} doesn't match CIM size {}",
problem.num_qubits, self.config.num_oscillators
)));
}
println!("Starting coherent Ising machine simulation...");
let start_time = Instant::now();
self.map_ising_to_optical(problem)?;
self.current_time = 0.0;
self.evolution_history.clear();
self.energy_history.clear();
let num_steps = (self.config.total_time / self.config.dt) as usize;
let mut best_energy = f64::INFINITY;
let mut best_solution = vec![0; problem.num_qubits];
let mut converged = false;
let mut convergence_time = self.config.total_time;
for step in 0..num_steps {
self.current_time = step as f64 * self.config.dt;
let pump_power = self.get_pump_power(self.current_time);
self.update_pump_power(pump_power);
self.evolve_system()?;
self.add_noise();
if step % 100 == 0 || step == num_steps - 1 {
self.record_state();
}
let current_solution = self.measure_solution()?;
let current_energy = self.evaluate_energy(problem, ¤t_solution)?;
self.energy_history.push(current_energy);
if current_energy < best_energy {
best_energy = current_energy;
best_solution = current_solution;
}
if !converged && self.check_convergence()? {
converged = true;
convergence_time = self.current_time;
if self.config.detailed_logging {
println!("Converged at time {convergence_time:.3}");
}
}
if step % 1000 == 0 && self.config.detailed_logging {
println!(
"Step {}: Time = {:.3}, Energy = {:.6}, Power = {:.3}",
step, self.current_time, current_energy, pump_power
);
}
}
let total_simulation_time = start_time.elapsed();
let optical_stats = self.calculate_optical_statistics();
let performance_metrics = self.calculate_performance_metrics(best_energy, convergence_time);
let results = CimResults {
best_solution,
best_energy,
final_optical_state: self.oscillators.iter().map(|osc| osc.amplitude).collect(),
energy_history: self.energy_history.clone(),
optical_evolution: self
.evolution_history
.iter()
.map(|(_, state)| state.clone())
.collect(),
time_points: self.evolution_history.iter().map(|(t, _)| *t).collect(),
converged,
convergence_time,
total_simulation_time,
optical_stats,
performance_metrics,
};
println!("CIM simulation completed in {total_simulation_time:.2?}");
println!("Best energy: {best_energy:.6}, Converged: {converged}");
Ok(results)
}
fn map_ising_to_optical(&mut self, problem: &IsingModel) -> CimResult<()> {
for i in 0..problem.num_qubits {
let bias = problem.get_bias(i).unwrap_or(0.0);
self.oscillators[i].injection = Complex::new(bias * 0.1, 0.0);
}
for coupling in &mut self.couplings {
if let Ok(ising_coupling) = problem.get_coupling(coupling.from, coupling.to) {
if ising_coupling != 0.0 {
let optical_strength = ising_coupling * 0.1;
coupling.strength = Complex::new(optical_strength, 0.0);
}
}
}
Ok(())
}
fn get_pump_power(&self, time: f64) -> f64 {
let normalized_time = time / self.config.total_time;
match &self.config.pump_schedule {
PumpSchedule::Linear {
initial_power,
final_power,
} => initial_power + (final_power - initial_power) * normalized_time,
PumpSchedule::Exponential {
initial_power,
final_power,
time_constant,
} => {
initial_power
+ (final_power - initial_power) * (1.0 - (-time / time_constant).exp())
}
PumpSchedule::Sigmoid {
initial_power,
final_power,
steepness,
midpoint,
} => {
let sigmoid = 1.0 / (1.0 + (-(normalized_time - midpoint) * steepness).exp());
initial_power + (final_power - initial_power) * sigmoid
}
PumpSchedule::Custom { power_function } => power_function(time),
}
}
fn update_pump_power(&mut self, pump_power: f64) {
for osc in &mut self.oscillators {
osc.gain = pump_power;
}
}
fn evolve_system(&mut self) -> CimResult<()> {
let dt = self.config.dt;
let mut new_amplitudes = Vec::new();
for i in 0..self.oscillators.len() {
let osc = &self.oscillators[i];
let mut derivative = Complex::new(0.0, 0.0);
let power = osc.amplitude.magnitude_squared();
if osc.gain > osc.threshold {
let net_gain = osc.gain - osc.threshold - osc.loss;
derivative = derivative + osc.amplitude * net_gain * (1.0 - power / osc.saturation);
} else {
derivative = derivative + osc.amplitude * (-osc.loss);
}
derivative = derivative + osc.injection;
for coupling in &self.couplings {
if coupling.to == i {
let source_amplitude = self.oscillators[coupling.from].amplitude;
derivative = derivative + source_amplitude * coupling.strength;
}
}
let new_amplitude = osc.amplitude + derivative * dt;
new_amplitudes.push(new_amplitude);
}
for (i, new_amplitude) in new_amplitudes.into_iter().enumerate() {
self.oscillators[i].amplitude = new_amplitude;
}
Ok(())
}
fn add_noise(&mut self) {
let noise_config = &self.config.noise_config;
for osc in &mut self.oscillators {
let quantum_noise_re = self.rng.random_range(-1.0..1.0) * noise_config.quantum_noise;
let quantum_noise_im = self.rng.random_range(-1.0..1.0) * noise_config.quantum_noise;
let phase_noise = self.rng.random_range(-1.0..1.0) * noise_config.phase_noise;
let current_phase = osc.amplitude.phase();
let new_phase = current_phase + phase_noise;
let amplitude_noise = self
.rng
.random_range(-1.0_f64..1.0)
.mul_add(noise_config.amplitude_noise, 1.0);
let current_magnitude = osc.amplitude.magnitude();
let new_magnitude = current_magnitude * amplitude_noise;
osc.amplitude = Complex::from_polar(new_magnitude, new_phase)
+ Complex::new(quantum_noise_re, quantum_noise_im);
osc.amplitude =
osc.amplitude * noise_config.decoherence_rate.mul_add(-self.config.dt, 1.0);
}
}
fn record_state(&mut self) {
let state: Vec<Complex> = self.oscillators.iter().map(|osc| osc.amplitude).collect();
self.evolution_history.push((self.current_time, state));
}
fn measure_solution(&self) -> CimResult<Vec<i8>> {
let mut solution = Vec::new();
for osc in &self.oscillators {
let measurement =
osc.amplitude.re * self.config.measurement_config.detection_efficiency;
let spin = if measurement > self.config.measurement_config.decision_threshold {
1
} else {
-1
};
solution.push(spin);
}
Ok(solution)
}
fn evaluate_energy(&self, problem: &IsingModel, solution: &[i8]) -> CimResult<f64> {
let mut energy = 0.0;
for i in 0..solution.len() {
energy += problem.get_bias(i).unwrap_or(0.0) * f64::from(solution[i]);
}
for i in 0..solution.len() {
for j in (i + 1)..solution.len() {
energy += problem.get_coupling(i, j).unwrap_or(0.0)
* f64::from(solution[i])
* f64::from(solution[j]);
}
}
Ok(energy)
}
fn check_convergence(&self) -> CimResult<bool> {
if self.energy_history.len() < 100 {
return Ok(false);
}
let recent_window = 50;
let recent_energies = &self.energy_history[self.energy_history.len() - recent_window..];
let energy_variance = {
let mean = recent_energies.iter().sum::<f64>() / recent_energies.len() as f64;
recent_energies
.iter()
.map(|&e| (e - mean).powi(2))
.sum::<f64>()
/ recent_energies.len() as f64
};
let energy_stable = energy_variance < self.config.convergence_config.energy_tolerance;
let oscillation_stable = self.oscillators.iter().all(|osc| {
osc.amplitude.magnitude() > self.config.convergence_config.oscillation_threshold
});
Ok(energy_stable && oscillation_stable)
}
fn calculate_optical_statistics(&self) -> OpticalStatistics {
let num_oscillators = self.oscillators.len();
let total_power: f64 = self
.oscillators
.iter()
.map(|osc| osc.amplitude.magnitude_squared())
.sum();
let average_power = total_power / num_oscillators as f64;
let power_variance = {
let powers: Vec<f64> = self
.oscillators
.iter()
.map(|osc| osc.amplitude.magnitude_squared())
.collect();
let mean = powers.iter().sum::<f64>() / powers.len() as f64;
powers.iter().map(|&p| (p - mean).powi(2)).sum::<f64>() / powers.len() as f64
};
let phase_coherence: Vec<f64> = self
.oscillators
.iter()
.map(|osc| osc.amplitude.magnitude().min(1.0))
.collect();
let mut cross_correlations = vec![vec![0.0; num_oscillators]; num_oscillators];
for i in 0..num_oscillators {
for j in 0..num_oscillators {
if i == j {
cross_correlations[i][j] = 1.0;
} else {
let phase_diff = (self.oscillators[i].amplitude.phase()
- self.oscillators[j].amplitude.phase())
.abs();
cross_correlations[i][j] = (phase_diff / std::f64::consts::PI).cos().abs();
}
}
}
let oscillation_frequencies = vec![1.0; num_oscillators];
OpticalStatistics {
average_power,
power_variance,
phase_coherence,
cross_correlations,
oscillation_frequencies,
pump_efficiency: 0.8, }
}
fn calculate_performance_metrics(
&self,
best_energy: f64,
convergence_time: f64,
) -> CimPerformanceMetrics {
let solution_quality = 1.0;
let time_to_convergence = convergence_time / self.config.total_time;
let average_power = self
.oscillators
.iter()
.map(|osc| osc.amplitude.magnitude_squared())
.sum::<f64>()
/ self.oscillators.len() as f64;
let power_efficiency = 1.0 / (1.0 + average_power);
CimPerformanceMetrics {
solution_quality,
time_to_convergence,
phase_transitions: 0, power_efficiency,
noise_resilience: 0.8, }
}
}
#[must_use]
pub fn create_standard_cim_config(num_oscillators: usize, simulation_time: f64) -> CimConfig {
CimConfig {
num_oscillators,
dt: 0.001,
total_time: simulation_time,
pump_schedule: PumpSchedule::Linear {
initial_power: 0.5,
final_power: 1.5,
},
topology: NetworkTopology::FullyConnected,
..Default::default()
}
}
#[must_use]
pub fn create_low_noise_cim_config(num_oscillators: usize) -> CimConfig {
let mut config = create_standard_cim_config(num_oscillators, 5.0);
config.noise_config = NoiseConfig {
quantum_noise: 0.001,
phase_noise: 0.0001,
amplitude_noise: 0.0001,
temperature: 0.001,
decoherence_rate: 0.0001,
};
config
}
#[must_use]
pub fn create_realistic_cim_config(num_oscillators: usize) -> CimConfig {
let mut config = create_standard_cim_config(num_oscillators, 10.0);
config.noise_config = NoiseConfig {
quantum_noise: 0.05,
phase_noise: 0.01,
amplitude_noise: 0.01,
temperature: 0.1,
decoherence_rate: 0.01,
};
config.detailed_logging = true;
config
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_complex_operations() {
let c1 = Complex::new(3.0, 4.0);
let c2 = Complex::new(1.0, 2.0);
assert_eq!(c1.magnitude(), 5.0);
assert!((c1.phase() - 4.0_f64.atan2(3.0)).abs() < 1e-10);
let sum = c1 + c2;
assert_eq!(sum.re, 4.0);
assert_eq!(sum.im, 6.0);
let product = c1 * c2;
assert_eq!(product.re, -5.0); assert_eq!(product.im, 10.0); }
#[test]
fn test_cim_config_creation() {
let config = create_standard_cim_config(4, 5.0);
assert_eq!(config.num_oscillators, 4);
assert_eq!(config.total_time, 5.0);
match config.topology {
NetworkTopology::FullyConnected => {}
_ => panic!("Expected fully connected topology"),
}
}
#[test]
fn test_optical_oscillator() {
let osc = OpticalParametricOscillator {
index: 0,
amplitude: Complex::new(1.0, 0.0),
gain: 1.5,
loss: 0.1,
saturation: 1.0,
injection: Complex::new(0.1, 0.0),
threshold: 1.0,
};
assert_eq!(osc.amplitude.magnitude(), 1.0);
assert_eq!(osc.gain, 1.5);
assert!(osc.gain > osc.threshold);
}
}