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