1 /* spacegroup.cpp: methods for spacegroup symmetry class */
2 //C Copyright (C) 2000-2006 Kevin Cowtan and University of York
3 //L
4 //L This library is free software and is distributed under the terms
5 //L and conditions of version 2.1 of the GNU Lesser General Public
6 //L Licence (LGPL) with the following additional clause:
7 //L
8 //L `You may also combine or link a "work that uses the Library" to
9 //L produce a work containing portions of the Library, and distribute
10 //L that work under terms of your choice, provided that you give
11 //L prominent notice with each copy of the work that the specified
12 //L version of the Library is used in it, and that you include or
13 //L provide public access to the complete corresponding
14 //L machine-readable source code for the Library including whatever
15 //L changes were used in the work. (i.e. If you make changes to the
16 //L Library you must distribute those, but you do not need to
17 //L distribute source or object code to those portions of the work
18 //L not covered by this licence.)'
19 //L
20 //L Note that this clause grants an additional right and does not impose
21 //L any additional restriction, and so does not affect compatibility
22 //L with the GNU General Public Licence (GPL). If you wish to negotiate
23 //L other terms, please contact the maintainer.
24 //L
25 //L You can redistribute it and/or modify the library under the terms of
26 //L the GNU Lesser General Public License as published by the Free Software
27 //L Foundation; either version 2.1 of the License, or (at your option) any
28 //L later version.
29 //L
30 //L This library is distributed in the hope that it will be useful, but
31 //L WITHOUT ANY WARRANTY; without even the implied warranty of
32 //L MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
33 //L Lesser General Public License for more details.
34 //L
35 //L You should have received a copy of the CCP4 licence and/or GNU
36 //L Lesser General Public License along with this library; if not, write
37 //L to the CCP4 Secretary, Daresbury Laboratory, Warrington WA4 4AD, UK.
38 //L The GNU Lesser General Public can also be obtained by writing to the
39 //L Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
40 //L MA 02111-1307 USA
41
42
43 #include "spacegroup.h"
44 #include "coords.h"
45 #include "clipper_instance.h"
46
47 #include <algorithm>
48 #include <sstream>
49
50
51 namespace clipper {
52
53
54 namespace data {
55
56 // matrices
57 Mat33<> mat_i ( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 );
58 Mat33<> mat_inv (-1.0, 0.0, 0.0, 0.0,-1.0, 0.0, 0.0, 0.0,-1.0 );
59 Mat33<> mat_2z (-1.0, 0.0, 0.0, 0.0,-1.0, 0.0, 0.0, 0.0, 1.0 );
60 Mat33<> mat_3z ( 0.0,-1.0, 0.0, 1.0,-1.0, 0.0, 0.0, 0.0, 1.0 );
61 Mat33<> mat_4z ( 0.0,-1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 );
62 Mat33<> mat_6z ( 1.0,-1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 );
63 Mat33<> mat_2q ( 0.0,-1.0, 0.0,-1.0, 0.0, 0.0, 0.0, 0.0,-1.0 );
64 Mat33<> mat_2qq ( 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,-1.0 );
65 Mat33<> mat_3abc( 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0 );
66
67 Vec3<> vec_0( 0.0, 0.0, 0.0 );
68 Vec3<> vec_a( 0.5, 0.0, 0.0 );
69 Vec3<> vec_b( 0.0, 0.5, 0.0 );
70 Vec3<> vec_c( 0.0, 0.0, 0.5 );
71 Vec3<> vec_n( 0.5, 0.5, 0.5 );
72 Vec3<> vec_u( 0.25, 0.0, 0.0 );
73 Vec3<> vec_v( 0.0, 0.25, 0.0 );
74 Vec3<> vec_w( 0.0, 0.0, 0.25 );
75 Vec3<> vec_d( 0.25, 0.25, 0.25 );
76
77 Vec3<> vec_A( 0.0, 0.5, 0.5 );
78 Vec3<> vec_B( 0.5, 0.0, 0.5 );
79 Vec3<> vec_C( 0.5, 0.5, 0.0 );
80 Vec3<> vec_I( 0.5, 0.5, 0.5 );
81 Vec3<> vec_R( 0.666667, 0.333333, 0.333333 );
82 Vec3<> vec_S( 0.333333, 0.666667, 0.333333 );
83 Vec3<> vec_T( 0.333333, 0.333333, 0.666667 );
84 Vec3<> vec_H( 0.666667, 0.333333, 0.0 );
85 Vec3<> vec_F1( 0.0, 0.5, 0.5 );
86 Vec3<> vec_F2( 0.5, 0.0, 0.5 );
87
88 } // namespace data
89
90
91 // spacegroup description object
92
init_hall(const String & symb)93 void Spgr_descr::Symop_codes::init_hall( const String& symb )
94 {
95 // interpret Hall symbol
96 using namespace data;
97
98 Symop_codes& ops = (*this);
99 std::vector<String> tokens;
100 String token, sym, chb;
101 char c, inv, lat;
102 int i, j, k, l;
103
104 // separate the Hall symbol from the change of basis
105 tokens = symb.split("()");
106 if ( tokens.size() > 0 ) sym = tokens[0].trim();
107 if ( tokens.size() > 1 ) chb = tokens[1].trim();
108
109 // now separate the parts of the Hall symbol
110 tokens = sym.split(" \t");
111
112 // first part: lattice and inversion
113 inv = lat = ' ';
114 token = tokens[0];
115 for ( j = 0; j < token.length(); j++ ) {
116 c = toupper( token[j] );
117 if ( c == '-' ) inv = c;
118 else lat = c;
119 }
120
121 // next 1-3 parts: generating matrices
122 int nmat = tokens.size()-1;
123 std::vector<char> invop(nmat,' '), order(nmat,' '), screw(nmat,' '),
124 axis1(nmat,' '), axis2(nmat,' ');
125 std::vector<String> trans(nmat);
126 for ( i = 0; i < nmat; i++ ) {
127 token = tokens[i+1];
128 for ( j = 0; j < token.length(); j++ ) {
129 c = tolower( token[j] );
130 if ( c == '-' ) invop[i] = c;
131 else if ( c >= '1' && c <= '6' )
132 if ( order[i] == ' ' ) order[i] = c;
133 else screw[i] = c;
134 else if ( c >= 'x' && c <= 'z' ) axis1[i] = c;
135 else if ( c >= '"' && c <= '*' ) axis2[i] = c;
136 else if ( c >= 'a' && c <= 'w' ) trans[i] += c;
137 }
138 }
139
140 // now interpret all the default conventions
141 // default first axis to z
142 if ( nmat >= 1 ) {
143 if ( axis1[0] == ' ' ) axis1[0] = 'z';
144 }
145 // default second axis on basis of first
146 if ( nmat >= 2 ) {
147 if ( axis1[1] == ' ' ) {
148 if ( order[1] == '2' ) {
149 if ( order[0] == '2' || order[0] == '4' ) {
150 if ( axis2[1] == ' ' ) axis1[1] = 'x';
151 } else if ( order[0] == '3' || order[0] == '6' ) {
152 axis1[1] = axis1[0];
153 if ( axis2[1] == ' ' ) axis2[1] = '\'';
154 }
155 } else if ( order[1] == '3' ) {
156 if ( order[0] == '2' || order[0] == '4' ) {
157 if ( axis2[1] == ' ' ) axis2[1] = '*';
158 }
159 }
160 }
161 }
162 // default third axis (not much choice here)
163 if ( nmat >= 3 ) {
164 if ( axis1[2] == ' ' ) {
165 if ( order[2] == '3' ) {
166 if ( axis2[2] == ' ' ) axis2[2] = '*';
167 }
168 }
169 }
170
171 // now check we have everything
172 for ( i = 0; i < nmat; i++ ) {
173 // add fake z axes for non-axis ops
174 if ( axis1[i] == ' ' ) {
175 if ( order[i] == '1' ) axis1[i] = 'z'; // fake axis
176 if ( axis2[i] != ' ' ) axis1[i] = 'z'; // fake axis
177 }
178 // error messages
179 if ( axis1[i] == ' ' ) Message::message(
180 Message_fatal("Spacegroup: Missing x/y/z in Hall symb:"+tokens[i+1]) );
181 if ( order[i] == ' ' ) Message::message(
182 Message_fatal("Spacegroup: Missing order in Hall symb:"+tokens[i+1]) );
183 }
184
185 // add identity and inversion
186 ops.clear();
187 ops.push_back( Symop_code::identity() );
188 if ( inv == '-' ) ops.push_back(Symop_code(Symop(RTop<>(mat_inv))));
189
190 // now make the generator matrices
191 Mat33<> mat, matperm;
192 Vec3<> vec;
193 for ( i = 0; i < nmat; i++ ) {
194 // make matrix part
195 mat = mat_i;
196 if ( order[i] == '2' && axis2[i] == ' ' ) mat = mat_2z;
197 if ( order[i] == '2' && axis2[i] == '\'') mat = mat_2q;
198 if ( order[i] == '2' && axis2[i] == '"' ) mat = mat_2qq;
199 if ( order[i] == '3' && axis2[i] == ' ' ) mat = mat_3z;
200 if ( order[i] == '3' && axis2[i] == '*' ) mat = mat_3abc;
201 if ( order[i] == '4' && axis2[i] == ' ' ) mat = mat_4z;
202 if ( order[i] == '6' && axis2[i] == ' ' ) mat = mat_6z;
203 // inverse (improper)
204 if ( invop[i] == '-' ) mat = mat_inv * mat;
205 // axis permutation
206 if ( axis1[i] == 'x' ) j = 0;
207 else if ( axis1[i] == 'y' ) j = 1;
208 else j = 2;
209 for ( k = 0; k < 3; k++ )
210 for ( l = 0; l < 3; l++ )
211 matperm( k, l ) = mat( (k+2-j)%3, (l+2-j)%3 );
212 // make translation part
213 vec = vec_0;
214 for ( k = 0; k < trans[i].length(); k++ ) {
215 if ( trans[i][k] == 'a' ) vec = vec + vec_a;
216 else if ( trans[i][k] == 'b' ) vec = vec + vec_b;
217 else if ( trans[i][k] == 'c' ) vec = vec + vec_c;
218 else if ( trans[i][k] == 'n' ) vec = vec + vec_n;
219 else if ( trans[i][k] == 'u' ) vec = vec + vec_u;
220 else if ( trans[i][k] == 'v' ) vec = vec + vec_v;
221 else if ( trans[i][k] == 'w' ) vec = vec + vec_w;
222 else if ( trans[i][k] == 'd' ) vec = vec + vec_d;
223 }
224 // screw translation
225 if ( screw[i] != ' ' )
226 vec[j] += ftype( screw[i] - '0' ) / ftype( order[i] - '0' );
227 // store the matrix
228 ops.push_back( Symop_code( Symop( RTop<>( matperm, vec ) ) ) );
229 }
230
231 // add lattice centering
232 if (lat=='A') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_A))));
233 if (lat=='B') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_B))));
234 if (lat=='C') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_C))));
235 if (lat=='I') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_I))));
236 if (lat=='R') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_R))));
237 if (lat=='S') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_S))));
238 if (lat=='T') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_T))));
239 if (lat=='H') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_H))));
240 if (lat=='Q') ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_S))));
241 if (lat=='F') {
242 ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_F1))));
243 ops.push_back(Symop_code(Symop(RTop<>(mat_i,vec_F2))));
244 }
245
246 // apply the change of basis operator
247 RTop_frac cbop( RTop_frac::identity() );
248 if ( chb.find( ',' ) != String::npos ) {
249 cbop = RTop_frac( chb );
250 } else {
251 std::vector<String> t = chb.split(" ");
252 for ( i = 0; i < Util::min( int(t.size()), 3 ); i++ )
253 cbop.trn()[i] = ftype( t[i].i() ) / 12.0;
254 }
255 RTop_frac cbopi( cbop.inverse() );
256 for ( i = 0; i < ops.size(); i++ )
257 ops[i] = Symop_code( Symop( cbop*ops[i].symop()*cbopi ) );
258 }
259
init_symops(const String & symb)260 void Spgr_descr::Symop_codes::init_symops( const String& symb )
261 {
262 Symop_codes& ops = (*this);
263 std::vector<String> symops = symb.split(";");
264 for ( int i = 0; i < symops.size(); i++ )
265 ops.push_back( Symop_code( Symop( RTop_frac( symops[i] ) ) ) );
266 }
267
expand() const268 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::expand() const
269 {
270 int i, j, l, size;
271 Symop_code k;
272
273 const Symop_codes& generators = (*this);
274 Symop_codes ops;
275 ops.push_back( Symop_code::identity() ); // identity is compulsory
276
277 // generate all the symops
278 do {
279 size = ops.size();
280 for ( i = 0; i < generators.size(); i++ )
281 if ( generators[i] != Symop_code::identity() ) {
282 for ( j = 0; j < size; j++ ) {
283 k = Symop_code( Isymop( generators[i].isymop() * ops[j].isymop() ) );
284 for ( l = 0; l < ops.size(); l++ )
285 if ( ops[l] == k ) break;
286 if ( l == ops.size() ) ops.push_back( k );
287 }
288 }
289 } while ( ops.size() > size );
290 return ops;
291 }
292
primitive_noninversion_ops() const293 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::primitive_noninversion_ops() const
294 {
295 Symop_codes pops = primitive_ops();
296 if ( inversion_ops().size() > 1 ) {
297 Symop_codes nops;
298 for ( int i = 0; i < pops.size(); i++ )
299 if ( pops[i].symop().rot().det() > 0.0 )
300 nops.push_back( pops[i] );
301 pops = nops;
302 }
303 return pops;
304 }
305
inversion_ops() const306 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::inversion_ops() const
307 {
308 const Symop_codes& ops = (*this);
309 Symop_codes pops;
310 Symop_code invop = Symop_code( Symop( RTop<>( data::mat_inv ) ) );
311 pops.push_back( Symop_code::identity() );
312 int i;
313 for ( i = 0; i < ops.size(); i++ )
314 if ( ops[i].code_rot() == invop ) { pops.push_back( ops[i] ); break; }
315 return pops;
316 }
317
primitive_ops() const318 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::primitive_ops() const
319 {
320 const Symop_codes& ops = (*this);
321 Symop_codes pops;
322 int i, j;
323 pops.push_back( Symop_code::identity() );
324 for ( i = 0; i < ops.size(); i++ ) {
325 for ( j = 0; j < pops.size(); j++ )
326 if ( ops[i].code_rot() == pops[j].code_rot() ) break;
327 if ( j == pops.size() ) pops.push_back( ops[i] );
328 }
329 return pops;
330 }
331
centering_ops() const332 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::centering_ops() const
333 {
334 const Symop_codes& ops = (*this);
335 Symop_codes cops;
336 for ( int i = 0; i < ops.size(); i++ )
337 if ( ops[i].code_rot() == Symop_code::identity() )
338 cops.push_back( ops[i] );
339 return cops;
340 }
341
laue_ops() const342 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::laue_ops() const
343 {
344 const Symop_codes& ops = (*this);
345 Symop_codes lops;
346 lops.push_back( Symop_code( Symop( RTop<>( data::mat_inv ) ) ) );
347 for ( int i = 0; i < ops.size(); i++ )
348 lops.push_back( ops[i].code_rot() );
349 return lops.expand();
350 }
351
pgrp_ops() const352 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::pgrp_ops() const
353 {
354 const Symop_codes& ops = (*this);
355 Symop_codes lops;
356 for ( int i = 0; i < ops.size(); i++ )
357 lops.push_back( ops[i].code_rot() );
358 return lops.expand();
359 }
360
patterson_ops() const361 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::patterson_ops() const
362 {
363 const Symop_codes& ops = (*this);
364 Symop_codes lops;
365 lops.push_back( Symop_code( Symop( RTop<>( data::mat_inv ) ) ) );
366 for ( int i = 0; i < ops.size(); i++ )
367 if ( ops[i].code_rot() == Symop_code::identity() )
368 lops.push_back( ops[i] );
369 else
370 lops.push_back( ops[i].code_rot() );
371 return lops.expand();
372 }
373
generator_ops() const374 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::generator_ops() const
375 {
376 Symop_codes ops = expand();
377 std::sort( ops.begin(), ops.end() );
378 Symop_codes cens = ops.centering_ops();
379 Symop_codes prms = ops.primitive_ops();
380 Symop_codes gens;
381 Symop_codes gend = gens.expand();
382
383 // first make the centering generators
384 for ( int i = 1; i < cens.size(); i++ ) {
385 for ( int j = 0; j < gend.size(); j++ )
386 if ( cens[i] == gend[j] ) goto skip1;
387 gens.push_back( cens[i] );
388 gend = gens.expand();
389 if ( gend.size() == cens.size() ) break; // optional optimisation
390 skip1:;
391 }
392 int ncen = gens.size();
393
394 // now add the rest of the generators
395 for ( int i = 1; i < prms.size(); i++ ) {
396 for ( int j = 0; j < gend.size(); j++ )
397 if ( prms[i] == gend[j] ) goto skip2;
398 gens.push_back( prms[i] );
399 gend = gens.expand();
400 if ( gend.size() == ops.size() ) break; // optional optimisation
401 skip2:;
402 }
403
404 back: // finally remove any redundent ops
405 for ( int i = ncen; i < gens.size(); i++ ) {
406 Symop_codes genp = gens;
407 genp.erase( genp.begin() + i );
408 if ( genp.expand().size() == ops.size() ) {
409 gens = genp;
410 goto back;
411 }
412 }
413
414 return gens; // return result
415 }
416
product(const Symop_codes & ops2) const417 Spgr_descr::Symop_codes Spgr_descr::Symop_codes::product( const Symop_codes& ops2 ) const
418 {
419 Symop_codes ops1 = (*this); // copy first list, implying identity
420 int i, j, n1, n2;
421 n1 = ops1.size();
422 n2 = ops2.size();
423 for ( j = 0; j < n2; j++ )
424 if ( ops2[j] != Symop_code::identity() ) // skip identity, if present
425 for ( i = 0; i < n1; i++ )
426 ops1.push_back(Symop_code(Isymop(ops1[i].isymop()*ops2[j].isymop())));
427 return ops1;
428 }
429
hash() const430 unsigned int Spgr_descr::Symop_codes::hash() const
431 {
432 Symop_codes data = expand();
433 std::sort( data.begin(), data.end() );
434 unsigned int polynomial = 0x04c11db7;
435 unsigned int remainder = 0xffffffff;
436 unsigned int datum;
437 for ( int word = 0; word < data.size(); word++ ) {
438 datum = data[word];
439 remainder ^= datum;
440 for ( int bit = 0; bit < 32; bit++ ) {
441 if ( remainder & 0x80000000 )
442 remainder = (remainder << 1) ^ polynomial;
443 else
444 remainder = (remainder << 1);
445 }
446 }
447 return remainder;
448 }
449
450
451 // Spacegroup desciption object
452
453 char Spgr_descr::pref_12 = '1';
454 char Spgr_descr::pref_hr = 'H';
455
456
457 /*! Construct a null description spacegroup. The result is initialised
458 to an invalid spacegroup code. */
Spgr_descr()459 Spgr_descr::Spgr_descr()
460 { hash_ = 0; }
461
462 /*! Construct a spacegroup description from a text description, i.e. a
463 symbol or operators. This may be one of the following:
464 - Hall symbol, e.g. " P 2ac 2ab"
465 - H-M symbol, e.g. "P 21 21 21"
466 - Number, e.g. "19"
467 - List of symmetry operators separated by semicolons, e.g.
468 "x,y,z;-x+1/2,-y,z+1/2;x+1/2,-y+1/2,-z;-x,y+1/2,-z+1/2"
469
470 It is best to specify the type of symbol being used, however if this
471 parameter is omitted a guess will be made. Unfortunately, Hall and
472 H-M symbols may be ambiguous. Any ambiguity may be removed by
473 appending parentheses "()" to the end of the Hall symbol, otherwise
474 the symbol will be interpreted as an H-M symbol, and a Hall symbol
475 if that fails.
476
477 H-M symbols and spacegroup numbers may correspond to 2 different
478 entries in international tables. The choice between 2 origin
479 settings or hexagonal/rhomohedral settings is made using the
480 set_preferred() method.
481
482 \param name The spacegroup symbol or operators.
483 \param type The type of symbol: Spacegroup::Symops, Spacegroup::Hall,
484 Spacegroup::HM, Spacegroup::XHM, Spacegroup::Number */
Spgr_descr(const String & symb,TYPE type)485 Spgr_descr::Spgr_descr( const String& symb, TYPE type )
486 {
487 using clipper::data::sgdata;
488
489 String symbx = symb.trim();
490
491 // try and guess symbol type (don't do this!)
492 if ( type == Unknown ) {
493 if ( symbx.find_first_of( "()" ) != String::npos )
494 type = Hall;
495 else if ( symbx.find_first_of( ":" ) != String::npos )
496 type = XHM;
497 else if ( symbx.find_first_of( "," ) != String::npos )
498 type = Symops;
499 else if ( symbx.find_first_of( "ABCFHIPRSTQ" ) == String::npos )
500 type = Number;
501 // otherwise still unknown: try HM then Hall.
502 }
503
504 // now make the ops
505 int i;
506 Symop_codes ops;
507
508 if ( type == Symops ) {
509 ops.init_symops( symbx );
510 } else if ( type == Hall ) {
511 ops.init_hall( symbx );
512 } else if ( type == XHM ) {
513 char ext = ' ';
514 size_t c = symbx.find_first_of( ":" );
515 if ( c != String::npos ) {
516 if ( c+1 < symbx.length() ) ext = symbx[c+1];
517 symbx = symbx.substr(0,c);
518 symbx = symbx.trim();
519 }
520 for ( i = 0; i < data::sgdata_size; i++ )
521 if ( symbx == String( sgdata[i].hm ) && sgdata[i].ext == ext ) break;
522 if ( i == data::sgdata_size )
523 Message::message( Message_fatal( "Spgr_descr: No such HM symbol" ) );
524 ops.init_hall( String( sgdata[i].hall ) );
525 } else if ( type == HM ) {
526 for ( i = 0; i < data::sgdata_size; i++ )
527 if ( symbx == String( sgdata[i].hm ) &&
528 ( sgdata[i].ext == pref_12 || sgdata[i].ext == pref_hr ||
529 sgdata[i].ext == ' ' ) ) break;
530 if ( i == data::sgdata_size )
531 Message::message( Message_fatal( "Spgr_descr: No such HM symbol" ) );
532 ops.init_hall( String( sgdata[i].hall ) );
533 } else if ( type == Number ) {
534 int num = symbx.i();
535 for ( i = 0; i < data::sgdata_size; i++ )
536 if ( num == sgdata[i].num &&
537 ( sgdata[i].ext == pref_12 || sgdata[i].ext == pref_hr ||
538 sgdata[i].ext == ' ' ) ) break;
539 if ( i == data::sgdata_size )
540 Message::message( Message_fatal( "Spgr_descr: No such SG number" ) );
541 ops.init_hall( String( sgdata[i].hall ) );
542 } else {
543 for ( i = 0; i < data::sgdata_size; i++ ) // try H-M then Hall.
544 if ( symbx == String( sgdata[i].hm ) &&
545 ( sgdata[i].ext == pref_12 || sgdata[i].ext == pref_hr ||
546 sgdata[i].ext == ' ' ) ) break;
547 if ( i != data::sgdata_size )
548 ops.init_hall( String( sgdata[i].hall ) );
549 else
550 ops.init_hall( symbx );
551 }
552
553 // store the hash and generators
554 hash_ = ops.hash();
555 generators_ = ops.generator_ops();
556 }
557
558 /*! See previous constuctor.
559 \param num The spacegroup number. */
Spgr_descr(const int & num)560 Spgr_descr::Spgr_descr( const int& num )
561 {
562 using data::sgdata;
563 Symop_codes ops;
564 int i;
565 for ( i = 0; i < data::sgdata_size; i++ )
566 if ( num == sgdata[i].num &&
567 ( sgdata[i].ext == pref_12 || sgdata[i].ext == pref_hr ||
568 sgdata[i].ext == ' ' ) ) break;
569 if ( i == data::sgdata_size )
570 Message::message( Message_fatal( "Spgr_descr: No such SG number" ) );
571 ops.init_hall( String( sgdata[i].hall ) );
572 ops = ops.expand();
573
574 // store the hash and generators
575 hash_ = ops.hash();
576 generators_ = ops.generator_ops();
577 }
578
579 /*! This is not normally used, except in conjunction with
580 Spgr_desc::generator_ops() to derive one group from another. */
Spgr_descr(const Symop_codes & ops)581 Spgr_descr::Spgr_descr( const Symop_codes& ops )
582 {
583 // store the hash and generators
584 hash_ = ops.hash();
585 generators_ = ops.generator_ops();
586 }
587
588 /*! The spacegroup number is only available if the spacegroup exists in
589 the internal table, see Hall & Grosse-Kunstleve.
590 \return The spacegroup number, or 0 if unavailable. */
spacegroup_number() const591 int Spgr_descr::spacegroup_number() const
592 {
593 for ( int i = 0; i < data::sgdata_size; i++ )
594 if ( data::sgdata[i].sghash == hash_ ) return data::sgdata[i].num;
595 return 0;
596 }
597
598 /*! The Hall symbol is only available if the spacegroup exists in
599 the internal table, see Hall & Grosse-Kunstleve.
600 \return The Hall symbol, or "Unknown" if unavailable. */
symbol_hall() const601 String Spgr_descr::symbol_hall() const
602 {
603 for ( int i = 0; i < data::sgdata_size; i++ )
604 if ( data::sgdata[i].sghash == hash_ ) return String(data::sgdata[i].hall);
605 return "Unknown";
606 }
607
608 /*! The H-M symbol is only available if the spacegroup exists in
609 the internal table, see Hall & Grosse-Kunstleve.
610 \return The H-M symbol, or "Unknown" if unavailable. */
symbol_hm() const611 String Spgr_descr::symbol_hm() const
612 {
613 for ( int i = 0; i < data::sgdata_size; i++ )
614 if ( data::sgdata[i].sghash == hash_ ) return String(data::sgdata[i].hm);
615 return "Unknown";
616 }
617
618 /*! The extended H-M symbol is only available if the spacegroup exists in
619 the internal table, see Hall & Grosse-Kunstleve.
620 \return The extended H-M symbol, or "Unknown" if unavailable. */
symbol_xhm() const621 String Spgr_descr::symbol_xhm() const
622 {
623 for ( int i = 0; i < data::sgdata_size; i++ )
624 if ( data::sgdata[i].sghash == hash_ ) {
625 String xhm( data::sgdata[i].hm );
626 if ( data::sgdata[i].ext != ' ' )
627 xhm = xhm + " :" + data::sgdata[i].ext;
628 return xhm;
629 }
630 return "Unknown";
631 }
632
633 /*! The extension H-M symbol is only available if the spacegroup exists in
634 the internal table, see Hall & Grosse-Kunstleve.
635 \return The extension H-M symbol, or "" */
symbol_hm_ext() const636 String Spgr_descr::symbol_hm_ext() const
637 {
638 String ext = "";
639 for ( int i = 0; i < data::sgdata_size; i++ )
640 if ( data::sgdata[i].sghash == hash_ )
641 if ( data::sgdata[i].ext != ' ' )
642 return ext + data::sgdata[i].ext;
643 return ext;
644 }
645
646 /*! Sets the preferred origin or setting for initialising all
647 Spgr_descr objects using H-M symbols or Spacegroup numbers. cctbx
648 uses origin choice '1' by default, CCP4 uses '2'. Both packages use
649 'H' in preference to 'R'. Preferred values are stored for
650 both. Defaults are '1' and 'H'.
651
652 CCP4 users may wish to add the following before using H-M codes or numbers.
653 \code
654 Spgr_descr::set_preferred('2');
655 \endcode
656
657 \param c Either '1' or '2', 'H' or 'R'. */
set_preferred(const char & c)658 void Spgr_descr::set_preferred( const char& c )
659 {
660 if ( c == '1' || c == '2' ) pref_12 = c;
661 if ( c == 'H' || c == 'R' ) pref_hr = c;
662 }
663
664
665
666 // Spacegroup cache object
667
668 Mutex Spgr_cacheobj::mutex = Mutex();
669
operator ()clipper::Compare_grid670 struct Compare_grid{ bool operator() ( const Vec3<>& c1, const Vec3<>& c2 ) const { return (c1[0]*c1[1]*c1[2]+0.001*c1[1]+0.00001*c1[0]) <= (c2[0]*c2[1]*c2[
671 2]+0.001*c2[1]+0.00001*c2[0]); } };
672
reci_asu(const Spgr_descr::Symop_codes & ops,data::ASUfn asufn)673 bool reci_asu( const Spgr_descr::Symop_codes& ops, data::ASUfn asufn )
674 {
675 HKL hkl, equ;
676 int i;
677
678 // make integerised symops
679 std::vector<Isymop> symops( ops.size() );
680 for ( i = 0; i < ops.size(); i++ ) symops[i] = ops[i].isymop();
681
682 // now test asu for uniqueness and completeness
683 for ( hkl.h() = -2; hkl.h() <= 2; hkl.h()++ )
684 for ( hkl.k() = -2; hkl.k() <= 2; hkl.k()++ )
685 for ( hkl.l() = -2; hkl.l() <= 2; hkl.l()++ ) {
686 if ( asufn( hkl.h(), hkl.k(), hkl.l() ) ) {
687 for ( i = 0; i < symops.size(); i++ ) {
688 equ = hkl.transform( symops[i] );
689 if ( equ != hkl && asufn(equ.h(),equ.k(),equ.l()) ) break;
690 equ = -equ;
691 if ( equ != hkl && asufn(equ.h(),equ.k(),equ.l()) ) break;
692 }
693 if ( i != symops.size() ) return false;
694 } else {
695 for ( i = 0; i < symops.size(); i++ ) {
696 equ = hkl.transform( symops[i] );
697 if ( asufn(equ.h(),equ.k(),equ.l()) ) break;
698 equ = -equ;
699 if ( asufn(equ.h(),equ.k(),equ.l()) ) break;
700 }
701 if ( i == symops.size() ) return false;
702 }
703 }
704 return true;
705 }
706
real_asu(const Spgr_descr::Symop_codes & ops)707 Vec3<> real_asu( const Spgr_descr::Symop_codes& ops )
708 {
709 int i, j, sym, nasu;
710
711 // make integerised symops
712 std::vector<Isymop> symops( ops.size() );
713 for ( i = 0; i < ops.size(); i++ ) symops[i] = ops[i].isymop();
714
715 // classify each grid point by a unique 'ASU number'
716 Coord_grid c;
717 Grid_sampling cgrid(24,24,24);
718 int symmap[13824], tstmap[13824];
719 for ( i = 0; i < cgrid.size(); i++ ) symmap[i] = -1;
720 nasu = 0;
721 for ( c = Coord_grid(0,0,0); !c.last(cgrid); c.next(cgrid) ) {
722 i = c.index(cgrid);
723 if ( symmap[i] == -1 ) {
724 for ( sym = 0; sym < symops.size(); sym++ )
725 symmap[ c.transform(symops[sym]).unit(cgrid).index(cgrid) ] = nasu;
726 nasu++;
727 }
728 }
729
730 // identify trigonal/hexagonal groups
731 bool trighex = false;
732 for ( sym = 0; sym < symops.size(); sym++ )
733 for ( i = 0; i < 3; i++ ) {
734 int c = 0, r = 0; // trigonal if any row/col adds to 2
735 for ( j = 0; j < 3; j++ ) {
736 c += abs( symops[sym].rot()(i,j) );
737 r += abs( symops[sym].rot()(j,i) );
738 trighex = trighex || ( c == 2 ) || ( r == 2 );
739 }
740 }
741
742 // now set search ASU search grid, dependent on symmetry
743 std::vector<ftype> gridlim;
744 const ftype d = 0.0001;
745 if ( trighex ) {
746 ftype lim[] = { 1./12-d, 1./6-d, 1./6+d, 1./3-d, 1./3+d,
747 1./2-d, 1./2+d, 2./3+d, 1.-d };
748 gridlim = std::vector<ftype>( &lim[0], &lim[ sizeof(lim)/sizeof(ftype) ] );
749 } else {
750 ftype lim[] = { 1./8+d, 1./4-d, 1./4+d, 1./2-d, 1./2+d, 1.-d };
751 gridlim = std::vector<ftype>( &lim[0], &lim[ sizeof(lim)/sizeof(ftype) ] );
752 }
753
754 // make a sorted list of grids
755 std::vector<Vec3<> > grids;
756 for ( int gui = 0; gui < gridlim.size(); gui++ )
757 for ( int gvi = 0; gvi < gridlim.size(); gvi++ )
758 for ( int gwi = 0; gwi < gridlim.size(); gwi++ )
759 grids.push_back( Vec3<>(gridlim[gui],gridlim[gvi],gridlim[gwi]) );
760 std::sort( grids.begin(), grids.end(), Compare_grid() );
761
762 // now find smallest viable grid
763 for ( j = 0; j < grids.size(); j++ ) {
764 Grid mgrid = Grid( Util::intc(grids[j][0]*cgrid.nu()),
765 Util::intc(grids[j][1]*cgrid.nv()),
766 Util::intc(grids[j][2]*cgrid.nw()) );
767 if ( mgrid.size() >= nasu ) { // is grid big enough to be ASU?
768 for ( i = 0; i < nasu; i++ ) tstmap[i] = 0; // if so, check for all pts
769 for ( c = Coord_grid(0,0,0); !c.last(mgrid); c.next(mgrid) )
770 tstmap[ symmap[ c.index(cgrid) ] ] = 1;
771 for ( i = 0; i < nasu; i++ ) if ( tstmap[i] == 0 ) break;
772 if ( i == nasu ) break; // found a full asu, so we're done
773 }
774 }
775 return grids[j];
776 }
777
Spgr_cacheobj(const Key & spgr_cachekey)778 Spgr_cacheobj::Spgr_cacheobj( const Key& spgr_cachekey )
779 {
780 spgr_cachekey_ = spgr_cachekey;
781
782 // tidy the ops
783 Spgr_descr::Symop_codes ops = spgr_cachekey.generator_ops().expand();
784 std::sort( ops.begin(), ops.end() );
785 Spgr_descr::Symop_codes pops = ops.primitive_noninversion_ops();
786 Spgr_descr::Symop_codes iops = ops.inversion_ops();
787 Spgr_descr::Symop_codes cops = ops.centering_ops();
788 std::sort( pops.begin(), pops.end() );
789 std::sort( iops.begin(), iops.end() );
790 std::sort( cops.begin(), cops.end() );
791 ops = pops;
792 ops = ops.product( iops );
793 ops = ops.product( cops );
794 nsym = ops.size();
795 nsymn = pops.size();
796 nsymi = iops.size();
797 nsymc = cops.size();
798 nsymp = nsymn*nsymi;
799
800 // consistency check (redundent)
801 if ( ops.hash() != spgr_cachekey.hash() )
802 Message::message( Message_fatal( "Spgr_cacheobj: symops fail" ) );
803
804 // Laue group
805 unsigned int lghash = ops.laue_ops().hash();
806 // first guess from the hash
807 for ( lgrp = 0; lgrp < data::lgdata_size; lgrp++ )
808 if ( data::lgdata[lgrp].lghash == lghash ) break;
809 if ( lgrp == data::lgdata_size ) lgrp = 0;
810 // if that fails, try all the ASU functions
811 if ( !reci_asu( pops, data::lgdata[lgrp].asufn ) ) {
812 std::ostringstream s;
813 s << "Spacegroup_registry: ASU warning, LGhash=0x";
814 s.width( 8 ); s.fill( '0' ); s.flags( s.hex | s.right ); s << lghash;
815 Message::message( Message_warn( s.str() ) );
816 for ( lgrp = 0; lgrp < data::lgdata_size; lgrp++ )
817 if ( reci_asu( pops, data::lgdata[lgrp].asufn ) ) break;
818 if ( lgrp == data::lgdata_size )
819 Message::message( Message_fatal( "Spacegroup_registry: ASU fail" ) );
820 }
821
822 // real ASU
823 asu_min_ = Vec3<>( -0.0001, -0.0001, -0.0001 );
824 asu_max_ = real_asu( ops );
825
826 // now make real symop lists
827 for ( int i = 0; i < ops.size(); i++ ) {
828 symops.push_back( ops[i].symop() );
829 isymops.push_back( ops[i].isymop() );
830 }
831 }
832
matches(const Key & spgr_cachekey) const833 bool Spgr_cacheobj::matches( const Key& spgr_cachekey ) const
834 { return spgr_cachekey_.hash() == spgr_cachekey.hash(); }
835
format() const836 String Spgr_cacheobj::format() const
837 { return spgr_cachekey_.symbol_hall(); }
838
839
840
841 // spacegroup object
842
843 // Null ASU function and symops
844 Symop SYMOP_NULL = Symop( RTop_frac::identity() );
ASU_NULL(const int & h,const int & k,const int & l)845 bool ASU_NULL( const int& h, const int& k, const int& l ) { return true; }
846
847 /*! Construct null or P1 spacegroup. This is faster than the normal
848 constructor.
849 \param type Spacegroup::Null or Spacegroup::P1 */
Spacegroup(TYPE type)850 Spacegroup::Spacegroup( TYPE type )
851 {
852 if ( type == P1 ) {
853 init( Spgr_descr( "x,y,z", Spgr_descr::Symops ) );
854 } else {
855 nsym = nsymp = 1; // workaround to enable handling of reflection data with null spacegroup
856 symops = &SYMOP_NULL;
857 asufn = ASU_NULL;
858 }
859 }
860
861 /*! Construct a spacegroup and initialise with a spacegroup description.
862 \param spgr_descr The spacegroup description. */
Spacegroup(const Spgr_descr & spgr_descr)863 Spacegroup::Spacegroup( const Spgr_descr& spgr_descr )
864 { init( spgr_descr ); }
865
866 /*! Initialise the spacegroup.
867 \param spgr_descr The spacegroup description. */
init(const Spgr_descr & spgr_descr)868 void Spacegroup::init( const Spgr_descr& spgr_descr )
869 {
870 // init spgr descr
871 hash_ = spgr_descr.hash();
872 generators_ = spgr_descr.generator_ops();
873 // init cache entry
874 cacheref = ClipperInstantiator::instance().spacegroup_cache().cache( spgr_descr );
875 symops = &(cacheref.data().symops[0]);
876 isymops = &(cacheref.data().isymops[0]);
877 nsym = cacheref.data().nsym;
878 nsymn = cacheref.data().nsymn;
879 nsymi = cacheref.data().nsymi;
880 nsymc = cacheref.data().nsymc;
881 nsymp = cacheref.data().nsymp;
882 asufn = data::lgdata[cacheref.data().lgrp].asufn;
883 }
884
885 /*! \return true if the object has not been initalised. */
is_null() const886 bool Spacegroup::is_null() const
887 { return cacheref.is_null(); }
888
889 /*! The number of rotational operators parallel to the specified axis
890 is returned.
891 \param axis The axis, A, B or C.
892 \return The order of the axis. */
order_of_symmetry_about_axis(const AXIS axis) const893 int Spacegroup::order_of_symmetry_about_axis( const AXIS axis ) const
894 {
895 int n = 0;
896 int a0 = int( axis );
897 int a1 = (a0+1)%3;
898 int a2 = (a0+2)%3;
899 for ( int i = 0; i < nsymp; i++ )
900 if ( symops[i].rot().det() > 0.0 )
901 if ( fabs( symops[i].rot()(a0,a1) ) + fabs( symops[i].rot()(a1,a0) ) +
902 fabs( symops[i].rot()(a0,a2) ) + fabs( symops[i].rot()(a2,a0) ) +
903 fabs( symops[i].rot()(a0,a0) - 1.0 ) < 1.0e-6 ) n++;
904 return n;
905 }
906
907 /*! The reflection class describes the type of a reflection in a given
908 spacegroup, including centricity, systematic absence, phase
909 restriction, and multiplicity.
910
911 This is a shortcut to constructing an HKL_class from the
912 spacegroup and HKL.
913 \param hkl The reflection HKL */
hkl_class(const HKL & hkl) const914 HKL_class Spacegroup::hkl_class( const HKL& hkl ) const
915 { return HKL_class( *this, hkl ); }
916
917 /*! The reciprocal ASU is chosen from one of 47 optimised functions.
918 \param hkl The HKL to test.
919 \return true if the HKL is in the ASU. */
recip_asu(const HKL & hkl) const920 bool Spacegroup::recip_asu( const HKL& hkl ) const
921 { return asufn( hkl.h(), hkl.k(), hkl.l() ); }
922
product_op(const int & s1,int & s2) const923 int Spacegroup::product_op( const int& s1, int& s2 ) const
924 {
925 Symop mat( symops[s1] * symops[s2] );
926 for ( int s3 = 0; s3 < nsym; s3++ )
927 if ( mat.equals( symops[s3], 0.001 ) ) return s3;
928 Message::message( Message_fatal("Spacegroup: Internal spacegroup error - missing product") ); return -1;
929 }
930
inverse_op(const int & s1) const931 int Spacegroup::inverse_op( const int& s1 ) const
932 {
933 for ( int s2 = 0; s2 < nsym; s2++ )
934 if ( symops[0].equals( symops[s1] * symops[s2], 0.001 ) ) return s2;
935 Message::message( Message_fatal("Spacegroup: Internal spacegroup error - missing inverse") ); return -1;
936 }
937
938 /*! The map ASU is an oblong which contains at least one assymetric
939 unit. It is guaranteed to be contained withing the unit box. The
940 lower limit is always 0,0,0.
941 \return Fractional coordinate of the upper bound of the ASU. */
asu_max() const942 Coord_frac Spacegroup::asu_max() const
943 { return Coord_frac( cacheref.data().asu_max_ ); }
944
945 /*! The map ASU is an oblong which contains at least one assymetric
946 unit. It is guaranteed to be contained withing the unit box. The
947 lower limit is always 0,0,0.
948 \return Fractional coordinate of the lower bound of the ASU. */
asu_min() const949 Coord_frac Spacegroup::asu_min() const
950 { return Coord_frac( cacheref.data().asu_min_ ); }
951
952 /*! Test if hand-change is possible.
953 \return true if a change of hand preserves the spacegroup. */
invariant_under_change_of_hand() const954 bool Spacegroup::invariant_under_change_of_hand() const
955 {
956 for ( int k=0; k<nsym; k++ )
957 for ( int j=0; j<3; j++ )
958 if ( isymops[k].rot()(j,j) == 1 )
959 if ( isymops[k].trn()[j] != 0 && isymops[k].trn()[j] != 12 )
960 return false;
961 return true;
962 }
963
964 /*! \return The Laue group symbol. i.e. one of
965 -1, 2/m, 2/mmm, -3, -3m, 4/m, 4/mmm, 6/m, 6/mmm, m-3, m-3m */
symbol_laue() const966 String Spacegroup::symbol_laue() const
967 { return String( data::lgdata[cacheref.data().lgrp].lgname ); }
968
debug() const969 void Spacegroup::debug() const
970 {
971 // print the members of the class
972 int k;
973 std::cout << spacegroup_number() << " " << nsym << " " << nsymp << " " << symbol_hall() << "\n";
974 for (k=0; k<nsym; k++)
975 std::cout << k << ": " << symop(k).format() << "\n";
976 }
977
978
979 } // namespace clipper
980