quantrs2_anneal/
non_stoquastic.rs

1//! Non-Stoquastic Hamiltonian Support for Quantum Annealing
2//!
3//! This module implements support for non-stoquastic Hamiltonians in quantum annealing systems.
4//! Non-stoquastic Hamiltonians have positive off-diagonal matrix elements in the computational
5//! basis, which can lead to the sign problem in quantum Monte Carlo simulations but may also
6//! provide quantum advantages for certain optimization problems.
7//!
8//! Key features:
9//! - XY and XYZ spin models
10//! - Complex-weighted coupling terms
11//! - Sign-problem aware quantum Monte Carlo algorithms
12//! - Population annealing with complex weights
13//! - Advanced sampling strategies (cluster algorithms, parallel tempering)
14//! - Integration with stoquastic Hamiltonians
15//! - Quantum advantage detection and analysis
16
17use 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/// Errors that can occur in non-stoquastic operations
30#[derive(Error, Debug)]
31pub enum NonStoquasticError {
32    /// Ising model error
33    #[error("Ising error: {0}")]
34    IsingError(#[from] IsingError),
35
36    /// Invalid Hamiltonian configuration
37    #[error("Invalid Hamiltonian: {0}")]
38    InvalidHamiltonian(String),
39
40    /// Simulation error
41    #[error("Simulation error: {0}")]
42    SimulationError(String),
43
44    /// Sign problem detected
45    #[error("Sign problem: {0}")]
46    SignProblem(String),
47
48    /// Convergence error
49    #[error("Convergence error: {0}")]
50    ConvergenceError(String),
51
52    /// Dimension mismatch
53    #[error("Dimension mismatch: expected {expected}, got {actual}")]
54    DimensionMismatch { expected: usize, actual: usize },
55
56    /// Complex arithmetic error
57    #[error("Complex arithmetic error: {0}")]
58    ComplexArithmeticError(String),
59}
60
61/// Result type for non-stoquastic operations
62pub type NonStoquasticResult<T> = Result<T, NonStoquasticError>;
63
64/// Types of non-stoquastic Hamiltonians
65#[derive(Debug, Clone, PartialEq)]
66pub enum HamiltonianType {
67    /// XY model: H = `Σ(J_x` `σ_x^i` `σ_x^j` + `J_y` `σ_y^i` `σ_y^j`)
68    XYModel { j_x: f64, j_y: f64 },
69
70    /// XYZ (Heisenberg) model: H = `Σ(J_x` `σ_x^i` `σ_x^j` + `J_y` `σ_y^i` `σ_y^j` + `J_z` `σ_z^i` `σ_z^j`)
71    XYZModel { j_x: f64, j_y: f64, j_z: f64 },
72
73    /// Complex-weighted Ising model
74    ComplexIsingModel,
75
76    /// Fermionic Hamiltonian (Jordan-Wigner transformed)
77    FermionicModel,
78
79    /// Custom non-stoquastic model
80    CustomModel,
81
82    /// Mixed stoquastic/non-stoquastic
83    MixedModel { stoquastic_fraction: f64 },
84}
85
86/// Complex coupling term for non-stoquastic interactions
87#[derive(Debug, Clone, PartialEq)]
88pub struct ComplexCoupling {
89    /// Site indices
90    pub sites: (usize, usize),
91    /// Complex coupling strength
92    pub strength: NComplex<f64>,
93    /// Interaction type
94    pub interaction_type: InteractionType,
95}
96
97/// Types of quantum interactions
98#[derive(Debug, Clone, PartialEq)]
99pub enum InteractionType {
100    /// XX interaction (`σ_x^i` `σ_x^j`)
101    XX,
102    /// YY interaction (`σ_y^i` `σ_y^j`)
103    YY,
104    /// ZZ interaction (`σ_z^i` `σ_z^j`)
105    ZZ,
106    /// XY interaction (`σ_x^i` `σ_y^j` + `σ_y^i` `σ_x^j`)
107    XY,
108    /// Complex XY interaction (σ_+^i σ_-^j + σ_-^i σ_+^j)
109    ComplexXY,
110    /// Custom interaction matrix
111    CustomMatrix(Vec<Vec<NComplex<f64>>>),
112}
113
114/// Non-stoquastic Hamiltonian representation
115#[derive(Debug, Clone)]
116pub struct NonStoquasticHamiltonian {
117    /// Number of qubits/spins
118    pub num_qubits: usize,
119
120    /// Hamiltonian type
121    pub hamiltonian_type: HamiltonianType,
122
123    /// Local magnetic field terms (real)
124    pub local_fields: Vec<f64>,
125
126    /// Complex coupling terms
127    pub complex_couplings: Vec<ComplexCoupling>,
128
129    /// Stoquastic (Ising) part for mixed models
130    pub ising_part: Option<IsingModel>,
131
132    /// Global phase factor
133    pub global_phase: NComplex<f64>,
134
135    /// Whether this Hamiltonian has the sign problem
136    pub has_sign_problem: bool,
137}
138
139impl NonStoquasticHamiltonian {
140    /// Create a new non-stoquastic Hamiltonian
141    #[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    /// Create XY model
166    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        // Add XY couplings for nearest neighbors (chain topology)
170        for i in 0..num_qubits - 1 {
171            // X-X coupling
172            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            // Y-Y coupling
179            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    /// Create XYZ (Heisenberg) model
190    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        // Add XYZ couplings for nearest neighbors
194        for i in 0..num_qubits - 1 {
195            // X-X coupling
196            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            // Y-Y coupling
203            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            // Z-Z coupling
210            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    /// Create complex-weighted Ising model
221    #[must_use]
222    pub fn complex_ising_model(num_qubits: usize) -> Self {
223        Self::new(num_qubits, HamiltonianType::ComplexIsingModel)
224    }
225
226    /// Add a complex coupling term
227    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    /// Set local magnetic field
240    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    /// Check if Hamiltonian is stoquastic
253    #[must_use]
254    pub const fn is_stoquastic(&self) -> bool {
255        !self.has_sign_problem
256    }
257
258    /// Estimate sign problem severity
259    #[must_use]
260    pub fn sign_problem_severity(&self) -> f64 {
261        if !self.has_sign_problem {
262            return 0.0;
263        }
264
265        // Estimate based on the magnitude of non-stoquastic terms
266        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    /// Convert to matrix representation (for small systems)
292    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        // Add local field terms
303        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        // Add coupling terms
314        for coupling in &self.complex_couplings {
315            let (i, j) = coupling.sites;
316
317            match coupling.interaction_type {
318                InteractionType::ZZ => {
319                    // Diagonal terms
320                    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                    // Off-diagonal terms
328                    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                    // Off-diagonal terms with imaginary factors
335                    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                    // Simplified handling for other interaction types
345                    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/// Configuration for non-stoquastic quantum Monte Carlo
358#[derive(Debug, Clone)]
359pub struct NonStoquasticQMCConfig {
360    /// Number of Monte Carlo steps
361    pub num_steps: usize,
362    /// Number of thermalization steps
363    pub thermalization_steps: usize,
364    /// Temperature
365    pub temperature: f64,
366    /// Imaginary time step size
367    pub tau: f64,
368    /// Number of time slices
369    pub num_time_slices: usize,
370    /// Population size for population annealing
371    pub population_size: usize,
372    /// Sign problem mitigation strategy
373    pub sign_mitigation: SignMitigationStrategy,
374    /// Random seed
375    pub seed: Option<u64>,
376    /// Measurement interval
377    pub measurement_interval: usize,
378    /// Convergence threshold
379    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/// Strategies for mitigating the sign problem
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub enum SignMitigationStrategy {
402    /// Simple reweighting method
403    ReweightingMethod,
404    /// Constrained path Monte Carlo
405    ConstrainedPath,
406    /// Population annealing with sign handling
407    PopulationAnnealing,
408    /// Meron cluster algorithm
409    MeronClusters,
410    /// Complex Langevin dynamics
411    ComplexLangevin,
412    /// Auxiliary field approach
413    AuxiliaryField,
414}
415
416/// Results from non-stoquastic quantum Monte Carlo simulation
417#[derive(Debug, Clone)]
418pub struct NonStoquasticResults {
419    /// Ground state energy estimate
420    pub ground_state_energy: NComplex<f64>,
421    /// Energy variance
422    pub energy_variance: f64,
423    /// Ground state configuration (if available)
424    pub ground_state: Option<Vec<i8>>,
425    /// Average sign of the wavefunction
426    pub average_sign: NComplex<f64>,
427    /// Sign problem severity
428    pub sign_problem_severity: f64,
429    /// Quantum Monte Carlo statistics
430    pub qmc_statistics: QMCStatistics,
431    /// Simulation time
432    pub simulation_time: Duration,
433    /// Convergence information
434    pub convergence_info: ConvergenceInfo,
435}
436
437/// Quantum Monte Carlo statistics
438#[derive(Debug, Clone)]
439pub struct QMCStatistics {
440    /// Acceptance rate
441    pub acceptance_rate: f64,
442    /// Autocorrelation time
443    pub autocorrelation_time: f64,
444    /// Effective sample size
445    pub effective_sample_size: usize,
446    /// Statistical error estimates
447    pub statistical_errors: HashMap<String, f64>,
448    /// Population size evolution (for population annealing)
449    pub population_evolution: Vec<usize>,
450}
451
452/// Convergence information
453#[derive(Debug, Clone)]
454pub struct ConvergenceInfo {
455    /// Whether simulation converged
456    pub converged: bool,
457    /// Convergence step
458    pub convergence_step: Option<usize>,
459    /// Energy history
460    pub energy_history: Vec<NComplex<f64>>,
461    /// Sign history
462    pub sign_history: Vec<NComplex<f64>>,
463}
464
465/// Non-stoquastic quantum Monte Carlo simulator
466pub struct NonStoquasticSimulator {
467    /// Configuration
468    config: NonStoquasticQMCConfig,
469    /// Random number generator
470    rng: ChaCha8Rng,
471    /// Current quantum state
472    current_state: QuantumState,
473    /// Hamiltonian being simulated
474    hamiltonian: NonStoquasticHamiltonian,
475}
476
477/// Quantum state representation for non-stoquastic systems
478#[derive(Debug, Clone)]
479pub struct QuantumState {
480    /// Number of qubits
481    pub num_qubits: usize,
482    /// Number of time slices
483    pub num_time_slices: usize,
484    /// State configurations for each time slice
485    pub configurations: Vec<Vec<i8>>,
486    /// Complex amplitudes (for small systems)
487    pub amplitudes: Option<Vec<NComplex<f64>>>,
488    /// Current energy
489    pub energy: NComplex<f64>,
490    /// Wavefunction sign
491    pub sign: NComplex<f64>,
492}
493
494impl QuantumState {
495    /// Create a new quantum state
496    #[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    /// Initialize with random configuration
511    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    /// Calculate overlap with another state
520    #[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    /// Create a new non-stoquastic simulator
535    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    /// Run quantum Monte Carlo simulation
555    pub fn simulate(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
556        let start_time = Instant::now();
557
558        // Initialize state
559        self.current_state.initialize_random(&mut self.rng);
560
561        // Choose simulation method based on sign problem severity
562        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    /// Simulate with sign problem handling
577    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    /// Simulate stoquastic-like systems
587    fn simulate_stoquastic_like(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
588        self.basic_quantum_monte_carlo()
589    }
590
591    /// Basic quantum Monte Carlo for nearly stoquastic systems
592    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        // Thermalization
599        for _ in 0..self.config.thermalization_steps {
600            self.propose_and_accept_move()?;
601        }
602
603        // Measurement phase
604        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        // Calculate results
621        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, // Simplified
655                effective_sample_size: energy_samples.len(),
656                statistical_errors: HashMap::new(),
657                population_evolution: Vec::new(),
658            },
659            simulation_time: Duration::from_secs(0), // Will be filled in
660            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    /// Reweighting method for sign problem
670    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, // Simplified
702            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, // Simplified
707                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    /// Population annealing simulation
723    fn population_annealing_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
724        let mut population = Vec::new();
725        let mut weights = Vec::new();
726
727        // Initialize population
728        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        // Population annealing steps
737        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            // Update weights
744            for (i, state) in population.iter().enumerate() {
745                let energy = self.calculate_state_energy(state)?;
746                weights[i] = (-beta * energy).exp();
747            }
748
749            // Resample population
750            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        // Calculate final results
757        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    /// Constrained path simulation
799    fn constrained_path_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
800        // Simplified constrained path method
801        // In practice, this would implement sophisticated path constraints
802        self.basic_complex_simulation()
803    }
804
805    /// Basic complex simulation
806    fn basic_complex_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
807        // Simplified complex simulation
808        self.basic_quantum_monte_carlo()
809    }
810
811    /// Propose and accept/reject a Monte Carlo move
812    fn propose_and_accept_move(&mut self) -> NonStoquasticResult<bool> {
813        // Choose random time slice and spin
814        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        // Calculate energy change
818        let energy_before = self.calculate_local_energy(time_slice, spin_site)?;
819
820        // Flip spin
821        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        // Metropolis acceptance criterion
827        let accept_prob = (-energy_diff.re / self.config.temperature).exp().min(1.0);
828
829        if self.rng.gen::<f64>() < accept_prob {
830            // Accept move
831            Ok(true)
832        } else {
833            // Reject move - flip spin back
834            self.current_state.configurations[time_slice][spin_site] *= -1;
835            Ok(false)
836        }
837    }
838
839    /// Calculate local energy contribution
840    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        // Local field contribution
848        let spin = f64::from(self.current_state.configurations[time_slice][site]);
849        energy += self.hamiltonian.local_fields[site] * spin;
850
851        // Coupling contributions
852        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                        // For Monte Carlo, treat as effective ZZ with complex weight
864                        energy += coupling.strength * spin_i * spin_j * 0.5;
865                    }
866                    _ => {
867                        // Simplified treatment
868                        energy += coupling.strength * spin_i * spin_j * 0.25;
869                    }
870                }
871            }
872        }
873
874        Ok(energy)
875    }
876
877    /// Calculate total energy of current state
878    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    /// Calculate energy of a specific state
891    fn calculate_state_energy(&self, state: &QuantumState) -> NonStoquasticResult<NComplex<f64>> {
892        let mut energy = NComplex::new(0.0, 0.0);
893
894        // Local fields
895        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        // Couplings
903        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; // Simplified
915                    }
916                }
917            }
918        }
919
920        Ok(energy / state.num_time_slices as f64)
921    }
922
923    /// Calculate wavefunction sign
924    fn calculate_sign(&self) -> NonStoquasticResult<NComplex<f64>> {
925        // Simplified sign calculation
926        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; // Simplified phase accumulation
938                        }
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    /// Calculate Monte Carlo weight
952    fn calculate_weight(&self) -> NonStoquasticResult<NComplex<f64>> {
953        // For reweighting method
954        let sign = self.calculate_sign()?;
955        Ok(sign)
956    }
957
958    /// Resample population for population annealing
959    fn resample_population(
960        &mut self,
961        mut population: Vec<QuantumState>,
962        weights: &[NComplex<f64>],
963    ) -> NonStoquasticResult<Vec<QuantumState>> {
964        // Normalize weights
965        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        // Systematic resampling
973        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/// Utility functions for non-stoquastic systems
998
999/// Detect whether a Hamiltonian is stoquastic
1000#[must_use]
1001pub const fn is_hamiltonian_stoquastic(hamiltonian: &NonStoquasticHamiltonian) -> bool {
1002    hamiltonian.is_stoquastic()
1003}
1004
1005/// Convert XY model to effective Ising model (approximation)
1006pub 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    // Convert local fields directly
1021    for (site, &field) in xy_hamiltonian.local_fields.iter().enumerate() {
1022        ising.set_bias(site, field)?;
1023    }
1024
1025    // Convert XY couplings to effective ZZ couplings
1026    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                // XY couplings contribute to effective ferromagnetic coupling
1035                -coupling.strength.re.abs() * 0.5
1036            }
1037            InteractionType::ZZ => coupling.strength.re,
1038            _ => coupling.strength.re * 0.25, // Simplified
1039        };
1040
1041        *coupling_map.entry(key).or_insert(0.0) += effective_strength;
1042    }
1043
1044    // Set effective couplings
1045    for ((i, j), strength) in coupling_map {
1046        ising.set_coupling(i, j, strength)?;
1047    }
1048
1049    Ok(ising)
1050}
1051
1052/// Create standard non-stoquastic test problems
1053
1054/// Create XY chain with periodic boundary conditions
1055pub 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    // Add periodic boundary condition
1063    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
1080/// Create transverse field XY model
1081pub 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    // Add transverse field
1090    for site in 0..num_qubits {
1091        hamiltonian.set_local_field(site, h_z)?;
1092    }
1093
1094    Ok(hamiltonian)
1095}
1096
1097/// Create frustrated XY model on triangular lattice
1098pub 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    // Add all pairwise XY interactions (frustrated triangle)
1108    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); // 3 XX + 3 YY couplings
1138    }
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); // 2 XX + 2 YY + 2 ZZ couplings
1147    }
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); // 2^2 = 4 states
1210        assert_eq!(matrix[0].len(), 4);
1211
1212        // Check that matrix is Hermitian
1213        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        // Check that couplings were converted
1240        let coupling_01 = ising.get_coupling(0, 1).expect("Failed to get coupling");
1241        assert!(coupling_01.abs() > 1e-10); // Should have non-zero coupling
1242    }
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); // Should have periodic boundary
1249
1250        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)); // All sites should have fields
1252
1253        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); // 3 pairs × 2 (XX, YY)
1257    }
1258}