1use crate::prelude::SimulatorError;
9use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
10use scirs2_core::parallel_ops::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
11use scirs2_core::Complex64;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15use crate::error::Result;
16use crate::scirs2_integration::SciRS2Backend;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PathIntegralMethod {
21 Exact,
23 MonteCarlo,
25 QuantumMonteCarlo,
27 SciRS2Optimized,
29 TrotterApproximation,
31}
32
33#[derive(Debug, Clone)]
35pub struct PathIntegralConfig {
36 pub method: PathIntegralMethod,
38 pub time_slices: usize,
40 pub monte_carlo_samples: usize,
42 pub temperature: f64,
44 pub time_step: f64,
46 pub tolerance: f64,
48 pub parallel: bool,
50 pub max_path_length: usize,
52}
53
54impl Default for PathIntegralConfig {
55 fn default() -> Self {
56 Self {
57 method: PathIntegralMethod::MonteCarlo,
58 time_slices: 100,
59 monte_carlo_samples: 10_000,
60 temperature: 0.0, time_step: 0.01,
62 tolerance: 1e-10,
63 parallel: true,
64 max_path_length: 1000,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct PathIntegralResult {
72 pub amplitudes: Vec<Complex64>,
74 pub path_weights: Vec<f64>,
76 pub action_values: Vec<f64>,
78 pub convergence_stats: ConvergenceStats,
80 pub execution_stats: PathIntegralStats,
82}
83
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
86pub struct ConvergenceStats {
87 pub iterations: usize,
89 pub final_error: f64,
91 pub converged: bool,
93 pub error_history: Vec<f64>,
95}
96
97#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct PathIntegralStats {
100 pub execution_time_ms: f64,
102 pub paths_evaluated: usize,
104 pub average_path_weight: f64,
106 pub sampling_efficiency: f64,
108 pub memory_usage_bytes: usize,
110 pub method_used: String,
112}
113
114#[derive(Debug, Clone)]
116pub struct QuantumPath {
117 pub coordinates: Array2<f64>,
119 pub action: f64,
121 pub weight: Complex64,
123 pub time_slices: usize,
125}
126
127impl QuantumPath {
128 #[must_use]
130 pub fn new(time_slices: usize, dimensions: usize) -> Self {
131 Self {
132 coordinates: Array2::zeros((time_slices, dimensions)),
133 action: 0.0,
134 weight: Complex64::new(1.0, 0.0),
135 time_slices,
136 }
137 }
138
139 pub fn calculate_action(
141 &mut self,
142 hamiltonian: &dyn Fn(&ArrayView1<f64>, f64) -> f64,
143 time_step: f64,
144 ) -> Result<()> {
145 let mut total_action = 0.0;
146
147 for t in 0..self.time_slices - 1 {
148 let current_pos = self.coordinates.row(t);
149 let next_pos = self.coordinates.row(t + 1);
150
151 let velocity = (&next_pos - ¤t_pos) / time_step;
153 let kinetic_energy = 0.5 * velocity.iter().map(|&v| v * v).sum::<f64>();
154
155 let potential_energy = hamiltonian(¤t_pos, t as f64 * time_step);
157
158 total_action += (kinetic_energy - potential_energy) * time_step;
160 }
161
162 self.action = total_action;
163 self.weight = Complex64::new(0.0, -self.action).exp();
164
165 Ok(())
166 }
167}
168
169pub struct PathIntegralSimulator {
171 dimensions: usize,
173 config: PathIntegralConfig,
175 backend: Option<SciRS2Backend>,
177 path_cache: HashMap<String, Vec<QuantumPath>>,
179 rng_seed: u64,
181}
182
183impl PathIntegralSimulator {
184 pub fn new(dimensions: usize, config: PathIntegralConfig) -> Result<Self> {
186 Ok(Self {
187 dimensions,
188 config,
189 backend: None,
190 path_cache: HashMap::new(),
191 rng_seed: 42,
192 })
193 }
194
195 pub fn with_backend(mut self) -> Result<Self> {
197 self.backend = Some(SciRS2Backend::new());
198 Ok(self)
199 }
200
201 pub fn evolve_system<H, I>(
203 &mut self,
204 initial_state: I,
205 hamiltonian: H,
206 evolution_time: f64,
207 ) -> Result<PathIntegralResult>
208 where
209 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
210 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
211 {
212 let start_time = std::time::Instant::now();
213
214 let result = match self.config.method {
215 PathIntegralMethod::Exact => {
216 self.evolve_exact(&initial_state, &hamiltonian, evolution_time)?
217 }
218 PathIntegralMethod::MonteCarlo => {
219 self.evolve_monte_carlo(&initial_state, &hamiltonian, evolution_time)?
220 }
221 PathIntegralMethod::QuantumMonteCarlo => {
222 self.evolve_quantum_monte_carlo(&initial_state, &hamiltonian, evolution_time)?
223 }
224 PathIntegralMethod::SciRS2Optimized => {
225 self.evolve_scirs2_optimized(&initial_state, &hamiltonian, evolution_time)?
226 }
227 PathIntegralMethod::TrotterApproximation => {
228 self.evolve_trotter_approximation(&initial_state, &hamiltonian, evolution_time)?
229 }
230 };
231
232 Ok(PathIntegralResult {
233 execution_stats: PathIntegralStats {
234 execution_time_ms: start_time.elapsed().as_secs_f64() * 1000.0,
235 method_used: format!("{:?}", self.config.method),
236 ..result.execution_stats
237 },
238 ..result
239 })
240 }
241
242 fn evolve_exact<H, I>(
244 &self,
245 initial_state: &I,
246 hamiltonian: &H,
247 evolution_time: f64,
248 ) -> Result<PathIntegralResult>
249 where
250 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
251 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
252 {
253 if self.config.time_slices > self.config.max_path_length {
254 return Err(SimulatorError::InvalidInput(
255 "Too many time slices for exact path integral".to_string(),
256 ));
257 }
258
259 let time_step = evolution_time / self.config.time_slices as f64;
260 let grid_points = 50; let grid_spacing = 10.0 / grid_points as f64; let paths = self.generate_exact_paths(grid_points, grid_spacing)?;
265
266 let mut total_amplitude = Complex64::new(0.0, 0.0);
267 let mut amplitudes = Vec::new();
268 let mut action_values = Vec::new();
269
270 for mut path in paths {
271 path.calculate_action(hamiltonian, time_step)?;
273 action_values.push(path.action);
274
275 let initial_amp = initial_state(&path.coordinates.row(0));
277 let path_contribution = initial_amp * path.weight;
278
279 amplitudes.push(path_contribution);
280 total_amplitude += path_contribution;
281 }
282
283 let norm = total_amplitude.norm();
285 if norm > 1e-15 {
286 for amp in &mut amplitudes {
287 *amp /= norm;
288 }
289 }
290
291 let num_amplitudes = amplitudes.len();
292
293 Ok(PathIntegralResult {
294 amplitudes,
295 path_weights: vec![1.0; action_values.len()],
296 action_values,
297 convergence_stats: ConvergenceStats {
298 iterations: 1,
299 converged: true,
300 final_error: 0.0,
301 error_history: vec![0.0],
302 },
303 execution_stats: PathIntegralStats {
304 paths_evaluated: num_amplitudes,
305 average_path_weight: 1.0,
306 sampling_efficiency: 1.0,
307 memory_usage_bytes: num_amplitudes * std::mem::size_of::<Complex64>(),
308 ..Default::default()
309 },
310 })
311 }
312
313 fn evolve_monte_carlo<H, I>(
315 &self,
316 initial_state: &I,
317 hamiltonian: &H,
318 evolution_time: f64,
319 ) -> Result<PathIntegralResult>
320 where
321 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
322 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
323 {
324 let time_step = evolution_time / self.config.time_slices as f64;
325 let samples = self.config.monte_carlo_samples;
326
327 let paths: Result<Vec<_>> = if self.config.parallel {
329 (0..samples)
330 .into_par_iter()
331 .map(|i| {
332 let mut path = self.generate_random_path(i as u64)?;
333 path.calculate_action(hamiltonian, time_step)?;
334 Ok(path)
335 })
336 .collect()
337 } else {
338 (0..samples)
339 .map(|i| {
340 let mut path = self.generate_random_path(i as u64)?;
341 path.calculate_action(hamiltonian, time_step)?;
342 Ok(path)
343 })
344 .collect()
345 };
346
347 let paths = paths?;
348
349 let mut amplitudes = Vec::with_capacity(samples);
351 let mut path_weights = Vec::with_capacity(samples);
352 let mut action_values = Vec::with_capacity(samples);
353 let mut total_weight = 0.0;
354
355 for path in &paths {
356 let initial_amp = initial_state(&path.coordinates.row(0));
357 let weight = path.weight.norm();
358 let amplitude = initial_amp * path.weight;
359
360 amplitudes.push(amplitude);
361 path_weights.push(weight);
362 action_values.push(path.action);
363 total_weight += weight;
364 }
365
366 if total_weight > 1e-15 {
368 for weight in &mut path_weights {
369 *weight /= total_weight;
370 }
371 }
372
373 let convergence_stats = self.calculate_convergence_stats(&litudes)?;
375 let avg_path_weight = total_weight / samples as f64;
376 let sampling_efficiency = self.calculate_sampling_efficiency(&paths);
377
378 Ok(PathIntegralResult {
379 amplitudes,
380 path_weights,
381 action_values,
382 convergence_stats,
383 execution_stats: PathIntegralStats {
384 paths_evaluated: samples,
385 average_path_weight: avg_path_weight,
386 sampling_efficiency,
387 memory_usage_bytes: samples * std::mem::size_of::<QuantumPath>(),
388 ..Default::default()
389 },
390 })
391 }
392
393 fn evolve_quantum_monte_carlo<H, I>(
395 &self,
396 initial_state: &I,
397 hamiltonian: &H,
398 evolution_time: f64,
399 ) -> Result<PathIntegralResult>
400 where
401 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
402 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
403 {
404 let time_step = evolution_time / self.config.time_slices as f64;
405 let samples = self.config.monte_carlo_samples;
406
407 let mut accepted_paths = Vec::new();
409 let mut acceptance_rate = 0.0;
410
411 for i in 0..samples {
412 let mut candidate_path = self.generate_random_path(i as u64)?;
413 candidate_path.calculate_action(hamiltonian, time_step)?;
414
415 let acceptance_prob =
417 (-candidate_path.action.abs() / self.config.temperature.max(0.1)).exp();
418
419 if fastrand::f64() < acceptance_prob {
420 accepted_paths.push(candidate_path);
421 acceptance_rate += 1.0;
422 }
423 }
424
425 acceptance_rate /= samples as f64;
426
427 if accepted_paths.is_empty() {
428 return Err(SimulatorError::NumericalError(
429 "No paths accepted in quantum Monte Carlo sampling".to_string(),
430 ));
431 }
432
433 let mut amplitudes = Vec::new();
435 let mut path_weights = Vec::new();
436 let mut action_values = Vec::new();
437
438 for path in &accepted_paths {
439 let initial_amp = initial_state(&path.coordinates.row(0));
440 let amplitude = initial_amp * path.weight;
441
442 amplitudes.push(amplitude);
443 path_weights.push(path.weight.norm());
444 action_values.push(path.action);
445 }
446
447 let convergence_stats = self.calculate_convergence_stats(&litudes)?;
448 let avg_path_weight = path_weights.iter().sum::<f64>() / path_weights.len() as f64;
449 let num_accepted_paths = accepted_paths.len();
450
451 Ok(PathIntegralResult {
452 amplitudes,
453 path_weights,
454 action_values,
455 convergence_stats,
456 execution_stats: PathIntegralStats {
457 paths_evaluated: num_accepted_paths,
458 average_path_weight: avg_path_weight,
459 sampling_efficiency: acceptance_rate,
460 memory_usage_bytes: num_accepted_paths * std::mem::size_of::<QuantumPath>(),
461 ..Default::default()
462 },
463 })
464 }
465
466 fn evolve_scirs2_optimized<H, I>(
468 &mut self,
469 initial_state: &I,
470 hamiltonian: &H,
471 evolution_time: f64,
472 ) -> Result<PathIntegralResult>
473 where
474 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
475 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
476 {
477 if let Some(_backend) = &mut self.backend {
478 self.evolve_scirs2_path_integral(initial_state, hamiltonian, evolution_time)
480 } else {
481 self.evolve_quantum_monte_carlo(initial_state, hamiltonian, evolution_time)
483 }
484 }
485
486 fn evolve_scirs2_path_integral<H, I>(
488 &self,
489 initial_state: &I,
490 hamiltonian: &H,
491 evolution_time: f64,
492 ) -> Result<PathIntegralResult>
493 where
494 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
495 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
496 {
497 let time_step = evolution_time / self.config.time_slices as f64;
499 let samples = self.config.monte_carlo_samples;
500
501 let mut paths = Vec::new();
503 let mut importance_weights = Vec::new();
504
505 for i in 0..samples / 10 {
507 let mut path = self.generate_random_path(i as u64)?;
508 path.calculate_action(hamiltonian, time_step)?;
509
510 let importance = (-path.action.abs()).exp();
511 importance_weights.push(importance);
512 paths.push(path);
513 }
514
515 let total_importance: f64 = importance_weights.iter().sum();
517 let mut resampled_paths = Vec::new();
518
519 for _ in 0..samples {
520 let mut cumulative = 0.0;
521 let target = fastrand::f64() * total_importance;
522
523 for (i, &weight) in importance_weights.iter().enumerate() {
524 cumulative += weight;
525 if cumulative >= target {
526 resampled_paths.push(paths[i].clone());
527 break;
528 }
529 }
530 }
531
532 let mut amplitudes = Vec::new();
534 let mut path_weights = Vec::new();
535 let mut action_values = Vec::new();
536
537 for path in &resampled_paths {
538 let initial_amp = initial_state(&path.coordinates.row(0));
539 let amplitude = initial_amp * path.weight;
540
541 amplitudes.push(amplitude);
542 path_weights.push(path.weight.norm());
543 action_values.push(path.action);
544 }
545
546 let convergence_stats = self.calculate_convergence_stats(&litudes)?;
547 let avg_path_weight = path_weights.iter().sum::<f64>() / path_weights.len() as f64;
548 let num_resampled_paths = resampled_paths.len();
549
550 Ok(PathIntegralResult {
551 amplitudes,
552 path_weights,
553 action_values,
554 convergence_stats,
555 execution_stats: PathIntegralStats {
556 paths_evaluated: num_resampled_paths,
557 average_path_weight: avg_path_weight,
558 sampling_efficiency: 0.8, memory_usage_bytes: num_resampled_paths * std::mem::size_of::<QuantumPath>(),
560 ..Default::default()
561 },
562 })
563 }
564
565 fn evolve_trotter_approximation<H, I>(
567 &self,
568 initial_state: &I,
569 hamiltonian: &H,
570 evolution_time: f64,
571 ) -> Result<PathIntegralResult>
572 where
573 H: Fn(&ArrayView1<f64>, f64) -> f64 + Sync + Send,
574 I: Fn(&ArrayView1<f64>) -> Complex64 + Sync + Send,
575 {
576 let time_step = evolution_time / self.config.time_slices as f64;
577
578 let grid_size = 50;
580 let mut amplitudes = Vec::new();
581 let mut action_values = Vec::new();
582
583 for i in 0..grid_size {
584 let position = -5.0 + 10.0 * i as f64 / grid_size as f64;
585 let pos_array = Array1::from_vec(vec![position]);
586
587 let mut total_action = 0.0;
589 for t in 0..self.config.time_slices {
590 let time = t as f64 * time_step;
591 let potential = hamiltonian(&pos_array.view(), time);
592 total_action += potential * time_step;
593 }
594
595 action_values.push(total_action);
596
597 let initial_amp = initial_state(&pos_array.view());
599 let evolution_weight = Complex64::new(0.0, -total_action).exp();
600 let amplitude = initial_amp * evolution_weight;
601
602 amplitudes.push(amplitude);
603 }
604
605 let convergence_stats = self.calculate_convergence_stats(&litudes)?;
606
607 Ok(PathIntegralResult {
608 amplitudes,
609 path_weights: vec![1.0 / grid_size as f64; grid_size],
610 action_values,
611 convergence_stats,
612 execution_stats: PathIntegralStats {
613 paths_evaluated: grid_size,
614 average_path_weight: 1.0 / grid_size as f64,
615 sampling_efficiency: 1.0,
616 memory_usage_bytes: grid_size * std::mem::size_of::<Complex64>(),
617 ..Default::default()
618 },
619 })
620 }
621
622 fn generate_exact_paths(
624 &self,
625 grid_points: usize,
626 grid_spacing: f64,
627 ) -> Result<Vec<QuantumPath>> {
628 if self.config.time_slices > 20 || grid_points > 100 {
629 return Err(SimulatorError::InvalidInput(
630 "System too large for exact path enumeration".to_string(),
631 ));
632 }
633
634 let mut paths = Vec::new();
635 let total_paths = grid_points.pow(self.config.time_slices as u32);
636
637 if total_paths > 1_000_000 {
638 return Err(SimulatorError::InvalidInput(
639 "Too many paths for exact calculation".to_string(),
640 ));
641 }
642
643 for path_index in 0..total_paths {
645 let mut path = QuantumPath::new(self.config.time_slices, self.dimensions);
646 let mut remaining_index = path_index;
647
648 for t in 0..self.config.time_slices {
649 let grid_index = remaining_index % grid_points;
650 remaining_index /= grid_points;
651
652 let position = (grid_index as f64).mul_add(grid_spacing, -5.0);
653 path.coordinates[[t, 0]] = position;
654 }
655
656 paths.push(path);
657 }
658
659 Ok(paths)
660 }
661
662 fn generate_random_path(&self, seed: u64) -> Result<QuantumPath> {
664 let mut path = QuantumPath::new(self.config.time_slices, self.dimensions);
665
666 fastrand::seed(self.rng_seed.wrapping_add(seed));
668
669 for t in 0..self.config.time_slices {
671 for d in 0..self.dimensions {
672 if t == 0 {
673 path.coordinates[[t, d]] = fastrand::f64().mul_add(10.0, -5.0);
675 } else {
676 let step = (fastrand::f64() - 0.5) * 2.0 * self.config.time_step.sqrt();
678 path.coordinates[[t, d]] = path.coordinates[[t - 1, d]] + step;
679 }
680 }
681 }
682
683 Ok(path)
684 }
685
686 fn calculate_convergence_stats(&self, amplitudes: &[Complex64]) -> Result<ConvergenceStats> {
688 if amplitudes.is_empty() {
689 return Ok(ConvergenceStats::default());
690 }
691
692 let mean_amplitude = amplitudes.iter().sum::<Complex64>() / amplitudes.len() as f64;
694 let variance = amplitudes
695 .iter()
696 .map(|&| (amp - mean_amplitude).norm_sqr())
697 .sum::<f64>()
698 / amplitudes.len() as f64;
699
700 let error = variance.sqrt() / (amplitudes.len() as f64).sqrt();
701
702 Ok(ConvergenceStats {
703 iterations: 1,
704 final_error: error,
705 converged: error < self.config.tolerance || amplitudes.len() > 10, error_history: vec![error],
707 })
708 }
709
710 fn calculate_sampling_efficiency(&self, paths: &[QuantumPath]) -> f64 {
712 if paths.is_empty() {
713 return 0.0;
714 }
715
716 let weights: Vec<f64> = paths.iter().map(|p| p.weight.norm()).collect();
718 let mean_weight = weights.iter().sum::<f64>() / weights.len() as f64;
719 let weight_variance = weights
720 .iter()
721 .map(|&w| (w - mean_weight).powi(2))
722 .sum::<f64>()
723 / weights.len() as f64;
724
725 1.0 / (1.0 + weight_variance / mean_weight.powi(2))
727 }
728
729 pub const fn set_seed(&mut self, seed: u64) {
731 self.rng_seed = seed;
732 }
733
734 #[must_use]
736 pub const fn get_config(&self) -> &PathIntegralConfig {
737 &self.config
738 }
739
740 pub const fn set_config(&mut self, config: PathIntegralConfig) {
742 self.config = config;
743 }
744}
745
746pub struct PathIntegralUtils;
748
749impl PathIntegralUtils {
750 pub fn harmonic_oscillator(omega: f64, mass: f64) -> impl Fn(&ArrayView1<f64>, f64) -> f64 {
752 move |position: &ArrayView1<f64>, _time: f64| -> f64 {
753 0.5 * mass * omega * omega * position[0] * position[0]
754 }
755 }
756
757 pub fn double_well(
759 barrier_height: f64,
760 well_separation: f64,
761 ) -> impl Fn(&ArrayView1<f64>, f64) -> f64 {
762 move |position: &ArrayView1<f64>, _time: f64| -> f64 {
763 let x = position[0];
764 let a = well_separation / 2.0;
765 barrier_height * (x.mul_add(x, -(a * a)) / a).powi(2)
766 }
767 }
768
769 pub fn time_dependent_field(
771 field_strength: f64,
772 frequency: f64,
773 ) -> impl Fn(&ArrayView1<f64>, f64) -> f64 {
774 move |position: &ArrayView1<f64>, time: f64| -> f64 {
775 -field_strength * position[0] * (frequency * time).cos()
776 }
777 }
778
779 pub fn gaussian_wave_packet(
781 center: f64,
782 width: f64,
783 momentum: f64,
784 ) -> impl Fn(&ArrayView1<f64>) -> Complex64 {
785 move |position: &ArrayView1<f64>| -> Complex64 {
786 let x = position[0];
787 let gaussian = (-(x - center).powi(2) / (2.0 * width * width)).exp();
788 let phase = Complex64::new(0.0, momentum * x).exp();
789 gaussian * phase
790 / (2.0 * std::f64::consts::PI * width * width)
791 .sqrt()
792 .powf(0.25)
793 }
794 }
795
796 pub fn coherent_state(alpha: Complex64) -> impl Fn(&ArrayView1<f64>) -> Complex64 {
798 move |position: &ArrayView1<f64>| -> Complex64 {
799 let x = position[0];
800 let gaussian = (-x * x / 2.0).exp();
801 let coherent_factor =
802 (-alpha.norm_sqr() / 2.0).exp() * (alpha * x - alpha.conj() * x).exp();
803 gaussian * coherent_factor / std::f64::consts::PI.powf(0.25)
804 }
805 }
806}
807
808pub fn benchmark_path_integral_methods(
810 dimensions: usize,
811 time_slices: usize,
812 evolution_time: f64,
813) -> Result<HashMap<String, PathIntegralStats>> {
814 let mut results = HashMap::new();
815
816 let methods = vec![
818 ("MonteCarlo", PathIntegralMethod::MonteCarlo),
819 ("QuantumMonteCarlo", PathIntegralMethod::QuantumMonteCarlo),
820 (
821 "TrotterApproximation",
822 PathIntegralMethod::TrotterApproximation,
823 ),
824 ];
825
826 for (name, method) in methods {
827 let hamiltonian = PathIntegralUtils::harmonic_oscillator(1.0, 1.0);
829 let initial_state = PathIntegralUtils::gaussian_wave_packet(0.0, 1.0, 0.0);
830
831 let config = PathIntegralConfig {
832 method,
833 time_slices,
834 monte_carlo_samples: 1000,
835 time_step: evolution_time / time_slices as f64,
836 ..Default::default()
837 };
838
839 let mut simulator = PathIntegralSimulator::new(dimensions, config)?;
840 let result = simulator.evolve_system(initial_state, hamiltonian, evolution_time)?;
841
842 results.insert(name.to_string(), result.execution_stats);
843 }
844
845 Ok(results)
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851 use approx::assert_abs_diff_eq;
852
853 #[test]
854 fn test_path_integral_config_default() {
855 let config = PathIntegralConfig::default();
856 assert_eq!(config.method, PathIntegralMethod::MonteCarlo);
857 assert_eq!(config.time_slices, 100);
858 assert_eq!(config.monte_carlo_samples, 10_000);
859 }
860
861 #[test]
862 fn test_quantum_path_creation() {
863 let path = QuantumPath::new(10, 1);
864 assert_eq!(path.time_slices, 10);
865 assert_eq!(path.coordinates.shape(), &[10, 1]);
866 assert_abs_diff_eq!(path.action, 0.0, epsilon = 1e-10);
867 }
868
869 #[test]
870 fn test_path_integral_simulator_creation() {
871 let config = PathIntegralConfig::default();
872 let simulator = PathIntegralSimulator::new(1, config).expect("Failed to create simulator");
873 assert_eq!(simulator.dimensions, 1);
874 }
875
876 #[test]
877 fn test_harmonic_oscillator_hamiltonian() {
878 let hamiltonian = PathIntegralUtils::harmonic_oscillator(1.0, 1.0);
879 let position = Array1::from_vec(vec![1.0]);
880 let energy = hamiltonian(&position.view(), 0.0);
881 assert_abs_diff_eq!(energy, 0.5, epsilon = 1e-10);
882 }
883
884 #[test]
885 fn test_gaussian_wave_packet() {
886 let initial_state = PathIntegralUtils::gaussian_wave_packet(0.0, 1.0, 0.0);
887 let position = Array1::from_vec(vec![0.0]);
888 let amplitude = initial_state(&position.view());
889 assert!(amplitude.norm() > 0.0);
890 }
891
892 #[test]
893 fn test_monte_carlo_path_integral() {
894 let config = PathIntegralConfig {
895 method: PathIntegralMethod::MonteCarlo,
896 time_slices: 10,
897 monte_carlo_samples: 100,
898 ..Default::default()
899 };
900
901 let mut simulator =
902 PathIntegralSimulator::new(1, config).expect("Failed to create simulator");
903 let hamiltonian = PathIntegralUtils::harmonic_oscillator(1.0, 1.0);
904 let initial_state = PathIntegralUtils::gaussian_wave_packet(0.0, 1.0, 0.0);
905
906 let result = simulator
907 .evolve_system(initial_state, hamiltonian, 1.0)
908 .expect("Failed to evolve system");
909
910 assert_eq!(result.amplitudes.len(), 100);
911 assert!(result.execution_stats.execution_time_ms > 0.0);
912 assert_eq!(result.execution_stats.paths_evaluated, 100);
913 }
914
915 #[test]
916 fn test_path_action_calculation() {
917 let mut path = QuantumPath::new(5, 1);
918
919 for t in 0..5 {
921 path.coordinates[[t, 0]] = t as f64;
922 }
923
924 let hamiltonian = PathIntegralUtils::harmonic_oscillator(1.0, 1.0);
925 path.calculate_action(&hamiltonian, 0.1)
926 .expect("Failed to calculate action");
927
928 assert!(path.action.abs() > 0.0);
929 assert!(path.weight.norm() > 0.0);
930 }
931
932 #[test]
933 fn test_trotter_approximation() {
934 let config = PathIntegralConfig {
935 method: PathIntegralMethod::TrotterApproximation,
936 time_slices: 20,
937 tolerance: 1e-3, ..Default::default()
939 };
940
941 let mut simulator =
942 PathIntegralSimulator::new(1, config).expect("Failed to create simulator");
943 let hamiltonian = PathIntegralUtils::harmonic_oscillator(1.0, 1.0);
944 let initial_state = PathIntegralUtils::gaussian_wave_packet(0.0, 1.0, 0.0);
945
946 let result = simulator
947 .evolve_system(initial_state, hamiltonian, 0.5)
948 .expect("Failed to evolve system");
949
950 assert!(!result.amplitudes.is_empty());
951 assert!(result.convergence_stats.converged);
952 }
953}