use crate::pimc::config::PimcConfig;
use scirs2_core::random::{Distribution, Normal, Rng, SeedableRng};
#[derive(Debug, Clone)]
pub struct RingPolymer {
pub beads: Vec<Vec<Vec<f64>>>,
pub n_particles: usize,
pub n_slices: usize,
pub dimension: usize,
}
impl RingPolymer {
pub fn new_random<R: Rng>(config: &PimcConfig, rng: &mut R) -> Self {
let normal = Normal::new(0.0_f64, 0.5_f64).expect("valid normal params");
let beads = (0..config.n_particles)
.map(|_| {
(0..config.n_slices)
.map(|_| {
(0..config.dimension)
.map(|_| normal.sample(rng))
.collect::<Vec<f64>>()
})
.collect::<Vec<Vec<f64>>>()
})
.collect::<Vec<Vec<Vec<f64>>>>();
Self {
beads,
n_particles: config.n_particles,
n_slices: config.n_slices,
dimension: config.dimension,
}
}
pub fn kinetic_action(&self, mass: f64, tau: f64) -> f64 {
let prefactor = mass / (2.0 * tau);
let mut sum = 0.0_f64;
for p in 0..self.n_particles {
for s in 0..self.n_slices {
let s_next = (s + 1) % self.n_slices;
let dist_sq = squared_distance(&self.beads[p][s], &self.beads[p][s_next]);
sum += dist_sq;
}
}
prefactor * sum
}
pub fn potential_action(&self, potential: &dyn Fn(&[f64]) -> f64, tau: f64) -> f64 {
let mut sum = 0.0_f64;
for p in 0..self.n_particles {
for s in 0..self.n_slices {
sum += potential(&self.beads[p][s]);
}
}
tau * sum
}
pub fn total_action(&self, potential: &dyn Fn(&[f64]) -> f64, mass: f64, tau: f64) -> f64 {
self.kinetic_action(mass, tau) + self.potential_action(potential, tau)
}
pub fn levy_bridge<R: Rng>(
start: &[f64],
end: &[f64],
n_beads: usize,
mass: f64,
tau: f64,
rng: &mut R,
) -> Vec<Vec<f64>> {
let dim = start.len();
let n_total = n_beads + 1;
(1..=n_beads)
.map(|k| {
let k_f = k as f64;
let n_f = n_total as f64;
let alpha = k_f / n_f;
let variance = (tau / mass) * k_f * (n_f - k_f) / n_f;
let std_dev = variance.max(0.0).sqrt();
(0..dim)
.map(|d| {
let mean = start[d] + alpha * (end[d] - start[d]);
if std_dev > 0.0 {
let normal =
Normal::new(mean, std_dev).expect("valid bridge normal params");
normal.sample(rng)
} else {
mean
}
})
.collect()
})
.collect()
}
}
#[inline]
pub(crate) fn squared_distance(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b.iter())
.map(|(ai, bi)| (ai - bi) * (ai - bi))
.sum()
}
pub(crate) use scirs2_core::random::seeded_rng;
pub(crate) type PimcRng =
scirs2_core::random::CoreRandom<scirs2_core::random::rand_prelude::StdRng>;
pub(crate) fn make_rng(seed: u64) -> PimcRng {
seeded_rng(seed)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pimc::config::PimcConfig;
fn default_rng() -> PimcRng {
make_rng(0)
}
#[test]
fn test_ring_polymer_dimensions() {
let cfg = PimcConfig {
n_particles: 2,
n_slices: 8,
dimension: 3,
..Default::default()
};
let mut rng = default_rng();
let poly = RingPolymer::new_random(&cfg, &mut rng);
assert_eq!(poly.beads.len(), 2);
assert_eq!(poly.beads[0].len(), 8);
assert_eq!(poly.beads[0][0].len(), 3);
}
#[test]
fn test_levy_bridge_endpoints_not_included() {
let start = vec![0.0_f64];
let end = vec![2.0_f64];
let mut rng = default_rng();
let bridge = RingPolymer::levy_bridge(&start, &end, 3, 1.0, 0.1, &mut rng);
assert_eq!(bridge.len(), 3);
}
#[test]
fn test_levy_bridge_zero_beads() {
let start = vec![1.0_f64, 2.0_f64];
let end = vec![3.0_f64, 4.0_f64];
let mut rng = default_rng();
let bridge = RingPolymer::levy_bridge(&start, &end, 0, 1.0, 0.05, &mut rng);
assert!(bridge.is_empty());
}
#[test]
fn test_kinetic_action_zero_for_constant_path() {
let cfg = PimcConfig {
n_particles: 1,
n_slices: 4,
dimension: 2,
..Default::default()
};
let mut poly = RingPolymer {
beads: vec![vec![vec![1.0_f64, 2.0_f64]; 4]],
n_particles: cfg.n_particles,
n_slices: cfg.n_slices,
dimension: cfg.dimension,
};
let _ = &mut poly; let s_k = poly.kinetic_action(1.0, 0.1);
assert!(s_k.abs() < 1e-12, "expected zero kinetic action, got {s_k}");
}
#[test]
fn test_potential_action_constant() {
let cfg = PimcConfig {
n_particles: 1,
n_slices: 4,
dimension: 1,
..Default::default()
};
let poly = RingPolymer {
beads: vec![vec![vec![0.0_f64]; 4]],
n_particles: cfg.n_particles,
n_slices: cfg.n_slices,
dimension: cfg.dimension,
};
let tau = 1.0 / 4.0; let sv = poly.potential_action(&|_: &[f64]| 1.0, tau);
assert!((sv - 1.0).abs() < 1e-12, "expected S_V=1.0, got {sv}");
}
}