1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// [[file:../gchemol-core.note::735b5f39][735b5f39]]
pub use gchemol_lattice::Lattice;

use crate::atom::Vector3f;
use crate::common::*;
use crate::molecule::Molecule;
// 735b5f39 ends here

// [[file:../gchemol-core.note::a86668b2][a86668b2]]
/// Lattice related methods
impl Molecule {
    #[cfg(feature = "adhoc")]
    /// Get a reference to `Lattice` struct.
    pub fn get_lattice(&self) -> Option<&Lattice> {
        self.lattice.as_ref()
    }

    #[cfg(feature = "adhoc")]
    /// Get a mutable reference to `Lattice` struct.
    pub fn get_lattice_mut(&mut self) -> Option<&mut Lattice> {
        self.lattice.as_mut()
    }

    /// Set periodic lattice as `lat`.
    pub fn set_lattice(&mut self, lat: Lattice) {
        self.lattice = Some(lat);
    }

    /// Set periodic Lattice as `lat`, and scaled atom positions to fit new
    /// lattice. Will panic if not a periodic structure.
    pub fn set_lattice_scaled(&mut self, lat: Lattice) {
        let fracs: Vec<_> = self.get_scaled_positions().expect("not periodic struct").collect();
        self.set_lattice(lat);
        self.set_scaled_positions(fracs);
    }

    /// Return true if `Molecule` is a periodic structure.
    pub fn is_periodic(&self) -> bool {
        self.lattice.is_some()
    }

    /// Unbuild current crystal structure leaving a nonperiodic
    /// structure. Return `Lattice` object if periodic, otherwise return None.
    pub fn unbuild_crystal(&mut self) -> Option<Lattice> {
        self.lattice.take()
    }

    #[deprecated(note = "use get_scaled_positions instead")]
    /// Return fractional coordinates relative to unit cell. Return None if not
    /// a periodic structure
    pub fn scaled_positions(&self) -> Option<impl Iterator<Item = [f64; 3]> + '_> {
        self.get_scaled_positions()
    }

    // FIXME: avoid type conversion
    /// Return fractional coordinates relative to unit cell. Return None if not
    /// a periodic structure
    pub fn get_scaled_positions(&self) -> Option<impl Iterator<Item = [f64; 3]> + '_> {
        self.lattice.map(|lat| self.positions().map(move |cart| lat.to_frac(cart).into()))
    }

    /// Set fractional coordinates of atoms in sequence order.
    ///
    /// Panics if Molecule is aperiodic.
    pub fn set_scaled_positions<T, P>(&mut self, scaled: T)
    where
        T: IntoIterator<Item = P>,
        P: Into<Vector3f>,
    {
        let lat = self.lattice.expect("cannot set scaled positions for aperiodic structure");
        let positions = scaled.into_iter().map(|frac| lat.to_cart(frac));
        self.set_positions(positions);
    }

    /// Set fractional coordinates of atoms specified in serial numbers.
    ///
    /// Panics if Molecule is aperiodic.
    pub fn set_scaled_positions_from<T, P>(&mut self, scaled: T)
    where
        T: IntoIterator<Item = (usize, P)>,
        P: Into<Vector3f>,
    {
        let lat = self.lattice.expect("cannot set scaled positions for aperiodic structure");

        for (i, fi) in scaled {
            let pi = lat.to_cart(fi);
            self.set_position(i, pi);
        }
    }

    #[cfg(feature = "adhoc")]
    /// Create a supercell.
    ///
    /// # Arguments
    ///
    /// * sa, sb, sc: An sequence of three scaling factors. E.g., [2, 1, 1]
    /// specifies that the supercell should have dimensions 2a x b x c
    pub fn supercell(&self, sa: usize, sb: usize, sc: usize) -> Option<Molecule> {
        // add atoms by looping over three lattice directions
        let lat = self.lattice?;
        let (sa, sb, sc) = (sa as isize, sb as isize, sc as isize);
        let mut atoms = vec![];
        for image in lat.replicate(0..sa, 0..sb, 0..sc) {
            let mut m = self.clone();
            let t = lat.to_cart(image);
            m.translate(t);
            for (_, atom) in m.atoms() {
                atoms.push(atom.clone());
            }
        }
        let mut mol_new = Molecule::from_atoms(atoms);

        // update lattice
        let mut vabc = lat.vectors();
        let size = [sa, sb, sc];
        for v in vabc.iter_mut() {
            for i in 0..3 {
                v[i] *= size[i] as f64;
            }
        }
        mol_new.name = self.name.to_string();

        mol_new.lattice = Some(Lattice::new(vabc));
        Some(mol_new)
    }
}
// a86668b2 ends here

// [[file:../gchemol-core.note::599d9ac9][599d9ac9]]
impl Molecule {
    #[cfg(feature = "adhoc")]
    /// Create a `Lattice` from the minimal bounding box of the `Molecule`
    /// extended by a positive value of `padding`. NOTE: padding has to be large
    /// enough (> 0.5) to avoid self interaction with its periodic mirror.
    pub fn set_lattice_from_bounding_box(&mut self, padding: f64) {
        self.recenter();
        let [a, b, c] = self.bounding_box(padding);
        let center = [a / 2.0, b / 2.0, c / 2.0];
        self.translate(center);
        let mat = [[a, 0.0, 0.0], [0.0, b, 0.0], [0.0, 0.0, c]];
        let lat = Lattice::new(mat);
        self.set_lattice(lat);
    }

    /// Return minimal bounding box in x, y, z directions
    pub fn bounding_box(&self, padding: f64) -> [f64; 3] {
        use vecfx::*;

        assert!(padding.is_sign_positive(), "invalid scale factor: {padding}");
        let xmax = self.positions().map(|[x, _, _]| x).float_max();
        let xmin = self.positions().map(|[x, _, _]| x).float_min();
        let ymax = self.positions().map(|[_, y, _]| y).float_max();
        let ymin = self.positions().map(|[_, y, _]| y).float_min();
        let zmax = self.positions().map(|[_, _, z]| z).float_max();
        let zmin = self.positions().map(|[_, _, z]| z).float_min();

        let a = xmax - xmin + 2.0 * padding;
        let b = ymax - ymin + 2.0 * padding;
        let c = zmax - zmin + 2.0 * padding;
        [a, b, c]
    }
}
// 599d9ac9 ends here