1use scirs2_core::random::prelude::*;
10use scirs2_core::random::ChaCha8Rng;
11use scirs2_core::random::{Rng, SeedableRng};
12use scirs2_core::RngExt;
13use std::collections::HashMap;
14use std::time::{Duration, Instant};
15use thiserror::Error;
16
17use crate::ising::{IsingError, IsingModel};
18
19#[derive(Error, Debug)]
21pub enum CimError {
22 #[error("Ising error: {0}")]
24 IsingError(#[from] IsingError),
25
26 #[error("Invalid optical parameters: {0}")]
28 InvalidOpticalParameters(String),
29
30 #[error("Simulation error: {0}")]
32 SimulationError(String),
33
34 #[error("Network topology error: {0}")]
36 TopologyError(String),
37
38 #[error("Convergence error: {0}")]
40 ConvergenceError(String),
41}
42
43pub type CimResult<T> = Result<T, CimError>;
45
46#[derive(Debug, Clone)]
48pub struct OpticalParametricOscillator {
49 pub index: usize,
51
52 pub amplitude: Complex,
54
55 pub gain: f64,
57
58 pub loss: f64,
60
61 pub saturation: f64,
63
64 pub injection: Complex,
66
67 pub threshold: f64,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
73pub struct Complex {
74 pub re: f64,
76 pub im: f64,
78}
79
80impl Complex {
81 #[must_use]
83 pub const fn new(re: f64, im: f64) -> Self {
84 Self { re, im }
85 }
86
87 #[must_use]
89 pub fn from_polar(magnitude: f64, phase: f64) -> Self {
90 Self {
91 re: magnitude * phase.cos(),
92 im: magnitude * phase.sin(),
93 }
94 }
95
96 #[must_use]
98 pub fn magnitude_squared(&self) -> f64 {
99 self.re.mul_add(self.re, self.im * self.im)
100 }
101
102 #[must_use]
104 pub fn magnitude(&self) -> f64 {
105 self.magnitude_squared().sqrt()
106 }
107
108 #[must_use]
110 pub fn phase(&self) -> f64 {
111 self.im.atan2(self.re)
112 }
113
114 #[must_use]
116 pub fn conjugate(&self) -> Self {
117 Self {
118 re: self.re,
119 im: -self.im,
120 }
121 }
122}
123
124impl std::ops::Add for Complex {
125 type Output = Self;
126
127 fn add(self, other: Self) -> Self {
128 Self {
129 re: self.re + other.re,
130 im: self.im + other.im,
131 }
132 }
133}
134
135impl std::ops::Sub for Complex {
136 type Output = Self;
137
138 fn sub(self, other: Self) -> Self {
139 Self {
140 re: self.re - other.re,
141 im: self.im - other.im,
142 }
143 }
144}
145
146impl std::ops::Mul for Complex {
147 type Output = Self;
148
149 fn mul(self, other: Self) -> Self {
150 Self {
151 re: self.re.mul_add(other.re, -(self.im * other.im)),
152 im: self.re.mul_add(other.im, self.im * other.re),
153 }
154 }
155}
156
157impl std::ops::Mul<f64> for Complex {
158 type Output = Self;
159
160 fn mul(self, scalar: f64) -> Self {
161 Self {
162 re: self.re * scalar,
163 im: self.im * scalar,
164 }
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct OpticalCoupling {
171 pub from: usize,
173
174 pub to: usize,
176
177 pub strength: Complex,
179
180 pub delay: f64,
182}
183
184#[derive(Debug, Clone)]
186pub struct CimConfig {
187 pub num_oscillators: usize,
189
190 pub dt: f64,
192
193 pub total_time: f64,
195
196 pub pump_schedule: PumpSchedule,
198
199 pub topology: NetworkTopology,
201
202 pub noise_config: NoiseConfig,
204
205 pub measurement_config: MeasurementConfig,
207
208 pub convergence_config: ConvergenceConfig,
210
211 pub seed: Option<u64>,
213
214 pub detailed_logging: bool,
216
217 pub output_file: Option<String>,
219}
220
221impl Default for CimConfig {
222 fn default() -> Self {
223 Self {
224 num_oscillators: 4,
225 dt: 0.001,
226 total_time: 10.0,
227 pump_schedule: PumpSchedule::Linear {
228 initial_power: 0.5,
229 final_power: 2.0,
230 },
231 topology: NetworkTopology::FullyConnected,
232 noise_config: NoiseConfig::default(),
233 measurement_config: MeasurementConfig::default(),
234 convergence_config: ConvergenceConfig::default(),
235 seed: None,
236 detailed_logging: false,
237 output_file: None,
238 }
239 }
240}
241
242pub enum PumpSchedule {
244 Linear {
246 initial_power: f64,
247 final_power: f64,
248 },
249
250 Exponential {
252 initial_power: f64,
253 final_power: f64,
254 time_constant: f64,
255 },
256
257 Sigmoid {
259 initial_power: f64,
260 final_power: f64,
261 steepness: f64,
262 midpoint: f64,
263 },
264
265 Custom {
267 power_function: Box<dyn Fn(f64) -> f64 + Send + Sync>,
268 },
269}
270
271impl std::fmt::Debug for PumpSchedule {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 match self {
274 Self::Linear {
275 initial_power,
276 final_power,
277 } => f
278 .debug_struct("Linear")
279 .field("initial_power", initial_power)
280 .field("final_power", final_power)
281 .finish(),
282 Self::Exponential {
283 initial_power,
284 final_power,
285 time_constant,
286 } => f
287 .debug_struct("Exponential")
288 .field("initial_power", initial_power)
289 .field("final_power", final_power)
290 .field("time_constant", time_constant)
291 .finish(),
292 Self::Sigmoid {
293 initial_power,
294 final_power,
295 steepness,
296 midpoint,
297 } => f
298 .debug_struct("Sigmoid")
299 .field("initial_power", initial_power)
300 .field("final_power", final_power)
301 .field("steepness", steepness)
302 .field("midpoint", midpoint)
303 .finish(),
304 Self::Custom { .. } => f
305 .debug_struct("Custom")
306 .field("power_function", &"<function>")
307 .finish(),
308 }
309 }
310}
311
312impl Clone for PumpSchedule {
313 fn clone(&self) -> Self {
314 match self {
315 Self::Linear {
316 initial_power,
317 final_power,
318 } => Self::Linear {
319 initial_power: *initial_power,
320 final_power: *final_power,
321 },
322 Self::Exponential {
323 initial_power,
324 final_power,
325 time_constant,
326 } => Self::Exponential {
327 initial_power: *initial_power,
328 final_power: *final_power,
329 time_constant: *time_constant,
330 },
331 Self::Sigmoid {
332 initial_power,
333 final_power,
334 steepness,
335 midpoint,
336 } => Self::Sigmoid {
337 initial_power: *initial_power,
338 final_power: *final_power,
339 steepness: *steepness,
340 midpoint: *midpoint,
341 },
342 Self::Custom { .. } => {
343 Self::Linear {
345 initial_power: 0.0,
346 final_power: 1.0,
347 }
348 }
349 }
350 }
351}
352
353#[derive(Debug, Clone)]
355pub enum NetworkTopology {
356 FullyConnected,
358
359 Ring,
361
362 Lattice2D { width: usize, height: usize },
364
365 Random { connectivity: f64 },
367
368 SmallWorld {
370 ring_connectivity: usize,
371 rewiring_probability: f64,
372 },
373
374 Custom { couplings: Vec<OpticalCoupling> },
376}
377
378#[derive(Debug, Clone)]
380pub struct NoiseConfig {
381 pub quantum_noise: f64,
383
384 pub phase_noise: f64,
386
387 pub amplitude_noise: f64,
389
390 pub temperature: f64,
392
393 pub decoherence_rate: f64,
395}
396
397impl Default for NoiseConfig {
398 fn default() -> Self {
399 Self {
400 quantum_noise: 0.01,
401 phase_noise: 0.001,
402 amplitude_noise: 0.001,
403 temperature: 0.01,
404 decoherence_rate: 0.001,
405 }
406 }
407}
408
409#[derive(Debug, Clone)]
411pub struct MeasurementConfig {
412 pub detection_efficiency: f64,
414
415 pub measurement_window: f64,
417
418 pub reference_phase: f64,
420
421 pub measurement_repetitions: usize,
423
424 pub decision_threshold: f64,
426}
427
428impl Default for MeasurementConfig {
429 fn default() -> Self {
430 Self {
431 detection_efficiency: 0.95,
432 measurement_window: 1.0,
433 reference_phase: 0.0,
434 measurement_repetitions: 100,
435 decision_threshold: 0.0,
436 }
437 }
438}
439
440#[derive(Debug, Clone)]
442pub struct ConvergenceConfig {
443 pub energy_tolerance: f64,
445
446 pub stagnation_time: f64,
448
449 pub oscillation_threshold: f64,
451
452 pub phase_stability: f64,
454}
455
456impl Default for ConvergenceConfig {
457 fn default() -> Self {
458 Self {
459 energy_tolerance: 1e-6,
460 stagnation_time: 1.0,
461 oscillation_threshold: 0.1,
462 phase_stability: 0.01,
463 }
464 }
465}
466
467#[derive(Debug, Clone)]
469pub struct CimResults {
470 pub best_solution: Vec<i8>,
472
473 pub best_energy: f64,
475
476 pub final_optical_state: Vec<Complex>,
478
479 pub energy_history: Vec<f64>,
481
482 pub optical_evolution: Vec<Vec<Complex>>,
484
485 pub time_points: Vec<f64>,
487
488 pub converged: bool,
490
491 pub convergence_time: f64,
493
494 pub total_simulation_time: Duration,
496
497 pub optical_stats: OpticalStatistics,
499
500 pub performance_metrics: CimPerformanceMetrics,
502}
503
504#[derive(Debug, Clone)]
506pub struct OpticalStatistics {
507 pub average_power: f64,
509
510 pub power_variance: f64,
512
513 pub phase_coherence: Vec<f64>,
515
516 pub cross_correlations: Vec<Vec<f64>>,
518
519 pub oscillation_frequencies: Vec<f64>,
521
522 pub pump_efficiency: f64,
524}
525
526#[derive(Debug, Clone)]
528pub struct CimPerformanceMetrics {
529 pub solution_quality: f64,
531
532 pub time_to_convergence: f64,
534
535 pub phase_transitions: usize,
537
538 pub power_efficiency: f64,
540
541 pub noise_resilience: f64,
543}
544
545pub struct CoherentIsingMachine {
547 config: CimConfig,
549
550 oscillators: Vec<OpticalParametricOscillator>,
552
553 couplings: Vec<OpticalCoupling>,
555
556 rng: ChaCha8Rng,
558
559 current_time: f64,
561
562 evolution_history: Vec<(f64, Vec<Complex>)>,
564
565 energy_history: Vec<f64>,
567}
568
569impl CoherentIsingMachine {
570 pub fn new(config: CimConfig) -> CimResult<Self> {
572 let rng = match config.seed {
573 Some(seed) => ChaCha8Rng::seed_from_u64(seed),
574 None => {
575 let seed = std::time::SystemTime::now()
578 .duration_since(std::time::UNIX_EPOCH)
579 .map(|d| {
580 d.subsec_nanos() as u64 ^ (d.as_secs().wrapping_mul(0x9e37_79b9_7f4a_7c15))
581 })
582 .unwrap_or(0x1234_5678_9abc_def0);
583 ChaCha8Rng::seed_from_u64(seed)
584 }
585 };
586
587 let mut cim = Self {
588 oscillators: Vec::new(),
589 couplings: Vec::new(),
590 rng,
591 current_time: 0.0,
592 evolution_history: Vec::new(),
593 energy_history: Vec::new(),
594 config,
595 };
596
597 cim.initialize_system()?;
598 Ok(cim)
599 }
600
601 fn initialize_system(&mut self) -> CimResult<()> {
603 for i in 0..self.config.num_oscillators {
605 let osc = OpticalParametricOscillator {
606 index: i,
607 amplitude: Complex::new(
608 self.rng.random_range(-0.1..0.1),
609 self.rng.random_range(-0.1..0.1),
610 ),
611 gain: 1.0,
612 loss: 0.1,
613 saturation: 1.0,
614 injection: Complex::new(0.0, 0.0),
615 threshold: 1.0,
616 };
617 self.oscillators.push(osc);
618 }
619
620 self.initialize_topology()?;
622
623 Ok(())
624 }
625
626 fn initialize_topology(&mut self) -> CimResult<()> {
628 self.couplings.clear();
629
630 let topology = self.config.topology.clone();
631 match topology {
632 NetworkTopology::FullyConnected => {
633 for i in 0..self.config.num_oscillators {
634 for j in (i + 1)..self.config.num_oscillators {
635 self.couplings.push(OpticalCoupling {
636 from: i,
637 to: j,
638 strength: Complex::new(0.1, 0.0),
639 delay: 0.0,
640 });
641 self.couplings.push(OpticalCoupling {
642 from: j,
643 to: i,
644 strength: Complex::new(0.1, 0.0),
645 delay: 0.0,
646 });
647 }
648 }
649 }
650
651 NetworkTopology::Ring => {
652 for i in 0..self.config.num_oscillators {
653 let j = (i + 1) % self.config.num_oscillators;
654 self.couplings.push(OpticalCoupling {
655 from: i,
656 to: j,
657 strength: Complex::new(0.2, 0.0),
658 delay: 0.0,
659 });
660 self.couplings.push(OpticalCoupling {
661 from: j,
662 to: i,
663 strength: Complex::new(0.2, 0.0),
664 delay: 0.0,
665 });
666 }
667 }
668
669 NetworkTopology::Lattice2D { width, height } => {
670 if width * height != self.config.num_oscillators {
671 return Err(CimError::TopologyError(
672 "Lattice dimensions don't match number of oscillators".to_string(),
673 ));
674 }
675
676 for i in 0..width {
678 for j in 0..height {
679 let idx = i * height + j;
680
681 if i + 1 < width {
683 let neighbor = (i + 1) * height + j;
684 self.add_bidirectional_coupling(idx, neighbor, 0.15);
685 }
686
687 if j + 1 < height {
689 let neighbor = i * height + (j + 1);
690 self.add_bidirectional_coupling(idx, neighbor, 0.15);
691 }
692 }
693 }
694 }
695
696 NetworkTopology::Random { connectivity } => {
697 let num_possible_edges =
698 self.config.num_oscillators * (self.config.num_oscillators - 1) / 2;
699 let num_edges = (num_possible_edges as f64 * connectivity) as usize;
700
701 let mut added_edges = std::collections::HashSet::new();
702
703 while added_edges.len() < num_edges {
704 let i = self.rng.random_range(0..self.config.num_oscillators);
705 let j = self.rng.random_range(0..self.config.num_oscillators);
706
707 if i != j {
708 let edge = if i < j { (i, j) } else { (j, i) };
709 if added_edges.insert(edge) {
710 let strength = self.rng.random_range(0.05..0.25);
711 self.add_bidirectional_coupling(edge.0, edge.1, strength);
712 }
713 }
714 }
715 }
716
717 NetworkTopology::SmallWorld {
718 ring_connectivity,
719 rewiring_probability,
720 } => {
721 for i in 0..self.config.num_oscillators {
723 for k in 1..=ring_connectivity {
724 let j = (i + k) % self.config.num_oscillators;
725
726 if self.rng.random_bool(rewiring_probability) {
728 let new_target = self.rng.random_range(0..self.config.num_oscillators);
729 if new_target != i {
730 self.add_bidirectional_coupling(i, new_target, 0.1);
731 }
732 } else {
733 self.add_bidirectional_coupling(i, j, 0.1);
734 }
735 }
736 }
737 }
738
739 NetworkTopology::Custom { couplings } => {
740 self.couplings = couplings;
741 }
742 }
743
744 Ok(())
745 }
746
747 fn add_bidirectional_coupling(&mut self, i: usize, j: usize, strength: f64) {
749 self.couplings.push(OpticalCoupling {
750 from: i,
751 to: j,
752 strength: Complex::new(strength, 0.0),
753 delay: 0.0,
754 });
755 self.couplings.push(OpticalCoupling {
756 from: j,
757 to: i,
758 strength: Complex::new(strength, 0.0),
759 delay: 0.0,
760 });
761 }
762
763 pub fn solve(&mut self, problem: &IsingModel) -> CimResult<CimResults> {
765 if problem.num_qubits != self.config.num_oscillators {
766 return Err(CimError::SimulationError(format!(
767 "Problem size {} doesn't match CIM size {}",
768 problem.num_qubits, self.config.num_oscillators
769 )));
770 }
771
772 println!("Starting coherent Ising machine simulation...");
773 let start_time = Instant::now();
774
775 self.map_ising_to_optical(problem)?;
777
778 self.current_time = 0.0;
780 self.evolution_history.clear();
781 self.energy_history.clear();
782
783 let num_steps = (self.config.total_time / self.config.dt) as usize;
785 let mut best_energy = f64::INFINITY;
786 let mut best_solution = vec![0; problem.num_qubits];
787 let mut converged = false;
788 let mut convergence_time = self.config.total_time;
789
790 for step in 0..num_steps {
791 self.current_time = step as f64 * self.config.dt;
792
793 let pump_power = self.get_pump_power(self.current_time);
795 self.update_pump_power(pump_power);
796
797 self.evolve_system()?;
799
800 self.add_noise();
802
803 if step % 100 == 0 || step == num_steps - 1 {
805 self.record_state();
806 }
807
808 let current_solution = self.measure_solution()?;
810 let current_energy = self.evaluate_energy(problem, ¤t_solution)?;
811 self.energy_history.push(current_energy);
812
813 if current_energy < best_energy {
815 best_energy = current_energy;
816 best_solution = current_solution;
817 }
818
819 if !converged && self.check_convergence()? {
821 converged = true;
822 convergence_time = self.current_time;
823 if self.config.detailed_logging {
824 println!("Converged at time {convergence_time:.3}");
825 }
826 }
827
828 if step % 1000 == 0 && self.config.detailed_logging {
830 println!(
831 "Step {}: Time = {:.3}, Energy = {:.6}, Power = {:.3}",
832 step, self.current_time, current_energy, pump_power
833 );
834 }
835 }
836
837 let total_simulation_time = start_time.elapsed();
838
839 let optical_stats = self.calculate_optical_statistics();
841 let performance_metrics =
842 self.calculate_performance_metrics(problem, best_energy, convergence_time);
843
844 let results = CimResults {
846 best_solution,
847 best_energy,
848 final_optical_state: self.oscillators.iter().map(|osc| osc.amplitude).collect(),
849 energy_history: self.energy_history.clone(),
850 optical_evolution: self
851 .evolution_history
852 .iter()
853 .map(|(_, state)| state.clone())
854 .collect(),
855 time_points: self.evolution_history.iter().map(|(t, _)| *t).collect(),
856 converged,
857 convergence_time,
858 total_simulation_time,
859 optical_stats,
860 performance_metrics,
861 };
862
863 println!("CIM simulation completed in {total_simulation_time:.2?}");
864 println!("Best energy: {best_energy:.6}, Converged: {converged}");
865
866 Ok(results)
867 }
868
869 fn map_ising_to_optical(&mut self, problem: &IsingModel) -> CimResult<()> {
871 for i in 0..problem.num_qubits {
873 let bias = problem.get_bias(i).unwrap_or(0.0);
874 self.oscillators[i].injection = Complex::new(bias * 0.1, 0.0);
875 }
876
877 for coupling in &mut self.couplings {
879 if let Ok(ising_coupling) = problem.get_coupling(coupling.from, coupling.to) {
880 if ising_coupling != 0.0 {
881 let optical_strength = ising_coupling * 0.1;
883 coupling.strength = Complex::new(optical_strength, 0.0);
884 }
885 }
886 }
887
888 Ok(())
889 }
890
891 fn get_pump_power(&self, time: f64) -> f64 {
893 let normalized_time = time / self.config.total_time;
894
895 match &self.config.pump_schedule {
896 PumpSchedule::Linear {
897 initial_power,
898 final_power,
899 } => initial_power + (final_power - initial_power) * normalized_time,
900
901 PumpSchedule::Exponential {
902 initial_power,
903 final_power,
904 time_constant,
905 } => {
906 initial_power
907 + (final_power - initial_power) * (1.0 - (-time / time_constant).exp())
908 }
909
910 PumpSchedule::Sigmoid {
911 initial_power,
912 final_power,
913 steepness,
914 midpoint,
915 } => {
916 let sigmoid = 1.0 / (1.0 + (-(normalized_time - midpoint) * steepness).exp());
917 initial_power + (final_power - initial_power) * sigmoid
918 }
919
920 PumpSchedule::Custom { power_function } => power_function(time),
921 }
922 }
923
924 fn update_pump_power(&mut self, pump_power: f64) {
926 for osc in &mut self.oscillators {
927 osc.gain = pump_power;
928 }
929 }
930
931 fn evolve_system(&mut self) -> CimResult<()> {
933 let dt = self.config.dt;
934 let mut new_amplitudes = Vec::new();
935
936 for i in 0..self.oscillators.len() {
938 let osc = &self.oscillators[i];
939 let mut derivative = Complex::new(0.0, 0.0);
940
941 let power = osc.amplitude.magnitude_squared();
943 if osc.gain > osc.threshold {
944 let net_gain = osc.gain - osc.threshold - osc.loss;
945 derivative = derivative + osc.amplitude * net_gain * (1.0 - power / osc.saturation);
946 } else {
947 derivative = derivative + osc.amplitude * (-osc.loss);
949 }
950
951 derivative = derivative + osc.injection;
953
954 for coupling in &self.couplings {
956 if coupling.to == i {
957 let source_amplitude = self.oscillators[coupling.from].amplitude;
958 derivative = derivative + source_amplitude * coupling.strength;
959 }
960 }
961
962 let new_amplitude = osc.amplitude + derivative * dt;
964 new_amplitudes.push(new_amplitude);
965 }
966
967 for (i, new_amplitude) in new_amplitudes.into_iter().enumerate() {
969 self.oscillators[i].amplitude = new_amplitude;
970 }
971
972 Ok(())
973 }
974
975 fn add_noise(&mut self) {
977 let noise_config = &self.config.noise_config;
978
979 for osc in &mut self.oscillators {
980 let quantum_noise_re = self.rng.random_range(-1.0..1.0) * noise_config.quantum_noise;
982 let quantum_noise_im = self.rng.random_range(-1.0..1.0) * noise_config.quantum_noise;
983
984 let phase_noise = self.rng.random_range(-1.0..1.0) * noise_config.phase_noise;
986 let current_phase = osc.amplitude.phase();
987 let new_phase = current_phase + phase_noise;
988
989 let amplitude_noise = self
991 .rng
992 .random_range(-1.0_f64..1.0)
993 .mul_add(noise_config.amplitude_noise, 1.0);
994 let current_magnitude = osc.amplitude.magnitude();
995 let new_magnitude = current_magnitude * amplitude_noise;
996
997 osc.amplitude = Complex::from_polar(new_magnitude, new_phase)
999 + Complex::new(quantum_noise_re, quantum_noise_im);
1000
1001 osc.amplitude =
1003 osc.amplitude * noise_config.decoherence_rate.mul_add(-self.config.dt, 1.0);
1004 }
1005 }
1006
1007 fn record_state(&mut self) {
1009 let state: Vec<Complex> = self.oscillators.iter().map(|osc| osc.amplitude).collect();
1010 self.evolution_history.push((self.current_time, state));
1011 }
1012
1013 fn measure_solution(&self) -> CimResult<Vec<i8>> {
1015 let mut solution = Vec::new();
1016
1017 for osc in &self.oscillators {
1018 let measurement =
1020 osc.amplitude.re * self.config.measurement_config.detection_efficiency;
1021
1022 let spin = if measurement > self.config.measurement_config.decision_threshold {
1024 1
1025 } else {
1026 -1
1027 };
1028
1029 solution.push(spin);
1030 }
1031
1032 Ok(solution)
1033 }
1034
1035 fn evaluate_energy(&self, problem: &IsingModel, solution: &[i8]) -> CimResult<f64> {
1037 let mut energy = 0.0;
1038
1039 for i in 0..solution.len() {
1041 energy += problem.get_bias(i).unwrap_or(0.0) * f64::from(solution[i]);
1042 }
1043
1044 for i in 0..solution.len() {
1046 for j in (i + 1)..solution.len() {
1047 energy += problem.get_coupling(i, j).unwrap_or(0.0)
1048 * f64::from(solution[i])
1049 * f64::from(solution[j]);
1050 }
1051 }
1052
1053 Ok(energy)
1054 }
1055
1056 fn check_convergence(&self) -> CimResult<bool> {
1058 if self.energy_history.len() < 100 {
1059 return Ok(false);
1060 }
1061
1062 let recent_window = 50;
1064 let recent_energies = &self.energy_history[self.energy_history.len() - recent_window..];
1065 let energy_variance = {
1066 let mean = recent_energies.iter().sum::<f64>() / recent_energies.len() as f64;
1067 recent_energies
1068 .iter()
1069 .map(|&e| (e - mean).powi(2))
1070 .sum::<f64>()
1071 / recent_energies.len() as f64
1072 };
1073
1074 let energy_stable = energy_variance < self.config.convergence_config.energy_tolerance;
1075
1076 let oscillation_stable = self.oscillators.iter().all(|osc| {
1078 osc.amplitude.magnitude() > self.config.convergence_config.oscillation_threshold
1079 });
1080
1081 Ok(energy_stable && oscillation_stable)
1082 }
1083
1084 fn calculate_optical_statistics(&self) -> OpticalStatistics {
1086 let num_oscillators = self.oscillators.len();
1087
1088 let total_power: f64 = self
1090 .oscillators
1091 .iter()
1092 .map(|osc| osc.amplitude.magnitude_squared())
1093 .sum();
1094 let average_power = total_power / num_oscillators as f64;
1095
1096 let power_variance = {
1098 let powers: Vec<f64> = self
1099 .oscillators
1100 .iter()
1101 .map(|osc| osc.amplitude.magnitude_squared())
1102 .collect();
1103 let mean = powers.iter().sum::<f64>() / powers.len() as f64;
1104 powers.iter().map(|&p| (p - mean).powi(2)).sum::<f64>() / powers.len() as f64
1105 };
1106
1107 let phase_coherence: Vec<f64> = self
1109 .oscillators
1110 .iter()
1111 .map(|osc| osc.amplitude.magnitude().min(1.0))
1112 .collect();
1113
1114 let mut cross_correlations = vec![vec![0.0; num_oscillators]; num_oscillators];
1116 for i in 0..num_oscillators {
1117 for j in 0..num_oscillators {
1118 if i == j {
1119 cross_correlations[i][j] = 1.0;
1120 } else {
1121 let phase_diff = (self.oscillators[i].amplitude.phase()
1123 - self.oscillators[j].amplitude.phase())
1124 .abs();
1125 cross_correlations[i][j] = (phase_diff / std::f64::consts::PI).cos().abs();
1126 }
1127 }
1128 }
1129
1130 let oscillation_frequencies = vec![1.0; num_oscillators]; OpticalStatistics {
1134 average_power,
1135 power_variance,
1136 phase_coherence,
1137 cross_correlations,
1138 oscillation_frequencies,
1139 pump_efficiency: 0.8, }
1141 }
1142
1143 fn calculate_performance_metrics(
1157 &self,
1158 problem: &IsingModel,
1159 best_energy: f64,
1160 convergence_time: f64,
1161 ) -> CimPerformanceMetrics {
1162 let naive_lower_bound: f64 = -problem
1163 .biases()
1164 .iter()
1165 .map(|(_, bias)| bias.abs())
1166 .sum::<f64>()
1167 - problem
1168 .couplings()
1169 .iter()
1170 .map(|c| c.strength.abs())
1171 .sum::<f64>();
1172
1173 let worst_energy = self
1174 .energy_history
1175 .iter()
1176 .copied()
1177 .fold(f64::NEG_INFINITY, f64::max);
1178
1179 let solution_quality =
1180 if !worst_energy.is_finite() || (worst_energy - naive_lower_bound).abs() < 1e-12 {
1181 0.0
1185 } else {
1186 ((worst_energy - best_energy) / (worst_energy - naive_lower_bound)).clamp(0.0, 1.0)
1187 };
1188
1189 let time_to_convergence = convergence_time / self.config.total_time;
1191
1192 let average_power = self
1194 .oscillators
1195 .iter()
1196 .map(|osc| osc.amplitude.magnitude_squared())
1197 .sum::<f64>()
1198 / self.oscillators.len() as f64;
1199 let power_efficiency = 1.0 / (1.0 + average_power);
1200
1201 let noise_level = self.config.noise_config.quantum_noise
1206 + self.config.noise_config.phase_noise
1207 + self.config.noise_config.amplitude_noise;
1208 let noise_resilience = 1.0 / (1.0 + noise_level);
1209
1210 CimPerformanceMetrics {
1211 solution_quality,
1212 time_to_convergence,
1213 phase_transitions: 0, power_efficiency,
1215 noise_resilience,
1216 }
1217 }
1218}
1219
1220#[must_use]
1224pub fn create_standard_cim_config(num_oscillators: usize, simulation_time: f64) -> CimConfig {
1225 CimConfig {
1226 num_oscillators,
1227 dt: 0.001,
1228 total_time: simulation_time,
1229 pump_schedule: PumpSchedule::Linear {
1230 initial_power: 0.5,
1231 final_power: 1.5,
1232 },
1233 topology: NetworkTopology::FullyConnected,
1234 ..Default::default()
1235 }
1236}
1237
1238#[must_use]
1240pub fn create_low_noise_cim_config(num_oscillators: usize) -> CimConfig {
1241 let mut config = create_standard_cim_config(num_oscillators, 5.0);
1242 config.noise_config = NoiseConfig {
1243 quantum_noise: 0.001,
1244 phase_noise: 0.0001,
1245 amplitude_noise: 0.0001,
1246 temperature: 0.001,
1247 decoherence_rate: 0.0001,
1248 };
1249 config
1250}
1251
1252#[must_use]
1254pub fn create_realistic_cim_config(num_oscillators: usize) -> CimConfig {
1255 let mut config = create_standard_cim_config(num_oscillators, 10.0);
1256 config.noise_config = NoiseConfig {
1257 quantum_noise: 0.05,
1258 phase_noise: 0.01,
1259 amplitude_noise: 0.01,
1260 temperature: 0.1,
1261 decoherence_rate: 0.01,
1262 };
1263 config.detailed_logging = true;
1264 config
1265}
1266
1267#[cfg(test)]
1268mod tests {
1269 use super::*;
1270
1271 #[test]
1272 fn test_complex_operations() {
1273 let c1 = Complex::new(3.0, 4.0);
1274 let c2 = Complex::new(1.0, 2.0);
1275
1276 assert_eq!(c1.magnitude(), 5.0);
1277 assert!((c1.phase() - 4.0_f64.atan2(3.0)).abs() < 1e-10);
1278
1279 let sum = c1 + c2;
1280 assert_eq!(sum.re, 4.0);
1281 assert_eq!(sum.im, 6.0);
1282
1283 let product = c1 * c2;
1284 assert_eq!(product.re, -5.0); assert_eq!(product.im, 10.0); }
1287
1288 #[test]
1289 fn test_cim_config_creation() {
1290 let config = create_standard_cim_config(4, 5.0);
1291 assert_eq!(config.num_oscillators, 4);
1292 assert_eq!(config.total_time, 5.0);
1293
1294 match config.topology {
1295 NetworkTopology::FullyConnected => {}
1296 _ => panic!("Expected fully connected topology"),
1297 }
1298 }
1299
1300 #[test]
1301 fn test_optical_oscillator() {
1302 let osc = OpticalParametricOscillator {
1303 index: 0,
1304 amplitude: Complex::new(1.0, 0.0),
1305 gain: 1.5,
1306 loss: 0.1,
1307 saturation: 1.0,
1308 injection: Complex::new(0.1, 0.0),
1309 threshold: 1.0,
1310 };
1311
1312 assert_eq!(osc.amplitude.magnitude(), 1.0);
1313 assert_eq!(osc.gain, 1.5);
1314 assert!(osc.gain > osc.threshold);
1315 }
1316
1317 #[test]
1318 fn solution_quality_is_real_and_data_dependent_not_a_fixed_one() {
1319 let config = create_standard_cim_config(2, 1.0);
1320 let mut cim = CoherentIsingMachine::new(config).expect("CIM creation should succeed");
1321
1322 let mut problem = IsingModel::new(2);
1323 problem.set_bias(0, 1.0).expect("set_bias should succeed");
1324 problem.set_bias(1, -1.0).expect("set_bias should succeed");
1325 problem
1326 .set_coupling(0, 1, 0.5)
1327 .expect("set_coupling should succeed");
1328 cim.energy_history = vec![-2.5, -2.0, -1.0, 0.5]; let metrics = cim.calculate_performance_metrics(&problem, -2.0, 0.5);
1332
1333 assert!(
1335 (metrics.solution_quality - (2.5 / 3.0)).abs() < 1e-9,
1336 "expected a real data-derived quality, got {}",
1337 metrics.solution_quality
1338 );
1339
1340 cim.energy_history = vec![0.5, 0.5, 0.5];
1343 let no_progress_metrics = cim.calculate_performance_metrics(&problem, 0.5, 0.5);
1344 assert!(no_progress_metrics.solution_quality < 0.1);
1345 }
1346
1347 #[test]
1348 fn noise_resilience_reflects_the_real_configured_noise_level() {
1349 let low_noise_config = create_low_noise_cim_config(2);
1350 let low_noise_cim =
1351 CoherentIsingMachine::new(low_noise_config).expect("CIM creation should succeed");
1352
1353 let mut high_noise_config = create_standard_cim_config(2, 1.0);
1354 high_noise_config.noise_config = NoiseConfig {
1355 quantum_noise: 0.5,
1356 phase_noise: 0.5,
1357 amplitude_noise: 0.5,
1358 temperature: 1.0,
1359 decoherence_rate: 0.1,
1360 };
1361 let high_noise_cim =
1362 CoherentIsingMachine::new(high_noise_config).expect("CIM creation should succeed");
1363
1364 let problem = IsingModel::new(2);
1365 let low = low_noise_cim.calculate_performance_metrics(&problem, 0.0, 0.5);
1366 let high = high_noise_cim.calculate_performance_metrics(&problem, 0.0, 0.5);
1367
1368 assert!(
1369 low.noise_resilience > high.noise_resilience,
1370 "a lower-noise configuration must score higher noise resilience than a higher-noise one \
1371 (low={}, high={})",
1372 low.noise_resilience,
1373 high.noise_resilience
1374 );
1375 }
1376}