pub fn generate_idpp_path(
start: &[f64],
end: &[f64],
n_images: usize,
max_steps: usize,
) -> Vec<Vec<f64>> {
let n_xyz = start.len();
let n_atoms = n_xyz / 3;
if n_atoms < 2 || n_images < 2 {
return linear_interpolation(start, end, n_images);
}
let d_start = pairwise_distances(start, n_atoms);
let d_end = pairwise_distances(end, n_atoms);
let mut images = linear_interpolation(start, end, n_images);
let step_size = 0.04;
for _iter in 0..max_steps {
let prev = images.clone();
let mut max_shift = 0.0f64;
for img_idx in 1..(n_images - 1) {
let t = img_idx as f64 / (n_images - 1) as f64;
let d_target: Vec<f64> = d_start
.iter()
.zip(d_end.iter())
.map(|(&ds, &de)| (1.0 - t) * ds + t * de)
.collect();
let grad = idpp_gradient(&prev[img_idx], n_atoms, &d_target);
for k in 0..n_xyz {
images[img_idx][k] = prev[img_idx][k] - step_size * grad[k];
let shift = (images[img_idx][k] - prev[img_idx][k]).abs();
if shift > max_shift {
max_shift = shift;
}
}
}
if max_shift < 1e-4 {
break;
}
}
images
}
fn pairwise_distances(coords: &[f64], n_atoms: usize) -> Vec<f64> {
let n_pairs = n_atoms * (n_atoms - 1) / 2;
let mut dists = Vec::with_capacity(n_pairs);
for i in 0..n_atoms {
for j in (i + 1)..n_atoms {
let dx = coords[j * 3] - coords[i * 3];
let dy = coords[j * 3 + 1] - coords[i * 3 + 1];
let dz = coords[j * 3 + 2] - coords[i * 3 + 2];
dists.push((dx * dx + dy * dy + dz * dz).sqrt());
}
}
dists
}
fn idpp_gradient(coords: &[f64], n_atoms: usize, d_target: &[f64]) -> Vec<f64> {
let n_xyz = n_atoms * 3;
let mut grad = vec![0.0f64; n_xyz];
let mut pair_idx = 0;
for i in 0..n_atoms {
for j in (i + 1)..n_atoms {
let dx = coords[i * 3] - coords[j * 3];
let dy = coords[i * 3 + 1] - coords[j * 3 + 1];
let dz = coords[i * 3 + 2] - coords[j * 3 + 2];
let dist = (dx * dx + dy * dy + dz * dz).sqrt();
let d_tgt = d_target[pair_idx];
pair_idx += 1;
if dist < 1e-8 || d_tgt < 1e-8 {
continue;
}
let w = 1.0 / (d_tgt * d_tgt * d_tgt * d_tgt);
let factor = 2.0 * w * (dist - d_tgt) / dist;
grad[i * 3] += factor * dx;
grad[i * 3 + 1] += factor * dy;
grad[i * 3 + 2] += factor * dz;
grad[j * 3] -= factor * dx;
grad[j * 3 + 1] -= factor * dy;
grad[j * 3 + 2] -= factor * dz;
}
}
grad
}
fn linear_interpolation(start: &[f64], end: &[f64], n_images: usize) -> Vec<Vec<f64>> {
let n_xyz = start.len();
(0..n_images)
.map(|i| {
let t = if n_images > 1 {
i as f64 / (n_images - 1) as f64
} else {
0.0
};
(0..n_xyz)
.map(|k| (1.0 - t) * start[k] + t * end[k])
.collect()
})
.collect()
}