Skip to content

Commit 3e4811e

Browse files
committed
feat(potentials): Implement DREIDING Hydrogen Bond potential (12-10)
1 parent 14854a7 commit 3e4811e

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

src/potentials/nonbonded/hbond.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use crate::math::Real;
2+
use crate::traits::HybridKernel;
3+
use crate::types::HybridEnergyDiff;
4+
5+
/// DREIDING Hydrogen Bond potential (12-10).
6+
///
7+
/// # Physics
8+
///
9+
/// Models the explicit hydrogen bonding interaction (typically D-H...A) using a
10+
/// specific 12-10 Radial potential modulated by a $\cos^N\theta$ Angular term.
11+
/// Standard DREIDING uses $N=4$.
12+
///
13+
/// - **Formula**: $$ E = D_{hb} \left[ 5 \left(\frac{R_{hb}}{r}\right)^{12} - 6 \left(\frac{R_{hb}}{r}\right)^{10} \right] \cos^N \theta $$
14+
/// - **Derivative Factor (Radial)**: $$ D_{rad} = - \frac{1}{r} \frac{\partial E}{\partial r} = \frac{60 D_{hb}}{r^2} \left[ \left(\frac{R_{hb}}{r}\right)^{12} - \left(\frac{R_{hb}}{r}\right)^{10} \right] \cos^N \theta $$
15+
/// - **Derivative Factor (Angular)**: $$ D_{ang} = \frac{\partial E}{\partial (\cos\theta)} = N \cdot E_{rad} \cos^{N-1} \theta $$
16+
///
17+
/// # Parameters
18+
///
19+
/// - `d_hb`: The energy well depth $D_{hb}$.
20+
/// - `r_hb_sq`: The squared equilibrium distance $R_{hb}^2$.
21+
/// - `N`: The cosine power exponent (const generic).
22+
///
23+
/// # Inputs
24+
///
25+
/// - `r_sq`: Squared distance $r^2$ between Donor (D) and Acceptor (A).
26+
/// - `cos_theta`: Cosine of the angle $\theta_{DHA}$ (at Hydrogen).
27+
///
28+
/// # Implementation Notes
29+
///
30+
/// - **Cutoff**: If $\cos\theta \le 0$, energy and forces represent 0.
31+
/// - **Optimization**: Uses $s = (R_{hb}/r)^2$ recurrence to compute $r^{-10}$ and $r^{-12}$ efficiently.
32+
/// - **Generics**: Uses `const N: usize` to unroll power calculations at compile time.
33+
#[derive(Clone, Copy, Debug, Default)]
34+
pub struct HydrogenBond<const N: usize>;
35+
36+
impl<T: Real, const N: usize> HybridKernel<T> for HydrogenBond<N> {
37+
type Params = (T, T);
38+
39+
/// Computes only the potential energy.
40+
///
41+
/// # Formula
42+
///
43+
/// $$ E = D_{hb} (5s^6 - 6s^5) \cos^N \theta, \quad \text{where } s = (R_{hb}/r)^2 $$
44+
#[inline(always)]
45+
fn energy(r_sq: T, cos_theta: T, (d_hb, r_hb_sq): Self::Params) -> T {
46+
let effective_cos = cos_theta.max(T::from(0.0));
47+
48+
let cos_n = pow_n_helper(effective_cos, N);
49+
50+
let s = r_hb_sq * r_sq.recip();
51+
let s2 = s * s;
52+
let s4 = s2 * s2;
53+
let s5 = s4 * s;
54+
let s6 = s4 * s2;
55+
56+
let term12 = T::from(5.0) * s6;
57+
let term10 = T::from(6.0) * s5;
58+
59+
(d_hb * (term12 - term10)) * cos_n
60+
}
61+
62+
/// Computes only the derivative factors.
63+
///
64+
/// # Formula
65+
///
66+
/// $$ D_{rad} = \frac{60 D_{hb}}{r^2} (s^6 - s^5) \cos^N \theta, \quad \text{where } s = (R_{hb}/r)^2 $$
67+
/// $$ D_{ang} = N E_{rad} \cos^{N-1} \theta $$
68+
///
69+
/// - `force_factor_rad` ($D_{rad}$): Used to compute the central force along the D-A axis:
70+
/// $ \vec{F}_{rad} = -D\_{rad} \cdot \vec{r}\_{DA} $
71+
/// - `force_factor_ang` ($D_{ang}$): Used to compute torque-like forces on the D-H-A angle
72+
/// via the Wilson B-matrix gradient chain rule:
73+
/// $ \vec{F}_i = -D\_{ang} \cdot \nabla_i (\cos\theta) $
74+
#[inline(always)]
75+
fn diff(r_sq: T, cos_theta: T, (d_hb, r_hb_sq): Self::Params) -> (T, T) {
76+
let effective_cos = cos_theta.max(T::from(0.0));
77+
78+
let inv_r2 = r_sq.recip();
79+
let s = r_hb_sq * inv_r2;
80+
let s2 = s * s;
81+
let s4 = s2 * s2;
82+
let s5 = s4 * s;
83+
let s6 = s4 * s2;
84+
85+
let term12 = T::from(5.0) * s6;
86+
let term10 = T::from(6.0) * s5;
87+
let e_rad_pure = d_hb * (term12 - term10);
88+
89+
let cos_n_minus_1 = if N == 0 {
90+
T::from(0.0)
91+
} else if N == 1 {
92+
T::from(1.0)
93+
} else {
94+
pow_n_helper(effective_cos, N - 1)
95+
};
96+
97+
let cos_n = if N == 0 {
98+
T::from(1.0)
99+
} else {
100+
cos_n_minus_1 * effective_cos
101+
};
102+
103+
let diff_rad_pure = T::from(60.0) * d_hb * inv_r2 * (s6 - s5);
104+
let force_factor_rad = diff_rad_pure * cos_n;
105+
106+
let force_factor_ang = T::from(N as f32) * e_rad_pure * cos_n_minus_1;
107+
108+
(force_factor_rad, force_factor_ang)
109+
}
110+
111+
/// Computes both energy and derivative factors efficiently.
112+
///
113+
/// This method reuses intermediate calculations to minimize operations.
114+
#[inline(always)]
115+
fn compute(r_sq: T, cos_theta: T, (d_hb, r_hb_sq): Self::Params) -> HybridEnergyDiff<T> {
116+
let effective_cos = cos_theta.max(T::from(0.0));
117+
118+
let inv_r2 = r_sq.recip();
119+
let s = r_hb_sq * inv_r2;
120+
let s2 = s * s;
121+
let s4 = s2 * s2;
122+
let s5 = s4 * s;
123+
let s6 = s4 * s2;
124+
125+
let term12 = T::from(5.0) * s6;
126+
let term10 = T::from(6.0) * s5;
127+
let e_rad_pure = d_hb * (term12 - term10);
128+
129+
let cos_n_minus_1 = if N == 0 {
130+
T::from(0.0)
131+
} else if N == 1 {
132+
T::from(1.0)
133+
} else {
134+
pow_n_helper(effective_cos, N - 1)
135+
};
136+
137+
let cos_n = if N == 0 {
138+
T::from(1.0)
139+
} else {
140+
cos_n_minus_1 * effective_cos
141+
};
142+
143+
let energy = e_rad_pure * cos_n;
144+
145+
let diff_rad_pure = T::from(60.0) * d_hb * inv_r2 * (s6 - s5);
146+
let force_factor_rad = diff_rad_pure * cos_n;
147+
148+
let force_factor_ang = T::from(N as f32) * e_rad_pure * cos_n_minus_1;
149+
150+
HybridEnergyDiff {
151+
energy,
152+
force_factor_rad,
153+
force_factor_ang,
154+
}
155+
}
156+
}
157+
158+
/// Helper to compute x^n using explicit unrolling for small common powers,
159+
/// and fast exponentiation for larger n.
160+
#[inline(always)]
161+
fn pow_n_helper<T: Real>(base: T, n: usize) -> T {
162+
match n {
163+
0 => T::from(1.0),
164+
1 => base,
165+
2 => base * base,
166+
3 => base * base * base,
167+
4 => {
168+
let x2 = base * base;
169+
x2 * x2
170+
}
171+
5 => {
172+
let x2 = base * base;
173+
let x4 = x2 * x2;
174+
x4 * base
175+
}
176+
6 => {
177+
let x2 = base * base;
178+
let x4 = x2 * x2;
179+
x4 * x2
180+
}
181+
_ => {
182+
let mut acc = T::from(1.0);
183+
let mut b = base;
184+
let mut e = n;
185+
while e > 0 {
186+
if e & 1 == 1 {
187+
acc = acc * b;
188+
}
189+
b = b * b;
190+
e >>= 1;
191+
}
192+
acc
193+
}
194+
}
195+
}

src/potentials/nonbonded/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
mod coulomb;
2+
mod hbond;
23
mod vdw;
34

45
pub use coulomb::Coulomb;
6+
pub use hbond::HydrogenBond;
57
pub use vdw::{Buckingham, LennardJones, SplinedBuckingham};

0 commit comments

Comments
 (0)