Fixed the pbc cluster option of trjconv by switching to a slower algorithm
authorErik Lindahl <lindahl@cbr.su.se>
Sun, 30 Jan 2011 20:15:41 +0000 (21:15 +0100)
committerErik Lindahl <lindahl@cbr.su.se>
Sun, 30 Jan 2011 20:15:41 +0000 (21:15 +0100)
src/tools/gmx_trjconv.c

index 6088b8eac4b47d3e8269ee25c57e31ccd418d439..db1385302c31877293265aefe5a7922bc58b59d3 100644 (file)
 #include "viewit.h"
 #include "xvgr.h"
 #include "gmx_ana.h"
+#include "gmx_sort.h"
 
 enum { euSel,euRect, euTric, euCompact, euNR};
 
-static real calc_isquared(int nmol,rvec m_com[],rvec m_shift[],rvec clust_com)
+
+static int 
+sort_comdist2(void *thunk, const void *a, const void *b)
 {
-    real Isq=0;
-    int  i;
-    rvec m0,dx;
-
-    for(i=0; (i<nmol); i++) {
-        rvec_add(m_com[i],m_shift[i],m0);
-        rvec_sub(m0,clust_com,dx);
-        Isq += iprod(dx,dx);
+    /* Thunk should point to a real array with the distance to the cluster COM for each molecule,
+     * a/b point to integers that refer to the molecule number. 
+     */
+    real *pcomdist2 = thunk;
+    int  ia    = * (int *)a;
+    int  ib    = * (int *)b;
+    int  rc;
+    
+    if(pcomdist2[ia]<pcomdist2[ib])
+    {
+        rc=-1;
+    }
+    else if (pcomdist2[ia]>pcomdist2[ib])
+    {
+        rc=1;
     }
-    return Isq;
+    else
+    {
+        rc=0;
+    }
+    return rc;
 }
 
+
 static void calc_pbc_cluster(int ecenter,int nrefat,t_topology *top,int ePBC,
                              rvec x[],atom_id index[],
-                             rvec clust_com,matrix box)
+                             rvec clust_com,matrix box, rvec clustercenter)
 {
-    const   real tol=1e-3;
-    gmx_bool    bChanged;
-    int     m,i,j,j0,j1,jj,ai,iter,is;
-    real    fac,Isq,min_dist2;
-    rvec    dx,ddx,xtest,xrm,box_center;
-    int     nmol,nmol_cl,imol_center;
+    int     m,i,j,j0,j1,jj,ai,aj;
+    int     imin,jmin;
+    real    fac,min_dist2;
+    rvec    dx,xtest,box_center;
+    int     nmol,imol_center;
     atom_id *molind;
-    gmx_bool    *bMol,*bTmp;
-    rvec    *m_com,*m_shift,m0;
+    gmx_bool *bMol,*bTmp;
+    rvec    *m_com,*m_shift;
     t_pbc   pbc;
-
+    real    *com_dist2;
+    int     *cluster;
+    int     *added;
+    int     ncluster,nadded;
+    real    tmp_r2;
+    
     calc_box_center(ecenter,box,box_center);
 
     /* Initiate the pbc structure */
@@ -107,8 +126,10 @@ static void calc_pbc_cluster(int ecenter,int nrefat,t_topology *top,int ePBC,
     snew(bMol,nmol);
     snew(m_com,nmol);
     snew(m_shift,nmol);
+    snew(cluster,nmol);
+    snew(added,nmol);
     snew(bTmp,top->atoms.nr);
-    nmol_cl = 0;
+
     for(i=0; (i<nrefat); i++) {
         /* Mark all molecules in the index */
         ai = index[i];
@@ -136,110 +157,119 @@ static void calc_pbc_cluster(int ecenter,int nrefat,t_topology *top,int ePBC,
      */
     min_dist2   = 10*sqr(trace(box));
     imol_center = -1;
-    for(i=0; (i<nmol); i++) {
-        for(j=molind[i]; (j<molind[i+1]); j++) {
+    ncluster    = 0;
+    for(i=0; i<nmol; i++) 
+    {
+        for(j=molind[i]; j<molind[i+1]; j++) 
+        {
             if (bMol[i] && !bTmp[j])
-                gmx_fatal(FARGS,"Molecule %d marked for clustering but not atom %d",
-                          i+1,j+1);
+            {
+                gmx_fatal(FARGS,"Molecule %d marked for clustering but not atom %d in it - check your index!",i+1,j+1);
+            }
             else if (!bMol[i] && bTmp[j])
-                gmx_fatal(FARGS,"Atom %d marked for clustering but not molecule %d",
-                          j+1,i+1);
-            else if (bMol[i]) {
-                /* Compute center of geometry of molecule */
+            {
+                gmx_fatal(FARGS,"Atom %d marked for clustering but not molecule %d - this is an internal error...",j+1,i+1);
+            }
+            else if (bMol[i]) 
+            {
+                /* Make molecule whole, move 2nd and higher atom to same periodicity as 1st atom in molecule */
+                if(j>molind[i])
+                {
+                    pbc_dx(&pbc,x[j],x[molind[i]],dx);
+                    rvec_add(x[molind[i]],dx,x[j]);
+                }
+                /* Compute center of geometry of molecule - m_com[i] was zeroed when we did snew() on it! */
                 rvec_inc(m_com[i],x[j]);
             }
         }
-        if (bMol[i]) {
+        if (bMol[i]) 
+        {
             /* Normalize center of geometry */
             fac = 1.0/(molind[i+1]-molind[i]);
             for(m=0; (m<DIM); m++) 
                 m_com[i][m] *= fac;
             /* Determine which molecule is closest to the center of the box */
             pbc_dx(&pbc,box_center,m_com[i],dx);
-            if (iprod(dx,dx) < min_dist2) {
-                min_dist2   = iprod(dx,dx);
+            tmp_r2=iprod(dx,dx);
+            
+            if (tmp_r2 < min_dist2) 
+            {
+                min_dist2   = tmp_r2;
                 imol_center = i;
             }
-            nmol_cl++;
+            cluster[ncluster++]=i;
         }
     }
     sfree(bTmp);
 
-    if (nmol_cl <= 0) {
+    if (ncluster <= 0) 
+    {
         fprintf(stderr,"No molecules selected in the cluster\n");
         return;
-    } else if (imol_center == -1) {
+    }
+    else if (imol_center == -1) 
+    {
         fprintf(stderr,"No central molecules could be found\n");
         return;
     }
-
-
-    /* First calculation is incremental */
-    clear_rvec(clust_com);
-    Isq = 0;
-    for(i=m=0; (i<nmol); i++) {
-        /* Check whether this molecule is part of the cluster */
-        if (bMol[i]) {
-            if ((i > 0) && (m > 0)) {
-                /* Compute center of cluster by dividing by number of molecules */
-                svmul(1.0/m,clust_com,xrm);
-                /* Distance vector between molecular COM and cluster */
-                pbc_dx(&pbc,m_com[i],xrm,dx);
-                rvec_add(xrm,dx,xtest);
-                /* xtest is now the image of m_com[i] that is closest to clust_com */
-                rvec_inc(clust_com,xtest);
-                rvec_sub(xtest,m_com[i],m_shift[i]);
-            }
-            else {
-                rvec_inc(clust_com,m_com[i]);
-            }
-            m++;
-        }
-    }
-    assert(m == nmol_cl);
-    svmul(1/nmol_cl,clust_com,clust_com);
-    put_atom_in_box(box,clust_com);
-
-    /* Now check if any molecule is more than half the box from the COM */
-    if (box) {
-        iter = 0;
-        do {
-            bChanged = FALSE;
-            for(i=0; (i<nmol) && !bChanged; i++) {
-                if (bMol[i]) {
-                    /* Sum com and shift from com */
-                    rvec_add(m_com[i],m_shift[i],m0);
-                    pbc_dx(&pbc,m0,clust_com,dx);
-                    rvec_add(clust_com,dx,xtest);
-                    rvec_sub(xtest,m0,ddx);
-                    if (iprod(ddx,ddx) > tol) {
-                        /* Here we have used the wrong image for contributing to the COM */
-                        rvec_sub(xtest,m_com[i],m_shift[i]);
-                        for(j=0; (j<DIM); j++) 
-                            clust_com[j] += (xtest[j]-m0[j])/nmol_cl;
-                        bChanged = TRUE;
-                    }
+    
+    nadded = 0;
+    added[nadded++]   = imol_center;
+    bMol[imol_center] = FALSE;
+    
+    while (nadded<ncluster)
+    {
+        /* Find min distance between cluster molecules and those remaining to be added */
+        min_dist2   = 10*sqr(trace(box));
+        imin        = -1;
+        jmin        = -1;
+        /* Loop over added mols */
+        for(i=0;i<nadded;i++)
+        {
+            ai = added[i];
+            /* Loop over all mols */
+            for(j=0;j<ncluster;j++)
+            {
+                aj = cluster[j];
+                /* check those remaining to be added */
+                if(bMol[aj])
+                {
+                    pbc_dx(&pbc,m_com[aj],m_com[ai],dx);
+                    tmp_r2=iprod(dx,dx);
+                    if (tmp_r2 < min_dist2) 
+                    {
+                        min_dist2   = tmp_r2;
+                        imin        = ai;
+                        jmin        = aj;
+                    } 
                 }
             }
-            Isq = calc_isquared(nmol,m_com,m_shift,clust_com);
-            put_atom_in_box(box,clust_com);
-
-            if (bChanged && (iter > 0))
-                printf("COM: %8.3f  %8.3f  %8.3f  iter = %d  Isq = %8.3f\n",
-                       clust_com[XX],clust_com[YY],clust_com[ZZ],iter,Isq);
-            iter++;
-        } while (bChanged);
-    }
-    /* Now transfer the shift to all atoms in the molecule */
-    for(i=0; (i<nmol); i++) {
-        if (bMol[i]) {
-            for(j=molind[i]; (j<molind[i+1]); j++)
-                rvec_inc(x[j],m_shift[i]);
         }
+        
+        /* Add the best molecule */
+        added[nadded++]   = jmin;
+        bMol[jmin]        = FALSE;
+        /* Calculate the shift from the ai molecule */
+        pbc_dx(&pbc,m_com[jmin],m_com[imin],dx);
+        rvec_add(m_com[imin],dx,xtest);
+        rvec_sub(xtest,m_com[jmin],m_shift[jmin]);
+        rvec_inc(m_com[jmin],m_shift[jmin]);
+
+        for(j=molind[jmin]; j<molind[jmin+1]; j++) 
+        {
+            rvec_inc(x[j],m_shift[jmin]);
+        }
+        fprintf(stdout,"\rClustering iteration %d of %d...",nadded,ncluster);
+        fflush(stdout);
     }
+    
+    sfree(added);
+    sfree(cluster);
     sfree(bMol);
     sfree(m_com);
     sfree(m_shift);
+    
+    fprintf(stdout,"\n");
 }
 
 static void put_molecule_com_in_box(int unitcell_enum,int ecenter,
@@ -553,6 +583,9 @@ int gmx_trjconv(int argc,char *argv[])
         "results if you in fact have a cluster. Luckily that can be checked",
         "afterwards using a trajectory viewer. Note also that if your molecules",
         "are broken this will not work either.[BR]",
+        "The separate option [TT]-clustercenter[tt] can be used to specify an",
+        "approximate center for the cluster. This is useful e.g. if you have",
+        "two big vesicles, and you want to maintain their relative positions.[BR]",
         "[TT]* whole[tt] only makes broken molecules whole.[PAR]",
         "Option [TT]-ur[tt] sets the unit cell representation for options",
         "[TT]mol[tt], [TT]res[tt] and [TT]atom[tt] of [TT]-pbc[tt].",
@@ -637,7 +670,8 @@ int gmx_trjconv(int argc,char *argv[])
     static char  *exec_command=NULL;
     static real  dropunder=0,dropover=0;
     static gmx_bool  bRound=FALSE;
-
+    static rvec  clustercenter = {0,0,0};
+    
     t_pargs
         pa[] =
             {
@@ -665,10 +699,13 @@ int gmx_trjconv(int argc,char *argv[])
                     { "-center", FALSE, etBOOL,
                         { &bCenter }, "Center atoms in box" },
                     { "-boxcenter", FALSE, etENUM,
-                        { center_opt }, "Center for -pbc and -center" },
+                        { center_opt }, "Center for -pbc and -center" },                
                     { "-box", FALSE, etRVEC,
                         { newbox },
                         "Size for new cubic box (default: read from input)" },
+                    { "-clustercenter", FALSE, etRVEC,
+                        { clustercenter }, 
+                        "Optional starting point for pbc cluster option" },
                     { "-trans", FALSE, etRVEC,
                         { trans }, 
                         "All coordinates will be translated by trans. This "
@@ -1195,7 +1232,7 @@ int gmx_trjconv(int argc,char *argv[])
                 else if (bCluster) {
                     rvec com;
 
-                    calc_pbc_cluster(ecenter,ifit,&top,ePBC,fr.x,ind_fit,com,fr.box);
+                    calc_pbc_cluster(ecenter,ifit,&top,ePBC,fr.x,ind_fit,com,fr.box,clustercenter);
                 }
 
                 if (bPFit) {