Gemmi C++ API
Loading...
Searching...
No Matches
cellred.hpp
Go to the documentation of this file.
1// Copyright 2021 Global Phasing Ltd.
2//
3// Unit cell reductions: Buerger, Niggli, Selling-Delaunay.
4
5#ifndef GEMMI_CELLRED_HPP_
6#define GEMMI_CELLRED_HPP_
7
8#include <cmath>
9#include <array>
10#include <memory> // for unique_ptr
11#include "math.hpp" // for deg
12#include "symmetry.hpp" // for Op
13#include "unitcell.hpp" // for UnitCell
14
15namespace gemmi {
16
17struct SellingVector;
18
19// GruberVector contains G6 vector (G for Gruber) and cell reduction algorithms.
20// Originally, in B. Gruber, Acta Cryst. A29, 433 (1973), the vector was called
21// "characteristic" of a lattice/cell.
22// Functions that take epsilon as a parameter use it for comparisons,
23// as proposed in Grosse-Kunstleve et al, Acta Cryst. (2004) A60, 1.
25 // a.a b.b c.c 2b.c 2a.c 2a.b
26 double A, B, C, xi, eta, zeta; // the 1973 paper uses names A B C ξ η ζ
27 std::unique_ptr<Op> change_of_basis; // we use only Op::Rot
28
29 // m - orthogonalization matrix of a primitive cell
30 explicit GruberVector(const Mat33& m)
31 : A(m.column_dot(0,0)),
32 B(m.column_dot(1,1)),
33 C(m.column_dot(2,2)),
34 xi(2 * m.column_dot(1,2)),
35 eta(2 * m.column_dot(0,2)),
36 zeta(2 * m.column_dot(0,1)) {}
37
38 explicit GruberVector(const std::array<double,6>& g6)
39 : A(g6[0]), B(g6[1]), C(g6[2]), xi(g6[3]), eta(g6[4]), zeta(g6[5]) {}
40
42 : GruberVector(u.primitive_orth_matrix(centring)) {
45 }
46
48 : GruberVector(u, sg ? sg->centring_type() : 'P', track_change_of_basis) {}
49
50 void set_change_of_basis(const Op& op) { change_of_basis.reset(new Op(op)); }
51
52 std::array<double,6> parameters() const { return {A, B, C, xi, eta, zeta}; }
53 std::array<double,6> cell_parameters() const {
54 // inverse of UnitCell::g6()
55 double a = std::sqrt(A);
56 double b = std::sqrt(B);
57 double c = std::sqrt(C);
58 return {a, b, c,
59 deg(std::acos(xi/(2*b*c))),
60 deg(std::acos(eta/(2*a*c))),
61 deg(std::acos(zeta/(2*a*b)))};
62 }
64
65 SellingVector selling() const;
66
67 bool is_normalized() const {
68 // eq(3) from Gruber 1973
69 return A <= B && B <= C &&
70 (A != B || std::abs(xi) <= std::abs(eta)) &&
71 (B != C || std::abs(eta) <= std::abs(zeta)) &&
72 (xi > 0) == (eta > 0) && (xi > 0) == (zeta > 0);
73 }
74
75 bool is_buerger(double epsilon=1e-9) const {
76 return is_normalized() &&
77 // eq (4) from Gruber 1973
78 std::abs(xi) <= B + epsilon &&
79 std::abs(eta) <= A + epsilon &&
80 std::abs(zeta) <= A + epsilon;
81 }
82
83 // Algorithm N from Gruber (1973).
84 // Returns branch taken in N3.
85 void normalize(double eps=1e-9) {
86 auto step_N1 = [&]() {
87 if (A - B > eps || (A - B >= -eps && std::abs(xi) > std::abs(eta) + eps)) { // N1
88 std::swap(A, B);
89 std::swap(xi, eta);
91 swap_columns_and_negate(0, 1);
92 }
93 };
94 step_N1();
95 if (B - C > eps || (B - C >= -eps && std::abs(eta) > std::abs(zeta) + eps)) { // N2
96 std::swap(B, C);
97 std::swap(eta, zeta);
99 swap_columns_and_negate(1, 2);
100 // To make it faster, instead of "go to the point N1" we repeat N1 once
101 // (which is equivalent - three swaps are sufficient to reorder ABC).
102 step_N1();
103 }
104 // N3
105 // xi * eta * zeta > 0 <=> positive count is 1 or 3 and no zeros
106 int pos_count = (xi > eps) + (eta > eps) + (zeta > eps);
107 int nonneg_count = (xi >= -eps) + (eta >= -eps) + (zeta >= -eps);
108 double sgn = (pos_count == nonneg_count && pos_count % 2 == 1) ? 1 : -1;
109 if (change_of_basis) {
110 if (sgn * xi < -eps) negate_column(0);
111 if (sgn * eta < -eps) negate_column(1);
112 if (sgn * zeta < -eps) negate_column(2);
113 if (pos_count != nonneg_count && pos_count % 2 == 1)
114 negate_column(std::fabs(zeta) <= eps ? 2 :
115 std::fabs(eta) <= eps ? 1 : 0);
116 }
117 xi = std::copysign(xi, sgn);
118 eta = std::copysign(eta, sgn);
119 zeta = std::copysign(zeta, sgn);
120 }
121
122 // Algorithm B from Gruber (1973).
123 // Returns true if no change was needed.
125 if (std::abs(xi) > B) { // B2
126 double j = std::floor(0.5*xi/B + 0.5);
127 C += j * (j*B - xi);
128 xi -= 2 * j * B;
129 eta -= j * zeta;
130 } else if (std::abs(eta) > A) { // B3
131 double j = std::floor(0.5*eta/A + 0.5);
132 C += j * (j*A - eta);
133 xi -= j * zeta;
134 eta -= 2 * j * A;
135 } else if (std::abs(zeta) > A) { // B4
136 double j = std::floor(0.5*zeta/A + 0.5);
137 B += j * (j*A - zeta);
138 xi -= j * eta;
139 zeta -= 2 * j * A;
140 } else if (xi + eta + zeta + A + B < 0) { // B5
141 double j = std::floor(0.5 * (xi + eta) / (A + B + zeta) + 0.5);
142 C += j * (j * (A + B + zeta) - (xi + eta));
143 xi -= j * (2*B + zeta);
144 eta -= j * (2*A + zeta);
145 } else {
146 return true;
147 }
148 return false;
149 }
150
151 // Returns number of iterations.
153 int n = 0;
154 double prev_sum = -1;
155 int stall_count = 0;
156 for (;;) {
157 normalize();
158 // In rare cases numerical errors push the algorithm into infinite loop,
159 // as described in Grosse-Kunstleve et al, Acta Cryst. (2004) A60, 1.
160 // Ad-hoc solution: stop if a+b+c is stalled for 5 iterations.
161 if (++n > 8) { // don't waste time during the first few iterations
162 double sum = std::sqrt(A) + std::sqrt(B) + std::sqrt(C);
163 if (std::abs(sum - prev_sum) < sum * 1e-6) {
164 if (++stall_count == 5)
165 break;
166 } else {
167 stall_count = 0;
168 }
169 prev_sum = sum;
170 }
171 if (buerger_step())
172 break;
173 }
174 return n;
175 }
176
177 // To be called after normalize() or is_normalized().
178 // Returns true if it already was Niggli cell.
179 // Algorithm from Krivy & Gruber, Acta Cryst. (1976) A32, 297.
180 bool niggli_step(double epsilon=1e-9) {
181 if (std::abs(xi) > B + epsilon || // step 5. from Krivy-Gruber (1976)
182 (xi >= B - epsilon && 2 * eta < zeta - epsilon) ||
183 (xi <= -(B - epsilon) && zeta < -epsilon)) {
184 double sign_xi = xi >= 0 ? 1 : -1;
185 C += B - xi * sign_xi;
186 eta -= zeta * sign_xi;
187 xi -= 2 * B * sign_xi;
188 if (change_of_basis)
189 add_column(1, 2, -int(sign_xi));
190 } else if (std::abs(eta) > A + epsilon || // step 6.
191 (eta >= A - epsilon && 2 * xi < zeta - epsilon) ||
192 (eta <= -(A - epsilon) && zeta < -epsilon)) {
193 double sign_eta = eta >= 0 ? 1 : -1;
194 C += A - eta * sign_eta;
195 xi -= zeta * sign_eta;
196 eta -= 2 * A * sign_eta;
197 if (change_of_basis)
198 add_column(0, 2, -int(sign_eta));
199 } else if (std::abs(zeta) > A + epsilon || // step 7.
200 (zeta >= A - epsilon && 2 * xi < eta - epsilon) ||
201 (zeta <= -(A - epsilon) && eta < -epsilon)) {
202 double sign_zeta = zeta >= 0 ? 1 : -1;
203 B += A - zeta * sign_zeta;
204 xi -= eta * sign_zeta;
205 zeta -= 2 * A * sign_zeta;
206 if (change_of_basis)
207 add_column(0, 1, -int(sign_zeta));
208 } else if (xi + eta + zeta + A + B < -epsilon || // step 8.
209 (xi + eta + zeta + A + B <= epsilon && 2 * (A + eta) + zeta > epsilon)) {
210 C += A + B + xi + eta + zeta;
211 xi += 2 * B + zeta;
212 eta += 2 * A + zeta;
213 if (change_of_basis) {
214 add_column(0, 2, 1);
215 add_column(1, 2, 1);
216 }
217 } else {
218 return true;
219 }
220 return false;
221 }
222
223 // Returns number of iterations.
224 int niggli_reduce(double epsilon=1e-9, int iteration_limit=100) {
225 int n = 0;
226 for (;;) {
228 if (++n == iteration_limit || niggli_step(epsilon))
229 break;
230 }
231 return n;
232 }
233
234 bool is_niggli(double epsilon=1e-9) const {
236 }
237
238private:
239 void swap_columns_and_negate(int i, int j) {
240 for (auto& r : change_of_basis->rot)
241 std::swap(r[i], r[j]);
242 for (auto& r : change_of_basis->rot)
243 for (auto& v : r)
244 v = -v;
245 }
246 void negate_column(int i) {
247 for (auto& r : change_of_basis->rot)
248 r[i] = -r[i];
249 }
250 void add_column(int pos, int dest, int sign) {
251 for (auto& r : change_of_basis->rot)
252 r[dest] += sign * r[pos];
253 }
254};
255
256
257// Selling-Delaunay reduction. Based on:
258// - chapter "Delaunay reduction and standardization" in
259// International Tables for Crystallography vol. A (2016), sec. 3.1.2.3.
260// https://onlinelibrary.wiley.com/iucr/itc/Ac/ch3o1v0001/
261// - Patterson & Love (1957), Acta Cryst. 10, 111,
262// "Remarks on the Delaunay reduction", doi:10.1107/s0365110x57000328
263// - Andrews et al (2019), Acta Cryst. A75, 115,
264// "Selling reduction versus Niggli reduction for crystallographic lattices".
266 // b.c a.c a.b a.d b.d c.d
267 std::array<double,6> s;
268
269 explicit SellingVector(const std::array<double,6>& s_) : s(s_) {}
270
271 explicit SellingVector(const Mat33& orth) {
272 Vec3 b[4];
273 for (int i = 0; i < 3; ++i)
274 b[i] = orth.column_copy(i);
275 b[3]= -b[0] - b[1] - b[2];
276 s[0] = b[1].dot(b[2]);
277 s[1] = b[0].dot(b[2]);
278 s[2] = b[0].dot(b[1]);
279 s[3] = b[0].dot(b[3]);
280 s[4] = b[1].dot(b[3]);
281 s[5] = b[2].dot(b[3]);
282 }
283
285 : SellingVector(u.primitive_orth_matrix(centring)) {}
287 : SellingVector(u, sg ? sg->centring_type() : 'P') {}
288
289 // The reduction minimizes the sum b_i^2 which is equal to -2 sum s_i.
290 double sum_b_squared() const {
291 return -2 * (s[0] + s[1] + s[2] + s[3] + s[4] + s[5]);
292 }
293
294 bool is_reduced(double eps=1e-9) const {
295 return std::all_of(s.begin(), s.end(), [eps](double x) { return x <= eps; });
296 }
297
298 bool reduce_step(double eps=1e-9) {
299 //printf(" s = %g %g %g %g %g %g sum=%g\n",
300 // s[0], s[1], s[2], s[3], s[4], s[5], sum_b_squared());
301 const int table[6][5] = {
302 // When negating s[n] we need to apply operations from table[n]:
303 // 2 x add, subtract, 2 x swap&add
304 {2, 4, 3, 1, 5}, // 0
305 {2, 3, 4, 0, 5}, // 1
306 {1, 3, 5, 0, 4}, // 2
307 {1, 2, 0, 4, 5}, // 3
308 {0, 2, 1, 3, 5}, // 4
309 {0, 1, 2, 3, 4}, // 5
310 };
311
312 double max_s = eps;
313 int max_s_pos = -1;
314 for (int i = 0; i < 6; ++i)
315 if (s[i] > max_s) {
316 max_s = s[i];
317 max_s_pos = i;
318 }
319 if (max_s_pos < 0)
320 return false;
321 const int (&indices)[5] = table[max_s_pos];
322 s[max_s_pos] = -max_s;
323 s[indices[0]] += max_s;
324 s[indices[1]] += max_s;
325 s[indices[2]] -= max_s;
326 std::swap(s[indices[3]], s[indices[4]]);
327 s[indices[3]] += max_s;
328 s[indices[4]] += max_s;
329 //printf(" s[%d]=%g sum: %g\n", max_s_pos, max_s, sum_b_squared());
330 return true;
331 }
332
333 // Returns number of iterations.
334 int reduce(double eps=1e-9, int iteration_limit=100) {
335 int n = 0;
336 while (++n != iteration_limit)
337 if (!reduce_step(eps))
338 break;
339 return n;
340 }
341
342 std::array<double,6> g6_parameters() const {
343 return {-s[1]-s[2]-s[3], -s[0]-s[2]-s[4], -s[0]-s[1]-s[5], 2*s[0], 2*s[1], 2*s[2]};
344 }
345
347
348 // Swap values to make a <= b <= c <= d
349 void sort(double eps=1e-9) {
350 double abcd_sq_neg[4] = {
351 // -a^2, -b^2, -c^2, -d^2 (negated - to be sorted in descending order)
352 s[1]+s[2]+s[3], s[0]+s[2]+s[4], s[0]+s[1]+s[5], s[3]+s[4]+s[5]
353 };
354 // First, make sure that d >= a,b,c (therefore -d^2 <= -a^2,...).
355 int min_idx = 3;
356 for (int i = 0; i < 3; ++i)
358 min_idx = i;
359 switch (min_idx) {
360 case 0: // a <-> d
361 std::swap(s[1], s[5]);
362 std::swap(s[2], s[4]);
363 break;
364 case 1: // b <-> d
365 std::swap(s[0], s[5]);
366 std::swap(s[2], s[3]);
367 break;
368 case 2: // c <-> d
369 std::swap(s[0], s[4]);
370 std::swap(s[1], s[3]);
371 break;
372 }
373 // we could stop here and not care about the order of a,b,c.
374 std::swap(abcd_sq_neg[min_idx], abcd_sq_neg[3]);
375 if (abcd_sq_neg[0] < abcd_sq_neg[1] - eps) { // a <-> b
376 std::swap(s[0], s[1]);
377 std::swap(s[3], s[4]);
378 std::swap(abcd_sq_neg[0], abcd_sq_neg[1]);
379 }
380 if (abcd_sq_neg[1] < abcd_sq_neg[2] - eps) { // b <-> c
381 std::swap(s[1], s[2]);
382 std::swap(s[4], s[5]);
383 std::swap(abcd_sq_neg[1], abcd_sq_neg[2]);
384 }
385 if (abcd_sq_neg[0] < abcd_sq_neg[1] - eps) { // a <-> b
386 std::swap(s[0], s[1]);
387 std::swap(s[3], s[4]);
388 //std::swap(abcd_sq_neg[0], abcd_sq_neg[1]);
389 }
390 }
391
392 std::array<double,6> cell_parameters() const {
393 return gruber().cell_parameters();
394 }
396};
397
399 double s0 = 0.5 * xi;
400 double s1 = 0.5 * eta;
401 double s2 = 0.5 * zeta;
402 return SellingVector({s0, s1, s2, -A - s1 - s2, -B - s0 - s2, -C - s0 - s1});
403}
404
405} // namespace gemmi
406#endif
constexpr double deg(double angle)
Definition math.hpp:31
Op::Rot centred_to_primitive(char centring_type)
Vec3_< double > Vec3
Definition math.hpp:101
Definition seqid.hpp:149
GruberVector(const std::array< double, 6 > &g6)
Definition cellred.hpp:38
void set_change_of_basis(const Op &op)
Definition cellred.hpp:50
void normalize(double eps=1e-9)
Definition cellred.hpp:85
int niggli_reduce(double epsilon=1e-9, int iteration_limit=100)
Definition cellred.hpp:224
bool niggli_step(double epsilon=1e-9)
Definition cellred.hpp:180
GruberVector(const UnitCell &u, const SpaceGroup *sg, bool track_change_of_basis=false)
Definition cellred.hpp:47
SellingVector selling() const
Definition cellred.hpp:398
bool is_buerger(double epsilon=1e-9) const
Definition cellred.hpp:75
std::unique_ptr< Op > change_of_basis
Definition cellred.hpp:27
bool is_normalized() const
Definition cellred.hpp:67
bool is_niggli(double epsilon=1e-9) const
Definition cellred.hpp:234
GruberVector(const UnitCell &u, char centring, bool track_change_of_basis=false)
Definition cellred.hpp:41
UnitCell get_cell() const
Definition cellred.hpp:63
GruberVector(const Mat33 &m)
Definition cellred.hpp:30
std::array< double, 6 > parameters() const
Definition cellred.hpp:52
std::array< double, 6 > cell_parameters() const
Definition cellred.hpp:53
Vec3 column_copy(int i) const
Definition math.hpp:139
std::array< double, 6 > s
Definition cellred.hpp:267
bool is_reduced(double eps=1e-9) const
Definition cellred.hpp:294
bool reduce_step(double eps=1e-9)
Definition cellred.hpp:298
std::array< double, 6 > g6_parameters() const
Definition cellred.hpp:342
SellingVector(const Mat33 &orth)
Definition cellred.hpp:271
SellingVector(const UnitCell &u, char centring)
Definition cellred.hpp:284
UnitCell get_cell() const
Definition cellred.hpp:395
std::array< double, 6 > cell_parameters() const
Definition cellred.hpp:392
int reduce(double eps=1e-9, int iteration_limit=100)
Definition cellred.hpp:334
SellingVector(const UnitCell &u, const SpaceGroup *sg)
Definition cellred.hpp:286
void sort(double eps=1e-9)
Definition cellred.hpp:349
SellingVector(const std::array< double, 6 > &s_)
Definition cellred.hpp:269
double sum_b_squared() const
Definition cellred.hpp:290
GruberVector gruber() const
Definition cellred.hpp:346
Unit cell.
Definition unitcell.hpp:139