1use scirs2_core::random::prelude::*;
18use scirs2_core::random::ChaCha8Rng;
19use scirs2_core::random::{Rng, SeedableRng};
20use scirs2_core::Complex as NComplex;
21use std::collections::HashMap;
22use std::f64::consts::PI;
23use std::time::{Duration, Instant};
24use thiserror::Error;
25
26use crate::ising::{IsingError, IsingModel};
27use crate::simulator::{AnnealingParams, AnnealingSolution};
28
29#[derive(Error, Debug)]
31pub enum NonStoquasticError {
32 #[error("Ising error: {0}")]
34 IsingError(#[from] IsingError),
35
36 #[error("Invalid Hamiltonian: {0}")]
38 InvalidHamiltonian(String),
39
40 #[error("Simulation error: {0}")]
42 SimulationError(String),
43
44 #[error("Sign problem: {0}")]
46 SignProblem(String),
47
48 #[error("Convergence error: {0}")]
50 ConvergenceError(String),
51
52 #[error("Dimension mismatch: expected {expected}, got {actual}")]
54 DimensionMismatch { expected: usize, actual: usize },
55
56 #[error("Complex arithmetic error: {0}")]
58 ComplexArithmeticError(String),
59}
60
61pub type NonStoquasticResult<T> = Result<T, NonStoquasticError>;
63
64#[derive(Debug, Clone, PartialEq)]
66pub enum HamiltonianType {
67 XYModel { j_x: f64, j_y: f64 },
69
70 XYZModel { j_x: f64, j_y: f64, j_z: f64 },
72
73 ComplexIsingModel,
75
76 FermionicModel,
78
79 CustomModel,
81
82 MixedModel { stoquastic_fraction: f64 },
84}
85
86#[derive(Debug, Clone, PartialEq)]
88pub struct ComplexCoupling {
89 pub sites: (usize, usize),
91 pub strength: NComplex<f64>,
93 pub interaction_type: InteractionType,
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub enum InteractionType {
100 XX,
102 YY,
104 ZZ,
106 XY,
108 ComplexXY,
110 CustomMatrix(Vec<Vec<NComplex<f64>>>),
112}
113
114#[derive(Debug, Clone)]
116pub struct NonStoquasticHamiltonian {
117 pub num_qubits: usize,
119
120 pub hamiltonian_type: HamiltonianType,
122
123 pub local_fields: Vec<f64>,
125
126 pub complex_couplings: Vec<ComplexCoupling>,
128
129 pub ising_part: Option<IsingModel>,
131
132 pub global_phase: NComplex<f64>,
134
135 pub has_sign_problem: bool,
137}
138
139impl NonStoquasticHamiltonian {
140 #[must_use]
142 pub fn new(num_qubits: usize, hamiltonian_type: HamiltonianType) -> Self {
143 let has_sign_problem = match hamiltonian_type {
144 HamiltonianType::XYModel { .. }
145 | HamiltonianType::XYZModel { .. }
146 | HamiltonianType::ComplexIsingModel
147 | HamiltonianType::FermionicModel
148 | HamiltonianType::CustomModel => true,
149 HamiltonianType::MixedModel {
150 stoquastic_fraction,
151 } => stoquastic_fraction < 1.0,
152 };
153
154 Self {
155 num_qubits,
156 hamiltonian_type,
157 local_fields: vec![0.0; num_qubits],
158 complex_couplings: Vec::new(),
159 ising_part: None,
160 global_phase: NComplex::new(1.0, 0.0),
161 has_sign_problem,
162 }
163 }
164
165 pub fn xy_model(num_qubits: usize, j_x: f64, j_y: f64) -> NonStoquasticResult<Self> {
167 let mut hamiltonian = Self::new(num_qubits, HamiltonianType::XYModel { j_x, j_y });
168
169 for i in 0..num_qubits - 1 {
171 hamiltonian.add_complex_coupling(ComplexCoupling {
173 sites: (i, i + 1),
174 strength: NComplex::new(j_x, 0.0),
175 interaction_type: InteractionType::XX,
176 })?;
177
178 hamiltonian.add_complex_coupling(ComplexCoupling {
180 sites: (i, i + 1),
181 strength: NComplex::new(j_y, 0.0),
182 interaction_type: InteractionType::YY,
183 })?;
184 }
185
186 Ok(hamiltonian)
187 }
188
189 pub fn xyz_model(num_qubits: usize, j_x: f64, j_y: f64, j_z: f64) -> NonStoquasticResult<Self> {
191 let mut hamiltonian = Self::new(num_qubits, HamiltonianType::XYZModel { j_x, j_y, j_z });
192
193 for i in 0..num_qubits - 1 {
195 hamiltonian.add_complex_coupling(ComplexCoupling {
197 sites: (i, i + 1),
198 strength: NComplex::new(j_x, 0.0),
199 interaction_type: InteractionType::XX,
200 })?;
201
202 hamiltonian.add_complex_coupling(ComplexCoupling {
204 sites: (i, i + 1),
205 strength: NComplex::new(j_y, 0.0),
206 interaction_type: InteractionType::YY,
207 })?;
208
209 hamiltonian.add_complex_coupling(ComplexCoupling {
211 sites: (i, i + 1),
212 strength: NComplex::new(j_z, 0.0),
213 interaction_type: InteractionType::ZZ,
214 })?;
215 }
216
217 Ok(hamiltonian)
218 }
219
220 #[must_use]
222 pub fn complex_ising_model(num_qubits: usize) -> Self {
223 Self::new(num_qubits, HamiltonianType::ComplexIsingModel)
224 }
225
226 pub fn add_complex_coupling(&mut self, coupling: ComplexCoupling) -> NonStoquasticResult<()> {
228 if coupling.sites.0 >= self.num_qubits || coupling.sites.1 >= self.num_qubits {
229 return Err(NonStoquasticError::InvalidHamiltonian(format!(
230 "Invalid coupling sites: ({}, {}) for {} qubits",
231 coupling.sites.0, coupling.sites.1, self.num_qubits
232 )));
233 }
234
235 self.complex_couplings.push(coupling);
236 Ok(())
237 }
238
239 pub fn set_local_field(&mut self, site: usize, field: f64) -> NonStoquasticResult<()> {
241 if site >= self.num_qubits {
242 return Err(NonStoquasticError::InvalidHamiltonian(format!(
243 "Invalid site index: {} for {} qubits",
244 site, self.num_qubits
245 )));
246 }
247
248 self.local_fields[site] = field;
249 Ok(())
250 }
251
252 #[must_use]
254 pub const fn is_stoquastic(&self) -> bool {
255 !self.has_sign_problem
256 }
257
258 #[must_use]
260 pub fn sign_problem_severity(&self) -> f64 {
261 if !self.has_sign_problem {
262 return 0.0;
263 }
264
265 let mut non_stoquastic_weight = 0.0;
267 let mut total_weight = 0.0;
268
269 for coupling in &self.complex_couplings {
270 let magnitude = coupling.strength.norm();
271 total_weight += magnitude;
272
273 match coupling.interaction_type {
274 InteractionType::XX
275 | InteractionType::YY
276 | InteractionType::XY
277 | InteractionType::ComplexXY => {
278 non_stoquastic_weight += magnitude;
279 }
280 _ => {}
281 }
282 }
283
284 if total_weight > 0.0 {
285 non_stoquastic_weight / total_weight
286 } else {
287 0.0
288 }
289 }
290
291 pub fn to_matrix(&self) -> NonStoquasticResult<Vec<Vec<NComplex<f64>>>> {
293 if self.num_qubits > 12 {
294 return Err(NonStoquasticError::SimulationError(
295 "Matrix representation only supported for ≤12 qubits".to_string(),
296 ));
297 }
298
299 let dim = 1 << self.num_qubits;
300 let mut matrix = vec![vec![NComplex::new(0.0, 0.0); dim]; dim];
301
302 for site in 0..self.num_qubits {
304 let field = self.local_fields[site];
305 if field.abs() > 1e-12 {
306 for state in 0..dim {
307 let spin = if (state >> site) & 1 == 1 { 1.0 } else { -1.0 };
308 matrix[state][state] += NComplex::new(field * spin, 0.0);
309 }
310 }
311 }
312
313 for coupling in &self.complex_couplings {
315 let (i, j) = coupling.sites;
316
317 match coupling.interaction_type {
318 InteractionType::ZZ => {
319 for state in 0..dim {
321 let spin_i = if (state >> i) & 1 == 1 { 1.0 } else { -1.0 };
322 let spin_j = if (state >> j) & 1 == 1 { 1.0 } else { -1.0 };
323 matrix[state][state] += coupling.strength * spin_i * spin_j;
324 }
325 }
326 InteractionType::XX => {
327 for state in 0..dim {
329 let flipped = state ^ (1 << i) ^ (1 << j);
330 matrix[state][flipped] += coupling.strength;
331 }
332 }
333 InteractionType::YY => {
334 for state in 0..dim {
336 let spin_i = if (state >> i) & 1 == 1 { 1.0 } else { -1.0 };
337 let spin_j = if (state >> j) & 1 == 1 { 1.0 } else { -1.0 };
338 let flipped = state ^ (1 << i) ^ (1 << j);
339 let phase = NComplex::new(0.0, spin_i * spin_j);
340 matrix[state][flipped] += coupling.strength * phase;
341 }
342 }
343 _ => {
344 for state in 0..dim {
346 let flipped = state ^ (1 << i) ^ (1 << j);
347 matrix[state][flipped] += coupling.strength * 0.5;
348 }
349 }
350 }
351 }
352
353 Ok(matrix)
354 }
355}
356
357#[derive(Debug, Clone)]
359pub struct NonStoquasticQMCConfig {
360 pub num_steps: usize,
362 pub thermalization_steps: usize,
364 pub temperature: f64,
366 pub tau: f64,
368 pub num_time_slices: usize,
370 pub population_size: usize,
372 pub sign_mitigation: SignMitigationStrategy,
374 pub seed: Option<u64>,
376 pub measurement_interval: usize,
378 pub convergence_threshold: f64,
380}
381
382impl Default for NonStoquasticQMCConfig {
383 fn default() -> Self {
384 Self {
385 num_steps: 10_000,
386 thermalization_steps: 1000,
387 temperature: 1.0,
388 tau: 0.1,
389 num_time_slices: 10,
390 population_size: 1000,
391 sign_mitigation: SignMitigationStrategy::ReweightingMethod,
392 seed: None,
393 measurement_interval: 10,
394 convergence_threshold: 1e-6,
395 }
396 }
397}
398
399#[derive(Debug, Clone, PartialEq, Eq)]
401pub enum SignMitigationStrategy {
402 ReweightingMethod,
404 ConstrainedPath,
406 PopulationAnnealing,
408 MeronClusters,
410 ComplexLangevin,
412 AuxiliaryField,
414}
415
416#[derive(Debug, Clone)]
418pub struct NonStoquasticResults {
419 pub ground_state_energy: NComplex<f64>,
421 pub energy_variance: f64,
423 pub ground_state: Option<Vec<i8>>,
425 pub average_sign: NComplex<f64>,
427 pub sign_problem_severity: f64,
429 pub qmc_statistics: QMCStatistics,
431 pub simulation_time: Duration,
433 pub convergence_info: ConvergenceInfo,
435}
436
437#[derive(Debug, Clone)]
439pub struct QMCStatistics {
440 pub acceptance_rate: f64,
442 pub autocorrelation_time: f64,
444 pub effective_sample_size: usize,
446 pub statistical_errors: HashMap<String, f64>,
448 pub population_evolution: Vec<usize>,
450}
451
452#[derive(Debug, Clone)]
454pub struct ConvergenceInfo {
455 pub converged: bool,
457 pub convergence_step: Option<usize>,
459 pub energy_history: Vec<NComplex<f64>>,
461 pub sign_history: Vec<NComplex<f64>>,
463}
464
465pub struct NonStoquasticSimulator {
467 config: NonStoquasticQMCConfig,
469 rng: ChaCha8Rng,
471 current_state: QuantumState,
473 hamiltonian: NonStoquasticHamiltonian,
475}
476
477#[derive(Debug, Clone)]
479pub struct QuantumState {
480 pub num_qubits: usize,
482 pub num_time_slices: usize,
484 pub configurations: Vec<Vec<i8>>,
486 pub amplitudes: Option<Vec<NComplex<f64>>>,
488 pub energy: NComplex<f64>,
490 pub sign: NComplex<f64>,
492}
493
494impl QuantumState {
495 #[must_use]
497 pub fn new(num_qubits: usize, num_time_slices: usize) -> Self {
498 let configurations = vec![vec![1; num_qubits]; num_time_slices];
499
500 Self {
501 num_qubits,
502 num_time_slices,
503 configurations,
504 amplitudes: None,
505 energy: NComplex::new(0.0, 0.0),
506 sign: NComplex::new(1.0, 0.0),
507 }
508 }
509
510 pub fn initialize_random(&mut self, rng: &mut ChaCha8Rng) {
512 for time_slice in &mut self.configurations {
513 for spin in time_slice {
514 *spin = if rng.gen_bool(0.5) { 1 } else { -1 };
515 }
516 }
517 }
518
519 #[must_use]
521 pub fn overlap(&self, other: &Self) -> NComplex<f64> {
522 if let (Some(ref amp1), Some(ref amp2)) = (&self.amplitudes, &other.amplitudes) {
523 amp1.iter()
524 .zip(amp2.iter())
525 .map(|(a, b)| a.conj() * b)
526 .sum()
527 } else {
528 NComplex::new(0.0, 0.0)
529 }
530 }
531}
532
533impl NonStoquasticSimulator {
534 pub fn new(
536 hamiltonian: NonStoquasticHamiltonian,
537 config: NonStoquasticQMCConfig,
538 ) -> NonStoquasticResult<Self> {
539 let rng = match config.seed {
540 Some(seed) => ChaCha8Rng::seed_from_u64(seed),
541 None => ChaCha8Rng::seed_from_u64(thread_rng().gen()),
542 };
543
544 let current_state = QuantumState::new(hamiltonian.num_qubits, config.num_time_slices);
545
546 Ok(Self {
547 config,
548 rng,
549 current_state,
550 hamiltonian,
551 })
552 }
553
554 pub fn simulate(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
556 let start_time = Instant::now();
557
558 self.current_state.initialize_random(&mut self.rng);
560
561 let result = if self.hamiltonian.sign_problem_severity() > 0.1 {
563 self.simulate_with_sign_problem()?
564 } else {
565 self.simulate_stoquastic_like()?
566 };
567
568 let simulation_time = start_time.elapsed();
569
570 Ok(NonStoquasticResults {
571 simulation_time,
572 ..result
573 })
574 }
575
576 fn simulate_with_sign_problem(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
578 match self.config.sign_mitigation {
579 SignMitigationStrategy::ReweightingMethod => self.reweighting_simulation(),
580 SignMitigationStrategy::PopulationAnnealing => self.population_annealing_simulation(),
581 SignMitigationStrategy::ConstrainedPath => self.constrained_path_simulation(),
582 _ => self.basic_complex_simulation(),
583 }
584 }
585
586 fn simulate_stoquastic_like(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
588 self.basic_quantum_monte_carlo()
589 }
590
591 fn basic_quantum_monte_carlo(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
593 let mut energy_samples = Vec::new();
594 let mut sign_samples = Vec::new();
595 let mut acceptance_count = 0;
596 let mut total_proposals = 0;
597
598 for _ in 0..self.config.thermalization_steps {
600 self.propose_and_accept_move()?;
601 }
602
603 for step in 0..self.config.num_steps {
605 total_proposals += 1;
606
607 if self.propose_and_accept_move()? {
608 acceptance_count += 1;
609 }
610
611 if step % self.config.measurement_interval == 0 {
612 let energy = self.calculate_energy()?;
613 let sign = self.calculate_sign()?;
614
615 energy_samples.push(energy);
616 sign_samples.push(sign);
617 }
618 }
619
620 let ground_state_energy = if energy_samples.is_empty() {
622 NComplex::new(0.0, 0.0)
623 } else {
624 energy_samples.iter().sum::<NComplex<f64>>() / energy_samples.len() as f64
625 };
626
627 let average_sign = if sign_samples.is_empty() {
628 NComplex::new(1.0, 0.0)
629 } else {
630 sign_samples.iter().sum::<NComplex<f64>>() / sign_samples.len() as f64
631 };
632
633 let energy_variance = if energy_samples.len() > 1 {
634 let mean = ground_state_energy;
635 energy_samples
636 .iter()
637 .map(|e| (e - mean).norm_sqr())
638 .sum::<f64>()
639 / (energy_samples.len() - 1) as f64
640 } else {
641 0.0
642 };
643
644 let acceptance_rate = f64::from(acceptance_count) / f64::from(total_proposals);
645
646 Ok(NonStoquasticResults {
647 ground_state_energy,
648 energy_variance,
649 ground_state: Some(self.current_state.configurations[0].clone()),
650 average_sign,
651 sign_problem_severity: self.hamiltonian.sign_problem_severity(),
652 qmc_statistics: QMCStatistics {
653 acceptance_rate,
654 autocorrelation_time: 1.0, effective_sample_size: energy_samples.len(),
656 statistical_errors: HashMap::new(),
657 population_evolution: Vec::new(),
658 },
659 simulation_time: Duration::from_secs(0), convergence_info: ConvergenceInfo {
661 converged: true,
662 convergence_step: Some(self.config.num_steps),
663 energy_history: energy_samples,
664 sign_history: sign_samples,
665 },
666 })
667 }
668
669 fn reweighting_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
671 let mut weighted_energy = NComplex::new(0.0, 0.0);
672 let mut total_weight = NComplex::new(0.0, 0.0);
673 let mut sign_samples = Vec::new();
674
675 for _ in 0..self.config.num_steps {
676 self.propose_and_accept_move()?;
677
678 let energy = self.calculate_energy()?;
679 let weight = self.calculate_weight()?;
680 let sign = self.calculate_sign()?;
681
682 weighted_energy += energy * weight;
683 total_weight += weight;
684 sign_samples.push(sign);
685 }
686
687 let ground_state_energy = if total_weight.norm() > 1e-12 {
688 weighted_energy / total_weight
689 } else {
690 NComplex::new(0.0, 0.0)
691 };
692
693 let average_sign = if sign_samples.is_empty() {
694 NComplex::new(1.0, 0.0)
695 } else {
696 sign_samples.iter().sum::<NComplex<f64>>() / sign_samples.len() as f64
697 };
698
699 Ok(NonStoquasticResults {
700 ground_state_energy,
701 energy_variance: 0.0, ground_state: Some(self.current_state.configurations[0].clone()),
703 average_sign,
704 sign_problem_severity: self.hamiltonian.sign_problem_severity(),
705 qmc_statistics: QMCStatistics {
706 acceptance_rate: 0.5, autocorrelation_time: 1.0,
708 effective_sample_size: self.config.num_steps,
709 statistical_errors: HashMap::new(),
710 population_evolution: Vec::new(),
711 },
712 simulation_time: Duration::from_secs(0),
713 convergence_info: ConvergenceInfo {
714 converged: average_sign.norm() > 0.1,
715 convergence_step: Some(self.config.num_steps),
716 energy_history: Vec::new(),
717 sign_history: sign_samples,
718 },
719 })
720 }
721
722 fn population_annealing_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
724 let mut population = Vec::new();
725 let mut weights = Vec::new();
726
727 for _ in 0..self.config.population_size {
729 let mut state =
730 QuantumState::new(self.hamiltonian.num_qubits, self.config.num_time_slices);
731 state.initialize_random(&mut self.rng);
732 population.push(state);
733 weights.push(NComplex::new(1.0, 0.0));
734 }
735
736 let num_annealing_steps = 10;
738 let mut population_evolution = Vec::new();
739
740 for step in 0..num_annealing_steps {
741 let beta = (step as f64 / (num_annealing_steps - 1) as f64) / self.config.temperature;
742
743 for (i, state) in population.iter().enumerate() {
745 let energy = self.calculate_state_energy(state)?;
746 weights[i] = (-beta * energy).exp();
747 }
748
749 population = self.resample_population(population, &weights)?;
751 weights.fill(NComplex::new(1.0, 0.0));
752
753 population_evolution.push(population.len());
754 }
755
756 let final_energies: Vec<NComplex<f64>> = population
758 .iter()
759 .map(|state| {
760 self.calculate_state_energy(state)
761 .unwrap_or(NComplex::new(0.0, 0.0))
762 })
763 .collect();
764
765 let ground_state_energy = final_energies
766 .iter()
767 .min_by(|a, b| {
768 a.norm()
769 .partial_cmp(&b.norm())
770 .unwrap_or(std::cmp::Ordering::Equal)
771 })
772 .copied()
773 .unwrap_or(NComplex::new(0.0, 0.0));
774
775 Ok(NonStoquasticResults {
776 ground_state_energy,
777 energy_variance: 0.0,
778 ground_state: population.first().map(|s| s.configurations[0].clone()),
779 average_sign: NComplex::new(1.0, 0.0),
780 sign_problem_severity: self.hamiltonian.sign_problem_severity(),
781 qmc_statistics: QMCStatistics {
782 acceptance_rate: 0.7,
783 autocorrelation_time: 1.0,
784 effective_sample_size: population.len(),
785 statistical_errors: HashMap::new(),
786 population_evolution,
787 },
788 simulation_time: Duration::from_secs(0),
789 convergence_info: ConvergenceInfo {
790 converged: true,
791 convergence_step: Some(num_annealing_steps),
792 energy_history: final_energies,
793 sign_history: Vec::new(),
794 },
795 })
796 }
797
798 fn constrained_path_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
800 self.basic_complex_simulation()
803 }
804
805 fn basic_complex_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
807 self.basic_quantum_monte_carlo()
809 }
810
811 fn propose_and_accept_move(&mut self) -> NonStoquasticResult<bool> {
813 let time_slice = self.rng.gen_range(0..self.config.num_time_slices);
815 let spin_site = self.rng.gen_range(0..self.hamiltonian.num_qubits);
816
817 let energy_before = self.calculate_local_energy(time_slice, spin_site)?;
819
820 self.current_state.configurations[time_slice][spin_site] *= -1;
822
823 let energy_after = self.calculate_local_energy(time_slice, spin_site)?;
824 let energy_diff = energy_after - energy_before;
825
826 let accept_prob = (-energy_diff.re / self.config.temperature).exp().min(1.0);
828
829 if self.rng.gen::<f64>() < accept_prob {
830 Ok(true)
832 } else {
833 self.current_state.configurations[time_slice][spin_site] *= -1;
835 Ok(false)
836 }
837 }
838
839 fn calculate_local_energy(
841 &self,
842 time_slice: usize,
843 site: usize,
844 ) -> NonStoquasticResult<NComplex<f64>> {
845 let mut energy = NComplex::new(0.0, 0.0);
846
847 let spin = f64::from(self.current_state.configurations[time_slice][site]);
849 energy += self.hamiltonian.local_fields[site] * spin;
850
851 for coupling in &self.hamiltonian.complex_couplings {
853 if coupling.sites.0 == site || coupling.sites.1 == site {
854 let (i, j) = coupling.sites;
855 let spin_i = f64::from(self.current_state.configurations[time_slice][i]);
856 let spin_j = f64::from(self.current_state.configurations[time_slice][j]);
857
858 match coupling.interaction_type {
859 InteractionType::ZZ => {
860 energy += coupling.strength * spin_i * spin_j;
861 }
862 InteractionType::XX | InteractionType::YY => {
863 energy += coupling.strength * spin_i * spin_j * 0.5;
865 }
866 _ => {
867 energy += coupling.strength * spin_i * spin_j * 0.25;
869 }
870 }
871 }
872 }
873
874 Ok(energy)
875 }
876
877 fn calculate_energy(&self) -> NonStoquasticResult<NComplex<f64>> {
879 let mut total_energy = NComplex::new(0.0, 0.0);
880
881 for time_slice in 0..self.config.num_time_slices {
882 for site in 0..self.hamiltonian.num_qubits {
883 total_energy += self.calculate_local_energy(time_slice, site)?;
884 }
885 }
886
887 Ok(total_energy / self.config.num_time_slices as f64)
888 }
889
890 fn calculate_state_energy(&self, state: &QuantumState) -> NonStoquasticResult<NComplex<f64>> {
892 let mut energy = NComplex::new(0.0, 0.0);
893
894 for (site, &field) in self.hamiltonian.local_fields.iter().enumerate() {
896 for time_slice in 0..state.num_time_slices {
897 let spin = f64::from(state.configurations[time_slice][site]);
898 energy += field * spin;
899 }
900 }
901
902 for coupling in &self.hamiltonian.complex_couplings {
904 let (i, j) = coupling.sites;
905 for time_slice in 0..state.num_time_slices {
906 let spin_i = f64::from(state.configurations[time_slice][i]);
907 let spin_j = f64::from(state.configurations[time_slice][j]);
908
909 match coupling.interaction_type {
910 InteractionType::ZZ => {
911 energy += coupling.strength * spin_i * spin_j;
912 }
913 _ => {
914 energy += coupling.strength * spin_i * spin_j * 0.5; }
916 }
917 }
918 }
919
920 Ok(energy / state.num_time_slices as f64)
921 }
922
923 fn calculate_sign(&self) -> NonStoquasticResult<NComplex<f64>> {
925 let sign = if let HamiltonianType::XYModel { .. } = self.hamiltonian.hamiltonian_type {
927 let mut phase = 0.0;
928
929 for coupling in &self.hamiltonian.complex_couplings {
930 if matches!(coupling.interaction_type, InteractionType::YY) {
931 let (i, j) = coupling.sites;
932 for time_slice in 0..self.config.num_time_slices {
933 let spin_i = self.current_state.configurations[time_slice][i];
934 let spin_j = self.current_state.configurations[time_slice][j];
935
936 if spin_i != spin_j {
937 phase += PI / 4.0; }
939 }
940 }
941 }
942
943 NComplex::new(phase.cos(), phase.sin())
944 } else {
945 NComplex::new(1.0, 0.0)
946 };
947
948 Ok(sign)
949 }
950
951 fn calculate_weight(&self) -> NonStoquasticResult<NComplex<f64>> {
953 let sign = self.calculate_sign()?;
955 Ok(sign)
956 }
957
958 fn resample_population(
960 &mut self,
961 mut population: Vec<QuantumState>,
962 weights: &[NComplex<f64>],
963 ) -> NonStoquasticResult<Vec<QuantumState>> {
964 let total_weight: f64 = weights.iter().map(|w| w.norm()).sum();
966 if total_weight < 1e-12 {
967 return Ok(population);
968 }
969
970 let probabilities: Vec<f64> = weights.iter().map(|w| w.norm() / total_weight).collect();
971
972 let mut new_population = Vec::new();
974 let n = population.len();
975 let step = 1.0 / n as f64;
976 let mut cumsum = 0.0;
977 let offset = self.rng.gen::<f64>() * step;
978
979 let mut i = 0;
980 for j in 0..n {
981 let target = (j as f64).mul_add(step, offset);
982
983 while cumsum < target && i < probabilities.len() {
984 cumsum += probabilities[i];
985 i += 1;
986 }
987
988 if i > 0 {
989 new_population.push(population[(i - 1).min(population.len() - 1)].clone());
990 }
991 }
992
993 Ok(new_population)
994 }
995}
996
997#[must_use]
1001pub const fn is_hamiltonian_stoquastic(hamiltonian: &NonStoquasticHamiltonian) -> bool {
1002 hamiltonian.is_stoquastic()
1003}
1004
1005pub fn xy_to_ising_approximation(
1007 xy_hamiltonian: &NonStoquasticHamiltonian,
1008) -> NonStoquasticResult<IsingModel> {
1009 if !matches!(
1010 xy_hamiltonian.hamiltonian_type,
1011 HamiltonianType::XYModel { .. }
1012 ) {
1013 return Err(NonStoquasticError::InvalidHamiltonian(
1014 "Expected XY model".to_string(),
1015 ));
1016 }
1017
1018 let mut ising = IsingModel::new(xy_hamiltonian.num_qubits);
1019
1020 for (site, &field) in xy_hamiltonian.local_fields.iter().enumerate() {
1022 ising.set_bias(site, field)?;
1023 }
1024
1025 let mut coupling_map: HashMap<(usize, usize), f64> = HashMap::new();
1027
1028 for coupling in &xy_hamiltonian.complex_couplings {
1029 let (i, j) = coupling.sites;
1030 let key = if i < j { (i, j) } else { (j, i) };
1031
1032 let effective_strength = match coupling.interaction_type {
1033 InteractionType::XX | InteractionType::YY => {
1034 -coupling.strength.re.abs() * 0.5
1036 }
1037 InteractionType::ZZ => coupling.strength.re,
1038 _ => coupling.strength.re * 0.25, };
1040
1041 *coupling_map.entry(key).or_insert(0.0) += effective_strength;
1042 }
1043
1044 for ((i, j), strength) in coupling_map {
1046 ising.set_coupling(i, j, strength)?;
1047 }
1048
1049 Ok(ising)
1050}
1051
1052pub fn create_xy_chain(
1056 num_qubits: usize,
1057 j_x: f64,
1058 j_y: f64,
1059) -> NonStoquasticResult<NonStoquasticHamiltonian> {
1060 let mut hamiltonian = NonStoquasticHamiltonian::xy_model(num_qubits, j_x, j_y)?;
1061
1062 if num_qubits > 2 {
1064 hamiltonian.add_complex_coupling(ComplexCoupling {
1065 sites: (num_qubits - 1, 0),
1066 strength: NComplex::new(j_x, 0.0),
1067 interaction_type: InteractionType::XX,
1068 })?;
1069
1070 hamiltonian.add_complex_coupling(ComplexCoupling {
1071 sites: (num_qubits - 1, 0),
1072 strength: NComplex::new(j_y, 0.0),
1073 interaction_type: InteractionType::YY,
1074 })?;
1075 }
1076
1077 Ok(hamiltonian)
1078}
1079
1080pub fn create_tfxy_model(
1082 num_qubits: usize,
1083 j_x: f64,
1084 j_y: f64,
1085 h_z: f64,
1086) -> NonStoquasticResult<NonStoquasticHamiltonian> {
1087 let mut hamiltonian = NonStoquasticHamiltonian::xy_model(num_qubits, j_x, j_y)?;
1088
1089 for site in 0..num_qubits {
1091 hamiltonian.set_local_field(site, h_z)?;
1092 }
1093
1094 Ok(hamiltonian)
1095}
1096
1097pub fn create_frustrated_xy_triangle(j_xy: f64) -> NonStoquasticResult<NonStoquasticHamiltonian> {
1099 let mut hamiltonian = NonStoquasticHamiltonian::new(
1100 3,
1101 HamiltonianType::XYModel {
1102 j_x: j_xy,
1103 j_y: j_xy,
1104 },
1105 );
1106
1107 for i in 0..3 {
1109 for j in (i + 1)..3 {
1110 hamiltonian.add_complex_coupling(ComplexCoupling {
1111 sites: (i, j),
1112 strength: NComplex::new(j_xy, 0.0),
1113 interaction_type: InteractionType::XX,
1114 })?;
1115
1116 hamiltonian.add_complex_coupling(ComplexCoupling {
1117 sites: (i, j),
1118 strength: NComplex::new(j_xy, 0.0),
1119 interaction_type: InteractionType::YY,
1120 })?;
1121 }
1122 }
1123
1124 Ok(hamiltonian)
1125}
1126
1127#[cfg(test)]
1128mod tests {
1129 use super::*;
1130
1131 #[test]
1132 fn test_xy_model_creation() {
1133 let hamiltonian =
1134 NonStoquasticHamiltonian::xy_model(4, 1.0, 0.5).expect("Failed to create XY model");
1135 assert_eq!(hamiltonian.num_qubits, 4);
1136 assert!(hamiltonian.has_sign_problem);
1137 assert_eq!(hamiltonian.complex_couplings.len(), 6); }
1139
1140 #[test]
1141 fn test_xyz_model_creation() {
1142 let hamiltonian = NonStoquasticHamiltonian::xyz_model(3, 1.0, 1.0, 0.5)
1143 .expect("Failed to create XYZ model");
1144 assert_eq!(hamiltonian.num_qubits, 3);
1145 assert!(hamiltonian.has_sign_problem);
1146 assert_eq!(hamiltonian.complex_couplings.len(), 6); }
1148
1149 #[test]
1150 fn test_sign_problem_detection() {
1151 let xy_hamiltonian =
1152 NonStoquasticHamiltonian::xy_model(4, 1.0, 1.0).expect("Failed to create XY model");
1153 assert!(xy_hamiltonian.sign_problem_severity() > 0.0);
1154
1155 let ising_like = NonStoquasticHamiltonian::new(
1156 4,
1157 HamiltonianType::MixedModel {
1158 stoquastic_fraction: 1.0,
1159 },
1160 );
1161 assert!(!ising_like.has_sign_problem);
1162 }
1163
1164 #[test]
1165 fn test_local_field_setting() {
1166 let mut hamiltonian =
1167 NonStoquasticHamiltonian::xy_model(3, 1.0, 1.0).expect("Failed to create XY model");
1168 hamiltonian
1169 .set_local_field(0, 0.5)
1170 .expect("Failed to set local field");
1171 hamiltonian
1172 .set_local_field(2, -0.3)
1173 .expect("Failed to set local field");
1174
1175 assert_eq!(hamiltonian.local_fields[0], 0.5);
1176 assert_eq!(hamiltonian.local_fields[1], 0.0);
1177 assert_eq!(hamiltonian.local_fields[2], -0.3);
1178 }
1179
1180 #[test]
1181 fn test_complex_coupling_addition() {
1182 let mut hamiltonian = NonStoquasticHamiltonian::new(4, HamiltonianType::CustomModel);
1183
1184 let coupling = ComplexCoupling {
1185 sites: (0, 2),
1186 strength: NComplex::new(0.5, 0.3),
1187 interaction_type: InteractionType::XY,
1188 };
1189
1190 hamiltonian
1191 .add_complex_coupling(coupling.clone())
1192 .expect("Failed to add complex coupling");
1193 assert_eq!(hamiltonian.complex_couplings.len(), 1);
1194 assert_eq!(hamiltonian.complex_couplings[0].sites, (0, 2));
1195 assert_eq!(
1196 hamiltonian.complex_couplings[0].strength,
1197 NComplex::new(0.5, 0.3)
1198 );
1199 }
1200
1201 #[test]
1202 fn test_matrix_representation_small() {
1203 let hamiltonian =
1204 NonStoquasticHamiltonian::xy_model(2, 1.0, 0.0).expect("Failed to create XY model");
1205 let matrix = hamiltonian
1206 .to_matrix()
1207 .expect("Failed to convert to matrix");
1208
1209 assert_eq!(matrix.len(), 4); assert_eq!(matrix[0].len(), 4);
1211
1212 for i in 0..4 {
1214 for j in 0..4 {
1215 let diff = (matrix[i][j] - matrix[j][i].conj()).norm();
1216 assert!(diff < 1e-10, "Matrix is not Hermitian at ({}, {})", i, j);
1217 }
1218 }
1219 }
1220
1221 #[test]
1222 fn test_quantum_state_creation() {
1223 let state = QuantumState::new(3, 5);
1224 assert_eq!(state.num_qubits, 3);
1225 assert_eq!(state.num_time_slices, 5);
1226 assert_eq!(state.configurations.len(), 5);
1227 assert_eq!(state.configurations[0].len(), 3);
1228 }
1229
1230 #[test]
1231 fn test_xy_to_ising_conversion() {
1232 let xy_hamiltonian =
1233 NonStoquasticHamiltonian::xy_model(3, 1.0, 1.0).expect("Failed to create XY model");
1234 let ising = xy_to_ising_approximation(&xy_hamiltonian)
1235 .expect("Failed to convert XY to Ising approximation");
1236
1237 assert_eq!(ising.num_qubits, 3);
1238
1239 let coupling_01 = ising.get_coupling(0, 1).expect("Failed to get coupling");
1241 assert!(coupling_01.abs() > 1e-10); }
1243
1244 #[test]
1245 fn test_helper_functions() {
1246 let xy_chain = create_xy_chain(4, 1.0, 0.5).expect("Failed to create XY chain");
1247 assert_eq!(xy_chain.num_qubits, 4);
1248 assert!(xy_chain.complex_couplings.len() > 6); let tfxy = create_tfxy_model(3, 1.0, 1.0, 0.5).expect("Failed to create TFXY model");
1251 assert!(tfxy.local_fields.iter().all(|&f| f.abs() > 1e-10)); let triangle =
1254 create_frustrated_xy_triangle(1.0).expect("Failed to create frustrated triangle");
1255 assert_eq!(triangle.num_qubits, 3);
1256 assert_eq!(triangle.complex_couplings.len(), 6); }
1258}