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