gchemol_lattice/
lib.rs

1// [[file:../lattice.note::*header][header:1]]
2//===============================================================================#
3//   DESCRIPTION:  Represents 3D periodic lattice
4//
5//       OPTIONS:  ---
6//  REQUIREMENTS:  ---
7//         NOTES:  ---
8//        AUTHOR:  Wenping Guo <ybyygu@gmail.com>
9//       LICENCE:  GPL version 3
10//       CREATED:  <2018-04-29 14:27>
11//       UPDATED:  <>
12//===============================================================================#
13// header:1 ends here
14
15// [[file:../lattice.note::*imports][imports:1]]
16use gchemol_gut::prelude::*;
17use vecfx::*;
18// imports:1 ends here
19
20// [[file:../lattice.note::*mods][mods:1]]
21mod mic;
22mod supercell;
23mod utils;
24
25use crate::utils::*;
26// mods:1 ends here
27
28// [[file:../lattice.note::*base][base:1]]
29/// Periodic 3D lattice
30#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
31pub struct Lattice {
32    /// internal translation matrix
33    matrix: Matrix3f,
34    /// Lattice origin
35    origin: Vector3f,
36    /// inverse of lattice matrix
37    inv_matrix: Matrix3f,
38}
39
40impl Default for Lattice {
41    fn default() -> Self {
42        let matrix = Matrix3f::identity();
43        let inv_matrix = get_inv_matrix(&matrix);
44        Lattice {
45            matrix,
46            inv_matrix,
47            origin: Vector3f::zeros(),
48        }
49    }
50}
51// base:1 ends here
52
53// [[file:../lattice.note::f072864d][f072864d]]
54impl Lattice {
55    /// Construct `Lattice` from three lattice vectors.
56    pub fn new<T: Into<Vector3f> + Copy>(tvs: [T; 3]) -> Self {
57        let vectors = [tvs[0].into(), tvs[1].into(), tvs[2].into()];
58        let matrix = Matrix3f::from_columns(&vectors);
59        Self::from_matrix(matrix)
60    }
61
62    /// Construct `Lattice` from lattice matrix (3x3).
63    pub fn from_matrix<T: Into<Matrix3f>>(tvs: T) -> Self {
64        let matrix = tvs.into();
65        let inv_matrix = get_inv_matrix(&matrix);
66        Lattice {
67            matrix,
68            inv_matrix,
69            ..Default::default()
70        }
71    }
72
73    /// Construct lattice from lattice parameters
74    /// Unit cell angles in degrees, lengths in Angstrom
75    pub fn from_params(a: f64, b: f64, c: f64, alpha: f64, beta: f64, gamma: f64) -> Self {
76        let alpha = alpha.to_radians();
77        let beta = beta.to_radians();
78        let gamma = gamma.to_radians();
79
80        let acos = alpha.cos();
81        let bcos = beta.cos();
82        let gcos = gamma.cos();
83        let gsin = gamma.sin();
84        let v = (1. - acos.powi(2) - bcos.powi(2) - gcos.powi(2) + 2.0 * acos * bcos * gcos).sqrt();
85
86        let va = [a, 0.0, 0.0];
87
88        let vb = [b * gcos, b * gsin, 0.0];
89
90        let vc = [c * bcos, c * (acos - bcos * gcos) / gsin, c * v / gsin];
91
92        Lattice::new([va, vb, vc])
93    }
94
95    /// Return the perpendicular widths of the cell along three directions. i.e.
96    /// the distance between opposite faces of the unit cell
97    pub fn widths(&self) -> [f64; 3] {
98        let volume = self.volume();
99        let [van, vbn, vcn] = self.lengths();
100
101        let wa = volume / (vbn * vcn);
102        let wb = volume / (vcn * van);
103        let wc = volume / (van * vbn);
104
105        [wa, wb, wc]
106    }
107
108    /// Return the volume of the unit cell
109    /// the cache will be updated if necessary
110    pub fn volume(&self) -> f64 {
111        get_cell_volume(self.matrix)
112    }
113
114    /// Set cell origin in Cartesian coordinates
115    pub fn set_origin<T: Into<Vector3f>>(&mut self, loc: T) {
116        self.origin = loc.into()
117    }
118
119    /// Lattice length parameters: a, b, c
120    pub fn lengths(&self) -> [f64; 3] {
121        get_cell_lengths(self.matrix).into()
122    }
123
124    /// Lattice angle parameters in degrees
125    pub fn angles(&self) -> [f64; 3] {
126        get_cell_angles(self.matrix).into()
127    }
128
129    /// Scale Lattice by a positive constant `v`
130    pub fn scale_by(&mut self, v: f64) {
131        assert!(v.is_sign_positive(), "invalid scale factor: {v}");
132        self.matrix *= v;
133        self.inv_matrix = get_inv_matrix(&self.matrix);
134    }
135
136    /// Scale Lattice in `a` direction by a positive constant `v`
137    pub fn scale_by_a(&mut self, v: f64) {
138        self.scale_by_abc(v, 0)
139    }
140
141    /// Scale Lattice in `b` direction by a positive constant `v`
142    pub fn scale_by_b(&mut self, v: f64) {
143        self.scale_by_abc(v, 1)
144    }
145
146    /// Scale Lattice in `c` direction by a positive constant `v`
147    pub fn scale_by_c(&mut self, v: f64) {
148        self.scale_by_abc(v, 2)
149    }
150
151    /// Scale Lattice in length direction by a positive constant
152    fn scale_by_abc(&mut self, v: f64, i: usize) {
153        assert!(v.is_sign_positive(), "invalid scale factor: {v}");
154        let mut x = self.matrix.column_mut(i);
155        x *= v;
156        self.inv_matrix = get_inv_matrix(&self.matrix);
157    }
158
159    /// Get cell origin in Cartesian coordinates
160    pub fn origin(&self) -> Vector3f {
161        self.origin
162    }
163
164    /// Returns the fractional coordinates given cartesian coordinates.
165    pub fn to_frac<T: Into<Vector3f>>(&self, p: T) -> Vector3f {
166        self.inv_matrix * (p.into() - self.origin)
167    }
168
169    /// Returns the cartesian coordinates given fractional coordinates.
170    pub fn to_cart<T: Into<Vector3f>>(&self, p: T) -> Vector3f {
171        self.matrix * p.into() + self.origin
172    }
173
174    /// Lattice vector a
175    pub fn vector_a(&self) -> Vector3f {
176        self.matrix.column(0).into()
177    }
178
179    /// Lattice vector b
180    pub fn vector_b(&self) -> Vector3f {
181        self.matrix.column(1).into()
182    }
183
184    /// Lattice vector c
185    pub fn vector_c(&self) -> Vector3f {
186        self.matrix.column(2).into()
187    }
188
189    /// Three lattice vectors.
190    pub fn vectors(&self) -> [Vector3f; 3] {
191        [self.vector_a(), self.vector_b(), self.vector_c()]
192    }
193
194    /// Lattice vectors
195    pub fn matrix(&self) -> Matrix3f {
196        self.matrix
197    }
198
199    /// inverse of lattice matrix
200    pub fn inv_matrix(&self) -> Matrix3f {
201        self.inv_matrix
202    }
203
204    /// Check if lattice is orthorhombic
205    pub fn is_orthorhombic(&self) -> bool {
206        let diag = self.matrix.diagonal();
207        let m = Matrix3f::from_diagonal(&diag);
208        m == self.matrix
209    }
210
211    /// Wrap a point in cartesian coordinates into unit cell, obeying the
212    /// periodic boundary conditions. Returns cartesian coordinates.
213    pub fn wrap<T: Into<Vector3f>>(&self, vec: T) -> Vector3f {
214        let f = self.to_frac(vec);
215        let fcoords_wrapped = self.wrap_frac(f);
216        self.to_cart(fcoords_wrapped)
217    }
218
219    /// Wrap a point in fractional coordinates into unit cell, obeying the
220    /// periodic boundary conditions. Returns fractional coordinates.
221    pub fn wrap_frac<T: Into<Vector3f>>(&self, f: T) -> Vector3f {
222        let f = f.into();
223        let fcoords_wrapped = [f.x - f.x.floor(), f.y - f.y.floor(), f.z - f.z.floor()];
224        fcoords_wrapped.into()
225    }
226
227    /// Return the shortest distance between `pi` (point i) and the periodic
228    /// images of `pj` (point j) under the minimum image convention
229    ///
230    /// Parameters
231    /// ----------
232    /// * pi, pj: Cartesian coordinates of point i and point j
233    pub fn distance<T: Into<Vector3f>>(&self, pi: T, pj: T) -> f64 {
234        let p = pj.into() - pi.into();
235        let pmic = self.apply_mic(p);
236        pmic.norm()
237    }
238
239    /// Return the shortest vector obeying the minimum image convention.
240    pub fn apply_mic<T: Into<[f64; 3]>>(&self, p: T) -> Vector3f {
241        let p = p.into();
242        // Tuckerman algorithm works well for Orthorombic cell
243        let v_naive = self.apply_mic_tuckerman(p);
244        if self.is_orthorhombic() {
245            v_naive
246        } else {
247            let r_max = 0.5 * self.widths().min();
248            if v_naive.norm() < r_max {
249                v_naive
250            } else {
251                self.apply_mic_brute_force(p)
252            }
253        }
254    }
255}
256// f072864d ends here