OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
Qsurf.cpp
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the following paper when you publish your work:
33 *
34 * [1] Drisko et al., J. Open Source Softw. 9, 7004 (2024).
35 *
36 * Good starting points for code and simulation methodology are:
37 *
38 * [2] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
39 * [3] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
40 * [4] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
41 * [5] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
42 * [6] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
43 * [7] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
44 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
45 * [9] Drisko & Gezelter, J. Chem. Theory Comput. 20, 4986-4997 (2024).
46 */
47
48#include "applications/sequentialProps/Qsurf.hpp"
49
50#include <algorithm>
51#include <cmath>
52#include <fstream>
53#include <iomanip>
54#include <sstream>
55#include <vector>
56
57#include "io/DumpReader.hpp"
59#include "utils/Constants.hpp"
60#include "utils/Revision.hpp"
61#include "utils/simError.h"
62
63namespace OpenMD {
64
65 Qsurf::Qsurf(SimInfo* info, const std::string& filename,
66 const std::string& sele1, const std::string& sele2,
67 RealType rCut, RealType voxelSize, RealType gaussWidth) :
68 SequentialAnalyzer(info, filename, sele1, sele2),
69 rCut_(rCut), voxelSize_(voxelSize), gaussWidth_(gaussWidth),
70 nBinsX_(0), nBinsY_(0), nBinsZ_(0) {
71 setSequenceType("Qsurf");
72
73 std::ostringstream params;
74 params << "rCut = " << rCut_ << ", voxelSize = " << voxelSize_
75 << ", gaussWidth = " << gaussWidth_;
76 setParameterString(params.str());
77
78 // Determine grid dimensions from the initial snapshot's box
79 Mat3x3d hmat =
80 info->getSnapshotManager()->getCurrentSnapshot()->getHmat();
81 nBinsX_ = int(hmat(0, 0) / voxelSize_);
82 nBinsY_ = int(hmat(1, 1) / voxelSize_);
83 nBinsZ_ = int(hmat(2, 2) / voxelSize_);
84 }
85
86 void Qsurf::doSequence() {
87 preSequence();
88
89 DumpReader reader(info_, dumpFilename_);
90 int nFrames = reader.getNFrames();
91
92 bool usePeriodicBoundaryConditions =
93 info_->getSimParams()->getUsePeriodicBoundaryConditions();
94
95 // Gaussian kernel truncation radius (in voxel units)
96 int kMax = int(5.0 * gaussWidth_ / voxelSize_);
97 int kSqLim = kMax * kMax;
98
99 // Determine the base name for output files.
100 // If outputFilename_ is "foo.dx", baseName is "foo"
101 // If outputFilename_ is "foo", baseName is "foo"
102 std::string baseName = outputFilename_;
103 std::string::size_type pos = baseName.rfind(".dx");
104 if (pos != std::string::npos && pos == baseName.length() - 3) {
105 baseName = baseName.substr(0, pos);
106 }
107
108 int nFramesWritten = 0;
109
110 for (frame_ = 0; frame_ < nFrames; frame_ += step_) {
111 reader.readFrame(frame_);
112 currentSnapshot_ = info_->getSnapshotManager()->getCurrentSnapshot();
113 RealType time = currentSnapshot_->getTime();
114 Mat3x3d hmat = currentSnapshot_->getHmat();
115 Vector3d halfBox =
116 Vector3d(hmat(0, 0), hmat(1, 1), hmat(2, 2)) / 2.0;
117
118 if (evaluator1_.isDynamic()) {
119 seleMan1_.setSelectionSet(evaluator1_.evaluate());
120 }
121 if (evaluator2_.isDynamic()) {
122 seleMan2_.setSelectionSet(evaluator2_.evaluate());
123 }
124
125 // ----------------------------------------------------------
126 // 1) Allocate and zero the 3D grids for this frame
127 // ----------------------------------------------------------
128 std::vector<std::vector<std::vector<RealType>>> qField(nBinsX_, std::vector<std::vector<RealType>>(nBinsY_, std::vector<RealType>(nBinsZ_, 0.0)));
129
130 std::vector<std::vector<std::vector<RealType>>> weight(nBinsX_, std::vector<std::vector<RealType>>(nBinsY_, std::vector<RealType>(nBinsZ_, 0.0)));
131
132 // ----------------------------------------------------------
133 // 2) Compute Q_k for each molecule in sele1, smear onto grid
134 // ----------------------------------------------------------
135 int isd1, isd2;
136 StuntDouble* sd;
137 StuntDouble* sd2;
138
139 for (sd = seleMan1_.beginSelected(isd1); sd != NULL;
140 sd = seleMan1_.nextSelected(isd1)) {
141 int myIndex = sd->getGlobalIndex();
142
143 // Find neighbors from sele2 within rCut
144 std::vector<std::pair<RealType, StuntDouble*>> neighbors;
145
146 for (sd2 = seleMan2_.beginSelected(isd2); sd2 != NULL;
147 sd2 = seleMan2_.nextSelected(isd2)) {
148 if (sd2->getGlobalIndex() == myIndex) continue;
149
150 Vector3d vec = sd->getPos() - sd2->getPos();
151 if (usePeriodicBoundaryConditions)
152 currentSnapshot_->wrapVector(vec);
153
154 RealType r = vec.length();
155 if (r < rCut_) { neighbors.push_back(std::make_pair(r, sd2)); }
156 }
157
158 std::sort(neighbors.begin(), neighbors.end());
159 int nbors = std::min((int)neighbors.size(), 4);
160 int nang = nbors * (nbors - 1) / 2;
161 if (nang == 0) continue;
162
163 // Compute Q_k (Errington-Debenedetti rescaled tetrahedrality)
164 Vector3d rk = sd->getPos();
165 RealType Qk = 1.0;
166
167 for (int i = 0; i < nbors - 1; i++) {
168 Vector3d rik = rk - neighbors[i].second->getPos();
169 if (usePeriodicBoundaryConditions)
170 currentSnapshot_->wrapVector(rik);
171 rik.normalize();
172
173 for (int j = i + 1; j < nbors; j++) {
174 Vector3d rkj = rk - neighbors[j].second->getPos();
175 if (usePeriodicBoundaryConditions)
176 currentSnapshot_->wrapVector(rkj);
177 rkj.normalize();
178
179 RealType cospsi = dot(rik, rkj);
180 Qk -= (pow(cospsi + 1.0 / 3.0, 2) * 2.25 / nang);
181 }
182 }
183
184 // Smear Q_k onto the voxel grid with a Gaussian kernel
185 if (usePeriodicBoundaryConditions)
186 currentSnapshot_->wrapVector(rk);
187
188 Vector3d gridPos = rk + halfBox;
189 Vector3i whichVoxel(int(gridPos[0] / voxelSize_),
190 int(gridPos[1] / voxelSize_),
191 int(gridPos[2] / voxelSize_));
192
193 RealType denom = pow(2.0 * sqrt(Constants::PI) * gaussWidth_, 3);
194
195 for (int l = -kMax; l <= kMax; l++) {
196 for (int m = -kMax; m <= kMax; m++) {
197 for (int n = -kMax; n <= kMax; n++) {
198 if (l * l + m * m + n * n > kSqLim) continue;
199
200 int ll = (whichVoxel[0] + l) % nBinsX_;
201 if (ll < 0) ll += nBinsX_;
202 int mm = (whichVoxel[1] + m) % nBinsY_;
203 if (mm < 0) mm += nBinsY_;
204 int nn = (whichVoxel[2] + n) % nBinsZ_;
205 if (nn < 0) nn += nBinsZ_;
206
207 Vector3d bPos = Vector3d(ll, mm, nn) * voxelSize_ - halfBox;
208 Vector3d d = bPos - rk;
209 currentSnapshot_->wrapVector(d);
210
211 RealType exponent = -dot(d, d) / pow(2.0 * gaussWidth_, 2);
212 RealType w = exp(exponent) / denom;
213
214 weight[ll][mm][nn] += w;
215 qField[ll][mm][nn] += w * Qk;
216 }
217 }
218 }
219 } // end sele1 loop
220
221 // ----------------------------------------------------------
222 // 3) Normalize: qField[i][j][k] = weighted average Q
223 // ----------------------------------------------------------
224 for (int i = 0; i < nBinsX_; i++) {
225 for (int j = 0; j < nBinsY_; j++) {
226 for (int k = 0; k < nBinsZ_; k++) {
227 if (weight[i][j][k] > 0.0)
228 qField[i][j][k] /= weight[i][j][k];
229 }
230 }
231 }
232
233 // ----------------------------------------------------------
234 // 4) Write this frame's Q field as an OpenDX file
235 // ----------------------------------------------------------
236 std::ostringstream dxFileName;
237 dxFileName << baseName << "." << std::setfill('0') << std::setw(4)
238 << nFramesWritten << ".dx";
239
240 Vector3d origin = -halfBox;
241 writeDXFrame(dxFileName.str(), qField, origin, time);
242 nFramesWritten++;
243
244 } // end frame loop
245
246 // ----------------------------------------------------------
247 // 5) Write a VMD Tcl loader script
248 // ----------------------------------------------------------
249 std::string scriptName = baseName + ".vmd";
250 writeVMDScript(scriptName, baseName, nFramesWritten);
251 }
252
253 void Qsurf::writeDXFrame(
254 const std::string& fname,
255 const std::vector<std::vector<std::vector<RealType>>>& field,
256 const Vector3d& origin,
257 RealType time) {
258
259 std::ofstream dxStream(fname.c_str());
260 if (!dxStream.is_open()) {
261 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
262 "Qsurf: unable to open %s for writing\n", fname.c_str());
263 painCave.isFatal = 1;
264 simError();
265 }
266
267 int nItems = nBinsX_ * nBinsY_ * nBinsZ_;
268
269 // ----------------------------------------------------------
270 // OpenDX header (APBS-compatible, VMD-readable)
271 //
272 // To load this file in VMD:
273 // mol addfile myfile.dx type dx waitfor all
274 // mol representation Isosurface 0.85 0 0 0 1 1
275 // mol addrep top
276 //
277 // To animate a series, use the companion .vmd script:
278 // vmd -e basename.vmd
279 //
280 // In Jmol, use:
281 // isosurface cutoff 0.85 "myfile.dx"
282 // ----------------------------------------------------------
283
284 Revision r;
285 dxStream << "# OpenDX Data Explorer format\n";
286 dxStream << "# Generated by OpenMD Qsurf (SequentialProps)\n";
287 dxStream << "# OpenMD " << r.getFullRevision() << "\n";
288 dxStream << "# " << r.getBuildDate() << "\n";
289 dxStream << "# Tetrahedral order parameter Q field\n";
290 dxStream << "# Simulation time: " << time << " fs\n";
291 dxStream << "# Grid: " << nBinsX_ << " x " << nBinsY_ << " x "
292 << nBinsZ_ << " voxelSize = " << voxelSize_ << " Ang\n";
293 dxStream << "#\n";
294 dxStream << "# To load in VMD:\n";
295 dxStream << "# mol new " << fname << " type dx waitfor all\n";
296 dxStream << "# mol representation Isosurface 0.85 0 0 0 1 1\n";
297 dxStream << "# mol addrep top\n";
298 dxStream << "#\n";
299 dxStream << "# To load a time series, use the companion .vmd script.\n";
300 dxStream << "#\n";
301 dxStream << "# In Jmol:\n";
302 dxStream << "# isosurface cutoff 0.85 \"" << fname << "\"\n";
303 dxStream << "#\n";
304
305 // Positions object
306 dxStream << "object 1 class gridpositions counts "
307 << nBinsX_ << " " << nBinsY_ << " " << nBinsZ_ << "\n";
308 dxStream << "origin "
309 << std::fixed << std::setprecision(6)
310 << origin[0] << " " << origin[1] << " " << origin[2] << "\n";
311 dxStream << "delta " << voxelSize_ << " 0 0\n";
312 dxStream << "delta 0 " << voxelSize_ << " 0\n";
313 dxStream << "delta 0 0 " << voxelSize_ << "\n";
314
315 // Connections object
316 dxStream << "object 2 class gridconnections counts "
317 << nBinsX_ << " " << nBinsY_ << " " << nBinsZ_ << "\n";
318
319 // Data array
320 dxStream << "object 3 class array type double rank 0 items "
321 << nItems << " data follows\n";
322
323 // Data values: x slow, y medium, z fast (DX convention)
324 int count = 0;
325 for (int i = 0; i < nBinsX_; i++) {
326 for (int j = 0; j < nBinsY_; j++) {
327 for (int k = 0; k < nBinsZ_; k++) {
328 dxStream << std::scientific << std::setprecision(6)
329 << field[i][j][k];
330 count++;
331 if (count % 3 == 0)
332 dxStream << "\n";
333 else
334 dxStream << " ";
335 }
336 }
337 }
338 if (count % 3 != 0) dxStream << "\n";
339
340 dxStream << "attribute \"dep\" string \"positions\"\n";
341 dxStream << "object \"Tetrahedrality Q\" class field\n";
342 dxStream << "component \"positions\" value 1\n";
343 dxStream << "component \"connections\" value 2\n";
344 dxStream << "component \"data\" value 3\n";
345
346 dxStream.close();
347 }
348
349 void Qsurf::writeVMDScript(const std::string& scriptName,
350 const std::string& baseName,
351 int nFramesWritten) {
352 std::ofstream tcl(scriptName.c_str());
353 if (!tcl.is_open()) {
354 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
355 "Qsurf: unable to open %s for writing\n",
356 scriptName.c_str());
357 painCave.isFatal = 1;
358 simError();
359 }
360
361 Revision r;
362 tcl << "#!/usr/bin/env vmd\n";
363 tcl << "# -------------------------------------------------------\n";
364 tcl << "# VMD Tcl script for animating Qsurf tetrahedrality field\n";
365 tcl << "# Generated by OpenMD " << r.getFullRevision() << "\n";
366 tcl << "# " << r.getBuildDate() << "\n";
367 tcl << "# -------------------------------------------------------\n";
368 tcl << "#\n";
369 tcl << "# Usage:\n";
370 tcl << "# vmd -e " << scriptName << "\n";
371 tcl << "#\n";
372 tcl << "# This script loads " << nFramesWritten
373 << " volumetric Q-field snapshots\n";
374 tcl << "# into a single VMD molecule, creates an Isosurface\n";
375 tcl << "# representation, and installs a trace callback so the\n";
376 tcl << "# displayed volumetric dataset updates as you scrub\n";
377 tcl << "# through frames.\n";
378 tcl << "#\n";
379 tcl << "# Once loaded, you can:\n";
380 tcl << "# - Adjust the isovalue in Graphics > Representations\n";
381 tcl << "# - Change Draw style (Solid/Wireframe/Points)\n";
382 tcl << "# - Color by Volume to see Q variation on the surface\n";
383 tcl << "# - Overlay the molecular trajectory by loading the\n";
384 tcl << "# .omd dump into the SAME molecule first, e.g.:\n";
385 tcl << "# mol new system.omd waitfor all\n";
386 tcl << "# then source this script with the molecule selected.\n";
387 tcl << "# -------------------------------------------------------\n";
388 tcl << "\n";
389
390 // Load the first frame to create the molecule
391 tcl << "# Load the first DX file as a new molecule\n";
392 tcl << "set qmol [mol new {" << baseName
393 << ".0000.dx} type dx waitfor all]\n\n";
394
395 // Load remaining frames
396 if (nFramesWritten > 1) {
397 tcl << "# Load remaining frames into the same molecule\n";
398 tcl << "for {set i 1} {$i < " << nFramesWritten << "} {incr i} {\n";
399 tcl << " set fname [format \"" << baseName
400 << ".%04d.dx\" $i]\n";
401 tcl << " mol addfile $fname type dx waitfor all molid $qmol\n";
402 tcl << "}\n\n";
403 }
404
405 // Create the isosurface representation
406 tcl << "# Create an isosurface representation\n";
407 tcl << "# Default isovalue = 0.85 (adjust to taste)\n";
408 tcl << "mol delrep 0 $qmol\n";
409 tcl << "mol representation Isosurface 0.85 0 0 0 1 1\n";
410 tcl << "mol color Volume 0\n";
411 tcl << "mol selection {all}\n";
412 tcl << "mol material Opaque\n";
413 tcl << "mol addrep $qmol\n\n";
414
415 // Store the rep name for the trace callback
416 tcl << "# Get the representation name for later updates\n";
417 tcl << "set qrep [mol repname $qmol 0]\n\n";
418
419 // Install the trace callback
420 tcl << "# -------------------------------------------------------\n";
421 tcl << "# Trace callback: when the frame changes, update the\n";
422 tcl << "# isosurface to use the volumetric dataset corresponding\n";
423 tcl << "# to the current frame number.\n";
424 tcl << "# -------------------------------------------------------\n";
425 tcl << "proc qsurf_update_frame {args} {\n";
426 tcl << " global qmol qrep\n";
427 tcl << " set f [molinfo $qmol get frame]\n";
428 tcl << " set repid [mol repindex $qmol $qrep]\n";
429 tcl << " if {$repid >= 0} {\n";
430 tcl << " mol modstyle $repid $qmol Isosurface 0.85 $f 0 0 1 1\n";
431 tcl << " }\n";
432 tcl << "}\n\n";
433 tcl << "trace variable vmd_frame($qmol) w qsurf_update_frame\n\n";
434
435 tcl << "# Set animation to first frame\n";
436 tcl << "animate goto 0\n";
437 tcl << "qsurf_update_frame\n\n";
438
439 tcl << "puts \"Qsurf: loaded " << nFramesWritten
440 << " frames into molecule $qmol\"\n";
441 tcl << "puts \"Qsurf: use the animation controls to scrub through "
442 << "frames\"\n";
443 tcl << "puts \"Qsurf: adjust isovalue in Graphics > Representations"
444 << "\"\n";
445
446 tcl.close();
447 }
448
449} // namespace OpenMD
"applications/sequentialProps/SequentialAnalyzer"
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Definition SimInfo.hpp:96
Real length() const
Returns the length of this vector.
Definition Vector.hpp:397
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.