Coverage Report

Created: 2026-08-13 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/formats/pointcloudformat.cpp
Line
Count
Source
1
//
2
// Point Cloud Plugin for Open Babel
3
// Copyright (C) 2015 M J Harvey,
4
// Acellera Ltd
5
// m.j.harvey ( at ) acellera.com
6
//
7
// This program is free software; you can redistribute it and/or
8
// modify it under the terms of the GNU General Public License
9
// as published by the Free Software Foundation; either version 2
10
// of the License, or (at your option) any later version.
11
12
// This program is distributed in the hope that it will be useful,
13
// but WITHOUT ANY WARRANTY; without even the implied warranty of
14
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
// GNU General Public License for more details.
16
//
17
// You should have received a copy of the GNU General Public License
18
// along with this program; if not, write to the Free Software
19
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
// MA  02110-1301, USA.
21
//
22
// $Author$
23
// $Date$
24
// $Revision$
25
//
26
27
28
#include <openbabel/mol.h>
29
#include <openbabel/atom.h>
30
#include <openbabel/obconversion.h>
31
#include <openbabel/obmolecformat.h>
32
#include <openbabel/elements.h>
33
34
#include <openbabel/obiter.h>
35
#include <openbabel/data.h>
36
#include <openbabel/bitvec.h>
37
38
#include <iostream>
39
40
#include <cstdlib>
41
#include <cmath>
42
#include <cstdlib>
43
44
#ifndef M_PI
45
#define M_PI 3.14159265358979323846
46
#endif
47
48
49
using namespace std;
50
51
namespace OpenBabel
52
{
53
54
55
  //==============================================================================
56
  /// Class to output a point cloud on a surface around a molecule
57
58
  class PointCloudFormat : public OpenBabel::OBMoleculeFormat
59
  {
60
    public:
61
      PointCloudFormat()
62
12
      {
63
12
        OpenBabel::OBConversion::RegisterFormat( "pointcloud", this );
64
        /*
65
        OBConversion::RegisterOptionParam("r", this, 1, OBConversion::OUTOPTIONS);
66
        OBConversion::RegisterOptionParam("d", this, 1, OBConversion::OUTOPTIONS);
67
        OBConversion::RegisterOptionParam("p", this, 1, OBConversion::OUTOPTIONS);
68
        OBConversion::RegisterOptionParam("x", this, 1, OBConversion::OUTOPTIONS);
69
        */
70
12
      }
71
72
      /// Return description.
73
      const char* Description() override  // required
74
0
      {
75
0
        return
76
0
          "Point cloud on VDW surface\n"
77
0
          "Generates a point cloud on the VDW surface around the molecule\n\n"
78
79
0
          "The surface location is calculated by adding the probe atom radius\n"
80
0
          "(if specified) to the Van der Waal radius of the particular atom multipled\n"
81
0
          "by the specified multiple (1.0 if unspecified)."
82
83
0
          "Output is a list of {x,y,z} tuples in Angstrom. Alternatively, if the ``x``\n"
84
0
          "option is specified, the :ref:`XYZ_cartesian_coordinates_format` is used\n"
85
0
          "instead.\n\n"
86
87
0
          "Write Options, e.g. -xx\n"
88
0
          "  r <radii> create a surface for each VDS radius (default 1.0)\n"
89
0
          "        A comma-separated list of VDW radius multiples\n"
90
0
          "  d <densities> for each surface, specify the point density (default 1.0 Angstrom^2)\n"
91
0
          "        A comma-separated list of densities\n"
92
0
          "  p <radius> radius of the probe atom in Angstrom (default 0.0)\n"
93
0
          "  x output in xyz format\n\n";
94
0
      }
95
96
      /// Return a specification url, not really a specification since
97
      /// I couldn't find it but close enough.
98
      const char* SpecificationURL() override
99
0
      {
100
0
        return "N/A";
101
0
      }
102
103
      /// Return MIME type, NULL in this case.
104
0
      const char* GetMIMEType() override { return "chemical/x-pointcloud"; }
105
106
      /// Return read/write flag: read only.
107
      unsigned int Flags() override
108
2.08k
      {
109
2.08k
        return WRITEONEONLY | NOTREADABLE;
110
2.08k
      }
111
112
      /// Skip to object: used for multi-object file formats.
113
0
      int SkipObjects( int /*n*/, OpenBabel::OBConversion* /*pConv*/ ) override { return 0; }
114
115
      /// Read: always return false.
116
      bool ReadMolecule(OpenBabel::OBBase*, OpenBabel::OBConversion*) override
117
0
      {
118
0
        return false;
119
0
      }
120
121
      /// Write.
122
      bool WriteMolecule(OpenBabel::OBBase*, OpenBabel::OBConversion*) override;
123
  };
124
125
126
  // Global variable used to register PointCloud format.
127
  PointCloudFormat thePointCloudFormat;
128
129
130
131
  //==============================================================================
132
133
0
  double rv( void ){
134
0
    return ((double)std::rand()) / RAND_MAX; // C++11 provides a better <random> but I think this will do here
135
0
  }
136
137
0
  vector3 surface_point( double cx, double cy, double cz, double radius ) {
138
0
    double  theta = rv() * 2. * M_PI;
139
0
    double  phi   = acos( 2. * rv() - 1. );
140
141
0
    double  x = radius * cos(theta) * sin(phi);
142
0
    double  y = radius * sin(theta) * sin(phi);
143
0
    double  z = radius * cos(phi);
144
145
0
    return vector3( cx+x, cy+y, cz+z );
146
0
  }
147
148
  // Only add a point if it's more than densit_r away from all others
149
0
  bool conditional_add( vector<vector3> &list, vector3 point, double density_r ) {
150
151
0
    double density_r2 = density_r * density_r;
152
0
    for( std::vector<vector3>::iterator it = list.begin(); it != list.end(); ++it ) {
153
0
      vector3 r = *it - point;
154
0
      double r2 = dot(r,r);
155
0
      if( r2 < density_r2 ) {
156
0
        return false;
157
0
      }
158
0
    }
159
0
    list.push_back( point );
160
0
    return true;
161
0
  }
162
163
  //------------------------------------------------------------------------------
164
  bool PointCloudFormat::WriteMolecule( OBBase* pOb, OBConversion* pConv )
165
2.07k
  {
166
2.07k
    OBMol* pmol = dynamic_cast< OBMol* >(pOb);
167
2.07k
    if (pmol == nullptr) return false;
168
169
2.07k
    ostream& os = *pConv->GetOutStream();
170
171
2.07k
    const char *radius_list_str  = nullptr;
172
2.07k
    const char *density_list_str = nullptr;
173
2.07k
    double probe_radius = 0.;
174
2.07k
    bool format_xyz = false;
175
176
2.07k
    if( pConv->IsOption( "r" ) ) {
177
0
      radius_list_str = pConv->IsOption("r", OBConversion::OUTOPTIONS);
178
0
    }
179
2.07k
    if( pConv->IsOption( "d" ) ) {
180
0
      density_list_str = pConv->IsOption("d", OBConversion::OUTOPTIONS);
181
0
    }
182
2.07k
    if( pConv->IsOption( "p" ) ) {
183
0
      probe_radius = atof( pConv->IsOption("p", OBConversion::OUTOPTIONS) );
184
0
      if( !isfinite(probe_radius) || probe_radius < 0. ) { probe_radius = 0.; }
185
0
    }
186
2.07k
    if( pConv->IsOption( "x" ) ) {
187
0
      format_xyz = true;
188
0
    }
189
190
191
2.07k
    int total_points = 0;
192
2.07k
    std::srand(0); // let's be deterministic(FSVO)
193
194
2.07k
    vector< vector3 > filtered_points;
195
196
2.07k
    vector<double> radius_mult_list;
197
2.07k
    vector<double> density_list;
198
199
    // Extract the any lists of radii and point densities
200
2.07k
    if( radius_list_str ) {
201
0
      char*a = strdup( radius_list_str );
202
0
      const char * x = strtok( a, "," );
203
0
      while( x ) {
204
0
        double d = atof(x);
205
0
        if( isfinite(d) && d>0. ) { radius_mult_list.push_back( d ); }
206
0
        x = strtok(nullptr, ",");
207
0
      }
208
0
      free(a);
209
0
    }
210
2.07k
    if( density_list_str ) {
211
0
      char*a = strdup( density_list_str );
212
0
      const char * x = strtok( a, "," );
213
0
      while( x ) {
214
0
        double d = atof(x);
215
0
        if( isfinite(d) && d>0. ) { density_list.push_back( d ); }
216
0
        x = strtok(nullptr, ",");
217
0
      }
218
0
      free(a);
219
0
    }
220
2.07k
    if( radius_mult_list.size() == 0 ) { radius_mult_list.push_back( 1.0 ); }
221
4.15k
    while( density_list.size() < radius_mult_list.size()) {
222
2.07k
      density_list.push_back(1.0);
223
2.07k
    }
224
225
    // foreach radius generate a point cloud
226
4.15k
    for( int j = 0; j < radius_mult_list.size(); j++ ) {
227
228
2.07k
      double radius_mult  = radius_mult_list[j];
229
2.07k
      double density      = density_list[j];
230
2.07k
      double density_r    = sqrt ( density / M_PI );
231
232
2.07k
      FOR_ATOMS_OF_MOL( a, *pmol )
233
0
      {
234
0
        vector<vector3> pt;
235
236
237
        // for each atom generate a cloud of points
238
        // on the atom-centred spherical surface r_VDW * radius_multiplier
239
        // ensuring each point is >= density_r from all others
240
0
        const double* c = a->GetCoordinate();
241
0
        double vdwrad   = probe_radius + ( OBElements::GetVdwRad( a->GetAtomicNum() ) * radius_mult );
242
243
        // estimate # of points by dividing area of VDW sphere
244
        // by the required density
245
0
        int estimate_number_of_points = (int)  ( 0.6 * (( 4. * M_PI * M_PI * vdwrad * vdwrad ) / ( density )) );
246
0
        int count = 0;
247
0
        while ( count < estimate_number_of_points ) {
248
0
          vector3 p = surface_point( c[0], c[1], c[2], vdwrad );
249
0
          if( conditional_add( pt, p, density_r ) ) {
250
0
            count++;
251
0
            total_points++;
252
0
          }
253
0
        }
254
255
        // now cull any points on that surface that are within r_VDW * radius_multiplier
256
        // of any other atom
257
0
        for( std::vector< vector3 >::iterator it = pt.begin(); it != pt.end(); ++it ) {
258
0
          bool exclude = false;
259
0
          FOR_ATOMS_OF_MOL( a, *pmol )
260
0
          {
261
0
            const double* c = a->GetCoordinate();
262
0
            double vdwrad   =  probe_radius + ( OBElements::GetVdwRad( a->GetAtomicNum() ) * radius_mult );
263
0
            vdwrad *= vdwrad;
264
0
            vector3 r = *it - vector3( c[0], c[1], c[2] );
265
0
            double r2 = r[0]*r[0] + r[1]*r[1] + r[2] * r[2];
266
0
            if( r2 < vdwrad ) { exclude = true; break; }
267
0
          }
268
0
          if( !exclude ) {
269
0
            filtered_points.push_back( *it );
270
0
          }
271
0
        }
272
0
      }
273
2.07k
    }
274
275
    // Output the final list of points in XYZ format
276
2.07k
    if( format_xyz ) {
277
0
      os << filtered_points.size() << "\n\n";
278
0
    }
279
280
2.07k
    for( std::vector<vector3>::iterator it2 = filtered_points.begin(); it2 != filtered_points.end(); ++it2 ) {
281
0
      if( format_xyz ) {
282
0
        os << "Xx\t";
283
0
      }
284
0
      os << (*it2)[0] << "\t" << (*it2)[1] << "\t" << (*it2)[2] << "\n";
285
0
    }
286
287
2.07k
    os.flush();
288
2.07k
    return true;
289
2.07k
  }
290
291
}