use super::rng::{DistGeomRng, MinstdRand};
use nalgebra::{DMatrix, SymmetricEigen};
use rand::Rng;
pub fn pick_rdkit_distances<R: DistGeomRng>(rng: &mut R, bounds: &DMatrix<f64>) -> DMatrix<f64> {
let n = bounds.nrows();
let mut dists = DMatrix::from_element(n, n, 0.0f64);
for i in 1..n {
for j in 0..i {
let ub = bounds[(j, i)];
let lb = bounds[(i, j)];
let rval = rng.next_double();
let d = lb + rval * (ub - lb);
dists[(i, j)] = d;
dists[(j, i)] = d;
}
}
dists
}
pub fn pick_random_distances<R: Rng>(rng: &mut R, bounds: &DMatrix<f64>) -> DMatrix<f32> {
let n = bounds.nrows();
let mut dists = DMatrix::from_element(n, n, 0.0f32);
for i in 0..n {
for j in (i + 1)..n {
let u = bounds[(i, j)] as f32;
let l = bounds[(j, i)] as f32;
let d = if u > l {
let mid = (u + l) / 2.0;
let half_width = (u - l) / 2.0;
let r: f32 = rng.gen();
mid + half_width * (r - 0.5) * 2.0
} else {
l
};
dists[(i, j)] = d;
dists[(j, i)] = d;
}
}
dists
}
pub fn pick_etkdg_distances<R: Rng>(rng: &mut R, bounds: &DMatrix<f64>) -> DMatrix<f32> {
let n = bounds.nrows();
let mut dists = DMatrix::from_element(n, n, 0.0f32);
for i in 0..n {
for j in (i + 1)..n {
let u = bounds[(i, j)] as f32;
let l = bounds[(j, i)] as f32;
if u > l {
let range = u - l;
let r: f32 = rng.gen();
let d = l + range * r;
dists[(i, j)] = d;
dists[(j, i)] = d;
} else {
dists[(i, j)] = l;
dists[(j, i)] = l;
}
}
}
dists
}
pub fn compute_metric_matrix(dists: &DMatrix<f32>) -> DMatrix<f32> {
let n = dists.nrows();
let mut sq_dists: Vec<f64> = vec![0.0; n * n];
let mut sum_sq_all = 0.0f64;
for i in 0..n {
for j in 0..n {
let d = dists[(i, j)] as f64;
sq_dists[i * n + j] = d * d;
sum_sq_all += d * d;
}
}
sum_sq_all /= (n * n) as f64;
let mut d0: Vec<f64> = vec![0.0; n];
for i in 0..n {
let mut row_sum = 0.0f64;
for j in 0..n {
row_sum += sq_dists[i * n + j];
}
d0[i] = row_sum / n as f64 - sum_sq_all;
}
let mut metric = DMatrix::from_element(n, n, 0.0f32);
for i in 0..n {
for j in 0..=i {
let val = 0.5 * (d0[i] + d0[j] - sq_dists[i * n + j]);
metric[(i, j)] = val as f32;
metric[(j, i)] = val as f32;
}
}
metric
}
pub fn generate_nd_coordinates<R: Rng>(
rng: &mut R,
mm: &DMatrix<f32>,
ndim: usize,
) -> DMatrix<f32> {
let n = mm.nrows();
let eigen = SymmetricEigen::new(mm.clone());
let mut evals: Vec<(usize, f32)> = eigen.eigenvalues.iter().copied().enumerate().collect();
evals.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut coords = DMatrix::from_element(n, ndim, 0.0);
for d in 0..ndim {
if d < evals.len() {
let (idx, val) = evals[d];
if val > 1e-3 {
let root = val.sqrt();
let evec = eigen.eigenvectors.column(idx);
for i in 0..n {
coords[(i, d)] = evec[i] * root;
}
} else if val.abs() < 1e-3 {
for i in 0..n {
let r: f32 = rng.gen_range(-1.0..1.0);
coords[(i, d)] = r * 1e-3;
}
} else {
for i in 0..n {
let r: f32 = rng.gen_range(-1.0..1.0);
coords[(i, d)] = r;
}
}
} else {
for i in 0..n {
let r: f32 = rng.gen_range(-1.0..1.0);
coords[(i, d)] = r * 1e-3;
}
}
}
coords
}
pub fn compute_initial_coords_nalgebra(
rng: &mut MinstdRand,
mm: &DMatrix<f32>,
ndim: usize,
) -> Option<DMatrix<f32>> {
let n = mm.nrows();
let eigen = SymmetricEigen::new(mm.clone());
let mut evals: Vec<(usize, f32)> = eigen.eigenvalues.iter().copied().enumerate().collect();
evals.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut coords = DMatrix::from_element(n, ndim, 0.0f32);
for d in 0..ndim {
if d < evals.len() {
let (idx, val) = evals[d];
if val > 1e-3 {
let root = val.sqrt();
let evec = eigen.eigenvectors.column(idx);
for i in 0..n {
coords[(i, d)] = evec[i] * root;
}
} else {
for i in 0..n {
coords[(i, d)] = 1.0 - 2.0 * rng.next_double() as f32;
}
}
} else {
for i in 0..n {
coords[(i, d)] = 1.0 - 2.0 * rng.next_double() as f32;
}
}
}
Some(coords)
}
pub fn compute_initial_coords_nalgebra_f64(
rng: &mut MinstdRand,
dists: &DMatrix<f64>,
ndim: usize,
) -> Option<DMatrix<f64>> {
let n = dists.nrows();
let d_size = n * (n + 1) / 2;
let mut sq_packed = vec![0.0f64; d_size];
let mut sum_sq_all = 0.0f64;
for i in 0..n {
let id = i * (i + 1) / 2;
for j in 0..=i {
let d = dists[(i, j)];
sq_packed[id + j] = d * d;
sum_sq_all += d * d;
}
}
sum_sq_all /= (n * n) as f64;
let mut d0 = vec![0.0f64; n];
for i in 0..n {
let mut row_sum = 0.0f64;
for j in 0..n {
let idx = if i >= j {
i * (i + 1) / 2 + j
} else {
j * (j + 1) / 2 + i
};
row_sum += sq_packed[idx];
}
d0[i] = row_sum / n as f64 - sum_sq_all;
if d0[i] < EIGVAL_TOL && n > 3 {
return None;
}
}
let mut t_full = DMatrix::from_element(n, n, 0.0f64);
for i in 0..n {
for j in 0..=i {
let sq_val = sq_packed[if i >= j {
i * (i + 1) / 2 + j
} else {
j * (j + 1) / 2 + i
}];
let val = 0.5 * (d0[i] + d0[j] - sq_val);
t_full[(i, j)] = val;
t_full[(j, i)] = val;
}
}
let eigen = SymmetricEigen::new(t_full);
let mut evals: Vec<(usize, f64)> = eigen.eigenvalues.iter().copied().enumerate().collect();
evals.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let n_eigs = ndim.min(n);
let mut zero_eigs = 0u32;
let mut coords = DMatrix::from_element(n, ndim, 0.0f64);
for d in 0..ndim {
if d < n_eigs && d < evals.len() {
let (idx, val) = evals[d];
if val > EIGVAL_TOL {
let root = val.sqrt();
let evec = eigen.eigenvectors.column(idx);
for i in 0..n {
coords[(i, d)] = evec[i] * root;
}
} else if val.abs() < EIGVAL_TOL {
zero_eigs += 1;
for i in 0..n {
coords[(i, d)] = 1.0 - 2.0 * rng.next_double();
}
} else {
for i in 0..n {
coords[(i, d)] = 1.0 - 2.0 * rng.next_double();
}
}
} else {
for i in 0..n {
coords[(i, d)] = 1.0 - 2.0 * rng.next_double();
}
}
}
if zero_eigs >= 1 && n > 3 {
return None;
}
Some(coords)
}
pub fn power_eigen_solver(
num_eig: usize,
mat: &mut [f64], n: usize,
mut seed: i32,
) -> Option<(Vec<f64>, Vec<Vec<f64>>)> {
const MAX_ITERATIONS: usize = 1000;
const TOLERANCE: f64 = 0.001;
const HUGE_EIGVAL: f64 = 1.0e10;
const TINY_EIGVAL: f64 = 1.0e-10;
let mut eigenvalues = Vec::with_capacity(num_eig);
let mut eigenvectors = Vec::with_capacity(num_eig);
for ei in 0..num_eig {
let mut eig_val = -HUGE_EIGVAL;
seed += ei as i32;
let mut v = vec![0.0f64; n];
{
let mut rng = MinstdRand::new(seed as u32);
for vi in v.iter_mut() {
*vi = rng.next_double();
}
let mut ns = 0.0f64;
for x in v.iter() {
ns += x * x;
}
let norm = ns.sqrt();
if norm > 0.0 {
for vi in v.iter_mut() {
*vi /= norm;
}
}
}
let mut converged = false;
let mut z = vec![0.0f64; n];
for _iter in 0..MAX_ITERATIONS {
for i in 0..n {
let mut accum = 0.0f64;
let id = i * (i + 1) / 2;
for j in 0..=i {
accum += mat[id + j] * v[j];
}
for j in (i + 1)..n {
accum += mat[j * (j + 1) / 2 + i] * v[j];
}
z[i] = accum;
}
let prev_val = eig_val;
let mut eval_id = 0;
let mut max_abs = -1.0f64;
for i in 0..n {
let a = z[i].abs();
if a > max_abs {
max_abs = a;
eval_id = i;
}
}
eig_val = z[eval_id];
if eig_val.abs() < TINY_EIGVAL {
break;
}
for i in 0..n {
v[i] = z[i] / eig_val;
}
if (eig_val - prev_val).abs() < TOLERANCE {
converged = true;
break;
}
}
if !converged {
return None;
}
let mut norm_sum = 0.0f64;
for x in v.iter() {
norm_sum += x * x;
}
let norm = norm_sum.sqrt();
if norm > 0.0 {
for x in &mut v {
*x /= norm;
}
}
eigenvalues.push(eig_val);
eigenvectors.push(v.clone());
for i in 0..n {
let id = i * (i + 1) / 2;
for j in 0..=i {
mat[id + j] -= eig_val * v[i] * v[j];
}
}
}
Some((eigenvalues, eigenvectors))
}
const EIGVAL_TOL: f64 = 1.0e-3;
pub fn compute_initial_coords_rdkit<R: DistGeomRng>(
rng: &mut R,
dists: &DMatrix<f64>,
ndim: usize,
) -> Option<DMatrix<f64>> {
let n = dists.nrows();
let _sq_dists = vec![0.0f64; n * n];
let mut sum_sq_all = 0.0f64;
let d_size = n * (n + 1) / 2;
let mut sq_packed = vec![0.0f64; d_size];
for i in 0..n {
let id = i * (i + 1) / 2;
for j in 0..=i {
let d = dists[(i, j)];
sq_packed[id + j] = d * d;
sum_sq_all += d * d;
}
}
sum_sq_all /= (n * n) as f64;
let mut d0 = vec![0.0f64; n];
for i in 0..n {
let mut row_sum = 0.0f64;
for j in 0..n {
let idx = if i >= j {
i * (i + 1) / 2 + j
} else {
j * (j + 1) / 2 + i
};
row_sum += sq_packed[idx];
}
d0[i] = row_sum / n as f64 - sum_sq_all;
if d0[i] < EIGVAL_TOL && n > 3 {
if std::env::var("DEBUG_EMBED").is_ok() {
eprintln!(" D0[{}] = {:.6} < EIGVAL_TOL, n={}", i, d0[i], n);
}
return None;
}
}
let mut t_packed = vec![0.0f64; d_size];
for i in 0..n {
let id = i * (i + 1) / 2;
for j in 0..=i {
let sq_val = sq_packed[if i >= j {
i * (i + 1) / 2 + j
} else {
j * (j + 1) / 2 + i
}];
t_packed[id + j] = 0.5 * (d0[i] + d0[j] - sq_val);
}
}
let n_eigs = ndim.min(n);
let eigen_seed = (sum_sq_all * n as f64) as i32;
let (eigenvalues, eigenvectors) = match power_eigen_solver(n_eigs, &mut t_packed, n, eigen_seed)
{
Some(result) => result,
None => {
if std::env::var("DEBUG_EMBED").is_ok() {
eprintln!(
" power_eigen_solver failed, n={}, ndim={}, seed={}",
n, ndim, eigen_seed
);
}
return None;
}
};
let mut eig_sqrt = vec![0.0f64; n_eigs];
let mut _found_neg = false;
let mut zero_eigs = 0u32;
for i in 0..n_eigs {
if eigenvalues[i] > EIGVAL_TOL {
eig_sqrt[i] = eigenvalues[i].sqrt();
} else if eigenvalues[i].abs() < EIGVAL_TOL {
eig_sqrt[i] = 0.0;
zero_eigs += 1;
} else {
_found_neg = true;
eig_sqrt[i] = eigenvalues[i]; }
}
if zero_eigs >= 1 && n > 3 {
if std::env::var("DEBUG_EMBED").is_ok() {
eprintln!(
" zero_eigs={}, eigenvalues: {:?}",
zero_eigs, &eigenvalues
);
}
return None;
}
let mut coords = DMatrix::from_element(n, ndim, 0.0f64);
for i in 0..n {
for j in 0..ndim {
if j < n_eigs && eig_sqrt[j] >= 0.0 {
coords[(i, j)] = eig_sqrt[j] * eigenvectors[j][i];
} else {
coords[(i, j)] = 1.0 - 2.0 * rng.next_double();
}
}
}
Some(coords)
}
pub fn generate_3d_coordinates<R: Rng>(rng: &mut R, mm: &DMatrix<f32>) -> DMatrix<f32> {
generate_nd_coordinates(rng, mm, 3)
}
pub fn generate_smart_coordinates<R: Rng>(
rng: &mut R,
bounds: &DMatrix<f64>,
mol: &crate::graph::Molecule,
) -> DMatrix<f32> {
let n = mol.graph.node_count();
let dists = pick_random_distances(rng, bounds);
let metric = compute_metric_matrix(&dists);
let mut coords = generate_nd_coordinates(rng, &metric, 3);
for _iter in 0..5 {
for i in 0..n {
for j in (i + 1)..n {
let dij = ((coords[(i, 0)] - coords[(j, 0)]).powi(2)
+ (coords[(i, 1)] - coords[(j, 1)]).powi(2)
+ (coords[(i, 2)] - coords[(j, 2)]).powi(2))
.sqrt();
let l = bounds[(j, i)] as f32;
let u = bounds[(i, j)] as f32;
if dij < l {
let diff = (l - dij) / (dij + 1e-6);
coords[(i, 0)] -= diff * (coords[(i, 0)] - coords[(j, 0)]) * 0.1;
coords[(i, 1)] -= diff * (coords[(i, 1)] - coords[(j, 1)]) * 0.1;
coords[(i, 2)] -= diff * (coords[(i, 2)] - coords[(j, 2)]) * 0.1;
} else if dij > u {
let diff = (dij - u) / (dij + 1e-6);
coords[(i, 0)] += diff * (coords[(i, 0)] - coords[(j, 0)]) * 0.1;
coords[(i, 1)] += diff * (coords[(i, 1)] - coords[(j, 1)]) * 0.1;
coords[(i, 2)] += diff * (coords[(i, 2)] - coords[(j, 2)]) * 0.1;
}
}
}
}
coords
}