1#![allow(dead_code)]
7
8use crate::sampler::{SampleResult, Sampler, SamplerError, SamplerResult};
9use scirs2_core::ndarray::{Array, Array1, Array2, IxDyn};
10use scirs2_core::random::prelude::*;
11use scirs2_core::random::{Distribution, RandNormal, Rng, SeedableRng};
12use scirs2_core::Complex64;
13use std::collections::HashMap;
14
15type Normal<T> = RandNormal<T>;
16use std::f64::consts::PI;
17
18#[derive(Clone)]
20pub struct CIMSimulator {
21 pub n_spins: usize,
23 pump_parameter: f64,
25 detuning: f64,
27 dt: f64,
29 evolution_time: f64,
31 noise_strength: f64,
33 coupling_scale: f64,
35 seed: Option<u64>,
37 use_feedback: bool,
39 feedback_delay: f64,
41}
42
43impl CIMSimulator {
44 pub const fn new(n_spins: usize) -> Self {
46 Self {
47 n_spins,
48 pump_parameter: 1.0,
49 detuning: 0.0,
50 dt: 0.01,
51 evolution_time: 10.0,
52 noise_strength: 0.1,
53 coupling_scale: 1.0,
54 seed: None,
55 use_feedback: true,
56 feedback_delay: 0.1,
57 }
58 }
59
60 pub const fn with_pump_parameter(mut self, pump: f64) -> Self {
62 self.pump_parameter = pump;
63 self
64 }
65
66 pub const fn with_detuning(mut self, detuning: f64) -> Self {
68 self.detuning = detuning;
69 self
70 }
71
72 pub const fn with_time_step(mut self, dt: f64) -> Self {
74 self.dt = dt;
75 self
76 }
77
78 pub const fn with_evolution_time(mut self, time: f64) -> Self {
80 self.evolution_time = time;
81 self
82 }
83
84 pub const fn with_noise_strength(mut self, noise: f64) -> Self {
86 self.noise_strength = noise;
87 self
88 }
89
90 pub const fn with_coupling_scale(mut self, scale: f64) -> Self {
92 self.coupling_scale = scale;
93 self
94 }
95
96 pub const fn with_seed(mut self, seed: u64) -> Self {
98 self.seed = Some(seed);
99 self
100 }
101
102 pub const fn with_feedback(mut self, use_feedback: bool) -> Self {
104 self.use_feedback = use_feedback;
105 self
106 }
107
108 fn simulate_cim(
110 &self,
111 coupling_matrix: &Array2<f64>,
112 local_fields: &Array1<f64>,
113 rng: &mut StdRng,
114 ) -> Result<Vec<f64>, String> {
115 let n = self.n_spins;
116 let steps = (self.evolution_time / self.dt) as usize;
117
118 let mut amplitudes: Vec<Complex64> = (0..n)
120 .map(|_| {
121 let r = rng.gen_range(0.0..0.1);
122 let theta = rng.gen_range(0.0..2.0 * PI);
123 Complex64::new(r * theta.cos(), r * theta.sin())
124 })
125 .collect();
126
127 let _noise_dist = Normal::new(0.0, self.noise_strength)
129 .map_err(|e| format!("Failed to create noise distribution: {e}"))?;
130
131 for step in 0..steps {
133 let mut new_amplitudes = amplitudes.clone();
134
135 for i in 0..n {
136 let mut coupling_term = Complex64::new(0.0, 0.0);
138 for j in 0..n {
139 if i != j {
140 let coupling = coupling_matrix[[i, j]] * self.coupling_scale;
141
142 if self.use_feedback {
143 let delayed_step =
145 (step as f64 - self.feedback_delay / self.dt).max(0.0) as usize;
146 let delayed_amp = if delayed_step < step {
147 amplitudes[j]
148 } else {
149 amplitudes[j]
150 };
151 coupling_term += coupling * delayed_amp.re;
152 } else {
153 coupling_term += coupling * amplitudes[j];
155 }
156 }
157 }
158
159 coupling_term += local_fields[i];
161
162 let nonlinear_term = amplitudes[i] * amplitudes[i].norm_sqr();
164 let pump_term = self.pump_parameter;
165 let detuning_term = Complex64::new(0.0, -self.detuning) * amplitudes[i];
166
167 let deterministic = (pump_term - 1.0) * amplitudes[i] - nonlinear_term
169 + detuning_term
170 + coupling_term;
171
172 let noise = Complex64::new(0.0, 0.0); new_amplitudes[i] =
177 amplitudes[i] + self.dt * deterministic + (self.dt.sqrt()) * noise;
178 }
179
180 amplitudes = new_amplitudes;
181
182 if step % 100 == 0 {
184 self.apply_constraints(&mut amplitudes);
185 }
186 }
187
188 let spins: Vec<f64> = amplitudes.iter().map(|amp| amp.re.signum()).collect();
190
191 Ok(spins)
192 }
193
194 fn apply_constraints(&self, amplitudes: &mut Vec<Complex64>) {
196 let max_amplitude = 2.0;
198 for amp in amplitudes.iter_mut() {
199 if amp.norm() > max_amplitude {
200 *amp = *amp / amp.norm() * max_amplitude;
201 }
202 }
203 }
204
205 fn qubo_to_ising(&self, qubo_matrix: &Array2<f64>) -> (Array2<f64>, Array1<f64>, f64) {
207 let n = qubo_matrix.shape()[0];
208 let mut j_matrix = Array2::zeros((n, n));
209 let mut h_vector = Array1::zeros(n);
210 let mut offset = 0.0;
211
212 for i in 0..n {
214 for j in 0..n {
215 if i == j {
216 h_vector[i] += qubo_matrix[[i, i]];
217 offset += qubo_matrix[[i, i]] / 2.0;
218 } else if i < j {
219 j_matrix[[i, j]] = qubo_matrix[[i, j]] / 4.0;
220 j_matrix[[j, i]] = qubo_matrix[[i, j]] / 4.0;
221 h_vector[i] += qubo_matrix[[i, j]] / 2.0;
222 h_vector[j] += qubo_matrix[[i, j]] / 2.0;
223 offset += qubo_matrix[[i, j]] / 4.0;
224 }
225 }
226 }
227
228 (j_matrix, h_vector, offset)
229 }
230
231 fn spins_to_binary(&self, spins: &[f64]) -> Vec<bool> {
233 spins.iter().map(|&s| s > 0.0).collect()
234 }
235
236 fn calculate_ising_energy(
238 &self,
239 spins: &[f64],
240 j_matrix: &Array2<f64>,
241 h_vector: &Array1<f64>,
242 ) -> f64 {
243 let n = spins.len();
244 let mut energy = 0.0;
245
246 for i in 0..n {
248 for j in i + 1..n {
249 energy += j_matrix[[i, j]] * spins[i] * spins[j];
250 }
251 }
252
253 for i in 0..n {
255 energy += h_vector[i] * spins[i];
256 }
257
258 energy
259 }
260}
261
262impl Sampler for CIMSimulator {
263 fn run_qubo(
264 &self,
265 qubo: &(Array2<f64>, HashMap<String, usize>),
266 shots: usize,
267 ) -> SamplerResult<Vec<SampleResult>> {
268 let (qubo_matrix, var_map) = qubo;
269 let n = qubo_matrix.shape()[0];
270
271 if n != self.n_spins {
272 return Err(SamplerError::InvalidParameter(format!(
273 "CIM configured for {} spins but QUBO has {} variables",
274 self.n_spins, n
275 )));
276 }
277
278 let (j_matrix, h_vector, offset) = self.qubo_to_ising(qubo_matrix);
280
281 let mut rng = match self.seed {
283 Some(seed) => StdRng::seed_from_u64(seed),
284 None => StdRng::seed_from_u64(42), };
286
287 let mut results = Vec::new();
288 let mut solution_counts: HashMap<Vec<bool>, (f64, usize)> = HashMap::new();
289
290 for _ in 0..shots {
292 let spins = self.simulate_cim(&j_matrix, &h_vector, &mut rng)?;
294
295 let binary = self.spins_to_binary(&spins);
297
298 let ising_energy = self.calculate_ising_energy(&spins, &j_matrix, &h_vector);
300 let qubo_energy = ising_energy + offset;
301
302 let entry = solution_counts
304 .entry(binary.clone())
305 .or_insert((qubo_energy, 0));
306 entry.1 += 1;
307 }
308
309 for (binary, (energy, count)) in solution_counts {
311 let assignments: HashMap<String, bool> = var_map
312 .iter()
313 .map(|(var, &idx)| (var.clone(), binary[idx]))
314 .collect();
315
316 results.push(SampleResult {
317 assignments,
318 energy,
319 occurrences: count,
320 });
321 }
322
323 results.sort_by(|a, b| {
325 a.energy
326 .partial_cmp(&b.energy)
327 .unwrap_or(std::cmp::Ordering::Equal)
328 });
329
330 Ok(results)
331 }
332
333 fn run_hobo(
334 &self,
335 _hobo: &(Array<f64, IxDyn>, HashMap<String, usize>),
336 _shots: usize,
337 ) -> SamplerResult<Vec<SampleResult>> {
338 Err(SamplerError::NotImplemented(
339 "CIM simulator currently only supports QUBO problems".to_string(),
340 ))
341 }
342}
343
344pub struct AdvancedCIM {
346 pub base_cim: CIMSimulator,
348 pulse_shape: PulseShape,
350 error_correction: ErrorCorrectionScheme,
352 pub bifurcation_control: BifurcationControl,
354 pub num_rounds: usize,
356}
357
358#[derive(Debug, Clone)]
359pub enum PulseShape {
360 Gaussian { width: f64, amplitude: f64 },
362 Sech { width: f64, amplitude: f64 },
364 Custom { name: String, parameters: Vec<f64> },
366}
367
368#[derive(Debug, Clone)]
369pub enum ErrorCorrectionScheme {
370 None,
372 MajorityVoting { window_size: usize },
374 ParityCheck { check_matrix: Array2<bool> },
376 Stabilizer { generators: Vec<Vec<bool>> },
378}
379
380#[derive(Debug, Clone)]
381pub struct BifurcationControl {
382 pub initial_param: f64,
384 pub final_param: f64,
386 ramp_time: f64,
388 ramp_type: RampType,
390}
391
392#[derive(Debug, Clone)]
393pub enum RampType {
394 Linear,
395 Exponential,
396 Sigmoid,
397 Adaptive,
398}
399
400impl AdvancedCIM {
401 pub const fn new(n_spins: usize) -> Self {
403 Self {
404 base_cim: CIMSimulator::new(n_spins),
405 pulse_shape: PulseShape::Gaussian {
406 width: 1.0,
407 amplitude: 1.0,
408 },
409 error_correction: ErrorCorrectionScheme::None,
410 bifurcation_control: BifurcationControl {
411 initial_param: 0.0,
412 final_param: 2.0,
413 ramp_time: 5.0,
414 ramp_type: RampType::Linear,
415 },
416 num_rounds: 1,
417 }
418 }
419
420 pub fn with_pulse_shape(mut self, shape: PulseShape) -> Self {
422 self.pulse_shape = shape;
423 self
424 }
425
426 pub fn with_error_correction(mut self, scheme: ErrorCorrectionScheme) -> Self {
428 self.error_correction = scheme;
429 self
430 }
431
432 pub const fn with_bifurcation_control(mut self, control: BifurcationControl) -> Self {
434 self.bifurcation_control = control;
435 self
436 }
437
438 pub const fn with_num_rounds(mut self, rounds: usize) -> Self {
440 self.num_rounds = rounds;
441 self
442 }
443
444 fn apply_pulse_shaping(&self, t: f64) -> f64 {
446 match &self.pulse_shape {
447 PulseShape::Gaussian { width, amplitude } => {
448 let sigma = width;
449 amplitude * (-t * t / (2.0 * sigma * sigma)).exp()
450 }
451 PulseShape::Sech { width, amplitude } => amplitude / (t / width).cosh(),
452 PulseShape::Custom { .. } => {
453 1.0
455 }
456 }
457 }
458
459 fn apply_error_correction(&self, spins: &mut Vec<f64>, history: &[Vec<f64>]) {
461 match &self.error_correction {
462 ErrorCorrectionScheme::None => {}
463 ErrorCorrectionScheme::MajorityVoting { window_size } => {
464 if history.len() >= *window_size {
465 for i in 0..spins.len() {
466 let mut sum = 0.0;
467 for h in history.iter().rev().take(*window_size) {
468 sum += h[i];
469 }
470 spins[i] = if sum > 0.0 { 1.0 } else { -1.0 };
471 }
472 }
473 }
474 ErrorCorrectionScheme::ParityCheck { check_matrix } => {
475 let n = spins.len();
477 let m = check_matrix.shape()[0];
478
479 for i in 0..m {
480 let mut parity = 0;
481 for j in 0..n {
482 if check_matrix[[i, j]] && spins[j] > 0.0 {
483 parity ^= 1;
484 }
485 }
486 if parity != 0 {
488 for j in 0..n {
491 if check_matrix[[i, j]] {
492 spins[j] *= -1.0;
493 break;
494 }
495 }
496 }
497 }
498 }
499 ErrorCorrectionScheme::Stabilizer { .. } => {
500 }
502 }
503 }
504
505 fn compute_bifurcation_param(&self, t: f64) -> f64 {
507 let progress = (t / self.bifurcation_control.ramp_time).min(1.0);
508 let initial = self.bifurcation_control.initial_param;
509 let final_param = self.bifurcation_control.final_param;
510
511 match self.bifurcation_control.ramp_type {
512 RampType::Linear => (final_param - initial).mul_add(progress, initial),
513 RampType::Exponential => {
514 (final_param - initial).mul_add(1.0 - (-5.0 * progress).exp(), initial)
515 }
516 RampType::Sigmoid => {
517 let x = 10.0 * (progress - 0.5);
518 let sigmoid = 1.0 / (1.0 + (-x).exp());
519 (final_param - initial).mul_add(sigmoid, initial)
520 }
521 RampType::Adaptive => {
522 (final_param - initial).mul_add(progress.powi(2), initial)
524 }
525 }
526 }
527}
528
529impl Sampler for AdvancedCIM {
530 fn run_qubo(
531 &self,
532 qubo: &(Array2<f64>, HashMap<String, usize>),
533 shots: usize,
534 ) -> SamplerResult<Vec<SampleResult>> {
535 let mut all_results = Vec::new();
536 let shots_per_round = shots / self.num_rounds.max(1);
537
538 for round in 0..self.num_rounds {
539 let t = round as f64 * self.base_cim.evolution_time / self.num_rounds as f64;
541 let pump = self.compute_bifurcation_param(t);
542
543 let mut round_cim = self.base_cim.clone();
544 round_cim.pump_parameter = pump * self.apply_pulse_shaping(t);
545
546 let round_results = round_cim.run_qubo(qubo, shots_per_round)?;
548 all_results.extend(round_results);
549 }
550
551 let mut aggregated: HashMap<Vec<bool>, (f64, usize)> = HashMap::new();
553
554 for result in all_results {
555 let state: Vec<bool> = qubo.1.keys().map(|var| result.assignments[var]).collect();
556
557 let entry = aggregated.entry(state).or_insert((result.energy, 0));
558 entry.1 += result.occurrences;
559 }
560
561 let mut final_results: Vec<SampleResult> = aggregated
562 .into_iter()
563 .map(|(state, (energy, count))| {
564 let assignments: HashMap<String, bool> = qubo
565 .1
566 .iter()
567 .zip(state.iter())
568 .map(|((var, _), &val)| (var.clone(), val))
569 .collect();
570
571 SampleResult {
572 assignments,
573 energy,
574 occurrences: count,
575 }
576 })
577 .collect();
578
579 final_results.sort_by(|a, b| {
580 a.energy
581 .partial_cmp(&b.energy)
582 .unwrap_or(std::cmp::Ordering::Equal)
583 });
584
585 Ok(final_results)
586 }
587
588 fn run_hobo(
589 &self,
590 hobo: &(Array<f64, IxDyn>, HashMap<String, usize>),
591 shots: usize,
592 ) -> SamplerResult<Vec<SampleResult>> {
593 self.base_cim.run_hobo(hobo, shots)
594 }
595}
596
597pub struct NetworkedCIM {
599 pub modules: Vec<CIMSimulator>,
601 topology: NetworkTopology,
603 sync_scheme: SynchronizationScheme,
605 comm_delay: f64,
607}
608
609#[derive(Debug, Clone)]
610pub enum NetworkTopology {
611 FullyConnected,
613 Ring,
615 Grid2D { rows: usize, cols: usize },
617 Hierarchical { levels: usize },
619 Custom { adjacency: Array2<bool> },
621}
622
623#[derive(Debug, Clone)]
624pub enum SynchronizationScheme {
625 Synchronous,
627 Asynchronous,
629 BlockSynchronous { block_size: usize },
631 EventDriven { threshold: f64 },
633}
634
635impl NetworkedCIM {
636 pub fn new(num_modules: usize, spins_per_module: usize, topology: NetworkTopology) -> Self {
638 let modules = (0..num_modules)
639 .map(|_| CIMSimulator::new(spins_per_module))
640 .collect();
641
642 Self {
643 modules,
644 topology,
645 sync_scheme: SynchronizationScheme::Synchronous,
646 comm_delay: 0.0,
647 }
648 }
649
650 pub const fn with_sync_scheme(mut self, scheme: SynchronizationScheme) -> Self {
652 self.sync_scheme = scheme;
653 self
654 }
655
656 pub const fn with_comm_delay(mut self, delay: f64) -> Self {
658 self.comm_delay = delay;
659 self
660 }
661
662 pub fn get_neighbors(&self, module_idx: usize) -> Vec<usize> {
664 match &self.topology {
665 NetworkTopology::FullyConnected => (0..self.modules.len())
666 .filter(|&i| i != module_idx)
667 .collect(),
668 NetworkTopology::Ring => {
669 let n = self.modules.len();
670 vec![(module_idx + n - 1) % n, (module_idx + 1) % n]
671 }
672 NetworkTopology::Grid2D { rows, cols } => {
673 let row = module_idx / cols;
674 let col = module_idx % cols;
675 let mut neighbors = Vec::new();
676
677 if row > 0 {
678 neighbors.push((row - 1) * cols + col);
679 }
680 if row < rows - 1 {
681 neighbors.push((row + 1) * cols + col);
682 }
683 if col > 0 {
684 neighbors.push(row * cols + (col - 1));
685 }
686 if col < cols - 1 {
687 neighbors.push(row * cols + (col + 1));
688 }
689
690 neighbors
691 }
692 _ => Vec::new(),
693 }
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700
701 #[test]
702 fn test_cim_simulator() {
703 let cim = CIMSimulator::new(4)
704 .with_pump_parameter(1.5)
705 .with_evolution_time(5.0)
706 .with_seed(42);
707
708 let mut qubo_matrix = Array2::zeros((4, 4));
710 qubo_matrix[[0, 1]] = -1.0;
711 qubo_matrix[[1, 0]] = -1.0;
712
713 let mut var_map = HashMap::new();
714 for i in 0..4 {
715 var_map.insert(format!("x{i}"), i);
716 }
717
718 let results = cim
719 .run_qubo(&(qubo_matrix, var_map), 10)
720 .expect("CIM run_qubo should succeed for valid QUBO input");
721 assert!(!results.is_empty());
722 }
723
724 #[test]
725 fn test_advanced_cim() {
726 let cim = AdvancedCIM::new(3)
727 .with_pulse_shape(PulseShape::Gaussian {
728 width: 1.0,
729 amplitude: 1.5,
730 })
731 .with_num_rounds(2);
732
733 assert_eq!(cim.num_rounds, 2);
734 }
735
736 #[test]
737 fn test_networked_cim() {
738 let net_cim = NetworkedCIM::new(4, 2, NetworkTopology::Ring)
739 .with_sync_scheme(SynchronizationScheme::Synchronous);
740
741 assert_eq!(net_cim.modules.len(), 4);
742 assert_eq!(net_cim.get_neighbors(0), vec![3, 1]);
743 }
744}