1use crate::prelude::SimulatorError;
7use fastrand;
8use ndarray::{Array1, Array2, Array3};
9use num_complex::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 WaveFunction::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 WaveFunction::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 WaveFunction::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 += (1.0 + h).ln();
130 }
131 Complex64::new(log_psi.exp(), 0.0)
132 }
133 WaveFunction::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 WaveFunction::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 fn new(num_qubits: usize, wave_function: WaveFunction, hamiltonian: Hamiltonian) -> Self {
181 Self {
182 wave_function,
183 num_qubits,
184 hamiltonian,
185 }
186 }
187
188 pub fn run(
190 &mut self,
191 num_samples: usize,
192 num_thermalization: usize,
193 optimization_steps: usize,
194 learning_rate: f64,
195 ) -> Result<VMCResult> {
196 let mut energies = Vec::new();
197 let mut variances = Vec::new();
198
199 for step in 0..optimization_steps {
200 let mut walker = Walker::new(self.num_qubits);
202 for _ in 0..num_thermalization {
203 self.metropolis_step(&mut walker)?;
204 }
205
206 let mut local_energies = Vec::new();
208 let mut gradients = [0.0; 2]; for _ in 0..num_samples {
211 self.metropolis_step(&mut walker)?;
212
213 let e_loc = self.local_energy(&walker.config)?;
215 local_energies.push(e_loc);
216
217 if let WaveFunction::Jastrow { .. } = &self.wave_function {
219 for p in 0..2 {
220 let deriv = self.wave_function.log_derivative(&walker.config, p);
221 gradients[p] += (e_loc.re
222 - local_energies.iter().map(|e| e.re).sum::<f64>()
223 / local_energies.len() as f64)
224 * deriv;
225 }
226 }
227 }
228
229 let mean_energy = local_energies.iter().map(|e| e.re).sum::<f64>() / num_samples as f64;
231 let variance = local_energies
232 .iter()
233 .map(|e| (e.re - mean_energy).powi(2))
234 .sum::<f64>()
235 / num_samples as f64;
236
237 energies.push(mean_energy);
238 variances.push(variance);
239
240 if let WaveFunction::Jastrow {
242 ref mut alpha,
243 ref mut beta,
244 } = &mut self.wave_function
245 {
246 *alpha -= learning_rate * gradients[0] / num_samples as f64;
247 *beta -= learning_rate * gradients[1] / num_samples as f64;
248 }
249
250 if step % 10 == 0 {
252 println!(
253 "VMC Step {}: E = {:.6} ± {:.6}",
254 step,
255 mean_energy,
256 variance.sqrt()
257 );
258 }
259 }
260
261 Ok(VMCResult {
262 final_energy: *energies.last().unwrap(),
263 energy_history: energies,
264 variance_history: variances,
265 })
266 }
267
268 fn metropolis_step(&self, walker: &mut Walker) -> Result<()> {
270 let qubit = fastrand::usize(..self.num_qubits);
272 let old_config = walker.config.clone();
273 walker.flip(qubit);
274
275 let old_amp = self.wave_function.amplitude(&old_config);
277 let new_amp = self.wave_function.amplitude(&walker.config);
278 let ratio = (new_amp.norm() / old_amp.norm()).powi(2);
279
280 if fastrand::f64() >= ratio {
282 walker.config = old_config; }
284
285 Ok(())
286 }
287
288 fn local_energy(&self, config: &[bool]) -> Result<Complex64> {
290 let psi = self.wave_function.amplitude(config);
291 if psi.norm() < 1e-15 {
292 return Ok(Complex64::new(0.0, 0.0));
293 }
294
295 let mut h_psi = Complex64::new(0.0, 0.0);
296
297 for term in &self.hamiltonian.terms {
299 match term {
300 HamiltonianTerm::SinglePauli {
301 qubit,
302 pauli,
303 coefficient,
304 } => {
305 match pauli.as_str() {
306 "Z" => {
307 let sign = if config[*qubit] { 1.0 } else { -1.0 };
309 h_psi += coefficient * sign * psi;
310 }
311 "X" => {
312 let mut flipped = config.to_vec();
314 flipped[*qubit] = !flipped[*qubit];
315 let psi_flipped = self.wave_function.amplitude(&flipped);
316 h_psi += coefficient * psi_flipped;
317 }
318 "Y" => {
319 let mut flipped = config.to_vec();
321 flipped[*qubit] = !flipped[*qubit];
322 let psi_flipped = self.wave_function.amplitude(&flipped);
323 let phase = if config[*qubit] {
324 Complex64::new(0.0, -1.0)
325 } else {
326 Complex64::new(0.0, 1.0)
327 };
328 h_psi += coefficient * phase * psi_flipped;
329 }
330 _ => {}
331 }
332 }
333 HamiltonianTerm::TwoPauli {
334 qubit1,
335 qubit2,
336 pauli1,
337 pauli2,
338 coefficient,
339 } => {
340 if pauli1 == "Z" && pauli2 == "Z" {
342 let sign1 = if config[*qubit1] { 1.0 } else { -1.0 };
343 let sign2 = if config[*qubit2] { 1.0 } else { -1.0 };
344 h_psi += coefficient * sign1 * sign2 * psi;
345 }
346 }
347 _ => {} }
349 }
350
351 Ok(h_psi / psi)
352 }
353}
354
355#[derive(Debug)]
357pub struct VMCResult {
358 pub final_energy: f64,
360 pub energy_history: Vec<f64>,
362 pub variance_history: Vec<f64>,
364}
365
366pub struct DMC {
368 reference_energy: f64,
370 tau: f64,
372 target_walkers: usize,
374 hamiltonian: Hamiltonian,
376 num_qubits: usize,
378}
379
380impl DMC {
381 pub fn new(
383 num_qubits: usize,
384 hamiltonian: Hamiltonian,
385 tau: f64,
386 target_walkers: usize,
387 ) -> Self {
388 Self {
389 reference_energy: 0.0,
390 tau,
391 target_walkers,
392 hamiltonian,
393 num_qubits,
394 }
395 }
396
397 pub fn run(&mut self, num_blocks: usize, steps_per_block: usize) -> Result<DMCResult> {
399 let mut walkers: Vec<Walker> = (0..self.target_walkers)
401 .map(|_| Walker::new(self.num_qubits))
402 .collect();
403
404 let mut energies = Vec::new();
405 let mut walker_counts = Vec::new();
406
407 for block in 0..num_blocks {
408 let mut block_energy = 0.0;
409 let mut total_weight = 0.0;
410
411 for _ in 0..steps_per_block {
412 let mut new_walkers = Vec::new();
414
415 for walker in &walkers {
416 let mut new_walker = walker.clone();
418 self.diffusion_step(&mut new_walker)?;
419
420 let local_e = self.diagonal_energy(&new_walker.config)?;
422 let growth_factor = (-self.tau * (local_e - self.reference_energy)).exp();
423 let num_copies = self.branch(growth_factor);
424
425 for _ in 0..num_copies {
426 new_walkers.push(new_walker.clone());
427 }
428
429 block_energy += local_e * walker.weight.norm();
430 total_weight += walker.weight.norm();
431 }
432
433 if new_walkers.is_empty() {
435 new_walkers.push(walkers[0].clone());
437 }
438
439 walkers = new_walkers;
440
441 self.population_control(&mut walkers)?;
443 }
444
445 let avg_energy = block_energy / total_weight;
447 energies.push(avg_energy);
448 walker_counts.push(walkers.len());
449
450 self.reference_energy =
452 avg_energy - (walkers.len() as f64 - self.target_walkers as f64).ln() / self.tau;
453
454 if block % 10 == 0 {
455 println!(
456 "DMC Block {}: E = {:.6}, Walkers = {}",
457 block,
458 avg_energy,
459 walkers.len()
460 );
461 }
462 }
463
464 Ok(DMCResult {
465 ground_state_energy: *energies.last().unwrap(),
466 energy_history: energies,
467 walker_history: walker_counts,
468 })
469 }
470
471 fn diffusion_step(&self, walker: &mut Walker) -> Result<()> {
473 let num_flips = fastrand::usize(1..=3.min(self.num_qubits));
475 for _ in 0..num_flips {
476 let qubit = fastrand::usize(..self.num_qubits);
477 walker.flip(qubit);
478 }
479 Ok(())
480 }
481
482 fn diagonal_energy(&self, config: &[bool]) -> Result<f64> {
484 let mut energy = 0.0;
485
486 for term in &self.hamiltonian.terms {
487 match term {
488 HamiltonianTerm::SinglePauli {
489 qubit,
490 pauli,
491 coefficient,
492 } => {
493 if pauli == "Z" {
494 let sign = if config[*qubit] { 1.0 } else { -1.0 };
495 energy += coefficient * sign;
496 }
497 }
498 HamiltonianTerm::TwoPauli {
499 qubit1,
500 qubit2,
501 pauli1,
502 pauli2,
503 coefficient,
504 } => {
505 if pauli1 == "Z" && pauli2 == "Z" {
506 let sign1 = if config[*qubit1] { 1.0 } else { -1.0 };
507 let sign2 = if config[*qubit2] { 1.0 } else { -1.0 };
508 energy += coefficient * sign1 * sign2;
509 }
510 }
511 _ => {}
512 }
513 }
514
515 Ok(energy)
516 }
517
518 fn branch(&self, growth_factor: f64) -> usize {
520 let clamped_factor = growth_factor.clamp(0.1, 3.0);
522 let expected = clamped_factor;
523 let integer_part = expected.floor() as usize;
524 let fractional_part = expected - integer_part as f64;
525
526 if fastrand::f64() < fractional_part {
527 integer_part + 1
528 } else {
529 integer_part
530 }
531 }
532
533 fn population_control(&self, walkers: &mut Vec<Walker>) -> Result<()> {
535 let current_size = walkers.len();
536
537 if current_size == 0 {
538 return Err(SimulatorError::ComputationError(
539 "All walkers died".to_string(),
540 ));
541 }
542
543 if current_size > 2 * self.target_walkers {
545 let mut new_walkers = Vec::new();
547 for (i, walker) in walkers.iter().enumerate() {
548 if i % 2 == 0 {
549 let mut w = walker.clone();
550 w.weight *= Complex64::new(2.0, 0.0);
551 new_walkers.push(w);
552 }
553 }
554 *walkers = new_walkers;
555 } else if current_size < self.target_walkers / 2 {
556 let mut new_walkers = walkers.clone();
558 for walker in walkers.iter() {
559 let mut w = walker.clone();
560 w.weight *= Complex64::new(0.5, 0.0);
561 new_walkers.push(w);
562 }
563 *walkers = new_walkers;
564 }
565
566 Ok(())
567 }
568}
569
570#[derive(Debug)]
572pub struct DMCResult {
573 pub ground_state_energy: f64,
575 pub energy_history: Vec<f64>,
577 pub walker_history: Vec<usize>,
579}
580
581pub struct PIMC {
583 num_slices: usize,
585 beta: f64,
587 num_qubits: usize,
589 hamiltonian: Hamiltonian,
591}
592
593impl PIMC {
594 pub fn new(num_qubits: usize, hamiltonian: Hamiltonian, beta: f64, num_slices: usize) -> Self {
596 Self {
597 num_slices,
598 beta,
599 num_qubits,
600 hamiltonian,
601 }
602 }
603
604 pub fn run(&self, num_samples: usize, num_thermalization: usize) -> Result<PIMCResult> {
606 let mut path: Vec<Vec<bool>> = (0..self.num_slices)
608 .map(|_| (0..self.num_qubits).map(|_| fastrand::bool()).collect())
609 .collect();
610
611 let tau = self.beta / self.num_slices as f64;
612 let mut energies = Vec::new();
613 let mut magnetizations = Vec::new();
614
615 for _ in 0..num_thermalization {
617 self.update_path(&mut path, tau)?;
618 }
619
620 for _ in 0..num_samples {
622 self.update_path(&mut path, tau)?;
623
624 let energy = self.measure_energy(&path)?;
626 let magnetization = self.measure_magnetization(&path);
627
628 energies.push(energy);
629 magnetizations.push(magnetization);
630 }
631
632 Ok(PIMCResult {
633 average_energy: energies.iter().sum::<f64>() / energies.len() as f64,
634 average_magnetization: magnetizations.iter().sum::<f64>() / magnetizations.len() as f64,
635 energy_samples: energies,
636 magnetization_samples: magnetizations,
637 })
638 }
639
640 fn update_path(&self, path: &mut Vec<Vec<bool>>, tau: f64) -> Result<()> {
642 for _ in 0..self.num_qubits * self.num_slices {
644 let slice = fastrand::usize(..self.num_slices);
645 let qubit = fastrand::usize(..self.num_qubits);
646
647 let action_old = self.path_action(path, tau)?;
649 path[slice][qubit] = !path[slice][qubit];
650 let action_new = self.path_action(path, tau)?;
651
652 if fastrand::f64() >= (-(action_new - action_old)).exp() {
654 path[slice][qubit] = !path[slice][qubit]; }
656 }
657
658 Ok(())
659 }
660
661 fn path_action(&self, path: &[Vec<bool>], tau: f64) -> Result<f64> {
663 let mut action = 0.0;
664
665 for s in 0..self.num_slices {
667 let next_s = (s + 1) % self.num_slices;
668 for q in 0..self.num_qubits {
669 if path[s][q] != path[next_s][q] {
670 action += -0.5 * tau.ln();
671 }
672 }
673 }
674
675 for s in 0..self.num_slices {
677 action += tau * self.diagonal_energy(&path[s])?;
678 }
679
680 Ok(action)
681 }
682
683 fn measure_energy(&self, path: &[Vec<bool>]) -> Result<f64> {
685 let mut total = 0.0;
686 for config in path {
687 total += self.diagonal_energy(config)?;
688 }
689 Ok(total / self.num_slices as f64)
690 }
691
692 fn measure_magnetization(&self, path: &[Vec<bool>]) -> f64 {
694 let mut total = 0.0;
695 for config in path {
696 let mag: f64 = config.iter().map(|&b| if b { 1.0 } else { -1.0 }).sum();
697 total += mag;
698 }
699 total / (self.num_slices * self.num_qubits) as f64
700 }
701
702 fn diagonal_energy(&self, config: &[bool]) -> Result<f64> {
704 let mut energy = 0.0;
705
706 for term in &self.hamiltonian.terms {
707 match term {
708 HamiltonianTerm::SinglePauli {
709 qubit,
710 pauli,
711 coefficient,
712 } => {
713 if pauli == "Z" {
714 let sign = if config[*qubit] { 1.0 } else { -1.0 };
715 energy += coefficient * sign;
716 }
717 }
718 HamiltonianTerm::TwoPauli {
719 qubit1,
720 qubit2,
721 pauli1,
722 pauli2,
723 coefficient,
724 } => {
725 if pauli1 == "Z" && pauli2 == "Z" {
726 let sign1 = if config[*qubit1] { 1.0 } else { -1.0 };
727 let sign2 = if config[*qubit2] { 1.0 } else { -1.0 };
728 energy += coefficient * sign1 * sign2;
729 }
730 }
731 _ => {}
732 }
733 }
734
735 Ok(energy)
736 }
737}
738
739#[derive(Debug)]
741pub struct PIMCResult {
742 pub average_energy: f64,
744 pub average_magnetization: f64,
746 pub energy_samples: Vec<f64>,
748 pub magnetization_samples: Vec<f64>,
750}
751
752#[cfg(test)]
753mod tests {
754 use super::*;
755 use crate::trotter::HamiltonianLibrary;
756
757 #[test]
758 fn test_walker() {
759 let walker = Walker::new(4);
760 assert_eq!(walker.config.len(), 4);
761 assert_eq!(walker.weight, Complex64::new(1.0, 0.0));
762 }
763
764 #[test]
765 fn test_wave_function_product() {
766 let amps = vec![Complex64::new(0.7, 0.0), Complex64::new(0.6, 0.0)];
767 let wf = WaveFunction::Product(amps);
768
769 let config = vec![true, false];
770 let amp = wf.amplitude(&config);
771 assert!((amp.norm() - 0.7 * 0.4).abs() < 1e-10);
772 }
773
774 #[test]
775 fn test_vmc_ising() {
776 let ham = HamiltonianLibrary::transverse_ising_1d(3, 1.0, 0.5, false).unwrap();
777 let wf = WaveFunction::Jastrow {
778 alpha: 0.5,
779 beta: 0.1,
780 };
781 let mut vmc = VMC::new(3, wf, ham);
782
783 let result = vmc.run(100, 50, 10, 0.01).unwrap();
784 assert!(result.final_energy.is_finite());
785 }
786
787 #[test]
788 fn test_dmc_simple() {
789 let ham = HamiltonianLibrary::transverse_ising_1d(2, 1.0, 1.0, false).unwrap();
790 let mut dmc = DMC::new(2, ham, 0.1, 50);
792
793 let result = dmc.run(5, 5).unwrap();
794 assert!(result.ground_state_energy.is_finite());
795 }
796
797 #[test]
798 fn test_pimc_thermal() {
799 let ham = HamiltonianLibrary::xy_model(3, 1.0, true).unwrap();
800 let pimc = PIMC::new(3, ham, 1.0, 10);
801
802 let result = pimc.run(100, 50).unwrap();
803 assert!(result.average_energy.is_finite());
804 }
805}