Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members   Related Pages  

MeasureSymmetry.C

Go to the documentation of this file.
00001 /***************************************************************************
00002  *cr                                                                       
00003  *cr            (C) Copyright 1995-2011 The Board of Trustees of the           
00004  *cr                        University of Illinois                       
00005  *cr                         All Rights Reserved                        
00006  *cr                                                                   
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  * RCS INFORMATION:
00011  *
00012  *      $RCSfile: MeasureSymmetry.C,v $
00013  *      $Author: saam $ $Locker:  $             $State: Exp $
00014  *      $Revision: 1.58 $       $Date: 2011/02/20 22:09:55 $
00015  *
00016  ***************************************************************************
00017  * DESCRIPTION:
00018  * The Symmetry class is the work horse behind the "measure symmetry"
00019  * command which guesses the pointgroup of a selection and returns the
00020  * according symmetry elements (mirror planes rotary axes, rotary reflection
00021  * axes). The algorithm is is fairly forgiving about molecules where atoms
00022  * are perturbed from the ideal position and tries its best to guess the
00023  * correct point group anyway. 
00024  * The tolerance can be controlled with the sigma parameter which is
00025  * the average allowed deviation from the ideal position.
00026  * Works nice on my 30 non-patholocical test cases. A pathological case
00027  * would for instance be a system with more than only a few atoms (say 15)
00028  * where only one atom distinguishes between two point groups.
00029  * If your selection contains more than a certain number of atoms then
00030  * only that much randomly chosen atoms are used to find planes and axes
00031  * in order to save time.
00032  *
00033  ***************************************************************************/
00034 
00035 #include <stdio.h>
00036 #include <math.h>
00037 #include "utilities.h"
00038 #include "Measure.h"
00039 #include "MeasureSymmetry.h"
00040 #include "AtomSel.h"
00041 #include "Matrix4.h"
00042 #include "MoleculeList.h"
00043 #include "SpatialSearch.h"     // for vmd_gridsearch3()
00044 #include "MoleculeGraphics.h"  // needed only for debugging
00045 #include "Inform.h"
00046 #include "WKFUtils.h"
00047 
00048 #define POINTGROUP_UNKNOWN 0
00049 #define POINTGROUP_C1      1
00050 #define POINTGROUP_CN      2
00051 #define POINTGROUP_CNV     3
00052 #define POINTGROUP_CNH     4
00053 #define POINTGROUP_CINFV   5
00054 #define POINTGROUP_DN      6
00055 #define POINTGROUP_DND     7
00056 #define POINTGROUP_DNH     8
00057 #define POINTGROUP_DINFH   9
00058 #define POINTGROUP_CI     10
00059 #define POINTGROUP_CS     11
00060 #define POINTGROUP_S2N    12
00061 #define POINTGROUP_T      13
00062 #define POINTGROUP_TD     14  
00063 #define POINTGROUP_TH     15
00064 #define POINTGROUP_O      16
00065 #define POINTGROUP_OH     17
00066 #define POINTGROUP_I      18
00067 #define POINTGROUP_IH     19
00068 #define POINTGROUP_KH     20
00069 
00070 #define OVERLAPCUTOFF 0.4
00071 
00072 // The bondsum is the sum of all normalized bond vectors
00073 // for an atom. If the endpoints of the bondsum vectors
00074 // of two atoms are further apart than BONDSUMTOL, then
00075 // The two atoms are not considered images of each other
00076 // with respect to the transformation that was tested.
00077 #define BONDSUMTOL    1.5
00078 
00079 // Minimum atom distance before the sanity check for
00080 // idealized coordinates fails.
00081 #define MINATOMDIST   0.6
00082 
00083 // Mirror plane classification
00084 #define VERTICALPLANE   1
00085 #define DIHEDRALPLANE   3
00086 #define HORIZONTALPLANE 4
00087 
00088 // Special axis order values
00089 #define INFINITE_ORDER -1
00090 #define PRELIMINARY_C2 -2
00091 
00092 // Flags for requesting special geometries for idealize_angle()
00093 #define TETRAHEDRON  -4
00094 #define OCTAHEDRON   -8
00095 #define DODECAHEDRON -12
00096 
00097 
00098 
00099 #if defined(_MSC_VER)
00100 // Microsoft's compiler lacks erfc() so we make our own here:
00101 static double myerfc(double x) {
00102   double p, a1, a2, a3, a4, a5;
00103   double t, erfcx;
00104 
00105   p  =  0.3275911;
00106   a1 =  0.254829592;
00107   a2 = -0.284496736;
00108   a3 =  1.421413741;
00109   a4 = -1.453152027;
00110   a5 =  1.061405429;
00111 
00112   t = 1.0 / (1.0 + p*x);
00113   erfcx = ((a1 + (a2 + (a3 + (a4 + a5*t)*t)*t)*t)*t) * exp(-pow(x,2.0));
00114   return erfcx;
00115 }
00116 #endif
00117 
00118 
00119 // Forward declarations of helper functions
00120 static inline bool coplanar(const float *normal1, const float *normal2, float tol);
00121 static inline bool collinear(const float *axis1, const float *axis2, float tol);
00122 static inline bool orthogonal(const float *axis1, const float *axis2, float tol);
00123 static inline bool behind_plane(const float *normal, const float *coor);
00124 static int isprime(int x);
00125 static int numprimefactors(int x);
00126 static void align_plane_with_axis(const float *normal, const float *axis, float *alignedplane);
00127 static void assign_atoms(AtomSel *sel, MoleculeList *mlist, float *(&mycoor), int *(&atomtype));
00128 static float trans_overlap(int *atomtype, float *(&coor), int numcoor,
00129                            const Matrix4 *trans, float sigma,
00130                            bool skipident, int maxnatoms, float &overlappermatch);
00131 static inline float trans_overlap(int *atomtype, float *(&coor), int numcoor,
00132                                   const Matrix4 *trans, float sigma,
00133                                   bool skipident, int maxnatoms);
00134 
00135 
00136 // Store the symmetry characteristics for a structure
00137 typedef struct {
00138   int pointgroup;
00139   int pointgrouporder;
00140   float rmsd; 
00141   float *idealcoor;
00142   Plane *planes;
00143   Axis  *axes;
00144   Axis  *rotreflections;
00145   bool linear;        
00146   bool planar;        
00147   bool inversion;     
00148   bool sphericaltop;  
00149   bool symmetrictop;  
00150 } Best;
00151 
00152 
00153 // Symmetry elements defining a pointgroup
00154 typedef struct {
00155   char  name[6]; // name string
00156   ElementSummary summary;   
00157 } PointGroupDefinition;
00158 
00159 PointGroupDefinition pgdefinitions[] = {
00160 //        inv sig Cinf      C2 C3 C4 C5 C6 C7 C8   S3        S8              S16
00161   { "C1",  {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00162   { "Cs",  {0, 1, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00163   { "Ci",  {1, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00164   { "C2",  {0, 0, 0, {0, 0, 1, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00165   { "C3",  {0, 0, 0, {0, 0, 0, 1, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00166   { "C4",  {0, 0, 0, {0, 0, 1, 0, 1, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00167   { "C5",  {0, 0, 0, {0, 0, 0, 0, 0, 1, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00168   { "C6",  {0, 0, 0, {0, 0, 1, 1, 0, 0, 1, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00169   { "C7",  {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 1, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00170   { "C8",  {0, 0, 0, {0, 0, 1, 0, 1, 0, 0, 0, 1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00171   { "D2",  {0, 0, 0, {0, 0, 3, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00172   { "D3",  {0, 0, 0, {0, 0, 3, 1, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00173   { "D4",  {0, 0, 0, {0, 0, 5, 0, 1, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00174   { "D5",  {0, 0, 0, {0, 0, 5, 0, 0, 1, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00175   { "D6",  {0, 0, 0, {0, 0, 7, 1, 0, 0, 1, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00176   { "D7",  {0, 0, 0, {0, 0, 7, 0, 0, 0, 0, 1, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00177   { "D8",  {0, 0, 0, {0, 0, 9, 0, 1, 0, 0, 0, 1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00178   { "C2v", {0, 2, 0, {0, 0, 1, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00179   { "C3v", {0, 3, 0, {0, 0, 0, 1, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00180   { "C4v", {0, 4, 0, {0, 0, 1, 0, 1, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00181   { "C5v", {0, 5, 0, {0, 0, 0, 0, 0, 1, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00182   { "C6v", {0, 6, 0, {0, 0, 1, 1, 0, 0, 1, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00183   { "C7v", {0, 7, 0, {0, 0, 0, 0, 0, 0, 0, 1, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00184   { "C8v", {0, 8, 0, {0, 0, 1, 0, 1, 0, 0, 0, 1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00185   { "C2h", {1, 1, 0, {0, 0, 1, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00186   { "C3h", {0, 1, 0, {0, 0, 0, 1, 0, 0, 0, 0, 0}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00187   { "C4h", {1, 1, 0, {0, 0, 1, 0, 1, 0, 0, 0, 0}, {0,1,0,0,0,0,0,0,0,0,0,0,0,0}} },
00188   { "C5h", {0, 1, 0, {0, 0, 0, 0, 0, 1, 0, 0, 0}, {0,0,1,0,0,0,0,0,0,0,0,0,0,0}} },
00189   { "C6h", {1, 1, 0, {0, 0, 1, 1, 0, 0, 1, 0, 0}, {0,0,0,1,0,0,0,0,0,0,0,0,0,0}} },
00190   { "C7h", {0, 1, 0, {0, 0, 0, 0, 0, 0, 0, 1, 0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0}} },
00191   { "C8h", {1, 1, 0, {0, 0, 1, 0, 1, 0, 0, 0, 1}, {0,1,0,0,0,1,0,0,0,0,0,0,0,0}} },
00192   { "D2h", {1, 3, 0, {0, 0, 3, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00193   { "D3h", {0, 4, 0, {0, 0, 3, 1, 0, 0, 0, 0, 0}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00194   { "D4h", {1, 5, 0, {0, 0, 5, 0, 1, 0, 0, 0, 0}, {0,1,0,0,0,0,0,0,0,0,0,0,0,0}} },
00195   { "D5h", {0, 6, 0, {0, 0, 5, 0, 0, 1, 0, 0, 0}, {0,0,1,0,0,0,0,0,0,0,0,0,0,0}} },
00196   { "D6h", {1, 7, 0, {0, 0, 7, 1, 0, 0, 1, 0, 0}, {1,0,0,1,0,0,0,0,0,0,0,0,0,0}} },
00197   { "D7h", {0, 8, 0, {0, 0, 7, 0, 0, 0, 0, 1, 0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0}} },
00198   { "D8h", {1, 9, 0, {0, 0, 9, 0, 1, 0, 0, 0, 1}, {0,1,0,0,0,1,0,0,0,0,0,0,0,0}} },
00199   { "D2d", {0, 2, 0, {0, 0, 3, 0, 0, 0, 0, 0, 0}, {0,1,0,0,0,0,0,0,0,0,0,0,0,0}} },
00200   { "D3d", {1, 3, 0, {0, 0, 3, 1, 0, 0, 0, 0, 0}, {0,0,0,1,0,0,0,0,0,0,0,0,0,0}} },
00201   { "D4d", {0, 4, 0, {0, 0, 5, 0, 1, 0, 0, 0, 0}, {0,0,0,0,0,1,0,0,0,0,0,0,0,0}} },
00202   { "D5d", {1, 5, 0, {0, 0, 5, 0, 0, 1, 0, 0, 0}, {0,0,0,0,0,0,0,1,0,0,0,0,0,0}} },
00203   { "D6d", {0, 6, 0, {0, 0, 7, 1, 0, 0, 1, 0, 0}, {0,1,0,0,0,0,0,0,0,1,0,0,0,0}} },
00204   { "D7d", {1, 7, 0, {0, 0, 7, 0, 0, 0, 0, 1, 0}, {0,0,0,0,0,0,0,0,0,0,0,1,0,0}} },
00205   { "D8d", {0, 8, 0, {0, 0, 9, 0, 1, 0, 0, 0, 1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,1}} },
00206   { "S4",  {0, 0, 0, {0, 0, 1, 0, 0, 0, 0, 0, 0}, {0,1,0,0,0,0,0,0,0,0,0,0,0,0}} },
00207   { "S6",  {1, 0, 0, {0, 0, 0, 1, 0, 0, 0, 0, 0}, {0,0,0,1,0,0,0,0,0,0,0,0,0,0}} },
00208   { "S8",  {0, 0, 0, {0, 0, 1, 0, 1, 0, 0, 0, 0}, {0,0,0,0,0,1,0,0,0,0,0,0,0,0}} },
00209   { "T",   {0, 0, 0, {0, 0, 3, 4, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00210   { "Th",  {1, 3, 0, {0, 0, 3, 4, 0, 0, 0, 0, 0}, {0,0,0,4,0,0,0,0,0,0,0,0,0,0}} },
00211   { "Td",  {0, 6, 0, {0, 0, 3, 4, 0, 0, 0, 0, 0}, {0,3,0,0,0,0,0,0,0,0,0,0,0,0}} },
00212   { "O",   {0, 0, 0, {0, 0, 9, 4, 3, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00213   { "Oh",  {1, 9, 0, {0, 0, 9, 4, 3, 0, 0, 0, 0}, {0,3,0,4,0,0,0,0,0,0,0,0,0,0}} },
00214   { "I",   {0, 0, 0, {0, 0,15,10, 0, 6, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00215   { "Ih",  {1,15, 0, {0, 0,15,10, 0, 6, 0, 0, 0}, {0,0,0,10,0,0,0,6,0,0,0,0,0,0}} },
00216   { "Cinfv", {0, 1, 1, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00217   { "Dinfh", {1, 2, 1, {0, 0, 1, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00218   { "Kh",    {1, 1, 1, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}} },
00219 };
00220 
00221 #define NUMPOINTGROUPS (sizeof(pgdefinitions)/sizeof(PointGroupDefinition))
00222 
00223 
00224 Symmetry::Symmetry(AtomSel *mysel, MoleculeList *mymlist, int verbosity) :
00225   sel(mysel),
00226   mlist(mymlist),
00227   verbose(verbosity)
00228 {
00229   sigma = 0.1f;
00230   sphericaltop = 0;
00231   symmetrictop = 0;
00232   linear = 0;
00233   planar = 0;
00234   inversion = 0;
00235   maxaxisorder = 0;
00236   maxrotreflorder = 0;
00237   maxweight  = 0;
00238   maxoverlap = 0.0;
00239   pointgroup = POINTGROUP_UNKNOWN;
00240   pointgrouporder = 0;
00241   numdihedralplanes = 0;
00242   numverticalplanes = 0;
00243   horizontalplane = -1;
00244   maxnatoms = 70;
00245   rmsd = 0.0;
00246   idealcoor = NULL;
00247   coor = NULL;
00248   checkbonds = 0;
00249   bondsum = NULL;
00250   bondsperatom = NULL;
00251   atomtype = NULL;
00252   atomindex = NULL;
00253   uniqueatoms = NULL;
00254   elementsummarystring = NULL;
00255   missingelementstring    = new char[2 + 10*(3+MAXORDERCN+2*MAXORDERCN)];
00256   additionalelementstring = new char[2 + 10*(3+MAXORDERCN+2*MAXORDERCN)];
00257   missingelementstring[0]    = '\0';
00258   additionalelementstring[0] = '\0';
00259 
00260   memset(&elementsummary, 0, sizeof(ElementSummary));
00261 
00262   if (sel->selected) {
00263     coor      = new float[3*sel->selected];
00264     idealcoor = new float[3*sel->selected];
00265     bondsum   = new float[3*sel->selected];
00266     bondsperatom = new Bondlist[sel->selected];
00267     atomtype    = new int[sel->selected];
00268     atomindex   = new int[sel->selected];
00269     uniqueatoms = new int[sel->selected];
00270 
00271     memset(bondsperatom, 0, sel->selected*sizeof(Bondlist));
00272   }
00273 
00274   memset(&(inertiaaxes[0][0]),  0, 9*sizeof(float));
00275   memset(&(inertiaeigenval[0]), 0, 3*sizeof(float));
00276   memset(&(uniqueprimary[0]),   0, 3*sizeof(int));
00277   memset(&(rcom[0]), 0, 3*sizeof(float));
00278 
00279   // Copy coordinates of selected atoms into local array and assign
00280   // and atomtypes based on chemial element and topology.
00281   assign_atoms(sel, mlist, coor, atomtype);
00282 
00283    // Determine the bond topology
00284   assign_bonds();
00285 
00286 
00287   // default collinearity tolerance ~0.9848
00288   collintol = cosf(float(DEGTORAD(10.0f)));
00289 
00290   // default coplanarity tolerance ~0.1736
00291   orthogtol = cosf(float(DEGTORAD(80.0f)));
00292 
00293   if (sel->selected <= 10) {
00294     collintol = cosf(float(DEGTORAD(15.0f)));
00295     orthogtol = cosf(float(DEGTORAD(75.0f)));
00296   }
00297 }
00298 
00299 
00300 
00301 Symmetry::~Symmetry(void) {
00302   if (coor)           delete [] coor;
00303   if (idealcoor)      delete [] idealcoor;
00304   if (bondsum)        delete [] bondsum;
00305   if (atomtype)       delete [] atomtype;
00306   if (atomindex)      delete [] atomindex;
00307   if (uniqueatoms)    delete [] uniqueatoms;
00308   if (elementsummarystring) delete [] elementsummarystring;
00309   delete [] missingelementstring;
00310   delete [] additionalelementstring;
00311   if (bondsperatom) {
00312     int i;
00313     for (i=0; i<sel->selected; i++) {
00314       if (bondsperatom[i].bondto) delete [] bondsperatom[i].bondto;
00315       if (bondsperatom[i].length) delete [] bondsperatom[i].length;
00316     }
00317     delete [] bondsperatom;
00318   }
00319 }
00320 
00323 void Symmetry::get_pointgroup(char pg[8], int *order) {
00324   char n[3];
00325   if (order==NULL) sprintf(n, "%i", pointgrouporder);
00326   else {
00327     strcpy(n, "n");
00328     *order = pointgrouporder;
00329   }
00330 
00331   switch (pointgroup) {
00332   case POINTGROUP_UNKNOWN:
00333     strcpy(pg, "Unknown");
00334     break;
00335   case POINTGROUP_C1:
00336     strcpy(pg, "C1");
00337     break;
00338   case POINTGROUP_CN:
00339     sprintf(pg, "C%s", n);
00340     break;
00341   case POINTGROUP_CNV:
00342     sprintf(pg, "C%sv", n);
00343     break;
00344   case POINTGROUP_CNH:
00345     sprintf(pg, "C%sh", n);
00346     break;
00347   case POINTGROUP_CINFV:
00348     strcpy(pg, "Cinfv");
00349     break;
00350   case POINTGROUP_DN:
00351     sprintf(pg, "D%s", n);
00352     break;
00353   case POINTGROUP_DND: 
00354     sprintf(pg, "D%sd", n);
00355     break;
00356   case POINTGROUP_DNH:
00357     sprintf(pg, "D%sh", n);
00358     break;
00359   case POINTGROUP_DINFH:
00360     strcpy(pg, "Dinfh");
00361     break;
00362   case POINTGROUP_CI:
00363     strcpy(pg, "Ci");
00364     break;
00365   case POINTGROUP_CS:
00366     strcpy(pg, "Cs");
00367     break;
00368   case POINTGROUP_S2N:
00369     if (!strcmp(n, "n")) 
00370       strcpy(pg, "S2n");
00371     else
00372       sprintf(pg, "S%i", 2*pointgrouporder);
00373     break;
00374   case POINTGROUP_T:
00375     strcpy(pg, "T");
00376     break;
00377   case POINTGROUP_TD:
00378     strcpy(pg, "Td");
00379     break;
00380   case POINTGROUP_TH:
00381     strcpy(pg, "Th");
00382     break;
00383   case POINTGROUP_O:
00384     strcpy(pg, "O");
00385     break;
00386   case POINTGROUP_OH: 
00387     strcpy(pg, "Oh");
00388     break;
00389   case POINTGROUP_I:
00390     strcpy(pg, "I");
00391     break;
00392   case POINTGROUP_IH:
00393     strcpy(pg, "Ih");
00394     break;
00395   case POINTGROUP_KH:
00396     strcpy(pg, "Kh");
00397     break;
00398   }
00399 }
00400 
00401 
00403 int Symmetry::get_axisorder(int n) {
00404   if (n<numaxes()) return axes[n].order;
00405   return 0;
00406 }
00407 
00409 int Symmetry::get_rotreflectorder(int n) {
00410   if (n<numrotreflect()) return rotreflections[n].order;
00411   return 0;
00412 }
00413 
00415 int Symmetry::numS2n() {
00416   int i=0, count=0;
00417   for (i=0; i<numrotreflect(); i++) {
00418     if (rotreflections[i].order % 2 == 0) count++;
00419   }
00420   return count;
00421 }
00422 
00424 int Symmetry::numprimaryaxes() {
00425   int i;
00426   int numprimary = 0;
00427   for (i=0; i<numaxes(); i++) {
00428     if (axes[i].order==maxaxisorder) numprimary++;
00429   }
00430   return numprimary;
00431 }
00432 
00433 
00434 // Impose certain symmetry elements on structure
00435 // by wrapping coordinates around and averaging them.
00436 // This creates symmetry, if there was one before or not.
00437 // If mutually incompatible elements are specified the
00438 // result is hard to predict:
00439 // Planes are idealized first, then rotary axes, then
00440 // rotary reflections and last an inversion.
00441 void Symmetry::impose(int have_inversion, 
00442                       int nplanes, const float *planev,
00443                       int naxes, const float *axisv, const int *axisorder,
00444                       int nrotrefl, const float* rotreflv,
00445                       const int *rotreflorder) {
00446   int i;
00447   char buf[256];
00448   if (have_inversion) {
00449     inversion = 1;
00450     if (verbose>1) {
00451       sprintf(buf, "imposing inversion\n");
00452       msgInfo << buf << sendmsg;
00453     }
00454   }
00455   planes.clear();
00456   for (i=0; i<nplanes; i++) {
00457     Plane p;
00458     vec_copy(p.v, &planev[3*i]);
00459     vec_normalize(p.v);
00460     if (norm(p.v)==0.f) continue;
00461     p.overlap = 1.f;
00462     p.weight = 1;
00463     p.type = 0;
00464     planes.append(p);
00465     if (verbose>1) {
00466       sprintf(buf, "imposing plane {% .2f % .2f % .2f}\n", p.v[0], p.v[1], p.v[2]);
00467       msgInfo << buf << sendmsg;
00468     }
00469   }
00470   axes.clear();
00471   for (i=0; i<naxes; i++) {
00472     if (axisorder[i]<=1) continue;
00473     Axis a;
00474     vec_copy(a.v, &axisv[3*i]);
00475     vec_normalize(a.v);
00476     if (norm(a.v)==0.f) continue;
00477     a.order = axisorder[i];
00478     a.overlap = 1.f;
00479     a.weight = 1;
00480     a.type = 0;
00481     axes.append(a);
00482     if (verbose>1) {
00483       sprintf(buf, "imposing axis {% .2f % .2f % .2f}\n", a.v[0], a.v[1], a.v[2]);
00484       msgInfo << buf << sendmsg;
00485     }
00486   }
00487   rotreflections.clear();
00488   for (i=0; i<nrotrefl; i++) {
00489     if (rotreflorder[i]<=1) continue;
00490     Axis a;
00491     vec_copy(a.v, &rotreflv[3*i]);
00492     vec_normalize(a.v);
00493     if (norm(a.v)==0.f) continue;
00494     a.order = rotreflorder[i];
00495     a.overlap = 1.f;
00496     a.weight = 1;
00497     a.type = 0;
00498     rotreflections.append(a);
00499     if (verbose>1) {
00500       sprintf(buf, "imposing rraxis {% .2f % .2f % .2f}\n", a.v[0], a.v[1], a.v[2]);
00501       msgInfo << buf << sendmsg;
00502     }
00503   }
00504 
00505   // Abuse measure_inertia() to update the center of mass
00506   float itensor[4][4];
00507   int ret = measure_inertia(sel, mlist, coor, rcom, inertiaaxes,
00508                             itensor, inertiaeigenval);
00509   if (ret < 0) msgErr << "measure inertia failed with code " << ret << sendmsg;
00510   
00511   //printf("rcom={%.2f %.2f %.2f}\n", rcom[0], rcom[1], rcom[2]);
00512 
00513   // Assign the bondsum vectors
00514   assign_bondvectors();
00515 
00516 
00517   for (i=0; i<2; i++) {
00518 
00519   // During idealization the coordinates are wrapped and
00520   // averaged.
00521   idealize_coordinates();
00522 
00523   // Use improved coordinates
00524   memcpy(coor, idealcoor, 3*sel->selected*sizeof(float));
00525   }
00526 }
00527 
00528 
00531 int Symmetry::guess(float mysigma) {
00532   if (!sel)               return MEASURE_ERR_NOSEL;
00533   if (sel->selected == 0) return MEASURE_ERR_NOATOMS;
00534 
00535   if (sel->selected == 1) {
00536     pointgroup = POINTGROUP_KH;
00537     elementsummarystring = new char[1];
00538     elementsummarystring[0] = '\0';
00539     uniqueatoms[0] = 1;
00540     memcpy(idealcoor, coor, 3*sel->selected*sizeof(float));
00541 
00542     return MEASURE_NOERR;
00543   }
00544    
00545 
00546   float maxsigma = mysigma;
00547 
00548   wkf_timerhandle timer = wkf_timer_create();
00549   wkf_timer_start(timer);
00550 
00551   // We don't know beforehand what is the ideal (sigma) tolerance
00552   // value to use in oder to find the right point group. If we
00553   // choose it too low we might find only a subgroup of the real 
00554   // pointgroup (e.g. Cs instead of D2h). However, if we choose
00555   // it too high we will find impossible combinations of symmetry
00556   // elements caused by false positives.
00557   // Thus we slowly ramp up the tolerance in steps of 0.05 until
00558   // we reach the user-specified maximum. This has some advantages:
00559   // After each step where we find a new non-C1 symmetry we'll
00560   // idealize the coordinatesand get a more symmetric structure
00561   // even if we just found a subgroup of the real one.
00562   // At some point we will find the higher symmetry point group.
00563   // But how do we know when to stop?
00564   // After each step the sanity of the idealized coordinates is
00565   // checked. Wrong point groups (i.e. wrong symmetry elements)
00566   // will screw up the idealized coordinates and bondsums which
00567   // can be easily detected. When this occcurs our search has
00568   // finished and we go back to the last sane symmetry.
00569   // The step where this symmetry occured the first time is
00570   // an indicator of the quality of the guess. The earlier we
00571   // found the right point group the less we had to bend the
00572   // coodinates to make them symmetric and the more trustworthy
00573   // is the guess.
00574   int beststep = -1;
00575   Best best;
00576   best.pointgroup = POINTGROUP_UNKNOWN;
00577   best.pointgrouporder = 0;
00578   best.idealcoor = NULL;
00579   int step, nstep;
00580   float stepsize = 0.05f;
00581   nstep = int(0.5f+(float)maxsigma/stepsize);
00582 
00583   for (step=0; step<nstep; step++) {
00584     sigma = (1+step)*stepsize;
00585     if (verbose>1) {
00586       msgInfo << sendmsg;
00587       msgInfo << "  STEP " << step << "  sigma=" << sigma << sendmsg
00588               << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << sendmsg << sendmsg;
00589     }
00590 
00591 
00592     // Self consistent iteration of symmetry element search
00593     if (!iterate_element_search()) continue;
00594 
00595     wkf_timer_stop(timer);
00596     if (verbose>0) {
00597       char buf[256];
00598       sprintf(buf, "Guess %i completed in %f seconds, RMSD=%.3f", step, wkf_timer_time(timer), rmsd);
00599       msgInfo << buf << sendmsg;
00600     }
00601 
00602 
00603     // If the idealized coordinates are not sane, i.e.
00604     // there are atoms closer than MINATOMDIST we stop.
00605     // Coordinates typically collapse during idealization
00606     // when false symmetry elements were found.
00607     // We can stop the search because with a higher tolerance
00608     // in the next step we are going to find the same bad
00609     // symmetry element again (and maybe even more).
00610     if (!ideal_coordinate_sanity()) {
00611       if (verbose>1) {
00612         msgInfo << "INSANE IDEAL COORDINATES" << sendmsg;
00613       }
00614       break;
00615     }
00616 
00617     if (verbose>1) {
00618       print_statistics();
00619     }
00620 
00621     // Determine the point group from symmetry elements
00622     determine_pointgroup();
00623 
00624     char pgname[8];
00625     get_pointgroup(pgname, NULL);
00626     compare_element_summary(pgname);
00627 
00628     if (verbose>1) {
00629       msgInfo << "Point group:     " << pgname << sendmsg << sendmsg;
00630       print_element_summary(pgname);
00631       //msgInfo << "Level:           " << pointgroup_rank(pointgroup, pointgrouporder)
00632       //        << sendmsg;
00633     }
00634 
00635     // Try to decide wether the current step yielded a
00636     // better guess:
00637     // Whenever we detect a new pointgroup we have to
00638     // compare the ranks in order to determine which one
00639     // has higher symmetry. Often we find a subgroup
00640     // of the actual point group first. When the tolerance 
00641     // has increased we find the real point group which
00642     // will have a higher rank than the subgroup.
00643     if (pointgroup!=best.pointgroup &&
00644         !strlen(additionalelementstring) &&
00645         !strlen(missingelementstring) &&
00646         pointgroup_rank(pointgroup, pointgrouporder) >
00647         pointgroup_rank(best.pointgroup, best.pointgrouporder)) {
00648       beststep = step;
00649       best.pointgroup = pointgroup;
00650       best.pointgrouporder = pointgrouporder;
00651       if (!best.idealcoor) best.idealcoor = new float[3*sel->selected];
00652       memcpy(best.idealcoor, idealcoor, 3*sel->selected*sizeof(float));
00653       best.linear = linear;
00654       best.planar = planar;
00655       best.inversion = inversion;
00656       best.sphericaltop = sphericaltop;
00657     }
00658   }
00659 
00660   // Reset sigma and idealized coordinates to the values of 
00661   // the best step and recompute the symmetry elements.
00662   sigma = (1+beststep)*stepsize;
00663   if (best.idealcoor) {
00664     memcpy(coor, best.idealcoor, 3*sel->selected*sizeof(float));
00665     delete [] best.idealcoor;
00666   }
00667   iterate_element_search();
00668 
00669   // Determine the point group from symmetry elements
00670   determine_pointgroup();
00671 
00672   if (verbose>0) {
00673     msgInfo << sendmsg
00674             << "RESULT:" << sendmsg
00675             << "=======" << sendmsg << sendmsg;
00676 
00677     msgInfo << "Needed tol = " << sigma << " to detect symmetry."
00678             << sendmsg;
00679 
00680     print_statistics();
00681     char pgname[8];
00682     get_pointgroup(pgname, NULL);
00683     compare_element_summary(pgname);
00684     msgInfo << "Point group:     " << pgname << sendmsg << sendmsg;
00685     print_element_summary(pgname);
00686   }
00687 
00688 
00689   // Sort planes in the order horizontal, dihedral, vertical, rest
00690   sort_planes();
00691 
00692   // Determine the unique coordinates of the molecule
00693   unique_coordinates();
00694 
00695   // Try to get a transformation matrix that orients the
00696   // molecule according to the GAMESS standard orientation.
00697   orient_molecule();
00698 
00699 
00700   wkf_timer_destroy(timer);
00701 
00702   return MEASURE_NOERR;
00703 }
00704 
00705 
00706 // Self consistent iteration of symmetry element search
00707 // ----------------------------------------------------
00708 // If we would base our point group guess on elements that were
00709 // found in a single pass, then the biggest problem is to decide
00710 // which symmetry elements to keep and which to discard, based
00711 // on their overlap score.
00712 // While a point group guess, relying only on certain key
00713 // features, can be quite good, the number of found elements would
00714 // often be wrong. In case additional elements were found this
00715 // could totally screw up the idealized coordinates.
00716 // To avoid these problems we are iterate the element guess.
00717 // In each iteration we only keep the elements with a very good
00718 // score. This ensures that we don't get false positives. We then
00719 // idealize the symmetry elements which means we correct the
00720 // angles between then (i.e. an 87deg angle between two planes
00721 // becomes 90deg). Now all atoms will be wrapped around each
00722 // symmetry element and their positions averaged with their images
00723 // which gives us an updated set of idealized coordinates that will
00724 // be "more symmetric" than the coordinates from the previous
00725 // generation. In the next pass we use these improved coordinates
00726 // for guessing the elements and will probably find a few more this
00727 // time. This is repeated until the number of found symmetry elements
00728 // converges.
00729 int Symmetry::iterate_element_search() {
00730   int iteration;
00731   char oldelementsummarystring[2 + 10*(3+MAXORDERCN+2*MAXORDERCN)];
00732   oldelementsummarystring[0] = '\0';
00733   float oldrmsd = 999999.9f;
00734   int converged = 0;
00735 
00736   for (iteration=0; iteration<20; iteration++) {
00737     // In each iteration we start with a clean slate
00738     pointgroup = POINTGROUP_UNKNOWN;
00739     inversion = 0;
00740     linear = 0;
00741     planar = 0;
00742     sphericaltop = 0;
00743     symmetrictop = 0;
00744     axes.clear();
00745     planes.clear();
00746     rotreflections.clear();
00747     bonds.clear();
00748     numdihedralplanes = 0;
00749     numverticalplanes = 0;
00750     horizontalplane   = -1;
00751 
00752     if (verbose>2) {
00753       msgInfo << sendmsg 
00754               << "###################" << sendmsg;
00755       msgInfo << "Iteration " << iteration << sendmsg;
00756       msgInfo << "###################" << sendmsg << sendmsg;
00757     }
00758 
00759     // Reassign the bondsum vectors
00760     assign_bondvectors();
00761 
00762     // Determine primary axes of inertia
00763     float itensor[4][4];
00764     int ret = measure_inertia(sel, mlist, coor, rcom, inertiaaxes,
00765                               itensor, inertiaeigenval);
00766     if (ret < 0) msgErr << "measure inertia failed with code " << ret << sendmsg;
00767 
00768 
00769     if (verbose>2) {
00770       char buf[256];
00771       sprintf(buf, "eigenvalues: %.2f  %.2f  %.2f", inertiaeigenval[0], inertiaeigenval[1], inertiaeigenval[2]);
00772       msgInfo << buf << sendmsg;
00773     }
00774 
00775 
00776     // Find all rotary axes, mirror planes and rotary reflection axes
00777     find_symmetry_elements();
00778 
00779     if (verbose>2) {
00780       if (linear)
00781         msgInfo << "Linear molecule" << sendmsg;
00782       if (planar)
00783         msgInfo << "Planar molecule" << sendmsg;
00784       if (sphericaltop)
00785         msgInfo << "Molecule is a spherical top." << sendmsg;
00786       if (symmetrictop)
00787         msgInfo << "Molecule is a symmetric top." << sendmsg;
00788 
00789       int i, numuniqueprimary = 0;
00790       for (i=0; i<3; i++) {
00791         if (uniqueprimary[i]) numuniqueprimary++;
00792       }
00793       msgInfo << "Number of unique primary axes = " << numuniqueprimary << sendmsg;
00794 
00795     }
00796 
00797     // Now we have a set of symmetry elements but since they were
00798     // constructed from the possibly inacccurate molecular coordinates
00799     // their relationship is not ideal. For example the angle between
00800     // mirror planes parallel to a 3-fold rotation axis will only
00801     // approximately be 120 degree. We have to idealize the symmetry
00802     // elements so that we have a basis to idealize the coordinates
00803     // and determine the symmetry unique atoms.
00804     idealize_elements();
00805 
00806     // Generate symmetricized coordinates by wrapping atoms around
00807     // the symmetry elements and average with these images with the 
00808     // original positions.
00809     idealize_coordinates();
00810 
00811     // Should check the sanity of the idealized coordinates
00812     // here. I.e. check if there are atoms closer than, 0.6 A
00813     // and check if the bondsums match with the original geometry.
00814 
00815     // Compute RMSD between original and idealized coordinates
00816     rmsd = ideal_coordinate_rmsd();
00817 
00818     // Update the symmetry element statistics
00819     build_element_summary();
00820 
00821     // Create a string representation of the summary
00822     // Example: (inv) 2*(sigma) (Cinf) (C2)
00823     build_element_summary_string(elementsummary, elementsummarystring);
00824 
00825     // Iterations have converged when the rmsd difference between this and
00826     // the last structure is below a threshold and the symmetry elements
00827     // haven't changed
00828     converged = 0;    
00829     if (fabs(rmsd-oldrmsd)<0.01 && 
00830         !strcmp(elementsummarystring, oldelementsummarystring)) {
00831       if (verbose>1) {
00832         msgInfo << "Symmetry search converged after " << iteration+1 
00833                 << " iterations" << sendmsg;
00834       }
00835       converged = 1;
00836     }
00837     
00838 
00839     if (verbose>3 && (converged || verbose>2)) {
00840       print_statistics();
00841     }
00842     if (converged) break;
00843 
00844     // Use improved coordinates for the next pass
00845     memcpy(coor, idealcoor, 3*sel->selected*sizeof(float));
00846 
00847     oldrmsd = rmsd;
00848     strcpy(oldelementsummarystring, elementsummarystring);
00849   }
00850 
00851   if (!converged) return 0;
00852   return 1;
00853 }
00854 
00855 
00856 // Find all mirror planes, rotary axes, rotary reflections.
00857 void Symmetry::find_symmetry_elements() {
00858 
00859   // ----- Planes -----------------
00860 
00861   // Check if the unique primary axes of inertia correspond to rotary axes
00862   find_elements_from_inertia();
00863 
00864   // Find all mirror planes
00865   find_planes();
00866 
00867   // Remove planes with too bad overlap
00868   purge_planes(0.5);
00869 
00870   // Are there horizontal/vertical/dihedral mirror planes?
00871   classify_planes();
00872 
00873   // Check for an inversion center
00874   check_add_inversion();
00875     
00876 
00877   // ------ Axes ------------------
00878 
00879   // Find all rotary axes that are intersections of planes.
00880   find_axes_from_plane_intersections();
00881 
00882   // Determine the order of each axis
00883   assign_axis_orders();
00884 
00885   // Some C2 axes cannot be found from principle axes of inertia
00886   // or through plane intersection. We must get these from the
00887   // atomic coordinates.
00888   find_C2axes();
00889 
00890   assign_prelimaxis_orders(2);
00891 
00892   if (!axes.num() && !planes.num()) {
00893     int j;
00894     for (j=3; j<=8; j++) {
00895       if (isprime(j) && !axes.num()) {
00896         find_axes(j);
00897         assign_prelimaxis_orders(j);
00898       }
00899     }
00900   }
00901 
00902   // Sort axes by decreasing order
00903   sort_axes();
00904 
00905 
00906   // ----- Rotary reflections -----
00907 
00908   // Since all rotary reflections are collinear with a rotation axis
00909   // we can simply go through the list of axes and check if which
00910   // axes correspond to improper rotations.
00911   int i;
00912   for (i=0; i<numaxes(); i++) {
00913     check_add_rotary_reflection(axes[i].v, 2*axes[i].order);
00914   }
00915 
00916   // Normalize the summed up rotary reflection vectors
00917   maxrotreflorder = 0;
00918   for (i=0; i<numrotreflect(); i++) {
00919     vec_normalize(rotreflections[i].v);
00920 
00921     if (rotreflections[i].order>maxrotreflorder)
00922       maxrotreflorder = rotreflections[i].order;     
00923   }
00924 
00925 
00926   // Remove axes with too bad overlap
00927   purge_axes(0.7f);
00928   purge_rotreflections(0.75f);
00929 
00930   // Must classify planes again because of dihedral planes depending
00931   // on the new axes:
00932   classify_planes();
00933 
00934   // Classify perpendicular axes
00935   classify_axes();
00936 }
00937 
00938 
00939 // Determine principle axes of inertia, check them for uniqueness
00940 // by comparing the according eigenvalues. Then, see if the unique
00941 // axes correspond to rotary axes or rotary reflections and, in
00942 // case, add axes to the list and assing the order.
00943 // We also check if there are horizontal mirror planes for the
00944 // principal axes.
00945 void Symmetry::find_elements_from_inertia() {
00946   int i;
00947   int numuniqueprimary = 0;
00948   memset(uniqueprimary, 0, 3*sizeof(int));
00949 
00950   // Normalize eigenvalues
00951   float eigenval[3];
00952   float e0 = inertiaeigenval[0];
00953   for (i=0; i<3; i++) {
00954     eigenval[i] = inertiaeigenval[i]/e0;
00955   }
00956 
00957   // Check if the molecule is linear
00958   if (fabs(eigenval[0]-eigenval[1])<0.05 && eigenval[2]<0.05) {
00959     linear = 1;
00960   }
00961 
00962   // Get provisional info about the rotational order of the inertia axes
00963   float overlap[3];
00964   int order[3], primaryCn[3];
00965   for (i=0; i<3; i++) {
00966     order[i] = axis_order(inertiaaxes[i], overlap+i);
00967     //printf("primary axis of inertia %i: order=%i overlap=%.2f\n", i, order[i], overlap[i]);
00968     if ((order[i]>1 && overlap[i]>1.5*OVERLAPCUTOFF) ||
00969         (linear && eigenval[i]<0.05)) {
00970       primaryCn[i] = 1;
00971     } else {
00972       primaryCn[i] = 0;
00973     }
00974   }
00975 
00976   float tol = 0.25f*sigma + 1.0f/(powf(float(sel->selected+1), 1.5f)); // empirical;
00977 
00978   // If all three moments of inertia are the same, the molecule is a spherical top,
00979   // i.e. the possible point groups are T, Td, Th, O, Oh, I, Ih.
00980   if (fabs(eigenval[0]-eigenval[1])<tol &&
00981       fabs(eigenval[1]-eigenval[2])<tol) {
00982 
00983     sphericaltop = 1;
00984 
00985   } else {
00986     // If two moments of inertia are the same, the molecule is a
00987     // symmetric top, i.e. the possible points groups are C1, Cn,
00988     // Cnv, Cnh, Sn, Dn, Dnh, Dnd.
00989     if (fabs(eigenval[0]-eigenval[1])<tol ||
00990         fabs(eigenval[1]-eigenval[2])<tol ||
00991         fabs(eigenval[0]-eigenval[2])<tol) {
00992 
00993       // Determine which is the unique primary axis. Also make sure
00994       // that this axis corresponds to a Cn rotation, otherwise we
00995       // have a false positive, i.e. two eigenvalues are identical
00996       // or similar only by incident.
00997       if (fabs(eigenval[1]-eigenval[2])<tol && primaryCn[0]) {
00998         uniqueprimary[0] = 1;
00999         numuniqueprimary++;
01000       }
01001       if (fabs(eigenval[0]-eigenval[2])<tol && primaryCn[1]) {
01002         uniqueprimary[1] = 1;
01003         numuniqueprimary++;
01004       }
01005       if (fabs(eigenval[0]-eigenval[1])<tol && primaryCn[2]) {
01006         uniqueprimary[2] = 1;
01007         numuniqueprimary++;
01008       }
01009 
01010       if (numuniqueprimary==1) {
01011         symmetrictop = 1;
01012       }
01013       else {
01014         uniqueprimary[0] = 1;
01015         uniqueprimary[1] = 1;
01016         uniqueprimary[2] = 1;
01017         numuniqueprimary = 3;   
01018       }
01019     } 
01020     else {
01021       uniqueprimary[0] = 1;
01022       uniqueprimary[1] = 1;
01023       uniqueprimary[2] = 1;
01024       numuniqueprimary = 3;
01025     }
01026   }
01027 
01028 
01029   for (i=0; i<3; i++) {
01030     // If the molecule is planar (but not linear) with the current
01031     // primary axis of inertia corresaponding to the plane normal
01032     // then we want to add that plane to the list.
01033     int planarmol = is_planar(inertiaaxes[i]);
01034     if (planarmol && !linear) {
01035       if (verbose>3) {
01036         msgInfo << "Planar mol: primary axes " << i << " defines plane" << sendmsg;
01037       }
01038       check_add_plane(inertiaaxes[i]);
01039       planar = 1;
01040     }
01041 
01042 
01043     if (!uniqueprimary[i]) continue;
01044 
01045     if (linear) {
01046       // Cinfv and Dinfh symmetry:
01047       // Use the unique primary axis of inertia as Cinf
01048       // For Dinfh an arbitrary C2 axis perpendicular to Cinf will be
01049       // automatically generated later by plane intersection.
01050       Axis a;
01051       vec_copy(a.v, inertiaaxes[i]);
01052       a.order = INFINITE_ORDER;
01053       float overlap1 = score_axis(a.v, 2);
01054       float overlap2 = score_axis(a.v, 4);
01055       a.overlap = 0.5f*(overlap1+overlap2);
01056       a.weight = sel->num_atoms/2;
01057       a.type = 0;
01058       axes.append(a);
01059 
01060       // We have to add an arbitrary vertical plane.
01061       Plane p;
01062       vec_copy(p.v, inertiaaxes[0]);
01063       p.overlap = eigenval[0]*eigenval[1] - eigenval[2];
01064       p.weight = sel->num_atoms/2;
01065       p.type = 0;
01066       planes.append(p);
01067 
01068     } else {
01069       //printf("primary axis of inertia %i: order=%i overlap=%.2f\n", i, order[i], overlap[i]);
01070 
01071       if (order[i] > 1 && overlap[i]>OVERLAPCUTOFF) {
01072         //printf("unique primary axes = %i planarmol = %i\n", i, planarmol);
01073         Axis a;
01074         vec_copy(a.v, inertiaaxes[i]);
01075         a.order = order[i];
01076         a.overlap = overlap[i];
01077         a.weight = 1;
01078         a.type = 0;
01079         axes.append(a);
01080       }
01081 
01082       // Add a possible horizontal mirror plane:
01083       // In case the molecule is planar the general plane finding algorithm
01084       // won't recognize the horizontal plane because identical atom transformations
01085       // are skipped. Thus we boost the weight here and provide it to 
01086       // check_add_plane().
01087       int weight = 1;
01088       if (planarmol) weight = sel->num_atoms/2;
01089 
01090       check_add_plane(inertiaaxes[i], weight);
01091     }
01092   }
01093 }
01094 
01095 
01096 // Find atoms with same distance to the center of mass and check if the 
01097 // plane that is orthogonal to their connecting vector is a mirror plane.
01098 void Symmetry::find_planes() {
01099   int i,j;
01100   float posA[3], posB[3];
01101 
01102   // Loop over all atoms
01103   for (i=0; i<sel->selected; i++) {
01104 
01105     // If we have more than maxnatoms atoms in the selection then pick 
01106     // only approximately maxnatoms random atoms  for the comparision.
01107     if (sel->selected>maxnatoms && vmd_random()>maxnatoms/sel->selected)
01108       continue;
01109 
01110     vec_sub(posA, coor+3*i, rcom);
01111 
01112     float rA = sqrtf(posA[0]*posA[0] + posA[1]*posA[1] + posA[2]*posA[2]); 
01113 
01114     // Exclude atoms that are close to COM:
01115     // Closer than sigma is too close in any case, otherwise exclude
01116     // more the larger the molecule is.
01117     if (rA < sigma || rA < sel->num_atoms/7.0*sigma) continue;
01118 
01119     for (j=0; j<i; j++) {
01120       vec_sub(posB, coor+3*j, rcom);
01121 
01122       float rB = sqrtf(posB[0]*posB[0] + posB[1]*posB[1] + posB[2]*posB[2]);
01123       
01124       if (fabs(rA-rB) > sigma) continue;
01125 
01126       // consider only pairs with identical atom types
01127       if (atomtype[i]==atomtype[j]) {
01128 
01129         // If the atoms are too close to the plane, the error gets
01130         // too big so we skip.
01131         float dist = distance(posA, posB);
01132         if (dist<0.25) continue;
01133 
01134         // We have found a hypothetical rotation axis or mirror plane
01135         
01136         // Subtracting the two position vectors yields a vector that
01137         // defines the normal of a plane that bisects the angle between
01138         // the two atoms, i.e. the plane is a potential mirror plane.
01139         float normal[3];
01140         vec_sub(normal, posA, posB);
01141         vec_normalize(normal);
01142 
01143         // Check plane and possibly add it to the list;
01144         check_add_plane(normal);
01145       }
01146     }
01147   }
01148 
01149   // Normalize the summed up normal vectors
01150   for (i=0; i<numplanes(); i++) {
01151     vec_normalize(planes[i].v);
01152     //printf("plane[%i]: weight=%3i, overlap=%.2f\n", i, planes[i].weight, planes[i].overlap);
01153   }
01154 }
01155 
01156 
01157 // Find C2 rotation axes from the vectors bisecting the angle between
01158 // two atoms and the center of mass.
01159 void Symmetry::find_C2axes() {
01160   int i,j;
01161   float posA[3], posB[3];
01162   float rA, rB;
01163   // Loop over all atoms
01164   for (i=0; i<sel->selected; i++) {
01165     // If we have more than maxnatoms atoms in the selection then pick 
01166     // only approximately maxnatoms random atoms  for the comparision.
01167     if (sel->selected>maxnatoms && vmd_random()>maxnatoms/sel->selected)
01168       continue;
01169 
01170     vec_sub(posA, coor+3*i, rcom);
01171 
01172     rA = sqrtf(posA[0]*posA[0] + posA[1]*posA[1] + posA[2]*posA[2]); 
01173 
01174     // Exclude atoms that are close to COM:
01175     // Closer than sigma is too close in any case, otherwise exclude
01176     // more the larger the molecule is.
01177     if (rA < sigma || rA < sel->num_atoms/7.0*sigma) continue;
01178 
01179     for (j=i+1; j<sel->selected; j++) {
01180       // If we have more than maxnatoms atoms in the selection then pick 
01181       // only approximately maxnatoms random atoms  for the comparision.
01182       if (sel->selected>maxnatoms && vmd_random()>maxnatoms/sel->selected) continue;
01183 
01184       vec_sub(posB, coor+3*j, rcom);
01185 
01186       rB = sqrtf(posB[0]*posB[0] + posB[1]*posB[1] + posB[2]*posB[2]);
01187       
01188       if (fabs(rA-rB) > sigma) continue;
01189 
01190       // consider only pairs with identical atom types
01191       if (atomtype[i]==atomtype[j]) {
01192         // We have found a hypothetical rotation axis or mirror plane
01193         float alpha = angle(posA, posB);
01194         
01195         // See if the vector bisecting the angle between the two atoms
01196         // and the center of mass corresponds to a C2 axis. We must
01197         // exclude alpha==180 because in this case the bisection is
01198         // not uniquely defined.
01199         if (fabs(fabs(alpha)-180) > 10) {
01200           float testaxis[3];
01201           vec_add(testaxis, posA, posB);
01202           vec_normalize(testaxis);
01203 
01204           // Check axis and possibly add it to the list;
01205           if (!planes.num() || numverticalplanes) {
01206             //printf("Checking C2 axis\n"); 
01207             check_add_axis(testaxis, 2);
01208           }
01209         }
01210       }
01211     }
01212   }
01213 
01214   // Normalize the summed up axis vectors
01215   for (i=0; i<numaxes(); i++) {
01216     vec_normalize(axes[i].v);
01217   }
01218 }
01219 
01220 // Computes the factorial of n
01221 static int fac(int n) {
01222   if (n==0) return 1;
01223   int i, x=1;
01224   for (i=1; i<=n; i++) x*=i;
01225   return x;
01226 }
01227 
01228 
01229 // Generate all n!/(k!(n-k)!) combinations of k different
01230 // elements drawn from a total of n elements.
01231 class Combinations {
01232 public:
01233   int *combolist;
01234   int *combo;
01235   int num;
01236   int n;
01237   int k;
01238 
01239   Combinations(int N, int K);
01240   ~Combinations();
01241 
01242   // Create the combinations (i.e. populate combolist)
01243   void create() { recurse(0, -1); }
01244 
01245   void recurse(int begin, int level);
01246   void print();
01247 
01248   // Get pointer to the i-th combination
01249   int* get(int i) { return &combolist[i*k]; }
01250 };
01251 
01252 Combinations::Combinations(int N, int K) : n(N), k(K) {
01253   if (n>10) n = 10; // prevent overflow of float by fac(n)
01254   combo = new int[k];
01255   combolist = new int[k*fac(n)/(fac(k)*fac(n-k))];
01256   num = 0;
01257 }
01258 
01259 Combinations::~Combinations() {
01260   delete [] combo;
01261   delete [] combolist;
01262 }
01263 
01264 // Recursive function to generate combinations
01265 void Combinations::recurse(int begin, int level) {
01266   int i;
01267   level++;
01268   if (level>=k) {
01269     for (i=0; i<k; i++) {
01270       combolist[num*k+i] = combo[i];
01271     }
01272     num++;
01273     return;
01274   }
01275   
01276   for (i=begin; i<n; i++) {
01277     combo[level] = i;
01278     recurse(i+1, level);
01279   }  
01280 }
01281 
01282 // Print the combinations
01283 void Combinations::print() {
01284   int i, j;
01285   for (i=0; i<fac(n)/(fac(k)*fac(n-k)); i++) {
01286     printf("combo %d/%d {", i, num);
01287     for (j=0; j<k; j++) {
01288       printf(" %d", combolist[i*k+j]);
01289     }
01290     printf("}\n");
01291   }
01292 
01293 }
01294 
01295 
01296 // Find Cn rotation axes from the atoms that lie in the
01297 // same plane. The plane normal defines the hypothetical
01298 // axis
01299 void Symmetry::find_axes(int order) {
01300   int i,j;
01301   float posA[3], posB[3];
01302   float rA, rB;
01303   int *atomtuple = new int[sel->selected];
01304 
01305   // Loop over all atoms
01306   for (i=0; i<sel->selected; i++) {
01307     // If we have more than maxnatoms atoms in the selection then pick 
01308     // only approximately maxnatoms random atoms  for the comparision.
01309     if (sel->selected>maxnatoms && vmd_random()>maxnatoms/sel->selected)
01310       continue;
01311 
01312     vec_sub(posA, coor+3*i, rcom);
01313 
01314     rA = sqrtf(posA[0]*posA[0] + posA[1]*posA[1] + posA[2]*posA[2]); 
01315 
01316     // Exclude atoms that are close to COM:
01317     // Closer than sigma is too close in any case, otherwise exclude
01318     // more the larger the molecule is.
01319     if (rA < sigma || rA < sel->num_atoms/7.0*sigma) continue;
01320 
01321     atomtuple[0] = i;
01322     int ntup = 1;
01323 
01324     for (j=i+1; j<sel->selected; j++) {
01325       // If we have more than maxnatoms atoms in the selection
01326       // then pick only approximately maxnatoms random atoms
01327       // for the comparison.
01328       if (sel->selected>maxnatoms &&
01329           vmd_random()>maxnatoms/sel->selected) {
01330         continue;
01331       }
01332       
01333       // Consider only pairs with identical atom types
01334       if (atomtype[j]!=atomtype[i]) continue;
01335       
01336       vec_sub(posB, coor+3*j, rcom);
01337       
01338       rB = sqrtf(posB[0]*posB[0] + posB[1]*posB[1] + posB[2]*posB[2]);
01339       
01340       // Atoms should have approximately same distance from COM
01341       if (fabs(rA-rB) > sigma) continue;
01342       
01343       atomtuple[ntup++] = j;
01344 
01345       // We don't consider more then 10 equivalent atoms
01346       // to save resources.
01347       if (ntup>=10) break;
01348     }
01349     if (ntup<order) continue;
01350 
01351     //printf("equiv. atoms: ");
01352     //for (j=0; j<ntup; j++) { printf("%d ",atomtuple[j]); }
01353     //printf("\n");
01354 
01355     // Generate all combinations of tuples with order different
01356     // elements drawn from a total of ntup elements.
01357     Combinations combo(ntup, order);
01358     combo.create();
01359 
01360 
01361     int m;
01362     for (j=0; j<combo.num; j++) {
01363       float testaxis[3], pos[3], pos2[3], cross[3], normal[3];
01364       vec_zero(testaxis);
01365       vec_zero(normal);
01366       //printf("combi %d: {", j);
01367 
01368       // Test wether all atoms in the tuple lie aproximately
01369       // within one plane
01370       int inplane = 1, anyinplane = 0;
01371       for (m=0; m<order; m++) {
01372         int atom = atomtuple[combo.get(j)[m]];
01373         //printf(" %d", atom); //combo.get(j)[m]);
01374         vec_sub(pos, coor+3*atom, rcom);
01375         vec_add(testaxis, testaxis, pos);
01376 
01377         // Find a second atom from the tuple that encloses
01378         // an angle of >45 deg with the first one.
01379         // If we don't find one then we ignore the in-plane
01380         // testing for the current atom.
01381         int n, found = 0;
01382         for (n=0; n<order; n++) {
01383           if (n==m) continue;
01384           int atom2 = atomtuple[combo.get(j)[m]];
01385           vec_sub(pos2, coor+3*atom2, rcom);
01386           if (angle(pos, pos2)>45.f) {
01387             found = 1;
01388             break;
01389           }
01390         }
01391         if (!found) continue;
01392         
01393         cross_prod(cross, pos2, pos);
01394         
01395         // Check if the cross product of the two atom positions
01396         // is collinear with the plane normal (which was summed
01397         // up from the cross products of the previous pairs).
01398         if (collinear(normal, cross, collintol)) {
01399           vec_add(normal, normal, cross);
01400           anyinplane = 1;
01401         } else {
01402           // This atom is not in plane with the others
01403           inplane = 0;
01404           break;
01405         }
01406       }
01407 
01408       //printf("}\n");
01409       
01410       // If the atoms of the current tuple aren't in one
01411       // plane then the positions cannot be used to define
01412       // a Cn axis and we skip it.
01413       if (!inplane || !anyinplane) continue;
01414 
01415       vec_normalize(normal);
01416 
01417       // Check axis and possibly add it to the list;
01418       printf("Checking C%d axis\n", order); 
01419       check_add_axis(normal, order);
01420     }
01421 
01422   }
01423    
01424   delete [] atomtuple;
01425 
01426   // Normalize the summed up axis vectors
01427   for (i=0; i<numaxes(); i++) {
01428     vec_normalize(axes[i].v);
01429   }
01430 }
01431 
01432 
01433 // Get rotation axes.
01434 // Each intersection of two mirror planes is itself a rotation
01435 // axis. Computing the intersection is easy in this case because
01436 // we know that any two mirror planes go through the center of
01437 // mass. The direction of the intersection line is just the 
01438 // cross product of the two plane normals.
01439 
01440 void Symmetry::find_axes_from_plane_intersections() {
01441   // If there aren't at least 2 planes we won't find any axes.
01442   if (numplanes()<2) return;
01443 
01444   int i,j;
01445 
01446   for (i=0; i<numplanes(); i++) {
01447     for (j=i+1; j<numplanes(); j++) {
01448       float newaxis[3];
01449       cross_prod(newaxis, plane(i), plane(j));
01450 
01451       vec_normalize(newaxis);
01452 
01453       // Ignore intersections of parallel planes
01454       if (norm(newaxis)<0.05) continue;
01455 
01456       // Loop over already existing axes and check if the new axis is
01457       // collinear with one of them. In this case we add the two axes
01458       // in order to obtain an average (after normalizing at the end).
01459       int k;
01460       bool found = 0;
01461       for (k=0; k<numaxes(); k++) {
01462         float avgaxis[3];
01463         vec_copy(avgaxis, axis(k));
01464         vec_normalize(avgaxis);
01465 
01466         float dot = dot_prod(avgaxis, newaxis);
01467         //printf("dot=% .4f fabs(dot)=%.4f, 1-collintol=%.2f\n", dot, fabs(dot), collintol);
01468         if (fabs(dot) > collintol) {
01469           // We are summing up the collinear axes to get the average
01470           // of the equivalent axes.
01471           if (dot>0) {
01472             vec_incr(axis(k), newaxis);         // axes[k] += normal
01473           } else {
01474             vec_sub(axis(k), axis(k), newaxis); // axes[k] -= newaxis
01475           }
01476           axes[k].weight++;
01477           found = 1;
01478           break;
01479         }
01480       }
01481 
01482       if (!found) {
01483         // We found no existing collinear axis, so add a new one to the list.
01484         Axis a;
01485         vec_copy(a.v, newaxis);
01486         a.type    = 0;
01487         a.order   = 0;
01488         a.overlap = 0.0;
01489         if (planes[i].weight<planes[j].weight)
01490           a.weight = planes[i].weight;
01491         else
01492           a.weight = planes[j].weight;
01493         axes.append(a);
01494       }
01495     }
01496   }
01497 
01498   // Normalize the summed up axis vectors
01499   for (i=0; i<numaxes(); i++) {
01500     vec_normalize(axis(i));
01501   }
01502 }
01503 
01504 
01505 // Determine which of the existing mirror planes are horizontal,
01506 // vertical, or dihedral planes with respect to the first axis.
01507 // The axes must have been sorted already so that the axes with
01508 // the highest order (primary axis) is the first one.
01509 // A horizontal plane is perpendicular to the primary axis.
01510 // A vertical plane includes the primary axis.
01511 // A dihedral plane is vertical to the corresponding axis and 
01512 // bisects the angle formed by a pair of C2 axes.
01513 void Symmetry::classify_planes() {
01514   if (!numplanes()) return;
01515   if (!numaxes())   return;
01516 
01517   numdihedralplanes = 0;
01518   numverticalplanes = 0;
01519   horizontalplane = -1;
01520 
01521   int i;
01522 
01523   // Is there a plane perpendicular to the highest axis?
01524   for (i=0; i<numplanes(); i++) {
01525     if (collinear(axis(0), plane(i), collintol)) {
01526       horizontalplane = i;
01527       planes[i].type = HORIZONTALPLANE;
01528       break;
01529     }
01530   }
01531 
01532 
01533   for (i=0; i<numplanes(); i++) {
01534     if (!orthogonal(axis(0), plane(i), orthogtol)) continue;
01535     
01536     // The current plane is vertical to the first axis.
01537     numverticalplanes++;
01538     planes[i].type = VERTICALPLANE;
01539 
01540     // Now loop over pairs of C2 axes that are in that plane:
01541     int j, k;
01542     for (j=1; j<numaxes(); j++) {
01543       if (axes[j].order != 2) continue;
01544       if (!orthogonal(axis(j), axis(0), orthogtol)) continue;
01545       
01546       for (k=j+1; k<numaxes(); k++) {
01547         if (axes[k].order != 2) continue;
01548         if (!orthogonal(axis(k), axis(0), orthogtol)) continue;
01549         
01550         // Check if the plane bisects the pair of C2 axes
01551         float bisect[3];
01552         vec_add(bisect, axis(j), axis(k));
01553         vec_normalize(bisect);
01554         if (orthogonal(bisect, plane(i), orthogtol)) {
01555           planes[i].type = DIHEDRALPLANE;
01556           numdihedralplanes++;
01557           j=numaxes(); // stop looping axis pairs
01558           break;
01559         }
01560 
01561         vec_sub(bisect, axis(j), axis(k));
01562         vec_normalize(bisect);
01563         if (orthogonal(bisect, plane(i), orthogtol)) {
01564           planes[i].type = DIHEDRALPLANE;
01565           numdihedralplanes++;
01566           j=numaxes(); // stop looping axis pairs
01567           break;
01568         }
01569         
01570       }
01571     }
01572   }
01573 }
01574 
01575 void Symmetry::classify_axes() {
01576   if (!numaxes()) return;
01577 
01578   // If n is the higest Cn axis order, are there n C2 axes
01579   // perpendicular to Cn?
01580   int numprimary = 0;
01581   int i;
01582   for (i=0; i<numaxes(); i++) {
01583     if (axes[i].order==maxaxisorder) {
01584       axes[i].type = PRINCIPAL_AXIS;
01585       numprimary++;
01586     }
01587   }
01588   for (i=1; i<numaxes(); i++) {
01589     if (orthogonal(axis(0), axis(i), orthogtol))
01590       axes[i].type |= PERPENDICULAR_AXIS;
01591   }
01592 }
01593 
01594 // Check if center of mass is an inversion center.
01595 float Symmetry::score_inversion() {
01596   // Construct inversion matrix
01597   Matrix4 inv;
01598   inv.translate(rcom[0], rcom[1], rcom[2]);
01599   inv.scale(-1.0);  // inversion
01600   inv.translate(-rcom[0], -rcom[1], -rcom[2]);
01601 
01602   // Return score       
01603   return trans_overlap(atomtype, coor, sel->selected, &inv, 1.5f*sigma,
01604                        NOSKIP_IDENTICAL, maxnatoms);
01605 }
01606 
01607 // Check if center of mass is an inversion center
01608 // and set the inversion flag accordingly.
01609 void Symmetry::check_add_inversion() {
01610   // Verify inversion center
01611   float overlap = score_inversion();
01612 
01613   //printf("inversion overlap = %.2f\n", overlap);
01614 
01615   // We trust planes and axes more than the inversion since 
01616   // they are averages over multiple detected elements.
01617   // Hencewe make the cutoff range from 0.3 to 0.5, depending
01618   // on the number of other found symmetry elements.
01619   if (overlap>0.5-0.2/(1+(planes.num()*axes.num()))) {
01620     inversion = 1;
01621   }
01622 }
01623 
01624 // Check if the given normal represents a mirror plane.
01625 float Symmetry::score_plane(const float *normal) {
01626   // A mirror symmetry operation is an inversion + a rotation
01627   // by 180 deg about the normal of the mirror plane.
01628   // We also need to shift to origin and back.
01629   Matrix4 mirror;
01630   mirror.translate(rcom[0], rcom[1], rcom[2]);
01631   mirror.scale(-1.0);  // inversion
01632   mirror.rotate_axis(normal, float(VMD_PI));
01633   mirror.translate(-rcom[0], -rcom[1], -rcom[2]);
01634           
01635   // Return score
01636   return trans_overlap(atomtype, coor, sel->selected, &mirror, 1.5f*sigma, NOSKIP_IDENTICAL, maxnatoms);
01637 }
01638 
01639 // Append the given plane to the list.
01640 // If a approximately coplanar plane exists already then add the new
01641 // plane normal to the existing one thus getting an average direction.
01642 // The input plane normal must have a length of 1.
01643 // Note: After the list of planes is complete you want to normalize all
01644 // plane vectors.
01645 void Symmetry::check_add_plane(const float *normal, int weight) {
01646           
01647   // Verify the mirror plane
01648   float overlap = score_plane(normal);
01649 
01650   // In case of significant overlap between the original and the mirror
01651   if (overlap<OVERLAPCUTOFF) return;
01652 
01653 
01654   // We have to loop over all existing planes and see if the current
01655   // plane is coplanar with one of them. If so, we add to that existing
01656   // one. The resulting plane normal vector can later be normalized so
01657   // that we get the average plane normal of the equivalent planes.
01658   // If no existing coplanar plane is found a new plane is added to the
01659   // array.
01660   int k;
01661   bool found=0;
01662   for (k=0; k<numplanes(); k++) {
01663     float avgnormal[3];
01664     vec_copy(avgnormal, plane(k));
01665     vec_normalize(avgnormal);
01666 
01667     // If two planes are coplanar we can use their average.
01668     // The planes are parallel if cross(n1,n2)==0.
01669     // However it is faster to use 
01670     // |cross(n1,n2)|^2 = |n1|*|n2| - dot(n1,n2)^2
01671     //                  =     1     - dot(n1,n2)^2
01672     // The latter is true because the normals have length 1.
01673     // Hence |cross(n1,n2)| == 0 is equivalent to 
01674     // |dot(n1,n2)| == 1.
01675     float dot = dot_prod(avgnormal, normal);
01676     if (fabs(dot) > collintol) {
01677 
01678       // We are summing up the coplanar planes to get the average
01679       // of the equivalent planes.
01680       if (dot>0) { 
01681         vec_incr(plane(k), normal);           // planes[k] += normal
01682       } else {
01683         vec_scaled_add(plane(k), -1, normal); // planes[k] -= normal
01684       }
01685       planes[k].overlap = (planes[k].weight*planes[k].overlap + overlap)/(planes[k].weight+1);
01686       (planes[k].weight)++;
01687       found = 1;
01688       break;
01689     }
01690   }
01691   if (!found) {
01692     // We found no existing coplanar plane, so add a new one to the list.
01693     Plane p;
01694     vec_copy(p.v, normal);
01695     p.overlap = overlap;
01696     p.weight  = weight;
01697     p.type    = 0;
01698     planes.append(p);
01699   }
01700 }
01701 
01702 
01703 // Check if vector testaxis defines a rotary axis of the given order.
01704 float Symmetry::score_axis(const float *testaxis, int order) {
01705   // Construct rotation matrix.
01706   Matrix4 rot;
01707   rot.translate(rcom[0], rcom[1], rcom[2]);
01708   rot.rotate_axis(testaxis, float(VMD_TWOPI)/order);
01709   rot.translate(-rcom[0], -rcom[1], -rcom[2]);
01710         
01711   // Verify symmetry axis
01712   return trans_overlap(atomtype, coor, sel->selected, &rot, 2*sigma,
01713                        NOSKIP_IDENTICAL, maxnatoms);
01714 }
01715 
01716 
01717 // Append a given axis to the list in case it is C2.
01718 // We look for C2 axes only, because these are the only ones
01719 // that cannot always be generated by intersecting mirror planes.
01720 // Examples are the three C2 axes perpendicular to the primary C3 in 
01721 // the D3 pointgroup.
01722 void Symmetry::check_add_axis(const float *testaxis, int order) {
01723   int k;
01724   bool found = 0;
01725   for (k=0; k<numaxes(); k++) {
01726     float avgaxis[3];
01727     vec_copy(avgaxis, axis(k));
01728     vec_normalize(avgaxis);
01729     
01730     
01731     float dot = dot_prod(avgaxis, testaxis);
01732     if (fabs(dot) > collintol) {
01733       // Preliminary axes (order<0) haven't been averaged and
01734       // collapsed yet. For these cases we are summing up the
01735       // collinear axes to get the average of the equivalent
01736       // axes.
01737       if (axes[k].order==-order) {
01738         if (dot>0) { 
01739           vec_incr(axis(k), testaxis);           // axes[k] += testaxis
01740         } else {
01741           vec_scaled_add(axis(k), -1, testaxis); // axes[k] -= testaxis
01742         }
01743         axes[k].weight++;
01744       }
01745       found = 1;
01746       break;
01747     }
01748   }
01749   if (!found) {
01750     // We found no existing collinear axis, so add a new one to the list.
01751     float overlap = score_axis(testaxis, order);
01752     if (overlap>OVERLAPCUTOFF) {
01753       Axis a;
01754       vec_copy(a.v, testaxis);
01755       // We tag the order as preliminary C2 in order to distinguish it 
01756       // from C2 axes found by plane intersection. The latter have a higher
01757       // accuracy since they are based on averaged planes.
01758       a.order  = -order; //PRELIMINARY_C2;
01759       a.overlap = overlap;
01760       a.weight = 1;
01761       a.type   = 0;
01762       axes.append(a);
01763     }
01764   }
01765 }
01766 
01767 // Get the score for given rotary reflection.
01768 float Symmetry::score_rotary_reflection(const float *testaxis, int order) {
01769   // Construct transformation matrix. An n-fold rotary
01770   // reflection is a rotation by 360/n deg followed by
01771   // a reflection about the plane perpendicular to the axis.
01772   Matrix4 rot;
01773   rot.translate(rcom[0], rcom[1], rcom[2]);
01774   rot.rotate_axis(testaxis, float(VMD_TWOPI)/order);
01775   rot.scale(-1.0);  // inversion
01776   rot.rotate_axis(testaxis, float(VMD_PI));
01777   rot.translate(-rcom[0], -rcom[1], -rcom[2]);
01778   
01779   // Return score
01780   return trans_overlap(atomtype, coor, sel->selected, &rot, sigma,
01781                        NOSKIP_IDENTICAL, maxnatoms);
01782 }
01783 
01784 
01785 // testaxis must be normalized.
01786 void Symmetry::check_add_rotary_reflection(const float *testaxis, int maxorder) {
01787   if (maxorder<4) return;
01788 
01789   //msgInfo << "checking improper: maxorder=" << maxorder << sendmsg;
01790 
01791   int n;
01792   for (n=3; n<=maxorder; n++) {
01793     if (n>=9 && n%2) continue;
01794     if (maxorder%n)  continue;
01795 
01796     // Get the overlap for each rotary reflection:
01797     float overlap = score_rotary_reflection(testaxis, n);
01798     //printf("rotrefl: n=%i, axis angle = %.2f, overlap = %.2f\n", n, 360.0/n, overlap);
01799   
01800 
01801     if (overlap>OVERLAPCUTOFF) {
01802       int k;
01803       bool found = 0;
01804       for (k=0; k<numrotreflect(); k++) {
01805         float avgaxis[3];
01806         vec_copy(avgaxis, rotreflect(k));
01807         vec_normalize(avgaxis);
01808       
01809         if (n!=rotreflections[k].order) continue;
01810 
01811         float dot = dot_prod(avgaxis, testaxis);
01812         if (fabs(dot) > collintol) {
01813           // We are summing up the collinear axes to get the average
01814           // of the equivalent axes.
01815           if (dot>0) { 
01816             vec_incr(rotreflect(k), testaxis);           // axes[k] += testaxis
01817           } else {
01818             vec_scaled_add(rotreflect(k), -1, testaxis); // axes[k] -= testaxis
01819           }
01820           rotreflections[k].weight++;
01821           found = 1;
01822           break;
01823         }
01824       }
01825       if (!found) {
01826         // We found no existing collinear rr-axis, so add a new one to the list.
01827         Axis a;
01828         vec_copy(a.v, testaxis);
01829         a.order   = n;
01830         a.overlap = overlap;
01831         a.weight  = 1;
01832         a.type    = 0;
01833         rotreflections.append(a);
01834       }
01835     }
01836   }
01837 }
01838 
01839 
01840 // Purge planes with an overlap below half of the maximum overlap
01841 // that occurs in planes and axes.
01842 void Symmetry::purge_planes(float cutoff) {
01843   maxweight  = 0;
01844   maxoverlap = 0.0;
01845   if (!numplanes()) return;
01846 
01847   maxweight  = planes[0].weight;
01848   maxoverlap = planes[0].overlap;
01849   int i;
01850   for (i=1; i<numplanes(); i++) {
01851     if (planes[i].overlap>maxoverlap) maxoverlap = planes[i].overlap;
01852     if (planes[i].weight >maxweight)  maxweight  = planes[i].weight;
01853   }
01854 
01855   float tol = cutoff*maxoverlap;
01856   int *keep = new int[planes.num()];
01857   memset(keep, 0, planes.num()*sizeof(int));
01858 
01859   for (i=0; i<numplanes(); i++) {
01860     if (planes[i].overlap>tol) {
01861       // keep this plane
01862       keep[i] = 1;
01863     }
01864   }
01865 
01866   prune_planes(keep);
01867 
01868   delete [] keep;
01869 }
01870 
01871 
01872 // Purge axes with an overlap below half of the maximum overlap
01873 // that occurs in planes and axes.
01874 void Symmetry::purge_axes(float cutoff) {
01875   if (!numaxes()) return;
01876 
01877   int i;
01878   for (i=0; i<numaxes(); i++) {
01879     if (axes[i].overlap>maxoverlap) maxoverlap = axes[i].overlap;
01880     if (axes[i].weight >maxweight)  maxweight  = axes[i].weight;
01881   }
01882 
01883   float tol = cutoff*maxoverlap;
01884   int *keep = new int[axes.num()];
01885   memset(keep, 0, axes.num()*sizeof(int));
01886 
01887   for (i=0; i<numaxes(); i++) {
01888     if (axes[i].overlap>tol) {
01889       // keep this axis
01890       keep[i] = 1;
01891     }
01892   }
01893 
01894   prune_axes(keep);
01895 
01896   delete [] keep;
01897 }
01898 
01899 
01900 // Purge rotary reflections with an overlap below half of the maximum overlap
01901 // that occurs in planes and axes.
01902 void Symmetry::purge_rotreflections(float cutoff) {
01903   if (!numrotreflect()) return;
01904 
01905   int i;
01906   for (i=0; i<numrotreflect(); i++) {
01907     if (rotreflections[i].overlap>maxoverlap) maxoverlap = rotreflections[i].overlap;
01908     if (rotreflections[i].weight >maxweight)  maxweight  = rotreflections[i].weight;
01909   }
01910 
01911   float tol = cutoff*maxoverlap;
01912   int *keep = new int[rotreflections.num()];
01913   memset(keep, 0, rotreflections.num()*sizeof(int));
01914 
01915   for (i=0; i<numrotreflect(); i++) {
01916     if (rotreflections[i].overlap>tol) {
01917       // keep this rotary reflection
01918       keep[i] = 1;
01919     }
01920     else if (verbose>4) {
01921       // purge this rotary reflection
01922       char buf[512];
01923       sprintf(buf, "purged S%i rotary reflection %i (weight=%i, overlap=%.2f tol=%.2f)",
01924               rotreflections[i].order, i, rotreflections[i].weight, rotreflections[i].overlap, tol);
01925       msgInfo << buf << sendmsg;
01926     }
01927   }
01928 
01929   prune_rotreflections(keep);
01930 
01931   delete [] keep;
01932 }
01933 
01934 
01935 // For each plane i prune from the list if keep[i]==0.
01936 void Symmetry::prune_planes(int *keep) {
01937   if (!planes.num()) return;
01938 
01939   numverticalplanes = 0;
01940   numdihedralplanes = 0;
01941 
01942   int numkept = 0;
01943   Plane *keptplanes = new Plane[numplanes()];
01944   int i;
01945   for (i=0; i<planes.num(); i++) {
01946     //printf("keep plane[%i] = %i\n", i, keep[i]);
01947     if (keep[i]) {
01948       // keep this plane
01949       keptplanes[numkept] = planes[i];
01950       if (planes[i].type==HORIZONTALPLANE) horizontalplane=numkept;
01951       else if (planes[i].type==VERTICALPLANE) numverticalplanes++;
01952       else if (planes[i].type==DIHEDRALPLANE) {
01953         numdihedralplanes++;
01954         numverticalplanes++;
01955       }
01956       numkept++;
01957       
01958     } else {
01959       // purge this plane
01960       if (planes[i].type==HORIZONTALPLANE) horizontalplane=-1;
01961       if (verbose>3) {
01962         char buf[256];
01963         sprintf(buf, "removed %s%s%s plane %i (weight=%i, overlap=%.2f)\n", 
01964                 (planes[i].type==HORIZONTALPLANE?"horiz":""),
01965                 (planes[i].type==VERTICALPLANE?"vert":""),
01966                 (planes[i].type==DIHEDRALPLANE?"dihed":""),
01967                 i, planes[i].weight, planes[i].overlap);
01968         msgInfo << buf << sendmsg;
01969       }
01970     }
01971   }
01972   memcpy(&(planes[0]), keptplanes, numkept*sizeof(Plane));
01973   planes.truncatelastn(numplanes()-numkept);
01974 
01975   delete [] keptplanes;
01976 }
01977 
01978 
01979 // For each axis i prune from the list if keep[i]==0.
01980 void Symmetry::prune_axes(int *keep) {
01981   if (!axes.num()) return;
01982 
01983   int numkept = 0;
01984   Axis *keptaxes = new Axis[numaxes()];
01985 
01986   maxaxisorder = 0;
01987   int i;
01988   for (i=0; i<axes.num(); i++) {
01989     //printf("keep axis[%i] = %i\n", i, keep[i]);
01990     if (keep[i]) {
01991       // keep this axis
01992       keptaxes[numkept++] = axes[i];
01993       
01994       if (axes[i].order>maxaxisorder) 
01995         maxaxisorder = axes[i].order;
01996     }
01997   }
01998   memcpy(&(axes[0]), keptaxes, numkept*sizeof(Axis));
01999   axes.truncatelastn(numaxes()-numkept);
02000   
02001   delete [] keptaxes;
02002 }
02003 
02004 
02005 // For each axis i prune from the list if keep[i]==0.
02006 void Symmetry::prune_rotreflections(int *keep) {
02007   if (!rotreflections.num()) return;
02008 
02009   int numkept = 0;
02010   Axis *keptrotrefl = new Axis[numrotreflect()];
02011 
02012   maxrotreflorder = 0;
02013   int i;
02014   for (i=0; i<numrotreflect(); i++) {
02015     if (keep[i]) {
02016       // keep this rotary reflection
02017       keptrotrefl[numkept++] = rotreflections[i];
02018 
02019       if (rotreflections[i].order>maxrotreflorder)
02020         maxrotreflorder = rotreflections[i].order;
02021     }
02022   }
02023 
02024   memcpy(&(rotreflections[0]), keptrotrefl, numkept*sizeof(Axis));
02025   rotreflections.truncatelastn(numrotreflect()-numkept);
02026 
02027   delete [] keptrotrefl;
02028 }
02029 
02030 
02031 // Assign orders to rotational symmetry axes.
02032 void Symmetry::assign_axis_orders() {
02033   if (!numaxes()) return;
02034 
02035   maxaxisorder = axes[0].order;
02036 
02037   // Compute all axis orders
02038   int i;
02039   for (i=0; i<numaxes(); i++) {
02040     if (!axes[i].order) axes[i].order = axis_order(axis(i), &(axes[i].overlap));
02041 
02042     //printf("axis[%i] {%f %f %f} order = %i, weight = %i overlap = %.2f\n", i,
02043     //     axes[i].v[0], axes[i].v[1], axes[i].v[2], axes[i].order, axes[i].weight, axes[i].overlap);
02044     if (axes[i].order>maxaxisorder) {
02045       maxaxisorder = axes[i].order;
02046     }
02047   }
02048 }
02049 
02050 
02051 // Assign orders to preliminary C2 rotational symmetry axes.
02052 void Symmetry::assign_prelimaxis_orders(int order) {
02053   int i;
02054   for (i=0; i<numaxes(); i++) {
02055     if (axes[i].order==-order) {
02056       axes[i].order=order; 
02057       if (axes[i].weight>1) {
02058         // We have computed the overlap only for
02059         // the first instance not for the average
02060         // so we want to re-score it here.
02061         axes[i].overlap = score_axis(axis(i), order);
02062       }
02063     }
02064 
02065     if (axes[i].order>maxaxisorder) maxaxisorder = axes[i].order;
02066   }
02067 }
02068 
02069 // Sort the axes according to decreasing order
02070 // Also eliminate C1 axes and Cn axes that are parallel
02071 // to a Cinf axes.
02072 void Symmetry::sort_axes() {
02073   int i;
02074 
02075   // count number of sorted axes.
02076   int numsortedaxes = 0;
02077   for (i=0; i<numaxes(); i++) {
02078     if (axes[i].order>1 || axes[i].order==INFINITE_ORDER) numsortedaxes++;
02079   }
02080 
02081   Axis *sortedaxes = new Axis[numsortedaxes*sizeof(Axis)];
02082   int j, k=0, have_inf=0;
02083   for (j=0; j<numaxes(); j++) {
02084     if (axes[j].order == INFINITE_ORDER) {
02085       sortedaxes[k] = axes[j];
02086       k++;
02087       have_inf = 1;
02088     }
02089   }
02090 
02091   for (i=maxaxisorder; i>1; i--) {
02092     for (j=0; j<numaxes(); j++) {
02093       if (axes[j].order == i) {
02094         if (have_inf && 
02095             collinear(sortedaxes[0].v, axes[j].v, collintol)) {
02096           continue;
02097         }
02098         sortedaxes[k] = axes[j];
02099         k++;
02100       }
02101     }
02102   }
02103 
02104   memcpy(&(axes[0]), sortedaxes, numsortedaxes*sizeof(Axis));
02105   axes.truncatelastn(numaxes()-numsortedaxes);
02106 
02107   delete [] sortedaxes;
02108 }
02109 
02110 
02111 // Sort the planes: horizontal plane first, then dihedral,
02112 // then vertical, then the rest
02113 void Symmetry::sort_planes() {
02114   Plane *sortedplanes = new Plane[numplanes()*sizeof(Plane)];
02115 
02116   int k = 0;
02117   if (horizontalplane>=0) {
02118     sortedplanes[k] = planes[horizontalplane];
02119     horizontalplane = k++;
02120   }
02121 
02122   int j;
02123   for (j=0; j<numplanes(); j++) {
02124     if (planes[j].type == DIHEDRALPLANE) {
02125       sortedplanes[k++] = planes[j];
02126     }
02127   }
02128   for (j=0; j<numplanes(); j++) {
02129     if (planes[j].type == VERTICALPLANE) {
02130       sortedplanes[k++] = planes[j];
02131     }
02132   }
02133   for (j=0; j<numplanes(); j++) {
02134     if (planes[j].type == 0) {
02135       sortedplanes[k++] = planes[j];
02136     }
02137   }
02138 
02139   memcpy(&(planes[0]), sortedplanes, numplanes()*sizeof(Plane));
02140 
02141   delete [] sortedplanes;
02142 }
02143 
02144 
02145 // Find the highest rotational symmetry order (up to 8) for the given axis.
02146 // Rotates the selection by 360/i degrees (i=2...8) and checks the structural
02147 // overlap.
02148 int Symmetry::axis_order(const float *axis, float *overlap) {
02149   int i;
02150 
02151   // Get the overlap for each rotation
02152   float overlaparray[MAXORDERCN+1];
02153   float overlappermarray[MAXORDERCN+1];
02154   overlappermarray[1] = 0.0;
02155   for (i=2; i<=MAXORDERCN; i++) {
02156     Matrix4 rot;
02157     // need to shift to origin and back
02158     rot.translate(rcom[0], rcom[1], rcom[2]);
02159     rot.rotate_axis(axis, float(DEGTORAD(360.0f/i)));
02160     rot.translate(-rcom[0], -rcom[1], -rcom[2]);
02161     overlaparray[i] = trans_overlap(atomtype, coor, sel->selected, &rot, 1.5f*sigma, SKIP_IDENTICAL, maxnatoms, overlappermarray[i]);
02162   }
02163 
02164   // Get the maximum overlap
02165   float maxover = overlaparray[2];
02166   //printf("orders: %.2f ", overlaparray[2]);
02167   for (i=3; i<=MAXORDERCN; i++) {
02168     //printf("%.2f ", overlaparray[i]);
02169     if (overlaparray[i]>maxover) {
02170       maxover = overlaparray[i];
02171     }
02172   }
02173 
02174   // If overlap for a certain rotation is greater than half maxover
02175   // we assume that the axis has the according order or a multiple of that.
02176   float maxfalseov = 0.0f;
02177   int maxorder = 1;
02178   short int orders[MAXORDERCN+1];
02179   for (i=2; i<=MAXORDERCN; i++) {
02180     if (overlaparray[i]>0.5f*maxover) {
02181       orders[i] = 1;
02182       maxorder = i;
02183     } else {
02184       orders[i] = 0;
02185       if (overlaparray[i]>maxfalseov) maxfalseov = overlaparray[i];
02186     }
02187   }
02188 
02189 #if defined(_MSC_VER)
02190   float tol1 = 0.25f + 0.15f*float(myerfc(0.05f*float(sel->selected-50)));
02191 #elif defined(__irix)
02192   float tol1 = 0.25f + 0.15f*erfc(0.05f*float(sel->selected-50));
02193 #else
02194   float tol1 = 0.25f + 0.15f*erfcf(0.05f*float(sel->selected-50));
02195 #endif
02196   //printf(" tol1=%.2f tol=%.2f\n", tol1, tol1-tol2+pow(0.09*maxorder,6));
02197 
02198   // Make the tolerance dependent on the maximum order.
02199   // For maxorder=2 tol=0.2 for maxorder=8 tol=0.27 (empirical)
02200   if (maxover<tol1+powf(0.09f*maxorder, 6)) {
02201     *overlap = maxover;
02202     return 1;
02203   }
02204 
02205   // We return the arithmetic mean of the relative and the maximum overlap.
02206   // The relative overlap is the quality of the matching axis orders wrt
02207   // the best non-matching one.
02208   if (orders[2]) {
02209     if (orders[3] && orders[6]) {
02210       float avgov = (overlaparray[2]+overlaparray[3]+overlaparray[6])/3.0f;
02211       *overlap = 0.5f*(maxover + (avgov-maxfalseov)/avgov);
02212       return 6;
02213     } else if (orders[4]) {
02214       if (MAXORDERCN>=8 && orders[8]) {
02215         float avgov = (overlaparray[2]+overlaparray[4]+overlaparray[8])/3.0f;
02216         *overlap = 0.5f*(maxover + (avgov-maxfalseov)/avgov);
02217         return 8;
02218       }
02219       *overlap = 0.5f*(maxover + (overlaparray[4]-maxfalseov)/overlaparray[4]);
02220       return 4;
02221     }
02222 
02223     *overlap = 0.5f*(maxover + (overlaparray[2]-maxfalseov)/overlaparray[2]);
02224     return 2;      
02225   } else if (orders[3]) {
02226     *overlap = 0.5f*(maxover + (overlaparray[3]-maxfalseov)/overlaparray[3]);
02227     return 3;
02228   } else if (orders[5]) {
02229     *overlap = 0.5f*(maxover + (overlaparray[5]-maxfalseov)/overlaparray[5]);
02230     return 5;
02231   } else if (orders[7]) {
02232     *overlap = 0.5f*(maxover + (overlaparray[7]-maxfalseov)/overlaparray[7]);
02233     return 7;
02234   }
02235 
02236   *overlap = maxover;
02237   return 1;
02238 }
02239 
02240 
02241 // Generate symmetricized coordinates by wrapping atoms around
02242 // the symmetry elements and average these images with the 
02243 // original positions.
02244 void Symmetry::idealize_coordinates() {
02245   int i;
02246 
02247   // copy atom coordinates into idealcoor array
02248   memcpy(idealcoor, coor, 3*sel->selected*sizeof(float));
02249 
02250   // -----------Planes-------------
02251   int *keep = new int[planes.num()];
02252   memset(keep, 0, planes.num()*sizeof(int));
02253 
02254   for (i=0; i<numplanes(); i++) {
02255     Matrix4 mirror;
02256     mirror.translate(rcom[0], rcom[1], rcom[2]);
02257     mirror.scale(-1.0);  // inversion
02258     mirror.rotate_axis(planes[i].v, float(VMD_PI));
02259     mirror.translate(-rcom[0], -rcom[1], -rcom[2]);
02260 
02261     //printf("PLANE %d\n", i);
02262     if (average_coordinates(&mirror)) keep[i] = 1;
02263   }
02264 
02265   prune_planes(keep);
02266 
02267 
02268   // -----------Axes-----------
02269   int   *weight  = new int[sel->selected];
02270   float *avgcoor = new float[3*sel->selected];
02271 
02272   delete [] keep;
02273   keep = new int[axes.num()];
02274   memset(keep, 0, axes.num()*sizeof(int));
02275 
02276   for (i=0; i<numaxes(); i++) {
02277     memset(weight,  0,   sel->selected*sizeof(int));
02278     memcpy(avgcoor, idealcoor, 3*sel->selected*sizeof(int));
02279     int order = axes[i].order;
02280     //printf("AXIS %i\n", i);
02281 
02282     // In case of a Cinf axis just use order 4 to idealize in the two
02283     // dimensions perpendicular to the axis
02284     if (order==INFINITE_ORDER) order = 4;
02285 
02286     // Average the coordinates over all rotary orientations
02287     int success = 1;
02288     int n, j;
02289     for (n=1; n<order; n++) {
02290       Matrix4 rot;
02291       rot.translate(rcom[0], rcom[1], rcom[2]);
02292       rot.rotate_axis(axis(i), n*float(VMD_TWOPI)/order);
02293       rot.translate(-rcom[0], -rcom[1], -rcom[2]);
02294 
02295       // For each atom find the closest transformed image atom with
02296       // the same chemical element. The array matchlist will contain
02297       // the closest match atom index (into the collapsed list of
02298       // selected atoms) for each selected atom.
02299       int nmatches;
02300       int *matchlist = NULL;
02301       identify_transformed_atoms(&rot, nmatches, matchlist);
02302 
02303       for(j=0; j<sel->selected; j++) {
02304         int m = matchlist[j];
02305         if (m<0) continue; // no match found;
02306 
02307         if (checkbonds) {
02308           // Rotate the bondsum vector according to the Cn axis
02309           // and compare
02310           if (!check_bondsum(j, m, &rot)) {
02311             success = 0;
02312             break;
02313           }
02314         }
02315 
02316         float tmpcoor[3];
02317         rot.multpoint3d(idealcoor+3*m, tmpcoor);
02318         vec_incr(avgcoor+3*j, tmpcoor);
02319         weight[j]++;
02320       }
02321       if (matchlist) delete [] matchlist;
02322 
02323       if (!success) break;
02324       //printf("average coor for angle=%.2f\n", n*360.f/order);
02325     }
02326 
02327     if (success) {
02328       // Combine the averaged coordinates for this axis with the existing
02329       // ideal coordinates
02330       for(j=0; j<sel->selected; j++) {
02331         vec_scale(idealcoor+3*j, 1.0f/(1+weight[j]), avgcoor+3*j);
02332       }
02333 
02334       keep[i] = 1;
02335     }
02336   }
02337 
02338   prune_axes(keep);
02339 
02340   delete [] weight;
02341   delete [] avgcoor;
02342 
02343 
02344   // -----------Rotary reflections-----------
02345   delete [] keep;
02346   keep = new int[rotreflections.num()];
02347   memset(keep, 0, rotreflections.num()*sizeof(int));
02348 
02349   for (i=0; i<numrotreflect(); i++) {
02350     Matrix4 rot;
02351     rot.translate(rcom[0], rcom[1], rcom[2]);
02352     rot.rotate_axis(rotreflect(i), float(VMD_TWOPI)/rotreflections[i].order);
02353     rot.scale(-1.0f);  // inversion
02354     rot.rotate_axis(rotreflect(i), float(VMD_PI));
02355     rot.translate(-rcom[0], -rcom[1], -rcom[2]);
02356 
02357     //printf("ROTREF %i\n", i);
02358     if (average_coordinates(&rot)) keep[i] = 1;
02359   }
02360 
02361   prune_rotreflections(keep);
02362 
02363   delete [] keep;
02364 
02365   // -----------Inversion-----------
02366   if (inversion) {
02367     // Construct inversion matrix
02368     Matrix4 inv;
02369     inv.translate(rcom[0], rcom[1], rcom[2]);
02370     inv.scale(-1.0f);  // inversion
02371     inv.translate(-rcom[0], -rcom[1], -rcom[2]);
02372 
02373     if (!average_coordinates(&inv)) inversion = 0;
02374   }
02375 }
02376 
02377 
02378 // Averages between original and transformed coordinates.
02379 int Symmetry::average_coordinates(Matrix4 *trans) {
02380   int j;
02381   int nmatches;
02382   int *matchlist = NULL;
02383   identify_transformed_atoms(trans, nmatches, matchlist);
02384 
02385   if (checkbonds) {
02386     int success = 1;
02387     for(j=0; j<sel->selected; j++) {
02388       int m = matchlist[j];
02389       
02390       if (m<0) continue; // no match found;
02391       
02392       if (!check_bondsum(j, m, trans)) {
02393         success = 0;
02394         break;
02395       }
02396     }
02397     if (!success) {
02398       if (verbose>3) {
02399         msgInfo << "Transformation messes up bonds!" << sendmsg;
02400       }
02401       if (matchlist) delete [] matchlist;
02402       return 0;
02403     }
02404   }
02405 
02406   for(j=0; j<sel->selected; j++) {
02407     int m = matchlist[j];
02408 
02409     if (m<0) continue; // no match found;
02410 
02411     // Average between original and image coordinates    
02412     float avgcoor[3];
02413     trans->multpoint3d(idealcoor+3*m, avgcoor);
02414     vec_incr(avgcoor, idealcoor+3*j);
02415     vec_scale(idealcoor+3*j, 0.5, avgcoor);
02416   }
02417   if (matchlist) delete [] matchlist;
02418 
02419   return 1;
02420 }
02421 
02422 // Check the bondsum for atom j and its image m generated by 
02423 // transformation trans.
02424 int Symmetry::check_bondsum(int j, int m, Matrix4 *trans) {
02425   float tmp[3];
02426   vec_add(tmp, bondsum+3*m, rcom);
02427   trans->multpoint3d(tmp, tmp);
02428   vec_sub(tmp, tmp, rcom);
02429 
02430   if (distance(bondsum+3*j, tmp)>BONDSUMTOL) {
02431     if (verbose>4) {
02432       char buf[256];
02433       sprintf(buf, "Bond mismatch %i-->%i, bondsum distance = %.2f",
02434               atomindex[j], atomindex[m], 
02435               distance(bondsum+3*j, tmp));
02436       msgInfo << buf << sendmsg;
02437     }
02438     //printf("bondsum 1:        {%.2f %.2f %.2f}\n", bondsum[3*j], bondsum[3*j+1], bondsum[3*j+2]);
02439     //printf("bondsum 2:        {%.2f %.2f %.2f}\n", bondsum[3*m], bondsum[3*m+1], bondsum[3*m+2]);
02440     //printf("bondsum 1 transf: {%.2f %.2f %.2f}\n", tmp[0], tmp[1], tmp[2]);
02441     return 0;
02442   }
02443   return 1;
02444 }
02445 
02446 
02447 // For each atom find the closest transformed image atom
02448 // with the same chemical element. The result is an array
02449 // containing the closest match atom index for each
02450 // selected atom.
02451 // XXX maybe put bondsum check in here?!
02452 void Symmetry::identify_transformed_atoms(Matrix4 *trans,
02453                                           int &nmatches,
02454                                           int *(&matchlist)) {
02455   // get atom coordinates
02456   const float *posA = coor;
02457 
02458   float *posB = new float[3*sel->selected];
02459 
02460   if (matchlist) delete [] matchlist;
02461   matchlist = new int[sel->selected];
02462 
02463   // generate transformed coordinates
02464   int i;
02465   for(i=0; i<sel->selected; i++) {
02466     trans->multpoint3d(posA+3*i, posB+3*i);
02467   }
02468 
02469   nmatches = 0;
02470   for(i=0; i<sel->selected; i++) {
02471     float minr2=999999.f;
02472     int k, kmatch = -1;
02473     for(k=0; k<sel->selected; k++) {
02474       // consider only pairs with identical atom types 
02475       if (atomtype[i]==atomtype[k]) {
02476 #if 0
02477         if (checkbonds) {
02478           float imagebondsum[3];
02479           vec_add(imagebondsum, bondsum+3*k, rcom);
02480           trans->multpoint3d(imagebondsum, imagebondsum);
02481           vec_sub(imagebondsum, imagebondsum, rcom);
02482 
02483           if (distance(bondsum+3*i, imagebondsum)>BONDSUMTOL) {
02484             continue;
02485           }
02486         }
02487 #endif
02488         float r2 = distance2(posA+3*i, posB+3*k);
02489 
02490         if (r2<minr2) { minr2 = r2; kmatch = k; }
02491       }
02492     }
02493 
02494     if (kmatch>=0) {
02495       matchlist[i] = kmatch;
02496       nmatches++;
02497       //printf("atom %i matches %i (atomtype %i)\n",
02498       //   atomindex[i], atomindex[kmatch], atomtype[i]);
02499     }
02500     else {
02501       // no match found
02502       matchlist[i] = i;
02503 
02504       if (verbose>3) {
02505         char buf[256];
02506         sprintf(buf, "No match for atom %i (atomtype %i)\n", atomindex[i], atomtype[i]);
02507         msgInfo << buf << sendmsg;
02508       }
02509     }
02510   }
02511 
02512   delete [] posB;
02513 }
02514 
02515 
02516 // Compute the RMSD between original and idealized coordinates
02517 float Symmetry::ideal_coordinate_rmsd () {
02518   // get original atom coordinates
02519   const float *pos = sel->coordinates(mlist);
02520 
02521   float rmsdsum = 0.0;
02522   int i, j=0;
02523   for (i=0; i<sel->num_atoms; i++) {
02524     if (sel->on[i]) {
02525       rmsdsum += distance2(pos+3*i, idealcoor+3*j);
02526       j++;
02527     }
02528   }
02529 
02530   return sqrtf(rmsdsum / sel->selected);
02531 }
02532 
02533 // Compute the RMSD between original and idealized coordinates
02534 int Symmetry::ideal_coordinate_sanity() {
02535   int i, j;
02536   float mindist2 = float(MINATOMDIST*MINATOMDIST);
02537   for (i=0; i<sel->selected; i++) {
02538     for (j=i+1; j<sel->selected; j++) {
02539       if (distance2(idealcoor+3*i, idealcoor+3*j)<mindist2)
02540         return 0;
02541     }
02542   }
02543 
02544   return 1;
02545 }
02546 
02547 
02548 // Idealize all symmetry elements so that they have certain geometries
02549 // with respect to each other. This means, for instance, to make sure
02550 // that the planes vertical to a C3 axis are evenly spaced by 60 degree
02551 // angles and that their intersection is exactly the C3 axis.
02552 //
02553 // We start with idealizing horizontal and vertical planes with respect
02554 // to the primary axis. If there is no unique primary axis (e.g. for
02555 // Oh, Th, Ih) we take each of the highest order axes, idealize it wrt
02556 // to the first one and then idealize the planes vertical and horizontal
02557 // to them. Since all plajnes should be idealized by now we next
02558 // idealize axes that are intersections of two planes. Then we adjust
02559 // axes that are ideally bisectors of two planes sch as they occur in
02560 // Dnd point groups. Finally we idealize axes that are not related to
02561 // any planes such as in Dn groups. They should be perpedicular to the
02562 // highest order axis and evenly spaced around it.
02563 // Rotary reflections are simply brought to collinearity with their
02564 // accompanying rotary axis (An improper axis never comes alone, e.g.
02565 // S4 implies a C2 axis).
02566 // Symmetry elements that could not be idealized are removed from the
02567 // list.
02568 void Symmetry::idealize_elements() {
02569   int i;
02570 
02571   // Array of flags indicating if a plane has been idealized
02572   int *idealp = NULL;
02573   if (planes.num()) {
02574     idealp = new int[planes.num()];
02575     memset(idealp, 0, planes.num()*sizeof(int));
02576   }
02577 
02578 
02579   // If there are axes present we will use the first axis as reference.
02580   // Since the axes were sorted by axis order before our reference will
02581   // be the axis with the highest order (or one of them).
02582   // Then align any horizontal and vertical planes with that axis.
02583   if (axes.num()) {
02584 
02585     // Make horizontal refplane truly perpendicular to reference axis
02586     if (horizontalplane>=0) {
02587       //msgInfo << "Idealizing horizontal plane " << horizontalplane << ", numplanes="
02588       //              << planes.num() << sendmsg;
02589       vec_copy(planes[horizontalplane].v, axes[0].v);
02590       idealp[horizontalplane] = 1;
02591     } 
02592 
02593     if (numverticalplanes) {
02594       // Find the first vertical plane and align it with the reference axis so it
02595       // becomes our vertical reference plane.
02596       int vref = -1;
02597       for (i=0; i<planes.num(); i++) {
02598         if (planes[i].type&VERTICALPLANE) {
02599           //msgInfo << "Idealizing vertical plane " << i << sendmsg;
02600           vref = i;
02601           float normal[3];
02602           cross_prod(normal, axes[0].v, plane(vref));
02603           cross_prod(planes[vref].v, axes[0].v, normal);
02604           vec_normalize(planes[vref].v);
02605           idealp[vref] = 1;
02606           break;
02607         }
02608       }
02609 
02610       // Find other vertical planes and idealize them wrt the vertical reference plane.
02611       // To do so, we first have to align the found vertical plane with the reference
02612       // axis and then idealize the angle wrt the vertical reference plane.
02613       for (i=vref+1; i<planes.num(); i++) {
02614         if (planes[i].type&VERTICALPLANE) {
02615           align_plane_with_axis(plane(i), axes[0].v, plane(i));
02616           //msgInfo << "Idealizing plane " << i << " wrt vertical plane " << vref << sendmsg;
02617 
02618           float idealplane[3];
02619           if (!idealize_angle(planes[vref].v, axes[0].v, plane(i), idealplane, axes[0].order)) {
02620             if (verbose>3)
02621               msgInfo << "Couldn't idealize vertical plane " << i << sendmsg;
02622             continue;
02623           }
02624 
02625           int first = plane_exists(idealplane);
02626           if (first>=0) {
02627             // Equivalent plane exists already.
02628             // Since idealp[i] will not be set 1, this plane will be deleted
02629             // at the end of this function.
02630             if (!idealp[first]) {
02631               vec_copy(planes[first].v, idealplane);
02632               idealp[first] = 1;
02633             }
02634           } else {
02635             vec_copy(planes[i].v, idealplane);
02636             idealp[i] = 1;
02637           }
02638         }
02639       }
02640     }
02641      
02642 
02643   } else {    // No axis present
02644 
02645     // If we have only one plane, it is automatically ideal.
02646     if (planes.num()<=1) {
02647       if (idealp) delete [] idealp;
02648       return;
02649     }
02650 
02651     // Actually, if there is more than one plane, at least one axis
02652     // should have been found by intersection. If we don't have it
02653     // here, it was probably purged in find_symmetry_elements().
02654     // That means that at least one of the planes is most likely bad.
02655     // We will keep the only best plane and mark it as ideal.
02656 
02657     int bestplane = 0;
02658     float bestscore = 0.f;
02659     for (i=0; i<planes.num(); i++) {
02660       float score = planes[i].overlap*planes[i].weight;
02661       if (score>bestscore) {
02662         bestplane = i;
02663         bestscore = score;
02664       }
02665       idealp[i] = 0;
02666     }
02667     idealp[bestplane] = 1;
02668     
02669     if (verbose>4) {
02670       msgErr << "Found planes without intersection axis!" << sendmsg;
02671       char buf[256];
02672       for (i=0; i<planes.num(); i++) {
02673         sprintf(buf, "plane[%i] {% .3f % .3f % .3f} overlap = %f, weight = %d", i,
02674                 planes[i].v[0], planes[i].v[1], planes[i].v[2],
02675                 planes[i].overlap, planes[i].weight);
02676         msgErr << buf << sendmsg;
02677       }
02678     }
02679   }
02680 
02681   int numidealaxes = 0;
02682   int *ideala = NULL;
02683   if (axes.num()) {
02684     ideala = new int[axes.num()];
02685     memset(ideala, 0, axes.num()*sizeof(int));
02686     ideala[0] = 1;
02687   }
02688 
02689   int geometry = 0;
02690   if      (maxaxisorder==2) geometry = 4;
02691   else if (maxaxisorder==3) geometry = TETRAHEDRON;
02692   else if (maxaxisorder==4) geometry = OCTAHEDRON;
02693   else if (maxaxisorder==5) geometry = DODECAHEDRON;
02694 
02695 
02696   for (i=1; i<numaxes(); i++) {
02697     if (axes[i].order<maxaxisorder) continue;
02698     int j, vref=-1;
02699     
02700     //msgInfo << "Idealize axis " << i << " (C" << axes[i].order << ")" << sendmsg;
02701 
02702     // Find plane that contains both axes[0] and axis[i]
02703     // and idealize axis[i] wrt reference axis[0]
02704     for (j=0; j<numplanes(); j++) {
02705       if (orthogonal(planes[j].v, axes[i].v, orthogtol) &&
02706           orthogonal(planes[j].v, axes[0].v, orthogtol)) {
02707         if (!idealp[j]) {
02708           // The plane couldn't be idealized in the previous step.
02709           // Skip it.
02710           continue;
02711         }
02712         
02713         float idealaxis[3];
02714         if (!idealize_angle(axes[0].v, planes[j].v, axes[i].v,
02715                             idealaxis, geometry)) {
02716           if (verbose>4) {
02717             msgInfo << "Couldn't idealize axis " << i
02718                     << " wrt reference axis in plane " << j
02719                     << "." << sendmsg;
02720           }
02721           continue;
02722         }
02723         vec_copy(axes[i].v, idealaxis);
02724         vref = j;
02725         ideala[i] = 1;
02726         break;
02727       }
02728     }
02729 
02730     if (vref<0) continue;
02731 
02732     // Find planes that are vertical to the current axis and
02733     // idealize them wrt plane vref.
02734     for (j=0; j<planes.num(); j++) {
02735       if (idealp[j]) continue;
02736 
02737       // Check if plane is vertical to axis[i]; 
02738       if (orthogonal(planes[j].v, axes[i].v, orthogtol)) {
02739         // align the current plane with the idealized axis
02740         align_plane_with_axis(planes[j].v, axes[i].v, planes[j].v);
02741         //msgInfo << "Idealizing plane " <<j<< " wrt vertical plane " <<vref<< sendmsg;
02742 
02743         // idealize vertical plane wrt vref
02744         float idealplane[3];
02745         if (!idealize_angle(planes[vref].v, axes[i].v, plane(j), idealplane, axes[i].order)) {
02746           if (verbose>4) {
02747             msgInfo << "Vertical plane " <<j<< " couldn't be idealized!" << sendmsg;
02748           }
02749           continue;
02750         }
02751         int first = plane_exists(idealplane);
02752         if (first>=0) {
02753           // Equivalent plane exists already.
02754           // Since idealp[i] will not be set 1, this plane will be deleted
02755           // below.
02756           if (!idealp[first]) {
02757             vec_copy(planes[first].v, idealplane);
02758             idealp[first] = 1;
02759           }
02760         } else {
02761           vec_copy(planes[j].v, idealplane);
02762           idealp[j] = 1;
02763         }
02764       }
02765     }
02766   }
02767 
02768   // Delete all planes that could not be idealized.
02769   prune_planes(idealp);
02770 
02771 
02772   // By now all planes should be idealized, so we can idealize remaining
02773   // axes that are intersections of planes.
02774   for (i=0; i<numplanes(); i++) {
02775     int j;
02776     for (j=i+1; j<numplanes(); j++) {
02777       // Get intersection of the two planes
02778       float intersect[3];
02779       cross_prod(intersect, plane(i), plane(j));
02780       vec_normalize(intersect);
02781 
02782       int k;
02783       for (k=0; k<axes.num(); k++) {
02784         if (ideala[k]) continue;
02785         
02786         if (collinear(intersect, axes[k].v, collintol)) {
02787           // Idealize axis by intersection of two planes
02788           vec_copy(axes[k].v, intersect);
02789           ideala[k] = 1; numidealaxes++;
02790           break;
02791         }
02792       }
02793     }
02794   }
02795 
02796   float halfcollintol = cosf(float(DEGTORAD(5.0f)));
02797   // Idealize axes that are bisectors of two planes.
02798   // We place this search after the one for plane intersections because 
02799   // it showed that otherwise we would get false positive bisectors.
02800   for (i=0; i<numplanes(); i++) {
02801     int j;
02802     for (j=i+1; j<numplanes(); j++) {
02803       // Get first axis bisecting the two planes
02804       float bisect1[3];
02805       vec_add(bisect1, planes[i].v, planes[j].v);
02806       vec_normalize(bisect1);
02807 
02808       // Get second axis bisecting the two planes
02809       float bisect2[3], tmp[3];
02810       vec_negate(tmp, planes[i].v);
02811       vec_add(bisect2, tmp, planes[j].v);
02812       vec_normalize(bisect2);
02813       
02814       int k;
02815       int foundbi1=0, foundbi2=0;
02816       for (k=0; k<axes.num(); k++) {
02817         if (ideala[k]) continue;
02818         
02819         if (!foundbi1 && collinear(bisect1, axes[k].v, halfcollintol)) {
02820           //printf("idealized bisect1 %i (C%i)\n", k, axes[k].order);
02821           vec_copy(axes[k].v, bisect1);
02822           ideala[k] = 1; numidealaxes++;
02823           foundbi1 = 1;
02824           if (foundbi2) break;
02825         }
02826         if (!foundbi2 && collinear(bisect2, axes[k].v, halfcollintol)) {
02827           //printf("idealized bisect2 %i (C%i)\n", k, axes[k].order);
02828           vec_copy(axes[k].v, bisect2);
02829           ideala[k] = 1; numidealaxes++;
02830           foundbi2 = 1;
02831           if (foundbi1) break;
02832         }
02833       }
02834     }
02835   }
02836   
02837 
02838   // We might still have axes that are not related to planes or we didn't
02839   // have planes at all (as in Dn). These axes should be orthogonal to
02840   // the reference.
02841   if (numidealaxes<axes.num()) {
02842     int firstorth = -1;
02843     for (i=0; i<numaxes(); i++) {
02844       if (ideala[i]) continue;
02845 
02846       if (!orthogonal(axes[i].v, axes[0].v, orthogtol)) continue;
02847 
02848       // make axis truly orthogonal to reference      
02849       float tmp[3];
02850       cross_prod(tmp, axes[0].v, axes[i].v);
02851       vec_normalize(tmp);
02852       cross_prod(axes[i].v, tmp, axes[0].v);
02853 
02854       if (firstorth<0) {
02855         firstorth = i;
02856         ideala[i] = 1;
02857         continue;
02858       }
02859       
02860       // Idealize angle between current and first orthogonal axis
02861       if (!idealize_angle(axes[firstorth].v, axes[0].v, axes[i].v, tmp, axes[0].order)) {
02862         if (verbose>4) {
02863           msgInfo << "Couldn't idealize axis "<<i<<" to first orthogonal axis "
02864                   <<firstorth<< "!" << sendmsg;
02865         }
02866         continue;
02867       }
02868       
02869       vec_copy(axes[i].v, tmp);
02870       ideala[i] = 1;
02871     }
02872   }
02873 
02874   // Delete all axes that could not be idealized
02875   prune_axes(ideala);
02876 
02877   if (ideala) delete [] ideala;
02878   if (idealp) delete [] idealp;
02879 
02880   // Idealize rotary reflections with their corresponding rotary axes.
02881   // If no such axis is found we delete the rotary reflection.
02882   if (numrotreflect()) {
02883     int *idealr = new int[numrotreflect()];
02884     memset(idealr, 0, numrotreflect()*sizeof(int));
02885 
02886     for (i=0; i<numrotreflect(); i++) {
02887       int j, success=0;
02888       for (j=0; j<numaxes(); j++) {
02889         float dot = dot_prod(rotreflections[i].v, axes[j].v);
02890         if (fabs(dot) > collintol) {
02891           float tmp[3];
02892           if (dot>0) vec_negate(tmp, axes[j].v);
02893           else       vec_copy(tmp, axes[j].v);
02894           
02895           vec_copy(rotreflections[i].v, tmp);
02896           rotreflections[i].type = j;
02897           success = 1;
02898           break;
02899         }
02900       }
02901       
02902       if (success) {
02903         // keep this rotary reflection
02904         idealr[i] = 1;
02905       }
02906     }
02907 
02908     prune_rotreflections(idealr);
02909     delete [] idealr;
02910   }
02911 
02912 }
02913 
02914 // Assuming that myaxis is close to a symmetry axis the function computes
02915 // the idealized axis, i.e. the closest symmetry axis. The reference axis
02916 // is rotated by the ideal angle around hub and the result is returned in
02917 // idealaxis. All multiples of 180/reforder are considered ideal angles.
02918 // Typically hub would correspond to an already idealized axis and one
02919 // one would just use the order of thar axis for the parameter reforder.
02920 // For instance, if the order of the principle axis is 3 than one would
02921 // expect that other elements like perpendicular axes or vertical planes
02922 // to be spaced at an angle of 180/3=60 degrees. Special values for
02923 // reforder (TETRAHEDRON, OCTAHEDRON, DODECAHEDRON) request ideal angles
02924 // (109, 90, 117) for the accordeing geometries.
02925 // If the measured angle between myaxis and refaxis is within a tolerance
02926 // of an ideal angle then the ideal axis is set and 1 is returned,
02927 // otherwise the return value is 0.
02928 int Symmetry::idealize_angle(const float *refaxis, const float *hub,
02929                              const float *myaxis, float *idealaxis, int reforder) {
02930   float alpha = angle(refaxis, myaxis);
02931 
02932   const float tetrahedron = float(RADTODEG(2.0f*atanf(sqrtf(2.0f)))); // tetrahedral angle ~109 deg
02933   const float octahedron = 90.0f;                           // octahedral angle = 90 deg
02934   const float dodecahedron = float(RADTODEG(acosf(-1/sqrtf(5.0f)))); // dodecahedral angle ~116.565 deg
02935   const float tol = 5.0f;
02936 
02937   int success = 0;
02938   float idealangle=0.0f;
02939 
02940   if      (reforder==TETRAHEDRON)  idealangle = tetrahedron;
02941   else if (reforder==OCTAHEDRON)   idealangle = octahedron;
02942   else if (reforder==DODECAHEDRON) idealangle = dodecahedron;
02943 
02944   if (reforder<0) {
02945     if (fabs(idealangle-alpha)<tol) {
02946       alpha = idealangle;
02947       success = 1;
02948 
02949     } else if (fabs(180.0-idealangle-alpha)<tol) {
02950       alpha = 180-idealangle;
02951       success = 1;
02952     }
02953   }
02954 
02955   int i;
02956   for (i=1; i<reforder; i++) {
02957     idealangle = i*180.0f/reforder;
02958     if (fabs(alpha-idealangle)<tol) {
02959       alpha = idealangle;
02960       success = 1;
02961       break;
02962     }
02963   }
02964 
02965   // Determine the ideal angle of the axis wrt the reference axis
02966   if (fabs(alpha)<tol || fabs(alpha-180)<tol) {
02967     // same axis
02968     alpha = 0;
02969     success = 1;
02970   }
02971 
02972   if (!success) {
02973     //printf("alpha = %.2f, tol = %.2f deg, reforder=%i\n", alpha, tol, reforder);
02974     return 0;
02975   }
02976   
02977   float normal[3];
02978   cross_prod(normal, refaxis, myaxis);
02979   if (dot_prod(hub, normal) < 0) alpha = -alpha;
02980 
02981   // Idealize the axis by rotating the reference axis
02982   // by the ideal angle around the hub.
02983   Matrix4 rot;
02984   rot.rotate_axis(hub, float(DEGTORAD(alpha)));
02985   rot.multpoint3d(refaxis, idealaxis);
02986   
02987   return 1;
02988 }
02989 
02990 
02991 // Determine which atoms are unique for the given rotary axis.
02992 // Result is a modified array of flags 'uniquelist' where 1 indicates
02993 // that the corresponding atom is unique.
02994 void Symmetry::collapse_axis(const float *axis, int order,
02995                              int refatom, const int *matchlist,
02996                              int *(&connectedunique)) {
02997   int i;
02998   float refcoor[3];
02999   vec_sub(refcoor, coor+3*refatom, rcom);
03000 
03001   // Project reference coordinate on the plane defined by the rotary axis
03002   float r0[3];
03003   vec_scale(r0, dot_prod(refcoor, axis), axis);
03004   vec_sub(r0, refcoor, r0);
03005 
03006   for (i=0; i<sel->selected; i++) {
03007     if (!uniqueatoms[i] || i==matchlist[i]) continue;
03008 
03009     // The unique atoms we have at this point will be scattered
03010     // randomly over the molecule. However, we want to find a
03011     // set of unique coordinates that is connected and confined
03012     // to one segment of the rotation.
03013     // Here we loop over all rotary images of the current unique
03014     // atom (regaring the given axis) and use the image that is
03015     // within the samme rotary segment as the reference atom
03016     // as the new unique atom.
03017     int image, found=0;
03018     int k = i;
03019     for (image=0; image<order; image++) {
03020       float tmp[3];
03021       vec_sub(tmp, idealcoor+3*k, rcom);
03022       
03023       // Project coordinate on the plane defined by the rotary axis
03024       float r[3];
03025       vec_scale(r, dot_prod(tmp, axis), axis);
03026       vec_sub(r, tmp, r);
03027       
03028       // Measure angle between projected coordinates of current and
03029       // reference atom. If the atom is outside the angle range we
03030       // swap with its image.
03031       if (angle(r, r0) <= 180.0/order) {
03032         found = 1;
03033         break;
03034       }
03035       k = matchlist[k];
03036     }
03037     if (found && k!=i) {
03038       //printf("atom[%i] --> %i image=%i\n", i, k, image);
03039       uniqueatoms[i] = 0;
03040       uniqueatoms[k] = 1;
03041     }
03042   }
03043 
03044   // Find a connected set of unique atoms
03045   wrap_unconnected_unique_atoms(refatom, matchlist, connectedunique);
03046 }
03047 
03048 
03049 // Find best set of unique atoms, i.e. the set with the most
03050 // atoms connected to atom 'root'. 
03051 // Array matchlist shall contains the closest match of the
03052 // same atom type for each selected atom.
03053 void Symmetry::wrap_unconnected_unique_atoms(int root,
03054                                              const int *matchlist,
03055                                              int *(&connectedunique)) {
03056   int i, k=0;
03057   int numswapped = 0;
03058 
03059   // The first time we call this function we need to construct
03060   // an initial set of unique connected atoms connected to the
03061   // first atom.
03062   if (!connectedunique) {
03063     connectedunique = new int[sel->selected];
03064   }
03065   find_connected_unique_atoms(connectedunique, root);
03066   
03067   // Repeatedly loop through the list of unconnected unique atoms
03068   // and try to swap them with images that are connected of that
03069   // are directly bonded to connected ones.
03070   do {
03071     numswapped = 0;
03072     for (i=0; i<sel->selected; i++) {
03073       
03074       if (!uniqueatoms[i] || connectedunique[i]) continue;
03075 
03076       int swap = 0;
03077       int image = 0;
03078       int j = matchlist[i];
03079       //printf("uniqueatoms[%d] = %d matching %d:\n", i, uniqueatoms[i], j);
03080       while (j!=i && image<sel->selected) {
03081         //printf("i=%d, j=%d\n", i, j);
03082         if (connectedunique[j]) {
03083           // If the image is already in the bondtree we can just swap
03084           swap = 1;
03085         } else {
03086           // See if one of the atoms directly bonded to the image
03087           // is in the bondtree. If yes, we can swap.
03088           int k;
03089           for (k=0; k<bondsperatom[j].numbonds; k++) {
03090             int bondto = bondsperatom[j].bondto[k];
03091             if (connectedunique[bondto]) swap = 1;
03092           }
03093         }
03094         if (swap) break;
03095         
03096         j = matchlist[j];
03097 
03098         image++;
03099       }
03100       
03101       if (swap) {
03102         //printf("nonbonded unique atom %i --> %i (image %i), connected=%i\n", i, j, image, connectedunique[j]);
03103         uniqueatoms[i] = 0;
03104         uniqueatoms[j] = 1;
03105         connectedunique[j] = 1;
03106         numswapped++;
03107       }
03108     }
03109 
03110     if (k>=sel->selected) {
03111       msgErr << "Stop looping unconnected unique atoms" << sendmsg;
03112       break;
03113     }
03114     k++;
03115 
03116   } while (numswapped);
03117 
03118 }
03119 
03120 
03121 // Find all atoms currently marked unique that are within a bond
03122 // tree of unique atoms rooted at 'root'. As a result the array
03123 // 'connectedunique' is populated with flags.
03124 void Symmetry::find_connected_unique_atoms(int *(&connectedunique),
03125                                            int root) {
03126   ResizeArray<int> leaves;
03127   ResizeArray<int> newleaves;
03128   leaves.append(root);
03129 
03130   memset(connectedunique, 0, sel->selected*sizeof(int));
03131   connectedunique[root] = 1;
03132 
03133   int numbonded = 1;
03134   int i;
03135 
03136   do {
03137     newleaves.clear();
03138 
03139     // Loop over all endpoints of the tree
03140     for (i=0; i<leaves.num(); i++) {
03141       int j = leaves[i];
03142       
03143       int k;
03144       for (k=0; k<bondsperatom[j].numbonds; k++) {
03145         int bondto = bondsperatom[j].bondto[k];
03146 
03147         if (uniqueatoms[bondto]&& !connectedunique[bondto]) {
03148           connectedunique[bondto] = 1;
03149           newleaves.append(bondto);
03150           numbonded++;
03151         }
03152       }
03153 
03154     }
03155 
03156     leaves.clear();
03157     for (i=0; i<newleaves.num(); i++) {
03158       leaves.append(newleaves[i]);
03159     }
03160 
03161   } while (newleaves.num());
03162 
03163   //   for (i=0; i<sel->selected; i++) {
03164   //     printf("connectedunique[%i] = %i, unique = %i\n", i, connectedunique[i], uniqueatoms[i]);
03165   //   }
03166 
03167 }
03168 
03169 // Determine the unique coordinates for the whole system.
03170 // Result is a modified array of flags 'uniquelist' where 1
03171 // indicates that the corresponding atom is unique.
03172 // We begin assuming all atoms are unique and go through all
03173 // symmetry elements subsequently flagging all atoms that can
03174 // be obtained by the according symmetry operations as 
03175 // not unique.
03176 void Symmetry::unique_coordinates() {
03177   int i;
03178   for(i=0; i<sel->selected; i++) {
03179     uniqueatoms[i] = 1;
03180   }
03181 
03182   // We use the first atom as starting point for searching 
03183   // connected unique atoms. In case this atom is at the
03184   // center of mass it will always have itself as image and
03185   // we better use the second atom.
03186   float refcoor[3];
03187   int refatom = 0;
03188   vec_sub(refcoor, coor, rcom);
03189   if (norm(refcoor)<0.1) {
03190     refatom = 1;
03191     vec_sub(refcoor, coor+3*refatom, rcom);
03192   }
03193 
03194   int *connectedunique = NULL;
03195 
03196   // -- Inversion --
03197   if (inversion) {
03198     Matrix4 inv;
03199     inv.translate(rcom[0], rcom[1], rcom[2]);
03200     inv.scale(-1.0);
03201     inv.translate(-rcom[0], -rcom[1], -rcom[2]);
03202 
03203     // Flag equivalent atoms
03204     int *matchlist = unique_coordinates(&inv);
03205 
03206     int j;
03207     for(j=0; j<sel->selected; j++) {
03208       if (!uniqueatoms[j] || j==matchlist[j]) continue;
03209       uniqueatoms[j] = 0;
03210       uniqueatoms[matchlist[j]] = 1;
03211     }
03212 
03213     wrap_unconnected_unique_atoms(refatom, matchlist, connectedunique);
03214 
03215     if (matchlist) delete [] matchlist;
03216   }
03217 
03218   // -- Rotary axes --
03219   for (i=0; i<numaxes(); i++) {
03220     Matrix4 rot;
03221     rot.translate(rcom[0], rcom[1], rcom[2]);
03222     rot.rotate_axis(axes[i].v, float(VMD_TWOPI)/axes[i].order);
03223     rot.translate(-rcom[0], -rcom[1], -rcom[2]);
03224     
03225     // Flag equivalent atoms
03226     int *matchlist = unique_coordinates(&rot);
03227     
03228     // Turn the unique list into a list of unique, connected
03229     // atoms that are within one segment of the rotation
03230     // (e.g within 90 degree for a 4th order axes).
03231     collapse_axis(axes[i].v, axes[i].order, refatom, matchlist,
03232                   connectedunique);
03233     
03234     if (matchlist) delete [] matchlist;
03235   }
03236 
03237   // -- Planes --
03238   for (i=0; i<numplanes(); i++) {
03239     Matrix4 mirror;
03240     mirror.translate(rcom[0], rcom[1], rcom[2]);
03241     mirror.scale(-1.0);  // inversion
03242     mirror.rotate_axis(planes[i].v, float(VMD_PI));
03243     mirror.translate(-rcom[0], -rcom[1], -rcom[2]);
03244 
03245     // Flag equivalent atoms
03246     int *matchlist = unique_coordinates(&mirror);
03247 
03248     int j;
03249     for(j=0; j<sel->selected; j++) {
03250       if (!uniqueatoms[j] || j==matchlist[j]) continue;
03251       
03252       float tmp[3];
03253       vec_sub(tmp, coor+3*j, rcom);
03254       if (behind_plane(planes[i].v, tmp)!=behind_plane(planes[i].v, refcoor)) {
03255         uniqueatoms[j] = 0;
03256         uniqueatoms[matchlist[j]] = 1;
03257       }
03258     }
03259     if (matchlist) delete [] matchlist;
03260   }
03261 
03262   // -- Rotary reflections --
03263   for (i=0; i<numrotreflect(); i++) {
03264     Matrix4 rotref;
03265     rotref.translate(rcom[0], rcom[1], rcom[2]);
03266     rotref.rotate_axis(rotreflections[i].v, float(VMD_TWOPI)/rotreflections[i].order);
03267     rotref.scale(-1.0);  // inversion
03268     rotref.rotate_axis(rotreflections[i].v, float(VMD_PI));
03269     rotref.translate(-rcom[0], -rcom[1], -rcom[2]);
03270     
03271     // Flag equivalent atoms
03272     int *matchlist = unique_coordinates(&rotref);
03273     
03274     collapse_axis(rotreflections[i].v, rotreflections[i].order, refatom, matchlist, connectedunique);
03275 
03276     if (matchlist) delete [] matchlist;
03277   }
03278 
03279   if (connectedunique) delete [] connectedunique;
03280 }
03281 
03282 
03283 // Goes through the list of atoms matching with original
03284 // ones after a transformation has been applied and assigns
03285 // a zero to the array of unique atom flags if the matching
03286 // index is greater than the original one.
03287 int* Symmetry::unique_coordinates(Matrix4 *trans) {
03288   int nmatches;
03289   int *matchlist = NULL;
03290 
03291   // For each atom find the closest transformed image atom with
03292   // the same chemical element. The array matchlist will contain
03293   // the closest match atom index (into the collapsed list of
03294   // selected atoms) for each selected atom.
03295   identify_transformed_atoms(trans, nmatches, matchlist);
03296   
03297   int j;
03298   for(j=0; j<sel->selected; j++) {
03299     if (!uniqueatoms[j]) continue;
03300     int m = matchlist[j];
03301     
03302     if (m<0) continue; // no match found;
03303     
03304     if (m>j) uniqueatoms[m] = 0;
03305   }
03306   
03307   return matchlist;
03308 }
03309 
03310 
03312 void Symmetry::determine_pointgroup() {
03313   if (linear) {
03314     if (inversion) pointgroup = POINTGROUP_DINFH;
03315     else           pointgroup = POINTGROUP_CINFV;
03316   
03317     pointgrouporder = -1;
03318   }
03319 
03320   else if (sphericaltop && maxaxisorder>=3 && axes[0].order>=2) {
03321     if (maxaxisorder==3) {
03322       if (numplanes()) {
03323         if (inversion) pointgroup = POINTGROUP_TH;
03324         else pointgroup = POINTGROUP_TD;
03325       }
03326       else pointgroup = POINTGROUP_T;
03327     }
03328     else if (maxaxisorder==4) {
03329       if (numplanes() || inversion) pointgroup = POINTGROUP_OH;
03330       else                          pointgroup = POINTGROUP_O;
03331     }
03332     else if (maxaxisorder==5) {
03333       if (numplanes() || inversion) pointgroup = POINTGROUP_IH;
03334       else                          pointgroup = POINTGROUP_I;
03335 
03336     }
03337     else pointgroup = POINTGROUP_UNKNOWN;
03338 
03339   }
03340 
03341   else if (numaxes()) {
03342     // If n is the higest Cn axis order, are there n C2 axes
03343     // perpendicular to Cn?
03344     int i;
03345     int perpC2 = 0;
03346     for (i=0; i<numaxes(); i++) {
03347       if (axes[i].order==2 && (axes[i].type & PERPENDICULAR_AXIS)) {
03348         perpC2++;
03349       }
03350     }
03351 
03352     if (perpC2==maxaxisorder) {
03353       if (horizontalplane>=0) pointgroup = POINTGROUP_DNH;
03354       else {
03355         // Are there n dihedral mirror planes Sd bisecting the angles
03356         // formed by pairs of C2 axes?
03357         if (numdihedralplanes==maxaxisorder) {
03358           pointgroup = POINTGROUP_DND;
03359         }
03360         else {
03361           pointgroup = POINTGROUP_DN;
03362         }
03363       }
03364 
03365       pointgrouporder = maxaxisorder;
03366     }
03367 
03368     else {
03369       if (horizontalplane>=0) pointgroup = POINTGROUP_CNH;
03370       else {
03371         // Are there n planes vertical to the highest Cn axis?
03372         if (numverticalplanes==maxaxisorder) {
03373           pointgroup = POINTGROUP_CNV;
03374         }
03375         else {
03376           if (numS2n()) {
03377             pointgroup = POINTGROUP_S2N;
03378           }
03379           else {
03380             pointgroup = POINTGROUP_CN;
03381           }
03382         }
03383       }
03384       pointgrouporder = maxaxisorder;
03385 
03386     }
03387   }
03388 
03389   else { // numaxes==0
03390     if (numplanes()==1) pointgroup = POINTGROUP_CS;
03391     else {
03392       if (inversion) pointgroup = POINTGROUP_CI;
03393       else           pointgroup = POINTGROUP_C1;
03394     }
03395   }
03396 }
03397 
03398 
03399 // Determine level in the pointgroup hierarchy
03400 // As far as I understand only crystallographic point
03401 // groups can be ranked in hierarchy, but we can use
03402 // heuristics to rank the others (e.g. I, Ih, C5, ...)
03403 int Symmetry::pointgroup_rank(int pg, int order) {
03404   if (pg==POINTGROUP_C1) return 1;
03405   if (pg==POINTGROUP_CS || pg==POINTGROUP_CI) return 2;
03406   if (pg==POINTGROUP_CN) {
03407     return 1+numprimefactors(order);
03408   }
03409   if (pg==POINTGROUP_S2N) {
03410     return 1+numprimefactors(order*2);
03411   }
03412   if (pg==POINTGROUP_DN || pg==POINTGROUP_CNV ||
03413       pg==POINTGROUP_CNH) {
03414     return 2+numprimefactors(order);
03415   }
03416   if (pg==POINTGROUP_DND || pg==POINTGROUP_DNH) {
03417     return 3+numprimefactors(order);
03418   }
03419   if (pg==POINTGROUP_CINFV) return 3;
03420   if (pg==POINTGROUP_DINFH || pg==POINTGROUP_T) return 4;
03421   if (pg==POINTGROUP_TD    || pg==POINTGROUP_TH ||
03422       pg==POINTGROUP_O) {
03423     return 5; 
03424   }
03425   if (pg==POINTGROUP_OH || pg==POINTGROUP_I) return 6;
03426   if (pg==POINTGROUP_IH) return 7;
03427   return 0;
03428 }
03429 
03430 
03431 // Generate transformation matrix that orients the molecule
03432 // according to the GAMESS 'master frame'.
03433 //
03434 // From the GAMESS documentation:
03435 // ------------------------------
03436 // The 'master frame' is just a standard orientation for                       
03437 // the molecule.  By default, the 'master frame' assumes that                      
03438 //    1.   z is the principal rotation axis (if any),                             
03439 //    2.   x is a perpendicular two-fold axis (if any),                           
03440 //    3.  xz is the sigma-v plane (if any), and                                   
03441 //    4.  xy is the sigma-h plane (if any).                                       
03442 // Use the lowest number rule that applies to your molecule.
03443 //                                                                               
03444 //         Some examples of these rules:                                           
03445 // Ammonia (C3v): the unique H lies in the XZ plane (R1,R3).                       
03446 // Ethane (D3d): the unique H lies in the YZ plane (R1,R2).                        
03447 // Methane (Td): the H lies in the XYZ direction (R2).  Since                      
03448 //          there is more than one 3-fold, R1 does not apply.                      
03449 // HP=O (Cs): the mirror plane is the XY plane (R4).                               
03450 //                                                                                
03451 // In general, it is a poor idea to try to reorient the                            
03452 // molecule.  Certain sections of the program, such as the                         
03453 // orbital symmetry assignment, do not know how to deal with                       
03454 // cases where the 'master frame' has been changed.                                
03455 //                                                                                
03456 // Linear molecules (C4v or D4h) must lie along the z axis,                        
03457 // so do not try to reorient linear molecules.                            
03458 
03459 // Note:
03460 // With perpendicular two-fold axis they seem to mean any C2 axis
03461 // that is not collinear with the principal axis. It does not
03462 // have to be orthogonal to the principal axis. Otherwise the methane
03463 // example would not work.
03464 
03465 // We must use the first or the first two rules that apply. 
03466 // This is always sufficient for proper orientation.
03467 // If more that two rules apply we must ignore the additional
03468 // ones, otherwise the orientation will be messed up.
03469 
03470 void Symmetry::orient_molecule() {
03471   if (pointgroup==POINTGROUP_C1) return;
03472 
03473   //msgInfo << "Creating standard orientation:" << sendmsg;
03474 
03475   // Special case: linear molecules
03476   if (linear) {
03477     // Bring X along Z
03478     orient.transvec(0, 0, 1);
03479     // Bring axis along X
03480     orient.transvecinv(axes[0].v[0], axes[0].v[1], axes[0].v[2]);
03481 
03482     orient.translate(-rcom[0], -rcom[1], -rcom[2]);
03483     return;
03484   } 
03485 
03486   int i;
03487   Matrix4 rot;
03488 
03489   // GAMESS rule #1:
03490   // z is the principal rotation axis 
03491   // (x is principal axis of inertia with smallest eigenvalue)
03492 
03493   if (!sphericaltop && numaxes()) {
03494     //msgInfo << "  Applying rule 1" << sendmsg;
03495 
03496     // Bring X along Z
03497     rot.transvec(0, 0, 1);
03498     // Bring first axis along X
03499     rot.transvecinv(axes[0].v[0], axes[0].v[1], axes[0].v[2]);
03500 
03501     // Find an axis of inertia that is orthogonal to the primary axis
03502     int j;
03503     for (j=0; j<=2; j++) {
03504       if (orthogonal(axes[0].v, inertiaaxes[j], orthogtol)) break;
03505     }
03506     float *ortho_inertiaaxis = inertiaaxes[j];
03507 
03508     // Apply same rotation to selected orthogonal axis of inerta
03509     // and then get transform m to bring it along X.
03510     Matrix4 m;
03511     float tmp[3];
03512     rot.multpoint3d(ortho_inertiaaxis, tmp);
03513     m.transvecinv(tmp[0], tmp[1], tmp[2]);
03514 
03515     // next 2 lines: postmultiply: m*rot 
03516     m.multmatrix(rot);
03517     rot.loadmatrix(m);
03518   }
03519 
03520   // GAMESS rule #2:
03521   // x is a 'perpendicular' (=noncollinear) two-fold axis (if any)
03522   int orthC2 = -1;
03523   for (i=1; i<numaxes(); i++) {
03524     if (axes[i].order==2 && 
03525         (orthogonal(axes[i].v, axes[0].v, orthogtol) ||
03526          (pointgroup>=POINTGROUP_T && pointgroup<=POINTGROUP_IH))) {
03527       orthC2 = i;
03528       break;
03529     }
03530   }
03531   if (orthC2>=0) {
03532     //msgInfo << "  Applying rule 2\n" << sendmsg;
03533 
03534     // Bring orth C2 axis along X
03535     float tmp[3];
03536     rot.multpoint3d(axes[orthC2].v, tmp);
03537     Matrix4 m;
03538     m.transvecinv(tmp[0], tmp[1], tmp[2]);
03539 
03540     // next 2 lines: postmultiply: m*rot 
03541     m.multmatrix(rot);
03542     rot.loadmatrix(m);
03543   }
03544 
03545   // GAMESS rule #3:
03546   // xz is the sigma-v plane (if any)
03547   if (numverticalplanes && orthC2<0) {
03548     for (i=0; i<numplanes(); i++) {
03549       if (planes[i].type==VERTICALPLANE) break;
03550     }
03551     //msgInfo << "Applying rule 3\n" << sendmsg;
03552 
03553     Matrix4 m;
03554 
03555     // Bring X along Y
03556     float Y[]={0, 1, 0};
03557     m.transvec(Y[0], Y[1], Y[2]);
03558 
03559     // Bring plane normal along X    
03560     float tmp[3];
03561     rot.multpoint3d(planes[i].v, tmp);
03562     m.transvecinv(tmp[0], tmp[1], tmp[2]);
03563 
03564     // next 2 lines: postmultiply: m*rot 
03565     m.multmatrix(rot);
03566     rot.loadmatrix(m);
03567   }
03568 
03569   // GAMESS rule #4:
03570   // xy is the sigma-h plane (if any)
03571   // If the pointgroup is Cs then treat the only plane as horizontal
03572   if ((horizontalplane>=0 && !numverticalplanes && orthC2<0) ||
03573       pointgroup==POINTGROUP_CS) {
03574     //msgInfo << "Applying rule 4\n" << sendmsg;
03575     if (pointgroup==POINTGROUP_CS) i = 0;
03576     else   i = horizontalplane;
03577 
03578     Matrix4 m;
03579     float Z[]={0, 0, 1};
03580 
03581     // Bring X along Z
03582     m.transvec(Z[0], Z[1], Z[2]);
03583 
03584     // Bring plane normal along X    
03585     float tmp[3];
03586     rot.multpoint3d(planes[i].v, tmp);
03587     m.transvecinv(tmp[0], tmp[1], tmp[2]);
03588 
03589     // next 2 lines: postmultiply: m*rot 
03590     m.multmatrix(rot);
03591     rot.loadmatrix(m);
03592   }
03593 
03594   if (pointgroup>=POINTGROUP_T && pointgroup<=POINTGROUP_IH) {
03595     //msgInfo << "Applying rule 5\n" << sendmsg;
03596 
03597     // Find a principal axis with a unique atom
03598     int found = 0;
03599     float uniqueaxis[3];
03600     for (i=1; i<numaxes(); i++) {
03601       if (axes[i].order<maxaxisorder) break;
03602       int j;
03603       for(j=0; j<sel->selected; j++) {
03604         if (!uniqueatoms[j]) continue;
03605 
03606         float tmpcoor[3];
03607         vec_sub(tmpcoor, coor+3*j, rcom);
03608         if (norm(tmpcoor)<0.1) continue;
03609 
03610         if (collinear(axes[i].v, tmpcoor, collintol)) {
03611           if (dot_prod(axes[i].v, tmpcoor)>0)
03612             vec_copy(uniqueaxis, axes[i].v);
03613           else
03614             vec_negate(uniqueaxis, axes[i].v);
03615           found = 1;
03616         }
03617       }
03618     }
03619 
03620     if (!found) 
03621       msgErr << "orient_molecule(): Couldn't find axis with unique atom!" << sendmsg;
03622 
03623     Matrix4 m;
03624     float XYZ[]={1, 1, 1};
03625 
03626     // Bring X along XYZ
03627     m.transvec(XYZ[0], XYZ[1], XYZ[2]);
03628 
03629     // Bring unique axis along X
03630     float tmp[3];
03631     rot.multpoint3d(uniqueaxis, tmp);
03632     m.transvecinv(tmp[0], tmp[1], tmp[2]);
03633 
03634     // next 2 lines: postmultiply: m*rot 
03635     m.multmatrix(rot);
03636     rot.loadmatrix(m);
03637   }
03638 
03639   orient.multmatrix(rot);
03640   orient.translate(-rcom[0], -rcom[1], -rcom[2]);
03641 }
03642 
03643 
03646 void Symmetry::print_statistics() {
03647   int i;
03648 
03649 #if 0
03650   for (i=0; i<numplanes(); i++) {
03651     printf("plane[%i]: weight=%3i, overlap=%.2f\n", i, planes[i].weight, planes[i].overlap);
03652   }
03653 
03654   for (i=0; i<numaxes(); i++) {
03655     printf("axis[%i]: order=%i, weight=%3i, overlap=%.2f {%.2f %.2f %.2f}\n", i, axes[i].order, axes[i].weight, axes[i].overlap, axes[i].v[0], axes[i].v[1], axes[i].v[2]);
03656   }
03657 
03658   for (i=0; i<numrotreflect(); i++) {
03659     printf("rotrefl[%i]: order=%i, weight=%3i, overlap=%.2f {%.2f %.2f %.2f}\n", i, rotreflections[i].order, rotreflections[i].weight, rotreflections[i].overlap, rotreflections[i].v[0], rotreflections[i].v[1], rotreflections[i].v[2]);
03660   }
03661 #endif
03662 
03663   char buf [256];
03664   msgInfo << sendmsg;
03665   msgInfo << "Summary of symmetry elements:" << sendmsg;
03666   msgInfo << "+===============================+" << sendmsg;
03667   msgInfo << "| inversion center:         " << (inversion ? "yes" : "no ") 
03668           << " |" << sendmsg;
03669 
03670   if (planes.num()) {
03671     int havehorizplane = 0;
03672     msgInfo << "|                               |" << sendmsg;
03673     if (horizontalplane>=0) {
03674       msgInfo << "| horizonal planes:          1  |" << sendmsg;
03675       havehorizplane = 1;
03676     }
03677     if (numverticalplanes) {
03678       sprintf(buf, "|  vertical planes:         %2d  |", numverticalplanes);
03679       msgInfo << buf << sendmsg;
03680     }
03681     if (numdihedralplanes) {
03682       sprintf(buf, "|  (%-2d of them dihedral)        |", numdihedralplanes);
03683       msgInfo << buf << sendmsg;
03684     }
03685     int numregplanes = numplanes()-numverticalplanes-havehorizplane;
03686     if (numregplanes) {
03687       sprintf(buf, "|   regular planes:         %2d  |", numregplanes);
03688       msgInfo << buf << sendmsg;
03689     }
03690     if (numplanes()>1) {
03691       msgInfo << "| ----------------------------- |" << sendmsg;
03692       sprintf(buf, "|     total planes:         %2d  |", numplanes());
03693       msgInfo << buf << sendmsg;
03694     }
03695   }
03696 
03697   if (axes.num()) {
03698     msgInfo << "|                               |" << sendmsg;
03699     if (elementsummary.Cinf) {
03700       sprintf(buf, "| Cinf  rotation axes:      %2d  |", (int)elementsummary.Cinf);
03701       msgInfo << buf << sendmsg;
03702     }
03703     for (i=maxaxisorder; i>=1; i--) {
03704       if (elementsummary.C[i]) {
03705         sprintf(buf, "| C%-4i rotation axes:      %2d  |", i, elementsummary.C[i]);
03706         msgInfo << buf << sendmsg;
03707       }
03708     }
03709     if (numaxes()>1) {
03710       msgInfo << "| ----------------------------- |" << sendmsg;
03711       sprintf(buf, "| total rotation axes:      %2d  |", numaxes());
03712       msgInfo << buf << sendmsg;
03713     }
03714   }
03715     
03716   if (rotreflections.num()) {
03717     msgInfo << "|                               |" << sendmsg;
03718     for (i=maxrotreflorder; i>=3; i--) {
03719       if (elementsummary.S[i-3]) {
03720         sprintf(buf, "| S%-4i rotary reflections: %2d  |", i, elementsummary.S[i-3]);
03721         msgInfo << buf << sendmsg;
03722       }
03723     }
03724     if (rotreflections.num()>1) {
03725       msgInfo << "| ----------------------------- |" << sendmsg;
03726       sprintf(buf, "| total rotary reflections: %2d  |", rotreflections.num());
03727       msgInfo << buf << sendmsg;
03728     }
03729   }
03730   msgInfo << "+===============================+" << sendmsg;
03731 
03732   msgInfo << sendmsg;
03733   msgInfo << "Element summary: " << elementsummarystring << sendmsg;
03734 }
03735 
03736 
03737 // Build a summary matrix of found symmetry elements
03738 void Symmetry::build_element_summary() {
03739   memset(&elementsummary, 0, sizeof(ElementSummary));
03740 
03741   elementsummary.inv   = inversion;
03742   elementsummary.sigma = planes.num();
03743 
03744   int i;
03745   for (i=0; i<numaxes(); i++) {
03746     if (axes[i].order==INFINITE_ORDER) {
03747       elementsummary.Cinf++;
03748     } else if (axes[i].order<=MAXORDERCN) {
03749       int j;
03750       for (j=2; j<=axes[i].order; j++) {
03751         if (axes[i].order % j == 0) {
03752           (elementsummary.C[j])++;
03753         }
03754       }
03755     }
03756   }
03757 
03758   for (i=0; i<numrotreflect(); i++) {
03759     if (rotreflections[i].order<=2*MAXORDERCN) {
03760       (elementsummary.S[rotreflections[i].order-3])++;
03761     }
03762   }
03763 }
03764 
03765 
03766 // Create human-readable string summarizing symmetry elements
03767 // given in summary.
03768 void Symmetry::build_element_summary_string(ElementSummary summary, char *(&sestring)) {
03769   int i ;
03770   if (sestring) delete [] sestring;
03771   sestring = new char[2 + 10*(MAXORDERCN+2*MAXORDERCN+summary.Cinf
03772                               +(summary.sigma?1:0)+summary.inv)];
03773   char buf[100] ;
03774   sestring[0] = '\0';
03775 
03776   if (inversion) strcat(sestring, "(inv) ");
03777 
03778   if (summary.sigma==1) strcat(sestring, "(sigma) ");
03779   if (summary.sigma>1) {
03780     sprintf(buf, "%d*(sigma) ", summary.sigma);
03781     strcat(sestring, buf);
03782   }
03783 
03784   if (summary.Cinf==1)
03785     strcat(sestring, "(Cinf) ");
03786   else if (summary.Cinf>1) {
03787     sprintf(buf, "%d*(Cinf) ", summary.Cinf);
03788     strcat(sestring, buf);
03789   }
03790   
03791   for (i=MAXORDERCN; i>=2; i--) {
03792     if (summary.C[i]==1) {
03793       sprintf(buf, "(C%d) ", i);
03794       strcat(sestring, buf);
03795     }
03796     else if (summary.C[i]>1) {
03797       sprintf(buf, "%d*(C%d) ", summary.C[i], i);
03798       strcat(sestring, buf);
03799     }
03800   }
03801   
03802   for (i=2*MAXORDERCN; i>=3; i--) {
03803     if (summary.S[i-3]==1) {
03804       sprintf(buf, "(S%d) ", i);
03805       strcat(sestring, buf);
03806     }
03807     else if (summary.S[i-3]>1) {
03808       sprintf(buf, "%d*(S%d) ", summary.S[i-3], i);
03809       strcat(sestring, buf);
03810     }
03811   }
03812 }
03813 
03814 
03815 // For the given point group name compare the ideal numbers of 
03816 // symmetry elements with the found ones and determine which
03817 // elements are missing and which were found in addition to the
03818 // ideal ones.
03819 void Symmetry::compare_element_summary(const char *pointgroupname) {
03820   missingelementstring[0]    = '\0';
03821   additionalelementstring[0] = '\0';
03822 
03823   if (!strcmp(pointgroupname, "Unknown")) return;
03824 
03825   unsigned int i;
03826   for (i=0; i<NUMPOINTGROUPS; i++) {
03827     if (!strcmp(pointgroupname, pgdefinitions[i].name)) {
03828 
03829       if      (elementsummary.inv<pgdefinitions[i].summary.inv) 
03830         strcat(missingelementstring, "(inv) ");
03831       else if (elementsummary.inv>pgdefinitions[i].summary.inv) 
03832         strcat(additionalelementstring, "(inv) ");
03833 
03834       char buf[100];
03835       if    (elementsummary.sigma<pgdefinitions[i].summary.sigma) {
03836         sprintf(buf, "%i*(sigma) ",
03837                 pgdefinitions[i].summary.sigma-elementsummary.sigma);
03838         strcat(missingelementstring, buf);
03839       }
03840       else if (elementsummary.sigma>pgdefinitions[i].summary.sigma) {
03841         sprintf(buf, "%i*(sigma) ",
03842                 elementsummary.sigma-pgdefinitions[i].summary.sigma);
03843         strcat(additionalelementstring, buf);
03844       }
03845 
03846       int j;
03847       for (j=MAXORDERCN; j>=2; j--) {
03848         if (elementsummary.C[j]<pgdefinitions[i].summary.C[j]) {
03849           sprintf(buf, "%i*(C%i) ", pgdefinitions[i].summary.C[j]-elementsummary.C[j], j);
03850           strcat(missingelementstring, buf);
03851         }
03852         if (elementsummary.C[j]>pgdefinitions[i].summary.C[j]) {
03853           sprintf(buf, "%i*(C%i) ", elementsummary.C[j]-pgdefinitions[i].summary.C[j], j);
03854           strcat(additionalelementstring, buf);
03855         }
03856       }
03857 
03858       for (j=2*MAXORDERCN; j>=3; j--) {
03859         if (elementsummary.S[j-3]<pgdefinitions[i].summary.S[j-3]) {
03860           sprintf(buf, "%i*(S%i) ",
03861                   pgdefinitions[i].summary.S[j-3]-elementsummary.S[j-3], j);
03862           strcat(missingelementstring, buf);
03863         }
03864         if (elementsummary.S[j-3]>pgdefinitions[i].summary.S[j-3]) {
03865           sprintf(buf, "%i*(S%i) ",
03866                   elementsummary.S[j-3]-pgdefinitions[i].summary.S[j-3], j);
03867           strcat(additionalelementstring, buf);
03868         }
03869       }
03870       break;
03871     }
03872   }
03873 }
03874 
03875 
03876 void Symmetry::print_element_summary(const char *pointgroupname) {
03877   int i, found = 0;
03878   for (i=0; i<(int)NUMPOINTGROUPS; i++) {
03879     if (!strcmp(pointgroupname, pgdefinitions[i].name)) {
03880       found = 1;
03881       break;
03882     }
03883   }
03884   if (!found) return;
03885 
03886   char *idealstring=NULL;
03887   build_element_summary_string(pgdefinitions[i].summary, idealstring);
03888 
03889   // If the found elements differ from the ideal elements
03890   // print a comparison
03891   if (strcmp(idealstring, elementsummarystring)) {
03892     char buf[256];
03893     sprintf(buf, "Ideal elements (%5s): %s\n", pgdefinitions[i].name, idealstring);
03894     msgInfo << buf << sendmsg;
03895     sprintf(buf, "Found elements (%5s): %s\n", pointgroupname, elementsummarystring);
03896     msgInfo << buf << sendmsg;
03897     if (strlen(additionalelementstring))
03898       msgInfo << "Additional elements:    " << additionalelementstring << sendmsg;
03899     if (strlen(missingelementstring))
03900       msgInfo << "Missing elements:       " << missingelementstring    << sendmsg;
03901   }
03902   delete [] idealstring;
03903 }
03904 
03905 
03906 // Draws a atom-colored spheres for each atom at the transformed
03907 // position and labels them according to the atom ID.
03908 // Use for debuggging only!
03909 void Symmetry::draw_transformed_mol(Matrix4 rot) {
03910   int i;
03911   Molecule *mol = mlist->mol_from_id(sel->molid());
03912   MoleculeGraphics *gmol = mol->moleculeGraphics();
03913   const float *pos = sel->coordinates(mlist);
03914   for (i=0; i<sel->num_atoms; i++) {
03915     switch (mol->atom(i)->atomicnumber) {
03916     case 1:
03917       gmol->use_color(8);
03918       break;
03919     case 6:
03920       gmol->use_color(10);
03921       break;
03922     case 7:
03923       gmol->use_color(0);
03924       break;
03925     case 8:
03926       gmol->use_color(1);
03927       break;
03928     default:
03929       gmol->use_color(2);
03930       break;
03931     }
03932     float p[3];
03933     rot.multpoint3d(pos+3*i, p);
03934     gmol->add_sphere(p, 2*sigma, 16);
03935     char tmp[10];
03936     sprintf(tmp, "     %i", i);
03937     gmol->add_text(p, tmp, 1, 1.0f);
03938   }
03939 }
03940 
03941 
03942 /***********  HELPER FUNCTIONS  ************/
03943 
03944 
03945 static inline bool coplanar(const float *normal1, const float *normal2, float tol) {
03946   return collinear(normal1, normal2, tol);
03947 }
03948 
03949 static inline bool collinear(const float *axis1, const float *axis2, float tol) {
03950   if (fabs(dot_prod(axis1, axis2)) > tol) return 1;
03951   return 0;
03952 }
03953 
03954 static inline bool orthogonal(const float *axis1, const float *axis2, float tol) {
03955   if (fabs(dot_prod(axis1, axis2)) < tol) return 1;
03956   return 0;
03957 }
03958 
03959 static inline bool behind_plane(const float *normal, const float *coor) {
03960   return (dot_prod(normal, coor)>0.01);
03961 }
03962 
03963 // currently unused
03964 static void align_plane_with_axis(const float *normal, const float *axis, float *alignedplane) {
03965   float inplane[3];
03966   cross_prod(inplane, axis, normal);
03967   cross_prod(alignedplane, inplane, axis);
03968   vec_normalize(alignedplane);
03969 }
03970 
03971 
03972 // Returns 1 if x is a prime number
03973 static int isprime(int x) {
03974   int i;
03975   for (i=2; i<x; i++) {
03976     if (!(x%i)) return 0;
03977   }
03978   return 1;
03979 }
03980 
03981 
03982 // Returns the number of prime factors for x
03983 static int numprimefactors(int x) {
03984   int i, numfac=0;
03985   for (i=2; i<=x; i++) {
03986     if (!isprime(i)) continue;
03987     if (!(x%i)) {
03988       x /= i;
03989       numfac++;
03990       i--;
03991     }
03992   }
03993   return numfac;
03994 }
03995 
03996 
03997 inline int Symmetry::find_collinear_axis(const float *myaxis) {
03998   int i;
03999   for (i=0; i<axes.num(); i++) {
04000     if (collinear(myaxis, axes[i].v, collintol)) {
04001       return i;
04002     }
04003   }
04004   return -1;
04005 }
04006 
04008 inline int Symmetry::plane_exists(const float *myplane) {
04009   int i;
04010   for (i=0; i<planes.num(); i++) {
04011     if (collinear(myplane, planes[i].v, collintol)) {
04012       return i;
04013     }
04014   }
04015   return -1;
04016 }
04017 
04018 
04019 // Checks if the molecule is planar with respect to the given plane normal.
04020 // Rotates the molecule so that he given normal is aligned with the x-axis
04021 // and then tests if all x coordinates are close to zero.
04022 int Symmetry::is_planar(const float *normal) {
04023   Matrix4 alignx;
04024   alignx.transvecinv(normal[0], normal[1], normal[2]);
04025   int j, k;
04026   float xmin=0.0, xmax=0.0;
04027   for (j=0; j<sel->selected; j++) {
04028     float tmpcoor[3];
04029     alignx.multpoint3d(coor+3*j, tmpcoor);
04030     xmin = tmpcoor[0];
04031     xmax = tmpcoor[0];
04032     break;
04033   }
04034   
04035   for (k=j+1; k<sel->selected; k++) {
04036     float tmpcoor[3];
04037     alignx.multpoint3d(coor+3*k, tmpcoor);
04038     if      (tmpcoor[0]<xmin) xmin = tmpcoor[0];
04039     else if (tmpcoor[0]>xmax) xmax = tmpcoor[0];
04040   }
04041   
04042   if (xmax-xmin < 1.5*sigma) return 1;
04043   
04044   return 0;
04045 }
04046 
04047 
04048 // Assign bond topology information to each atom
04049 void Symmetry::assign_bonds() {
04050   Molecule *mol = mlist->mol_from_id(sel->molid());
04051 
04052   // Assign an array of atom indexes associating local indexes
04053   // with the global ones.
04054   int i, j=0;
04055   for (i=0; i<sel->num_atoms; i++) {
04056     if (sel->on[i]) atomindex[j++] = i;
04057   }
04058 
04059   for (i=0; i<sel->selected; i++) {
04060     int j = atomindex[i];
04061 
04062     bondsperatom[i].numbonds = 0;
04063     if (bondsperatom[i].bondto) delete [] bondsperatom[i].bondto;
04064     if (bondsperatom[i].length) delete [] bondsperatom[i].length;
04065     bondsperatom[i].bondto = new int[mol->atom(j)->bonds];
04066     bondsperatom[i].length = new float[mol->atom(j)->bonds];
04067 
04068     int k;
04069     for (k=0; k<mol->atom(j)->bonds; k++) {
04070       int bondto = mol->atom(j)->bondTo[k];
04071 
04072       // Only consider bonds to atoms within the selection
04073       if (!sel->on[bondto]) continue;
04074 
04075       float order = mol->getbondorder(j, k);
04076       if (order<0.f) order = 1.f;
04077 
04078       if (bondto > j) { 
04079         // Find the local index of the bonded atom
04080         int m;
04081         int found = 0;
04082         for (m=i+1; m<sel->selected; m++) {
04083           if (atomindex[m]==bondto) { found = 1; break; }
04084         }
04085 
04086         // If the local index is not found then this means that
04087         // the bonded atom is outside the selection.
04088         if (found) {
04089           // Add a new bond to the list
04090           Bond b;
04091           b.atom0 = i; // index into collapsed atomlist
04092           b.atom1 = m; // index into collapsed atomlist
04093           b.order = order;
04094           //printf("i=%i, m=%i, j=%i, bondto=%i\n", i, m, j, bondto);
04095           b.length = distance(coor+3*i, coor+3*m);
04096           bonds.append(b);
04097         }
04098       }
04099     }
04100   }
04101 
04102   for (i=0; i<bonds.num(); i++) {
04103     if (verbose>3) {
04104       char buf[256];
04105       sprintf(buf, "Bond %d: %d--%d %.1f L=%.2f", i,
04106               atomindex[bonds[i].atom0],
04107               atomindex[bonds[i].atom1],
04108               bonds[i].order, bonds[i].length);
04109       msgInfo << buf << sendmsg;
04110     }
04111     int numbonds;
04112     numbonds = bondsperatom[bonds[i].atom0].numbonds;
04113     bondsperatom[bonds[i].atom0].bondto[numbonds] = bonds[i].atom1;
04114     bondsperatom[bonds[i].atom0].length[numbonds] = bonds[i].length;
04115     bondsperatom[bonds[i].atom0].numbonds++;
04116 
04117     numbonds = bondsperatom[bonds[i].atom1].numbonds;
04118     bondsperatom[bonds[i].atom1].bondto[numbonds] = bonds[i].atom0;
04119     bondsperatom[bonds[i].atom1].length[numbonds] = bonds[i].length;
04120     bondsperatom[bonds[i].atom1].numbonds++;
04121   }
04122 }
04123 
04124 
04125 // Build a list of vectors that are the sum of all bond directions
04126 // weighted by bondorder. This serves like a checksum for bonding
04127 // topology and orientation for each atom. Comparing the bondsum
04128 // between atoms and its transformed images can be used to purge
04129 // symmetry elements that would reorient the bonding pattern.
04130 // For example, two sp2 carbons will always have the same atom type
04131 // but the bondsum of will point in direction of the double bond, 
04132 // thus allowing to detect if the bonding pattern has changed.
04133 void Symmetry::assign_bondvectors() {
04134   Molecule *mol = mlist->mol_from_id(sel->molid());
04135   memset(bondsum, 0, 3*sel->selected*sizeof(float));
04136   int i;
04137   for (i=0; i<sel->selected; i++) {
04138     int k;
04139     for (k=0; k<bondsperatom[i].numbonds; k++) {
04140       int bondto = bondsperatom[i].bondto[k];
04141 
04142       // Only consider bonds to atoms within the selection
04143       if (!sel->on[bondto]) continue;
04144 
04145       int j = atomindex[i];
04146       float order = mol->getbondorder(j, k);
04147       if (order<0.f) order = 1.f;
04148 
04149       float bondvec[3];
04150       vec_sub(bondvec, coor+3*bondto, coor+3*i);
04151       vec_normalize(bondvec);
04152       vec_scaled_add(bondsum+3*i, order, bondvec);
04153     }
04154   }
04155 }
04156 
04157 // Copy coordinates of selected atoms into local array and assign
04158 // and atomtypes based on chemial element and topology.
04159 // Since we also use this in measure_trans_overlap() we don't make it
04160 // a class member of Symmetry:: and pass parameters instead.
04161 static void assign_atoms(AtomSel *sel, MoleculeList *mlist, float *(&mycoor), int *(&atomtype)) {
04162   // get atom coordinates
04163   const float *allcoor = sel->coordinates(mlist);
04164 
04165   Molecule *mol = mlist->mol_from_id(sel->molid());
04166 
04167   // array of strings describing the atom type
04168   char **typestringptr = new char*[sel->selected];
04169   int numtypes = 0;
04170 
04171   int i, j=0;
04172   for (i=0; i<sel->num_atoms; i++) {
04173     if (!sel->on[i]) continue;
04174 
04175     // copy coordinates of selected atoms into local array
04176     vec_copy(mycoor+3*j, allcoor+3*i);
04177 
04178 
04179     // Calculate lightest and heaviest element bonded to this atom
04180     int k;
04181     int minatomicnum = 999;
04182     int maxatomicnum = 0;
04183     for (k=0; k<mol->atom(i)->bonds; k++) {
04184       int bondto = mol->atom(i)->bondTo[k];
04185       int atomicnum = mol->atom(bondto)->atomicnumber;
04186       if (atomicnum<minatomicnum) minatomicnum = atomicnum;
04187       if (atomicnum>maxatomicnum) maxatomicnum = atomicnum;
04188     }
04189 
04190     // Build up a string describing the atom type. It is not meant
04191     // to be human-readable, so it's not nice but since the contained
04192     // properties are ordered it can be used to compare two atoms.
04193     char *typestring = new char[8+12*mol->atom(i)->bonds];
04194     typestring[0] = '\0';
04195     char buf[100];
04196 
04197     // atomic number and number of bonds
04198     sprintf(buf, "%i %i ", mol->atom(i)->atomicnumber, mol->atom(i)->bonds);
04199     strcat(typestring, buf);
04200 
04201     // For each chemical element get the number of bonds for each 
04202     // bond order. We distinguish half and integer bond orders,
04203     // e.g. 2 and 3 mean bond orders 1 and 1.5 respectively.
04204     int m;
04205     for (m=minatomicnum; m<=maxatomicnum; m++) {
04206       unsigned char bondorder[7];
04207       memset(bondorder, 0, 7*sizeof(unsigned char));
04208 
04209       unsigned char bondedatomicnum = 0;
04210       for (k=0; k<mol->atom(i)->bonds; k++) {
04211         if (m == mol->atom(mol->atom(i)->bondTo[k])->atomicnumber) {
04212           bondedatomicnum++;
04213           float bo = mol->getbondorder(i, k);
04214           if (bo<0.f) bo = 1.f;
04215           (bondorder[(int)(2*bo)])++;
04216         }
04217       }
04218       for (k=0; k<7; k++) {
04219         if (bondorder[k]) {
04220           sprintf(buf, "%i*(%i)%i ", bondorder[k], k, m);
04221           strcat(typestring, buf);
04222         }
04223       }
04224     }
04225 
04226     // Try to find this atom's type in the list, if it doesn't exist
04227     // add a new string and numerical type.     
04228     int found = 0;
04229     for (k=0; k<numtypes; k++) {
04230       if (!strcmp(typestringptr[k], typestring)) {
04231         atomtype[j] = k;
04232         found = 1;
04233         delete [] typestring;
04234         break;
04235       }
04236     }
04237     if (!found) {
04238       atomtype[j] = numtypes;
04239       typestringptr[numtypes++] = typestring;
04240     }
04241 
04242     //printf("%i: type=%i {%s}\n", j, atomtype[j], typestringptr[atomtype[j]]);
04243     j++;
04244   }
04245 
04246   for (i=0; i<numtypes; i++) {
04247     delete [] typestringptr[i];
04248   }
04249   delete [] typestringptr;
04250 }
04251 
04252 
04253 // Calculate the structural overlap of a set of points with a copy of
04254 // themselves that was transformed according to the given transformation
04255 // matrix.
04256 // Returns the normalized sum over all gaussian function values of the
04257 // pair distances between the atoms in the original and the transformed
04258 // position.
04259 // Two atoms are only considered overlapping if their atom type is
04260 // identical. Atom types must be provided as an array with an integer
04261 // for each atom.
04262 inline static float trans_overlap(int *atomtype, float *(&coor), int numcoor,
04263                                   const Matrix4 *trans, float sigma,
04264                                   bool skipident, int maxnatoms) {
04265   float overlappermatch;
04266   return trans_overlap(atomtype, coor, numcoor, trans, sigma, skipident, maxnatoms, overlappermatch);
04267 }
04268 
04269 static float trans_overlap(int *atomtype, float *(&coor), int numcoor,
04270                            const Matrix4 *trans, float sigma,
04271                            bool skipident, int maxnatoms, float &overlappermatch) {
04272   // get atom coordinates
04273   const float *posA;
04274   posA = coor;
04275 
04276   int   *flgs = new int[numcoor];
04277   float *posB = new float[3*numcoor];
04278   memset(flgs, 0, numcoor*sizeof(int));
04279 
04280   // generate transformed coordinates
04281   int i, ncompare=0;
04282   for(i=0; i<numcoor; i++) {
04283     // If we have more than maxnatoms atoms in the selection then pick 
04284     // only approximately maxnatoms random atoms  for the comparision.
04285     if (numcoor>maxnatoms && vmd_random()>maxnatoms/numcoor) continue;
04286 
04287     trans->multpoint3d(posA+3*i, posB+3*i);
04288 
04289     // Depending on the flag skip atoms that underwent an almost
04290     // identical transformation.
04291     if (!(skipident && distance(posA+3*i, posB+3*i) < sigma)) {
04292       flgs[i] = 1;
04293       ncompare++;   // # of compared atoms with dist<sigma
04294     }
04295   }
04296 
04297   if (ncompare<0.5*numcoor) {
04298     // Not enough atoms to compare
04299     delete [] flgs;
04300     delete [] posB;
04301     return 0.0;
04302   }
04303 
04304   // If the pair distance gets too small the performance is terrible
04305   // even for small systems. So we limit it to at least 4.5A
04306   float dist;
04307   //if (sigma<1.5) dist = 4.5;
04308   dist = 3*sigma;
04309   float wrongelementpenalty = 100.0f/ncompare;
04310 
04311   float overlap = 0.0;
04312   float antioverlap = 0.0;
04313   int i1, nmatches = 0, noverlap = 0, nwrongelement = 0;
04314   float maxr2=powf(1.0f*dist, 2);
04315   float itwosig2 = 1.0f/(2.0f*sigma*sigma);
04316 
04317   // Now go through all atoms and find matching pairs
04318   for (i1=0; i1<numcoor; i1++) {
04319     if (!flgs[i1]) continue;
04320 
04321     float minr2 = maxr2+1.0f;
04322     float r2 = 0.0f;
04323 
04324     // Find the nearest atom
04325     int j, i2=-1;
04326     for (j=0; j<numcoor; j++) {
04327       if (!flgs[j]) continue;
04328 
04329       r2 = distance2(posA+3*i1, posB+3*j);
04330 
04331       if (r2<minr2) { minr2 = r2; i2 = j; }
04332     }
04333 
04334     // Compute the score for the closest atom
04335     if (minr2<maxr2) {
04336       noverlap++;
04337       
04338       // consider only pairs with identical atom types
04339       if (atomtype[i1]==atomtype[i2]) {
04340         // Gaussian function of the pair distance
04341         overlap += expf(-itwosig2*minr2);
04342         nmatches++;
04343       }
04344       else {
04345         // wrong element matching 
04346         antioverlap += wrongelementpenalty*expf(-itwosig2*r2);
04347         nwrongelement++;
04348         //printf("wrong elements %i-%i (atoms %i-%i)\n", mol->atom(i1)->atomicnumber,mol->atom(i2)->atomicnumber,i1,i2);
04349       }
04350     }
04351   }
04352 
04353   float nomatchpenalty = 0.0;
04354   overlappermatch = 0.0;
04355   int numnomatch = ncompare-nmatches;
04356 
04357   // We make the penalty for unmatched atoms dependent on the
04358   // average overlap for the overlapping atoms with correct element
04359   // matching. In other words, the better the structures match in
04360   // general the higher the penalty for a nonmatching element.
04361   // This helps for instance to prevent mistaking nitrogens for
04362   // carbons in rings.
04363   // On the other hand the algorithm is, at least for larger structures,
04364   // fairly forgiving about not matching an atom at all. This helps
04365   // recognizing symmetry when one or very few atoms are astray.
04366   if (nmatches) overlappermatch = overlap/nmatches;
04367 
04368   overlap -= antioverlap;
04369 
04370   if (!(numnomatch==0)) {
04371     //nomatchpenalty = powf(overlappermatch, 5)*(1-powf(0.2f+float(numnomatch),-2));
04372     nomatchpenalty = powf(overlappermatch, 5);//*(1-powf(0.1f,4.0f)/powf(float(numnomatch)/ncompare,4.0f));
04373     //overlap *= 1- nomatchpenalty;
04374     overlap -= 8*numnomatch*nomatchpenalty;
04375   }
04376   if (overlap<0) overlap = 0.0f;
04377 
04378   overlap /= ncompare;
04379 
04380   //printf("nsel=%i, wrongelementpen=%.2f, nmatches/noverlap/ncompare=%i/%i/%i, overlap/match=%.2f, nomatchpen=%.2f, ov=%.2f\n",
04381   //numcoor, wrongelementpenalty, nmatches, noverlap, ncompare, overlappermatch, nomatchpenalty, overlap);
04382 
04383   delete [] flgs;
04384   delete [] posB;
04385 
04386   return overlap;
04387 }
04388 
04389 
04390 
04391 /*******  OTHER FUNCTIONS WITH TCL INTERFACE  *********/
04392 
04393 
04394 
04395 // Backend of the TCL interface for measure transoverlap:
04396 // Calculate the structural overlap of a selection with a copy of itself
04397 // that is transformed according to a given transformation matrix.
04398 // Returns the normalized sum over all gaussian function values of the
04399 // pair distances between atoms in the original and the transformed
04400 // selection.
04401 // Two atoms are only considered overlapping if their atom type is
04402 // identical. The atom type is determined based on the chemical element
04403 // and number and order of bonds to different elements.
04404 int measure_trans_overlap(AtomSel *sel, MoleculeList *mlist, const Matrix4 *trans,
04405                           float sigma, bool skipident, int maxnatoms, float &overlap) {
04406   if (!sel)                     return MEASURE_ERR_NOSEL;
04407   if (sel->num_atoms == 0)      return MEASURE_ERR_NOATOMS;
04408 
04409   float *coor = new float[3*sel->selected];
04410 
04411   int *atomtypes = new int[sel->selected];
04412   assign_atoms(sel, mlist, coor, atomtypes);
04413 
04414   overlap = trans_overlap(atomtypes, coor, sel->selected, trans, sigma, skipident, maxnatoms);
04415 
04416   return MEASURE_NOERR;
04417 }
04418 
04419 
04420 
04421 // Calculate the structural overlap between two point sets.
04422 // The overlap of two atoms is defined as the Gaussian of their distance.
04423 // If two atoms have identical positions the overlap is 1. The parameter
04424 // sigma controls the greediness (i.e. the width) of the overlap function.
04425 // Returns the average overlap for all atoms.
04426 int measure_pointset_overlap(const float *posA, int natomsA, int *flagsA,
04427                              const float *posB, int natomsB, int *flagsB,
04428                              float sigma, float pairdist, float &overlap) {
04429 
04430   int nsmall = natomsA<natomsB ? natomsA : natomsB;
04431   
04432   int maxpairs = -1;
04433   GridSearchPair *pairlist, *p;
04434   pairlist = vmd_gridsearch3(posA, natomsA, flagsA, posB, natomsB, flagsB, pairdist,
04435                              1, maxpairs);
04436 
04437   overlap = 0.0;
04438   int i1, i2;
04439   float dx, dy, dz, r2, itwosig2 = 1.0f/(2.0f*sigma*sigma);
04440   for (p=pairlist; p; p=p->next) {
04441     i1 = p->ind1;
04442     i2 = p->ind2;
04443     dx = posA[3*i1  ]-posB[3*i2];
04444     dy = posA[3*i1+1]-posB[3*i2+1];
04445     dz = posA[3*i1+2]-posB[3*i2+2];
04446     r2 = dx*dx + dy*dy + dz*dz;
04447     overlap += expf(-itwosig2*r2);
04448   }
04449 
04450   overlap /= nsmall;
04451 
04452   return MEASURE_NOERR;
04453 }
04454 

Generated on Sat May 26 01:48:10 2012 for VMD (current) by doxygen1.2.14 written by Dimitri van Heesch, © 1997-2002