1 // $Id$
2 //
3 //   Copyright (C) 2005-2008 Greg Landrum and Rational Discovery LLC
4 //
5 //   @@ All Rights Reserved @@
6 //  This file is part of the RDKit.
7 //  The contents are covered by the terms of the BSD license
8 //  which is included in the file license.txt, found at the root
9 //  of the RDKit source tree.
10 //
11 #include "UniformGrid3D.h"
12 #include <DataStructs/DiscreteValueVect.h>
13 #include <RDGeneral/StreamOps.h>
14 #include <RDGeneral/Exceptions.h>
15 #include "point.h"
16 #include <fstream>
17 #include <cstdint>
18 
19 constexpr double OFFSET_TOL = 1.e-8;
20 constexpr double SPACING_TOL = 1.e-8;
21 
22 using namespace RDKit;
23 
24 namespace RDGeom {
25 unsigned int ci_GRIDPICKLE_VERSION = 0x1;
26 
UniformGrid3D(const UniformGrid3D & other)27 UniformGrid3D::UniformGrid3D(const UniformGrid3D &other) : Grid3D(other) {
28   PRECONDITION(other.dp_storage, "cannot copy an uninitialized grid");
29   auto *data = new RDKit::DiscreteValueVect(*other.dp_storage);
30   initGrid(other.d_numX * other.d_spacing, other.d_numY * other.d_spacing,
31            other.d_numZ * other.d_spacing, other.d_spacing,
32            other.dp_storage->getValueType(), other.d_offSet, data);
33 }
34 
operator =(const UniformGrid3D & other)35 UniformGrid3D &UniformGrid3D::operator=(const UniformGrid3D &other) {
36   if (&other == this) {
37     return *this;
38   }
39   PRECONDITION(other.dp_storage, "cannot copy an uninitialized grid");
40   delete dp_storage;
41   auto *data = new RDKit::DiscreteValueVect(*other.dp_storage);
42   initGrid(other.d_numX * other.d_spacing, other.d_numY * other.d_spacing,
43            other.d_numZ * other.d_spacing, other.d_spacing,
44            other.dp_storage->getValueType(), other.d_offSet, data);
45   return *this;
46 }
47 
initGrid(double dimX,double dimY,double dimZ,double spacing,RDKit::DiscreteValueVect::DiscreteValueType valType,const RDGeom::Point3D & offSet,RDKit::DiscreteValueVect * data)48 void UniformGrid3D::initGrid(
49     double dimX, double dimY, double dimZ, double spacing,
50     RDKit::DiscreteValueVect::DiscreteValueType valType,
51     const RDGeom::Point3D &offSet, RDKit::DiscreteValueVect *data) {
52   PRECONDITION(dimX > 0.0, "Invalid x-dimension for grid");
53   PRECONDITION(dimY > 0.0, "Invalid y-dimension for grid");
54   PRECONDITION(dimZ > 0.0, "Invalid z-dimension for grid");
55   PRECONDITION(spacing > 0.0, "Invalid spacing for grid");
56   d_numX = static_cast<unsigned int>(floor(dimX / spacing + 0.5));
57   d_numY = static_cast<unsigned int>(floor(dimY / spacing + 0.5));
58   d_numZ = static_cast<unsigned int>(floor(dimZ / spacing + 0.5));
59   PRECONDITION((!data) || data->getValueType() == valType,
60                "grid data type mismatch");
61   PRECONDITION((!data) || data->getLength() == d_numX * d_numY * d_numZ,
62                "grid data size mismatch");
63 
64   d_spacing = spacing;
65   d_offSet = offSet;
66   if (!data) {
67     dp_storage =
68         new RDKit::DiscreteValueVect(valType, d_numX * d_numY * d_numZ);
69   } else {
70     dp_storage = data;
71   }
72 }
73 
UniformGrid3D(const std::string & pkl)74 UniformGrid3D::UniformGrid3D(const std::string &pkl) {
75   dp_storage = nullptr;
76   this->initFromText(pkl.c_str(), pkl.size());
77 }
UniformGrid3D(const char * pkl,const unsigned int len)78 UniformGrid3D::UniformGrid3D(const char *pkl, const unsigned int len) {
79   dp_storage = nullptr;
80   this->initFromText(pkl, len);
81 }
82 
~UniformGrid3D()83 UniformGrid3D::~UniformGrid3D() {
84   delete dp_storage;
85   dp_storage = nullptr;
86 }
87 
getGridIndex(unsigned int xi,unsigned int yi,unsigned int zi) const88 int UniformGrid3D::getGridIndex(unsigned int xi, unsigned int yi,
89                                 unsigned int zi) const {
90   if (xi >= d_numX) {
91     return -1;
92   }
93   if (yi >= d_numY) {
94     return -1;
95   }
96   if (zi >= d_numZ) {
97     return -1;
98   }
99   return (zi * d_numX * d_numY + yi * d_numX + xi);
100 }
101 
getGridIndices(unsigned int idx,unsigned int & xi,unsigned int & yi,unsigned int & zi) const102 void UniformGrid3D::getGridIndices(unsigned int idx, unsigned int &xi,
103                                    unsigned int &yi, unsigned int &zi) const {
104   if (idx >= d_numX * d_numY * d_numZ) {
105     throw IndexErrorException(idx);
106   }
107   xi = idx % d_numX;
108   yi = (idx % (d_numX * d_numY)) / d_numX;
109   zi = idx / (d_numX * d_numY);
110 }
111 
getGridPointIndex(const Point3D & point) const112 int UniformGrid3D::getGridPointIndex(const Point3D &point) const {
113   Point3D tPt(point);
114   tPt -= d_offSet;  // d_origin;
115   tPt /= d_spacing;
116   int xi, yi, zi;
117   double move = 0.5;
118   xi = static_cast<int>(floor(tPt.x + move));
119   yi = static_cast<int>(floor(tPt.y + move));
120   zi = static_cast<int>(floor(tPt.z + move));
121 
122   if ((xi < 0) || (xi >= static_cast<int>(d_numX))) {
123     return -1;
124   }
125   if ((yi < 0) || (yi >= static_cast<int>(d_numY))) {
126     return -1;
127   }
128   if ((zi < 0) || (zi >= static_cast<int>(d_numZ))) {
129     return -1;
130   }
131 
132   return (zi * d_numX * d_numY + yi * d_numX + xi);
133 }
134 
getVal(const Point3D & point) const135 int UniformGrid3D::getVal(const Point3D &point) const {
136   int id = getGridPointIndex(point);
137   if (id < 0) {
138     return -1;
139   }
140   return dp_storage->getVal(static_cast<unsigned int>(id));
141 }
142 
getVal(unsigned int pointId) const143 unsigned int UniformGrid3D::getVal(unsigned int pointId) const {
144   return dp_storage->getVal(pointId);
145 }
146 
setVal(const Point3D & point,unsigned int val)147 void UniformGrid3D::setVal(const Point3D &point, unsigned int val) {
148   int id = getGridPointIndex(point);
149   if (id < 0) {
150     return;
151   }
152   dp_storage->setVal(static_cast<unsigned int>(id), val);
153 }
154 
getGridPointLoc(unsigned int pointId) const155 Point3D UniformGrid3D::getGridPointLoc(unsigned int pointId) const {
156   if (pointId >= d_numX * d_numY * d_numZ) {
157     throw IndexErrorException(pointId);
158   }
159   Point3D res;
160   res.x = (pointId % d_numX) * d_spacing;
161   // the rounding here is intentional, we want the coordinates of a grid point
162   res.y =
163       static_cast<double>((pointId % (d_numX * d_numY)) / d_numX) * d_spacing;
164   res.z = static_cast<double>(pointId / (d_numX * d_numY)) * d_spacing;
165   res += d_offSet;  // d_origin;
166   return res;
167 }
168 
setVal(unsigned int pointId,unsigned int val)169 void UniformGrid3D::setVal(unsigned int pointId, unsigned int val) {
170   dp_storage->setVal(pointId, val);
171 }
172 
compareParams(const UniformGrid3D & other) const173 bool UniformGrid3D::compareParams(const UniformGrid3D &other) const {
174   if (d_numX != other.getNumX()) {
175     return false;
176   }
177   if (d_numY != other.getNumY()) {
178     return false;
179   }
180   if (d_numZ != other.getNumZ()) {
181     return false;
182   }
183   if (fabs(d_spacing - other.getSpacing()) > SPACING_TOL) {
184     return false;
185   }
186   Point3D dOffset = d_offSet;
187   dOffset -= other.getOffset();
188   if (dOffset.lengthSq() > OFFSET_TOL) {
189     return false;
190   }
191   return true;
192 }
193 
setSphereOccupancy(const Point3D & center,double radius,double stepSize,int maxNumLayers,bool ignoreOutOfBound)194 void UniformGrid3D::setSphereOccupancy(const Point3D &center, double radius,
195                                        double stepSize, int maxNumLayers,
196                                        bool ignoreOutOfBound) {
197   int ptIndex = this->getGridPointIndex(center);
198   if (ptIndex == -1) {
199     if (ignoreOutOfBound) {
200       return;
201     } else {
202       throw GridException("Center outside the grdi boundary");
203     }
204   }
205   Point3D gPt(center);  // point on the grid
206   gPt -= d_offSet;
207 
208   gPt /= d_spacing;
209 
210   // unsigned int z = ptIndex/(d_numX*d_numY);
211   // unsigned int y = (ptIndex%(d_numX*d_numY))/d_numX;
212   // unsigned int x = ptIndex%d_numX;
213 
214   unsigned int bPerVal = dp_storage->getNumBitsPerVal();
215   unsigned int maxVal = (1 << bPerVal) - 1;
216   unsigned int nLayers = maxVal;
217   unsigned int valStep = 1;
218   if ((maxNumLayers > 0) && (maxNumLayers <= static_cast<int>(nLayers))) {
219     nLayers = maxNumLayers;
220     valStep = (maxVal + 1) / nLayers;
221   }
222   double bgRad = radius / d_spacing;        // base radius in grid coords
223   double gStepSize = stepSize / d_spacing;  // step size in grid coords
224   double gRadius =
225       bgRad + nLayers * gStepSize;  // largest radius in grid coords
226   double gRad2 = gRadius * gRadius;
227   double bgRad2 = bgRad * bgRad;
228   int i, j, k;
229   double dx, dy, dz, d, d2, dy2z2, dz2;
230   int xmin, xmax, ymin, ymax, zmin, zmax;
231   xmax = (int)floor(gPt.x + gRadius);
232   xmin = (int)ceil(gPt.x - gRadius);
233   ymax = (int)floor(gPt.y + gRadius);
234   ymin = (int)ceil(gPt.y - gRadius);
235   zmax = (int)floor(gPt.z + gRadius);
236   zmin = (int)ceil(gPt.z - gRadius);
237 
238   unsigned int oval, val, valChange;
239   int ptId1, ptId2;
240   for (k = zmin; k <= zmax; ++k) {
241     if ((k >= 0) &&
242         (k < (int)d_numZ)) {  // we are inside the grid in the z-direction
243       dz = static_cast<double>(k) - gPt.z;
244       dz2 = dz * dz;
245       ptId1 = k * d_numX * d_numY;
246       for (j = ymin; j <= ymax; ++j) {
247         if ((j >= 0) &&
248             (j < (int)d_numY)) {  // inside the grid in the y-direction
249           dy = static_cast<double>(j) - gPt.y;
250           dy2z2 = dy * dy + dz2;
251           if (dy2z2 < gRad2) {  // we are within the radius at least from the
252                                 // y,z coordinates
253             ptId2 = ptId1 + j * d_numX;
254             for (i = xmin; i <= xmax; ++i) {
255               if ((i >= 0) && (i < (int)d_numX)) {
256                 oval = dp_storage->getVal((unsigned int)(ptId2 + i));
257                 if (oval < maxVal) {  // if we are already at maxVal we will not
258                                       // change that
259                   dx = static_cast<double>(i) - gPt.x;
260                   d2 = dx * dx + dy2z2;
261                   if (d2 < gRad2) {  // we are inside the sphere
262                     if (d2 < bgRad2) {
263                       val = maxVal;
264                     } else {
265                       d = sqrt(d2);
266                       valChange = (static_cast<unsigned int>(
267                                       (d - bgRad) / gStepSize + 1)) *
268                                   (valStep);
269                       if (valChange < maxVal) {
270                         val = maxVal - valChange;
271                       } else {
272                         val = 0;
273                       }
274                     }
275                     if (val > oval) {
276                       dp_storage->setVal(ptId2 + i, val);
277                     }
278                   }  // we are inside the sphere
279                 }    // grid point does not already have maxVal
280               }      // inside the grid in x-direction
281             }        // loop over points in x-direction
282           }          // inside the sphere based on only z and y coords
283         }            // we are inside the grid in the y-direction
284       }              // loop over points in y-direction
285     }                // inside grid in z-direction
286   }                  // loop over points in z-direction
287 }
288 
operator |=(const UniformGrid3D & other)289 UniformGrid3D &UniformGrid3D::operator|=(const UniformGrid3D &other) {
290   PRECONDITION(dp_storage, "uninitialized grid");
291   PRECONDITION(other.dp_storage, "uninitialized grid");
292   PRECONDITION(compareParams(other), "incompatible grids");
293 
294   // EFF: we're probably doing too much copying here:
295   RDKit::DiscreteValueVect *newData =
296       new RDKit::DiscreteValueVect((*dp_storage) | (*other.dp_storage));
297   delete dp_storage;
298   dp_storage = newData;
299   return *this;
300 }
301 
operator &=(const UniformGrid3D & other)302 UniformGrid3D &UniformGrid3D::operator&=(const UniformGrid3D &other) {
303   PRECONDITION(dp_storage, "uninitialized grid");
304   PRECONDITION(other.dp_storage, "uninitialized grid");
305   PRECONDITION(compareParams(other), "incompatible grids");
306 
307   // EFF: we're probably doing too much copying here:
308   RDKit::DiscreteValueVect *newData =
309       new RDKit::DiscreteValueVect((*dp_storage) & (*other.dp_storage));
310   delete dp_storage;
311   dp_storage = newData;
312   return *this;
313 }
314 
operator +=(const UniformGrid3D & other)315 UniformGrid3D &UniformGrid3D::operator+=(const UniformGrid3D &other) {
316   PRECONDITION(dp_storage, "uninitialized grid");
317   PRECONDITION(other.dp_storage, "uninitialized grid");
318   PRECONDITION(compareParams(other), "incompatible grids");
319 
320   // EFF: we're probably doing too much copying here:
321   *dp_storage += *other.dp_storage;
322   return *this;
323 }
324 
operator -=(const UniformGrid3D & other)325 UniformGrid3D &UniformGrid3D::operator-=(const UniformGrid3D &other) {
326   PRECONDITION(dp_storage, "uninitialized grid");
327   PRECONDITION(other.dp_storage, "uninitialized grid");
328   PRECONDITION(compareParams(other), "incompatible grids");
329 
330   // EFF: we're probably doing too much copying here:
331   *dp_storage -= *other.dp_storage;
332   return *this;
333 }
334 
toString() const335 std::string UniformGrid3D::toString() const {
336   std::stringstream ss(std::ios_base::binary | std::ios_base::out |
337                        std::ios_base::in);
338   std::int32_t tVers = ci_GRIDPICKLE_VERSION * -1;
339   streamWrite(ss, tVers);
340   std::uint32_t tInt;
341   tInt = d_numX;
342   streamWrite(ss, tInt);
343   tInt = d_numY;
344   streamWrite(ss, tInt);
345   tInt = d_numZ;
346   streamWrite(ss, tInt);
347   streamWrite(ss, d_spacing);
348   streamWrite(ss, d_offSet.x);
349   streamWrite(ss, d_offSet.y);
350   streamWrite(ss, d_offSet.z);
351 
352   std::string storePkl = dp_storage->toString();
353   std::uint32_t pklSz = storePkl.size();
354   streamWrite(ss, pklSz);
355   ss.write(storePkl.c_str(), pklSz * sizeof(char));
356 
357   std::string res(ss.str());
358   return (res);
359 }
initFromText(const char * pkl,const unsigned int length)360 void UniformGrid3D::initFromText(const char *pkl, const unsigned int length) {
361   std::stringstream ss(std::ios_base::binary | std::ios_base::in |
362                        std::ios_base::out);
363   ss.write(pkl, length);
364   std::int32_t tVers;
365   streamRead(ss, tVers);
366   tVers *= -1;
367   if (tVers == 0x1) {
368   } else {
369     throw ValueErrorException("bad version in UniformGrid3D pickle");
370   }
371   std::uint32_t tInt;
372   streamRead(ss, tInt);
373   d_numX = tInt;
374   streamRead(ss, tInt);
375   d_numY = tInt;
376   streamRead(ss, tInt);
377   d_numZ = tInt;
378   streamRead(ss, d_spacing);
379   double oX, oY, oZ;
380   streamRead(ss, oX);
381   streamRead(ss, oY);
382   streamRead(ss, oZ);
383   d_offSet = Point3D(oX, oY, oZ);
384 
385   std::uint32_t pklSz;
386   streamRead(ss, pklSz);
387   auto *buff = new char[pklSz];
388   ss.read(buff, pklSz * sizeof(char));
389   delete dp_storage;
390   dp_storage = new RDKit::DiscreteValueVect(buff, pklSz);
391   delete[] buff;
392 }
393 
writeGridToStream(const UniformGrid3D & grid,std::ostream & outStrm)394 void writeGridToStream(const UniformGrid3D &grid, std::ostream &outStrm) {
395   int dimX = (int)grid.getNumX();  //+2;
396   int dimY = (int)grid.getNumY();  //+2;
397   int dimZ = (int)grid.getNumZ();  //+2;
398   double spacing = grid.getSpacing();
399   double lenX = dimX * spacing;
400   double lenY = dimY * spacing;
401   double lenZ = dimZ * spacing;
402   Point3D offSet = grid.getOffset();
403   offSet /= spacing;
404   outStrm << "Grid file representing a Shape \n\n";
405   outStrm << lenX << " " << lenY << " " << lenZ << " 90.0 90.0 90.0"
406           << std::endl;
407   outStrm << dimX - 1 << " " << dimY - 1 << " " << dimZ - 1 << std::endl;
408 
409   int outX1 = (int)(floor(offSet.x + 0.5));
410   int outX2 = (int)(floor(offSet.x + 0.5)) + (int)(dimX - 1);
411   // REVIEW: ok - here is a fix to try and make the grid closer to the molecule
412   // when displayed
413   // (at least in PyMol). The difference between the pair of values (outX1,
414   // outX2) is (dimX-1),
415   // that is not the case with pairs the (outY1, outY2) a (outZ1, outZ2). In
416   // these cases, the difference is
417   // dimY and dimZ respectively. Not sure why this should be the case, but
418   // almost always we get a
419   // better display in PyMol.
420   int outY1 = (int)(floor(offSet.y + 0.5));
421   int outY2 = (int)(floor(offSet.y + 0.5)) + (int)(dimY);
422   int outZ1 = (int)(floor(offSet.z + 0.5));
423   int outZ2 = (int)(floor(offSet.z + 0.5)) + (int)(dimZ);
424 
425   outStrm << "1"
426           << " " << outX1 << " " << outX2 << " " << outY1 << " " << outY2 << " "
427           << outZ1 << " " << outZ2 << "\n";
428   unsigned int i, nPts = grid.getSize();
429   for (i = 0; i < nPts; i++) {
430     outStrm << static_cast<double>(grid.getVal(i)) << std::endl;
431   }
432 }
433 
writeGridToFile(const UniformGrid3D & grid,const std::string & filename)434 void writeGridToFile(const UniformGrid3D &grid, const std::string &filename) {
435   // std::ofstream ofStrm(filename.c_str());
436   auto *ofStrm = new std::ofstream(filename.c_str());
437   auto *oStrm = static_cast<std::ostream *>(ofStrm);
438   writeGridToStream(grid, *oStrm);
439   delete ofStrm;
440 }
441 }  // namespace RDGeom
442