SIRIUS 7.5.0
Electronic structure library and applications
crystal_symmetry.cpp
Go to the documentation of this file.
1// Copyright (c) 2013-2017 Anton Kozhevnikov, Thomas Schulthess
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without modification, are permitted provided that
5// the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
8// following disclaimer.
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10// and the following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
13// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
14// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
15// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
16// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
17// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20/** \file crystal_symmetry.cpp
21 *
22 * \brief Contains implementation of sirius::Crystal_symmetry class.
23 */
24
25#include <numeric>
26#include "crystal_symmetry.hpp"
27#include "lattice.hpp"
28#include "rotation.hpp"
30
31namespace sirius {
32
33static std::pair<std::vector<int>, std::vector<r3::vector<int>>>
34find_sym_atom(int num_atoms__, sddk::mdarray<double, 2> const& positions__, r3::matrix<int> const& R__,
35 r3::vector<double> const& t__, double tolerance__, bool inverse__ = false)
36{
37 PROFILE("sirius::find_sym_atom");
38
39 std::vector<int> sym_atom(num_atoms__);
40 std::vector<r3::vector<int>> sym_atom_T(num_atoms__);
41
42 for (int ia = 0; ia < num_atoms__; ia++) {
43 /* position of atom ia */
44 r3::vector<double> r_ia(positions__(0, ia), positions__(1, ia), positions__(2, ia));
45 /* apply crystal symmetry {R|t} or it's inverse */
46 /* rp = {R|t}r or {R|t}^{-1}r */
47 auto rp_ia = (inverse__) ? dot(inverse(R__), r_ia - t__) : dot(R__, r_ia) + t__;
48
49 bool found{false};
50 /* find the transformed atom */
51 for (int ja = 0; ja < num_atoms__; ja++) {
52 r3::vector<double> r_ja(positions__(0, ja), positions__(1, ja), positions__(2, ja));
53
54 auto v = rp_ia - r_ja;
55
56 r3::vector<int> T;
57 double diff{0};
58 for (int x : {0, 1, 2}) {
59 T[x] = std::round(v[x]);
60 diff += std::abs(T[x] - v[x]);
61 }
62 /* translation vector rp_ia = r_ja + T is found */
63 if (diff < tolerance__) {
64 found = true;
65 sym_atom[ia] = ja;
66 sym_atom_T[ia] = T;
67 break;
68 }
69 }
70 if (!found) {
71 std::stringstream s;
72 s << "equivalent atom was not found" << std::endl
73 << " symmetry operaton R:" << R__ << ", t: " << t__ << std::endl
74 << " atom: " << ia << ", position: " << r_ia << ", transformed position: " << rp_ia << std::endl
75 << " tolerance: " << tolerance__;
76 RTE_THROW(s);
77 }
78 }
79 return std::make_pair(sym_atom, sym_atom_T);
80}
81
82static space_group_symmetry_descriptor
83get_spg_sym_op(int isym_spg__, SpglibDataset* spg_dataset__, r3::matrix<double> const& lattice_vectors__, int num_atoms__,
84 sddk::mdarray<double, 2> const& positions__, double tolerance__)
85{
86 space_group_symmetry_descriptor sym_op;
87
88 auto inverse_lattice_vectors = inverse(lattice_vectors__);
89
90 /* rotation matrix in lattice coordinates */
91 sym_op.R = r3::matrix<int>(spg_dataset__->rotations[isym_spg__]);
92 /* sanity check */
93 int p = sym_op.R.det();
94 if (!(p == 1 || p == -1)) {
95 RTE_THROW("wrong rotation matrix");
96 }
97 /* inverse of the rotation matrix */
98 sym_op.invR = inverse(sym_op.R);
99 /* inverse transpose */
100 sym_op.invRT = transpose(sym_op.invR);
101 /* fractional translation */
102 sym_op.t = r3::vector<double>(spg_dataset__->translations[isym_spg__][0],
103 spg_dataset__->translations[isym_spg__][1],
104 spg_dataset__->translations[isym_spg__][2]);
105 /* is this proper or improper rotation */
106 sym_op.proper = p;
107 /* rotation in Cartesian coordinates */
108 sym_op.Rc = dot(dot(lattice_vectors__, r3::matrix<double>(sym_op.R)), inverse_lattice_vectors);
109 /* proper rotation in Cartesian coordinates */
110 sym_op.Rcp = sym_op.Rc * p;
111 try {
112 /* get Euler angles of the rotation */
113 sym_op.euler_angles = euler_angles(sym_op.Rcp, tolerance__);
114 } catch(std::exception const& e) {
115 std::stringstream s;
116 s << e.what() << std::endl;
117 s << "number of symmetry operations found by spglib: " << spg_dataset__->n_operations << std::endl
118 << "symmetry operation: " << isym_spg__ << std::endl
119 << "rotation matrix in lattice coordinates: " << sym_op.R << std::endl
120 << "rotation matrix in Cartesian coordinates: " << sym_op.Rc << std::endl
121 << "lattice vectors: " << lattice_vectors__ << std::endl
122 << "metric tensor error: " << metric_tensor_error(lattice_vectors__, sym_op.R) << std::endl
123 << std::endl
124 << "possible solution: decrease the spglib_tolerance";
125 RTE_THROW(s);
126 }
127 try {
128 /* get symmetry related atoms */
129 auto result = find_sym_atom(num_atoms__, positions__, sym_op.R, sym_op.t, tolerance__);
130 sym_op.sym_atom = result.first;
131 result = find_sym_atom(num_atoms__, positions__, sym_op.R, sym_op.t, tolerance__, true);
132 sym_op.inv_sym_atom = result.first;
133 sym_op.inv_sym_atom_T = result.second;
134 for (int ia = 0; ia < num_atoms__; ia++) {
135 int ja = sym_op.sym_atom[ia];
136 if (sym_op.inv_sym_atom[ja] != ia) {
137 std::stringstream s;
138 s << "atom symmetry tables are not consistent" << std::endl
139 << "ia: " << ia << " sym_atom[ia]: " << ja << " inv_sym_atom[sym_atom[ia]]: "
140 << sym_op.inv_sym_atom[ja];
141 RTE_THROW(s);
142 }
143 }
144 } catch(std::exception const& e) {
145 std::stringstream s;
146 s << e.what() << std::endl;
147 s << "R: " << sym_op.R << std::endl
148 << "t: " << sym_op.t << std::endl
149 << "tolerance: " << tolerance__;
150 RTE_THROW(s);
151 }
152
153 return sym_op;
154}
155
156static space_group_symmetry_descriptor
157get_identity_spg_sym_op(int num_atoms__)
158{
159 space_group_symmetry_descriptor sym_op;
160
161 sym_op.R = r3::matrix<int>({{1, 0, 0}, {0, 1, 0}, {0, 0, 1}});
162 /* inverse of the rotation matrix */
163 sym_op.invR = inverse(sym_op.R);
164 /* inverse transpose */
165 sym_op.invRT = transpose(sym_op.invR);
166 /* fractional translation */
167 sym_op.t = r3::vector<double>(0, 0, 0);
168 /* is this proper or improper rotation */
169 sym_op.proper = 1;
170 /* rotation in Cartesian coordinates */
171 sym_op.Rcp = sym_op.Rc = r3::matrix<double>({{1, 0, 0}, {0, 1, 0}, {0, 0, 1}});
172 /* get Euler angles of the rotation */
173 sym_op.euler_angles = euler_angles(sym_op.Rc, 1e-10);
174 /* trivial atom symmetry table */
175 sym_op.sym_atom = std::vector<int>(num_atoms__);
176 std::iota(sym_op.sym_atom.begin(), sym_op.sym_atom.end(), 0);
177 /* trivial atom symmetry table for inverse operation */
178 sym_op.inv_sym_atom = std::vector<int>(num_atoms__);
179 std::iota(sym_op.inv_sym_atom.begin(), sym_op.inv_sym_atom.end(), 0);
180 sym_op.inv_sym_atom_T = std::vector<r3::vector<int>>(num_atoms__, r3::vector<int>(0, 0, 0));
181
182 return sym_op;
183}
184
185Crystal_symmetry::Crystal_symmetry(r3::matrix<double> const& lattice_vectors__, int num_atoms__,
186 int num_atom_types__, std::vector<int> const& types__, sddk::mdarray<double, 2> const& positions__,
187 sddk::mdarray<double, 2> const& spins__, bool spin_orbit__, double tolerance__, bool use_sym__)
188 : lattice_vectors_(lattice_vectors__)
189 , num_atoms_(num_atoms__)
190 , num_atom_types_(num_atom_types__)
191 , types_(types__)
192 , tolerance_(tolerance__)
193{
194 PROFILE("sirius::Crystal_symmetry");
195
196 /* check lattice vectors */
197 if (lattice_vectors__.det() < 0 && use_sym__) {
198 std::stringstream s;
199 s << "spglib requires positive determinant for a matrix of lattice vectors";
200 RTE_THROW(s);
201 }
202
203 /* make inverse */
204 inverse_lattice_vectors_ = inverse(lattice_vectors_);
205
206 double lattice[3][3];
207 for (int i: {0, 1, 2}) {
208 for (int j: {0, 1, 2}) {
209 lattice[i][j] = lattice_vectors_(i, j);
210 }
211 }
212
213 positions_ = sddk::mdarray<double, 2>(3, num_atoms_);
214 sddk::copy(positions__, positions_);
215
216 magnetization_ = sddk::mdarray<double, 2>(3, num_atoms_);
217 sddk::copy(spins__, magnetization_);
218
219 PROFILE_START("sirius::Crystal_symmetry|spg");
220 if (use_sym__) {
221 /* make a call to spglib */
222 spg_dataset_ = spg_get_dataset(lattice, (double(*)[3])&positions_(0, 0), &types_[0], num_atoms_, tolerance_);
223 if (spg_dataset_ == NULL) {
224 RTE_THROW("spg_get_dataset() returned NULL");
225 }
226
227 if (spg_dataset_->spacegroup_number == 0) {
228 RTE_THROW("spg_get_dataset() returned 0 for the space group");
229 }
230
231 if (spg_dataset_->n_atoms != num_atoms__) {
232 std::stringstream s;
233 s << "spg_get_dataset() returned wrong number of atoms (" << spg_dataset_->n_atoms << ")" << std::endl
234 << "expected number of atoms is " << num_atoms__;
235 RTE_THROW(s);
236 }
237 }
238 PROFILE_STOP("sirius::Crystal_symmetry|spg");
239
240 if (spg_dataset_) {
241 auto lat_sym = find_lat_sym(lattice_vectors_, tolerance_);
242 /* make a list of crystal symmetries */
243 for (int isym = 0; isym < spg_dataset_->n_operations; isym++) {
244 r3::matrix<int> R(spg_dataset_->rotations[isym]);
245 for (auto& e: lat_sym) {
246 if (e == R) {
247 auto sym_op = get_spg_sym_op(isym, spg_dataset_, lattice_vectors__, num_atoms__, positions__, tolerance__);
248 /* add symmetry operation to a list */
249 space_group_symmetry_.push_back(sym_op);
250 }
251 }
252 }
253 } else { /* add only identity element */
254 auto sym_op = get_identity_spg_sym_op(num_atoms__);
255 /* add symmetry operation to a list */
256 space_group_symmetry_.push_back(sym_op);
257 }
258
259 PROFILE_START("sirius::Crystal_symmetry|mag");
260 /* loop over spatial symmetries */
261 for (int isym = 0; isym < num_spg_sym(); isym++) {
262 int jsym0 = 0;
263 int jsym1 = num_spg_sym() - 1;
264 if (spin_orbit__) {
265 jsym0 = jsym1 = isym;
266 }
267 /* loop over spin symmetries */
268 for (int jsym = jsym0; jsym <= jsym1; jsym++) {
269 /* take proper part of rotation matrix */
270 auto Rspin = space_group_symmetry(jsym).Rcp;
271
272 int n{0};
273 /* check if all atoms transform under spatial and spin symmetries */
274 for (int ia = 0; ia < num_atoms_; ia++) {
275 int ja = space_group_symmetry(isym).sym_atom[ia];
276
277 /* now check that vector field transforms from atom ia to atom ja */
278 /* vector field of atom is expected to be in Cartesian coordinates */
279 auto vd = dot(Rspin, r3::vector<double>(spins__(0, ia), spins__(1, ia), spins__(2, ia))) -
280 r3::vector<double>(spins__(0, ja), spins__(1, ja), spins__(2, ja));
281
282 if (vd.length() < 1e-10) {
283 n++;
284 }
285 }
286 /* if all atoms transform under spin rotaion, add it to a list */
287 if (n == num_atoms_) {
288 magnetic_group_symmetry_descriptor mag_op;
289 mag_op.spg_op = space_group_symmetry(isym);
290 mag_op.spin_rotation = Rspin;
291 mag_op.spin_rotation_inv = inverse(Rspin);
292 mag_op.spin_rotation_su2 = rotation_matrix_su2(Rspin);
293 /* add symmetry to the list */
294 magnetic_group_symmetry_.push_back(std::move(mag_op));
295 break;
296 }
297 }
298 }
299 PROFILE_STOP("sirius::Crystal_symmetry|mag");
300}
301
302double
304{
305 double diff{0};
306 for (auto const& e: magnetic_group_symmetry_) {
307 diff = std::max(diff, sirius::metric_tensor_error(lattice_vectors_, e.spg_op.R));
308 }
309 return diff;
310}
311
312double
313Crystal_symmetry::sym_op_R_error() const
314{
315 double diff{0};
316 for (auto const& e: magnetic_group_symmetry_) {
317 auto R = e.spg_op.Rcp;
318 auto R1 = inverse(transpose(R));
319 for (int i: {0, 1, 2}) {
320 for (int j: {0, 1, 2}) {
321 diff = std::max(diff, std::abs(R1(i, j) - R(i, j)));
322 }
323 }
324 }
325 return diff;
326}
327
328void
329Crystal_symmetry::print_info(std::ostream& out__, int verbosity__) const
330{
331 if (this->spg_dataset_ && (this->spg_dataset_->n_operations != this->num_spg_sym())) {
332 out__ << "space group found by spglib is different" << std::endl
333 << " num. sym. spglib : " << this->spg_dataset_->n_operations << std::endl
334 << " num. sym. actual : " << this->num_spg_sym() << std::endl
335 << " tolerance : " << this->tolerance_ << std::endl;
336 } else {
337 out__ << "space group number : " << this->spacegroup_number() << std::endl
338 << "international symbol : " << this->international_symbol() << std::endl
339 << "Hall symbol : " << this->hall_symbol() << std::endl
340 << "space group transformation matrix : " << std::endl;
341 auto tm = this->transformation_matrix();
342 for (int i = 0; i < 3; i++) {
343 for (int j = 0; j < 3; j++) {
344 out__ << ffmt(8, 4) << tm(i, j);
345 }
346 out__ << std::endl;
347 }
348 out__ << "space group origin shift : " << std::endl;
349 auto t = this->origin_shift();
350 for (auto x: {0, 1, 2}) {
351 out__ << ffmt(8, 4) << t[x];
352 }
353 out__ << std::endl;
354 }
355 out__ << "number of space group operations : " << this->num_spg_sym() << std::endl
356 << "number of magnetic group operations : " << this->size() << std::endl
357 << "metric tensor error: " << std::scientific << this->metric_tensor_error() << std::endl
358 << "rotation matrix error: " << std::scientific << this->sym_op_R_error() << std::endl;
359
360 if (verbosity__ >= 2) {
361 out__ << std::endl
362 << "symmetry operations " << std::endl
363 << std::endl;
364 for (int isym = 0; isym < this->size(); isym++) {
365 auto R = this->operator[](isym).spg_op.R;
366 auto Rc = this->operator[](isym).spg_op.Rc;
367 auto t = this->operator[](isym).spg_op.t;
368 auto S = this->operator[](isym).spin_rotation;
369
370 out__ << "isym : " << isym << std::endl
371 << "R : ";
372 for (int i: {0, 1, 2}) {
373 if (i) {
374 out__ << " ";
375 }
376 for (int j: {0, 1, 2,}) {
377 out__ << std::setw(3) << R(i, j);
378 }
379 out__ << std::endl;
380 }
381 out__ << "Rc: ";
382 for (int i: {0, 1, 2}) {
383 if (i) {
384 out__ << " ";
385 }
386 for (int j: {0, 1, 2}) {
387 out__ << ffmt(8, 4) << Rc(i, j);
388 }
389 out__ << std::endl;
390 }
391 out__ << "t : ";
392 for (int j: {0, 1, 2}) {
393 out__ << ffmt(8, 4) << t[j];
394 }
395 out__ << std::endl;
396 out__ << "S : ";
397 for (int i: {0, 1, 2}) {
398 if (i) {
399 out__ << " ";
400 }
401 for (int j: {0, 1, 2}) {
402 out__ << ffmt(8, 4) << S(i, j);
403 }
404 out__ << std::endl;
405 }
406 out__ << "proper: " << std::setw(2) << this->operator[](isym).spg_op.proper << std::endl
407 << std::endl;
408 }
409 }
410}
411
412} // namespace
413
Floating-point formatting (precision and width).
Contains definition and partial implementation of sirius::Crystal_symmetry class.
Crystal lattice functions.
void copy(T *target__, T const *source__, size_t n__)
Copy memory inside a device.
Definition: acc.hpp:320
auto transpose(matrix< T > src)
Return transpose of the matrix.
Definition: r3.hpp:445
auto inverse(matrix< int > src)
Return inverse of the integer matrix.
Definition: r3.hpp:475
Namespace of the SIRIUS library.
Definition: sirius.f90:5
auto euler_angles(r3::matrix< double > const &rot__, double tolerance__)
Compute Euler angles corresponding to the proper rotation matrix.
Definition: rotation.hpp:196
auto rotation_matrix_su2(std::array< double, 3 > u__, double theta__)
Generate SU(2) rotation matrix from the axes and angle.
Definition: rotation.hpp:48
double metric_tensor_error(r3::matrix< double > const &lat_vec__, r3::matrix< int > const &R__)
Compute error of the symmetry-transformed metric tensor.
Definition: lattice.hpp:43
Output stream tools.
Generate rotation matrices and related entities.
r3::matrix< int > R
Rotational part of symmetry operation (fractional coordinates).