uff_relax/forcefield/
interactions.rs1use glam::DVec3;
2use crate::params::get_uff_params;
3use crate::atom::UffAtomType;
4
5pub struct InteractionResult {
7 pub energy: f64,
8 pub forces: Vec<(usize, DVec3)>,
9}
10
11pub fn calculate_bond(
12 _i: usize,
13 _j: usize,
14 _pos_i: DVec3,
15 _pos_j: DVec3,
16 order: f32,
17 type_i: &UffAtomType,
18 type_j: &UffAtomType,
19 dist_vec: DVec3,
20) -> Option<(f64, DVec3)> {
21 let dist = dist_vec.length();
22 if dist < 1e-6 { return None; }
23
24 let pi = get_uff_params(type_i)?;
25 let pj = get_uff_params(type_j)?;
26
27 let r_bo = -0.1332 * (pi.r1 + pj.r1) * (order as f64).ln().max(0.0);
28 let r_en = pi.r1 * pj.r1 * (pi.chi.sqrt() - pj.chi.sqrt()).powi(2) / (pi.chi * pi.r1 + pj.chi * pj.r1);
29 let r0 = pi.r1 + pj.r1 + r_bo - r_en;
30
31 let k = 664.12 * pi.z_star * pj.z_star / (r0.powi(3));
32 let dr = dist - r0;
33 let energy = 0.5 * k * dr * dr;
34
35 let force_mag = -k * dr;
36 let f_vec = dist_vec.normalize() * force_mag.clamp(-1000.0, 1000.0);
37
38 Some((energy, f_vec))
39}
40
41pub fn calculate_lj(
42 dist_vec: DVec3,
43 type_i: &UffAtomType,
44 type_j: &UffAtomType,
45 cutoff_sq: f64,
46 scale: f64,
47) -> Option<(f64, DVec3)> {
48 let dist_sq = dist_vec.length_squared();
49 if dist_sq < cutoff_sq && dist_sq > 1e-12 {
50 let dist = dist_sq.sqrt();
51 let pi = get_uff_params(type_i)?;
52 let pj = get_uff_params(type_j)?;
53 let x_ij = (pi.x1 * pj.x1).sqrt();
54 let d_ij = (pi.d1 * pj.d1).sqrt() * scale;
55
56 let d = dist.max(x_ij * 0.1);
57
58 let r_ratio = x_ij / d;
59 let r_ratio_6 = r_ratio.powi(6);
60 let r_ratio_12 = r_ratio_6 * r_ratio_6;
61
62 let raw_energy = d_ij * (r_ratio_12 - 2.0 * r_ratio_6);
63 let raw_f_mag = 12.0 * d_ij / d * (r_ratio_12 - r_ratio_6);
64
65 let r_off = cutoff_sq.sqrt();
67 let r_on = 0.9 * r_off;
68
69 let (sw, dsw) = if d > r_on {
70 let x = (d - r_on) / (r_off - r_on);
71 let sw = 1.0 - 10.0*x.powi(3) + 15.0*x.powi(4) - 6.0*x.powi(5);
72 let dsw = (-30.0*x.powi(2) + 60.0*x.powi(3) - 30.0*x.powi(4)) / (r_off - r_on);
73 (sw, dsw)
74 } else {
75 (1.0, 0.0)
76 };
77
78 let energy = raw_energy * sw;
79 let f_mag = raw_f_mag * sw - raw_energy * dsw;
80
81 let clamped_f_mag = if f_mag > 0.0 {
83 f_mag.min(10000.0)
84 } else {
85 f_mag.max(-500.0)
86 };
87
88 let f_vec = dist_vec.normalize() * clamped_f_mag;
89 return Some((energy, f_vec));
90 }
91 None
92}
93
94pub fn calculate_angle(
95 _pos_i: DVec3, _pos_j: DVec3, _pos_k: DVec3,
96 type_i: &UffAtomType, type_j: &UffAtomType, type_k: &UffAtomType,
97 v_ji: DVec3, v_jk: DVec3,
98) -> Option<(f64, DVec3, DVec3, DVec3)> {
99 let pi_param = get_uff_params(type_i)?;
100 let pj_param = get_uff_params(type_j)?;
101 let pk_param = get_uff_params(type_k)?;
102
103 let r_ji = v_ji.length(); let r_jk = v_jk.length();
104 if r_ji < 1e-6 || r_jk < 1e-6 { return None; }
105
106 let cos_theta = (v_ji.dot(v_jk) / (r_ji * r_jk)).clamp(-1.0, 1.0);
107 let theta0_rad = pj_param.theta0.to_radians();
108 let cos_theta0 = theta0_rad.cos();
109
110 let r0_ij = pi_param.r1 + pj_param.r1;
111 let r0_jk = pk_param.r1 + pj_param.r1;
112 let r0_ik_sq = (r0_ij*r0_ij + r0_jk*r0_jk - 2.0*r0_ij*r0_jk*cos_theta0).max(1e-6);
113 let r0_ik = r0_ik_sq.sqrt();
114 let k_ijk = 664.12 * pi_param.z_star * pk_param.z_star / (r0_ik.powi(5)) *
115 (3.0 * r0_ij * r0_jk * (1.0 - cos_theta0.powi(2)) - r0_ik_sq * cos_theta0);
116
117 let theta = cos_theta.acos();
118 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt().max(1e-6);
119 let d_e_d_theta = k_ijk * (theta - theta0_rad);
120 let energy = 0.5 * k_ijk * (theta - theta0_rad).powi(2);
121
122 let grad_theta_i = ((v_ji * cos_theta / (r_ji * r_ji)) - (v_jk / (r_ji * r_jk))) / sin_theta;
123 let grad_theta_k = ((v_jk * cos_theta / (r_jk * r_jk)) - (v_ji / (r_ji * r_jk))) / sin_theta;
124
125 let f_i = grad_theta_i * (-d_e_d_theta);
126 let f_k = grad_theta_k * (-d_e_d_theta);
127 let f_j = -(f_i + f_k);
128
129 Some((energy, f_i, f_j, f_k))
130}
131
132pub fn calculate_torsion(
133 b1: DVec3, b2: DVec3, b3: DVec3,
134 type_j: &UffAtomType, type_k: &UffAtomType,
135 order: f32,
136) -> Option<(f64, DVec3, DVec3, DVec3, DVec3)> {
137 let pj = get_uff_params(type_j)?;
138 let pk = get_uff_params(type_k)?;
139
140 let n1 = b1.cross(b2); let n2 = b2.cross(b3);
141 let n1_mag_sq = n1.length_squared(); let n2_mag_sq = n2.length_squared();
142 if n1_mag_sq < 1e-9 || n2_mag_sq < 1e-9 { return None; }
143
144 let m1 = n1.cross(b2) / b2.length();
145 let cos_phi = (n1.dot(n2) / (n1_mag_sq.sqrt() * n2_mag_sq.sqrt())).clamp(-1.0, 1.0);
146 let sin_phi = (m1.dot(n2) / (m1.length() * n2_mag_sq.sqrt())).clamp(-1.0, 1.0);
147 let phi = sin_phi.atan2(cos_phi);
148
149 let mut v = 5.0 * (pj.u_i * pk.u_i).sqrt();
150 let mut n_period = 3.0;
151 let phi0 = 180.0f64.to_radians();
152
153 if order > 1.2 {
154 v = 5.0 * (pj.u_i * pk.u_i).sqrt() * (1.0 + 4.18 * (order as f64 - 1.0));
155 n_period = 2.0;
156 }
157
158 let energy = 0.5 * v * (1.0 - (n_period * phi0).cos() * (n_period * phi).cos());
159 let d_e_d_phi = 0.5 * v * n_period * (n_period * phi0).cos() * (n_period * phi).sin();
160
161 let f_i = n1 * (-d_e_d_phi * b2.length() / n1_mag_sq);
162 let f_l = n2 * (d_e_d_phi * b2.length() / n2_mag_sq);
163 let f_j = f_i * (b1.dot(b2) / b2.length_squared() - 1.0) - f_l * (b3.dot(b2) / b2.length_squared());
164 let f_k = -(f_i + f_l + f_j);
165
166 Some((energy, f_i, f_j, f_k, f_l))
167}
168
169pub fn calculate_inversion(
170 _p0: DVec3, _p1: DVec3, _p2: DVec3, _p3: DVec3,
171 v01: DVec3, v21: DVec3, v31: DVec3,
172) -> Option<(f64, DVec3, [DVec3; 3])> {
173 let cross = v21.cross(v31);
174 if cross.length_squared() < 1e-9 { return None; }
175 let normal = cross.normalize();
176 let h = v01.dot(normal);
177
178 let k_inv = 40.0;
179 let energy = 0.5 * k_inv * h * h;
180 let f_j = normal * (k_inv * h);
181 let f_others = [f_j * -0.333333, f_j * -0.333333, f_j * -0.333333];
182
183 Some((energy, f_j, f_others))
184}