#[derive(Debug, Clone)]
pub struct ParallelBoundsConfig {
pub gpu_threshold: usize,
pub use_gpu: bool,
}
impl Default for ParallelBoundsConfig {
fn default() -> Self {
Self {
gpu_threshold: 500,
use_gpu: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ParallelBoundsResult {
pub lower: Vec<f64>,
pub upper: Vec<f64>,
pub n: usize,
pub used_gpu: bool,
pub time_ms: f64,
}
#[cfg(feature = "parallel")]
pub fn compute_bounds_parallel(
elements: &[u8],
bonds: &[(usize, usize, String)],
) -> ParallelBoundsResult {
use rayon::prelude::*;
use std::time::Instant;
let start = Instant::now();
let n = elements.len();
let adj = build_adjacency(n, bonds);
let pairs: Vec<(usize, usize)> = (0..n)
.flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
.collect();
let bounds: Vec<(usize, usize, f64, f64)> = pairs
.par_iter()
.map(|&(i, j)| {
let (lower, upper) = compute_pair_bounds(elements, &adj, i, j);
(i, j, lower, upper)
})
.collect();
let mut lower_mat = vec![0.0f64; n * n];
let mut upper_mat = vec![f64::MAX; n * n];
for &(i, j, lo, hi) in &bounds {
lower_mat[i * n + j] = lo;
lower_mat[j * n + i] = lo;
upper_mat[i * n + j] = hi;
upper_mat[j * n + i] = hi;
}
let time_ms = start.elapsed().as_secs_f64() * 1000.0;
ParallelBoundsResult {
lower: lower_mat,
upper: upper_mat,
n,
used_gpu: false,
time_ms,
}
}
pub fn smooth_bounds_gpu(bounds: &mut [f64], n: usize, is_upper: bool) -> bool {
#[cfg(feature = "experimental-gpu")]
{
if n >= 500 {
if let Some(result) = try_gpu_floyd_warshall(bounds, n, is_upper) {
bounds.copy_from_slice(&result);
return true;
}
}
}
floyd_warshall_cpu(bounds, n, is_upper);
false
}
pub fn floyd_warshall_cpu(matrix: &mut [f64], n: usize, is_upper: bool) {
for k in 0..n {
for i in 0..n {
for j in 0..n {
if i == j || i == k || j == k {
continue;
}
let via_k = matrix[i * n + k] + matrix[k * n + j];
if is_upper {
if via_k < matrix[i * n + j] {
matrix[i * n + j] = via_k;
}
} else {
if via_k > matrix[i * n + j] {
matrix[i * n + j] = via_k;
}
}
}
}
}
}
#[cfg(feature = "parallel")]
pub fn floyd_warshall_parallel(matrix: &mut [f64], n: usize, is_upper: bool) {
use rayon::prelude::*;
for k in 0..n {
let k_row: Vec<f64> = (0..n).map(|j| matrix[k * n + j]).collect();
let k_col: Vec<f64> = (0..n).map(|i| matrix[i * n + k]).collect();
let row_updates: Vec<Vec<(usize, f64)>> = (0..n)
.into_par_iter()
.map(|i| {
if i == k {
return vec![];
}
let mut updates = Vec::new();
for j in 0..n {
if j == i || j == k {
continue;
}
let via_k = k_col[i] + k_row[j];
let current = matrix[i * n + j];
if is_upper {
if via_k < current {
updates.push((j, via_k));
}
} else if via_k > current {
updates.push((j, via_k));
}
}
updates
})
.collect();
for (i, updates) in row_updates.into_iter().enumerate() {
for (j, val) in updates {
matrix[i * n + j] = val;
}
}
}
}
#[cfg(feature = "experimental-gpu")]
fn try_gpu_floyd_warshall(bounds: &[f64], n: usize, _is_upper: bool) -> Option<Vec<f64>> {
use crate::gpu::context::GpuContext;
let ctx = match GpuContext::try_create() {
Ok(ctx) => ctx,
Err(_) => return None,
};
let matrix_bytes = n * n * std::mem::size_of::<f32>();
if matrix_bytes > 128 * 1024 * 1024 {
return None; }
let data_f32: Vec<f32> = bounds
.iter()
.map(|&v| if v == f64::MAX { f32::MAX } else { v as f32 })
.collect();
let _ = (ctx, data_f32);
None
}
fn build_adjacency(n: usize, bonds: &[(usize, usize, String)]) -> Vec<Vec<(usize, f64)>> {
let mut adj = vec![vec![]; n];
for (a, b, order) in bonds {
let weight = match order.as_str() {
"SINGLE" => 1.0,
"DOUBLE" => 1.0,
"TRIPLE" => 1.0,
"AROMATIC" => 1.0,
_ => 1.0,
};
adj[*a].push((*b, weight));
adj[*b].push((*a, weight));
}
adj
}
fn compute_pair_bounds(
elements: &[u8],
adj: &[Vec<(usize, f64)>],
i: usize,
j: usize,
) -> (f64, f64) {
let ri = crate::graph::get_covalent_radius(elements[i]);
let rj = crate::graph::get_covalent_radius(elements[j]);
let n = adj.len();
let mut dist = vec![usize::MAX; n];
dist[i] = 0;
let mut queue = std::collections::VecDeque::new();
queue.push_back(i);
while let Some(node) = queue.pop_front() {
for &(neighbor, _) in &adj[node] {
if dist[neighbor] == usize::MAX {
dist[neighbor] = dist[node] + 1;
queue.push_back(neighbor);
}
}
}
let path_len = dist[j];
if path_len == usize::MAX {
let vi = crate::graph::get_vdw_radius(elements[i]);
let vj = crate::graph::get_vdw_radius(elements[j]);
return (vi + vj - 0.5, 100.0);
}
let lower = (ri + rj) * 0.8 * path_len as f64;
let upper = (ri + rj + 0.4) * path_len as f64;
(lower.max(0.1), upper)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_floyd_warshall_cpu() {
let mut m = vec![0.0, 1.0, f64::MAX, 1.0, 0.0, 2.0, f64::MAX, 2.0, 0.0];
floyd_warshall_cpu(&mut m, 3, true);
assert!((m[2] - 3.0).abs() < 1e-10);
}
#[cfg(feature = "parallel")]
#[test]
fn test_compute_bounds_parallel() {
let elements = vec![6u8, 6, 8]; let bonds = vec![(0, 1, "SINGLE".to_string()), (1, 2, "SINGLE".to_string())];
let result = compute_bounds_parallel(&elements, &bonds);
assert_eq!(result.n, 3);
assert!(result.lower[1] > 0.0);
}
}