/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 | | } |