use super::*;
use gchemol::{Bond, Molecule};
use std::collections::HashMap;
#[derive(Clone, Debug, Copy, Serialize, Deserialize)]
pub struct ElasticSpring {
pub u: usize,
pub v: usize,
pub k: Option<f64>,
pub l: Option<f64>,
pub r: Option<f64>,
}
impl ElasticSpring {
pub fn spring_constant(&self) -> f64 {
let k = self.k.unwrap_or(1.0);
assert!(k.is_sign_positive(), "invalid spring constant {}", k);
k
}
pub fn natural_length(&self) -> f64 {
let l = self.l.expect("natural length not set");
assert!(l.is_sign_positive(), "invalid spring natural length {}", l);
l
}
pub fn hook_length(&self) -> f64 {
let r = self.r.unwrap_or_default();
assert!(r.is_sign_positive(), "invalid spring hook length {}", r);
r
}
pub fn atom1(&self) -> usize {
if self.u > self.v {
self.v
} else {
self.u
}
}
pub fn atom2(&self) -> usize {
if self.u > self.v {
self.u
} else {
self.v
}
}
fn elastic_energy_between(&self, pij: Vector3f) -> f64 {
let d = pij.norm();
let l = self.natural_length();
let k = self.spring_constant();
let r = self.hook_length();
info!(
"distance({}--{}) = {:4.2}, spring (l= {}, r= {}, k= {})",
self.u, self.v, d, l, r, k
);
let vij = if d > l && d < l + r {
0.0
} else {
0.5 * k * (l - d).powi(2)
};
vij
}
fn elastic_force_between(&self, pij: Vector3f) -> Vector3f {
let d = pij.norm();
let l = self.natural_length();
let k = self.spring_constant();
let r = self.hook_length();
let k = self.spring_constant();
let r = self.hook_length();
let fij = -k * (l - d) * pij / l;
if d > l && d < l + r {
fij * 0.0
} else {
fij
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ElasticModel {
pub constraints: HashMap<String, ElasticSpring>,
}
use vecfx::*;
impl ElasticModel {
fn get_elastic_energy_between(&self, mol: &Molecule, i: usize, j: usize, b: &Bond) -> Option<f64> {
let sij = self.get_spring_between(i, j)?;
let pij = get_pij_vector(mol, i, j)?;
let vij = sij.elastic_energy_between(pij);
Some(vij)
}
fn get_elastic_force_between(&self, mol: &Molecule, i: usize, j: usize) -> Option<Vector3f> {
let sij = mol.get_bond(i, j).and_then(|b| self.get_spring_between(i, j))?;
let pij = get_pij_vector(mol, i, j)?;
sij.elastic_force_between(pij).into()
}
fn get_spring_between(&self, u: usize, v: usize) -> Option<ElasticSpring> {
let (u, v) = if u > v { (v, u) } else { (u, v) };
self.constraints.iter().find_map(|(_, &s)| {
if s.atom1() == u && s.atom2() == v {
Some(s)
} else {
None
}
})
}
fn add_dummy_bonds(&self, mol: &mut Molecule) {
use gchemol::Bond;
for (_, s) in &self.constraints {
mol.add_bond(s.atom1(), s.atom2(), Bond::dummy());
}
}
}
fn get_pij_vector(mol: &Molecule, i: usize, j: usize) -> Option<Vector3f> {
let pi: Vector3f = mol.get_atom(i)?.position().into();
let pj: Vector3f = mol.get_atom(j)?.position().into();
if let Some(lat) = mol.lattice {
lat.apply_mic(pj - pi)
} else {
pj - pi
}
.into()
}
use gosh_model::*;
use gut::config::*;
impl ElasticModel {
pub fn apply_constraints(&self, mol: &mut Molecule) {
self.add_dummy_bonds(mol);
}
pub fn elastic_energy(&self, mol: &Molecule) -> f64 {
mol.bonds()
.map(|(i, j, b)| self.get_elastic_energy_between(mol, i, j, b).unwrap_or_default())
.sum()
}
pub fn elastic_force(&self, mol: &Molecule) -> Vec<[f64; 3]> {
mol.atoms()
.map(|(i, a)| {
let fi: Vector3f = mol
.connected(i)
.map(|j| self.get_elastic_force_between(mol, i, j).unwrap_or_default())
.sum();
fi.into()
})
.collect()
}
}
impl ChemicalModel for ElasticModel {
fn compute(&mut self, mol: &Molecule) -> Result<ModelProperties> {
let mut mp = ModelProperties::default();
mp.set_energy(self.elastic_energy(&mol));
mp.set_forces(self.elastic_force(&mol));
Ok(mp)
}
}