Skip to content

Commit 1faa9b2

Browse files
drroeDaniel R. Roe
andauthored
Use cubic spline interpolation to speed up volmap (#869)
* SPAM shouldn't be hidden. * Skip peaks where the bin calculation would potentially overflow * Avoid using zero bandwidth for KDE histogram when only 1 value available * Start adding dataset that can be used to hold vectors and an associated scalar value. * Add Sync() * Update dependencies * Add VECTOR_SCALAR set * Add DataIO class for writing peaks. * Enable peaks file output * Use vector scalar data to store peaks, write to data file. * Make the vector scalar set 0 dimension so it is forced to use the DataIO_Peaks format. May want to do this in a less hacky way in the future. * Re-organize branches in volmap loop to avoid some unnecessary calculation. Slightly more efficient (1-2%) and avoids using a 'continue'. * A line with no elements cannot be a Tinker atom line * Start adding code to read the peaks file. * Remove some debug info * Add peaks read. * Rewrite peaks read to make it more strict. Must conform to what ID_DataFormat expects. * Use DataSet/DataFile framework for peaks in SPAM * Ensure that the GetNextString for output file is the last possible thing parsed from the argument list * Fix class const variable name * Make it so that you can calculate peaks without having to write them to a file. * Fix code comment * Fix up dependencies * Add class for approximating functions with splines * Ensure table is cleared when fill is called. * Use the splines. 36% speed increase! * Disable some debug checking * Woops, bring back return statement * Remove some debug info. Add scaling factor argument. * Add splinedx keyword * Add code note about function calls in openmp parallel regions * Fix up help. * Warn if extra arguments remain after 'purewater' specified for SPAM * Fix up SPAM documentation. * Update the volmap manual entry. * More manual updates for volmap. Fix up help. * Fix up some dependencies * Add /= operator * Add IncrementElement() function * Add SetGrid function * Change SetGrid to accept index instead * Enable DIV and MULT for grids * Add option to read grids in as double precision * Add function to return format type * Add == operator for TextFormat * Fix the operator * Ensure debug level is set for files being written * When writing opendx files, if the format is still the default for grids, use general format (matches previous behavior). However, if another format has been set use that instead. * Add check for out of range value in Yval(). Save input X values for use in a more accurate function, Yval_accurate(). * Add another way of filling the table; can give better results with Yvals_accurate() * DRR - Omit out of bounds check, rely on args being reasonable. Results in minor speedup (e.g. 5.4 fps to 6.4 fps). * Put the range check behind a define. * Allow volmap to be compiled for either single or double precision grid * Add ability to compile and use exponential function. * Add define for using the more accurate but slower table lookup * Add some debug * Add some debug info and a new function that uses the internal X table. * Add spline function table check for Yval_xtable * Add VOLMAP_USEXTABLE * Ensure the xvalues used in the mesh are generated in the same way as during the Y eval step. Round-off makes the addition method prone to errors for small table spacings * When enabled, make sure the out of range check happens before dx calculation so it actually takes effect * Add splinescale arg, improve output * Add back the ReadHelp function. * Remove scaling argument from spline table setup. Instead, have volmap add 1.0 to the max expected value to avoid Runges phenomenon. * Add fast exponential from N. N. Schraudolph, Neural Computation 11, 853–862 (1999) * Add define for using fast exp from Schraudolph * Add 64 bit version and header protection * Fix the always inline attribute; add volmap define for using 64 bit fast exps * Add library from https://github.com/simonpf/fastexp for testing * Add ability to test the fast exp code from https://github.com/simonpf/fastexp * Remove the "fast" exponential code. Was only for testing. Leave the hooks in, just in case we ever need to test it again. Fast exponential code will be kept in a separate repository. * Remove obsolete splinescale argument * Clean up output * Remove old code, improve code documentation. * Put spline info print into separate functions. * Add debug info. Do not set up spline table if using system exp() * Add a warning if any grid spacings are smaller than 0.4 * Use SplineFxnTable for erfc * Make erfc things private * Make function private * Rename function to avoid confusion * Make function private. Add some code docs * Make more vars private * Remove obsolete code * Make more variables private * Minor version bump for using splines to speed up volmap. In principle the defaults chosen should make it so that the results are exactly the same as with system exp(); however there is the potential for some differences, so bump the minor version instead of revision. * Add 'splinedx' to volmap help Co-authored-by: Daniel R. Roe <daniel.roe@nih.gov>
1 parent d3f4ea1 commit 1faa9b2

File tree

12 files changed

+420
-85
lines changed

12 files changed

+420
-85
lines changed

doc/cpptraj.lyx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35435,7 +35435,7 @@ volmap [out <filename>] <mask> [radscale <factor>] [stepfac <fac>]
3543535435
\end_layout
3543635436

3543735437
\begin_layout LyX-Code
35438-
[sphere] [radii {vdw | element}]
35438+
[sphere] [radii {vdw | element}] [splinedx <spacing>]
3543935439
\end_layout
3544035440

3544135441
\begin_layout LyX-Code
@@ -35528,6 +35528,14 @@ radii
3552835528
radii.
3552935529
\end_layout
3553035530

35531+
\begin_layout Description
35532+
splinedx
35533+
\begin_inset space ~
35534+
\end_inset
35535+
35536+
<spacing> Spacing to use for cubic spline interpolation (default 0.01 Ang.).
35537+
\end_layout
35538+
3553135539
\begin_layout Description
3553235540
calcpeaks If specified, peaks in the grid density will be calculated and
3553335541
saved to set <setname> with aspect
@@ -35737,6 +35745,25 @@ buffer
3573735745
.
3573835746
\end_layout
3573935747

35748+
\begin_layout Standard
35749+
The calculation is sped up by using cubic splines to interpolate the exponential
35750+
function when calculating the Gaussians.
35751+
The details are in Roe & Brooks,
35752+
\begin_inset Quotes eld
35753+
\end_inset
35754+
35755+
Improving the Speed of Volumetric Density Map Generation via Cubic Spline
35756+
Interpolation
35757+
\begin_inset Quotes erd
35758+
\end_inset
35759+
35760+
, J.
35761+
Mol.
35762+
Graph.
35763+
Model.
35764+
(2021).
35765+
\end_layout
35766+
3574035767
\begin_layout Subsection
3574135768
volume
3574235769
\end_layout

src/Action_Volmap.cpp

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
#ifdef _OPENMP
1616
# include <omp.h>
1717
#endif
18+
//#if defined(VOLMAP_USEFASTEXPS) || defined(VOLMAP_USEFASTEXP64)
19+
//# incl ude "FastExp_Schraudolph.h"
20+
//#elif defined(VOLMAP_USEFASTEXPIEEE)
21+
//# incl ude "FastExp_fastexp.h"
22+
//#endif
1823

1924
const double Action_Volmap::sqrt_8_pi_cubed_ = sqrt(8.0*Constants::PI*Constants::PI*Constants::PI);
2025

@@ -39,7 +44,8 @@ Action_Volmap::Action_Volmap() :
3944
peakcut_(0.05),
4045
buffer_(3.0),
4146
radscale_(1.0),
42-
stepfac_(4.1)
47+
stepfac_(4.1),
48+
splineDx_(0.01) // Recommendation from Roe & Brooks JMGM 2021
4349
{}
4450

4551
void Action_Volmap::Help() const {
@@ -55,7 +61,7 @@ void Action_Volmap::Help() const {
5561

5662
void Action_Volmap::RawHelp() const {
5763
mprintf("\t[out <filename>] <mask> [radscale <factor>] [stepfac <fac>]\n"
58-
"\t[sphere] [radii {vdw | element}]\n"
64+
"\t[sphere] [radii {vdw | element}] [splinedx <spacing>]\n"
5965
"\t[calcpeaks] [peakcut <cutoff>] [peakfile <xyzfile>]\n"
6066
"\t{ data <existing set> |\n"
6167
"\t name <setname> <dx> [<dy> <dz>]\n"
@@ -82,6 +88,8 @@ Action::RetType Action_Volmap::Init(ArgList& actionArgs, ActionInit& init, int d
8288
radscale_ = 0.5;
8389
stepfac_ = 1.0;
8490
}
91+
//splineDx_ = actionArgs.getKeyDouble("splinedx", 1.0/5000.0);
92+
splineDx_ = actionArgs.getKeyDouble("splinedx", 0.01);
8593
radscale_ = 1.0 / actionArgs.getKeyDouble("radscale", radscale_);
8694
stepfac_ = actionArgs.getKeyDouble("stepfac", stepfac_);
8795
std::string radarg = actionArgs.GetStringKey("radii");
@@ -214,7 +222,11 @@ Action::RetType Action_Volmap::Init(ArgList& actionArgs, ActionInit& init, int d
214222
xmin_ = oxyz[0];
215223
ymin_ = oxyz[1];
216224
zmin_ = oxyz[2];
217-
}
225+
}
226+
// Warn for small grid spacing
227+
if (dx_ < 0.4 || dy_ < 0.4 || dz_ < 0.4)
228+
mprintf("Warning: Grid spacings smaller than 0.4 Ang. may be very slow;\n"
229+
"Warning: consider using larger grid spacings.\n");
218230
//std::string density = actionArgs.GetStringKey("density"); // FIXME obsolete?
219231
// Get the required mask
220232
std::string reqmask = actionArgs.GetMaskNext();
@@ -295,7 +307,26 @@ Action::RetType Action_Volmap::Init(ArgList& actionArgs, ActionInit& init, int d
295307
mprintf("\tWhen smearing Gaussian, voxels farther than radii/2 will be skipped.\n");
296308
mprintf("\tDividing radii by %f\n", 1.0/radscale_);
297309
mprintf("\tFactor for determining number of bins to smear Gaussian is %f\n", stepfac_);
298-
310+
# if defined(VOLMAP_USEEXP)
311+
mprintf("\tUsing system exp() function for evaluating Gaussians.\n");
312+
//# elif defined(VOLMAP_USEFASTEXPS)
313+
// mprintf("\tUsing exp() from N. Schraudolph, Neural Computation 11, 853–862 (1999).\n");
314+
//# elif defined(VOLMAP_USEFASTEXP64)
315+
// mprintf("\tUsing 64 bit version of exp() from N. Schraudolph, Neural Computation 11, 853–862 (1999).\n");
316+
//# elif defined(VOLMAP_USEFASTEXPIEEE)
317+
// mprintf("\tUsing exp() from Schraudolph & Malossi et al.\n");
318+
# else /* VOLMAP_USEEXP */
319+
mprintf("\tExponential for Gaussians will be approximated using cubic splines\n"
320+
"\t with a spacing of %g Ang.\n", splineDx_);
321+
mprintf("# Citation: Roe, D. R.; Brooks, B. R.; \"Improving the Speed of Volumetric\n"
322+
"# Density Map Generation via Cubic Spline Interpolation\".\n"
323+
"# Journal of Molecular Graphics and Modelling (2021).\n");
324+
# if defined(VOLMAP_USEACCURATE)
325+
mprintf("\tSplines using more accurate but slower table lookup.\n");
326+
# elif defined(VOLMAP_USEXTABLE)
327+
mprintf("\tSplines using less accurate lookup with tabled X values.\n");
328+
# endif
329+
# endif /* VOLMAP_USEEXP */
299330
if (outfile != 0)
300331
mprintf("\tDensity will wrtten to '%s'\n", outfile->DataFilename().full());
301332
mprintf("\tGrid dataset name is '%s'\n", grid_->legend());
@@ -381,8 +412,14 @@ Action::RetType Action_Volmap::Setup(ActionSetup& setup) {
381412
//mprintf("DEBUG: nx= %i ny= %i nz= %i\n", nxstep, nystep, nzstep);
382413
//mprintf("DEBUG: %g %g %g %g\n", maxx, maxy, maxz, maxDist);
383414
maxDist *= (-1.0 / (2.0 * maxRad * maxRad));
384-
if (debug_ > 1)
385-
mprintf("DEBUG: maxDist= %g\n", maxDist);
415+
//mprintf("DEBUG: max= %g\n", maxDist);
416+
# ifndef VOLMAP_USEEXP
417+
// Set up the interpolation table
418+
if (table_.FillTable( exp, splineDx_, maxDist, 1.0 )) return Action::ERR;
419+
table_.PrintMemUsage("\t");
420+
if (debug_ > 0)
421+
table_.PrintTableInfo("DEBUG: ");
422+
# endif
386423

387424
if ((int)Atoms_.size() < densitymask_.Nselected())
388425
mprintf("Warning: %i atoms have 0.0 radii and will be skipped.\n",
@@ -511,9 +548,39 @@ Action::RetType Action_Volmap::DoAction(int frameNum, ActionFrame& frm) {
511548
if (dist2 < rcut2) {
512549
//mprintf("DEBUG: rhalf= %g dist2= %g exfac= %g exp= %g\n", rhalf, dist2, exfac, exfac*dist2);
513550
# ifdef _OPENMP
551+
// NOTE: It is OK to call table_.Yval() here because in OpenMP
552+
// local variables of called functions are private.
553+
# if defined(VOLMAP_USEEXP)
514554
GRID_THREAD_[mythread].incrementBy(xval, yval, zval, norm * exp(exfac * dist2));
555+
//# elif defined(VOLMAP_USEFASTEXPS)
556+
// GRID_THREAD[mythread].incrementBy(xval, yval, zval, norm * FASTEXPS(exfac * dist2));
557+
//# elif defined(VOLMAP_USEFASTEXP64)
558+
// GRID_THREAD[mythread].incrementBy(xval, yval, zval, norm * fast_exps_64(exfac * dist2));
559+
//# elif defined(VOLMAP_USEFASTEXPIEEE)
560+
// GRID_THREAD[mythread].incrementBy(xval, yval, zval, norm * fastexp::exp<double, fastexp::IEEE, 4>(exfac * dist2));
561+
# elif defined(VOLMAP_USEACCURATE)
562+
GRID_THREAD_[mythread].incrementBy(xval, yval, zval, norm * table_.Yval_accurate(exfac * dist2));
563+
# elif defined(VOLMAP_USEXTABLE)
564+
GRID_THREAD_[mythread].incrementBy(xval, yval, xval, norm * table_.Yval_xtable(exfac * dist2));
565+
# else
566+
GRID_THREAD_[mythread].incrementBy(xval, yval, zval, norm * table_.Yval(exfac * dist2));
567+
# endif
515568
# else /* OPENMP */
569+
# if defined(VOLMAP_USEEXP)
516570
grid_->Increment(xval, yval, zval, norm * exp(exfac * dist2));
571+
//# elif defined(VOLMAP_USEFASTEXPS)
572+
// grid_->Increment(xval, yval, zval, norm * FASTEXPS(exfac * dist2));
573+
//# elif defined(VOLMAP_USEFASTEXP64)
574+
// grid_->Increment(xval, yval, zval, norm * fast_exps_64(exfac * dist2));
575+
//# elif defined(VOLMAP_USEFASTEXPIEEE)
576+
// grid_->Increment(xval, yval, zval, norm * fastexp::exp<double, fastexp::IEEE, 4>(exfac * dist2));
577+
# elif defined(VOLMAP_USEACCURATE)
578+
grid_->Increment(xval, yval, zval, norm * table_.Yval_accurate(exfac * dist2));
579+
# elif defined(VOLMAP_USEXTABLE)
580+
grid_->Increment(xval, yval, zval, norm * table_.Yval_xtable(exfac * dist2));
581+
# else
582+
grid_->Increment(xval, yval, zval, norm * table_.Yval(exfac * dist2));
583+
# endif
517584
# endif /* OPENMP */
518585
}
519586
} // END loop over zval
@@ -531,6 +598,7 @@ Action::RetType Action_Volmap::DoAction(int frameNum, ActionFrame& frm) {
531598
}
532599

533600
#ifdef MPI
601+
/** Sync grid to the master process. */
534602
int Action_Volmap::SyncAction() {
535603
# ifdef _OPENMP
536604
CombineGridThreads();

src/Action_Volmap.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define INC_ACTION_VOLMAP_H
33
#include "Action.h"
44
#include "Grid.h"
5+
#include "SplineFxnTable.h"
56
#ifdef VOLMAP_DOUBLE
67
# define VOLMAP_DS_T DataSet_GridDbl
78
# define VOLMAP_T double
@@ -10,6 +11,14 @@
1011
# define VOLMAP_T float
1112
#endif
1213
class VOLMAP_DS_T;
14+
/// Calculate atomic volumetric density maps from trajectory data.
15+
/** By default the grid type used is single-precision, mostly to save space.
16+
* A double-precision grid can be used by compiling with the
17+
* VOLMAP_DOUBLE define.
18+
* Also by default the exp() function will be approximated with cubic spline
19+
* interpolation. To use the system exp() function, compile with the
20+
* VOLMAP_USEEXP define.
21+
*/
1322
class Action_Volmap : public Action {
1423
public:
1524
Action_Volmap();
@@ -51,6 +60,8 @@ class Action_Volmap : public Action {
5160
double radscale_; ///< The scaling factor to divide all radii by
5261
double stepfac_; ///< Factor for determining how many steps to smear Gaussian
5362
static const double sqrt_8_pi_cubed_;
63+
SplineFxnTable table_;
64+
double splineDx_;
5465
# ifdef _OPENMP
5566
typedef std::vector< Grid<VOLMAP_T> > Garray;
5667
Garray GRID_THREAD_;

src/Ewald.cpp

Lines changed: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,17 @@
1313

1414
/// CONSTRUCTOR
1515
Ewald::Ewald() :
16-
sumq_(0.0),
17-
sumq2_(0.0),
1816
ew_coeff_(0.0),
1917
lw_coeff_(0.0),
2018
switch_width_(0.0),
2119
cutoff_(0.0),
2220
cut2_(0.0),
2321
cut2_0_(0.0),
2422
dsumTol_(0.0),
25-
erfcTableDx_(0.0),
26-
one_over_Dx_(0.0),
27-
debug_(0)
23+
debug_(0),
24+
sumq_(0.0),
25+
sumq2_(0.0),
26+
Vdw_Recip_term_(0)
2827
{
2928
# ifdef DEBUG_EWALD
3029
// Save fractional translations for 1 cell in each direction (and primary cell).
@@ -91,44 +90,9 @@ double Ewald::erfc_func(double xIn) {
9190
return erfc;
9291
}
9392

94-
// Ewald::FillErfcTable()
95-
void Ewald::FillErfcTable(double cutoffIn, double dxdr) {
96-
one_over_Dx_ = 1.0 / erfcTableDx_;
97-
unsigned int erfcTableSize = (unsigned int)(dxdr * one_over_Dx_ * cutoffIn * 1.5);
98-
Darray erfc_X, erfc_Y;
99-
erfc_X.reserve( erfcTableSize );
100-
erfc_Y.reserve( erfcTableSize );
101-
// Save X and Y values so we can calc the spline coefficients
102-
double xval = 0.0;
103-
for (unsigned int i = 0; i != erfcTableSize; i++) {
104-
double yval = erfc_func( xval );
105-
erfc_X.push_back( xval );
106-
erfc_Y.push_back( yval );
107-
xval += erfcTableDx_;
108-
}
109-
Spline cspline;
110-
cspline.CubicSpline_Coeff(erfc_X, erfc_Y);
111-
erfc_X.clear();
112-
// Store values in Spline table
113-
erfc_table_.reserve( erfcTableSize * 4 ); // Y B C D
114-
for (unsigned int i = 0; i != erfcTableSize; i++) {
115-
erfc_table_.push_back( erfc_Y[i] );
116-
erfc_table_.push_back( cspline.B_coeff()[i] );
117-
erfc_table_.push_back( cspline.C_coeff()[i] );
118-
erfc_table_.push_back( cspline.D_coeff()[i] );
119-
}
120-
// Memory saved Y values plus spline B, C, and D coefficient arrays.
121-
mprintf("\tMemory used by Erfc table and splines: %s\n",
122-
ByteString(erfc_table_.size() * sizeof(double), BYTE_DECIMAL).c_str());
123-
}
124-
12593
// Ewald::ERFC()
12694
double Ewald::ERFC(double xIn) const {
127-
int xidx = ((int)(one_over_Dx_ * xIn));
128-
double dx = xIn - ((double)xidx * erfcTableDx_);
129-
xidx *= 4;
130-
return erfc_table_[xidx] +
131-
dx*(erfc_table_[xidx+1] + dx*(erfc_table_[xidx+2] + dx*erfc_table_[xidx+3]));
95+
return table_.Yval( xIn);
13296
}
13397

13498
/** Determine Ewald coefficient from cutoff and direct sum tolerance.
@@ -200,7 +164,7 @@ void Ewald::CalculateC6params(Topology const& topIn, AtomMask const& maskIn) {
200164
}
201165

202166
/** Set up exclusion lists for selected atoms. */
203-
void Ewald::SetupExcluded(Topology const& topIn, AtomMask const& maskIn)
167+
void Ewald::SetupExclusionList(Topology const& topIn, AtomMask const& maskIn)
204168
{
205169
// Use distance of 4 (up to dihedrals)
206170
if (Excluded_.SetupExcluded(topIn.Atoms(), maskIn, 4,
@@ -223,7 +187,7 @@ int Ewald::CheckInput(Box const& boxIn, int debugIn, double cutoffIn, double dsu
223187
ew_coeff_ = ew_coeffIn;
224188
lw_coeff_ = lw_coeffIn;
225189
switch_width_ = switch_widthIn;
226-
erfcTableDx_ = erfcTableDxIn;
190+
double erfcTableDx = erfcTableDxIn;
227191
// Check input
228192
if (cutoff_ < Constants::SMALL) {
229193
mprinterr("Error: Direct space cutoff (%g) is too small.\n", cutoff_);
@@ -252,9 +216,14 @@ int Ewald::CheckInput(Box const& boxIn, int debugIn, double cutoffIn, double dsu
252216
dsumTol_ = 1E-5;
253217
if (DABS(ew_coeff_) < Constants::SMALL)
254218
ew_coeff_ = FindEwaldCoefficient( cutoff_, dsumTol_ );
255-
if (erfcTableDx_ <= 0.0) erfcTableDx_ = 1.0 / 5000;
219+
if (erfcTableDx <= 0.0) erfcTableDx = 1.0 / 5000;
256220
// TODO make this optional
257-
FillErfcTable( cutoff_, ew_coeff_ );
221+
if (table_.FillTable( erfc_func, erfcTableDx, 0.0, cutoff_*ew_coeff_*1.5 )) {
222+
mprinterr("Error: Could not set up spline table for ERFC\n");
223+
return 1;
224+
}
225+
table_.PrintMemUsage("\t");
226+
table_.PrintTableInfo("\t");
258227
// TODO do for C6 as well
259228
// TODO for C6 correction term
260229
if (lw_coeff_ < 0.0)

0 commit comments

Comments
 (0)