use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetawayDescriptors {
pub leverages: Vec<f64>,
pub hat_autocorrelation: Vec<f64>,
pub r_autocorrelation: Vec<f64>,
pub hat_total: f64,
pub r_total: f64,
pub h_information: f64,
pub h_max: f64,
pub h_mean: f64,
pub max_lag: usize,
}
pub fn compute_getaway(
elements: &[u8],
positions: &[[f64; 3]],
bonds: &[(usize, usize)],
max_lag: usize,
) -> GetawayDescriptors {
let n = elements.len().min(positions.len());
if n < 2 {
return GetawayDescriptors {
leverages: vec![],
hat_autocorrelation: vec![0.0; max_lag],
r_autocorrelation: vec![0.0; max_lag],
hat_total: 0.0,
r_total: 0.0,
h_information: 0.0,
h_max: 0.0,
h_mean: 0.0,
max_lag,
};
}
let mut centroid = [0.0f64; 3];
for i in 0..n {
for d in 0..3 {
centroid[d] += positions[i][d];
}
}
for d in 0..3 {
centroid[d] /= n as f64;
}
let mut xtx = [[0.0f64; 3]; 3];
for i in 0..n {
let dx = [
positions[i][0] - centroid[0],
positions[i][1] - centroid[1],
positions[i][2] - centroid[2],
];
for r in 0..3 {
for c in 0..3 {
xtx[r][c] += dx[r] * dx[c];
}
}
}
let xtx_inv = invert_3x3(&xtx);
let mut leverages = Vec::with_capacity(n);
for i in 0..n {
let dx = [
positions[i][0] - centroid[0],
positions[i][1] - centroid[1],
positions[i][2] - centroid[2],
];
let mut h = 0.0;
for r in 0..3 {
for c in 0..3 {
h += dx[r] * xtx_inv[r][c] * dx[c];
}
}
leverages.push(h);
}
let topo_dist = build_topo_distance(n, bonds);
let mut h_matrix = vec![vec![0.0f64; n]; n];
for i in 0..n {
h_matrix[i][i] = leverages[i];
let dxi = [
positions[i][0] - centroid[0],
positions[i][1] - centroid[1],
positions[i][2] - centroid[2],
];
for j in (i + 1)..n {
let dxj = [
positions[j][0] - centroid[0],
positions[j][1] - centroid[1],
positions[j][2] - centroid[2],
];
let mut hij = 0.0;
for r in 0..3 {
for c in 0..3 {
hij += dxi[r] * xtx_inv[r][c] * dxj[c];
}
}
h_matrix[i][j] = hij;
h_matrix[j][i] = hij;
}
}
let mut geo_dist = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in (i + 1)..n {
let dx = positions[i][0] - positions[j][0];
let dy = positions[i][1] - positions[j][1];
let dz = positions[i][2] - positions[j][2];
let r = (dx * dx + dy * dy + dz * dz).sqrt();
geo_dist[i][j] = r;
geo_dist[j][i] = r;
}
}
let mut hat_autocorrelation = vec![0.0f64; max_lag];
let mut r_autocorrelation = vec![0.0f64; max_lag];
let mut hat_total = 0.0;
let mut r_total = 0.0;
for i in 0..n {
for j in (i + 1)..n {
let d = topo_dist[i][j];
if d == 0 || d > max_lag {
continue;
}
let k = d - 1;
let hi = leverages[i].max(0.0).sqrt();
let hj = leverages[j].max(0.0).sqrt();
hat_autocorrelation[k] += hi * hj;
let rij = geo_dist[i][j];
if rij > 1e-12 {
r_autocorrelation[k] += hi * hj / rij;
}
hat_total += h_matrix[i][j].abs();
if rij > 1e-12 {
r_total += h_matrix[i][j].abs() / rij;
}
}
}
let h_sum: f64 = leverages.iter().sum();
let h_information = if h_sum > 1e-12 {
let mut entropy = 0.0;
for &h in &leverages {
let p = h / h_sum;
if p > 1e-12 {
entropy -= p * p.ln();
}
}
entropy
} else {
0.0
};
let h_max = leverages.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let h_mean = if n > 0 { h_sum / n as f64 } else { 0.0 };
GetawayDescriptors {
leverages,
hat_autocorrelation,
r_autocorrelation,
hat_total,
r_total,
h_information,
h_max,
h_mean,
max_lag,
}
}
fn build_topo_distance(n: usize, bonds: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut adj = vec![vec![]; n];
for &(a, b) in bonds {
if a < n && b < n {
adj[a].push(b);
adj[b].push(a);
}
}
let mut dist = vec![vec![0usize; n]; n];
for start in 0..n {
let mut visited = vec![false; n];
visited[start] = true;
let mut queue = std::collections::VecDeque::new();
queue.push_back((start, 0usize));
while let Some((node, d)) = queue.pop_front() {
dist[start][node] = d;
for &nb in &adj[node] {
if !visited[nb] {
visited[nb] = true;
queue.push_back((nb, d + 1));
}
}
}
}
dist
}
fn invert_3x3(m: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
- m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
if det.abs() < 1e-12 {
return [[0.0; 3]; 3];
}
let inv_det = 1.0 / det;
let mut inv = [[0.0f64; 3]; 3];
inv[0][0] = (m[1][1] * m[2][2] - m[1][2] * m[2][1]) * inv_det;
inv[0][1] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * inv_det;
inv[0][2] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * inv_det;
inv[1][0] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * inv_det;
inv[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * inv_det;
inv[1][2] = (m[0][2] * m[1][0] - m[0][0] * m[1][2]) * inv_det;
inv[2][0] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * inv_det;
inv[2][1] = (m[0][1] * m[2][0] - m[0][0] * m[2][1]) * inv_det;
inv[2][2] = (m[0][0] * m[1][1] - m[0][1] * m[1][0]) * inv_det;
inv
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_getaway_ethane() {
let elements = vec![6, 6, 1, 1, 1, 1, 1, 1];
let positions = vec![
[0.0, 0.0, 0.0], [1.54, 0.0, 0.0], [-0.5, 0.9, 0.0], [-0.5, -0.9, 0.0], [-0.5, 0.0, 0.9], [2.04, 0.9, 0.0], [2.04, -0.9, 0.0], [2.04, 0.0, 0.9], ];
let bonds = vec![(0, 1), (0, 2), (0, 3), (0, 4), (1, 5), (1, 6), (1, 7)];
let g = compute_getaway(&elements, &positions, &bonds, 8);
assert_eq!(g.leverages.len(), 8);
assert!(g.hat_total > 0.0);
assert!(g.h_max > 0.0);
}
#[test]
fn test_getaway_empty() {
let g = compute_getaway(&[], &[], &[], 8);
assert!(g.leverages.is_empty());
assert_eq!(g.hat_total, 0.0);
}
}