1use super::*;
3use vecfx::*;
4
5type Point3 = [f64; 3];
6mod qcprot;
10mod quaternion;
11#[derive(Clone, Copy, Debug)]
15pub enum SuperpositionAlgo {
16 QCP,
17 Quaternion,
18}
19
20impl Default for SuperpositionAlgo {
21 fn default() -> Self {
22 SuperpositionAlgo::QCP
23 }
25}
26
27#[derive(Clone, Debug)]
29pub struct Superposition {
30 pub rmsd: f64,
32
33 pub translation: Vector3f,
35
36 pub rotation_matrix: Matrix3f,
38}
39
40impl Superposition {
41 pub fn apply(&self, conf: &[[f64; 3]]) -> Vec<[f64; 3]> {
43 let mut res = Vec::with_capacity(conf.len());
44 for &v in conf {
45 let v = Vector3f::from(v);
46 let v = self.rotation_matrix * v + self.translation;
47 res.push(v.into());
48 }
49
50 if res.as_flat().iter().any(|x| x.is_nan()) {
52 dbg!(&self);
53 panic!("found invalid float numbers!");
54 }
55
56 res
57 }
58
59 pub fn apply_translation(&self, conf: &[Point3]) -> Vec<Point3> {
61 let mut res = Vec::with_capacity(conf.len());
62 for &v in conf {
63 let v = Vector3f::from(v);
64 let v = v + self.translation;
65 res.push(v.into());
66 }
67
68 res
69 }
70
71 pub fn apply_rotation(&self, conf: &[Point3]) -> Vec<Point3> {
73 let mut res = Vec::with_capacity(conf.len());
74 for &v in conf {
75 let v = Vector3f::from(v);
76 let v = v + self.rotation_matrix * v;
77 res.push(v.into());
78 }
79
80 res
81 }
82}
83#[deprecated(note = "use Superpose instead")]
87#[derive(Clone, Debug)]
89pub struct Alignment<'a> {
90 positions: &'a [[f64; 3]],
92
93 pub algorithm: SuperpositionAlgo,
95}
96
97impl<'a> Alignment<'a> {
98 pub fn new(positions: &'a [[f64; 3]]) -> Self {
100 Alignment {
101 positions,
102 algorithm: SuperpositionAlgo::default(),
103 }
104 }
105
106 pub fn rmsd(&self, reference: &[[f64; 3]], weights: Option<&[f64]>) -> Result<f64> {
113 let npts = self.positions.len();
115 if reference.len() != npts {
116 bail!("points size mismatch!");
117 }
118 if weights.is_some() && weights.unwrap().len() != npts {
119 bail!("weights size mismatch!");
120 }
121
122 let mut ws = 0.0f64;
124 for i in 0..npts {
125 let wi = weights.map_or_else(|| 1.0, |w| w[i]);
127 let dx = wi * (self.positions[i][0] - reference[i][0]);
128 let dy = wi * (self.positions[i][1] - reference[i][1]);
129 let dz = wi * (self.positions[i][2] - reference[i][2]);
130
131 ws += dx.powi(2) + dy.powi(2) + dz.powi(2);
132 }
133 let ws = ws.sqrt();
134
135 Ok(ws)
136 }
137
138 pub fn superpose(&mut self, reference: &[[f64; 3]], weights: Option<&[f64]>) -> Result<Superposition> {
146 let (rmsd, trans, rot) = match self.algorithm {
148 SuperpositionAlgo::QCP => self::qcprot::calc_rmsd_rotational_matrix(&reference, &self.positions, weights),
149 SuperpositionAlgo::Quaternion => self::quaternion::calc_rmsd_rotational_matrix(&reference, &self.positions, weights),
150 };
151
152 let rotation_matrix = if let Some(rot) = rot {
154 Matrix3f::from_row_slice(&rot)
155 } else {
156 Matrix3f::identity()
157 };
158
159 let sp = Superposition {
161 rmsd,
162 translation: trans.into(),
163 rotation_matrix,
164 };
165
166 Ok(sp)
167 }
168}
169pub type Superimpose<'a> = Superpose<'a>;
174
175#[derive(Clone, Debug)]
177pub struct Superpose<'a> {
178 positions: &'a [[f64; 3]],
180
181 pub algorithm: SuperpositionAlgo,
183}
184
185impl<'a> Superpose<'a> {
186 pub fn new(positions: &'a [[f64; 3]]) -> Self {
188 Self {
189 positions,
190 algorithm: SuperpositionAlgo::default(),
191 }
192 }
193
194 pub fn rmsd(&self, reference: &[[f64; 3]], weights: Option<&[f64]>) -> Result<f64> {
201 let npts = self.positions.len();
203 if reference.len() != npts {
204 bail!("points size mismatch!");
205 }
206 if weights.is_some() && weights.unwrap().len() != npts {
207 bail!("weights size mismatch!");
208 }
209
210 let mut ws = 0.0f64;
212 for i in 0..npts {
213 let wi = weights.map_or_else(|| 1.0, |w| w[i]);
215 let dx = wi * (self.positions[i][0] - reference[i][0]);
216 let dy = wi * (self.positions[i][1] - reference[i][1]);
217 let dz = wi * (self.positions[i][2] - reference[i][2]);
218
219 ws += dx.powi(2) + dy.powi(2) + dz.powi(2);
220 }
221 let ws = ws.sqrt();
222
223 Ok(ws)
224 }
225
226 pub fn onto(&mut self, reference: &[[f64; 3]], weights: Option<&[f64]>) -> Superposition {
234 let (rmsd, trans, rot) = match self.algorithm {
236 SuperpositionAlgo::QCP => self::qcprot::calc_rmsd_rotational_matrix(&reference, &self.positions, weights),
237 SuperpositionAlgo::Quaternion => self::quaternion::calc_rmsd_rotational_matrix(&reference, &self.positions, weights),
238 };
239
240 let rotation_matrix = if let Some(rot) = rot {
242 Matrix3f::from_row_slice(&rot)
243 } else {
244 Matrix3f::identity()
245 };
246
247 Superposition {
249 rmsd,
250 translation: trans.into(),
251 rotation_matrix,
252 }
253 }
254}
255#[test]
259fn test_alignment() {
260 use vecfx::*;
261
262 let (reference, candidate, weights) = qcprot::prepare_test_data();
264
265 let sp = Superpose::new(&candidate).onto(&reference, Some(&weights));
267 let rot = sp.rotation_matrix;
268
269 let rot_expected = Matrix3f::from_row_slice(&[
271 0.77227551,
272 0.63510272,
273 -0.01533190,
274 -0.44544846,
275 0.52413614,
276 -0.72584914,
277 -0.45295276,
278 0.56738509,
279 0.68768304,
280 ]);
281 approx::assert_relative_eq!(rot_expected, rot, epsilon = 1e-4);
282}
283#[test]
287fn test_alignment_hcn() {
288 use vecfx::*;
289
290 let positions_ref = [
291 [0.83334699, 0.716865, 0.0],
292 [-0.5486581, -0.35588, 0.0],
293 [-0.2855828, 1.036928, 0.0],
294 ];
295
296 let positions_can = [[-0.634504, -0.199638, -0.0], [0.970676, 0.670662, 0.0], [-0.337065, 0.926883, 0.0]];
297
298 let weights = vec![0.0001; 3];
299 let sp = Superpose::new(&positions_can).onto(&positions_ref, Some(&weights));
300 approx::assert_relative_eq!(sp.rmsd, 0.0614615, epsilon = 1e-4);
301
302 let t = Vector3f::from([0.423160235, 0.2715202, 0.0]);
303 let r = Matrix3f::from_column_slice(&[-0.4167190, -0.9090353, 0.0, -0.909035, 0.4167190, 0.0, 0.0, 0.0, -1.0]);
304 approx::assert_relative_eq!(sp.rotation_matrix, r, epsilon = 1e-4);
305}
306