1use crate::prelude::SimulatorError;
7use fastrand;
8use scirs2_core::ndarray::{Array1, Array2, Array3};
9use scirs2_core::Complex64;
10
11use crate::error::Result;
12use crate::trotter::{Hamiltonian, HamiltonianTerm};
13
14#[derive(Debug, Clone)]
16pub struct Walker {
17 pub config: Vec<bool>,
19 pub weight: Complex64,
21 pub local_energy: Complex64,
23}
24
25impl Walker {
26 pub fn new(num_qubits: usize) -> Self {
28 let mut config = vec![false; num_qubits];
29 for bit in &mut config {
31 *bit = fastrand::bool();
32 }
33
34 Self {
35 config,
36 weight: Complex64::new(1.0, 0.0),
37 local_energy: Complex64::new(0.0, 0.0),
38 }
39 }
40
41 pub fn flip(&mut self, qubit: usize) {
43 if qubit < self.config.len() {
44 self.config[qubit] = !self.config[qubit];
45 }
46 }
47
48 pub fn as_integer(&self) -> usize {
50 let mut result = 0;
51 for (i, &bit) in self.config.iter().enumerate() {
52 if bit {
53 result |= 1 << i;
54 }
55 }
56 result
57 }
58}
59
60#[derive(Debug, Clone)]
62pub enum WaveFunction {
63 Product(Vec<Complex64>),
65 Jastrow { alpha: f64, beta: f64 },
67 NeuralNetwork {
69 weights: Array2<f64>,
70 biases: Array1<f64>,
71 },
72 MPS {
74 tensors: Vec<Array3<Complex64>>,
75 bond_dim: usize,
76 },
77}
78
79impl WaveFunction {
80 pub fn amplitude(&self, config: &[bool]) -> Complex64 {
82 match self {
83 Self::Product(amps) => {
84 let mut result = Complex64::new(1.0, 0.0);
85 for (i, &bit) in config.iter().enumerate() {
86 if i < amps.len() {
87 result *= if bit {
88 amps[i]
89 } else {
90 Complex64::new(1.0, 0.0) - amps[i]
91 };
92 }
93 }
94 result
95 }
96 Self::Jastrow { alpha, beta } => {
97 let mut exponent = 0.0;
99 for (i, &n_i) in config.iter().enumerate() {
100 if n_i {
101 exponent += alpha;
102 for (j, &n_j) in config.iter().enumerate() {
103 if i != j && n_j {
104 exponent += beta / (1.0 + (i as f64 - j as f64).abs());
105 }
106 }
107 }
108 }
109 Complex64::new(exponent.exp(), 0.0)
110 }
111 Self::NeuralNetwork { weights, biases } => {
112 let input: Vec<f64> = config.iter().map(|&b| if b { 1.0 } else { 0.0 }).collect();
114 let hidden_dim = weights.nrows();
115
116 let mut hidden = vec![0.0; hidden_dim];
117 for h in 0..hidden_dim {
118 let mut sum = biases[h];
119 for (v, &x) in input.iter().enumerate() {
120 if v < weights.ncols() {
121 sum += weights[[h, v]] * x;
122 }
123 }
124 hidden[h] = 1.0 / (1.0 + (-sum).exp()); }
126
127 let mut log_psi = 0.0;
128 for &h in &hidden {
129 log_psi += h.ln_1p();
130 }
131 Complex64::new(log_psi.exp(), 0.0)
132 }
133 Self::MPS { .. } => {
134 Complex64::new(1.0, 0.0)
136 }
137 }
138 }
139
140 pub fn log_derivative(&self, config: &[bool], param_idx: usize) -> f64 {
142 match self {
143 Self::Jastrow { alpha, beta } => {
144 if param_idx == 0 {
146 config.iter().filter(|&&b| b).count() as f64
148 } else {
149 let mut sum = 0.0;
151 for (i, &n_i) in config.iter().enumerate() {
152 if n_i {
153 for (j, &n_j) in config.iter().enumerate() {
154 if i != j && n_j {
155 sum += 1.0 / (1.0 + (i as f64 - j as f64).abs());
156 }
157 }
158 }
159 }
160 sum
161 }
162 }
163 _ => 0.0, }
165 }
166}
167
168pub struct VMC {
170 wave_function: WaveFunction,
172 num_qubits: usize,
174 hamiltonian: Hamiltonian,
176}
177
178impl VMC {
179 pub const fn new(
181 num_qubits: usize,
182 wave_function: WaveFunction,
183 hamiltonian: Hamiltonian,
184 ) -> Self {
185 Self {
186 wave_function,
187 num_qubits,
188 hamiltonian,
189 }
190 }
191
192 pub fn run(
194 &mut self,
195 num_samples: usize,
196 num_thermalization: usize,
197 optimization_steps: usize,
198 learning_rate: f64,
199 ) -> Result<VMCResult> {
200 let mut energies = Vec::new();
201 let mut variances = Vec::new();
202
203 for step in 0..optimization_steps {
204 let mut walker = Walker::new(self.num_qubits);
206 for _ in 0..num_thermalization {
207 self.metropolis_step(&mut walker)?;
208 }
209
210 let mut local_energies = Vec::new();
212 let mut gradients = [0.0; 2]; for _ in 0..num_samples {
215 self.metropolis_step(&mut walker)?;
216
217 let e_loc = self.local_energy(&walker.config)?;
219 local_energies.push(e_loc);
220
221 if let WaveFunction::Jastrow { .. } = &self.wave_function {
223 for p in 0..2 {
224 let deriv = self.wave_function.log_derivative(&walker.config, p);
225 gradients[p] += (e_loc.re
226 - local_energies.iter().map(|e| e.re).sum::<f64>()
227 / local_energies.len() as f64)
228 * deriv;
229 }
230 }
231 }
232
233 let mean_energy = local_energies.iter().map(|e| e.re).sum::<f64>() / num_samples as f64;
235 let variance = local_energies
236 .iter()
237 .map(|e| (e.re - mean_energy).powi(2))
238 .sum::<f64>()
239 / num_samples as f64;
240
241 energies.push(mean_energy);
242 variances.push(variance);
243
244 if let WaveFunction::Jastrow {
246 ref mut alpha,
247 ref mut beta,
248 } = &mut self.wave_function
249 {
250 *alpha -= learning_rate * gradients[0] / num_samples as f64;
251 *beta -= learning_rate * gradients[1] / num_samples as f64;
252 }
253
254 if step % 10 == 0 {
256 println!(
257 "VMC Step {}: E = {:.6} ± {:.6}",
258 step,
259 mean_energy,
260 variance.sqrt()
261 );
262 }
263 }
264
265 Ok(VMCResult {
266 final_energy: *energies.last().unwrap(),
267 energy_history: energies,
268 variance_history: variances,
269 })
270 }
271
272 fn metropolis_step(&self, walker: &mut Walker) -> Result<()> {
274 let qubit = fastrand::usize(..self.num_qubits);
276 let old_config = walker.config.clone();
277 walker.flip(qubit);
278
279 let old_amp = self.wave_function.amplitude(&old_config);
281 let new_amp = self.wave_function.amplitude(&walker.config);
282 let ratio = (new_amp.norm() / old_amp.norm()).powi(2);
283
284 if fastrand::f64() >= ratio {
286 walker.config = old_config; }
288
289 Ok(())
290 }
291
292 fn local_energy(&self, config: &[bool]) -> Result<Complex64> {
294 let psi = self.wave_function.amplitude(config);
295 if psi.norm() < 1e-15 {
296 return Ok(Complex64::new(0.0, 0.0));
297 }
298
299 let mut h_psi = Complex64::new(0.0, 0.0);
300
301 for term in &self.hamiltonian.terms {
303 match term {
304 HamiltonianTerm::SinglePauli {
305 qubit,
306 pauli,
307 coefficient,
308 } => {
309 match pauli.as_str() {
310 "Z" => {
311 let sign = if config[*qubit] { 1.0 } else { -1.0 };
313 h_psi += coefficient * sign * psi;
314 }
315 "X" => {
316 let mut flipped = config.to_vec();
318 flipped[*qubit] = !flipped[*qubit];
319 let psi_flipped = self.wave_function.amplitude(&flipped);
320 h_psi += coefficient * psi_flipped;
321 }
322 "Y" => {
323 let mut flipped = config.to_vec();
325 flipped[*qubit] = !flipped[*qubit];
326 let psi_flipped = self.wave_function.amplitude(&flipped);
327 let phase = if config[*qubit] {
328 Complex64::new(0.0, -1.0)
329 } else {
330 Complex64::new(0.0, 1.0)
331 };
332 h_psi += coefficient * phase * psi_flipped;
333 }
334 _ => {}
335 }
336 }
337 HamiltonianTerm::TwoPauli {
338 qubit1,
339 qubit2,
340 pauli1,
341 pauli2,
342 coefficient,
343 } => {
344 if pauli1 == "Z" && pauli2 == "Z" {
346 let sign1 = if config[*qubit1] { 1.0 } else { -1.0 };
347 let sign2 = if config[*qubit2] { 1.0 } else { -1.0 };
348 h_psi += coefficient * sign1 * sign2 * psi;
349 }
350 }
351 _ => {} }
353 }
354
355 Ok(h_psi / psi)
356 }
357}
358
359#[derive(Debug)]
361pub struct VMCResult {
362 pub final_energy: f64,
364 pub energy_history: Vec<f64>,
366 pub variance_history: Vec<f64>,
368}
369
370pub struct DMC {
372 reference_energy: f64,
374 tau: f64,
376 target_walkers: usize,
378 hamiltonian: Hamiltonian,
380 num_qubits: usize,
382}
383
384impl DMC {
385 pub const fn new(
387 num_qubits: usize,
388 hamiltonian: Hamiltonian,
389 tau: f64,
390 target_walkers: usize,
391 ) -> Self {
392 Self {
393 reference_energy: 0.0,
394 tau,
395 target_walkers,
396 hamiltonian,
397 num_qubits,
398 }
399 }
400
401 pub fn run(&mut self, num_blocks: usize, steps_per_block: usize) -> Result<DMCResult> {
403 let mut walkers: Vec<Walker> = (0..self.target_walkers)
405 .map(|_| Walker::new(self.num_qubits))
406 .collect();
407
408 let mut energies = Vec::new();
409 let mut walker_counts = Vec::new();
410
411 for block in 0..num_blocks {
412 let mut block_energy = 0.0;
413 let mut total_weight = 0.0;
414
415 for _ in 0..steps_per_block {
416 let mut new_walkers = Vec::new();
418
419 for walker in &walkers {
420 let mut new_walker = walker.clone();
422 self.diffusion_step(&mut new_walker)?;
423
424 let local_e = self.diagonal_energy(&new_walker.config)?;
426 let growth_factor = (-self.tau * (local_e - self.reference_energy)).exp();
427 let num_copies = self.branch(growth_factor);
428
429 for _ in 0..num_copies {
430 new_walkers.push(new_walker.clone());
431 }
432
433 block_energy += local_e * walker.weight.norm();
434 total_weight += walker.weight.norm();
435 }
436
437 if new_walkers.is_empty() {
439 new_walkers.push(walkers[0].clone());
441 }
442
443 walkers = new_walkers;
444
445 self.population_control(&mut walkers)?;
447 }
448
449 let avg_energy = block_energy / total_weight;
451 energies.push(avg_energy);
452 walker_counts.push(walkers.len());
453
454 self.reference_energy =
456 avg_energy - (walkers.len() as f64 - self.target_walkers as f64).ln() / self.tau;
457
458 if block % 10 == 0 {
459 println!(
460 "DMC Block {}: E = {:.6}, Walkers = {}",
461 block,
462 avg_energy,
463 walkers.len()
464 );
465 }
466 }
467
468 Ok(DMCResult {
469 ground_state_energy: *energies.last().unwrap(),
470 energy_history: energies,
471 walker_history: walker_counts,
472 })
473 }
474
475 fn diffusion_step(&self, walker: &mut Walker) -> Result<()> {
477 let num_flips = fastrand::usize(1..=3.min(self.num_qubits));
479 for _ in 0..num_flips {
480 let qubit = fastrand::usize(..self.num_qubits);
481 walker.flip(qubit);
482 }
483 Ok(())
484 }
485
486 fn diagonal_energy(&self, config: &[bool]) -> Result<f64> {
488 let mut energy = 0.0;
489
490 for term in &self.hamiltonian.terms {
491 match term {
492 HamiltonianTerm::SinglePauli {
493 qubit,
494 pauli,
495 coefficient,
496 } => {
497 if pauli == "Z" {
498 let sign = if config[*qubit] { 1.0 } else { -1.0 };
499 energy += coefficient * sign;
500 }
501 }
502 HamiltonianTerm::TwoPauli {
503 qubit1,
504 qubit2,
505 pauli1,
506 pauli2,
507 coefficient,
508 } => {
509 if pauli1 == "Z" && pauli2 == "Z" {
510 let sign1 = if config[*qubit1] { 1.0 } else { -1.0 };
511 let sign2 = if config[*qubit2] { 1.0 } else { -1.0 };
512 energy += coefficient * sign1 * sign2;
513 }
514 }
515 _ => {}
516 }
517 }
518
519 Ok(energy)
520 }
521
522 fn branch(&self, growth_factor: f64) -> usize {
524 let clamped_factor = growth_factor.clamp(0.1, 3.0);
526 let expected = clamped_factor;
527 let integer_part = expected.floor() as usize;
528 let fractional_part = expected - integer_part as f64;
529
530 if fastrand::f64() < fractional_part {
531 integer_part + 1
532 } else {
533 integer_part
534 }
535 }
536
537 fn population_control(&self, walkers: &mut Vec<Walker>) -> Result<()> {
539 let current_size = walkers.len();
540
541 if current_size == 0 {
542 return Err(SimulatorError::ComputationError(
543 "All walkers died".to_string(),
544 ));
545 }
546
547 if current_size > 2 * self.target_walkers {
549 let mut new_walkers = Vec::new();
551 for (i, walker) in walkers.iter().enumerate() {
552 if i % 2 == 0 {
553 let mut w = walker.clone();
554 w.weight *= Complex64::new(2.0, 0.0);
555 new_walkers.push(w);
556 }
557 }
558 *walkers = new_walkers;
559 } else if current_size < self.target_walkers / 2 {
560 let mut new_walkers = walkers.clone();
562 for walker in walkers.iter() {
563 let mut w = walker.clone();
564 w.weight *= Complex64::new(0.5, 0.0);
565 new_walkers.push(w);
566 }
567 *walkers = new_walkers;
568 }
569
570 Ok(())
571 }
572}
573
574#[derive(Debug)]
576pub struct DMCResult {
577 pub ground_state_energy: f64,
579 pub energy_history: Vec<f64>,
581 pub walker_history: Vec<usize>,
583}
584
585pub struct PIMC {
587 num_slices: usize,
589 beta: f64,
591 num_qubits: usize,
593 hamiltonian: Hamiltonian,
595}
596
597impl PIMC {
598 pub const fn new(
600 num_qubits: usize,
601 hamiltonian: Hamiltonian,
602 beta: f64,
603 num_slices: usize,
604 ) -> Self {
605 Self {
606 num_slices,
607 beta,
608 num_qubits,
609 hamiltonian,
610 }
611 }
612
613 pub fn run(&self, num_samples: usize, num_thermalization: usize) -> Result<PIMCResult> {
615 let mut path: Vec<Vec<bool>> = (0..self.num_slices)
617 .map(|_| (0..self.num_qubits).map(|_| fastrand::bool()).collect())
618 .collect();
619
620 let tau = self.beta / self.num_slices as f64;
621 let mut energies = Vec::new();
622 let mut magnetizations = Vec::new();
623
624 for _ in 0..num_thermalization {
626 self.update_path(&mut path, tau)?;
627 }
628
629 for _ in 0..num_samples {
631 self.update_path(&mut path, tau)?;
632
633 let energy = self.measure_energy(&path)?;
635 let magnetization = self.measure_magnetization(&path);
636
637 energies.push(energy);
638 magnetizations.push(magnetization);
639 }
640
641 Ok(PIMCResult {
642 average_energy: energies.iter().sum::<f64>() / energies.len() as f64,
643 average_magnetization: magnetizations.iter().sum::<f64>() / magnetizations.len() as f64,
644 energy_samples: energies,
645 magnetization_samples: magnetizations,
646 })
647 }
648
649 fn update_path(&self, path: &mut Vec<Vec<bool>>, tau: f64) -> Result<()> {
651 for _ in 0..self.num_qubits * self.num_slices {
653 let slice = fastrand::usize(..self.num_slices);
654 let qubit = fastrand::usize(..self.num_qubits);
655
656 let action_old = self.path_action(path, tau)?;
658 path[slice][qubit] = !path[slice][qubit];
659 let action_new = self.path_action(path, tau)?;
660
661 if fastrand::f64() >= (-(action_new - action_old)).exp() {
663 path[slice][qubit] = !path[slice][qubit]; }
665 }
666
667 Ok(())
668 }
669
670 fn path_action(&self, path: &[Vec<bool>], tau: f64) -> Result<f64> {
672 let mut action = 0.0;
673
674 for s in 0..self.num_slices {
676 let next_s = (s + 1) % self.num_slices;
677 for q in 0..self.num_qubits {
678 if path[s][q] != path[next_s][q] {
679 action += -0.5 * tau.ln();
680 }
681 }
682 }
683
684 for s in 0..self.num_slices {
686 action += tau * self.diagonal_energy(&path[s])?;
687 }
688
689 Ok(action)
690 }
691
692 fn measure_energy(&self, path: &[Vec<bool>]) -> Result<f64> {
694 let mut total = 0.0;
695 for config in path {
696 total += self.diagonal_energy(config)?;
697 }
698 Ok(total / self.num_slices as f64)
699 }
700
701 fn measure_magnetization(&self, path: &[Vec<bool>]) -> f64 {
703 let mut total = 0.0;
704 for config in path {
705 let mag: f64 = config.iter().map(|&b| if b { 1.0 } else { -1.0 }).sum();
706 total += mag;
707 }
708 total / (self.num_slices * self.num_qubits) as f64
709 }
710
711 fn diagonal_energy(&self, config: &[bool]) -> Result<f64> {
713 let mut energy = 0.0;
714
715 for term in &self.hamiltonian.terms {
716 match term {
717 HamiltonianTerm::SinglePauli {
718 qubit,
719 pauli,
720 coefficient,
721 } => {
722 if pauli == "Z" {
723 let sign = if config[*qubit] { 1.0 } else { -1.0 };
724 energy += coefficient * sign;
725 }
726 }
727 HamiltonianTerm::TwoPauli {
728 qubit1,
729 qubit2,
730 pauli1,
731 pauli2,
732 coefficient,
733 } => {
734 if pauli1 == "Z" && pauli2 == "Z" {
735 let sign1 = if config[*qubit1] { 1.0 } else { -1.0 };
736 let sign2 = if config[*qubit2] { 1.0 } else { -1.0 };
737 energy += coefficient * sign1 * sign2;
738 }
739 }
740 _ => {}
741 }
742 }
743
744 Ok(energy)
745 }
746}
747
748#[derive(Debug)]
750pub struct PIMCResult {
751 pub average_energy: f64,
753 pub average_magnetization: f64,
755 pub energy_samples: Vec<f64>,
757 pub magnetization_samples: Vec<f64>,
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764 use crate::trotter::HamiltonianLibrary;
765
766 #[test]
767 fn test_walker() {
768 let walker = Walker::new(4);
769 assert_eq!(walker.config.len(), 4);
770 assert_eq!(walker.weight, Complex64::new(1.0, 0.0));
771 }
772
773 #[test]
774 fn test_wave_function_product() {
775 let amps = vec![Complex64::new(0.7, 0.0), Complex64::new(0.6, 0.0)];
776 let wf = WaveFunction::Product(amps);
777
778 let config = vec![true, false];
779 let amp = wf.amplitude(&config);
780 assert!(0.7f64.mul_add(-0.4, amp.norm()).abs() < 1e-10);
781 }
782
783 #[test]
784 fn test_vmc_ising() {
785 let ham = HamiltonianLibrary::transverse_ising_1d(3, 1.0, 0.5, false).unwrap();
786 let wf = WaveFunction::Jastrow {
787 alpha: 0.5,
788 beta: 0.1,
789 };
790 let mut vmc = VMC::new(3, wf, ham);
791
792 let result = vmc.run(100, 50, 10, 0.01).unwrap();
793 assert!(result.final_energy.is_finite());
794 }
795
796 #[test]
797 fn test_dmc_simple() {
798 let ham = HamiltonianLibrary::transverse_ising_1d(2, 1.0, 1.0, false).unwrap();
799 let mut dmc = DMC::new(2, ham, 0.1, 50);
801
802 let result = dmc.run(5, 5).unwrap();
803 assert!(result.ground_state_energy.is_finite());
804 }
805
806 #[test]
807 fn test_pimc_thermal() {
808 let ham = HamiltonianLibrary::xy_model(3, 1.0, true).unwrap();
809 let pimc = PIMC::new(3, ham, 1.0, 10);
810
811 let result = pimc.run(100, 50).unwrap();
812 assert!(result.average_energy.is_finite());
813 }
814}