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