use scirs2_core::random::prelude::*;
use scirs2_core::random::ChaCha8Rng;
use scirs2_core::random::{Rng, SeedableRng};
use scirs2_core::Complex as NComplex;
use std::collections::HashMap;
use std::f64::consts::PI;
use std::time::{Duration, Instant};
use thiserror::Error;
use crate::ising::{IsingError, IsingModel};
use crate::simulator::{AnnealingParams, AnnealingSolution};
#[derive(Error, Debug)]
pub enum NonStoquasticError {
#[error("Ising error: {0}")]
IsingError(#[from] IsingError),
#[error("Invalid Hamiltonian: {0}")]
InvalidHamiltonian(String),
#[error("Simulation error: {0}")]
SimulationError(String),
#[error("Sign problem: {0}")]
SignProblem(String),
#[error("Convergence error: {0}")]
ConvergenceError(String),
#[error("Dimension mismatch: expected {expected}, got {actual}")]
DimensionMismatch { expected: usize, actual: usize },
#[error("Complex arithmetic error: {0}")]
ComplexArithmeticError(String),
}
pub type NonStoquasticResult<T> = Result<T, NonStoquasticError>;
#[derive(Debug, Clone, PartialEq)]
pub enum HamiltonianType {
XYModel { j_x: f64, j_y: f64 },
XYZModel { j_x: f64, j_y: f64, j_z: f64 },
ComplexIsingModel,
FermionicModel,
CustomModel,
MixedModel { stoquastic_fraction: f64 },
}
#[derive(Debug, Clone, PartialEq)]
pub struct ComplexCoupling {
pub sites: (usize, usize),
pub strength: NComplex<f64>,
pub interaction_type: InteractionType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum InteractionType {
XX,
YY,
ZZ,
XY,
ComplexXY,
CustomMatrix(Vec<Vec<NComplex<f64>>>),
}
#[derive(Debug, Clone)]
pub struct NonStoquasticHamiltonian {
pub num_qubits: usize,
pub hamiltonian_type: HamiltonianType,
pub local_fields: Vec<f64>,
pub complex_couplings: Vec<ComplexCoupling>,
pub ising_part: Option<IsingModel>,
pub global_phase: NComplex<f64>,
pub has_sign_problem: bool,
}
impl NonStoquasticHamiltonian {
#[must_use]
pub fn new(num_qubits: usize, hamiltonian_type: HamiltonianType) -> Self {
let has_sign_problem = match hamiltonian_type {
HamiltonianType::XYModel { .. }
| HamiltonianType::XYZModel { .. }
| HamiltonianType::ComplexIsingModel
| HamiltonianType::FermionicModel
| HamiltonianType::CustomModel => true,
HamiltonianType::MixedModel {
stoquastic_fraction,
} => stoquastic_fraction < 1.0,
};
Self {
num_qubits,
hamiltonian_type,
local_fields: vec![0.0; num_qubits],
complex_couplings: Vec::new(),
ising_part: None,
global_phase: NComplex::new(1.0, 0.0),
has_sign_problem,
}
}
pub fn xy_model(num_qubits: usize, j_x: f64, j_y: f64) -> NonStoquasticResult<Self> {
let mut hamiltonian = Self::new(num_qubits, HamiltonianType::XYModel { j_x, j_y });
for i in 0..num_qubits - 1 {
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, i + 1),
strength: NComplex::new(j_x, 0.0),
interaction_type: InteractionType::XX,
})?;
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, i + 1),
strength: NComplex::new(j_y, 0.0),
interaction_type: InteractionType::YY,
})?;
}
Ok(hamiltonian)
}
pub fn xyz_model(num_qubits: usize, j_x: f64, j_y: f64, j_z: f64) -> NonStoquasticResult<Self> {
let mut hamiltonian = Self::new(num_qubits, HamiltonianType::XYZModel { j_x, j_y, j_z });
for i in 0..num_qubits - 1 {
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, i + 1),
strength: NComplex::new(j_x, 0.0),
interaction_type: InteractionType::XX,
})?;
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, i + 1),
strength: NComplex::new(j_y, 0.0),
interaction_type: InteractionType::YY,
})?;
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, i + 1),
strength: NComplex::new(j_z, 0.0),
interaction_type: InteractionType::ZZ,
})?;
}
Ok(hamiltonian)
}
#[must_use]
pub fn complex_ising_model(num_qubits: usize) -> Self {
Self::new(num_qubits, HamiltonianType::ComplexIsingModel)
}
pub fn add_complex_coupling(&mut self, coupling: ComplexCoupling) -> NonStoquasticResult<()> {
if coupling.sites.0 >= self.num_qubits || coupling.sites.1 >= self.num_qubits {
return Err(NonStoquasticError::InvalidHamiltonian(format!(
"Invalid coupling sites: ({}, {}) for {} qubits",
coupling.sites.0, coupling.sites.1, self.num_qubits
)));
}
self.complex_couplings.push(coupling);
Ok(())
}
pub fn set_local_field(&mut self, site: usize, field: f64) -> NonStoquasticResult<()> {
if site >= self.num_qubits {
return Err(NonStoquasticError::InvalidHamiltonian(format!(
"Invalid site index: {} for {} qubits",
site, self.num_qubits
)));
}
self.local_fields[site] = field;
Ok(())
}
#[must_use]
pub const fn is_stoquastic(&self) -> bool {
!self.has_sign_problem
}
#[must_use]
pub fn sign_problem_severity(&self) -> f64 {
if !self.has_sign_problem {
return 0.0;
}
let mut non_stoquastic_weight = 0.0;
let mut total_weight = 0.0;
for coupling in &self.complex_couplings {
let magnitude = coupling.strength.norm();
total_weight += magnitude;
match coupling.interaction_type {
InteractionType::XX
| InteractionType::YY
| InteractionType::XY
| InteractionType::ComplexXY => {
non_stoquastic_weight += magnitude;
}
_ => {}
}
}
if total_weight > 0.0 {
non_stoquastic_weight / total_weight
} else {
0.0
}
}
pub fn to_matrix(&self) -> NonStoquasticResult<Vec<Vec<NComplex<f64>>>> {
if self.num_qubits > 12 {
return Err(NonStoquasticError::SimulationError(
"Matrix representation only supported for ≤12 qubits".to_string(),
));
}
let dim = 1 << self.num_qubits;
let mut matrix = vec![vec![NComplex::new(0.0, 0.0); dim]; dim];
for site in 0..self.num_qubits {
let field = self.local_fields[site];
if field.abs() > 1e-12 {
for state in 0..dim {
let spin = if (state >> site) & 1 == 1 { 1.0 } else { -1.0 };
matrix[state][state] += NComplex::new(field * spin, 0.0);
}
}
}
for coupling in &self.complex_couplings {
let (i, j) = coupling.sites;
match coupling.interaction_type {
InteractionType::ZZ => {
for state in 0..dim {
let spin_i = if (state >> i) & 1 == 1 { 1.0 } else { -1.0 };
let spin_j = if (state >> j) & 1 == 1 { 1.0 } else { -1.0 };
matrix[state][state] += coupling.strength * spin_i * spin_j;
}
}
InteractionType::XX => {
for state in 0..dim {
let flipped = state ^ (1 << i) ^ (1 << j);
matrix[state][flipped] += coupling.strength;
}
}
InteractionType::YY => {
for state in 0..dim {
let spin_i = if (state >> i) & 1 == 1 { 1.0 } else { -1.0 };
let spin_j = if (state >> j) & 1 == 1 { 1.0 } else { -1.0 };
let flipped = state ^ (1 << i) ^ (1 << j);
let phase = NComplex::new(0.0, spin_i * spin_j);
matrix[state][flipped] += coupling.strength * phase;
}
}
_ => {
for state in 0..dim {
let flipped = state ^ (1 << i) ^ (1 << j);
matrix[state][flipped] += coupling.strength * 0.5;
}
}
}
}
Ok(matrix)
}
}
#[derive(Debug, Clone)]
pub struct NonStoquasticQMCConfig {
pub num_steps: usize,
pub thermalization_steps: usize,
pub temperature: f64,
pub tau: f64,
pub num_time_slices: usize,
pub population_size: usize,
pub sign_mitigation: SignMitigationStrategy,
pub seed: Option<u64>,
pub measurement_interval: usize,
pub convergence_threshold: f64,
}
impl Default for NonStoquasticQMCConfig {
fn default() -> Self {
Self {
num_steps: 10_000,
thermalization_steps: 1000,
temperature: 1.0,
tau: 0.1,
num_time_slices: 10,
population_size: 1000,
sign_mitigation: SignMitigationStrategy::ReweightingMethod,
seed: None,
measurement_interval: 10,
convergence_threshold: 1e-6,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SignMitigationStrategy {
ReweightingMethod,
ConstrainedPath,
PopulationAnnealing,
MeronClusters,
ComplexLangevin,
AuxiliaryField,
}
#[derive(Debug, Clone)]
pub struct NonStoquasticResults {
pub ground_state_energy: NComplex<f64>,
pub energy_variance: f64,
pub ground_state: Option<Vec<i8>>,
pub average_sign: NComplex<f64>,
pub sign_problem_severity: f64,
pub qmc_statistics: QMCStatistics,
pub simulation_time: Duration,
pub convergence_info: ConvergenceInfo,
}
#[derive(Debug, Clone)]
pub struct QMCStatistics {
pub acceptance_rate: f64,
pub autocorrelation_time: f64,
pub effective_sample_size: usize,
pub statistical_errors: HashMap<String, f64>,
pub population_evolution: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct ConvergenceInfo {
pub converged: bool,
pub convergence_step: Option<usize>,
pub energy_history: Vec<NComplex<f64>>,
pub sign_history: Vec<NComplex<f64>>,
}
pub struct NonStoquasticSimulator {
config: NonStoquasticQMCConfig,
rng: ChaCha8Rng,
current_state: QuantumState,
hamiltonian: NonStoquasticHamiltonian,
}
#[derive(Debug, Clone)]
pub struct QuantumState {
pub num_qubits: usize,
pub num_time_slices: usize,
pub configurations: Vec<Vec<i8>>,
pub amplitudes: Option<Vec<NComplex<f64>>>,
pub energy: NComplex<f64>,
pub sign: NComplex<f64>,
}
impl QuantumState {
#[must_use]
pub fn new(num_qubits: usize, num_time_slices: usize) -> Self {
let configurations = vec![vec![1; num_qubits]; num_time_slices];
Self {
num_qubits,
num_time_slices,
configurations,
amplitudes: None,
energy: NComplex::new(0.0, 0.0),
sign: NComplex::new(1.0, 0.0),
}
}
pub fn initialize_random(&mut self, rng: &mut ChaCha8Rng) {
for time_slice in &mut self.configurations {
for spin in time_slice {
*spin = if rng.random_bool(0.5) { 1 } else { -1 };
}
}
}
#[must_use]
pub fn overlap(&self, other: &Self) -> NComplex<f64> {
if let (Some(ref amp1), Some(ref amp2)) = (&self.amplitudes, &other.amplitudes) {
amp1.iter()
.zip(amp2.iter())
.map(|(a, b)| a.conj() * b)
.sum()
} else {
NComplex::new(0.0, 0.0)
}
}
}
impl NonStoquasticSimulator {
pub fn new(
hamiltonian: NonStoquasticHamiltonian,
config: NonStoquasticQMCConfig,
) -> NonStoquasticResult<Self> {
let rng = match config.seed {
Some(seed) => ChaCha8Rng::seed_from_u64(seed),
None => ChaCha8Rng::seed_from_u64(thread_rng().random()),
};
let current_state = QuantumState::new(hamiltonian.num_qubits, config.num_time_slices);
Ok(Self {
config,
rng,
current_state,
hamiltonian,
})
}
pub fn simulate(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
let start_time = Instant::now();
self.current_state.initialize_random(&mut self.rng);
let result = if self.hamiltonian.sign_problem_severity() > 0.1 {
self.simulate_with_sign_problem()?
} else {
self.simulate_stoquastic_like()?
};
let simulation_time = start_time.elapsed();
Ok(NonStoquasticResults {
simulation_time,
..result
})
}
fn simulate_with_sign_problem(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
match self.config.sign_mitigation {
SignMitigationStrategy::ReweightingMethod => self.reweighting_simulation(),
SignMitigationStrategy::PopulationAnnealing => self.population_annealing_simulation(),
SignMitigationStrategy::ConstrainedPath => self.constrained_path_simulation(),
_ => self.basic_complex_simulation(),
}
}
fn simulate_stoquastic_like(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
self.basic_quantum_monte_carlo()
}
fn basic_quantum_monte_carlo(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
let mut energy_samples = Vec::new();
let mut sign_samples = Vec::new();
let mut acceptance_count = 0;
let mut total_proposals = 0;
for _ in 0..self.config.thermalization_steps {
self.propose_and_accept_move()?;
}
for step in 0..self.config.num_steps {
total_proposals += 1;
if self.propose_and_accept_move()? {
acceptance_count += 1;
}
if step % self.config.measurement_interval == 0 {
let energy = self.calculate_energy()?;
let sign = self.calculate_sign()?;
energy_samples.push(energy);
sign_samples.push(sign);
}
}
let ground_state_energy = if energy_samples.is_empty() {
NComplex::new(0.0, 0.0)
} else {
energy_samples.iter().sum::<NComplex<f64>>() / energy_samples.len() as f64
};
let average_sign = if sign_samples.is_empty() {
NComplex::new(1.0, 0.0)
} else {
sign_samples.iter().sum::<NComplex<f64>>() / sign_samples.len() as f64
};
let energy_variance = if energy_samples.len() > 1 {
let mean = ground_state_energy;
energy_samples
.iter()
.map(|e| (e - mean).norm_sqr())
.sum::<f64>()
/ (energy_samples.len() - 1) as f64
} else {
0.0
};
let acceptance_rate = f64::from(acceptance_count) / f64::from(total_proposals);
Ok(NonStoquasticResults {
ground_state_energy,
energy_variance,
ground_state: Some(self.current_state.configurations[0].clone()),
average_sign,
sign_problem_severity: self.hamiltonian.sign_problem_severity(),
qmc_statistics: QMCStatistics {
acceptance_rate,
autocorrelation_time: 1.0, effective_sample_size: energy_samples.len(),
statistical_errors: HashMap::new(),
population_evolution: Vec::new(),
},
simulation_time: Duration::from_secs(0), convergence_info: ConvergenceInfo {
converged: true,
convergence_step: Some(self.config.num_steps),
energy_history: energy_samples,
sign_history: sign_samples,
},
})
}
fn reweighting_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
let mut weighted_energy = NComplex::new(0.0, 0.0);
let mut total_weight = NComplex::new(0.0, 0.0);
let mut sign_samples = Vec::new();
for _ in 0..self.config.num_steps {
self.propose_and_accept_move()?;
let energy = self.calculate_energy()?;
let weight = self.calculate_weight()?;
let sign = self.calculate_sign()?;
weighted_energy += energy * weight;
total_weight += weight;
sign_samples.push(sign);
}
let ground_state_energy = if total_weight.norm() > 1e-12 {
weighted_energy / total_weight
} else {
NComplex::new(0.0, 0.0)
};
let average_sign = if sign_samples.is_empty() {
NComplex::new(1.0, 0.0)
} else {
sign_samples.iter().sum::<NComplex<f64>>() / sign_samples.len() as f64
};
Ok(NonStoquasticResults {
ground_state_energy,
energy_variance: 0.0, ground_state: Some(self.current_state.configurations[0].clone()),
average_sign,
sign_problem_severity: self.hamiltonian.sign_problem_severity(),
qmc_statistics: QMCStatistics {
acceptance_rate: 0.5, autocorrelation_time: 1.0,
effective_sample_size: self.config.num_steps,
statistical_errors: HashMap::new(),
population_evolution: Vec::new(),
},
simulation_time: Duration::from_secs(0),
convergence_info: ConvergenceInfo {
converged: average_sign.norm() > 0.1,
convergence_step: Some(self.config.num_steps),
energy_history: Vec::new(),
sign_history: sign_samples,
},
})
}
fn population_annealing_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
let mut population = Vec::new();
let mut weights = Vec::new();
for _ in 0..self.config.population_size {
let mut state =
QuantumState::new(self.hamiltonian.num_qubits, self.config.num_time_slices);
state.initialize_random(&mut self.rng);
population.push(state);
weights.push(NComplex::new(1.0, 0.0));
}
let num_annealing_steps = 10;
let mut population_evolution = Vec::new();
for step in 0..num_annealing_steps {
let beta = (step as f64 / (num_annealing_steps - 1) as f64) / self.config.temperature;
for (i, state) in population.iter().enumerate() {
let energy = self.calculate_state_energy(state)?;
weights[i] = (-beta * energy).exp();
}
population = self.resample_population(population, &weights)?;
weights.fill(NComplex::new(1.0, 0.0));
population_evolution.push(population.len());
}
let final_energies: Vec<NComplex<f64>> = population
.iter()
.map(|state| {
self.calculate_state_energy(state)
.unwrap_or(NComplex::new(0.0, 0.0))
})
.collect();
let ground_state_energy = final_energies
.iter()
.min_by(|a, b| {
a.norm()
.partial_cmp(&b.norm())
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
.unwrap_or(NComplex::new(0.0, 0.0));
Ok(NonStoquasticResults {
ground_state_energy,
energy_variance: 0.0,
ground_state: population.first().map(|s| s.configurations[0].clone()),
average_sign: NComplex::new(1.0, 0.0),
sign_problem_severity: self.hamiltonian.sign_problem_severity(),
qmc_statistics: QMCStatistics {
acceptance_rate: 0.7,
autocorrelation_time: 1.0,
effective_sample_size: population.len(),
statistical_errors: HashMap::new(),
population_evolution,
},
simulation_time: Duration::from_secs(0),
convergence_info: ConvergenceInfo {
converged: true,
convergence_step: Some(num_annealing_steps),
energy_history: final_energies,
sign_history: Vec::new(),
},
})
}
fn constrained_path_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
self.basic_complex_simulation()
}
fn basic_complex_simulation(&mut self) -> NonStoquasticResult<NonStoquasticResults> {
self.basic_quantum_monte_carlo()
}
fn propose_and_accept_move(&mut self) -> NonStoquasticResult<bool> {
let time_slice = self.rng.random_range(0..self.config.num_time_slices);
let spin_site = self.rng.random_range(0..self.hamiltonian.num_qubits);
let energy_before = self.calculate_local_energy(time_slice, spin_site)?;
self.current_state.configurations[time_slice][spin_site] *= -1;
let energy_after = self.calculate_local_energy(time_slice, spin_site)?;
let energy_diff = energy_after - energy_before;
let accept_prob = (-energy_diff.re / self.config.temperature).exp().min(1.0);
if self.rng.random::<f64>() < accept_prob {
Ok(true)
} else {
self.current_state.configurations[time_slice][spin_site] *= -1;
Ok(false)
}
}
fn calculate_local_energy(
&self,
time_slice: usize,
site: usize,
) -> NonStoquasticResult<NComplex<f64>> {
let mut energy = NComplex::new(0.0, 0.0);
let spin = f64::from(self.current_state.configurations[time_slice][site]);
energy += self.hamiltonian.local_fields[site] * spin;
for coupling in &self.hamiltonian.complex_couplings {
if coupling.sites.0 == site || coupling.sites.1 == site {
let (i, j) = coupling.sites;
let spin_i = f64::from(self.current_state.configurations[time_slice][i]);
let spin_j = f64::from(self.current_state.configurations[time_slice][j]);
match coupling.interaction_type {
InteractionType::ZZ => {
energy += coupling.strength * spin_i * spin_j;
}
InteractionType::XX | InteractionType::YY => {
energy += coupling.strength * spin_i * spin_j * 0.5;
}
_ => {
energy += coupling.strength * spin_i * spin_j * 0.25;
}
}
}
}
Ok(energy)
}
fn calculate_energy(&self) -> NonStoquasticResult<NComplex<f64>> {
let mut total_energy = NComplex::new(0.0, 0.0);
for time_slice in 0..self.config.num_time_slices {
for site in 0..self.hamiltonian.num_qubits {
total_energy += self.calculate_local_energy(time_slice, site)?;
}
}
Ok(total_energy / self.config.num_time_slices as f64)
}
fn calculate_state_energy(&self, state: &QuantumState) -> NonStoquasticResult<NComplex<f64>> {
let mut energy = NComplex::new(0.0, 0.0);
for (site, &field) in self.hamiltonian.local_fields.iter().enumerate() {
for time_slice in 0..state.num_time_slices {
let spin = f64::from(state.configurations[time_slice][site]);
energy += field * spin;
}
}
for coupling in &self.hamiltonian.complex_couplings {
let (i, j) = coupling.sites;
for time_slice in 0..state.num_time_slices {
let spin_i = f64::from(state.configurations[time_slice][i]);
let spin_j = f64::from(state.configurations[time_slice][j]);
match coupling.interaction_type {
InteractionType::ZZ => {
energy += coupling.strength * spin_i * spin_j;
}
_ => {
energy += coupling.strength * spin_i * spin_j * 0.5; }
}
}
}
Ok(energy / state.num_time_slices as f64)
}
fn calculate_sign(&self) -> NonStoquasticResult<NComplex<f64>> {
let sign = if let HamiltonianType::XYModel { .. } = self.hamiltonian.hamiltonian_type {
let mut phase = 0.0;
for coupling in &self.hamiltonian.complex_couplings {
if matches!(coupling.interaction_type, InteractionType::YY) {
let (i, j) = coupling.sites;
for time_slice in 0..self.config.num_time_slices {
let spin_i = self.current_state.configurations[time_slice][i];
let spin_j = self.current_state.configurations[time_slice][j];
if spin_i != spin_j {
phase += PI / 4.0; }
}
}
}
NComplex::new(phase.cos(), phase.sin())
} else {
NComplex::new(1.0, 0.0)
};
Ok(sign)
}
fn calculate_weight(&self) -> NonStoquasticResult<NComplex<f64>> {
let sign = self.calculate_sign()?;
Ok(sign)
}
fn resample_population(
&mut self,
mut population: Vec<QuantumState>,
weights: &[NComplex<f64>],
) -> NonStoquasticResult<Vec<QuantumState>> {
let total_weight: f64 = weights.iter().map(|w| w.norm()).sum();
if total_weight < 1e-12 {
return Ok(population);
}
let probabilities: Vec<f64> = weights.iter().map(|w| w.norm() / total_weight).collect();
let mut new_population = Vec::new();
let n = population.len();
let step = 1.0 / n as f64;
let mut cumsum = 0.0;
let offset = self.rng.random::<f64>() * step;
let mut i = 0;
for j in 0..n {
let target = (j as f64).mul_add(step, offset);
while cumsum < target && i < probabilities.len() {
cumsum += probabilities[i];
i += 1;
}
if i > 0 {
new_population.push(population[(i - 1).min(population.len() - 1)].clone());
}
}
Ok(new_population)
}
}
#[must_use]
pub const fn is_hamiltonian_stoquastic(hamiltonian: &NonStoquasticHamiltonian) -> bool {
hamiltonian.is_stoquastic()
}
pub fn xy_to_ising_approximation(
xy_hamiltonian: &NonStoquasticHamiltonian,
) -> NonStoquasticResult<IsingModel> {
if !matches!(
xy_hamiltonian.hamiltonian_type,
HamiltonianType::XYModel { .. }
) {
return Err(NonStoquasticError::InvalidHamiltonian(
"Expected XY model".to_string(),
));
}
let mut ising = IsingModel::new(xy_hamiltonian.num_qubits);
for (site, &field) in xy_hamiltonian.local_fields.iter().enumerate() {
ising.set_bias(site, field)?;
}
let mut coupling_map: HashMap<(usize, usize), f64> = HashMap::new();
for coupling in &xy_hamiltonian.complex_couplings {
let (i, j) = coupling.sites;
let key = if i < j { (i, j) } else { (j, i) };
let effective_strength = match coupling.interaction_type {
InteractionType::XX | InteractionType::YY => {
-coupling.strength.re.abs() * 0.5
}
InteractionType::ZZ => coupling.strength.re,
_ => coupling.strength.re * 0.25, };
*coupling_map.entry(key).or_insert(0.0) += effective_strength;
}
for ((i, j), strength) in coupling_map {
ising.set_coupling(i, j, strength)?;
}
Ok(ising)
}
pub fn create_xy_chain(
num_qubits: usize,
j_x: f64,
j_y: f64,
) -> NonStoquasticResult<NonStoquasticHamiltonian> {
let mut hamiltonian = NonStoquasticHamiltonian::xy_model(num_qubits, j_x, j_y)?;
if num_qubits > 2 {
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (num_qubits - 1, 0),
strength: NComplex::new(j_x, 0.0),
interaction_type: InteractionType::XX,
})?;
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (num_qubits - 1, 0),
strength: NComplex::new(j_y, 0.0),
interaction_type: InteractionType::YY,
})?;
}
Ok(hamiltonian)
}
pub fn create_tfxy_model(
num_qubits: usize,
j_x: f64,
j_y: f64,
h_z: f64,
) -> NonStoquasticResult<NonStoquasticHamiltonian> {
let mut hamiltonian = NonStoquasticHamiltonian::xy_model(num_qubits, j_x, j_y)?;
for site in 0..num_qubits {
hamiltonian.set_local_field(site, h_z)?;
}
Ok(hamiltonian)
}
pub fn create_frustrated_xy_triangle(j_xy: f64) -> NonStoquasticResult<NonStoquasticHamiltonian> {
let mut hamiltonian = NonStoquasticHamiltonian::new(
3,
HamiltonianType::XYModel {
j_x: j_xy,
j_y: j_xy,
},
);
for i in 0..3 {
for j in (i + 1)..3 {
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, j),
strength: NComplex::new(j_xy, 0.0),
interaction_type: InteractionType::XX,
})?;
hamiltonian.add_complex_coupling(ComplexCoupling {
sites: (i, j),
strength: NComplex::new(j_xy, 0.0),
interaction_type: InteractionType::YY,
})?;
}
}
Ok(hamiltonian)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xy_model_creation() {
let hamiltonian =
NonStoquasticHamiltonian::xy_model(4, 1.0, 0.5).expect("Failed to create XY model");
assert_eq!(hamiltonian.num_qubits, 4);
assert!(hamiltonian.has_sign_problem);
assert_eq!(hamiltonian.complex_couplings.len(), 6); }
#[test]
fn test_xyz_model_creation() {
let hamiltonian = NonStoquasticHamiltonian::xyz_model(3, 1.0, 1.0, 0.5)
.expect("Failed to create XYZ model");
assert_eq!(hamiltonian.num_qubits, 3);
assert!(hamiltonian.has_sign_problem);
assert_eq!(hamiltonian.complex_couplings.len(), 6); }
#[test]
fn test_sign_problem_detection() {
let xy_hamiltonian =
NonStoquasticHamiltonian::xy_model(4, 1.0, 1.0).expect("Failed to create XY model");
assert!(xy_hamiltonian.sign_problem_severity() > 0.0);
let ising_like = NonStoquasticHamiltonian::new(
4,
HamiltonianType::MixedModel {
stoquastic_fraction: 1.0,
},
);
assert!(!ising_like.has_sign_problem);
}
#[test]
fn test_local_field_setting() {
let mut hamiltonian =
NonStoquasticHamiltonian::xy_model(3, 1.0, 1.0).expect("Failed to create XY model");
hamiltonian
.set_local_field(0, 0.5)
.expect("Failed to set local field");
hamiltonian
.set_local_field(2, -0.3)
.expect("Failed to set local field");
assert_eq!(hamiltonian.local_fields[0], 0.5);
assert_eq!(hamiltonian.local_fields[1], 0.0);
assert_eq!(hamiltonian.local_fields[2], -0.3);
}
#[test]
fn test_complex_coupling_addition() {
let mut hamiltonian = NonStoquasticHamiltonian::new(4, HamiltonianType::CustomModel);
let coupling = ComplexCoupling {
sites: (0, 2),
strength: NComplex::new(0.5, 0.3),
interaction_type: InteractionType::XY,
};
hamiltonian
.add_complex_coupling(coupling.clone())
.expect("Failed to add complex coupling");
assert_eq!(hamiltonian.complex_couplings.len(), 1);
assert_eq!(hamiltonian.complex_couplings[0].sites, (0, 2));
assert_eq!(
hamiltonian.complex_couplings[0].strength,
NComplex::new(0.5, 0.3)
);
}
#[test]
fn test_matrix_representation_small() {
let hamiltonian =
NonStoquasticHamiltonian::xy_model(2, 1.0, 0.0).expect("Failed to create XY model");
let matrix = hamiltonian
.to_matrix()
.expect("Failed to convert to matrix");
assert_eq!(matrix.len(), 4); assert_eq!(matrix[0].len(), 4);
for i in 0..4 {
for j in 0..4 {
let diff = (matrix[i][j] - matrix[j][i].conj()).norm();
assert!(diff < 1e-10, "Matrix is not Hermitian at ({}, {})", i, j);
}
}
}
#[test]
fn test_quantum_state_creation() {
let state = QuantumState::new(3, 5);
assert_eq!(state.num_qubits, 3);
assert_eq!(state.num_time_slices, 5);
assert_eq!(state.configurations.len(), 5);
assert_eq!(state.configurations[0].len(), 3);
}
#[test]
fn test_xy_to_ising_conversion() {
let xy_hamiltonian =
NonStoquasticHamiltonian::xy_model(3, 1.0, 1.0).expect("Failed to create XY model");
let ising = xy_to_ising_approximation(&xy_hamiltonian)
.expect("Failed to convert XY to Ising approximation");
assert_eq!(ising.num_qubits, 3);
let coupling_01 = ising.get_coupling(0, 1).expect("Failed to get coupling");
assert!(coupling_01.abs() > 1e-10); }
#[test]
fn test_helper_functions() {
let xy_chain = create_xy_chain(4, 1.0, 0.5).expect("Failed to create XY chain");
assert_eq!(xy_chain.num_qubits, 4);
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");
assert!(tfxy.local_fields.iter().all(|&f| f.abs() > 1e-10));
let triangle =
create_frustrated_xy_triangle(1.0).expect("Failed to create frustrated triangle");
assert_eq!(triangle.num_qubits, 3);
assert_eq!(triangle.complex_couplings.len(), 6); }
}