Skip to main content

molframe_pdb/header/
mtrix.rs

1//! Non-crystallographic operators of the `MTRIX1..3` records.
2
3use crate::PdbHeaders;
4use crate::fixed;
5
6/// One non-crystallographic operator, from `MTRIX1..3`.
7#[derive(Clone, Debug, PartialEq)]
8pub struct Mtrix {
9    /// Operator serial number.
10    pub serial: u32,
11    /// Row-major rotation.
12    pub rotation: [[f64; 3]; 3],
13    /// Translation in ångström.
14    pub translation: [f64; 3],
15    /// Whether the transformed copy is already present in the coordinates.
16    pub given: bool,
17}
18
19/// Serial, the three rows read so far, and the given flag.
20type PartialOperator = (u32, [Option<[f64; 4]>; 3], bool);
21
22impl PdbHeaders {
23    /// The `MTRIX` operators in deposited order.
24    ///
25    /// A serial that lacks any of its three rows is dropped rather than
26    /// completed with zeros: a partial matrix would silently collapse atoms.
27    #[must_use]
28    pub fn mtrix(&self) -> Vec<Mtrix> {
29        let mut rows: Vec<PartialOperator> = Vec::new();
30        for (row, tag) in ["MTRIX1", "MTRIX2", "MTRIX3"].into_iter().enumerate() {
31            for record in self.named(tag) {
32                let line = record.line();
33                let Some(serial) = fixed::integer(line, 8, 10).and_then(|n| u32::try_from(n).ok())
34                else {
35                    continue;
36                };
37                let (Some(a), Some(b), Some(c), Some(t)) = (
38                    fixed::real(line, 11, 20),
39                    fixed::real(line, 21, 30),
40                    fixed::real(line, 31, 40),
41                    fixed::real(line, 46, 55),
42                ) else {
43                    continue;
44                };
45                let given = fixed::text(line, 60, 60) == "1";
46                let position = rows.iter().position(|entry| entry.0 == serial);
47                let position = if let Some(position) = position {
48                    position
49                } else {
50                    rows.push((serial, [None; 3], given));
51                    rows.len() - 1
52                };
53                rows[position].1[row] = Some([a, b, c, t]);
54            }
55        }
56        rows.into_iter()
57            .filter_map(|(serial, rows, given)| {
58                let [Some(x), Some(y), Some(z)] = rows else {
59                    return None;
60                };
61                Some(Mtrix {
62                    serial,
63                    rotation: [[x[0], x[1], x[2]], [y[0], y[1], y[2]], [z[0], z[1], z[2]]],
64                    translation: [x[3], y[3], z[3]],
65                    given,
66                })
67            })
68            .collect()
69    }
70}
71
72#[cfg(test)]
73#[path = "mtrix_tests.rs"]
74mod tests;