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

VolMapCreateILS.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  * RCS INFORMATION:
00010  *
00011  *      $RCSfile: VolMapCreateILS.C,v $
00012  *      $Author: johns $        $Locker:  $             $State: Exp $
00013  *      $Revision: 1.166 $      $Date: 2011/01/19 21:55:43 $
00014  *
00015  ***************************************************************************/
00016 
00017 #include <math.h>
00018 #include <stdio.h>
00019 #include "VolMapCreate.h"
00020 #include "MoleculeList.h"
00021 #include "VolumetricData.h"
00022 #include "utilities.h"
00023 #include "WKFUtils.h"
00024 
00025 /* avoid parameter name collisions with AIX5 "hz" macro */
00026 #undef hz
00027 
00028 //#define DEBUG 1
00029 #include "VMDApp.h"
00030 #if defined(DEBUG)
00031 #include "MoleculeGraphics.h"  // needed only for debugging
00032 #endif
00033 
00034 #include "Measure.h"
00035 #include "Inform.h"
00036 
00037 #if defined(VMDCUDA)
00038 #include "CUDAKernels.h"
00039 #endif
00040 
00041 #define TIMING
00042 
00043 typedef union flint_t {
00044   float f;
00045   int i;
00046   char c[4];
00047 } flint;
00048 
00049 #define BIN_DEPTH     8  // number of slots per bin
00050 #define BIN_SLOTSIZE  4  // slot permits x, y, z, vdwtype  (4 elements)
00051 #define BIN_SIZE      (BIN_DEPTH * BIN_SLOTSIZE)  // size given in "flints"
00052 
00053 
00054 #define BIN_DEPTH  8
00055 
00056 
00057 struct AtomPosType {
00058   float x, y, z;                   // position coordinates of atom
00059   int vdwtype;                     // type index for van der Waals params
00060 };
00061 
00062 
00063 struct BinOfAtoms {
00064   AtomPosType atom[BIN_DEPTH];     // use fixed bin depth for CUDA
00065 };
00066 
00067 
00068 typedef struct ComputeOccupancyMap_t {
00069 
00070   // these are initialized by caller (pointers to existing memory allocations)
00071 
00072   float *map;                    // buffer space for occupancy map
00073   int mx, my, mz;                // map dimensions
00074   float lx, ly, lz;              // map lengths
00075   float x0, y0, z0;              // map origin
00076   float ax[3], ay[3], az[3];     // map basis vectors, aligned
00077   float alignmat[16];            // 4x4 matrix used for alignment
00078   int num_coords;                // number of atoms
00079   const float *coords;           // atom coords x/y/z, length 3*num_coords
00080 
00081   const int *vdw_type;           // type index for each atom, length num_coords
00082 
00083   const float *vdw_params;       // vdw parameters for system atoms, listed as
00084                                  // (epsilon, rmin) pairs for each type number
00085 
00086   const float *probe_vdw_params; // vdw parameters for probe atoms, listed as
00087                                  // (epsilon, rmin) pairs for each probe atom,
00088                                  // length 2*num_probes
00089 
00090   const float *conformers;       // length 3*num_probes*num_conformers,
00091                                  // with probe atoms listed consecutively
00092                                  // for each conformer
00093 
00094   int num_probes;                // number of probe atoms
00095   int num_conformers;            // number of conformers
00096 
00097   float cutoff;                  // cutoff distance
00098   float extcutoff;               // extended cutoff, includes probe radius
00099 
00100   float excl_dist;               // exclusion distance threshold
00101   float excl_energy;             // exclusion energy threshold
00102 
00103   int kstart, kstop;             // z-direction indexing for multi-threaded
00104                                  // slab decomposition of maps
00105                                  // kstart = starting index of slab
00106                                  // kstop = last index + 1
00107 
00108   // internal data (memory is allocated)
00109 
00110   float hx, hy, hz;              // derived map spacings
00111   float bx, by, bz;              // atom bin lengths
00112   float bx_1, by_1, bz_1;        // reciprocal bin lengths
00113   int mpblx, mpbly, mpblz;       // map points per bin side in x, y, z
00114   int cpu_only;                  // flag indicates that CUDA cannot be used
00115 
00116   BinOfAtoms *bin;               // padded bin of atoms
00117   BinOfAtoms *bin_zero;          // bin pointer shifted to (0,0,0)-index
00118   char *bincnt;                  // count occupied slots in each bin
00119   char *bincnt_zero;             // bincnt pointer shifted to (0,0,0)-index
00120 
00121   int nbx, nby, nbz;             // number of bins in x, y, z directions
00122   int padx, pady, padz;          // bin padding
00123 
00124   char *bin_offsets;             // bin neighborhood index offset
00125   int num_bin_offsets;           // length of tight 
00126 
00127   AtomPosType *extra;            // extra atoms that over fill bins
00128   int num_extras;                // number of extra atoms
00129 
00130   char *exclusions;              // same dimensions as map
00131 
00132   // data for CUDA goes here
00133 
00134 } ComputeOccupancyMap;
00135 
00136 
00137 #define DEFAULT_EXCL_DIST      1.f
00138 #define DEFAULT_EXCL_ENERGY   87.f
00139 
00140 #define DEFAULT_BIN_LENGTH     3.f
00141 #define MAX_BIN_VOLUME        27.f
00142 #define MIN_BIN_VOLUME         8.f
00143 
00144 
00145 // Must already have ComputeOccupancyMap initialized.
00146 // Performs geometric hashing of atoms into bins, along with all related
00147 // memory management.  Also allocates memory for map exclusions.
00148 static int ComputeOccupancyMap_setup(ComputeOccupancyMap *);
00149 
00150 // Calculate occupancy for slab of map indexed from kstart through kstop.
00151 // Starts by finding the exclusions, then continues by calculating the
00152 // occupancy for the given probe and conformers.
00153 static int ComputeOccupancyMap_calculate_slab(ComputeOccupancyMap *);
00154 
00155 // Cleanup memory allocations.
00156 static void ComputeOccupancyMap_cleanup(ComputeOccupancyMap *);
00157 
00158 
00159 // Write bin histogram into a dx map
00160 static void write_bin_histogram_map(
00161     const ComputeOccupancyMap *,
00162     const char *filename
00163     );
00164 
00165 static void atom_bin_stats(const ComputeOccupancyMap *);
00166 
00167 
00168 // XXX slow quadratic complexity algorithm for checking correctness
00169 //     for every map point, for every probe atom in all conformers,
00170 //     iterate over all atoms
00171 static void compute_allatoms(
00172     float *map,                    // return calculated occupancy map
00173     int mx, int my, int mz,        // dimensions of map
00174     float lx, float ly, float lz,  // lengths of map
00175     const float origin[3],         // origin of map
00176     const float axes[9],           // basis vectors of aligned map
00177     const float alignmat[16],      // 4x4 alignment matrix
00178     int num_coords,                // number of atoms
00179     const float *coords,           // atom coordinates, length 3*num_coords
00180     const int *vdw_type,           // vdw type numbers, length num_coords
00181     const float *vdw_params,       // scaled vdw parameters for atoms
00182     float cutoff,                  // cutoff distance
00183     const float *probe_vdw_params, // scaled vdw parameters for probe atoms
00184     int num_probe_atoms,           // number of atoms in probe
00185     int num_conformers,            // number of conformers
00186     const float *conformers,       // length 3*num_probe_atoms*num_conformers
00187     float excl_energy              // exclusion energy threshold
00188     );
00189 
00190 
00192 
00193 // Computes free energy maps from implicit ligand sampling.
00194 // I.e. it creates a map of the estimated potential of mean force
00195 // (in units of k_B*T at the specified temperature) of placing a 
00196 // weakly-interacting gas monoatomic or diatomic ligand in every 
00197 // voxel. Each voxel can be divided into a subgrid in order to obtain
00198 // a potential value for that voxel that is averaged over its subgrid 
00199 // positions. For diatomic ligands for each (sub-)gridpoint one can
00200 // (and should) also average over different random rotamers.
00201 
00202 // For the computation the trajectory frames must be aligned. One can
00203 // either manually align them beforehand or provide a selection
00204 // (AtomSel *alignsel) on which automatic alignment should be based.
00205 // If such a selection was provided then it is used to align all
00206 // trajectory frames to the first frame.
00207 // Note that the results are slightly different from what you get when
00208 // you align all frames manually prior to the computation.
00209 // The differences are quite noticable when comparing the files as
00210 // ascii texts but actually they are of numerical nature and when you
00211 // display the maps they look the same.
00212 // Suppose you want to align your trajectory to a reference frame from a
00213 // different molecule then you can specify the alignment matrix (as
00214 // returned by "measure fit") that would align the first frame to the
00215 // reference. The corresponding member variable is Matrix4 transform.
00216 
00217 VolMapCreateILS::VolMapCreateILS(VMDApp *_app,
00218                                  int mol, int firstframe, int lastframe,
00219                                  float T, float res, int nsub,
00220                                  float cut, int mask) : 
00221   app(_app), molid(mol), cutoff(cut),
00222   temperature(T), first(firstframe), last(lastframe),
00223   maskonly(mask)
00224 {
00225   compute_elec = false;
00226 
00227   num_probe_atoms = 0;
00228   num_conformers = 0;
00229   conformer_freq = 1;
00230   conformers = NULL;
00231   probe_coords = NULL;
00232   probe_vdw    = NULL;
00233   probe_charge = NULL;
00234   probe_axisorder1 = 1;
00235   probe_axisorder2 = 1;
00236   probe_tetrahedralsymm = 0;
00237   probe_effsize = 0.f;
00238   extcutoff = cutoff;
00239 
00240   max_energy = DEFAULT_EXCL_ENERGY;
00241   min_occup = expf(-max_energy);
00242 
00243   nsubsamp = 1;
00244   if (nsub>1) nsubsamp = nsub;
00245   delta = res/float(nsubsamp);
00246 
00247   alignsel = NULL;
00248   pbc = false;
00249   pbcbox = false;
00250   pbccenter[0] = pbccenter[1] = pbccenter[2] = 0.f;
00251   
00252   num_unique_types = 0;
00253   vdwparams  = NULL;
00254   atomtypes  = NULL;
00255 
00256   minmax[0] = 0.f;
00257   minmax[1] = 0.f;
00258   minmax[2] = 0.f;
00259   minmax[3] = 0.f;
00260   minmax[4] = 0.f;
00261   minmax[5] = 0.f;
00262   
00263   volmap  = NULL;
00264   volmask = NULL;
00265 }
00266 
00267 
00268 VolMapCreateILS::~VolMapCreateILS() {
00269   if (probe_coords) delete [] probe_coords;
00270   if (probe_vdw)    delete [] probe_vdw;
00271   if (probe_charge) delete [] probe_charge;
00272 
00273   if (conformers) delete [] conformers;
00274   if (vdwparams)  delete [] vdwparams;
00275   if (atomtypes)  delete [] atomtypes;
00276 
00277   if (volmap) delete volmap;
00278 
00279   //  if (volmask) delete volmask;
00280 }
00281 
00282 
00285 int VolMapCreateILS::write_map(const char *filename) {
00286   // Override the name string:
00287   // We include the number of frames used for sampling and the temperature
00288   // of the underlying MD simulation in the dataset name string.
00289   // This way we can use the VolumetricData class without adding new
00290   // non-general fields to it.
00291   char tmpstr[256];
00292   if (maskonly) {
00293     sprintf(tmpstr, "ligand pmf mask, %i frames, cutoff = %.2fA",
00294             computed_frames, cutoff);
00295   } else {
00296     sprintf(tmpstr, "ligand pmf [kT], %i frames, T = %.2f Kelvin, maxenergy = %g, cutoff = %.2fA",
00297             computed_frames, temperature, max_energy, cutoff);
00298   }
00299   volmap->set_name(tmpstr);
00300 
00301 
00302   // Add volmap to a molecule so that we can use
00303   // the plugin interface to write the dx map.
00304   if (!add_map_to_molecule()) return 0;
00305 
00306   Molecule *mol = app->moleculeList->mol_from_id(molid);
00307   FileSpec spec;
00308   spec.nvolsets = 1;        // one volumeset to write
00309   spec.setids = new int[1]; // ID of the volumeset
00310   spec.setids[0] = mol->num_volume_data()-1;
00311 
00312   if (!app->molecule_savetrajectory(molid, filename, "dx", &spec)) {
00313     msgErr << "Couldn't write dx file!" << sendmsg;
00314   }
00315 
00316   return 1;
00317 }
00318 
00319 
00320 // Add volumetric data to the molecule
00321 int VolMapCreateILS::add_map_to_molecule() {
00322   Molecule *mol = app->moleculeList->mol_from_id(molid);
00323   float origin[3], xaxis[3], yaxis[3], zaxis[3];
00324   int i;
00325   for (i=0; i<3; i++) {
00326     origin[i] = (float) volmap->origin[i];
00327     xaxis[i] = (float) volmap->xaxis[i];
00328     yaxis[i] = (float) volmap->yaxis[i];
00329     zaxis[i] = (float) volmap->zaxis[i];
00330   }
00331   float *voldata = volmap->data;
00332 
00333   int err = app->molecule_add_volumetric(molid, 
00334               volmap->name, origin, xaxis, yaxis, zaxis,
00335               volmap->xsize, volmap->ysize, volmap->zsize,
00336               voldata);
00337   if (err != 1) {
00338     msgErr << "ERROR: Adding volmap " << mol->num_volume_data()-1
00339            << " to molecule " << molid << " was unsuccessful!"
00340            << sendmsg;
00341     return 0;
00342   }
00343 
00344   // Avoid data being deleted by volmap's destructor
00345   // since it is now owned by the molecule and will be
00346   // freed by its destructor .
00347   volmap->data = NULL;
00348 
00349   msgInfo << "Added volmap " << mol->num_volume_data()-1
00350           << " to molecule " << molid << "." << sendmsg;
00351 
00352   return 1;
00353 }
00354 
00355 // Set probe coordinates,charges and VDW parameters.
00356 // Currently we only support up to 2 probe atoms but
00357 // we should change that later.
00358 void VolMapCreateILS::set_probe(int numatoms, int num_conf,
00359                                 const float *pcoords,
00360                                 const float *vdwrmin,
00361                                 const float *vdweps,
00362                                 const float *charge) {
00363   if (numatoms<=0) return;
00364   if (numatoms==1 && num_conf>0) num_conf = 0;
00365 
00366   conformer_freq  = num_conf;
00367   num_probe_atoms = numatoms;
00368   probe_coords = new float[3*numatoms];
00369   probe_vdw    = new float[2*numatoms];
00370   probe_charge = new float[numatoms];
00371 
00372   // Thermopdynamic beta = 1/kT factor (k = Boltzmann const.)
00373   // 1/k [kcal/mol] = 1.0/(1.38066*6.022/4184) = 503.2206
00374   const float beta = 503.2206f/temperature;
00375 
00376   // The combination rules for VDW parameters of
00377   // two atoms are:
00378   //
00379   // eps(i,j)  = sqrt(eps(i) * eps(j))
00380   // rmin(i,j) = rmin(i)/2 + rmin(j)/2
00381   //
00382   // where rmin(i,j) is the distance of the two atoms at the
00383   // energy minimum. The parameters are provided from the
00384   // user interface in form the contribution of each atom
00385   // rmin(i)/2 (which can be interpreted as the VDW radius).
00386 
00387   // We take the sqrt(eps) here already so that during
00388   // the computation we merely have to multiply the two
00389   // parameters.
00390   // We are computing the occupancy
00391   //   rho = sum_i exp(-1/kT * U[i]) 
00392   //     U = eps*[(rmin/r)^12 - 2*(rmin/r)^6]
00393   // We include beta = 1/kT into the probe eps parameter
00394   // for speed.
00395   int i;
00396   for (i=0; i<num_probe_atoms; i++) {
00397     probe_vdw[2*i  ] = beta*sqrtf(-vdweps[i]);
00398     probe_vdw[2*i+1] = vdwrmin[i];
00399   }
00400 
00401   if (charge) {
00402     memcpy(probe_charge, charge, numatoms*sizeof(float));
00403   } else {
00404     memset(probe_charge, 0, numatoms*sizeof(float));
00405   }
00406 
00407   // Get geometric center:
00408   float cgeom[3];
00409   vec_zero(cgeom);
00410 
00411   for (i=0; i<num_probe_atoms; i++) {
00412     vec_add(cgeom, cgeom, &pcoords[3*i]);
00413   }
00414   vec_scale(cgeom, 1.f/(float)num_probe_atoms, cgeom);
00415 
00416   // Shift geometric center to origin:
00417   for (i=0; i<num_probe_atoms; i++) {
00418     vec_sub(&probe_coords[3*i], &pcoords[3*i], cgeom);
00419   }
00420 }
00421 
00422 
00423 // Set the two highest symmetry axes for the probe and a flag
00424 // telling if we have a tetrahedral symmetry.
00425 void VolMapCreateILS::set_probe_symmetry(
00426         int order1, const float *axis1,
00427         int order2, const float *axis2,
00428         int tetrahedral) {
00429   probe_tetrahedralsymm = tetrahedral;
00430 
00431   if (axis1 && order1>1) {
00432     probe_axisorder1 = order1;
00433     vec_copy(probe_symmaxis1, axis1);
00434     vec_normalize(probe_symmaxis1);
00435   }
00436   if (axis2 && order2>1) {
00437     probe_axisorder2 = order2;
00438     vec_copy(probe_symmaxis2, axis2);
00439     vec_normalize(probe_symmaxis2);
00440   }
00441   if (!tetrahedral && probe_axisorder1>1 && probe_axisorder2>1 &&
00442       dot_prod(probe_symmaxis1, probe_symmaxis2) > 0.05) {
00443     // Axes not orthogonal, drop lower order axis
00444     if (probe_axisorder1<probe_axisorder2) {
00445       probe_axisorder1 = probe_axisorder2;
00446     }
00447     probe_axisorder2 = 1;
00448   }
00449 }
00450 
00451 
00452 // Set minmax coordinates of rectangular grid bounding box
00453 void VolMapCreateILS::set_minmax (float minx, float miny, float minz,
00454                                   float maxx, float maxy, float maxz) {
00455   minmax[0] = minx;
00456   minmax[1] = miny;
00457   minmax[2] = minz;
00458   minmax[3] = maxx;
00459   minmax[4] = maxy;
00460   minmax[5] = maxz;
00461 }
00462 
00463 
00464 // Request PBC aware computation.
00465 // Optionally one can set the PBC cell center
00466 // (default is {0 0 0}).
00467 // If bbox is TRUE then the bounding box of the grid will
00468 // be chosen as the bounding box of the (possibly rhombic)
00469 // PBC cell.
00470 void VolMapCreateILS::set_pbc(float center[3], int bbox) {
00471   pbc    = 1;
00472   pbcbox = bbox;
00473   if (center) vec_copy(pbccenter, center);
00474 }
00475 
00476 
00477 // Set maximum energy considered in the calculation.
00478 void VolMapCreateILS::set_maxenergy(float maxenergy) {
00479   max_energy = maxenergy;
00480   if (max_energy>DEFAULT_EXCL_ENERGY) {
00481     max_energy = DEFAULT_EXCL_ENERGY;
00482   }
00483 }
00484 
00485 
00486 // Set selection to be used for alignment.
00487 void VolMapCreateILS::set_alignsel(AtomSel *asel) {
00488   if (asel) alignsel = asel;
00489 }
00490 
00491 
00492 // Set transformation matrix that was used for the
00493 // alignment of the first frame.
00494 void VolMapCreateILS::set_transform(const Matrix4 *mat) {
00495   transform = *mat;
00496 }
00497 
00498 
00499 // Set grid dimensions to the minmax coordinates and
00500 // pad the positive side of each dimension.
00501 //
00502 //        lattice
00503 //   +---+---+---+---+
00504 //  _|___|___|___|_  |  _
00505 // | |   |   |   | | |  |
00506 // | o---o---o---o---+  |
00507 // | |   |   |   | | |  |
00508 // | |   |   |   | | |  |
00509 // | o---o---o---o---+  |minmax
00510 // |_|_  |   |   | | |  |
00511 // | | | |   |   | | |  |
00512 // | o---o---o---o---+  |
00513 // |___|___________|    L
00514 //  
00515 //   |---|       
00516 //   delta
00517 //               
00518 //   |-----------|
00519 //   xaxis
00520 //
00521 int VolMapCreateILS::set_grid() {
00522   // Number of samples in the final downsampled map.
00523   int nx = (int) ceilf((minmax[3]-minmax[0])/(delta*nsubsamp));
00524   int ny = (int) ceilf((minmax[4]-minmax[1])/(delta*nsubsamp));
00525   int nz = (int) ceilf((minmax[5]-minmax[2])/(delta*nsubsamp));
00526 
00527   // Number of samples used during computation.
00528   nsampx = nx*nsubsamp;
00529   nsampy = ny*nsubsamp;
00530   nsampz = nz*nsubsamp;
00531 
00532   // For volumetric maps the origin is the center of the
00533   // first cell (i.e. the location of the first sample)
00534   // rather than the lower corner of the minmax box.
00535 
00536   // Origin for final downsampled map
00537   float origin[3];
00538   origin[0] = minmax[0] + 0.5f*delta*nsubsamp;
00539   origin[1] = minmax[1] + 0.5f*delta*nsubsamp;
00540   origin[2] = minmax[2] + 0.5f*delta*nsubsamp;
00541 
00542   // Origin for the highres map used during computation
00543   gridorigin[0] = minmax[0] + 0.5f*delta;
00544   gridorigin[1] = minmax[1] + 0.5f*delta;
00545   gridorigin[2] = minmax[2] + 0.5f*delta;
00546   
00547 
00548   // Cell spanning vectors,
00549   // (delta is the distance between two samples)
00550   float cellx[3], celly[3], cellz[3];
00551   cellx[0] = delta*nsubsamp;
00552   cellx[1] = 0.f;
00553   cellx[2] = 0.f;
00554   celly[0] = 0.f;
00555   celly[1] = delta*nsubsamp;
00556   celly[2] = 0.f;
00557   cellz[0] = 0.f;
00558   cellz[1] = 0.f;
00559   cellz[2] = delta*nsubsamp;
00560 
00561   // The axes that span the whole lattice i.e. the vector
00562   // between the first and the last sample point in each
00563   // dimension.
00564   float xaxis[3], yaxis[3], zaxis[3];
00565   int i;
00566   for (i=0; i<3; i++) {
00567     xaxis[i] = cellx[i]*(nx-1);
00568     yaxis[i] = celly[i]*(ny-1);
00569     zaxis[i] = cellz[i]*(nz-1);
00570   }
00571 
00572   // Initialize the final downsampled map:
00573   float *data = new float[nx*ny*nz];
00574   if (maskonly) {
00575     // Fill mask map with ones
00576     for (i=0; i<nx*ny*nz; i++) {
00577       data[i] = 1.f;
00578     }    
00579   } else {
00580     memset(data, 0, nx*ny*nz*sizeof(float));
00581   }
00582 
00583   volmap = new VolumetricData("\0", origin,
00584                   xaxis, yaxis, zaxis, nx, ny, nz, data);
00585 
00586   if (!volmap->gridsize())
00587     return MEASURE_ERR_ZEROGRIDSIZE;
00588 
00589   return 0;
00590 }
00591 
00592 
00593 // Initialize the ILS calculation
00594 int VolMapCreateILS::initialize() {
00595   if (!app->molecule_numframes(molid))
00596     return MEASURE_ERR_NOFRAMES;
00597 
00598   msgInfo << "\n-- Implicit Ligand Sampling --\n\n"
00599           << "This computes the potential of mean force (free energy) over\n"
00600           << "a 3-D grid, for a small ligand.\n\n" << sendmsg;
00601     //    << "If you use this method in your work, please cite:\n\n"
00602     //    << "  J COHEN, A ARKHIPOV, R BRAUN and K SCHULTEN, \"Imaging the\n"
00603     //    << "  migration pathways for O2, CO, NO, and Xe inside myoglobin\".\n"
00604     //    << "  Biophysical Journal 91:1844-1857, 2006.\n\n" << sendmsg;
00605   char tmpstr[256];
00606   sprintf(tmpstr, "Temperature:    %g K", temperature);
00607   msgInfo << tmpstr << sendmsg;
00608   sprintf(tmpstr, "Energy cutoff:  %g kT", max_energy);
00609   msgInfo << tmpstr << sendmsg;
00610 
00611   // Compute electrostatics only if probe charges are nonzero
00612   if (compute_elec && probe_charge[0]==0.0 && probe_charge[1]==0.0) {
00613     compute_elec = 0;
00614   }
00615   msgInfo << "Electrostatics: ";
00616   if (compute_elec) msgInfo << "on"  << sendmsg;
00617   else              msgInfo << "off" << sendmsg;
00618 
00619   sprintf(tmpstr, "Map resolution: %g Angstrom", delta);  
00620   msgInfo << tmpstr << sendmsg;
00621   sprintf(tmpstr, "Subgrid res:    %d points", nsubsamp);
00622   msgInfo << tmpstr << sendmsg;
00623     
00624   // Initialize parameters
00625   if (num_probe_atoms==1) {
00626     sprintf(tmpstr, "VDW cutoff:     %6.3f Angstrom", cutoff);
00627     msgInfo << tmpstr << sendmsg;
00628   }
00629   else {
00630     // Get the max probe radius, i.e. the largest distance
00631     // of an atom to the center of mass
00632 
00633     float max_proberad = 0.f;
00634     int i;
00635     for (i=0; i<num_probe_atoms; i++) {
00636       float dist = norm(&probe_coords[3*i]);
00637       if (dist>max_proberad) max_proberad = dist;
00638     }
00639 
00640     sprintf(tmpstr, "VDW cutoff:     %6.3f Angstrom (%6.3f + probe radius)",
00641             cutoff+max_proberad, cutoff);
00642     msgInfo << tmpstr << sendmsg;
00643 
00644     extcutoff = cutoff + max_proberad;
00645   }
00646 
00647   if (alignsel) {
00648     // Get coordinates of the alignment reference frame (the current frame of alignsel)
00649     alignrefpos = alignsel->coordinates(app->moleculeList);
00650 
00651   } else if (!alignsel && last-first>1) {
00652     msgWarn << sendmsg;
00653     msgWarn << "Use of periodic boundaries requested (-pbc) but" << sendmsg;
00654     msgWarn << "no alignment matrix specified (you didn't use -alignsel)." << sendmsg;
00655     msgWarn << "Have you aligned your structure prior to this calculation?" << sendmsg;
00656     msgWarn << "Hopefully not, since it will have totally messed" << sendmsg;
00657     msgWarn << "up the definition of your PBC cells. Instead you" << sendmsg;
00658     msgWarn << "should use the -alignsel option and let volmap handle" << sendmsg;
00659     msgWarn << "the alignment." << sendmsg;
00660   }
00661 
00662   if (pbc && pbcbox) {
00663     // Compute minmax based on the PBC cell of the first frame:
00664     // Get the smallest rectangular box that encloses the
00665     // entire (possibly nonorthogonal) PBC cell
00666     int err = compute_pbcminmax(app->moleculeList, molid, first,
00667                    pbccenter, &transform, &minmax[0], &minmax[3]);
00668     if (err) return err;
00669   }
00670 
00671   sprintf(tmpstr, "{%g %g %g} {%g %g %g}\n", minmax[0], minmax[1], minmax[2], minmax[3], minmax[4], minmax[5]);
00672   msgInfo << "Grid minmax = " << tmpstr << sendmsg; 
00673 
00674   // Automatically add the force field cutoff to the system
00675   float ffcutoff[3];
00676   ffcutoff[0] = ffcutoff[1] = ffcutoff[2] = extcutoff;
00677   float cutminmax[6];
00678   vec_sub(cutminmax,   minmax,   ffcutoff);
00679   vec_add(cutminmax+3, minmax+3, ffcutoff);
00680   
00681   if (!box_inside_pbccell(first, minmax)) {
00682     if (!pbc) {
00683       msgWarn << sendmsg;
00684       msgWarn << "Selected grid exceeds periodic cell boundaries." << sendmsg;
00685       msgWarn << "Parts of the map will be undefined!" << sendmsg;
00686       msgWarn << "(Consider using -pbc flag to ensure map is valid over entire grid.)"
00687               << sendmsg << sendmsg;
00688     } else {
00689       msgInfo << "Selected grid exceeds periodic cell boundaries." << sendmsg;
00690       msgInfo << "Using PBC image atoms to ensure map is valid over entire grid."
00691               << sendmsg;
00692     }
00693   } else if (!box_inside_pbccell(first, cutminmax)) {
00694     if (!pbc) {
00695       msgWarn << sendmsg;
00696       msgWarn << "Nonbonded interaction cutoff region needed around" << sendmsg;
00697       msgWarn << "selected grid exceeds periodic cell boundaries." << sendmsg;
00698       msgWarn << "Parts of the map will be ill-defined!" << sendmsg;
00699       msgWarn << "(Consider using -pbc flag to ensure map is valid over entire grid.)"
00700               << sendmsg << sendmsg;;
00701     } else {
00702       msgInfo << "Nonbonded interaction cutoff region needed around" << sendmsg;
00703       msgInfo << "selected grid exceeds periodic cell boundaries." << sendmsg;
00704       msgInfo << "Using PBC image atoms to ensure map is valid over entire grid."
00705               << sendmsg;
00706     }
00707   }
00708 
00709   // Compute and set the grid dimensions
00710   set_grid();
00711   msgInfo << "Grid origin = {"
00712           << volmap->origin[0] << " "
00713           << volmap->origin[1] << " "
00714           << volmap->origin[2] << "}" << sendmsg;
00715 
00716   char tmp[64];
00717   sprintf(tmp, "Grid size   = %dx%dx%d (%.1f MB)",
00718           nsampx, nsampy, nsampz,
00719           sizeof(float)*nsampx*nsampy*nsampz/(1024.*1024.));
00720   msgInfo << tmp << sendmsg;
00721 
00722   if (nsubsamp>1) {
00723     sprintf(tmp, "Downsampling final map to %dx%dx%d  (%.1f MB)",
00724             volmap->xsize, volmap->ysize, volmap->zsize,
00725             sizeof(float)*volmap->gridsize()/(1024.*1024.));
00726     msgInfo << tmp << sendmsg;
00727   }
00728 
00729   Molecule *mol = app->moleculeList->mol_from_id(molid);
00730   num_atoms = mol->nAtoms;
00731 
00732   msgInfo << "Global transformation for all frames:" << sendmsg;
00733   print_Matrix4(&transform);
00734 
00735   if (alignsel) msgInfo << "Aligning all frames to the first one." << sendmsg;
00736   else          msgInfo << "Assuming all frames are aligned." << sendmsg;
00737 
00738   if (maskonly) {
00739     msgInfo << sendmsg << "Masking mode:" << sendmsg
00740             << "Generating a mask map containing 1 for grid points that" << sendmsg
00741             << "have a valid contribution from each frame and 0 otherwise."
00742             << sendmsg << sendmsg;
00743     return MEASURE_NOERR;
00744   }
00745 
00746 
00747   // Create list of unique VDW parameters which can be accessed
00748   // through an index list.
00749   create_unique_paramlist();
00750 
00751   // Find smallest VDW rmin parameter for all system atoms 
00752   float min_sysrmin = vdwparams[1];
00753   int i;
00754   for (i=1; i<num_unique_types; i++) {
00755     if (vdwparams[2*i+1]<min_sysrmin) min_sysrmin = vdwparams[2*i+1];
00756   }
00757 
00758   // Find largest VDW rmin parameter for all probe atoms
00759   float min_probermin = probe_vdw[1];
00760   for (i=1; i<num_probe_atoms; i++) {
00761     if (probe_vdw[2*i+1]<min_probermin) min_probermin = probe_vdw[2*i+1];
00762   }
00763   
00764 
00765   const float invbeta = temperature/503.2206f;
00766   msgInfo << "Probe with "<<num_probe_atoms<<" atoms:" << sendmsg;
00767   for (i=0; i<num_probe_atoms; i++) {
00768     sprintf(tmpstr, "  atom %d: epsilon = %g, rmin/2 = %g, charge = % .3f",
00769             i, -pow(invbeta*probe_vdw[2*i],2),
00770             probe_vdw[2*i+1], probe_charge[i]);
00771     msgInfo << tmpstr << sendmsg;
00772   }
00773 
00774   // Create conformers for multiatom probes
00775   if (conformer_freq && num_probe_atoms>1) {
00776     initialize_probe();
00777   } else {
00778     msgInfo << "Ignoring orientations for monoatomic probe." << sendmsg;
00779   }
00780 
00781   get_eff_proberadius();
00782 
00783 
00784   // Define a cutoff for identifying obvious atom clashes:
00785   sprintf(tmpstr, "Clash exclusion distance:  %.3f Angstrom", excl_dist);
00786   msgInfo << tmpstr << sendmsg;
00787 
00788 //   volmask = new VolumetricData(volmap->name, volmap->origin,
00789 //                     volmap->xaxis, volmap->yaxis, volmap->zaxis,
00790 //                     volmap->xsize, volmap->ysize, volmap->zsize, NULL);
00791   //volmask = new VolumetricData(*volmap);
00792   //volmask->data = new float[gridsize*sizeof(float)];
00793   //memset(volmask->data, 0, sizeof(float)*gridsize);
00794   //char name[8];
00795   //strcpy(name, "mask");
00796   //volmask->set_name(name);
00797 
00798   return MEASURE_NOERR;
00799 }
00800 
00801 
00802 void VolMapCreateILS::get_eff_proberadius() {
00803   int numconf, numorient, numrot;
00804   float *conf;
00805   if (probe_tetrahedralsymm) {
00806     numconf = gen_conf_tetrahedral(conf, 6, numorient, numrot);
00807   } else {
00808     numconf = gen_conf(conf, 8, numorient, numrot);
00809   }
00810 
00811   int t, i, j, k;
00812   float max_proberad = 0.f;
00813   for (k=0; k<num_probe_atoms; k++) {
00814     float dist = norm(&probe_coords[3*k]);
00815     float rmin = probe_vdw[2*k+1];
00816     if (dist+rmin>max_proberad) max_proberad = dist+rmin;
00817   }
00818 
00819   float stepsize = 0.01f;
00820   float *effrad = new float[num_unique_types];
00821   excl_dist = 999999.f;
00822 
00823   for (t=0; t<num_unique_types; t++) {
00824     float vdweps  = vdwparams[2*t  ];
00825     float vdwrmin = vdwparams[2*t+1];
00826     //printf("vdweps=%f, vdwrmin=%f\n", vdweps, vdwrmin);
00827     float begin = max_proberad + vdwrmin;
00828     int maxnumstep = int(0.5+begin/stepsize);
00829     //printf("maxproberad=%.2f\n", max_proberad);
00830     float Wmin = 0.f;
00831     float Ropt = 0.f;
00832 
00833     for (i=0; i<maxnumstep; i++) {
00834       float dist = begin - float(i)*stepsize;
00835       if (dist<=0.0f) break;
00836 
00837       float avgocc = 0.f;
00838       for (j=0; j<numconf; j++) {
00839         float *coor = &conf[3*num_probe_atoms*j];
00840         float u = 0.f;
00841         for (k=0; k<num_probe_atoms; k++) {
00842           float dx = dist-coor[3*k];
00843           float dy = coor[3*k+1];
00844           float dz = coor[3*k+2];
00845           float r2 = dx*dx + dy*dy + dz*dz;
00846           float epsilon = vdweps  * probe_vdw[2*k];
00847           float rmin    = vdwrmin + probe_vdw[2*k+1];
00848           float rm6 = rmin*rmin / r2;
00849           rm6 = rm6 * rm6 * rm6;
00850           u += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
00851           //printf("     u[%d] = %f, r2=%f\n", k, epsilon * rm6 * (rm6 - 2.f), r2);
00852         }
00853         //printf("  u[%d] = %f\n", j, u);
00854 
00855         float occ = expf(-float(u));
00856 
00857         avgocc += occ;
00858       }
00859       avgocc /= float(numconf);
00860       float W = -logf(avgocc);
00861       //printf("dist=%.2f occ=%g; dG=%g\n", dist, avgocc, -logf(avgocc));
00862       if (W<Wmin) { Wmin = W; Ropt = dist; }
00863       if (W>max_energy+5.f) {
00864         effrad[t] = dist;
00865         break;
00866       }
00867     }
00868 
00869     //printf("effrad[%d]=%.3f Ropt=%.3f, Wmin=%f\n", t, effrad[t], Ropt, Wmin);
00870     if (effrad[t]<excl_dist) excl_dist = effrad[t];
00871   }
00872 
00873   delete [] effrad;
00874   delete [] conf;
00875 }
00876 
00877 
00878 // Check if two vectors are collinear within a given tolerance.
00879 // Assumes that the vectors are normalized.
00880 static bool collinear(const float *axis1, const float *axis2, float tol) {
00881   if (fabs(dot_prod(axis1, axis2)) > 1.f-DEGTORAD(tol)) return 1;
00882   return 0;
00883 }
00884 
00885 
00886 // Check if probe is a linear molecule and returns
00887 // the Cinf axis.
00888 int VolMapCreateILS::is_probe_linear(float *axis) {
00889   if (num_probe_atoms==1) return 0;
00890 
00891   float vec0[3], vec1[3];
00892   vec_sub(vec0, &probe_coords[3], &probe_coords[0]);
00893   vec_copy(axis, vec0);
00894   if (num_probe_atoms==2) return 1;
00895 
00896   float norm0 = norm(vec0);
00897   int i;
00898   for (i=2; i<num_probe_atoms; i++) {
00899     vec_sub(vec1, &probe_coords[3*i], &probe_coords[0]);
00900     float dot = dot_prod(vec0, vec1)/(norm0*norm(vec1));
00901     if (fabs(dot) < 0.95f) return 0;
00902 
00903     if (dot>0.f) vec_add(axis, axis, vec1);
00904     else         vec_sub(axis, axis, vec1);
00905   }
00906   vec_normalize(axis);
00907   return 1;
00908 }
00909 
00910 
00911 // Subdivide a triangle specified by the points pole, eq1 and eq2.
00912 // A freqency of 1 means no partitioning, 2 signifies that each edge
00913 // is subdivided into 2 segments and so on.
00914 // The resulting vertices are returned in v.
00915 static int triangulate(const float *pole, const float *eq1,
00916                         const float *eq2, int freq, float *v) {
00917   if (freq==0) {
00918     vec_copy(v, pole);
00919     return 1;
00920   }
00921 
00922   float meridian[3], parallel[3];
00923   vec_sub(meridian, eq1, pole);
00924   vec_sub(parallel, eq2, eq1);
00925   float mlen = norm(meridian);
00926   float plen = norm(parallel);
00927   vec_normalize(meridian);
00928   vec_normalize(parallel);
00929   int i, k = 0;
00930   for (i=0; i<=freq; i++) {
00931     float latitude = float(i)/float(freq)*mlen;
00932     float p[3], p0[3];
00933     vec_copy(p0, pole);
00934     vec_scaled_add(p0, latitude, meridian);
00935     int j;
00936     for (j=0; j<=i; j++) {
00937       float longitude = float(j)/float(freq)*plen;
00938       vec_copy(p, p0);
00939       vec_scaled_add(p, longitude, parallel);
00940       vec_copy(&v[3*k], p);
00941       k++;
00942     }
00943   }
00944   return k;
00945 }
00946 
00947 
00948 // Generate the 6 vertices of an octahedron
00949 // (or only 3 if the symmetry flag was set)
00950 static void octahedron(float *vertices, int C2symm) {
00951   const float v[] = {1.f, 0.f, 0.f,
00952                      0.f, 1.f, 0.f,
00953                      0.f, 0.f, 1.f};
00954   memcpy(vertices, v, 9*sizeof(float));
00955   if (!C2symm) {
00956     int i;
00957     for (i=0; i<3; i++) {
00958       vec_negate(&vertices[9+3*i], &vertices[3*i]);
00959     }
00960   }
00961 }
00962 
00963 // Generate the 8 vertices of a hexahedron
00964 // (or only 4 if the symmetry flag was set)
00965 static void hexahedron(float *vertices, int C2symm) {
00966   const float v[] = {1.f,  1.f,  1.f,
00967                      1.f, -1.f,  1.f,
00968                      1.f,  1.f, -1.f,
00969                      1.f, -1.f, -1.f};
00970   memcpy(vertices, v, 12*sizeof(float));
00971 
00972   if (!C2symm) {
00973     int i;
00974     for (i=0; i<4; i++) {
00975       vec_negate(&vertices[12+3*i], &vertices[3*i]);
00976     }
00977   }
00978 }
00979 
00980 // Generate normal vectors for the 12 dodecahedral faces
00981 // (or only 6 if the symmetry flag was set) and the 20
00982 // vertices. The vertices are at the same time the faces
00983 // of the icosahedron, the dual of the dodecahedron.
00984 // XXX I know that this takes more space than just listing
00985 //     the hardcoded points...
00986 static void dodecahedron(float *faces, float *vertices, int C2symm) {
00987   // Angle between two faces
00988   const float dihedral = float(180.f-RADTODEG(acos(-1.f/sqrtf(5.f))));
00989 
00990   // Faces:
00991   float x[3];
00992   vec_zero(x); x[0] = 1.f;
00993   vec_copy(&faces[0], x);
00994   // Contruct first point in ring
00995   Matrix4 rot;
00996   rot.rot(dihedral, 'z');
00997   rot.multpoint3d(&faces[0], &faces[3]);
00998   // Get other points by rotation
00999   int i;
01000   for (i=1; i<5; i++) {
01001     rot.identity();
01002     rot.rot(float(i)*72.f, 'x');
01003     rot.multpoint3d(&faces[3], &faces[3+3*i]);
01004   }
01005   if (!C2symm) {
01006     for (i=0; i<6; i++) {
01007       vec_negate(&faces[18+3*i], &faces[3*i]);
01008     }
01009   }
01010 
01011   // Vertices
01012   // center and first ring
01013   for (i=0; i<5; i++) {
01014     vec_copy(&vertices[3*i], &faces[0]);
01015     vec_add(&vertices[3*i], &vertices[3*i], &faces[3*(i+1)]);
01016     vec_add(&vertices[3*i], &vertices[3*i], &faces[3*((i+1)%5+1)]);
01017     vec_normalize(&vertices[3*i]);
01018   }
01019   // second ring
01020   vec_copy(&vertices[3*5], &faces[3*1]);
01021   vec_add(&vertices[3*5], &vertices[3*5], &faces[3*2]);
01022   vec_normalize(&vertices[3*5]);
01023   float cross[3];
01024   cross_prod(cross, &vertices[3*5], &faces[0]);
01025   vec_normalize(cross);
01026   float phi = angle(&vertices[3*5], &vertices[0]);
01027   rot.identity();
01028   rot.rotate_axis(cross, float(DEGTORAD(-phi)));
01029   rot.multpoint3d(&vertices[3*5], &vertices[3*5]);
01030   for (i=1; i<5; i++) {
01031     rot.identity();
01032     rot.rot(float(i)*72.f, 'x');
01033     rot.multpoint3d(&vertices[3*5], &vertices[3*5+3*i]);
01034   }
01035 
01036   // opposite orientations
01037   if (!C2symm) {
01038     for (i=0; i<10; i++) {
01039       vec_negate(&vertices[30+3*i], &vertices[3*i]);
01040     }
01041   }
01042 }
01043 
01044 
01045 // Geodesic triangulation of an icosahedron.
01046 // Parameter freq is the geodesic frequency, the 
01047 // flag C2symm signifies if the molecule is symmetric
01048 // and we can omit one hemisphere.
01049 // Allocates memory for the orientation vectors
01050 // and returns the number of generated orientations.
01051 static int icosahedron_geodesic(float *(&orientations),
01052                                 int C2symm, int freq) {
01053   int i;
01054   float faces[3*12];
01055   float junk[3*20];
01056   dodecahedron(faces, junk, 0);
01057   float meridian[3], parallel[3];
01058 
01059   int numvertex = 0; // number of triangle vertices
01060   for (i=1; i<=freq+1; i++) numvertex += i;
01061   int symmfac = C2symm ? 2 : 1;
01062   int numorient = (10*freq*freq + 2)/symmfac;
01063   orientations = new float[3*numorient];
01064 
01065   // Add pole to array of orientations
01066   vec_copy(&orientations[0], &faces[0]);
01067   int k = 1;
01068 
01069   for (i=0; i<5; i++) {
01070     // First ring
01071     float p0[3], p1[3], p2[3];
01072     vec_sub(parallel, &faces[3+3*((i+1)%5)], &faces[3*(i+1)]);
01073     float edgelen = norm(parallel);
01074     vec_normalize(parallel);
01075     vec_sub(meridian, &faces[3*(i+1)], &faces[0]);
01076     vec_normalize(meridian);
01077     vec_copy(p0, &faces[0]);
01078     vec_copy(p1, &faces[3*(i+1)]);
01079     vec_copy(p2, &faces[3+3*((i+1)%5)]);
01080     vec_scaled_add(p0,  1.f/float(freq)*edgelen, meridian);
01081     vec_scaled_add(p2, -1.f/float(freq)*edgelen, parallel);
01082     triangulate(p0, p1, p2, freq-1, &orientations[3*k]);
01083     k += numvertex-(freq+1);
01084 
01085     // Second ring
01086     vec_sub(meridian, &faces[3*(i+1)], &faces[21+3*((i+3)%5)]);
01087     vec_normalize(meridian);
01088     vec_copy(p0, &faces[21+3*((i+3)%5)]);
01089     vec_copy(p1, &faces[3*(i+1)]);
01090     vec_scaled_add(p0,  1.f/float(freq)*edgelen, meridian);
01091     vec_scaled_add(p1, -1.f/float(freq)*edgelen, meridian);
01092     vec_copy(p2, p1);
01093     vec_scaled_add(p2, float(freq-2)/float(freq)*edgelen, parallel);
01094     triangulate(p0, p1, p2, freq-2, &orientations[3*k]);
01095     k += numvertex-(freq+1)-freq;
01096   } 
01097 
01098   if (!C2symm) {
01099     for (i=0; i<numorient/2; i++) {
01100       vec_negate(&orientations[3*numorient/2+3*i], &orientations[3*i]);
01101     }
01102   }
01103 
01104   return numorient;
01105 }
01106 
01107 float VolMapCreateILS::dimple_depth(float phi) {
01108   int i;
01109   phi = 0.5f*phi;
01110   // Find smallest system atom
01111   float min_syseps  = vdwparams[0];
01112   float min_sysrmin = vdwparams[1];
01113   for (i=1; i<num_unique_types; i++) {
01114     if (vdwparams[2*i+1]<min_sysrmin) {
01115       min_syseps  = vdwparams[2*i  ];
01116       min_sysrmin = vdwparams[2*i+1];
01117     }
01118   }
01119   float maxdepth = 0.f;
01120 
01121   // Check all probe atoms for dimple depth
01122   for (i=0; i<num_probe_atoms; i++) {
01123     float d = norm(&probe_coords[3*i]);
01124     float a = d*sinf(float(DEGTORAD(phi)));
01125     float m = a/cosf(float(DEGTORAD(phi)));
01126     if (phi == 90.f) m = a;
01127     float c = probe_vdw[2*i+1] + min_sysrmin;
01128     if (m>c) {
01129       maxdepth = d;
01130       break;
01131     }
01132     float b = sqrtf(c*c-m*m);
01133     float depth = d + c - d*cosf(float(DEGTORAD(phi))) - b;
01134     //printf("d=%f, rp=%.3f, rs=%.3f, g=%f, b=%f, a=%f, c=%f\n",
01135     //       d, probe_vdw[2*i+1], min_sysrmin, d*cosf(DEGTORAD(phi)), b, m, c);
01136 
01137     float epsilon = min_syseps * probe_vdw[2*i];
01138     float rmin = min_sysrmin + probe_vdw[2*i+1];
01139     // Get energy in dimple (atoms are touching)
01140     float r2 = c*c;
01141     float rm6 = rmin*rmin / r2;
01142     rm6 = rm6 * rm6 * rm6;
01143     float u0 = epsilon * rm6 * (rm6 - 2.f);
01144     // Get energy for outer radius
01145     r2 = (c+depth)*(c+depth);
01146     rm6 = rmin*rmin / r2;
01147     rm6 = rm6 * rm6 * rm6;
01148     float u1 = epsilon * rm6 * (rm6 - 2.f);
01149     // Get energy for outer radius
01150     r2 = (c-depth)*(c-depth);
01151     rm6 = rmin*rmin / r2;
01152     rm6 = rm6 * rm6 * rm6;
01153     float u2 = epsilon * rm6 * (rm6 - 2.f);
01154     float du1 = u1-u0;
01155     float du2 = u2-u0;
01156     printf("phi = %.2f: %d dimple depth = %f = %5.2f%%, dU1 = %fkT = %5.2f%%; dU1 = %fkT = %5.2f%%\n",
01157            phi, i, depth, 100.f*depth/(d+probe_vdw[2*i]), du1, fabs(100.f*du1/u0), du2, fabs(100.f*du2/u0));
01158 
01159     if (depth>maxdepth) maxdepth = depth;
01160   }
01161   return maxdepth;
01162 }
01163 
01164 
01165 // Generate conformers for tetrahedral symmetries.
01166 // Allocates memory for *conform array and returns the number
01167 // of generated conformers.
01168 // numorient:  # symmetry unique orientations
01169 // numrot:     # rotamers per orientation
01170 //
01171 // Rotate around the 4 axes defined by the corners of
01172 // the tetrahedron and its dual (also a tetrahedron).
01173 // XXX:
01174 // This approach exploits the probe symmetry very well
01175 // but for higher frequencies you will start seeing 
01176 // "holes" in the pattern. These holes are in the middle
01177 // of the 12 corners of the cube spanned by the vertices
01178 // of the two dual tetrahedra
01179 // One idea how to fix this would be to generate two
01180 // extra sets of conformations where the basic tetrahedra
01181 // are rotated such that their vertices are in the holes.
01182 int VolMapCreateILS::gen_conf_tetrahedral(float *(&conform),
01183                      int freq, int &numorient, int &numrot) {
01184   // Generate the 4 corners of the tetrahedron
01185   float tetra0[3], tetra1[3], tetra2[3], tetra3[3];
01186   vec_zero(tetra0);
01187   tetra0[0] = 1.f;
01188   Matrix4 rot;
01189   rot.rot(109.47122f, 'z');
01190   rot.multpoint3d(tetra0, tetra1);
01191   rot.identity();
01192   rot.rot(120.f, 'x');
01193   rot.multpoint3d(tetra1, tetra2);
01194   rot.multpoint3d(tetra2, tetra3);
01195 
01196 #if defined(DEBUG)
01197   Molecule *mol = app->moleculeList->mol_from_id(molid);
01198   MoleculeGraphics *gmol = mol->moleculeGraphics();
01199   gmol->use_color(8);
01200   gmol->add_line(tetra0, tetra1, 0, 1);
01201   gmol->add_line(tetra0, tetra2, 0, 1);
01202   gmol->add_line(tetra0, tetra3, 0, 1);
01203   gmol->add_line(tetra1, tetra2, 0, 1);
01204   gmol->add_line(tetra1, tetra3, 0, 1);
01205   gmol->add_line(tetra2, tetra3, 0, 1);
01206 #endif
01207 
01208   // array of probe orientation vectors
01209   float *orientations;
01210   orientations = new float[3*8];
01211 
01212   vec_copy(&orientations[3*0], tetra0);
01213   vec_copy(&orientations[3*1], tetra1);
01214   vec_copy(&orientations[3*2], tetra2);
01215   vec_copy(&orientations[3*3], tetra3);
01216   float face[3];
01217   vec_copy(face, tetra0);
01218   vec_add(face, face, tetra1);
01219   vec_add(face, face, tetra2);
01220   vec_copy(&orientations[3*4], face);
01221   vec_copy(face, tetra0);
01222   vec_add(face, face, tetra1);
01223   vec_add(face, face, tetra3);
01224   vec_copy(&orientations[3*5], face);
01225   vec_copy(face, tetra0);
01226   vec_add(face, face, tetra2);
01227   vec_add(face, face, tetra3);
01228   vec_copy(&orientations[3*6], face);
01229   vec_copy(face, tetra1);
01230   vec_add(face, face, tetra2);
01231   vec_add(face, face, tetra3);
01232   vec_copy(&orientations[3*7], face);
01233 
01234   numrot = freq;  // # rotamers per orientation
01235   numorient = 8;  // the tetrahedron and its dual
01236   int numconf = numorient*numrot-6;
01237   conform = new float[3*num_probe_atoms*numconf];
01238   memset(conform, 0, 3*num_probe_atoms*numconf*sizeof(float));
01239 
01240   int conf = 0;
01241   int i;
01242   for (i=0; i<numorient; i++) {
01243     float *dir = &orientations[3*i];
01244     vec_normalize(dir);
01245 
01246     // Apply rotations around dir
01247     int j;
01248     for (j=0; j<numrot; j++) {
01249       // The non-rotated orientations are equivalent
01250       // for each tetrahedral dual so we skip them.
01251       if (i%4 && j==0) continue;
01252 
01253       float psi = float(j)/float(numrot)*360.f/float(probe_axisorder1);
01254       Matrix4 rot;
01255       if (i>=numorient/2) {
01256         // Flip orientation by 180 deg in order to get the
01257         // dual of the tetrahedron which is also a tetrahedron
01258         // with corners and faces exchanged.
01259         float z[3];
01260         vec_zero(z); z[2]=1.f;
01261         rot.rotate_axis(z, float(DEGTORAD(180.f)));
01262       }
01263 
01264       // Rotate around dir
01265       rot.rotate_axis(dir, float(DEGTORAD(psi)));
01266     
01267       // Apply rotation to all probe atoms
01268       int k;
01269       for (k=0; k<num_probe_atoms; k++) {
01270         rot.multpoint3d(&probe_coords[3*k],
01271                         &conform[3*num_probe_atoms*conf + 3*k]);
01272       }
01273 
01274       conf++;
01275     }
01276   }
01277 
01278   delete [] orientations;
01279 
01280   // Return the number of generated conformers
01281   return numconf;
01282 }
01283 
01284 
01285 // Compute angle that rotates cross(axis, v1) into cross(axis, v2)
01286 // with respect to rotation around axis.
01287 static float signed_angle(const float *axis,
01288                           const float *v1, const float *v2) {
01289   float normaxis[3], cross1[3], cross2[3], cross3[3];
01290   cross_prod(cross1, axis, v1);
01291   cross_prod(cross2, axis, v2);
01292   cross_prod(cross3, v1, v2);
01293   vec_normalize(cross3);
01294   vec_copy(normaxis, axis);
01295   vec_normalize(normaxis);
01296   float phi = angle(cross1, cross2);
01297   if (dot_prod(axis, cross3)<0) {
01298     phi = -phi;
01299   }
01300   return phi;
01301 }
01302 
01303 
01304 // Generate conformers for all non-tetrahedral symmetries.
01305 // Allocates memory for *conform array and returns the number
01306 // of generated conformers.
01307 // numorient:  # symmetry unique orientations
01308 // numrot:     # rotamers per orientation
01309 int VolMapCreateILS::gen_conf(float *(&conform), int freq,
01310                               int &numorient, int &numrot) {
01311   int i;
01312   float *orientations = NULL; // array of probe orientation vectors
01313   int C2symm = (probe_axisorder2==2 ? 1 : 0);
01314   int symmfac = C2symm ? 2 : 1;
01315   float anglespacing = 360.f;
01316 
01317   switch (freq) {
01318   case 1:
01319     numorient = 1;
01320     orientations = new float[3*numorient];
01321     break;
01322   case 2:
01323     // 6 octahedral vertices
01324     numorient = 6/symmfac;
01325     orientations = new float[3*numorient];
01326     octahedron(orientations, C2symm);
01327     anglespacing = 90.f;
01328     break;
01329   case 3:
01330     // 8 hexahedral vertices
01331     numorient = 8/symmfac;
01332     orientations = new float[3*numorient];
01333     hexahedron(orientations, C2symm);
01334     anglespacing = 109.47122f;
01335     break;
01336   case 4:
01337     // 12 dodecahedral faces 
01338     numorient = 12/symmfac;
01339     orientations = new float[3*numorient];
01340     float vertices[3*20]; // junk
01341     dodecahedron(orientations, vertices, C2symm);
01342     anglespacing = float(180.f-RADTODEG(acos(-1.f/sqrtf(5.f))));
01343       break;
01344   case 5:
01345     // 20 dodecahedral vertices
01346     numorient = 20/symmfac;
01347     orientations = new float[3*numorient];
01348     float faces[3*12]; // junk
01349     dodecahedron(faces, orientations, C2symm);
01350     anglespacing = 180.f-138.189685f;
01351     break;
01352   case 6:
01353     // 12 faces and 20 vertices of a dodecahedron
01354     numorient = 32/symmfac;
01355     orientations = new float[3*numorient];
01356     dodecahedron(&orientations[0], &orientations[3*12/symmfac], C2symm);
01357     anglespacing = 37.377380f;
01358     break;
01359   default:
01360     // Triangulate icosahedral faces
01361     freq -= 5;
01362  
01363     numorient = icosahedron_geodesic(orientations, C2symm, freq);
01364 
01365     anglespacing = (180.f-138.189685f)/freq;
01366     break;
01367   }
01368 
01369   // Number of rotamers per orientation
01370   // Chosen such that the rotation stepping angle is as close
01371   // as possible to the angle between two adjacent orientations.
01372   numrot = 1;
01373   if (probe_axisorder1>=0) {
01374     numrot = int(floorf(360.f/probe_axisorder1/anglespacing + 0.5f));
01375   }
01376 
01377   int numconf = numorient*numrot;
01378   //printf("numorient=%d, numrot=%d, numconf=%d, num_probe_atoms=%d\n",numorient,numrot,numconf,num_probe_atoms);
01379   conform = new float[3*num_probe_atoms*numconf];
01380   memset(conform, 0, 3*num_probe_atoms*numconf*sizeof(float));
01381 
01382 #if defined(DEBUG)
01383   Molecule *mol = app->moleculeList->mol_from_id(molid);
01384   MoleculeGraphics *gmol = mol->moleculeGraphics();
01385   for (i=0; i<numorient; i++) {
01386     float dir[3];
01387     vec_scale(dir, 0.8, &orientations[3*i]);
01388     gmol->use_color(7);
01389     if (i==0) gmol->use_color(4);
01390     if (i==1) gmol->use_color(8);
01391     if (i==2) gmol->use_color(9);
01392     gmol->add_sphere(dir, 0.1, 8);
01393   }
01394 #endif
01395 
01396   // Generate conformer coordinates
01397   int conf = 0;
01398   for (i=0; i<numorient; i++) {
01399     float *dir = &orientations[3*i];
01400     vec_normalize(dir);
01401 
01402     float cross[3], x[3], y[3], z[3];
01403     vec_zero(x); x[0]=1.f;
01404     vec_zero(y); y[1]=1.f;
01405     vec_zero(z); z[2]=1.f;
01406     float phi = 0.f;
01407     float theta = 0.f;
01408 
01409     if (!collinear(x, dir, 2.f)) {
01410       // Get rotation axis and angle phi to rotate x-axis into dir
01411       cross_prod(cross, x, dir);
01412       phi = angle(dir, x);
01413       // Get rotation around X so that Y would be in the plane
01414       // spanned by X and dir. (If we have a second symmetry axis
01415       // then this rotates axis2 into that plane because we have
01416       // previously aligned axis2 with Y.)
01417       float cross2[3];
01418       cross_prod(cross2, x, y);
01419       theta = signed_angle(x, cross2, cross);
01420     } else if (dot_prod(x, dir)<0.f) {
01421       // dir and x are antiparallel
01422       phi = 180.f;
01423     }
01424     //printf("dir[%d] = {%.2f %.2f %.2f},  phi=%.2f, theta=%.2f\n", i, dir[0], dir[1], dir[2], phi, theta);
01425 
01426     
01427     // Apply rotations around dir
01428     int j;
01429     for (j=0; j<numrot; j++) {
01430       Matrix4 m;
01431       float psi = float(j)/float(numrot)*360.f/float(probe_axisorder1);
01432 
01433       // Rotate around dir
01434       m.rotate_axis(dir, float(DEGTORAD(psi)));
01435 
01436       // Rotate around X
01437       m.rot(theta, 'x');
01438       
01439       // Tilt X into dir
01440       m.rotate_axis(z, float(DEGTORAD(phi)));
01441 
01442       // Apply rotation to all probe atoms
01443       int k;
01444       for (k=0; k<num_probe_atoms; k++) {
01445         m.multpoint3d(&probe_coords[3*k],
01446                       &conform[3*num_probe_atoms*conf + 3*k]);
01447       }
01448 
01449       conf++;
01450     }
01451   }
01452 
01453   delete [] orientations;
01454 
01455   // Return the number of generated conformers
01456   return numconf;
01457 }
01458 
01459 
01460 // Perform simple symmetry check on the probe molecule.
01461 // Checks if the molecule is linear molecules and if it has an
01462 // additional horizontal symmetry plane (Cinfv vs. Dinfh pointgroup)
01463 // Also recognizes sp3 centers with 4 identical ligands like
01464 // methane (Td pointgroup).
01465 void VolMapCreateILS::check_probe_symmetry() {
01466   float principal[3];
01467   if (is_probe_linear(principal)) {
01468     probe_axisorder1 = -1;
01469     vec_copy(probe_symmaxis1, principal);
01470 
01471     // Check if there is an additional C2 axis, i.e.
01472     // is there an identical image for each atom.
01473     // This means the pointgroup would be Dinfv, 
01474     // otherwise we just have Cinfv.
01475     int Dinfv = 1;
01476     int i, j;
01477     for (i=0; i<num_probe_atoms; i++) {
01478       float image[3];
01479       vec_negate(image, &probe_coords[3*i]);
01480       int match = 1;
01481       for (j=i+1; j<num_probe_atoms; j++) {
01482         if (distance(&probe_coords[3*j], image)>0.05) continue;
01483         if (probe_vdw[2*i  ]!=probe_vdw[2*j  ] ||
01484             probe_vdw[2*i+1]!=probe_vdw[2*j+1] ||
01485             probe_charge[i]!=probe_charge[j]) {
01486           match = 0;
01487           break;
01488         }
01489       }
01490       if (!match) {
01491         Dinfv = 0;
01492         break;
01493       }
01494     }
01495 
01496     if (Dinfv) {
01497       // Construct perpendicular C2 symmetry axis
01498       float v[3]; // helper vector used to construct orthogonal
01499       vec_zero(v); v[0] = 1.f;
01500       if (fabs(dot_prod(probe_symmaxis1, v))>0.95) {
01501         // Almost parallel, choose a different vector
01502         v[0] = 0.f; v[1] = 1.f;
01503       }
01504       float cross[3];
01505       cross_prod(cross, probe_symmaxis1, v);
01506       cross_prod(probe_symmaxis2, cross, probe_symmaxis1);
01507       probe_axisorder2 = 2;
01508     }
01509   }
01510   else if (num_probe_atoms==5) {
01511     // Try a very simple check for tetrahedral symmetry:
01512     // It will recognize molecules with a central atom and
01513     // 4 equivalent atoms in the corners of a tetrahedron
01514     // such as methane.
01515 
01516     // Look for central atom
01517     int i, icenter = -1;
01518     float zero[3];
01519     vec_zero(zero);
01520     for (i=0; i<num_probe_atoms; i++) {
01521       if (distance(&probe_coords[3*i], zero)<0.05) {
01522         icenter = i;
01523         break;
01524       }
01525     }
01526 
01527     if (icenter>=0) {
01528       float corner[12];
01529       float vdweps=0.f, vdwrmin=0.f, charge=0.f, dist=0.f;
01530       int match = 1;
01531       int j = 0;
01532 
01533       // Check if all ligand atom have the same type and
01534       // build a coordinate list.
01535       for (i=0; i<num_probe_atoms; i++) {
01536         if (i==icenter) continue;
01537         if (j==0) {
01538           vdweps  = probe_vdw[2*i  ];
01539           vdwrmin = probe_vdw[2*i+1];
01540           charge  = probe_charge[i];
01541           dist = norm(&probe_coords[3*i]);
01542         }
01543         else if (probe_vdw[2*i  ] != vdweps  ||
01544                  probe_vdw[2*i+1] != vdwrmin ||
01545                  probe_charge[i]  != charge   ||
01546                  norm(&probe_coords[3*i])-dist > 0.05) {
01547           match = 0;
01548           break;
01549         }
01550 
01551         vec_copy(&corner[3*j], &probe_coords[3*i]);
01552         j++;
01553       }
01554 
01555       // Check the tetrahedral angles
01556       if (match &&
01557           angle(&corner[0], &corner[3])-109.47122f < 5.f &&
01558           angle(&corner[0], &corner[6])-109.47122f < 5.f &&
01559           angle(&corner[0], &corner[9])-109.47122f < 5.f &&
01560           angle(&corner[3], &corner[6])-109.47122f < 5.f &&
01561           angle(&corner[3], &corner[9])-109.47122f < 5.f &&
01562           angle(&corner[6], &corner[9])-109.47122f < 5.f) {
01563         probe_tetrahedralsymm = 1;
01564         probe_axisorder1 = 3;
01565         probe_axisorder2 = 3;
01566         vec_copy(probe_symmaxis1, &corner[0]);
01567         vec_copy(probe_symmaxis2, &corner[3]);
01568       }
01569     }
01570   }
01571 }
01572 
01573 
01574 // Generate probe conformations with uniformly distributed
01575 // orientations and rotations around the orientation vector
01576 // and store the resulting probe coordinates in *conformers.
01577 void VolMapCreateILS::initialize_probe() {
01578   // Perform simple check on probe symmetry and determine
01579   // symmetry axes and order.
01580   check_probe_symmetry();
01581 
01582   // We can make use of up to two symmetry axes in two
01583   // independent operations: The orientation of the probe's
01584   // principal axis and the rotation around it.
01585   // If only one axis was found or specified we will use it to
01586   // exploit symmetry during rotation of the probe around the
01587   // orientation vectors.
01588   // In case we have an additional (orthogonal) 2-fold rotary
01589   // axis we can omit half of the orientations. The idea is that
01590   // if a 180 deg rotation turns the molecule into an identical
01591   // image then we don't have to generate the conformer corresponding
01592   // to the orientation vector pointing in the opposite direction.
01593   // Actually this applies only to linear symmetric molecules like
01594   // oxygen, but since for each orientation the molecule will also be
01595   // rotated around the orientation vector we can extend the concept
01596   // to cases where such an additional rotation turns the flipped
01597   // molecule into the identical image. Strictly, this rotation
01598   // should correspond to one of the generated rotamers but because
01599   // the phase for generating the rotamers was chosen arbitrarily
01600   // anyway we don't need this correspondence.
01601   //
01602   // probe_axisorder1: for probe rotation
01603   // probe_axisorder2: for probe orientation
01604   if (probe_axisorder1==1 || 
01605       (probe_axisorder2!=1 && probe_axisorder2!=2)) {
01606     // Swap the axes
01607     int tmpord = probe_axisorder1;
01608     probe_axisorder1 = probe_axisorder2;
01609     probe_axisorder2 = tmpord;
01610 
01611     float tmp[3];
01612     vec_copy(tmp, probe_symmaxis1);
01613     vec_copy(probe_symmaxis1, probe_symmaxis2);
01614     vec_copy(probe_symmaxis2, tmp);
01615   }
01616 
01617 
01618   // Rotate the probe so that symmetry axis 1 is along X
01619   Matrix4 rot;
01620   rot.transvecinv(probe_symmaxis1[0], probe_symmaxis1[1], probe_symmaxis1[2]);
01621   int i;
01622   for (i=0; i<num_probe_atoms; i++) {
01623     rot.multpoint3d(&probe_coords[3*i], &probe_coords[3*i]);
01624   }
01625   rot.multpoint3d(probe_symmaxis1, probe_symmaxis1);
01626   rot.multpoint3d(probe_symmaxis2, probe_symmaxis2);
01627 
01628   // Rotate the probe so that symmetry axis 2 is along Y
01629   if (probe_axisorder2>1) {
01630     float cross[3], z[3];
01631     vec_zero(z); z[2] = 1.f;
01632     cross_prod(cross, probe_symmaxis1, probe_symmaxis2);
01633 
01634     float phi = angle(cross, z);
01635     rot.identity();
01636     rot.rotate_axis(probe_symmaxis1, float(DEGTORAD(phi)));
01637 
01638     for (i=0; i<num_probe_atoms; i++) {
01639       rot.multpoint3d(&probe_coords[3*i], &probe_coords[3*i]);
01640     }
01641     rot.multpoint3d(probe_symmaxis1, probe_symmaxis1);
01642     rot.multpoint3d(probe_symmaxis2, probe_symmaxis2);
01643   }
01644 
01645 
01646   if (getenv("VMDILSNOSYMM")) {
01647     // Omit any symmetry in the probe conformer generation
01648     // (useful for benchmarking).
01649     probe_axisorder1 = 1;
01650     probe_axisorder2 = 1;
01651     probe_tetrahedralsymm = 0;
01652     msgWarn << "env(VMDILSNOSYMM) is set: Ignoring probe symmetry!" << sendmsg;
01653   }
01654 
01655   num_orientations = 0;  // # symmetry unique orientations
01656   num_rotations    = 1;  // # rotamers per orientation
01657 
01658   if (probe_tetrahedralsymm) {
01659     msgInfo << "Probe symmetry: tetrahedral" << sendmsg;
01660 
01661     num_conformers = gen_conf_tetrahedral(conformers,
01662                          conformer_freq, num_orientations,
01663                          num_rotations);
01664   }
01665 
01666   else {
01667     // Probe is not tetrahedral, generate geodesic orientations
01668     // based on platonic solids.
01669 
01670     if (probe_axisorder1<=1 && probe_axisorder1<=1) {
01671       msgInfo << "Probe symmetry: none" << sendmsg;
01672     }
01673 
01674     else if (probe_axisorder1==-1) {
01675       if (probe_axisorder2==2) {
01676         msgInfo << "Probe symmetry: Dinfh (linear, C2)" << sendmsg;
01677       } else {
01678         msgInfo << "Probe symmetry: Cinfv (linear)" << sendmsg;
01679       }
01680       msgInfo << "  Probe is linear, generating probe orientations only." << sendmsg;
01681     }
01682     else if (probe_axisorder1>1) {
01683       if (probe_axisorder2==2) {
01684         msgInfo << "Probe symmetry: C" << probe_axisorder1
01685                 << ", C2" << sendmsg;
01686       } else {
01687         msgInfo << "Probe symmetry: C" << probe_axisorder1 << sendmsg;
01688       }
01689       msgInfo << "  Exploiting C" << probe_axisorder1
01690               << " rotary symmetry for rotation of the oriented probe." << sendmsg;
01691     }
01692 
01693     if (probe_axisorder2==2) {
01694       msgInfo << "  Exploiting C2 rotary symmetry for probe orientations" 
01695               << sendmsg;
01696     }
01697 
01698     // dimple_depth(180.f); // single orientation
01699     // dimple_depth(90.f); // hexahedron
01700     // dimple_depth(180.f-109.47122f); // octahedron
01701     // dimple_depth(180.f-116.56557f); // dodecahedron
01702     // dimple_depth(180.f-138.18966f); // icosahedron
01703     // dimple_depth(25.f); // icosahedron faces+vertices
01704 
01705     num_conformers = gen_conf(conformers, conformer_freq,
01706                               num_orientations, num_rotations);
01707   }
01708 
01709   msgInfo << "Probe orientations:       " << num_orientations
01710           << sendmsg;
01711   msgInfo << "Rotamers per orientation: " << num_rotations
01712           << sendmsg;
01713   msgInfo << "Conformers generated:     " << num_conformers
01714           << sendmsg << sendmsg;
01715 }
01716 
01717 
01718 // Check if the box given by the minmax coordinates is located
01719 // entirely inside the PBC unit cell of the given frame and in
01720 // this case return 1, otherwise return 0. 
01721 int VolMapCreateILS::box_inside_pbccell(int frame, float *minmaxcoor) {
01722   Matrix4 mat;
01723 
01724   // Get the PBC --> orthonormal unitcell transformation
01725   measure_pbc2onc(app->moleculeList, molid,
01726                   frame, pbccenter, mat);
01727 
01728   // Get vectors describing the box edges
01729   float box[9];
01730   memset(box, 0, 9*sizeof(float));
01731   box[0] = minmaxcoor[3]-minmaxcoor[0];
01732   box[4] = minmaxcoor[4]-minmaxcoor[1];
01733   box[8] = minmaxcoor[5]-minmaxcoor[2];
01734   // printf("box = {%g %g %g}\n", box[0], box[1], box[2]);
01735   // printf("box = {%g %g %g}\n", box[3], box[4], box[5]);
01736   // printf("box = {%g %g %g}\n", box[6], box[7], box[8]);
01737 
01738   // Create coordinates for the 8 corners of the box
01739   // and transform them into the system of the orthonormal
01740   // PBC unit cell.
01741   float node[8*3];
01742   memset(node, 0, 8*3*sizeof(float));
01743   int n=0;
01744   int i, j, k;
01745   for (i=0; i<=1; i++) {
01746     for (j=0; j<=1; j++) {
01747       for (k=0; k<=1; k++) {
01748         vec_copy(node+3*n, &minmaxcoor[0]);
01749         vec_scaled_add(node+3*n, float(i), &box[0]);
01750         vec_scaled_add(node+3*n, float(j), &box[3]);
01751         vec_scaled_add(node+3*n, float(k), &box[6]);
01752         // Apply the PBC --> orthonormal unitcell transformation
01753         // to the current test point.
01754         mat.multpoint3d(node+3*n, node+3*n);
01755         n++;
01756       }
01757     }
01758   }
01759 
01760   // Check if corners lie inside the orthonormal unitcell
01761   for (n=0; n<8; n++) {
01762     //printf("node[%i] = {%g %g %g}\n", n, node[3*n], node[3*n+1], node[3*n+2]);
01763     if (node[3*n  ]<0.f) return 0;
01764     if (node[3*n+1]<0.f) return 0;
01765     if (node[3*n+2]<0.f) return 0;
01766     if (node[3*n  ]>1.f) return 0;
01767     if (node[3*n+1]>1.f) return 0;
01768     if (node[3*n+2]>1.f) return 0;
01769   }
01770   return 1;
01771 }
01772 
01773 
01774 // Check if the entire volmap grid is located entirely inside
01775 // the PBC unit cell of the given frame (taking the alignment
01776 // into account) and in this case return 1, otherwise return 0.
01777 // Also sets the the gridpoints of volmap that are outside the
01778 // PBC cell to zero (used in the maskonly mode).
01779 int VolMapCreateILS::grid_inside_pbccell(int frame, float *maskvoldata, 
01780                                          const Matrix4 &alignment) {
01781   Matrix4 AA, BB, CC;
01782 
01783   Molecule *mol = app->moleculeList->mol_from_id(molid);
01784   mol->get_frame(frame)->get_transforms(AA, BB, CC);
01785 
01786   // Construct the cell spanning vectors
01787   float cella[3], cellb[3], cellc[3];
01788   cella[0] = AA.mat[12];
01789   cella[1] = AA.mat[13];
01790   cella[2] = AA.mat[14];
01791   cellb[0] = BB.mat[12];
01792   cellb[1] = BB.mat[13];
01793   cellb[2] = BB.mat[14];
01794   cellc[0] = CC.mat[12];
01795   cellc[1] = CC.mat[13];
01796   cellc[2] = CC.mat[14];
01797   // Construct the normals of the 6 cell boundary planes
01798   float normals[3*6];
01799   cross_prod(&normals[0], cella, cellb);
01800   cross_prod(&normals[3], cella, cellc);
01801   cross_prod(&normals[6], cellb, cellc);
01802   vec_normalize(&normals[0]);
01803   vec_normalize(&normals[3]);
01804   vec_normalize(&normals[6]);
01805   vec_scale(&normals[0], cutoff, &normals[0]);
01806   vec_scale(&normals[3], cutoff, &normals[3]);
01807   vec_scale(&normals[6], cutoff, &normals[6]);
01808   vec_negate(&normals[9],  &normals[0]);
01809   vec_negate(&normals[12], &normals[3]);
01810   vec_negate(&normals[15], &normals[6]);
01811 
01812   Matrix4 pbc2onc;
01813   int allinside = 1;
01814 
01815   // Get the PBC --> orthonormal unitcell transformation
01816   measure_pbc2onc(app->moleculeList, molid, frame, pbccenter, pbc2onc);
01817 
01818   // In order to transform a point P into the orthonormal cell (P') it 
01819   // first has to be unaligned (the inverse of the alignment):
01820   // P' = M_norm * (alignment^-1) * P
01821   Matrix4 alignmentinv(alignment);
01822   alignmentinv.inverse();
01823 
01824   Matrix4 coretransform(pbc2onc);
01825   coretransform.multmatrix(alignmentinv);
01826 
01827   float testpos[3], gridpos[3], extgridpos[3];
01828 
01829   int n;
01830   for (n=0; n<nsampx*nsampy*nsampz; n++) {
01831     // Position of grid cell's center
01832     gridpos[0] = float((n%nsampx)         *delta + gridorigin[0]);
01833     gridpos[1] = float(((n/nsampx)%nsampy)*delta + gridorigin[1]);
01834     gridpos[2] = float((n/(nsampx*nsampy))*delta + gridorigin[2]); 
01835 
01836     // Construct 6 test points that are at cutoff distance along
01837     // the 6 cell normal vectors. The closest system boundary
01838     // must lie along one of these normals. If all 6 points are
01839     // within the PBC cell then all possible interaction partner
01840     // will be within the cell, too.
01841     int i;
01842     for (i=0; i<6; i++) {
01843       vec_add(extgridpos, gridpos, &normals[3*i]);
01844       // Project into an orthonormal system that is convenient
01845       // for testing if a point is outside the cell:
01846       coretransform.multpoint3d(extgridpos, testpos);
01847       if (testpos[0]<0.f || testpos[0]>1.f ||
01848           testpos[1]<0.f || testpos[1]>1.f ||
01849           testpos[2]<0.f || testpos[2]>1.f) {
01850         // The test point is outside the PBC cell
01851         maskvoldata[n] = 0.f;
01852         allinside = 0;
01853         i = 6;
01854       }
01855     }
01856   }
01857 
01858   return allinside;
01859 }
01860 
01861 
01870 int VolMapCreateILS::create_unique_paramlist() {
01871   Molecule *mol = app->moleculeList->mol_from_id(molid);
01872 
01873   // typecast pointers to "flint" so that we can do int compare below
01874   const flint *radius = (flint *) mol->extraflt.data("radius");
01875   if (!radius) return MEASURE_ERR_NORADII;
01876   const flint *occupancy = (flint *) mol->extraflt.data("occupancy");
01877   if (!occupancy) return MEASURE_ERR_NORADII;
01878 
01879   int i, j;
01880 
01881 #define MAX_UNIQUE_TYPES  200
01882   // Any sane data set should have no more than about 25 unique types.
01883   // We guard against malformed data by setting an upper bound on the
01884   // number of types, preventing our O(N) search to devolve into O(N^2).
01885 
01886   atomtypes = new int[mol->nAtoms];  // each atom stores its type index
01887   atomtypes[0] = 0;  // first atom is automatically assigned first type
01888   flint *unique_occ = new flint[MAX_UNIQUE_TYPES];
01889   flint *unique_rad = new flint[MAX_UNIQUE_TYPES];
01890   unique_occ[0].f = occupancy[0].f;
01891   unique_rad[0].f = radius[0].f;
01892   num_unique_types = 1;
01893 
01894   for (i=1; i<mol->nAtoms; i++) {
01895     int found = 0;
01896     // Compare VDW params of current atom against all
01897     // existing unique VDW types.
01898     for (j=0; j<num_unique_types; j++) {
01899       // perform == test on ints because it's safer
01900       if (occupancy[i].i==unique_occ[j].i && radius[i].i==unique_rad[j].i) {
01901         found = 1;
01902         break;
01903       }
01904     }
01905     if (!found) {
01906       if (MAX_UNIQUE_TYPES==num_unique_types) {
01907         msgErr << "Exceeded maximum number " << MAX_UNIQUE_TYPES
01908                << " of unique atom parameter types" << sendmsg;
01909         return -1;
01910       }
01911       // No matching VDW type found, create a new one
01912       unique_occ[j].f = occupancy[i].f;
01913       unique_rad[j].f = radius[i].f;
01914       num_unique_types++;
01915     }
01916     atomtypes[i] = j;
01917   }
01918 
01919   vdwparams  = new float[2*num_unique_types];
01920   for (j=0; j<num_unique_types; j++) {
01921     // check validity of VDW parameters
01922     if ( !(unique_occ[j].f <= 0.f && unique_rad[j].f > 0.f) ) {
01923       msgErr << "Found invalid VDW parameters " << j
01924         << ": occupancy=" << unique_occ[j].f
01925         << ", radius=" << unique_rad[j].f
01926         << sendmsg;
01927       return -1;
01928     }
01929     // The combination rule for VDW eps of 2 atoms is:
01930     // eps(i,j)  = sqrt(eps(i)) * sqrt(eps(j))
01931     // so we take the sqrt here already.
01932     vdwparams[2*j  ] = sqrtf(-unique_occ[j].f);
01933     vdwparams[2*j+1] = unique_rad[j].f;
01934   }
01935   delete [] unique_occ;
01936   delete [] unique_rad;
01937 
01938   msgInfo << "Number of atom types: " << num_unique_types << sendmsg;
01939 
01940 #if 0
01941   float *epsilon = new float[mol->nAtoms];
01942   for (i=0; i<mol->nAtoms; i++) {
01943     // The combination rule for VDW eps of 2 atoms is:
01944     // eps(i,j)  = sqrt(eps(i)) * sqrt(eps(j))
01945     // so we take the sqrt here already.
01946     epsilon[i] = sqrtf(-occupancy[i]);
01947   }  
01948 
01949   atomtypes = new int[mol->nAtoms];
01950   atomtypes[0] = 0;
01951   float *unique_eps = new float[mol->nAtoms];
01952   float *unique_rad = new float[mol->nAtoms];
01953   unique_eps[0] = epsilon[0];
01954   unique_rad[0] = radius[0];
01955   num_unique_types = 1;
01956 
01957   for (i=1; i<mol->nAtoms; i++) {
01958     int found = 0;
01959     // Compare VDW params of current atom against all
01960     // existing unique VDW types.
01961     for (j=0; j<num_unique_types; j++) {
01962       if (epsilon[i]==unique_eps[j] && radius[i]==unique_rad[j]) {
01963         found = 1;
01964         break;
01965       }
01966     }
01967     if (!found) {
01968       // No matching VDW type found, create a new one
01969       unique_eps[j] = epsilon[i];
01970       unique_rad[j] = radius[i];
01971       num_unique_types++;
01972     }
01973     atomtypes[i] = j;
01974   }
01975 
01976   vdwparams  = new float[2*num_unique_types];
01977   for (j=0; j<num_unique_types; j++) {
01978     vdwparams[2*j  ] = unique_eps[j];
01979     vdwparams[2*j+1] = unique_rad[j];
01980     //printf("eps=%f, rmin=%f\n", unique_eps[j], unique_rad[j]);
01981   }
01982   delete [] epsilon;
01983   delete [] unique_eps;
01984   delete [] unique_rad;
01985 
01986   msgInfo << "Number of atom types: " << num_unique_types << sendmsg;
01987 #endif
01988 
01989   return 0;
01990 }
01991 
01992 
01993 // Perform ILS calculation for all specified frames.
01994 int VolMapCreateILS::compute() {
01995   int numframes = app->molecule_numframes(molid);
01996   if (first<0 || last>=numframes) return -1;
01997 
01998   int err = initialize();
01999   if (err) return err;
02000   
02001   int n, frame;
02002   int gridsize = nsampx*nsampy*nsampz;
02003   float *frame_voldata = new float[gridsize]; // individual frame voldata
02004   float *combo_voldata = new float[gridsize]; // combo cache voldata
02005   if (maskonly) {
02006     // Fill mask map with ones
02007     for (n=0; n<gridsize; n++) {
02008       combo_voldata[n] =  1.f;
02009       frame_voldata[n] =  1.f;
02010     }    
02011   } else {
02012     memset(combo_voldata, 0, gridsize*sizeof(float));
02013   }
02014 
02015   msgInfo << "Processing frames " << first << "-" << last
02016           << ", " << last-first+1 << " frames in total..." << sendmsg;
02017 
02018   computed_frames = 0;
02019 
02020   wkf_timerhandle timer = wkf_timer_create();
02021   wkf_timerhandle alltimer = wkf_timer_create();
02022   wkf_timer_start(alltimer);
02023 
02024   // Combine frame_voldata into combo_voldata, one frame at a time
02025   for (frame=first; frame<=last; frame++) { 
02026     msgInfo << "ILS frame " << frame-first+1 << "/" << last-first+1;
02027 
02028 #ifdef TIMING
02029     msgInfo << sendmsg;
02030 #else
02031     msgInfo << "   ";
02032 #endif
02033 
02034     wkf_timer_start(timer);
02035 
02036     // Perform the actual ILS calculation for this frame
02037     compute_frame(frame, frame_voldata);
02038 
02039     msgInfo << "Total frame time = " << wkf_timer_timenow(timer) << " s" << sendmsg;
02040 
02041     if (maskonly) {
02042       for (n=0; n<gridsize; n++) {
02043         combo_voldata[n] *= frame_voldata[n];
02044       }
02045     } else {
02046       // For each cell combine occupancies of the new frame with the
02047       // sum of the existing ones (we will divide by the number of
02048       // frames later to get the average).
02049       int numexcl = 0;
02050       for (n=0; n<gridsize; n++) {
02051         combo_voldata[n] += frame_voldata[n];
02052         if (frame_voldata[n]<=min_occup) numexcl++;
02053       }
02054       //printf("numexcl = %d/%d\n", numexcl, gridsize);
02055     }
02056 
02057     computed_frames++;
02058   }
02059 
02060   double allframetime = wkf_timer_timenow(alltimer);
02061 
02062   // Downsampling of the final map
02063   if (nsubsamp>1||1) {
02064     int ndownsampx = volmap->xsize;
02065     int ndownsampy = volmap->ysize;
02066     int ix, iy, iz;
02067 
02068     if (maskonly) {
02069       for (iz=0; iz<nsampz; iz++) {
02070         int isubz = iz/nsubsamp*ndownsampy*ndownsampx;
02071         for (iy=0; iy<nsampy; iy++) {
02072           int isuby = iy/nsubsamp*ndownsampx;
02073           for (ix=0; ix<nsampx; ix++) {
02074             n = iz*nsampy*nsampx + iy*nsampx + ix;
02075             float val = combo_voldata[n];
02076             int m = isubz + isuby + ix/nsubsamp; 
02077             // If any of the subsamples where zero,
02078             // the downsampled voxel will be zero:
02079             volmap->data[m] *= val; 
02080           }
02081         }
02082       }
02083 
02084     } else {
02085       for (iz=0; iz<nsampz; iz++) {
02086         int isubz = iz/nsubsamp*ndownsampy*ndownsampx;
02087         for (iy=0; iy<nsampy; iy++) {
02088           int isuby = iy/nsubsamp*ndownsampx;
02089           for (ix=0; ix<nsampx; ix++) {
02090             n = iz*nsampy*nsampx + iy*nsampx + ix;
02091             float val = combo_voldata[n];
02092             int m = isubz + isuby + ix/nsubsamp; 
02093             //printf("%d: val[%2d,%2d,%2d]=%g -->%d\n", n, ix, iy, iz, val, m);
02094             volmap->data[m] += val;
02095           }
02096         }
02097       }
02098 
02099       // Finally, we have to divide by the number of frames
02100       // and by the number of subsamples.
02101       float nsamppercell = float(nsubsamp*nsubsamp*nsubsamp*computed_frames);
02102       for (n=0; n<volmap->gridsize(); n++) {
02103         volmap->data[n] = volmap->data[n]/nsamppercell;
02104       }
02105     }
02106 
02107     if (!maskonly) {
02108       // Our final maps contain the PMF W which is related to the
02109       // occupancy rho (the probability to find a particle at
02110       // that point) by
02111       //
02112       // W = -ln(rho);  [in units of kT]
02113       //
02114       // Additionally we clamp large energies to the user-provided
02115       // max_energy value.
02116       //
02117       for (n=0; n<volmap->gridsize(); n++) {
02118         float val = volmap->data[n];
02119         if (val<=min_occup) {
02120           volmap->data[n] = max_energy;
02121         } else {
02122           val = -logf(val);
02123           if (val > max_energy) val = max_energy;
02124           volmap->data[n] = val;
02125         }
02126       }
02127     }
02128   }
02129 
02130   delete[] frame_voldata;
02131   delete[] combo_voldata;
02132 
02133   msgInfo << "#################################################"
02134           << sendmsg << sendmsg;
02135   msgInfo << "Total time for all frames = "
02136           << allframetime << " s" << sendmsg;
02137   msgInfo << "Avg time per frame        = " 
02138           << allframetime/(last-first+1) << " s" << sendmsg;
02139   msgInfo << "Downsampling              = "
02140           << wkf_timer_timenow(alltimer)-allframetime << " s"
02141           << sendmsg << sendmsg;
02142   msgInfo << "#################################################"
02143           << sendmsg << sendmsg;
02144 
02145   wkf_timer_destroy(timer);
02146   wkf_timer_destroy(alltimer);
02147 
02148   return 0;
02149 }
02150 
02151 
02152 
02153 // Align current frame to the reference
02154 void VolMapCreateILS::align_frame(Molecule *mol, int frame, float *coords,
02155                                   Matrix4 &alignment) {
02156   // In case alignsel is not NULL we align the current frame to the
02157   // first frame according to the provided selection.
02158   if (alignsel) {
02159     int i;
02160     int save_frame_alignsel = alignsel->which_frame;
02161     
02162     alignsel->which_frame = frame;
02163     alignsel->change(NULL, mol);
02164 
02165     float *weight = new float[alignsel->selected]; 
02166     for (i=0; i<alignsel->selected; i++) weight[i] = 1.0;
02167     
02168     measure_fit(alignsel, alignsel, coords, alignrefpos, weight, NULL, &alignment);
02169     delete[] weight;
02170     
02171     if (!getenv("VMDILSALIGNMAPS")) {
02172       // Do the alignment
02173       // (For the neighboring pbc coordinates the alignment is 
02174       // implicitely done below).
02175       for (i=0; i < mol->nAtoms; i++) { 
02176         alignment.multpoint3d(coords, coords);
02177         coords += 3;
02178       }
02179     }
02180     
02181     alignsel->which_frame = save_frame_alignsel;
02182   }
02183 
02184   // Combine frame alignment with general transform (global alignment)
02185   alignment.multmatrix(transform);
02186 }
02187 
02188 
02189 // Get array of coordinates of selected atoms.
02190 // If the pbc flag was set we also generate coordinates
02191 // for atoms within a cutoff in adjacent pbc image cells.
02192 // The image coordinates can be related to the according atoms
02193 // in the main cell through the indexmap.
02194 int VolMapCreateILS::get_atom_coordinates(int frame, Matrix4 &alignment,
02195                                           int *(&vdwtypes),
02196                                           float *(&coords)) {
02197   wkf_timerhandle timer = wkf_timer_create();
02198   wkf_timer_start(timer);
02199 
02200   // Select all atoms within the extended cutoff of the
02201   // user specified grid minmax box.
02202   int *selon = new int[num_atoms];
02203   memset(selon, 0, num_atoms*sizeof(int));
02204 
02205   float minx = minmax[0]-extcutoff;
02206   float miny = minmax[1]-extcutoff;
02207   float minz = minmax[2]-extcutoff;
02208   float maxx = minmax[3]+extcutoff;
02209   float maxy = minmax[4]+extcutoff;
02210   float maxz = minmax[5]+extcutoff;
02211 
02212   int numselected = 0;
02213   int i;
02214   for (i=0; i<num_atoms; i++) {
02215     float x = coords[3*i  ];
02216     float y = coords[3*i+1];
02217     float z = coords[3*i+2];
02218     if (x>=minx && x<=maxx &&
02219         y>=miny && y<=maxy &&
02220         z>=minz && z<=maxz) {
02221       selon[i] = 1;
02222       numselected++;
02223     }
02224   }
02225 
02226   int numcoords = numselected;
02227 
02228   float *selcoords = NULL;
02229 
02230   // If pbc is set the user requests a PBC aware computation and we
02231   // must generate the extended coordinates, i.e. the positions of
02232   // the atoms in the neighboring pbc cells.
02233   if (pbc) {
02234     // Automatically add the force field cutoff to the system.
02235     float ffcutoff[3];
02236     ffcutoff[0] = ffcutoff[1] = ffcutoff[2] = cutoff;
02237 
02238     // Positions of the atoms in the neighboring pbc cells.
02239     ResizeArray<float> extcoord_array;
02240 
02241     // The indexmap_array contains the index of the according
02242     // unitcell atom for each extended atom.
02243     ResizeArray<int>   indexmap_array;
02244 
02245     // Generate coordinates for atoms in the neighboring cells.
02246     // The indexmap_array tells to which atom the extended coordinate
02247     // corresponds.
02248     // We have to use NULL instead of sel (2nd parameter) in order
02249     // to return all PBC neighbors and not only the ones within
02250     // ffcutoff of sel. The reason is that we have no atomselection
02251     // and are computing the interaction between the all system atoms
02252     // and the probe located at the gridpoints.
02253     measure_pbc_neighbors(app->moleculeList, NULL, molid, frame,
02254                           &alignment, pbccenter, ffcutoff, minmax, 
02255                           &extcoord_array, &indexmap_array);
02256 
02257     numcoords = numselected+indexmap_array.num();
02258 
02259     selcoords = new float[3*numcoords];
02260     vdwtypes  = new int[numcoords];
02261 
02262     int j = numselected;
02263     for (i=0; i<indexmap_array.num(); i++) {
02264       selcoords[3*j  ] = extcoord_array[3*i  ];
02265       selcoords[3*j+1] = extcoord_array[3*i+1];
02266       selcoords[3*j+2] = extcoord_array[3*i+2];
02267       vdwtypes[j] = atomtypes[indexmap_array[i]];
02268       j++;
02269     }
02270 
02271     //printf("volmap: considering %d PBC neighbor atoms.\n", indexmap_array.num());
02272   } else {
02273     selcoords = new float[3*numcoords];
02274     vdwtypes  = new int[numcoords];
02275   }
02276 
02277   // Get the core coordinates (selected atoms in the main PBC cell)
02278   int j=0;
02279   for (i=0; i<num_atoms; i++) { 
02280     if (!selon[i]) continue; //atom is not selected
02281     selcoords[3*j  ] = coords[3*i  ];
02282     selcoords[3*j+1] = coords[3*i+1];
02283     selcoords[3*j+2] = coords[3*i+2];
02284     vdwtypes[j] = atomtypes[i];
02285     j++;
02286   }
02287   //printf("volmap: considering %d core atoms.\n", j);
02288 
02289   coords = selcoords;
02290 
02291   delete [] selon;
02292 
02293 #ifdef TIMING
02294   msgInfo << "Coord setup: " << wkf_timer_timenow(timer) << " s" << sendmsg;
02295 #endif
02296   wkf_timer_destroy(timer);
02297 
02298   return numcoords;
02299 }
02300 
02301 
02302 
02304 //  This is the function driving ILS for each frame    //
02306 
02307 // Computes, for each gridpoint, the VdW energy to the nearest atoms
02308 int VolMapCreateILS::compute_frame(int frame, float *voldata) { 
02309   Matrix4 alignment;
02310   float *coords;
02311 
02312   Molecule *mol = app->moleculeList->mol_from_id(molid);
02313   if (!mol) return -1;
02314 
02315 #ifdef TIMING
02316   char report[128];
02317   wkf_timerhandle timer = wkf_timer_create();
02318 #endif
02319 
02320   // Advance to next frame
02321   coords = mol->get_frame(frame)->pos;
02322 
02323   // In case alignsel is not NULL, align the current frame to the
02324   // first frame according to the provided selection and get the
02325   // alignment transformation matrix.
02326   align_frame(mol, frame, coords, alignment);
02327 
02328   if (maskonly) {
02329 #ifdef TIMING
02330     wkf_timer_start(timer);
02331 #endif
02332 
02333     // We are only creating a mask map that defines the
02334     // gridpoints that (after alignment) still overlap in 
02335     // each frame with the reference grid and thus are not
02336     // undersampled.
02337     grid_inside_pbccell(frame, voldata, alignment);
02338 
02339 #ifdef TIMING
02340     msgInfo << "Masking:     " << wkf_timer_timenow(timer) << " s" << sendmsg;
02341 #endif
02342 
02343     return MEASURE_NOERR;
02344   }
02345 
02346 
02347   // Get array of coordinates of selected atoms and their
02348   // neighbors (within a cutoff) in the PBC images.
02349   // Memory for *vdwtypes and *coords will be allocated.
02350   int *vdwtypes = NULL;
02351   int numcoords;
02352   float originalign[3];
02353   float axesalign[9];
02354   float gridaxes[9];
02355   memset(gridaxes, 0, 9*sizeof(float));
02356   gridaxes[0] = gridaxes[4] = gridaxes[8] = 1.f;
02357 
02358   if (getenv("VMDILSALIGNMAPS")) {
02359     // We use all atoms unaligned, but be align the map instead
02360     numcoords = num_atoms;
02361     vdwtypes = atomtypes;
02362     alignment.multpoint3d(gridorigin, originalign);
02363     alignment.multpoint3d(&gridaxes[0], &axesalign[0]);
02364     alignment.multpoint3d(&gridaxes[3], &axesalign[3]);
02365     alignment.multpoint3d(&gridaxes[6], &axesalign[6]);
02366     msgInfo << "Aligning maps." << sendmsg;
02367   } else {
02368     // Get extended list of aligned atom coordinates
02369     numcoords = get_atom_coordinates(frame, alignment,
02370                                      vdwtypes, coords);
02371     memcpy(originalign, gridorigin, 3*sizeof(float));
02372     memcpy(axesalign,   gridaxes,   9*sizeof(float));
02373     msgInfo << "Aligning frames." << sendmsg;
02374   }
02375   
02376   if (getenv("VMDALLATOMILS")) {
02377 #ifdef TIMING
02378     wkf_timer_start(timer);
02379 #endif
02380     
02381     // Assuming the grid is aligned with the coordinate axes:
02382     float lenx = float(nsampx*delta);
02383     float leny = float(nsampy*delta);
02384     float lenz = float(nsampz*delta);
02385     
02386     compute_allatoms(voldata, nsampx, nsampy, nsampz,
02387                      lenx, leny, lenz, originalign, axesalign,
02388                      alignment.mat, numcoords, coords,
02389                      vdwtypes, vdwparams, cutoff, probe_vdw, num_probe_atoms,
02390                      num_conformers, conformers, max_energy); 
02391     
02392 #ifdef TIMING
02393     sprintf(report, "compute_allatoms()                                     "
02394         "%f s\n", wkf_timer_timenow(timer));
02395     msgInfo << report << sendmsg;
02396 #endif
02397 
02398   } else {
02399 
02400 #ifdef TIMING
02401     wkf_timer_start(timer);
02402 #endif
02403 
02404     // Assuming the grid is aligned with the coordinate axes:
02405     float lenx = float(nsampx*delta);
02406     float leny = float(nsampy*delta);
02407     float lenz = float(nsampz*delta);
02408 
02409     int retval;
02410 
02411     ComputeOccupancyMap om;
02412 
02413     // must be set by caller
02414     om.map = voldata;
02415     om.mx = nsampx;
02416     om.my = nsampy;
02417     om.mz = nsampz;
02418     om.lx = lenx;
02419     om.ly = leny;
02420     om.lz = lenz;
02421     om.x0 = originalign[0];
02422     om.y0 = originalign[1];
02423     om.z0 = originalign[2];
02424     memcpy(om.ax, &axesalign[0], 3*sizeof(float));
02425     memcpy(om.ay, &axesalign[3], 3*sizeof(float));
02426     memcpy(om.az, &axesalign[6], 3*sizeof(float));
02427     memcpy(om.alignmat, alignment.mat, 16*sizeof(float));
02428     om.num_coords = numcoords;
02429     om.coords = coords;
02430     om.vdw_type = vdwtypes;
02431     om.vdw_params = vdwparams;
02432     om.probe_vdw_params = probe_vdw;
02433     om.conformers = conformers;
02434     om.num_probes = num_probe_atoms;
02435     om.num_conformers = num_conformers;
02436     om.cutoff = cutoff;
02437     om.extcutoff = extcutoff;
02438     om.excl_dist = excl_dist; 
02439     om.excl_energy = max_energy;
02440 
02441     // single threaded version calculates one largest slab
02442     om.kstart = 0;
02443     om.kstop = om.mz;
02444 
02445     retval = ComputeOccupancyMap_setup(&om);
02446 
02447 #ifdef TIMING
02448     sprintf(report, "ComputeOccupancyMap_setup()                            "
02449         "%f s\n", wkf_timer_timenow(timer));
02450     msgInfo << report << sendmsg;
02451 #endif
02452 
02453     if (getenv("VMDILSVERBOSE")) { // XXX debugging
02454       atom_bin_stats(&om
02455           /*
02456           gridorigin, lenx, leny, lenz, extcutoff, cutoff,
02457           om.bincnt, om.nbx, om.nby, om.nbz, om.padx,
02458           om.num_bin_offsets, om.num_extras */);
02459     }
02460 
02461     if (retval != 0) {
02462       if (getenv("VMDILSVERBOSE")) { // XXX debugging
02463         int i, j, k;
02464         int total_extra_atoms = 0;
02465         for (k = 0;  k < om.nbz;  k++) {
02466           for (j = 0;  j < om.nby;  j++) {
02467             for (i = 0;  i < om.nbx;  i++) {
02468               int index = (k*om.nby + j)*om.nbx + i;
02469               if (om.bincnt[index] > BIN_DEPTH) {
02470                 printf("*** bin[%d,%d,%d] tried to fill with %d atoms\n",
02471                     i, j, k, om.bincnt[index]);
02472                 total_extra_atoms += om.bincnt[index] - BIN_DEPTH;
02473               }
02474             }
02475           }
02476         }
02477         // XXX should have total_extra_atoms > num_extra_atoms
02478         printf("*** can't handle total of %d extra atoms\n", total_extra_atoms);
02479       }
02480       ComputeOccupancyMap_cleanup(&om);
02481       return -1;
02482     }
02483 
02484 #if defined(VMDCUDA)
02485     if (getenv("VMDILSVERBOSE")) {
02486       printf("*** cpu only = %d\n", om.cpu_only);
02487     }
02488     if (!getenv("VMDNOCUDA") && !(om.cpu_only) &&
02489         (retval=
02490          vmd_cuda_evaluate_occupancy_map(om.mx, om.my, om.mz, om.map,
02491            om.excl_energy, om.cutoff, om.hx, om.hy, om.hz,
02492            om.x0, om.y0, om.z0, om.bx_1, om.by_1, om.bz_1, 
02493            om.nbx, om.nby, om.nbz, (float *) om.bin, (float *) om.bin_zero,
02494            om.num_bin_offsets, om.bin_offsets,
02495            om.num_extras, (float *) om.extra,
02496            num_unique_types, om.vdw_params,
02497            om.num_probes, om.probe_vdw_params,
02498            om.num_conformers, om.conformers)) == 0) {
02499       // successfully ran ILS with CUDA, otherwise fall back on CPU
02500     } else {
02501       if (retval != 0) {
02502         msgInfo << "vmd_cuda_evaluate_occupancy_map() FAILED,"
02503           " using CPU for calculation\n" << sendmsg;
02504       }
02505 #endif /* CUDA... */
02506 
02507 #ifdef TIMING
02508       wkf_timer_start(timer);
02509 #endif
02510 
02511       retval = ComputeOccupancyMap_calculate_slab(&om);
02512 
02513 #ifdef TIMING
02514       sprintf(report, "ComputeOccupancyMap_calculate_slab()                   "
02515               "%f s\n", wkf_timer_timenow(timer));
02516       msgInfo << report << sendmsg;
02517 #endif
02518 
02519       if (retval != 0) {
02520         if (getenv("VMDILSVERBOSE")) { // XXX debugging
02521           printf("*** ComputeOccupancyMap_calculate_slab() failed\n");
02522         }
02523         ComputeOccupancyMap_cleanup(&om);
02524         return -1;
02525       }
02526 
02527 #if defined(VMDCUDA)
02528     } // end else not VMDCUDAILS
02529 #endif
02530 
02531     ComputeOccupancyMap_cleanup(&om);
02532   }
02533 
02534   if (!getenv("VMDILSALIGNMAPS")) {
02535     delete[] coords;
02536     delete[] vdwtypes;
02537   }
02538 
02539 #ifdef TIMING
02540   wkf_timer_destroy(timer);
02541 #endif
02542       
02543   return MEASURE_NOERR; 
02544 }
02545 
02546 
02548 // Here follows the new implementation.                //
02550 
02551 static int fill_atom_bins(ComputeOccupancyMap *p);
02552 static void tighten_bin_neighborhood(ComputeOccupancyMap *p);
02553 static void find_distance_exclusions(ComputeOccupancyMap *p);
02554 static void find_energy_exclusions(ComputeOccupancyMap *p);
02555 static void compute_occupancy_monoatom(ComputeOccupancyMap *p);
02556 static void compute_occupancy_multiatom(ComputeOccupancyMap *p);
02557 
02558 
02559 int ComputeOccupancyMap_setup(ComputeOccupancyMap *p) {
02560 
02561   // initialize pointer fields to zero
02562   p->bin = NULL;
02563   p->bin_zero = NULL;
02564   p->bincnt = NULL;
02565   p->bincnt_zero = NULL;
02566   p->bin_offsets = NULL;
02567   p->extra = NULL;
02568   p->exclusions = NULL;
02569 
02570   // initialize occupancy map, allocate and initialize exclusion map
02571   int mtotal = p->mx * p->my * p->mz;
02572   memset(p->map, 0, mtotal * sizeof(float));  // zero occupancy by default
02573   p->exclusions = new char[mtotal];
02574   memset(p->exclusions, 0, mtotal * sizeof(char));  // no exclusions yet
02575 
02576   // derive map spacing based on length and number of points
02577   p->hx = p->lx / p->mx;
02578   p->hy = p->ly / p->my;
02579   p->hz = p->lz / p->mz;
02580 
02581   p->cpu_only = 0;  // attempt to use CUDA
02582 
02583   // set expected map points per bin length
02584   // note: we want CUDA thread blocks to calculate 4^3 map points
02585   //       we want each thread block contained inside a single bin
02586   //       we want bin volume to be no more than MAX_BIN_VOLUME (27 A^3)
02587   //       and no smaller than MIN_BIN_VOLUME (8 A^3) due to fixed bin depth
02588   //       we expect map spacing to be about 0.25 A but depends on caller
02589 
02590   // start with trying to pack 3^3 thread blocks per atom bin
02591   p->mpblx = 12;
02592   p->mpbly = 12;
02593   p->mpblz = 12;
02594 
02595   // starting bin lengths
02596   p->bx = p->mpblx * p->hx;
02597   p->by = p->mpbly * p->hy;
02598   p->bz = p->mpblz * p->hz;
02599 
02600   // refine bin side lengths if volume of bin is too large
02601   while (p->bx * p->by * p->bz > MAX_BIN_VOLUME) {
02602 
02603     // find longest bin side and reduce its length
02604     if (p->bx > p->by && p->bx > p->bz) {
02605       p->mpblx -= 4;
02606       p->bx = p->mpblx * p->hx;
02607     }
02608     else if (p->by >= p->bx && p->by > p->bz) {
02609       p->mpbly -= 4;
02610       p->by = p->mpbly * p->hy;
02611     }
02612     else {
02613       p->mpblz -= 4;
02614       p->bz = p->mpblz * p->hz;
02615     }
02616 
02617   } // end refinement of bins
02618 
02619   if (p->bx * p->by * p->bz < MIN_BIN_VOLUME) {
02620     // refinement failed due to some hx, hy, hz being too large
02621     // now there is no known correspondence between map points and bins
02622     p->bx = p->by = p->bz = DEFAULT_BIN_LENGTH;
02623     p->mpblx = p->mpbly = p->mpblz = 0;
02624     p->cpu_only = 1;  // CUDA can't be used, map too coarse
02625   }
02626 
02627   p->bx_1 = 1.f / p->bx;
02628   p->by_1 = 1.f / p->by;
02629   p->bz_1 = 1.f / p->bz;
02630 
02631   if (fill_atom_bins(p)) {
02632     return -1;  // failed due to too many extra atoms for bin size
02633   }
02634 
02635   tighten_bin_neighborhood(p);
02636 
02637   return 0;
02638 } // ComputeOccupancyMap_setup()
02639 
02640 
02641 int ComputeOccupancyMap_calculate_slab(ComputeOccupancyMap *p) {
02642 #ifdef TIMING
02643   char report[128];
02644   wkf_timerhandle timer = wkf_timer_create();
02645 #endif
02646 
02647   // each of these routines operates on the slab
02648   // designated by kstart through kstop (z-axis indices)
02649   //
02650   // XXX we are planning CUDA kernels for each of the following routines
02651 
02652 #if 1
02653 #ifdef TIMING
02654   wkf_timer_start(timer);
02655 #endif
02656   find_distance_exclusions(p);
02657   int i, numexcl=0;
02658   for (i=0; i<p->mx * p->my * p->mz; i++) {
02659     if (p->exclusions[i]) numexcl++;
02660   }
02661 #ifdef TIMING
02662   sprintf(report, "ComputeOccupancyMap: find_distance_exclusions()        "
02663       "%f s\n", wkf_timer_timenow(timer));
02664   msgInfo << report << sendmsg;
02665 #endif
02666 #endif
02667 
02668   if (1 == p->num_probes) {
02669 #ifdef TIMING
02670     wkf_timer_start(timer);
02671 #endif
02672     compute_occupancy_monoatom(p);
02673 #ifdef TIMING
02674     sprintf(report, "ComputeOccupancyMap: compute_occupancy_monoatom()      "
02675         "%f s\n", wkf_timer_timenow(timer));
02676     msgInfo << report << sendmsg;
02677 #endif
02678 
02679   }
02680   else {
02681 
02682 #if 1
02683 #ifdef TIMING
02684     wkf_timer_start(timer);
02685 #endif
02686     find_energy_exclusions(p);
02687     int i, numexcl=0;
02688     for (i=0; i<p->mx * p->my * p->mz; i++) {
02689       if (p->exclusions[i]) numexcl++;
02690     }
02691 #ifdef TIMING
02692     sprintf(report, "ComputeOccupancyMap: find_energy_exclusions()          "
02693         "%f s  -> %d exclusions\n", wkf_timer_timenow(timer), numexcl);
02694     msgInfo << report << sendmsg;
02695 #endif
02696 #endif
02697 
02698 #ifdef TIMING
02699     wkf_timer_start(timer);
02700 #endif
02701     compute_occupancy_multiatom(p);
02702 #ifdef TIMING
02703     sprintf(report, "ComputeOccupancyMap: compute_occupancy_multiatom()     "
02704         "%f s\n", wkf_timer_timenow(timer));
02705     msgInfo << report << sendmsg;
02706 #endif
02707 
02708   }
02709 
02710 #ifdef TIMING
02711   wkf_timer_destroy(timer);
02712 #endif
02713 
02714   return 0;
02715 } // ComputeOccupancyMap_calculate_slab()
02716 
02717 
02718 void ComputeOccupancyMap_cleanup(ComputeOccupancyMap *p) {
02719   delete[] p->bin_offsets;
02720   delete[] p->extra;
02721   delete[] p->bincnt;
02722   delete[] p->bin;
02723   delete[] p->exclusions;
02724 } // ComputeOccupancyMap_cleanup()
02725 
02726 
02727 // XXX the CUDA kernels can handle up to 50 extra atoms, set this as
02728 // the bound on "max_extra_atoms" rather than the heuristic below
02729 #define MAX_EXTRA_ATOMS  50
02730 
02731 int fill_atom_bins(ComputeOccupancyMap *p) {
02732   int too_many_extra_atoms = 0;  // be optimistic
02733   int max_extra_atoms = MAX_EXTRA_ATOMS;
02734   //int max_extra_atoms = (int) ceilf(p->num_coords / 10000.f);
02735       // assume no more than 1 over full bin per 10000 atoms
02736   int count_extras = 0;
02737   int n, i, j, k;
02738 
02739   const int *vdw_type = p->vdw_type;
02740   const float *coords = p->coords;
02741   const int num_coords = p->num_coords;
02742   const float lx = p->lx;
02743   const float ly = p->ly;
02744   const float lz = p->lz;
02745   const float bx_1 = p->bx_1;
02746   const float by_1 = p->by_1;
02747   const float bz_1 = p->bz_1;
02748   const float x0 = p->x0;
02749   const float y0 = p->y0;
02750   const float z0 = p->z0;
02751   const float extcutoff = p->extcutoff;
02752 
02753   // padding is based on extended cutoff distance
02754   p->padx = (int) ceilf(extcutoff * bx_1);
02755   p->pady = (int) ceilf(extcutoff * by_1);
02756   p->padz = (int) ceilf(extcutoff * bz_1);
02757 
02758   const int nbx = p->nbx = (int) ceilf(lx * bx_1) + 2*p->padx;
02759   const int nby = p->nby = (int) ceilf(ly * by_1) + 2*p->pady;
02760   p->nbz = (int) ceilf(lz * bz_1) + 2*p->padz;
02761 
02762   int nbins = nbx * nby * p->nbz;
02763 
02764   BinOfAtoms *bin = p->bin = new BinOfAtoms[nbins];
02765   char *bincnt = p->bincnt = new char[nbins];
02766   AtomPosType *extra = p->extra = new AtomPosType[max_extra_atoms];
02767 
02768   memset(bin, 0, nbins * sizeof(BinOfAtoms));
02769   memset(bincnt, 0, nbins * sizeof(char));
02770 
02771   // shift array pointer to the (0,0,0)-bin, which will correspond to
02772   // the map origin
02773   BinOfAtoms *bin_zero
02774     = p->bin_zero = bin + ((p->padz*nby + p->pady)*nbx + p->padx);
02775   char *bincnt_zero
02776     = p->bincnt_zero = bincnt + ((p->padz*nby + p->pady)*nbx + p->padx);
02777 
02778   for (n = 0;  n < num_coords;  n++) {
02779     float x = coords[3*n    ];  // atom coordinates
02780     float y = coords[3*n + 1];
02781     float z = coords[3*n + 2];
02782 
02783     float sx = x - x0;  // translate relative to map origin
02784     float sy = y - y0;
02785     float sz = z - z0;
02786 
02787     if (sx < -extcutoff || sx > lx + extcutoff ||
02788         sy < -extcutoff || sy > ly + extcutoff ||
02789         sz < -extcutoff || sz > lz + extcutoff) {
02790       continue;  // atom is beyond influence of lattice
02791     }
02792 
02793     i = (int) floorf(sx * bx_1);  // bin number
02794     j = (int) floorf(sy * by_1);
02795     k = (int) floorf(sz * bz_1);
02796 
02797     /*
02798     // XXX this test should never be true after passing previous test
02799     if (i < -p->padx || i >= p->nbx + p->padx ||
02800         j < -p->pady || j >= p->nby + p->pady ||
02801         k < -p->padz || k >= p->nbz + p->padz) {
02802       continue;  // atom is outside bin array
02803     }
02804     */
02805 
02806     int index = (k*nby + j)*nbx + i;  // flat index into bin array
02807     int slot = bincnt_zero[index];  // slot within bin to place atom
02808 
02809     if (slot < BIN_DEPTH) {
02810       AtomPosType *atom = bin_zero[index].atom;  // place atom in next slot
02811       atom[slot].x = x;
02812       atom[slot].y = y;
02813       atom[slot].z = z;
02814       atom[slot].vdwtype = vdw_type[n];
02815     }
02816     else if (count_extras < max_extra_atoms) {
02817       extra[count_extras].x = x;
02818       extra[count_extras].y = y;
02819       extra[count_extras].z = z;
02820       extra[count_extras].vdwtype = vdw_type[n];
02821       count_extras++;
02822     }
02823     else {
02824       // XXX debugging
02825       printf("*** too many extras, atom index %d\n", n);
02826       too_many_extra_atoms = 1;
02827     }
02828 
02829     bincnt_zero[index]++;  // increase count of atoms in bin
02830   }
02831   p->num_extras = count_extras;
02832 
02833   // mark unused atom slots
02834   // XXX set vdwtype to -1
02835   for (n = 0;  n < nbins;  n++) {
02836     for (k = bincnt[n];  k < BIN_DEPTH;  k++) {
02837       bin[n].atom[k].vdwtype = -1;
02838     }
02839   }
02840 
02841   return (too_many_extra_atoms ? -1 : 0);
02842 } // fill_atom_bins()
02843 
02844 
02845 // setup tightened bin index offset array of 3-tuples
02846 void tighten_bin_neighborhood(ComputeOccupancyMap *p) {
02847   const int padx = p->padx;
02848   const int pady = p->pady;
02849   const int padz = p->padz;
02850   const float bx2 = p->bx * p->bx;
02851   const float by2 = p->by * p->by;
02852   const float bz2 = p->bz * p->bz;
02853   const float r = p->extcutoff + sqrtf(bx2 + by2 + bz2);  // add bin diagonal
02854   const float r2 = r*r;
02855   int n = 0, i, j, k;
02856   char *bin_offsets
02857     = p->bin_offsets = new char[3 * (2*padx+1)*(2*pady+1)*(2*padz+1)];
02858   for (k = -padz;  k <= padz;  k++) {
02859     for (j = -pady;  j <= pady;  j++) {
02860       for (i = -padx;  i <= padx;  i++) {
02861         if (i*i*bx2 + j*j*by2 + k*k*bz2 >= r2) continue;
02862         bin_offsets[3*n    ] = (char) i;
02863         bin_offsets[3*n + 1] = (char) j;
02864         bin_offsets[3*n + 2] = (char) k;
02865         n++;
02866       }
02867     }
02868   }
02869   p->num_bin_offsets = n;
02870 } // tighten_bin_neighborhood()
02871 
02872 
02873 // For each grid point loop over the close atoms and 
02874 // determine if one of them is closer than excl_dist
02875 // away. If so we assume the clash with the probe will
02876 // result in a very high interaction energy and we can 
02877 // exclude this point from calcultation.
02878 void find_distance_exclusions(ComputeOccupancyMap *p) {
02879   const AtomPosType *extra = p->extra;
02880   const BinOfAtoms *bin_zero = p->bin_zero;
02881   char *excl = p->exclusions;
02882 
02883   int i, j, k, n, index;
02884   int ic, jc, kc;
02885 
02886   const int mx = p->mx;
02887   const int my = p->my;
02888   const int kstart = p->kstart;
02889   const int kstop = p->kstop;
02890   const int nbx = p->nbx;
02891   const int nby = p->nby;
02892   const float excl_dist = p->excl_dist;
02893   const float bx_1 = p->bx_1;
02894   const float by_1 = p->by_1;
02895   const float bz_1 = p->bz_1;
02896   const float hx = p->hx;
02897   const float hy = p->hy;
02898   const float hz = p->hz;
02899   const float x0 = p->x0;
02900   const float y0 = p->y0;
02901   const float z0 = p->z0;
02902   const int num_extras = p->num_extras;
02903   const int bdx = (int) ceilf(excl_dist * bx_1);  // width of nearby bins
02904   const int bdy = (int) ceilf(excl_dist * by_1);  // width of nearby bins
02905   const int bdz = (int) ceilf(excl_dist * bz_1);  // width of nearby bins
02906   const float excldist2 = excl_dist * excl_dist;
02907 
02908   for (k = kstart;  k < kstop;  k++) {  // k index loops over slab
02909     for (j = 0;  j < my;  j++) {
02910       for (i = 0;  i < mx;  i++) {  // loop over map points
02911 
02912         float px = i*hx;
02913         float py = j*hy;
02914         float pz = k*hz;  // translated coordinates of map point
02915 
02916         int ib = (int) floorf(px * bx_1);
02917         int jb = (int) floorf(py * by_1);
02918         int kb = (int) floorf(pz * bz_1);  // zero-based bin index
02919 
02920         px += x0;
02921         py += y0;
02922         pz += z0;  // absolute position
02923 
02924         for (kc = kb - bdz;  kc <= kb + bdz;  kc++) {
02925           for (jc = jb - bdy;  jc <= jb + bdy;  jc++) {
02926             for (ic = ib - bdx;  ic <= ib + bdx;  ic++) {
02927 
02928               const AtomPosType *atom
02929                 = bin_zero[(kc*nby + jc)*nbx + ic].atom;
02930 
02931               for (n = 0;  n < BIN_DEPTH;  n++) {  // atoms in bin
02932                 if (-1 == atom[n].vdwtype) break;  // finished atoms in bin
02933                 float dx = px - atom[n].x;
02934                 float dy = py - atom[n].y;
02935                 float dz = pz - atom[n].z;
02936                 float r2 = dx*dx + dy*dy + dz*dz;
02937                 if (r2 <= excldist2) {
02938                   index = (k*my + j)*mx + i;
02939                   excl[index] = 1;
02940                   goto NEXT_MAP_POINT;  // don't have to look at more atoms
02941                 }
02942               } // end loop over atoms in bin
02943 
02944             }
02945           }
02946         } // end loop over nearby bins
02947 
02948         for (n = 0;  n < num_extras;  n++) {  // extra atoms
02949           float dx = px - extra[n].x;
02950           float dy = py - extra[n].y;
02951           float dz = pz - extra[n].z;
02952           float r2 = dx*dx + dy*dy + dz*dz;
02953           if (r2 <= excldist2) {
02954             index = (k*my + j)*mx + i;
02955             excl[index] = 1;
02956             goto NEXT_MAP_POINT;  // don't have to look at more atoms
02957           }
02958         } // end loop over extra atoms
02959 
02960 NEXT_MAP_POINT:
02961         ; // continue loop over lattice points
02962 
02963       }
02964     }
02965   } // end loop over lattice points
02966 
02967 } // find_distance_exclusions()
02968 
02969 
02970 
02971 // For each grid point sum up the energetic contribution
02972 // of all close atoms. If that interaction energy is above
02973 // the excl_energy cutoff value we don't have to consider
02974 // this grid point in the subsequent calculation.
02975 // Hence, we save the computation of the different probe
02976 // orientation for multiatom probes.
02977 void find_energy_exclusions(ComputeOccupancyMap *p) {
02978   const char *bin_offsets = p->bin_offsets;
02979   const float *vdw_params = p->vdw_params;
02980   const AtomPosType *extra = p->extra;
02981   const BinOfAtoms *bin_zero = p->bin_zero;
02982   char *excl = p->exclusions;
02983 
02984   const float probe_vdweps = p->probe_vdw_params[0];   // use first probe param
02985   const float probe_vdwrmin = p->probe_vdw_params[1];  // for epsilon and rmin
02986 
02987   const int mx = p->mx;
02988   const int my = p->my;
02989   const int kstart = p->kstart;
02990   const int kstop = p->kstop;
02991   const int nbx = p->nbx;
02992   const int nby = p->nby;
02993   const float hx = p->hx;
02994   const float hy = p->hy;
02995   const float hz = p->hz;
02996   const float bx_1 = p->bx_1;
02997   const float by_1 = p->by_1;
02998   const float bz_1 = p->bz_1;
02999   const float x0 = p->x0;
03000   const float y0 = p->y0;
03001   const float z0 = p->z0;
03002   const int num_bin_offsets = p->num_bin_offsets;
03003   const int num_extras = p->num_extras;
03004   const float excl_energy = p->excl_energy;
03005 
03006   int i, j, k, n, index;
03007   const float cutoff2 = p->cutoff * p->cutoff;
03008 
03009   for (k = kstart;  k < kstop;  k++) {  // k index loops over slab
03010     for (j = 0;  j < my;  j++) {
03011       for (i = 0;  i < mx;  i++) {  // loop over map points
03012 
03013         int lindex = (k*my + j)*mx + i;  // map index
03014         if (excl[lindex]) continue;  // already excluded based on distance
03015 
03016         float px = i*hx;
03017         float py = j*hy;
03018         float pz = k*hz;  // translated coordinates of map point
03019 
03020         int ib = (int) floorf(px * bx_1);
03021         int jb = (int) floorf(py * by_1);
03022         int kb = (int) floorf(pz * bz_1);  // zero-based bin index
03023 
03024         px += x0;
03025         py += y0;
03026         pz += z0;  // absolute position
03027 
03028         float u = 0.f;
03029 
03030         for (index = 0;  index < num_bin_offsets;  index++) { // neighborhood
03031           int ic = ib + (int) bin_offsets[3*index    ];
03032           int jc = jb + (int) bin_offsets[3*index + 1];
03033           int kc = kb + (int) bin_offsets[3*index + 2];
03034 
03035           const AtomPosType *atom
03036             = bin_zero[(kc*nby + jc)*nbx + ic].atom;
03037 
03038           for (n = 0;  n < BIN_DEPTH;  n++) {  // atoms in bin
03039             if (-1 == atom[n].vdwtype) break;  // finished atoms in bin
03040             float dx = px - atom[n].x;
03041             float dy = py - atom[n].y;
03042             float dz = pz - atom[n].z;
03043             float r2 = dx*dx + dy*dy + dz*dz;
03044             if (r2 >= cutoff2) continue;
03045             int pindex = 2 * atom[n].vdwtype;
03046             float epsilon = vdw_params[pindex] * probe_vdweps;
03047             float rmin = vdw_params[pindex + 1] + probe_vdwrmin;
03048             float rm6 = rmin*rmin / r2;
03049             rm6 = rm6 * rm6 * rm6;
03050             u += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03051           } // end loop atoms in bin
03052 
03053         } // end loop bin neighborhood
03054 
03055         for (n = 0;  n < num_extras;  n++) {  // extra atoms
03056           float dx = px - extra[n].x;
03057           float dy = py - extra[n].y;
03058           float dz = pz - extra[n].z;
03059           float r2 = dx*dx + dy*dy + dz*dz;
03060           if (r2 >= cutoff2) continue;
03061           int pindex = 2 * extra[n].vdwtype;
03062           float epsilon = vdw_params[pindex] * probe_vdweps;
03063           float rmin = vdw_params[pindex + 1] + probe_vdwrmin;
03064           float rm6 = rmin*rmin / r2;
03065           rm6 = rm6 * rm6 * rm6;
03066           u += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03067         } // end loop over extra atoms
03068 
03069         if (u >= excl_energy) excl[lindex] = 1;
03070 
03071       }
03072     }
03073   } // end loop over lattice
03074 
03075 } // find_energy_exclusions()
03076 
03077 
03078 // For a monoatomic probe compute the occupancy rho
03079 // (probability of finding the probe)
03080 //
03081 // For each map point the occupancy is computed as
03082 //
03083 //   rho = exp(-U)
03084 //
03085 // where U is the interaction energy of the probe with the system
03086 // due to the VDW force field.
03087 //
03088 void compute_occupancy_monoatom(ComputeOccupancyMap *p) {
03089   const char *bin_offsets = p->bin_offsets;
03090   const float *vdw_params = p->vdw_params;
03091   const AtomPosType *extra = p->extra;
03092   const BinOfAtoms *bin_zero = p->bin_zero;
03093   const char *excl = p->exclusions;
03094   float *map = p->map;
03095 
03096   const int mx = p->mx;
03097   const int my = p->my;
03098   const int kstart = p->kstart;
03099   const int kstop = p->kstop;
03100   const int nbx = p->nbx;
03101   const int nby = p->nby;
03102   const float hx = p->hx;
03103   const float hy = p->hy;
03104   const float hz = p->hz;
03105   const float bx_1 = p->bx_1;
03106   const float by_1 = p->by_1;
03107   const float bz_1 = p->bz_1;
03108   const float x0 = p->x0;
03109   const float y0 = p->y0;
03110   const float z0 = p->z0;
03111   const int num_bin_offsets = p->num_bin_offsets;
03112   const int num_extras = p->num_extras;
03113 
03114   float probe_vdweps = p->probe_vdw_params[0];   // use first probe param
03115   float probe_vdwrmin = p->probe_vdw_params[1];  //   for epsilon and rmin
03116 
03117   int i, j, k, n, index;
03118   float max_energy = p->excl_energy;
03119   float cutoff2 = p->cutoff * p->cutoff;
03120 
03121   for (k = kstart;  k < kstop;  k++) {  // k index loops over slab
03122     for (j = 0;  j < my;  j++) {
03123       for (i = 0;  i < mx;  i++) {  // loop over lattice points
03124 
03125         int lindex = (k*my + j)*mx + i;  // map index
03126 #if 1
03127         if (excl[lindex]) {  // is map point excluded?
03128           map[lindex] = 0.f;  // clamp occupancy to zero
03129           continue;
03130         }
03131 #endif
03132 
03133         float px = i*hx;
03134         float py = j*hy;
03135         float pz = k*hz;  // translated coordinates of lattice point
03136 
03137         int ib = (int) floorf(px * bx_1);
03138         int jb = (int) floorf(py * by_1);
03139         int kb = (int) floorf(pz * bz_1);  // zero-based bin index
03140 
03141         px += x0;
03142         py += y0;
03143         pz += z0;  // absolute position
03144 
03145         float u = 0.f;
03146 
03147         for (index = 0;  index < num_bin_offsets;  index++) { // neighborhood
03148           int ic = ib + (int) bin_offsets[3*index    ];
03149           int jc = jb + (int) bin_offsets[3*index + 1];
03150           int kc = kb + (int) bin_offsets[3*index + 2];
03151 
03152           const AtomPosType *atom
03153             = bin_zero[(kc*nby + jc)*nbx + ic].atom;
03154 
03155           for (n = 0;  n < BIN_DEPTH;  n++) {  // atoms in bin
03156             if (-1 == atom[n].vdwtype) break;  // finished atoms in bin
03157             float dx = px - atom[n].x;
03158             float dy = py - atom[n].y;
03159             float dz = pz - atom[n].z;
03160             float r2 = dx*dx + dy*dy + dz*dz;
03161             if (r2 >= cutoff2) continue;
03162             int pindex = 2 * atom[n].vdwtype;
03163             float epsilon = vdw_params[pindex] * probe_vdweps;
03164             float rmin = vdw_params[pindex + 1] + probe_vdwrmin;
03165             float rm6 = rmin*rmin / r2;
03166             rm6 = rm6 * rm6 * rm6;
03167             u += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03168           } // end loop atoms in bin
03169 
03170         } // end loop bin neighborhood
03171 
03172         for (n = 0;  n < num_extras;  n++) {  // extra atoms
03173           float dx = px - extra[n].x;
03174           float dy = py - extra[n].y;
03175           float dz = pz - extra[n].z;
03176           float r2 = dx*dx + dy*dy + dz*dz;
03177           if (r2 >= cutoff2) continue;
03178           int pindex = 2 * extra[n].vdwtype;
03179           float epsilon = vdw_params[pindex] * probe_vdweps;
03180           float rmin = vdw_params[pindex + 1] + probe_vdwrmin;
03181           float rm6 = rmin*rmin / r2;
03182           rm6 = rm6 * rm6 * rm6;
03183           u += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03184         } // end loop over extra atoms
03185 
03186         float occ = 0.f;
03187         if (u < max_energy) {
03188           occ = expf(-u);
03189         }
03190         map[lindex] = occ;  // the occupancy
03191 
03192       }
03193     }
03194   } // end loop over lattice
03195 
03196 } // compute_occupancy_monoatom()
03197 
03198 
03199 // For a multiatom probe compute the occupancy rho
03200 // (probability of finding the probe)
03201 //
03202 // Calculate occupancy rho at each map point,
03203 // where rho = (1/m) sum_m ( -exp(u[i]) ) over m conformers,
03204 // u[i] is the potential energy of the i-th conformer.
03205 //
03206 void compute_occupancy_multiatom(ComputeOccupancyMap *p) {
03207   const char *bin_offsets = p->bin_offsets;
03208   const float *vdw_params = p->vdw_params;
03209   const float *probe_vdw_params = p->probe_vdw_params;
03210   const float *conformers = p->conformers;
03211   const AtomPosType *extra = p->extra;
03212   const float hx = p->hx;
03213   const float hy = p->hy;
03214   const float hz = p->hz;
03215   const float x0 = p->x0;
03216   const float y0 = p->y0;
03217   const float z0 = p->z0;
03218   const float bx_1 = p->bx_1;
03219   const float by_1 = p->by_1;
03220   const float bz_1 = p->bz_1;
03221   const float inv_num_conformers = 1.f / (float) p->num_conformers;
03222   const int num_bin_offsets = p->num_bin_offsets;
03223   const int num_extras = p->num_extras;
03224   const int num_probes = p->num_probes;
03225   const int num_conformers = p->num_conformers;
03226   const int nbx = p->nbx;
03227   const int nby = p->nby;
03228   const int mx = p->mx;
03229   const int my = p->my;
03230   const int kstart = p->kstart;
03231   const int kstop = p->kstop;
03232 
03233   const BinOfAtoms *bin_zero = p->bin_zero;
03234   const char *excl = p->exclusions;
03235   float *map = p->map;
03236 
03237   int i, j, k, n, nb;
03238 
03239   const float minocc = expf(-p->excl_energy);
03240   const float cutoff2 = p->cutoff * p->cutoff;
03241 
03242   float *u = new float[p->num_conformers];  // cal potential for each conformer
03243 
03244   for (k = kstart;  k < kstop;  k++) {  // k index loops over slab
03245     for (j = 0;  j < my;  j++) {
03246       for (i = 0;  i < mx;  i++) {  // loop over lattice points
03247 
03248         int lindex = (k*my + j)*mx + i;  // map index
03249         if (excl[lindex]) {  // is map point excluded?
03250           map[lindex] = 0.f;  // clamp occupancy to zero
03251           continue;
03252         }
03253 
03254         float px = i*hx;
03255         float py = j*hy;
03256         float pz = k*hz;  // translated coordinates of lattice point
03257 
03258         int ib = (int) floorf(px * bx_1);
03259         int jb = (int) floorf(py * by_1);
03260         int kb = (int) floorf(pz * bz_1);  // zero-based bin index
03261 
03262         int m, ma;
03263 
03264         px += x0;
03265         py += y0;
03266         pz += z0;  // absolute position
03267 
03268         memset(u, 0, num_conformers * sizeof(float));
03269 
03270         for (nb = 0;  nb < num_bin_offsets;  nb++) { // bin neighborhood
03271           int ic = ib + (int) bin_offsets[3*nb    ];
03272           int jc = jb + (int) bin_offsets[3*nb + 1];
03273           int kc = kb + (int) bin_offsets[3*nb + 2];
03274 
03275           const AtomPosType *atom = bin_zero[(kc*nby + jc)*nbx + ic].atom;
03276 
03277           for (n = 0;  n < BIN_DEPTH;  n++) {  // atoms in bin
03278             if (-1 == atom[n].vdwtype) break;  // finished atoms in bin
03279 
03280             for (m = 0;  m < num_conformers;  m++) {  // conformers
03281               float v = 0.f;
03282               for (ma = 0;  ma < num_probes;  ma++) {  // probe
03283                 int index = m*num_probes + ma;
03284                 float dx = conformers[3*index    ] + px - atom[n].x;
03285                 float dy = conformers[3*index + 1] + py - atom[n].y;
03286                 float dz = conformers[3*index + 2] + pz - atom[n].z;
03287                 float r2 = dx*dx + dy*dy + dz*dz;
03288                 if (r2 >= cutoff2) continue;
03289                 int pindex = 2 * atom[n].vdwtype;
03290                 float epsilon = vdw_params[pindex] * probe_vdw_params[2*ma];
03291                 float rmin = vdw_params[pindex + 1] + probe_vdw_params[2*ma + 1];
03292                 float rm6 = rmin*rmin / r2;
03293                 rm6 = rm6 * rm6 * rm6;
03294                 v += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03295               } // end loop probe
03296 
03297               u[m] += v;  // contribution of one system atom to conformer
03298 
03299             } // end loop conformers
03300 
03301           } // end loop atoms in bin
03302 
03303         } // end loop bin neighborhood
03304 
03305         for (n = 0;  n < num_extras;  n++) {  // extra atoms
03306           for (m = 0;  m < num_conformers;  m++) {  // conformers
03307             float v = 0.f;
03308             for (ma = 0;  ma < num_probes;  ma++) {  // probe
03309               int index = m*num_probes + ma;
03310               float dx = conformers[3*index    ] + px - extra[n].x;
03311               float dy = conformers[3*index + 1] + py - extra[n].y;
03312               float dz = conformers[3*index + 2] + pz - extra[n].z;
03313               float r2 = dx*dx + dy*dy + dz*dz;
03314               if (r2 >= cutoff2) continue;
03315               int pindex = 2 *extra[n].vdwtype;
03316               float epsilon = vdw_params[pindex] * probe_vdw_params[2*ma];
03317               float rmin = vdw_params[pindex + 1] + probe_vdw_params[2*ma + 1];
03318               float rm6 = rmin*rmin / r2;
03319               rm6 = rm6 * rm6 * rm6;
03320               v += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03321             } // end loop probe
03322 
03323             u[m] += v;  // contribution of one system atom to conformer
03324 
03325           } // end loop conformers
03326         } // end loop over extra atoms
03327 
03328         // now we have energies of all conformers u[i], i=0..m-1
03329 
03330         float z = 0.f;
03331         for (m = 0;  m < num_conformers;  m++) {  // average over conformers
03332           z += expf(-u[m]);
03333         }
03334 
03335         float occ = z * inv_num_conformers;  // the occupency
03336         map[lindex] = (occ > minocc ? occ : 0.f);
03337       }
03338     }
03339   } // end loop over lattice
03340 
03341   delete[] u;  // free extra memory
03342 } // compute_occupancy_multiatom()
03343 
03344 
03345 // Write bin histogram into a dx map
03346 static void write_bin_histogram_map(
03347     const ComputeOccupancyMap *p,
03348     const char *filename
03349     ) {
03350   float xaxis[3], yaxis[3], zaxis[3];
03351   memset(xaxis, 0, 3*sizeof(float));
03352   memset(yaxis, 0, 3*sizeof(float));
03353   memset(zaxis, 0, 3*sizeof(float));
03354   xaxis[0] = p->nbx * p->bx;
03355   yaxis[1] = p->nby * p->by;
03356   zaxis[2] = p->nbz * p->bz;
03357 
03358   int gridsize = p->nbx * p->nby * p->nbz;
03359   float *data = new float[gridsize];
03360 
03361   int i;
03362   for (i=0; i<gridsize; i++) {
03363     data[i] = (float) p->bincnt[i];
03364   }
03365 
03366   float ori[3];
03367   ori[0] = p->x0 - p->padx * p->bx;
03368   ori[1] = p->y0 - p->pady * p->by;
03369   ori[2] = p->z0 - p->padz * p->bz;
03370  
03371   VolumetricData *volhist;
03372   volhist = new VolumetricData("atom binning histogram",
03373                                ori, xaxis, yaxis, zaxis,
03374                                p->nbx, p->nby, p->nbz, data);
03375 
03376   // Call the file writer in the VolMapCreate.C:
03377   volmap_write_dx_file(volhist, filename);
03378 
03379   delete volhist;  // XXX does data get deleted as part of volhist?
03380 }
03381 
03382 
03383 // XXX print out histogram of atom bins
03384 void atom_bin_stats(const ComputeOccupancyMap *p) {
03385   int histogram[10] = { 0 };
03386   int i, j, k;
03387 
03388   printf("*** origin = %g %g %g\n", p->x0, p->y0, p->z0);
03389   printf("*** lenx = %g  leny = %g  lenz = %g\n", p->lx, p->ly, p->lz);
03390   printf("*** bin lengths = %g %g %g\n", p->bx, p->by, p->bz);
03391   printf("*** inverse bin lengths = %g %g %g\n", p->bx_1, p->by_1, p->bz_1);
03392   printf("*** bin array:  %d X %d X %d\n", p->nbx, p->nby, p->nbz);
03393   printf("*** bin padding:  %d %d %d\n", p->padx, p->pady, p->padz);
03394   printf("*** cutoff = %g\n", p->cutoff);
03395   printf("*** extcutoff = %g\n", p->extcutoff);
03396   printf("*** size of tight neighborhood = %d\n", p->num_bin_offsets);
03397 
03398   for (k = 0;  k < p->nbz;  k++) {
03399     for (j = 0;  j < p->nby;  j++) {
03400       for (i = 0;  i < p->nbx;  i++) {
03401         int index = (k*p->nby + j)*p->nbx + i;
03402         int count = p->bincnt[index];
03403         histogram[(count <= 9 ? count : 9)]++;
03404       }
03405     }
03406   }
03407 
03408   printf("*** histogram of bin fill:\n");
03409   for (i = 0;  i < (int) (sizeof(histogram) / sizeof(int));  i++) {
03410     if (i < 9) {
03411       printf("***     atom count %d     number of bins %d\n",
03412           i, histogram[i]);
03413     }
03414     else {
03415       printf("***     atom count > 8   number of bins %d\n", histogram[i]);
03416     }
03417   }
03418   printf("*** number of extra atoms = %d\n", p->num_extras);
03419 
03420   write_bin_histogram_map(p, "binning_histogram.dx");
03421 }
03422 
03423 
03424 // XXX slow quadratic complexity algorithm for checking correctness
03425 //     for every map point, for every probe atom in all conformers,
03426 //     iterate over all atoms
03427 void compute_allatoms(
03428     float *map,                    // return calculated occupancy map
03429     int mx, int my, int mz,        // dimensions of map
03430     float lx, float ly, float lz,  // lengths of map
03431     const float origin[3],         // origin of map
03432     const float axes[9],           // basis vectors of aligned map
03433     const float alignmat[16],      // 4x4 alignment matrix
03434     int num_coords,                // number of atoms
03435     const float *coords,           // atom coordinates, length 3*num_coords
03436     const int *vdw_type,           // vdw type numbers, length num_coords
03437     const float *vdw_params,       // scaled vdw parameters for atoms
03438     float cutoff,                  // cutoff distance
03439     const float *probe_vdw_params, // scaled vdw parameters for probe atoms
03440     int num_probe_atoms,           // number of atoms in probe
03441     int num_conformers,            // number of conformers
03442     const float *conformers,       // length 3*num_probe_atoms*num_conformers
03443     float excl_energy              // exclusion energy threshold
03444     ) {
03445   const float theTrivialConformer[] = { 0.f, 0.f, 0.f };
03446 
03447   if (0 == num_conformers) {  // fix to handle trivial case consistently
03448     num_conformers = 1;
03449     conformers = theTrivialConformer;
03450   }
03451 
03452   float hx = lx / mx;  // lattice spacing
03453   float hy = ly / my;
03454   float hz = lz / mz;
03455 
03456   int i, j, k, n, m, ma;
03457 
03458   float cutoff2 = cutoff * cutoff;
03459   float minocc = expf(-excl_energy);  // minimum nonzero occupancy
03460 
03461   float *u = new float[num_conformers];  // calc potential for each conformer
03462 
03463 #if 1
03464   printf("*** All atoms calculation (quadratic complexity)\n");
03465   printf("***   number of map points = %d\n", mx*my*mz);
03466   printf("***   number of atoms = %d\n", num_coords);
03467   printf("***   number of conformers = %d\n", num_conformers);
03468   printf("***   number of probe atoms = %d\n", num_probe_atoms);
03469 #endif
03470 
03471   for (k = 0;  k < mz;  k++) {
03472     for (j = 0;  j < my;  j++) {
03473       for (i = 0;  i < mx;  i++) {  // loop over lattice points
03474 
03475         int mindex = (k*my + j)*mx + i;  // map flat index
03476 
03477         float px = i*hx + origin[0];
03478         float py = j*hy + origin[1];
03479         float pz = k*hz + origin[2];  // coordinates of lattice point
03480 
03481         memset(u, 0, num_conformers * sizeof(float));
03482 
03483         for (n = 0;  n < num_coords;  n++) {  // all atoms
03484 
03485           for (m = 0;  m < num_conformers;  m++) {  // conformers
03486             float v = 0.f;
03487             for (ma = 0;  ma < num_probe_atoms;  ma++) {  // probe atoms
03488               int index = m*num_probe_atoms + ma;
03489               float dx = conformers[3*index    ] + px - coords[3*n    ];
03490               float dy = conformers[3*index + 1] + py - coords[3*n + 1];
03491               float dz = conformers[3*index + 2] + pz - coords[3*n + 2];
03492               float r2 = dx*dx + dy*dy + dz*dz;
03493               if (r2 >= cutoff2) continue;
03494               int pindex = 2 * vdw_type[n];
03495               float epsilon = vdw_params[pindex] * probe_vdw_params[2*ma];
03496               float rmin = vdw_params[pindex + 1] + probe_vdw_params[2*ma + 1];
03497               float rm6 = rmin*rmin / r2;
03498               rm6 = rm6 * rm6 * rm6;
03499               v += epsilon * rm6 * (rm6 - 2.f);  // sum vdw contribution
03500             } // end loop probe atoms
03501 
03502             u[m] += v;  // contribution of one system atom to conformer
03503 
03504           } // end loop conformers
03505 
03506         } // end loop over all atoms
03507 
03508         float z = 0.f;
03509         for (m = 0;  m < num_conformers;  m++) {  // average conformer energies
03510           z += expf(-u[m]);
03511         }
03512 
03513         map[mindex] = z / num_conformers;  // store average occupancy
03514         if (map[mindex] < minocc) map[mindex] = 0.f;
03515       }
03516     }
03517   } // end loop over map
03518 
03519   delete[] u;
03520 }
03521 

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