Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2023 The plumed team
3 : (see the PEOPLE file at the root of the distribution for a list of names)
4 :
5 : See http://www.plumed.org for more information.
6 :
7 : This file is part of plumed, version 2.
8 :
9 : plumed is free software: you can redistribute it and/or modify
10 : it under the terms of the GNU Lesser General Public License as published by
11 : the Free Software Foundation, either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : plumed is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU Lesser General Public License for more details.
18 :
19 : You should have received a copy of the GNU Lesser General Public License
20 : along with plumed. If not, see <http://www.gnu.org/licenses/>.
21 : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
22 :
23 : #ifdef __PLUMED_HAS_LIBTORCH
24 : #include "colvar/Colvar.h"
25 : #include "core/ActionRegister.h"
26 : #include "core/PlumedMain.h"
27 : #include "tools/Communicator.h"
28 : #include "tools/Matrix.h"
29 : #include "core/GenericMolInfo.h"
30 : #include "core/ActionSet.h"
31 : #include "tools/File.h"
32 : #include "tools/OpenMP.h"
33 : #include <string>
34 : #include <cmath>
35 : #include <map>
36 : #include <numeric>
37 : #include <ctime>
38 : #include <algorithm>
39 : #include <utility>
40 : #include "tools/Random.h"
41 :
42 : //this avoids nasty warnings on which we have no control
43 : //Here I am using the pragma for gcc/clang, for visual studio you'll need something different
44 : //This should be the correct direction,
45 : //but the warning that it is blocking everithing is:
46 : // error: ISO C++11 requires at least one argument for the "..." in a variadic macro [-Werror]
47 : //that is not named like [-Werror=shadow]
48 : //so , at time of writing this I did not found any solution but deactivating warning in the makefile
49 :
50 : #pragma GCC diagnostic push
51 : #pragma GCC diagnostic ignored "-Wshadow"
52 :
53 : #include <torch/torch.h>
54 : #include <torch/script.h>
55 :
56 : #pragma GCC diagnostic push
57 :
58 : #ifndef M_PI
59 : #define M_PI 3.14159265358979323846
60 : #endif
61 :
62 : namespace PLMD {
63 : namespace isdb {
64 :
65 : //+PLUMEDOC ISDB_COLVAR EMMIVOX
66 : /*
67 : Bayesian single-structure and ensemble refinement with cryo-EM maps.
68 :
69 : This action implements the Bayesian approach for single-structure and ensemble refinement from cryo-EM maps introduced <a href="https://doi.org/10.1371/journal.pcbi.1012180">here</a>.
70 : EMMIVox does not require fitting the cryo-EM map with a Gaussian Mixture Model, as done in [EMMI](EMMI.md), but uses directly the voxels in the deposited map.
71 :
72 : When run in single-replica mode, this action allows atomistic, flexible refinement (and B-factors inference) of an individual structure into a density map.
73 : A coarse-grained forward model can also be used in combination with the MARTINI force field.
74 : Combined with a multi-replica framework (such as the -multi option in GROMACS), the user can model an ensemble of structures using
75 : the Metainference approach that is introduced in the paper cited below. The approach can be used to model continous dynamics of flexible regions as well as semi-ordered waters, lipids, and ions.
76 :
77 : !!! note "installing libtorch"
78 :
79 : To use EMMIVOX, PLUMED must be linked against the LibTorch library using the instructions [on the module page](module_isdb.md).
80 :
81 : ## Examples
82 :
83 : Complete tutorials for single-structure and ensemble refinement can be found <a href="https://github.com/COSBlab/EMMIVox">here</a>.
84 :
85 : */
86 : //+ENDPLUMEDOC
87 :
88 : class EMMIVOX : public Colvar {
89 :
90 : private:
91 :
92 : // temperature in kbt
93 : double kbt_;
94 : // model - atom types
95 : std::vector<unsigned> Model_type_;
96 : // model - list of atom sigmas - one per atom type
97 : std::vector<Vector5d> Model_s_;
98 : // model - list of atom weights - one per atom type
99 : std::vector<Vector5d> Model_w_;
100 : // model - map between residue/chain IDs and list of atoms
101 : std::map< std::pair<int,std::string>, std::vector<unsigned> > Model_resmap_;
102 : // model - list of residue/chain IDs per atom
103 : std::vector< std::pair<int,std::string> > Model_res_;
104 : // model - list of neighboring voxels per atom
105 : std::vector< std::vector<unsigned> > Model_nb_;
106 : // model - map between residue/chain ID and bfactor
107 : std::map< std::pair<int, std::string>, double> Model_b_;
108 : // model - global list of residue/chain IDs
109 : std::vector< std::pair<int,std::string> > Model_rlist_;
110 : // model density
111 : std::vector<double> ovmd_;
112 :
113 : // data map - voxel position
114 : std::vector<Vector> Map_m_;
115 : // data map - density
116 : std::vector<double> ovdd_;
117 : // data map - error
118 : std::vector<double> exp_err_;
119 :
120 : // derivatives
121 : std::vector<Vector> ovmd_der_;
122 : std::vector<Vector> atom_der_;
123 : std::vector<double> score_der_;
124 : // constants
125 : double inv_sqrt2_, sqrt2_pi_, inv_pi2_;
126 : std::vector<Vector5d> pref_;
127 : std::vector<Vector5d> invs2_;
128 : std::vector<Vector5d> cfact_;
129 : std::vector<double> cut_;
130 : // metainference
131 : unsigned nrep_;
132 : unsigned replica_;
133 : std::vector<double> ismin_;
134 : // neighbor list
135 : double nl_dist_cutoff_;
136 : double nl_gauss_cutoff_;
137 : unsigned nl_stride_;
138 : bool first_time_;
139 : std::vector< std::pair<unsigned,unsigned> > nl_;
140 : std::vector< std::pair<unsigned,unsigned> > ns_;
141 : std::vector<Vector> refpos_;
142 : // averaging
143 : bool no_aver_;
144 : // correlation;
145 : bool do_corr_;
146 : // Monte Carlo stuff
147 : Random random_;
148 : // Scale and Offset
149 : double scale_;
150 : double offset_;
151 : // Bfact Monte Carlo
152 : double dbfact_;
153 : double bfactmin_;
154 : double bfactmax_;
155 : double bfactsig_;
156 : bool bfactnoc_;
157 : bool bfactread_;
158 : int MCBstride_;
159 : double MCBaccept_;
160 : double MCBtrials_;
161 : // residue neighbor list
162 : std::vector< std::vector<unsigned> > nl_res_;
163 : bool bfactemin_;
164 : // Martini scattering factors
165 : bool martini_;
166 : // status stuff
167 : unsigned int statusstride_;
168 : std::string statusfilename_;
169 : OFile statusfile_;
170 : bool first_status_;
171 : // total energy and virial
172 : double ene_;
173 : Tensor virial_;
174 : double eps_;
175 : // model density file
176 : unsigned int mapstride_;
177 : std::string mapfilename_;
178 : // Libtorch stuff
179 : bool gpu_;
180 : torch::Tensor ovmd_gpu_;
181 : torch::Tensor ovmd_der_gpu_;
182 : torch::Tensor ismin_gpu_;
183 : torch::Tensor ovdd_gpu_;
184 : torch::Tensor Map_m_gpu_;
185 : torch::Tensor pref_gpu_;
186 : torch::Tensor invs2_gpu_;
187 : torch::Tensor nl_id_gpu_;
188 : torch::Tensor nl_im_gpu_;
189 : torch::Tensor pref_nl_gpu_;
190 : torch::Tensor invs2_nl_gpu_;
191 : torch::Tensor Map_m_nl_gpu_;
192 : torch::DeviceType device_t_;
193 : //
194 : // write file with model density
195 : void write_model_density(long int step);
196 : // get median of vector
197 : double get_median(std::vector<double> v);
198 : // read and write status
199 : void read_status();
200 : void print_status(long int step);
201 : // accept or reject
202 : bool doAccept(double oldE, double newE, double kbt);
203 : // vector of close residues
204 : void get_close_residues();
205 : // do MonteCarlo for Bfactor
206 : void doMonteCarloBfact();
207 : // calculate model parameters
208 : std::vector<double> get_Model_param(std::vector<AtomNumber> &atoms);
209 : // read data file
210 : void get_exp_data(const std::string &datafile);
211 : // auxiliary methods
212 : void prepare_gpu();
213 : void initialize_Bfactor(double reso);
214 : void get_auxiliary_vectors();
215 : void push_auxiliary_gpu();
216 : // calculate overlap between two Gaussians
217 : double get_overlap(const Vector &d_m, const Vector &m_m,
218 : const Vector5d &cfact, const Vector5d &m_s, double bfact);
219 : // update the neighbor list
220 : void update_neighbor_list();
221 : // update data on device
222 : void update_gpu();
223 : // update the neighbor sphere
224 : void update_neighbor_sphere();
225 : bool do_neighbor_sphere();
226 : // calculate forward model and score on device
227 : void calculate_fmod();
228 : void calculate_score();
229 : // calculate correlation
230 : void calculate_corr();
231 :
232 : public:
233 : static void registerKeywords( Keywords& keys );
234 : explicit EMMIVOX(const ActionOptions&);
235 : // active methods:
236 : void prepare() override;
237 : void calculate() override;
238 : };
239 :
240 : PLUMED_REGISTER_ACTION(EMMIVOX,"EMMIVOX")
241 :
242 6 : void EMMIVOX::registerKeywords( Keywords& keys ) {
243 6 : Colvar::registerKeywords( keys );
244 6 : keys.add("atoms","ATOMS","atoms used in the calculation of the density map, typically all heavy atoms");
245 6 : keys.add("compulsory","DATA_FILE","file with cryo-EM map");
246 6 : keys.add("compulsory","RESOLUTION", "cryo-EM map resolution");
247 6 : keys.add("compulsory","NORM_DENSITY","integral of experimental density");
248 6 : keys.add("compulsory","WRITE_STRIDE","stride for writing status file");
249 6 : keys.add("optional","NL_DIST_CUTOFF","neighbor list distance cutoff");
250 6 : keys.add("optional","NL_GAUSS_CUTOFF","neighbor list Gaussian sigma cutoff");
251 6 : keys.add("optional","NL_STRIDE","neighbor list update frequency");
252 6 : keys.add("optional","SIGMA_MIN","minimum density error");
253 6 : keys.add("optional","DBFACT","Bfactor MC step");
254 6 : keys.add("optional","BFACT_MAX","Bfactor maximum value");
255 6 : keys.add("optional","MCBFACT_STRIDE", "Bfactor MC stride");
256 6 : keys.add("optional","BFACT_SIGMA","Bfactor sigma prior");
257 6 : keys.add("optional","STATUS_FILE","write a file with all the data useful for restart");
258 6 : keys.add("optional","SCALE","scale factor");
259 6 : keys.add("optional","OFFSET","offset");
260 6 : keys.add("optional","TEMP","temperature");
261 6 : keys.add("optional","WRITE_MAP","file with model density");
262 6 : keys.add("optional","WRITE_MAP_STRIDE","stride for writing model density to file");
263 6 : keys.addFlag("NO_AVER",false,"no ensemble averaging in multi-replica mode");
264 6 : keys.addFlag("CORRELATION",false,"calculate correlation coefficient");
265 6 : keys.addFlag("GPU",false,"calculate EMMIVOX on GPU with Libtorch");
266 6 : keys.addFlag("BFACT_NOCHAIN",false,"Do not use chain ID for Bfactor MC");
267 6 : keys.addFlag("BFACT_READ",false,"Read Bfactor on RESTART (automatic with DBFACT>0)");
268 6 : keys.addFlag("BFACT_MINIMIZE",false,"Accept only moves that decrease energy");
269 6 : keys.addFlag("MARTINI",false,"Use Martini scattering factors");
270 6 : keys.add("optional","SEED","seed of the random number generator used by the Monte Carlo sampling of the Bfactors (by default it is taken from the clock at every run)");
271 12 : keys.addOutputComponent("scoreb","default","scalar","Bayesian score");
272 12 : keys.addOutputComponent("scale", "default","scalar","scale factor");
273 12 : keys.addOutputComponent("offset","default","scalar","offset");
274 12 : keys.addOutputComponent("accB", "default","scalar", "Bfactor MC acceptance");
275 12 : keys.addOutputComponent("kbt", "default","scalar", "temperature in energy unit");
276 12 : keys.addOutputComponent("corr", "CORRELATION","scalar", "correlation coefficient");
277 6 : keys.addDOI("10.1126/sciadv.1501177");
278 6 : }
279 :
280 2 : EMMIVOX::EMMIVOX(const ActionOptions&ao):
281 : PLUMED_COLVAR_INIT(ao),
282 2 : nl_dist_cutoff_(1.0), nl_gauss_cutoff_(3.0), nl_stride_(50),
283 2 : first_time_(true), no_aver_(false), do_corr_(false),
284 2 : scale_(1.), offset_(0.),
285 2 : dbfact_(0.0), bfactmin_(0.05), bfactmax_(5.0),
286 2 : bfactsig_(0.1), bfactnoc_(false), bfactread_(false),
287 2 : MCBstride_(1), MCBaccept_(0.), MCBtrials_(0.), bfactemin_(false),
288 2 : martini_(false), statusstride_(0), first_status_(true),
289 2 : eps_(0.0001), mapstride_(0), gpu_(false) {
290 : // set constants
291 2 : inv_sqrt2_ = 1.0/sqrt(2.0);
292 2 : sqrt2_pi_ = sqrt(2.0 / M_PI);
293 2 : inv_pi2_ = 0.5 / M_PI / M_PI;
294 :
295 : // list of atoms
296 2 : std::vector<AtomNumber> atoms;
297 4 : parseAtomList("ATOMS", atoms);
298 :
299 : // file with experimental cryo-EM map
300 : std::string datafile;
301 2 : parse("DATA_FILE", datafile);
302 :
303 : // neighbor list cutoffs
304 2 : parse("NL_DIST_CUTOFF",nl_dist_cutoff_);
305 2 : parse("NL_GAUSS_CUTOFF",nl_gauss_cutoff_);
306 : // checks
307 2 : if(nl_dist_cutoff_<=0. && nl_gauss_cutoff_<=0.) {
308 0 : error("You must specify either NL_DIST_CUTOFF or NL_GAUSS_CUTOFF or both");
309 : }
310 2 : if(nl_gauss_cutoff_<=0.) {
311 0 : nl_gauss_cutoff_ = 1.0e+10;
312 : }
313 2 : if(nl_dist_cutoff_<=0.) {
314 0 : nl_dist_cutoff_ = 1.0e+10;
315 : }
316 : // neighbor list update stride
317 2 : parse("NL_STRIDE",nl_stride_);
318 2 : if(nl_stride_<=0) {
319 0 : error("NL_STRIDE must be explicitly specified and positive");
320 : }
321 :
322 : // minimum value for error
323 2 : double sigma_min = 0.2;
324 2 : parse("SIGMA_MIN", sigma_min);
325 2 : if(sigma_min<0.) {
326 0 : error("SIGMA_MIN must be greater or equal to zero");
327 : }
328 :
329 : // status file parameters
330 2 : parse("WRITE_STRIDE", statusstride_);
331 2 : if(statusstride_<=0) {
332 0 : error("you must specify a positive WRITE_STRIDE");
333 : }
334 2 : parse("STATUS_FILE", statusfilename_);
335 2 : if(statusfilename_=="") {
336 0 : statusfilename_ = "EMMIStatus"+getLabel();
337 : }
338 :
339 : // integral of the experimetal density
340 : double norm_d;
341 2 : parse("NORM_DENSITY", norm_d);
342 :
343 : // temperature
344 2 : kbt_ = getkBT();
345 :
346 : // scale and offset
347 2 : parse("SCALE", scale_);
348 2 : parse("OFFSET",offset_);
349 :
350 : // B-factors MC
351 2 : parse("DBFACT",dbfact_);
352 : // read Bfactors
353 2 : parseFlag("BFACT_READ",bfactread_);
354 : // do not use chains
355 2 : parseFlag("BFACT_NOCHAIN",bfactnoc_);
356 : // other parameters
357 2 : if(dbfact_>0.) {
358 1 : parse("MCBFACT_STRIDE",MCBstride_);
359 1 : parse("BFACT_MAX",bfactmax_);
360 1 : parse("BFACT_SIGMA",bfactsig_);
361 1 : parseFlag("BFACT_MINIMIZE",bfactemin_);
362 : // checks
363 1 : if(MCBstride_<=0) {
364 0 : error("you must specify a positive MCBFACT_STRIDE");
365 : }
366 1 : if(bfactmax_<=bfactmin_) {
367 0 : error("you must specify a positive BFACT_MAX");
368 : }
369 1 : if(MCBstride_%nl_stride_!=0) {
370 0 : error("MCBFACT_STRIDE must be multiple of NL_STRIDE");
371 : }
372 1 : if(bfactsig_<=0.) {
373 0 : error("you must specify a positive BFACT_SIGMA");
374 : }
375 : }
376 :
377 : // read map resolution
378 : double reso;
379 2 : parse("RESOLUTION", reso);
380 2 : if(reso<=0.) {
381 0 : error("RESOLUTION must be strictly positive");
382 : }
383 :
384 : // averaging or not
385 2 : parseFlag("NO_AVER",no_aver_);
386 :
387 : // calculate correlation coefficient
388 2 : parseFlag("CORRELATION",do_corr_);
389 :
390 : // write density file
391 2 : parse("WRITE_MAP_STRIDE", mapstride_);
392 2 : parse("WRITE_MAP", mapfilename_);
393 2 : if(mapstride_>0 && mapfilename_=="") {
394 0 : error("With WRITE_MAP_STRIDE you must specify WRITE_MAP");
395 : }
396 :
397 : // use GPU?
398 2 : parseFlag("GPU",gpu_);
399 : // set device
400 2 : if (gpu_ && torch::cuda::is_available()) {
401 0 : device_t_ = torch::kCUDA;
402 : } else {
403 2 : device_t_ = torch::kCPU;
404 2 : gpu_ = false;
405 : }
406 :
407 : // Martini model
408 2 : parseFlag("MARTINI",martini_);
409 :
410 : // seed of the random number generator
411 2 : int seed=-1;
412 2 : parse("SEED",seed);
413 2 : if(seed<-1) {
414 0 : error("SEED must be a non-negative integer");
415 : }
416 :
417 : // check read
418 2 : checkRead();
419 :
420 : // set parallel stuff
421 2 : unsigned mpisize=comm.Get_size();
422 2 : if(mpisize>1) {
423 0 : error("EMMIVOX supports only OpenMP parallelization");
424 : }
425 :
426 : // get number of replicas
427 2 : if(no_aver_) {
428 0 : nrep_ = 1;
429 : } else {
430 2 : nrep_ = multi_sim_comm.Get_size();
431 : }
432 2 : replica_ = multi_sim_comm.Get_rank();
433 :
434 2 : if(nrep_>1 && dbfact_>0) {
435 0 : error("Bfactor sampling not supported with ensemble averaging");
436 : }
437 :
438 2 : log.printf(" number of atoms involved : %u\n", atoms.size());
439 2 : log.printf(" experimental density map : %s\n", datafile.c_str());
440 2 : if(no_aver_) {
441 0 : log.printf(" without ensemble averaging\n");
442 : }
443 2 : if(gpu_) {
444 0 : log.printf(" running on GPU \n");
445 : } else {
446 2 : log.printf(" running on CPU \n");
447 : }
448 2 : if(nl_dist_cutoff_ <1.0e+10) {
449 2 : log.printf(" neighbor list distance cutoff : %lf\n", nl_dist_cutoff_);
450 : }
451 2 : if(nl_gauss_cutoff_<1.0e+10) {
452 2 : log.printf(" neighbor list Gaussian sigma cutoff : %lf\n", nl_gauss_cutoff_);
453 : }
454 2 : log.printf(" neighbor list update stride : %u\n", nl_stride_);
455 2 : log.printf(" minimum density error : %f\n", sigma_min);
456 2 : log.printf(" scale factor : %lf\n", scale_);
457 2 : log.printf(" offset : %lf\n", offset_);
458 2 : log.printf(" reading/writing to status file : %s\n", statusfilename_.c_str());
459 2 : log.printf(" with stride : %u\n", statusstride_);
460 2 : if(dbfact_>0) {
461 1 : log.printf(" maximum Bfactor MC move : %f\n", dbfact_);
462 1 : log.printf(" stride MC move : %u\n", MCBstride_);
463 1 : log.printf(" using prior with sigma : %f\n", bfactsig_);
464 : }
465 2 : if(bfactread_) {
466 0 : log.printf(" reading Bfactors from file : %s\n", statusfilename_.c_str());
467 : }
468 2 : log.printf(" temperature of the system in energy unit : %f\n", kbt_);
469 2 : if(nrep_>1) {
470 0 : log.printf(" number of replicas for averaging: %u\n", nrep_);
471 0 : log.printf(" replica ID : %u\n", replica_);
472 : }
473 2 : if(mapstride_>0) {
474 1 : log.printf(" writing model density to file : %s\n", mapfilename_.c_str());
475 1 : log.printf(" with stride : %u\n", mapstride_);
476 : }
477 2 : if(martini_) {
478 0 : log.printf(" using Martini scattering factors\n");
479 : }
480 :
481 : // calculate model constant parameters
482 2 : std::vector<double> Model_w = get_Model_param(atoms);
483 :
484 : // read experimental map and errors
485 2 : get_exp_data(datafile);
486 2 : log.printf(" number of voxels : %u\n", static_cast<unsigned>(ovdd_.size()));
487 :
488 : // normalize atom weight map
489 : double norm_m = accumulate(Model_w.begin(), Model_w.end(), 0.0);
490 : // renormalization and constant factor on atom types
491 130 : for(unsigned i=0; i<Model_w_.size(); ++i) {
492 128 : Vector5d cf;
493 768 : for(unsigned j=0; j<5; ++j) {
494 640 : Model_w_[i][j] *= norm_d / norm_m;
495 640 : cf[j] = Model_w_[i][j]/pow( 2.0*pi, 1.5 );
496 : }
497 128 : cfact_.push_back(cf);
498 : }
499 :
500 : // median density
501 2 : double ovdd_m = get_median(ovdd_);
502 : // median experimental error
503 2 : double err_m = get_median(exp_err_);
504 : // minimum error
505 2 : double minerr = sigma_min*ovdd_m;
506 : // print out statistics
507 2 : log.printf(" median density : %lf\n", ovdd_m);
508 2 : log.printf(" minimum error : %lf\n", minerr);
509 2 : log.printf(" median error : %lf\n", err_m);
510 : // populate ismin: cycle on all voxels
511 14668 : for(unsigned id=0; id<ovdd_.size(); ++id) {
512 : // define smin
513 14666 : double smin = std::max(minerr, exp_err_[id]);
514 : // and to ismin_
515 14666 : ismin_.push_back(1.0/smin);
516 : }
517 :
518 : // prepare gpu stuff: map centers, data, and error
519 2 : prepare_gpu();
520 :
521 : // initialize Bfactors
522 2 : initialize_Bfactor(reso);
523 :
524 : // read status file if restarting
525 2 : if(getRestart() || bfactread_) {
526 0 : read_status();
527 : }
528 :
529 : // prepare auxiliary vectors
530 2 : get_auxiliary_vectors();
531 :
532 : // prepare other vectors: data and derivatives
533 2 : ovmd_.resize(ovdd_.size());
534 2 : atom_der_.resize(Model_type_.size());
535 2 : score_der_.resize(ovdd_.size());
536 :
537 : // add components
538 4 : addComponentWithDerivatives("scoreb");
539 4 : componentIsNotPeriodic("scoreb");
540 4 : addComponent("scale");
541 4 : componentIsNotPeriodic("scale");
542 4 : addComponent("offset");
543 4 : componentIsNotPeriodic("offset");
544 4 : addComponent("kbt");
545 2 : componentIsNotPeriodic("kbt");
546 2 : if(dbfact_>0) {
547 2 : addComponent("accB");
548 2 : componentIsNotPeriodic("accB");
549 : }
550 2 : if(do_corr_) {
551 2 : addComponent("corr");
552 2 : componentIsNotPeriodic("corr");
553 : }
554 :
555 : // initialize random seed
556 : // with SEED replica r gets the stream 1+(SEED*nreplicas+r) mod (2^31-1)
557 : unsigned iseed;
558 2 : if(seed>=0) {
559 1 : const unsigned long long nr = multi_sim_comm.Get_size();
560 1 : iseed = static_cast<unsigned>(1 + (static_cast<unsigned long long>(seed)*nr+replica_) % 2147483646ULL);
561 : } else {
562 1 : iseed = time(NULL)+replica_;
563 : }
564 2 : random_.setSeed(-static_cast<int>(iseed));
565 :
566 : // request atoms
567 2 : requestAtoms(atoms);
568 :
569 : // print bibliography
570 2 : log<<" Bibliography "<<plumed.cite("Bonomi, Camilloni, Bioinformatics, 33, 3999 (2017)");
571 2 : log<<plumed.cite("Hoff, Thomasen, Lindorff-Larsen, Bonomi, PLoS Comput. Biol. 20 (2024) e1012180");
572 2 : if(!no_aver_ && nrep_>1) {
573 0 : log<<plumed.cite("Bonomi, Camilloni, Cavalli, Vendruscolo, Sci. Adv. 2, e150117 (2016)");
574 : }
575 2 : log<<"\n";
576 2 : }
577 :
578 2 : void EMMIVOX::prepare_gpu() {
579 : // number of data points
580 2 : int nd = ovdd_.size();
581 : // 1) put ismin_ on device_t_
582 8 : ismin_gpu_ = torch::from_blob(ismin_.data(), {nd}, torch::kFloat64).to(torch::kFloat32).to(device_t_);
583 : // 2) put ovdd_ on device_t_
584 6 : ovdd_gpu_ = torch::from_blob(ovdd_.data(), {nd}, torch::kFloat64).to(torch::kFloat32).to(device_t_);
585 : // 3) put Map_m_ on device_t_
586 2 : std::vector<double> Map_m_gpu(3*nd);
587 2 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
588 : for(int i=0; i<nd; ++i) {
589 : Map_m_gpu[i] = Map_m_[i][0];
590 : Map_m_gpu[i+nd] = Map_m_[i][1];
591 : Map_m_gpu[i+2*nd] = Map_m_[i][2];
592 : }
593 : // libtorch tensor
594 8 : Map_m_gpu_ = torch::from_blob(Map_m_gpu.data(), {3,nd}, torch::kFloat64).clone().to(torch::kFloat32).to(device_t_);
595 2 : }
596 :
597 2 : void EMMIVOX::write_model_density(long int step) {
598 2 : OFile ovfile;
599 2 : ovfile.link(*this);
600 : std::string num;
601 2 : Tools::convert(step,num);
602 2 : std::string name = mapfilename_+"-"+num;
603 2 : ovfile.open(name);
604 : ovfile.setHeavyFlush();
605 2 : ovfile.fmtField("%10.7e ");
606 : // write density
607 14668 : for(unsigned i=0; i<ovmd_.size(); ++i) {
608 14666 : ovfile.printField("Model", ovmd_[i]);
609 29332 : ovfile.printField("ModelScaled", scale_ * ovmd_[i] + offset_);
610 14666 : ovfile.printField("Data", ovdd_[i]);
611 14666 : ovfile.printField();
612 : }
613 2 : ovfile.close();
614 2 : }
615 :
616 4 : double EMMIVOX::get_median(std::vector<double> v) {
617 : // dimension of vector
618 4 : unsigned size = v.size();
619 : // in case of only one entry
620 4 : if (size==1) {
621 0 : return v[0];
622 : } else {
623 : // reorder vector
624 4 : sort(v.begin(), v.end());
625 : // odd or even?
626 4 : if (size%2==0) {
627 0 : return (v[size/2-1]+v[size/2])/2.0;
628 : } else {
629 4 : return v[size/2];
630 : }
631 : }
632 : }
633 :
634 0 : void EMMIVOX::read_status() {
635 : double MDtime;
636 : // open file
637 0 : IFile *ifile = new IFile();
638 0 : ifile->link(*this);
639 0 : if(ifile->FileExist(statusfilename_)) {
640 0 : ifile->open(statusfilename_);
641 0 : while(ifile->scanField("MD_time", MDtime)) {
642 : // read scale and offset
643 0 : ifile->scanField("scale", scale_);
644 0 : ifile->scanField("offset", offset_);
645 : // read bfactors if doing fitting of reading it at restart
646 0 : if(dbfact_>0 || bfactread_) {
647 : // cycle on residues
648 0 : for(unsigned ir=0; ir<Model_rlist_.size(); ++ir) {
649 : // key: pair of residue/chain IDs
650 : std::pair<int,std::string> key = Model_rlist_[ir];
651 : // convert ires to std::string
652 : std::string num;
653 0 : Tools::convert(key.first,num);
654 : // read entry
655 0 : std::string ch = key.second;
656 0 : if(ch==" ") {
657 : ch="";
658 : }
659 0 : ifile->scanField("bf-"+num+":"+ch, Model_b_[key]);
660 : }
661 : }
662 : // new line
663 0 : ifile->scanField();
664 : }
665 0 : ifile->close();
666 : } else {
667 0 : error("Cannot find status file "+statusfilename_+"\n");
668 : }
669 0 : delete ifile;
670 0 : }
671 :
672 6 : void EMMIVOX::print_status(long int step) {
673 : // if first time open the file
674 6 : if(first_status_) {
675 2 : first_status_ = false;
676 2 : statusfile_.link(*this);
677 2 : statusfile_.open(statusfilename_);
678 : statusfile_.setHeavyFlush();
679 4 : statusfile_.fmtField("%10.7e ");
680 : }
681 : // write fields
682 6 : double MDtime = static_cast<double>(step)*getTimeStep();
683 6 : statusfile_.printField("MD_time", MDtime);
684 : // write scale and offset
685 6 : statusfile_.printField("scale", scale_);
686 6 : statusfile_.printField("offset", offset_);
687 : // write bfactors only if doing fitting or reading bfactors
688 6 : if(dbfact_>0 || bfactread_) {
689 : // cycle on residues
690 93 : for(unsigned ir=0; ir<Model_rlist_.size(); ++ir) {
691 : // key: pair of residue/chain IDs
692 : std::pair<int,std::string> key = Model_rlist_[ir];
693 : // bfactor from map
694 90 : double bf = Model_b_[key];
695 : // convert ires to std::string
696 : std::string num;
697 90 : Tools::convert(key.first,num);
698 : // print entry
699 270 : statusfile_.printField("bf-"+num+":"+key.second, bf);
700 : }
701 : }
702 6 : statusfile_.printField();
703 6 : }
704 :
705 60 : bool EMMIVOX::doAccept(double oldE, double newE, double kbt) {
706 : bool accept = false;
707 : // calculate delta energy
708 60 : double delta = ( newE - oldE ) / kbt;
709 : // if delta is negative always accept move
710 60 : if( delta < 0.0 ) {
711 : accept = true;
712 : } else {
713 : // otherwise extract random number
714 34 : double s = random_.RandU01();
715 34 : if( s < exp(-delta) ) {
716 : accept = true;
717 : }
718 : }
719 60 : return accept;
720 : }
721 :
722 2 : std::vector<double> EMMIVOX::get_Model_param(std::vector<AtomNumber> &atoms) {
723 : // check if MOLINFO line is present
724 2 : auto* moldat=plumed.getActionSet().selectLatest<GenericMolInfo*>(this);
725 2 : if(!moldat) {
726 0 : error("MOLINFO DATA not found\n");
727 : }
728 2 : log<<" MOLINFO DATA found with label " <<moldat->getLabel()<<", using proper atom names\n";
729 :
730 : // list of weights - one per atom
731 2 : std::vector<double> Model_w;
732 : // 5-Gaussians parameters
733 : // map of atom types to A and B coefficients of scattering factor
734 : // f(s) = A * exp(-B*s**2)
735 : // B is in Angstrom squared
736 : // Elastic atomic scattering factors of electrons for neutral atoms
737 : // and s up to 6.0 A^-1: as implemented in PLUMED
738 : // map between an atom type and an index
739 : std::map<std::string, unsigned> type_map;
740 : // atomistic types
741 2 : type_map["C"]=0;
742 2 : type_map["O"]=1;
743 2 : type_map["N"]=2;
744 2 : type_map["S"]=3;
745 2 : type_map["P"]=4;
746 2 : type_map["F"]=5;
747 2 : type_map["NA"]=6;
748 2 : type_map["MG"]=7;
749 2 : type_map["CL"]=8;
750 2 : type_map["CA"]=9;
751 2 : type_map["K"]=10;
752 2 : type_map["ZN"]=11;
753 : // Martini types
754 2 : type_map["ALA_BB"]=12;
755 2 : type_map["ALA_SC1"]=13;
756 2 : type_map["CYS_BB"]=14;
757 2 : type_map["CYS_SC1"]=15;
758 2 : type_map["ASP_BB"]=16;
759 2 : type_map["ASP_SC1"]=17;
760 2 : type_map["GLU_BB"]=18;
761 2 : type_map["GLU_SC1"]=19;
762 2 : type_map["PHE_BB"]=20;
763 2 : type_map["PHE_SC1"]=21;
764 2 : type_map["PHE_SC2"]=22;
765 2 : type_map["PHE_SC3"]=23;
766 2 : type_map["GLY_BB"]=24;
767 2 : type_map["HIS_BB"]=25;
768 2 : type_map["HIS_SC1"]=26;
769 2 : type_map["HIS_SC2"]=27;
770 2 : type_map["HIS_SC3"]=28;
771 2 : type_map["ILE_BB"]=29;
772 2 : type_map["ILE_SC1"]=30;
773 2 : type_map["LYS_BB"]=31;
774 2 : type_map["LYS_SC1"]=32;
775 2 : type_map["LYS_SC2"]=33;
776 2 : type_map["LEU_BB"]=34;
777 2 : type_map["LEU_SC1"]=35;
778 2 : type_map["MET_BB"]=36;
779 2 : type_map["MET_SC1"]=37;
780 2 : type_map["ASN_BB"]=38;
781 2 : type_map["ASN_SC1"]=39;
782 2 : type_map["PRO_BB"]=40;
783 2 : type_map["PRO_SC1"]=41;
784 2 : type_map["GLN_BB"]=42;
785 2 : type_map["GLN_SC1"]=43;
786 2 : type_map["ARG_BB"]=44;
787 2 : type_map["ARG_SC1"]=45;
788 2 : type_map["ARG_SC2"]=46;
789 2 : type_map["SER_BB"]=47;
790 2 : type_map["SER_SC1"]=48;
791 2 : type_map["THR_BB"]=49;
792 2 : type_map["THR_SC1"]=50;
793 2 : type_map["VAL_BB"]=51;
794 2 : type_map["VAL_SC1"]=52;
795 2 : type_map["TRP_BB"]=53;
796 2 : type_map["TRP_SC1"]=54;
797 2 : type_map["TRP_SC2"]=55;
798 2 : type_map["TRP_SC3"]=56;
799 2 : type_map["TRP_SC4"]=57;
800 2 : type_map["TRP_SC5"]=58;
801 2 : type_map["TYR_BB"]=59;
802 2 : type_map["TYR_SC1"]=60;
803 2 : type_map["TYR_SC2"]=61;
804 2 : type_map["TYR_SC3"]=62;
805 2 : type_map["TYR_SC4"]=63;
806 : // fill in sigma vector for atoms
807 4 : Model_s_.push_back(0.01*Vector5d(0.114,1.0825,5.4281,17.8811,51.1341)); // C
808 4 : Model_s_.push_back(0.01*Vector5d(0.0652,0.6184,2.9449,9.6298,28.2194)); // O
809 4 : Model_s_.push_back(0.01*Vector5d(0.0541,0.5165,2.8207,10.6297,34.3764)); // N
810 4 : Model_s_.push_back(0.01*Vector5d(0.0838,0.7788,4.3462,15.5846,44.63655)); // S
811 4 : Model_s_.push_back(0.01*Vector5d(0.0977,0.9084,4.9654,18.5471,54.3648)); // P
812 4 : Model_s_.push_back(0.01*Vector5d(0.0613,0.5753,2.6858,8.8214,25.6668)); // F
813 4 : Model_s_.push_back(0.01*Vector5d(0.1684,1.7150,8.8386,50.8265,147.2073)); // NA
814 4 : Model_s_.push_back(0.01*Vector5d(0.1356,1.3579,6.9255,32.3165,92.1138)); // MG
815 4 : Model_s_.push_back(0.01*Vector5d(0.0694,0.6443,3.5351,12.5058,35.8633)); // CL
816 4 : Model_s_.push_back(0.01*Vector5d(0.1742,1.8329,8.8407,47.4583,134.9613)); // CA
817 4 : Model_s_.push_back(0.01*Vector5d(0.1660,1.6906,8.7447,46.7825,165.6923)); // K
818 4 : Model_s_.push_back(0.01*Vector5d(0.0876,0.8650,3.8612,18.8726,64.7016)); // ZN
819 : // fill in sigma vector for Martini beads
820 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // ALA_BB
821 4 : Model_s_.push_back(0.01*Vector5d(0.500000,0.500000,0.500000,0.500000,0.500000)); // ALA_SC1
822 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // CYS_BB
823 4 : Model_s_.push_back(0.01*Vector5d(8.500000,8.500000,8.500000,8.500000,8.500000)); // CYS_SC1
824 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // ASP_BB
825 4 : Model_s_.push_back(0.01*Vector5d(17.000000,17.000000,17.000000,17.000000,17.000000)); // ASP_SC1
826 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // GLU_BB
827 4 : Model_s_.push_back(0.01*Vector5d(24.000000,24.000000,24.000000,24.000000,24.000000)); // GLU_SC1
828 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // PHE_BB
829 4 : Model_s_.push_back(0.01*Vector5d(17.500000,17.500000,17.500000,17.500000,17.500000)); // PHE_SC1
830 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // PHE_SC2
831 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // PHE_SC3
832 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // GLY_BB
833 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // HIS_BB
834 4 : Model_s_.push_back(0.01*Vector5d(11.500000,11.500000,11.500000,11.500000,11.500000)); // HIS_SC1
835 4 : Model_s_.push_back(0.01*Vector5d(9.000000,9.000000,9.000000,9.000000,9.000000)); // HIS_SC2
836 4 : Model_s_.push_back(0.01*Vector5d(8.500000,8.500000,8.500000,8.500000,8.500000)); // HIS_SC3
837 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // ILE_BB
838 4 : Model_s_.push_back(0.01*Vector5d(25.500000,25.500000,25.500000,25.500000,25.500000)); // ILE_SC1
839 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // LYS_BB
840 4 : Model_s_.push_back(0.01*Vector5d(18.000000,18.000000,18.000000,18.000000,18.000000)); // LYS_SC1
841 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // LYS_SC2
842 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // LEU_BB
843 4 : Model_s_.push_back(0.01*Vector5d(21.500000,21.500000,21.500000,21.500000,21.500000)); // LEU_SC1
844 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // MET_BB
845 4 : Model_s_.push_back(0.01*Vector5d(22.500000,22.500000,22.500000,22.500000,22.500000)); // MET_SC1
846 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // ASN_BB
847 4 : Model_s_.push_back(0.01*Vector5d(18.500000,18.500000,18.500000,18.500000,18.500000)); // ASN_SC1
848 4 : Model_s_.push_back(0.01*Vector5d(23.500000,23.500000,23.500000,23.500000,23.500000)); // PRO_BB
849 4 : Model_s_.push_back(0.01*Vector5d(17.500000,17.500000,17.500000,17.500000,17.500000)); // PRO_SC1
850 4 : Model_s_.push_back(0.01*Vector5d(22.000000,22.000000,22.000000,22.000000,22.000000)); // GLN_BB
851 4 : Model_s_.push_back(0.01*Vector5d(24.500000,24.500000,24.500000,24.500000,24.500000)); // GLN_SC1
852 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // ARG_BB
853 4 : Model_s_.push_back(0.01*Vector5d(18.000000,18.000000,18.000000,18.000000,18.000000)); // ARG_SC1
854 4 : Model_s_.push_back(0.01*Vector5d(18.000000,18.000000,18.000000,18.000000,18.000000)); // ARG_SC2
855 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // SER_BB
856 4 : Model_s_.push_back(0.01*Vector5d(9.000000,9.000000,9.000000,9.000000,9.000000)); // SER_SC1
857 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // THR_BB
858 4 : Model_s_.push_back(0.01*Vector5d(17.000000,17.000000,17.000000,17.000000,17.000000)); // THR_SC1
859 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // VAL_BB
860 4 : Model_s_.push_back(0.01*Vector5d(18.000000,18.000000,18.000000,18.000000,18.000000)); // VAL_SC1
861 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // TRP_BB
862 4 : Model_s_.push_back(0.01*Vector5d(11.500000,11.500000,11.500000,11.500000,11.500000)); // TRP_SC1
863 4 : Model_s_.push_back(0.01*Vector5d(9.000000,9.000000,9.000000,9.000000,9.000000)); // TRP_SC2
864 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // TRP_SC3
865 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // TRP_SC4
866 4 : Model_s_.push_back(0.01*Vector5d(9.500000,9.500000,9.500000,9.500000,9.500000)); // TRP_SC5
867 4 : Model_s_.push_back(0.01*Vector5d(23.000000,23.000000,23.000000,23.000000,23.000000)); // TYR_BB
868 4 : Model_s_.push_back(0.01*Vector5d(12.000000,12.000000,12.000000,12.000000,12.000000)); // TYR_SC1
869 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // TYR_SC2
870 4 : Model_s_.push_back(0.01*Vector5d(11.000000,11.000000,11.000000,11.000000,11.000000)); // TYR_SC3
871 4 : Model_s_.push_back(0.01*Vector5d(8.500000,8.500000,8.500000,8.500000,8.500000)); // TYR_SC4
872 : // fill in weight vector for atoms
873 2 : Model_w_.push_back(Vector5d(0.0489,0.2091,0.7537,1.1420,0.3555)); // C
874 2 : Model_w_.push_back(Vector5d(0.0365,0.1729,0.5805,0.8814,0.3121)); // O
875 2 : Model_w_.push_back(Vector5d(0.0267,0.1328,0.5301,1.1020,0.4215)); // N
876 2 : Model_w_.push_back(Vector5d(0.0915,0.4312,1.0847,2.4671,1.0852)); // S
877 2 : Model_w_.push_back(Vector5d(0.1005,0.4615,1.0663,2.5854,1.2725)); // P
878 2 : Model_w_.push_back(Vector5d(0.0382,0.1822,0.5972,0.7707,0.2130)); // F
879 2 : Model_w_.push_back(Vector5d(0.1260,0.6442,0.8893,1.8197,1.2988)); // NA
880 2 : Model_w_.push_back(Vector5d(0.1130,0.5575,0.9046,2.1580,1.4735)); // MG
881 2 : Model_w_.push_back(Vector5d(0.0799,0.3891,1.0037,2.3332,1.0507)); // CL
882 2 : Model_w_.push_back(Vector5d(0.2355,0.9916,2.3959,3.7252,2.5647)); // CA
883 2 : Model_w_.push_back(Vector5d(0.2149,0.8703,2.4999,2.3591,3.0318)); // K
884 2 : Model_w_.push_back(Vector5d(0.1780,0.8096,1.6744,1.9499,1.4495)); // ZN
885 : // fill in weight vector for Martini beads
886 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // ALA_BB
887 2 : Model_w_.push_back(Vector5d(0.100000,0.100000,0.100000,0.100000,0.100000)); // ALA_SC1
888 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // CYS_BB
889 2 : Model_w_.push_back(Vector5d(1.100000,1.100000,1.100000,1.100000,1.100000)); // CYS_SC1
890 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // ASP_BB
891 2 : Model_w_.push_back(Vector5d(1.700000,1.700000,1.700000,1.700000,1.700000)); // ASP_SC1
892 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // GLU_BB
893 2 : Model_w_.push_back(Vector5d(2.300000,2.300000,2.300000,2.300000,2.300000)); // GLU_SC1
894 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // PHE_BB
895 2 : Model_w_.push_back(Vector5d(1.400000,1.400000,1.400000,1.400000,1.400000)); // PHE_SC1
896 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // PHE_SC2
897 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // PHE_SC3
898 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // GLY_BB
899 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // HIS_BB
900 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // HIS_SC1
901 2 : Model_w_.push_back(Vector5d(0.800000,0.800000,0.800000,0.800000,0.800000)); // HIS_SC2
902 2 : Model_w_.push_back(Vector5d(0.800000,0.800000,0.800000,0.800000,0.800000)); // HIS_SC3
903 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // ILE_BB
904 2 : Model_w_.push_back(Vector5d(2.000000,2.000000,2.000000,2.000000,2.000000)); // ILE_SC1
905 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // LYS_BB
906 2 : Model_w_.push_back(Vector5d(1.400000,1.400000,1.400000,1.400000,1.400000)); // LYS_SC1
907 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // LYS_SC2
908 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // LEU_BB
909 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // LEU_SC1
910 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // MET_BB
911 2 : Model_w_.push_back(Vector5d(2.300000,2.300000,2.300000,2.300000,2.300000)); // MET_SC1
912 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // ASN_BB
913 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // ASN_SC1
914 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // PRO_BB
915 2 : Model_w_.push_back(Vector5d(1.400000,1.400000,1.400000,1.400000,1.400000)); // PRO_SC1
916 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // GLN_BB
917 2 : Model_w_.push_back(Vector5d(2.300000,2.300000,2.300000,2.300000,2.300000)); // GLN_SC1
918 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // ARG_BB
919 2 : Model_w_.push_back(Vector5d(1.400000,1.400000,1.400000,1.400000,1.400000)); // ARG_SC1
920 2 : Model_w_.push_back(Vector5d(1.800000,1.800000,1.800000,1.800000,1.800000)); // ARG_SC2
921 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // SER_BB
922 2 : Model_w_.push_back(Vector5d(0.800000,0.800000,0.800000,0.800000,0.800000)); // SER_SC1
923 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // THR_BB
924 2 : Model_w_.push_back(Vector5d(1.400000,1.400000,1.400000,1.400000,1.400000)); // THR_SC1
925 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // VAL_BB
926 2 : Model_w_.push_back(Vector5d(1.400000,1.400000,1.400000,1.400000,1.400000)); // VAL_SC1
927 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // TRP_BB
928 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // TRP_SC1
929 2 : Model_w_.push_back(Vector5d(0.800000,0.800000,0.800000,0.800000,0.800000)); // TRP_SC2
930 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // TRP_SC3
931 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // TRP_SC4
932 2 : Model_w_.push_back(Vector5d(0.800000,0.800000,0.800000,0.800000,0.800000)); // TRP_SC5
933 2 : Model_w_.push_back(Vector5d(1.900000,1.900000,1.900000,1.900000,1.900000)); // TYR_BB
934 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // TYR_SC1
935 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // TYR_SC2
936 2 : Model_w_.push_back(Vector5d(0.900000,0.900000,0.900000,0.900000,0.900000)); // TYR_SC3
937 2 : Model_w_.push_back(Vector5d(0.800000,0.800000,0.800000,0.800000,0.800000)); // TYR_SC4
938 : // cycle on atoms
939 470 : for(unsigned i=0; i<atoms.size(); ++i) {
940 : // get atom name
941 468 : std::string name = moldat->getAtomName(atoms[i]);
942 : // get residue name
943 468 : std::string resname = moldat->getResidueName(atoms[i]);
944 : // type of atoms/bead
945 : std::string type_s;
946 : // Martini model
947 468 : if(martini_) {
948 0 : type_s = resname+"_"+name;
949 : // Atomistic model
950 : } else {
951 : char type;
952 : // get atom type
953 468 : char first = name.at(0);
954 : // GOLDEN RULE: type is first letter, if not a number
955 468 : if (!isdigit(first)) {
956 : type = first;
957 : // otherwise is the second
958 : } else {
959 0 : type = name.at(1);
960 : }
961 : // convert to std::string
962 468 : type_s = std::string(1,type);
963 : // special cases
964 468 : if(name=="SOD" || name=="NA" || name =="Na") {
965 : type_s = "NA";
966 : }
967 468 : if(name=="MG" || name=="Mg") {
968 : type_s = "MG";
969 : }
970 468 : if(name=="CLA" || name=="CL" || name =="Cl") {
971 : type_s = "CL";
972 : }
973 468 : if((resname=="CAL" || resname=="CA") && (name=="CAL" || name=="CA" || name =="C0")) {
974 : type_s = "CA";
975 : }
976 468 : if(name=="POT" || name=="K") {
977 : type_s = "K";
978 : }
979 468 : if(name=="ZN" || name=="Zn") {
980 : type_s = "ZN";
981 : }
982 : }
983 : // check if key in map
984 468 : if(type_map.find(type_s) != type_map.end()) {
985 : // save atom type
986 468 : Model_type_.push_back(type_map[type_s]);
987 : // this will be normalized in the final density
988 468 : Vector5d w = Model_w_[type_map[type_s]];
989 468 : Model_w.push_back(w[0]+w[1]+w[2]+w[3]+w[4]);
990 : // get residue id
991 468 : const int ires = moldat->getResidueNumber(atoms[i]);
992 : // and chain
993 468 : std::string c ("*");
994 468 : if(!bfactnoc_) {
995 936 : c = moldat->getChainID(atoms[i]);
996 : }
997 : // define pair residue/chain IDs
998 : std::pair<int,std::string> key = std::make_pair(ires,c);
999 : // add to map between residue/chain and list of atoms
1000 468 : Model_resmap_[key].push_back(i);
1001 : // and global list of residue/chain per atom
1002 468 : Model_res_.push_back(key);
1003 : // initialize Bfactor map
1004 468 : Model_b_[key] = 0.0;
1005 : } else {
1006 0 : error("Wrong atom type "+type_s+" from atom name "+name+"\n");
1007 : }
1008 : }
1009 : // create ordered vector of residue-chain IDs
1010 470 : for(unsigned i=0; i<Model_res_.size(); ++i) {
1011 : std::pair<int,std::string> key = Model_res_[i];
1012 : // search in Model_rlist_
1013 468 : if(find(Model_rlist_.begin(), Model_rlist_.end(), key) == Model_rlist_.end()) {
1014 60 : Model_rlist_.push_back(key);
1015 : }
1016 : }
1017 : // return weights
1018 2 : return Model_w;
1019 0 : }
1020 :
1021 : // read experimental data file in PLUMED format:
1022 2 : void EMMIVOX::get_exp_data(const std::string &datafile) {
1023 2 : Vector pos;
1024 : double dens, err;
1025 : int idcomp;
1026 :
1027 : // open file
1028 2 : IFile *ifile = new IFile();
1029 2 : if(ifile->FileExist(datafile)) {
1030 2 : ifile->open(datafile);
1031 29336 : while(ifile->scanField("Id",idcomp)) {
1032 14666 : ifile->scanField("Pos_0",pos[0]);
1033 14666 : ifile->scanField("Pos_1",pos[1]);
1034 14666 : ifile->scanField("Pos_2",pos[2]);
1035 14666 : ifile->scanField("Density",dens);
1036 14666 : ifile->scanField("Error",err);
1037 : // voxel center
1038 14666 : Map_m_.push_back(pos);
1039 : // experimental density
1040 14666 : ovdd_.push_back(dens);
1041 : // error
1042 14666 : exp_err_.push_back(err);
1043 : // new line
1044 14666 : ifile->scanField();
1045 : }
1046 2 : ifile->close();
1047 : } else {
1048 0 : error("Cannot find DATA_FILE "+datafile+"\n");
1049 : }
1050 2 : delete ifile;
1051 2 : }
1052 :
1053 2 : void EMMIVOX::initialize_Bfactor(double reso) {
1054 2 : double bfactini = 0.0;
1055 : // if doing Bfactor Monte Carlo
1056 2 : if(dbfact_>0) {
1057 : // initialize B factor based on empirical relation between resolution and average bfactor
1058 : // calculated on ~8000 cryo-EM data with resolution < 5 Ang
1059 : // Bfact = A*reso**2+B; with A=6.95408 B=-2.45697/100.0 nm^2
1060 1 : bfactini = 6.95408*reso*reso - 0.01*2.45697;
1061 : // check for min and max
1062 3 : bfactini = std::min(bfactmax_, std::max(bfactmin_, bfactini));
1063 : }
1064 : // set initial Bfactor
1065 62 : for(std::map< std::pair<int,std::string>, double>::iterator it=Model_b_.begin(); it!=Model_b_.end(); ++it) {
1066 60 : it->second = bfactini;
1067 : }
1068 2 : log.printf(" experimental map resolution : %3.2f\n", reso);
1069 : // if doing Bfactor Monte Carlo
1070 2 : if(dbfact_>0) {
1071 1 : log.printf(" minimum Bfactor value : %3.2f\n", bfactmin_);
1072 1 : log.printf(" maximum Bfactor value : %3.2f\n", bfactmax_);
1073 1 : log.printf(" initial Bfactor value : %3.2f\n", bfactini);
1074 : }
1075 2 : }
1076 :
1077 : // prepare auxiliary vectors
1078 4 : void EMMIVOX::get_auxiliary_vectors() {
1079 : // number of atoms
1080 4 : unsigned natoms = Model_res_.size();
1081 : // clear lists
1082 4 : pref_.clear();
1083 4 : invs2_.clear();
1084 4 : cut_.clear();
1085 : // resize
1086 4 : pref_.resize(natoms);
1087 4 : invs2_.resize(natoms);
1088 4 : cut_.resize(natoms);
1089 : // cycle on all atoms
1090 4 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1091 : for(unsigned im=0; im<natoms; ++im) {
1092 : // get atom type
1093 : unsigned atype = Model_type_[im];
1094 : // get residue/chain IDs
1095 : std::pair<int,std::string> key = Model_res_[im];
1096 : // get bfactor
1097 : double bfact = Model_b_[key];
1098 : // sigma for 5 gaussians
1099 : Vector5d m_s = Model_s_[atype];
1100 : // calculate constant quantities
1101 : Vector5d pref, invs2;
1102 : // calculate cutoff
1103 : double n = 0.0;
1104 : double d = 0.0;
1105 : for(unsigned j=0; j<5; ++j) {
1106 : // total value of b
1107 : double m_b = m_s[j] + bfact/4.0;
1108 : // calculate invs2
1109 : invs2[j] = 1.0/(inv_pi2_*m_b);
1110 : // prefactor
1111 : pref[j] = cfact_[atype][j] * pow(invs2[j],1.5);
1112 : // cutoff
1113 : n += pref[j] / invs2[j];
1114 : d += pref[j];
1115 : }
1116 : // put into global lists
1117 : pref_[im] = pref;
1118 : invs2_[im] = invs2;
1119 : cut_[im] = std::min(nl_dist_cutoff_, sqrt(n/d)*nl_gauss_cutoff_);
1120 : }
1121 : // push to GPU
1122 4 : push_auxiliary_gpu();
1123 4 : }
1124 :
1125 4 : void EMMIVOX::push_auxiliary_gpu() {
1126 : // 1) create vector of pref_ and invs2_
1127 4 : int natoms = Model_type_.size();
1128 4 : std::vector<double> pref(5*natoms), invs2(5*natoms);
1129 4 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1130 : for(int i=0; i<natoms; ++i) {
1131 : for(int j=0; j<5; ++j) {
1132 : pref[i+j*natoms] = pref_[i][j];
1133 : invs2[i+j*natoms] = invs2_[i][j];
1134 : }
1135 : }
1136 : // 2) initialize gpu tensors
1137 16 : pref_gpu_ = torch::from_blob(pref.data(), {5,natoms}, torch::kFloat64).clone().to(torch::kFloat32).to(device_t_);
1138 16 : invs2_gpu_ = torch::from_blob(invs2.data(), {5,natoms}, torch::kFloat64).clone().to(torch::kFloat32).to(device_t_);
1139 4 : }
1140 :
1141 2 : void EMMIVOX::get_close_residues() {
1142 : // two residues are neighbors if two of their atoms are closer than this
1143 : const double dcut = 0.5;
1144 2 : const unsigned nres = Model_rlist_.size();
1145 : // clear neighbor list
1146 2 : nl_res_.clear();
1147 2 : nl_res_.resize(nres);
1148 :
1149 : // atoms, center and radius of each residue: two residues whose spheres are
1150 : // farther than dcut cannot have atoms closer than dcut
1151 2 : std::vector< const std::vector<unsigned>* > ratoms(nres);
1152 2 : std::vector<Vector> rcenter(nres);
1153 2 : std::vector<double> rradius(nres,0.0);
1154 62 : for(unsigned i=0; i<nres; ++i) {
1155 60 : ratoms[i] = &Model_resmap_[Model_rlist_[i]];
1156 : Vector c;
1157 528 : for(const auto im : *ratoms[i]) {
1158 468 : c += getPosition(im);
1159 : }
1160 60 : if(ratoms[i]->size()>0) {
1161 60 : c /= static_cast<double>(ratoms[i]->size());
1162 : }
1163 60 : rcenter[i] = c;
1164 528 : for(const auto im : *ratoms[i]) {
1165 634 : rradius[i] = std::max(rradius[i], delta(c,getPosition(im)).modulo());
1166 : }
1167 : }
1168 :
1169 : // loop in parallel
1170 2 : const unsigned nt = OpenMP::getNumThreads();
1171 2 : std::vector< std::vector< std::vector<unsigned> > > nl_res_t(nt);
1172 2 : #pragma omp parallel num_threads(nt)
1173 : {
1174 : // private variable
1175 : std::vector< std::vector<unsigned> > & nl_res_l = nl_res_t[OpenMP::getThreadNum()];
1176 : nl_res_l.resize(nres);
1177 : // cycle on residues/chains #1
1178 : #pragma omp for schedule(dynamic)
1179 : for(unsigned i=0; i<nres; ++i) {
1180 : // cycle over residues/chains #2
1181 : for(unsigned j=i+1; j<nres; ++j) {
1182 : if(delta(rcenter[i],rcenter[j]).modulo() > rradius[i]+rradius[j]+dcut) {
1183 : continue;
1184 : }
1185 : // set flag neighbor
1186 : bool neigh = false;
1187 : // cycle over all the atoms belonging to residue i
1188 : for(const auto im1 : *ratoms[i]) {
1189 : // get atom position #1
1190 : const Vector pos1 = getPosition(im1);
1191 : // cycle over all the atoms belonging to residue j
1192 : for(const auto im2 : *ratoms[j]) {
1193 : // if closer than 0.5 nm, then residues i and j are neighbors
1194 : if(delta(pos1,getPosition(im2)).modulo()<dcut) {
1195 : neigh = true;
1196 : break;
1197 : }
1198 : }
1199 : // check if neighbor already found
1200 : if(neigh) {
1201 : break;
1202 : }
1203 : }
1204 : // if neighbors, add to local list
1205 : if(neigh) {
1206 : nl_res_l[i].push_back(j);
1207 : nl_res_l[j].push_back(i);
1208 : }
1209 : }
1210 : }
1211 : }
1212 : // add to global list, sorted (reproducible results); the lists of the threads
1213 : // that did not run (e.g. without OpenMP) are empty and are skipped
1214 6 : for(unsigned t=0; t<nt; ++t) {
1215 4 : if(nl_res_t[t].empty()) {
1216 0 : continue;
1217 : }
1218 124 : for(unsigned i=0; i<nres; ++i) {
1219 120 : nl_res_[i].insert(nl_res_[i].end(), nl_res_t[t][i].begin(), nl_res_t[t][i].end());
1220 : }
1221 : }
1222 62 : for(unsigned i=0; i<nres; ++i) {
1223 60 : std::sort(nl_res_[i].begin(), nl_res_[i].end());
1224 : }
1225 6 : }
1226 :
1227 2 : void EMMIVOX::doMonteCarloBfact() {
1228 : // update residue neighbor list
1229 2 : get_close_residues();
1230 :
1231 : // cycle over residues/chains
1232 62 : for(unsigned ir=0; ir<Model_rlist_.size(); ++ir) {
1233 :
1234 : // key: pair of residue/chain IDs
1235 : std::pair<int,std::string> key = Model_rlist_[ir];
1236 : // old bfactor
1237 60 : double bfactold = Model_b_[key];
1238 :
1239 : // propose move in bfactor
1240 60 : double bfactnew = bfactold + dbfact_ * ( 2.0 * random_.RandU01() - 1.0 );
1241 : // check boundaries
1242 60 : if(bfactnew > bfactmax_) {
1243 0 : bfactnew = 2.0*bfactmax_ - bfactnew;
1244 : }
1245 60 : if(bfactnew < bfactmin_) {
1246 0 : bfactnew = 2.0*bfactmin_ - bfactnew;
1247 : }
1248 :
1249 : // useful quantities
1250 : std::map<unsigned, double> deltaov;
1251 60 : const unsigned nt = OpenMP::getNumThreads();
1252 60 : std::vector< std::map<unsigned, double> > deltaov_t(nt);
1253 :
1254 60 : #pragma omp parallel num_threads(nt)
1255 : {
1256 : // private variables
1257 : std::map<unsigned, double> & deltaov_l = deltaov_t[OpenMP::getThreadNum()];
1258 : #pragma omp for schedule(static)
1259 : // cycle over all the atoms belonging to key (residue/chain)
1260 : for(unsigned ia=0; ia<Model_resmap_[key].size(); ++ia) {
1261 :
1262 : // get atom id
1263 : unsigned im = Model_resmap_[key][ia];
1264 : // get atom type
1265 : unsigned atype = Model_type_[im];
1266 : // sigma for 5 Gaussians
1267 : Vector5d m_s = Model_s_[atype];
1268 : // prefactors
1269 : Vector5d cfact = cfact_[atype];
1270 : // and position
1271 : Vector pos = getPosition(im);
1272 :
1273 : // cycle on all the neighboring voxels affected by a change in Bfactor
1274 : for(unsigned i=0; i<Model_nb_[im].size(); ++i) {
1275 : // voxel id
1276 : unsigned id = Model_nb_[im][i];
1277 : // get contribution to density in id before change
1278 : double dold = get_overlap(Map_m_[id], pos, cfact, m_s, bfactold);
1279 : // get contribution after change
1280 : double dnew = get_overlap(Map_m_[id], pos, cfact, m_s, bfactnew);
1281 : // update delta density
1282 : deltaov_l[id] += dnew-dold;
1283 : }
1284 : }
1285 : }
1286 : // add to global list, in thread order (reproducible results)
1287 180 : for(unsigned t=0; t<nt; ++t) {
1288 18445 : for(const auto & itov : deltaov_t[t]) {
1289 18325 : deltaov[itov.first] += itov.second;
1290 : }
1291 : }
1292 :
1293 : // now calculate new and old score
1294 : double old_ene = 0.0;
1295 : double new_ene = 0.0;
1296 :
1297 : // cycle on all affected voxels
1298 13661 : for(std::map<unsigned,double>::iterator itov=deltaov.begin(); itov!=deltaov.end(); ++itov) {
1299 : // id of the component
1300 13601 : unsigned id = itov->first;
1301 : // new value
1302 13601 : double ovmdnew = ovmd_[id]+itov->second;
1303 : // deviations
1304 13601 : double devold = scale_ * ovmd_[id] + offset_ - ovdd_[id];
1305 13601 : double devnew = scale_ * ovmdnew + offset_ - ovdd_[id];
1306 : // inverse of sigma_min
1307 13601 : double ismin = ismin_[id];
1308 : // scores
1309 13601 : if(devold==0.0) {
1310 0 : old_ene += -kbt_ * std::log( 0.5 * sqrt2_pi_ * ismin );
1311 : } else {
1312 13601 : old_ene += -kbt_ * std::log( 0.5 / devold * erf ( devold * inv_sqrt2_ * ismin ));
1313 : }
1314 13601 : if(devnew==0.0) {
1315 0 : new_ene += -kbt_ * std::log( 0.5 * sqrt2_pi_ * ismin );
1316 : } else {
1317 13601 : new_ene += -kbt_ * std::log( 0.5 / devnew * erf ( devnew * inv_sqrt2_ * ismin ));
1318 : }
1319 : }
1320 :
1321 : // list of neighboring residues
1322 60 : std::vector<unsigned> close = nl_res_[ir];
1323 : // add restraint to keep Bfactors of close residues close
1324 484 : for(unsigned i=0; i<close.size(); ++i) {
1325 : // residue/chain IDs of neighbor
1326 424 : std::pair<int,std::string> keyn = Model_rlist_[close[i]];
1327 : // deviations
1328 424 : double devold = bfactold - Model_b_[keyn];
1329 424 : double devnew = bfactnew - Model_b_[keyn];
1330 : // inverse of sigma_min
1331 424 : double ismin = 1.0 / bfactsig_;
1332 : // scores
1333 424 : if(devold==0.0) {
1334 204 : old_ene += -kbt_ * std::log( 0.5 * sqrt2_pi_ * ismin );
1335 : } else {
1336 220 : old_ene += -kbt_ * std::log( 0.5 / devold * erf ( devold * inv_sqrt2_ * ismin ));
1337 : }
1338 424 : if(devnew==0.0) {
1339 0 : new_ene += -kbt_ * std::log( 0.5 * sqrt2_pi_ * ismin );
1340 : } else {
1341 424 : new_ene += -kbt_ * std::log( 0.5 / devnew * erf ( devnew * inv_sqrt2_ * ismin ));
1342 : }
1343 : }
1344 :
1345 : // increment number of trials
1346 60 : MCBtrials_ += 1.0;
1347 :
1348 : // accept or reject
1349 : bool accept = false;
1350 60 : if(bfactemin_) {
1351 0 : if(new_ene < old_ene) {
1352 : accept = true;
1353 : }
1354 : } else {
1355 60 : accept = doAccept(old_ene, new_ene, kbt_);
1356 : }
1357 :
1358 : // in case of acceptance
1359 60 : if(accept) {
1360 : // update acceptance rate
1361 28 : MCBaccept_ += 1.0;
1362 : // update bfactor
1363 28 : Model_b_[key] = bfactnew;
1364 : // change all the ovmd_ affected
1365 6432 : for(std::map<unsigned,double>::iterator itov=deltaov.begin(); itov!=deltaov.end(); ++itov) {
1366 6404 : ovmd_[itov->first] += itov->second;
1367 : }
1368 : }
1369 :
1370 60 : } // end cycle on bfactors
1371 :
1372 : // update auxiliary lists (to update pref_gpu_, invs2_gpu_, and cut_ on CPU/GPU)
1373 2 : get_auxiliary_vectors();
1374 : // update neighbor list (new cut_ + update pref_nl_gpu_ and invs2_nl_gpu_ on GPU)
1375 2 : update_neighbor_list();
1376 : // recalculate fmod (to update derivatives)
1377 2 : calculate_fmod();
1378 2 : }
1379 :
1380 : // get overlap
1381 59636 : double EMMIVOX::get_overlap(const Vector &d_m, const Vector &m_m,
1382 : const Vector5d &cfact, const Vector5d &m_s, double bfact) {
1383 : // calculate vector difference
1384 : Vector md = delta(m_m, d_m);
1385 : // norm squared
1386 59636 : double md2 = md[0]*md[0]+md[1]*md[1]+md[2]*md[2];
1387 : // cycle on 5 Gaussians
1388 : double ov_tot = 0.0;
1389 357816 : for(unsigned j=0; j<5; ++j) {
1390 : // total value of b
1391 298180 : double m_b = m_s[j]+bfact/4.0;
1392 : // calculate invs2
1393 298180 : double invs2 = 1.0/(inv_pi2_*m_b);
1394 : // final calculation
1395 298180 : ov_tot += cfact[j] * pow(invs2, 1.5) * std::exp(-0.5 * md2 * invs2);
1396 : }
1397 59636 : return ov_tot;
1398 : }
1399 :
1400 4 : void EMMIVOX::update_neighbor_sphere() {
1401 : // number of atoms
1402 4 : const unsigned natoms = Model_type_.size();
1403 : // clear neighbor sphere
1404 4 : ns_.clear();
1405 : // store reference positions
1406 4 : refpos_ = getPositions();
1407 4 : if(natoms==0) {
1408 0 : return;
1409 : }
1410 :
1411 : // the atoms are binned in cells larger than the largest sphere radius (2*cut_),
1412 : // so that only the 27 cells around a voxel have to be searched
1413 4 : const double rc = 2.0 * (*std::max_element(cut_.begin(), cut_.end()));
1414 4 : Vector lo = refpos_[0], hi = refpos_[0];
1415 936 : for(unsigned im=1; im<natoms; ++im) {
1416 3728 : for(unsigned k=0; k<3; ++k) {
1417 3026 : lo[k] = std::min(lo[k], refpos_[im][k]);
1418 2796 : hi[k] = std::max(hi[k], refpos_[im][k]);
1419 : }
1420 : }
1421 : int ncell[3];
1422 : double csize[3];
1423 16 : for(unsigned k=0; k<3; ++k) {
1424 : // at most 200 cells per direction
1425 12 : const double len = hi[k]-lo[k];
1426 24 : ncell[k] = std::max(1, std::min(200, static_cast<int>(std::floor(len/rc))));
1427 12 : csize[k] = std::max(len/static_cast<double>(ncell[k]), rc);
1428 : }
1429 90804 : auto cellIndex=[&](const Vector & x, const unsigned k) {
1430 90804 : return static_cast<int>(std::floor((x[k]-lo[k])/csize[k]));
1431 4 : };
1432 4 : const unsigned ncells = ncell[0]*ncell[1]*ncell[2];
1433 : // atoms of each cell, in ascending order
1434 4 : std::vector<unsigned> cell_start(ncells+1,0), cell_atoms(natoms), atom_cell(natoms);
1435 940 : for(unsigned im=0; im<natoms; ++im) {
1436 : int c[3];
1437 3744 : for(unsigned k=0; k<3; ++k) {
1438 8113 : c[k] = std::min(ncell[k]-1, std::max(0, cellIndex(refpos_[im],k)));
1439 : }
1440 936 : atom_cell[im] = (c[0]*ncell[1]+c[1])*ncell[2]+c[2];
1441 936 : cell_start[atom_cell[im]+1]++;
1442 : }
1443 54244 : for(unsigned ic=0; ic<ncells; ++ic) {
1444 54240 : cell_start[ic+1] += cell_start[ic];
1445 : }
1446 : {
1447 4 : std::vector<unsigned> fill(cell_start.begin(), cell_start.end()-1);
1448 940 : for(unsigned im=0; im<natoms; ++im) {
1449 936 : cell_atoms[fill[atom_cell[im]]++] = im;
1450 : }
1451 4 : }
1452 :
1453 : // cycle on voxels - in parallel; the pairs are sorted by voxel and then by atom
1454 4 : const unsigned nt = OpenMP::getNumThreads();
1455 4 : std::vector< std::vector< std::pair<unsigned,unsigned> > > ns_t(nt);
1456 4 : #pragma omp parallel num_threads(nt)
1457 : {
1458 : // private variables
1459 : std::vector< std::pair<unsigned,unsigned> > & ns_l = ns_t[OpenMP::getThreadNum()];
1460 : std::vector<unsigned> close;
1461 : #pragma omp for schedule(static)
1462 : for(unsigned id=0; id<ovdd_.size(); ++id) {
1463 : // grid point
1464 : const Vector d_m = Map_m_[id];
1465 : int c[3];
1466 : bool outside = false;
1467 : for(unsigned k=0; k<3; ++k) {
1468 : c[k] = cellIndex(d_m,k);
1469 : // more than one cell away from the atoms
1470 : if(c[k]<-1 || c[k]>ncell[k]) {
1471 : outside = true;
1472 : }
1473 : }
1474 : if(outside) {
1475 : continue;
1476 : }
1477 : close.clear();
1478 : for(int i0=std::max(0,c[0]-1); i0<=std::min(ncell[0]-1,c[0]+1); ++i0) {
1479 : for(int i1=std::max(0,c[1]-1); i1<=std::min(ncell[1]-1,c[1]+1); ++i1) {
1480 : for(int i2=std::max(0,c[2]-1); i2<=std::min(ncell[2]-1,c[2]+1); ++i2) {
1481 : const unsigned ic = (i0*ncell[1]+i1)*ncell[2]+i2;
1482 : for(unsigned j=cell_start[ic]; j<cell_start[ic+1]; ++j) {
1483 : const unsigned im = cell_atoms[j];
1484 : // calculate distance
1485 : const double dist = delta(getPosition(im), d_m).modulo();
1486 : // add to local list
1487 : if(dist<=2.0*cut_[im]) {
1488 : close.push_back(im);
1489 : }
1490 : }
1491 : }
1492 : }
1493 : }
1494 : std::sort(close.begin(), close.end());
1495 : for(const auto im : close) {
1496 : ns_l.push_back(std::make_pair(id,im));
1497 : }
1498 : }
1499 : }
1500 : // add to global list, in thread order
1501 12 : for(unsigned t=0; t<nt; ++t) {
1502 8 : ns_.insert(ns_.end(), ns_t[t].begin(), ns_t[t].end());
1503 : }
1504 4 : }
1505 :
1506 7 : bool EMMIVOX::do_neighbor_sphere() {
1507 7 : std::vector<double> dist(getPositions().size());
1508 : bool update = false;
1509 :
1510 : // calculate displacement
1511 7 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1512 : for(unsigned im=0; im<dist.size(); ++im) {
1513 : dist[im] = delta(getPosition(im),refpos_[im]).modulo()/cut_[im];
1514 : }
1515 :
1516 : // check if update or not
1517 7 : double maxdist = *max_element(dist.begin(), dist.end());
1518 7 : if(maxdist>=1.0) {
1519 : update=true;
1520 : }
1521 :
1522 : // return if update or not
1523 7 : return update;
1524 : }
1525 :
1526 11 : void EMMIVOX::update_neighbor_list() {
1527 : // number of atoms
1528 : unsigned natoms = Model_type_.size();
1529 : // clear neighbor list
1530 11 : nl_.clear();
1531 :
1532 : // cycle on neighbour sphere - in parallel, the order of the pairs is preserved
1533 11 : const unsigned nt = OpenMP::getNumThreads();
1534 11 : std::vector< std::vector< std::pair<unsigned,unsigned> > > nl_t(nt);
1535 11 : #pragma omp parallel num_threads(nt)
1536 : {
1537 : // private variables
1538 : std::vector< std::pair<unsigned,unsigned> > & nl_l = nl_t[OpenMP::getThreadNum()];
1539 : #pragma omp for schedule(static)
1540 : for(unsigned long long i=0; i<ns_.size(); ++i) {
1541 : // calculate distance
1542 : double dist = delta(Map_m_[ns_[i].first], getPosition(ns_[i].second)).modulo();
1543 : // add to local neighbour list
1544 : if(dist<=cut_[ns_[i].second]) {
1545 : nl_l.push_back(ns_[i]);
1546 : }
1547 : }
1548 : }
1549 : // add to global list, in thread order
1550 33 : for(unsigned t=0; t<nt; ++t) {
1551 22 : nl_.insert(nl_.end(), nl_t[t].begin(), nl_t[t].end());
1552 : }
1553 :
1554 : // new dimension of neighbor list
1555 : unsigned long long nl_size = nl_.size();
1556 : // now resize derivatives
1557 11 : ovmd_der_.resize(nl_size);
1558 :
1559 : // in case of B-factors sampling - at the right step
1560 11 : if(dbfact_>0 && getStep()%MCBstride_==0) {
1561 : // list of voxels per atom, in the order of the neighbor list
1562 5 : Model_nb_.clear();
1563 5 : Model_nb_.resize(natoms);
1564 75426 : for(unsigned long long i=0; i<nl_size; ++i) {
1565 75421 : Model_nb_[nl_[i].second].push_back(nl_[i].first);
1566 : }
1567 : }
1568 :
1569 : // transfer data to gpu
1570 11 : update_gpu();
1571 11 : }
1572 :
1573 11 : void EMMIVOX::update_gpu() {
1574 : // dimension of neighbor list
1575 11 : unsigned nl_size = nl_.size();
1576 : // create useful vectors
1577 11 : std::vector<int> nl_id(nl_size), nl_im(nl_size);
1578 11 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1579 : for(unsigned long long i=0; i<nl_size; ++i) {
1580 : nl_id[i] = static_cast<int>(nl_[i].first);
1581 : nl_im[i] = static_cast<int>(nl_[i].second);
1582 : }
1583 : // create tensors on device
1584 44 : nl_id_gpu_ = torch::from_blob(nl_id.data(), {nl_size}, torch::kInt32).clone().to(device_t_);
1585 33 : nl_im_gpu_ = torch::from_blob(nl_im.data(), {nl_size}, torch::kInt32).clone().to(device_t_);
1586 : // now we need to create pref_nl_gpu_ [5,nl_size]
1587 11 : pref_nl_gpu_ = torch::index_select(pref_gpu_,1,nl_im_gpu_);
1588 : // and invs2_nl_gpu_ [5,nl_size]
1589 11 : invs2_nl_gpu_ = torch::index_select(invs2_gpu_,1,nl_im_gpu_);
1590 : // and Map_m_nl_gpu_ [3,nl_size]
1591 11 : Map_m_nl_gpu_ = torch::index_select(Map_m_gpu_,1,nl_id_gpu_);
1592 11 : }
1593 :
1594 12 : void EMMIVOX::prepare() {
1595 12 : if(getExchangeStep()) {
1596 0 : first_time_=true;
1597 : }
1598 12 : }
1599 :
1600 : // calculate forward model on gpu
1601 14 : void EMMIVOX::calculate_fmod() {
1602 : // number of atoms
1603 14 : int natoms = Model_type_.size();
1604 : // number of data points
1605 14 : int nd = ovdd_.size();
1606 :
1607 : // fill positions in parallel
1608 14 : std::vector<double> posg(3*natoms);
1609 14 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1610 : for (int i=0; i<natoms; ++i) {
1611 : // fill vectors
1612 : posg[i] = getPosition(i)[0];
1613 : posg[i+natoms] = getPosition(i)[1];
1614 : posg[i+2*natoms] = getPosition(i)[2];
1615 : }
1616 : // transfer positions to pos_gpu [3,natoms]
1617 28 : torch::Tensor pos_gpu = torch::from_blob(posg.data(), {3,natoms}, torch::kFloat64).to(torch::kFloat32).to(device_t_);
1618 : // create pos_nl_gpu_ [3,nl_size]
1619 14 : torch::Tensor pos_nl_gpu = torch::index_select(pos_gpu,1,nl_im_gpu_);
1620 : // calculate vector difference [3,nl_size]
1621 14 : torch::Tensor md = Map_m_nl_gpu_ - pos_nl_gpu;
1622 : // calculate norm squared by column [1,nl_size]
1623 28 : torch::Tensor md2 = torch::sum(md*md,0);
1624 : // calculate density [5,nl_size]
1625 42 : torch::Tensor ov = pref_nl_gpu_ * torch::exp(-0.5 * md2 * invs2_nl_gpu_);
1626 : // and derivatives [5,nl_size]
1627 14 : ovmd_der_gpu_ = invs2_nl_gpu_ * ov;
1628 : // sum density over 5 columns [1,nl_size]
1629 28 : ov = torch::sum(ov,0);
1630 : // sum contributions from the same atom
1631 14 : auto options = torch::TensorOptions().device(device_t_).dtype(torch::kFloat32);
1632 14 : ovmd_gpu_ = torch::zeros({nd}, options);
1633 14 : ovmd_gpu_.index_add_(0, nl_id_gpu_, ov);
1634 : // sum derivatives over 5 rows [1,nl_size] and multiply by md [3,nl_size]
1635 14 : ovmd_der_gpu_ = md * torch::sum(ovmd_der_gpu_,0);
1636 :
1637 : // in case of metainference: average them across replicas
1638 14 : if(!no_aver_ && nrep_>1) {
1639 : // communicate ovmd_gpu_ to CPU [1, nd]
1640 0 : torch::Tensor ovmd_cpu = ovmd_gpu_.detach().to(torch::kCPU).to(torch::kFloat64);
1641 : // and put them in ovmd_
1642 0 : ovmd_ = std::vector<double>(ovmd_cpu.data_ptr<double>(), ovmd_cpu.data_ptr<double>() + ovmd_cpu.numel());
1643 : // sum across replicas
1644 0 : multi_sim_comm.Sum(&ovmd_[0], nd);
1645 : // and divide by number of replicas
1646 0 : double escale = 1.0 / static_cast<double>(nrep_);
1647 0 : for(int i=0; i<nd; ++i) {
1648 0 : ovmd_[i] *= escale;
1649 : }
1650 : // put back on device
1651 0 : ovmd_gpu_ = torch::from_blob(ovmd_.data(), {nd}, torch::kFloat64).to(torch::kFloat32).to(device_t_);
1652 : }
1653 :
1654 : // communicate back model density
1655 : // this is needed only in certain situations
1656 14 : long int step = getStep();
1657 : bool do_comm = false;
1658 14 : if(mapstride_>0 && step%mapstride_==0) {
1659 : do_comm = true;
1660 : }
1661 14 : if(dbfact_>0 && step%MCBstride_==0) {
1662 : do_comm = true;
1663 : }
1664 14 : if(do_corr_) {
1665 : do_comm = true;
1666 : }
1667 : // in case of metainference: already communicated
1668 14 : if(!no_aver_ && nrep_>1) {
1669 : do_comm = false;
1670 : }
1671 14 : if(do_comm) {
1672 : // communicate ovmd_gpu_ to CPU [1, nd]
1673 33 : torch::Tensor ovmd_cpu = ovmd_gpu_.detach().to(torch::kCPU).to(torch::kFloat64);
1674 : // and put them in ovmd_
1675 33 : ovmd_ = std::vector<double>(ovmd_cpu.data_ptr<double>(), ovmd_cpu.data_ptr<double>() + ovmd_cpu.numel());
1676 : }
1677 14 : }
1678 :
1679 : // calculate score
1680 12 : void EMMIVOX::calculate_score() {
1681 : // number of atoms
1682 12 : int natoms = Model_type_.size();
1683 :
1684 : // calculate deviation model/data [1, nd]
1685 36 : torch::Tensor dev = scale_ * ovmd_gpu_ + offset_ - ovdd_gpu_;
1686 : // error function [1, nd]
1687 24 : torch::Tensor errf = torch::erf( dev * inv_sqrt2_ * ismin_gpu_ );
1688 : // take care of dev = zero
1689 12 : torch::Tensor zeros_d = torch::ne(dev, 0.0);
1690 : // redefine dev
1691 60 : dev = dev * zeros_d + eps_ * torch::logical_not(zeros_d);
1692 : // take care of errf = zero
1693 12 : torch::Tensor zeros_e = torch::ne(errf, 0.0);
1694 : // redefine errf
1695 60 : errf = errf * zeros_e + eps_ * torch::logical_not(zeros_e);
1696 : // logical AND: both dev and errf different from zero
1697 : torch::Tensor zeros = torch::logical_and(zeros_d, zeros_e);
1698 : // energy - with limit dev going to zero
1699 60 : torch::Tensor ene = 0.5 * ( errf / dev * zeros + torch::logical_not(zeros) * sqrt2_pi_ * ismin_gpu_);
1700 : // logarithm and sum
1701 : // the sum over the voxels is done in double precision
1702 36 : ene = -kbt_ * torch::sum(torch::log(ene).to(torch::kFloat64));
1703 : // and derivatives [1, nd]
1704 72 : torch::Tensor d_der = -kbt_ * zeros * ( sqrt2_pi_ * torch::exp( -0.5 * dev * dev * ismin_gpu_ * ismin_gpu_ ) * ismin_gpu_ / errf - 1.0 / dev );
1705 : // tensor for derivatives wrt atoms [1, nl_size]
1706 12 : torch::Tensor der_gpu = torch::index_select(d_der,0,nl_id_gpu_);
1707 : // multiply by ovmd_der_gpu_ and scale [3, nl_size]
1708 24 : der_gpu = ovmd_der_gpu_ * scale_ * der_gpu;
1709 : // sum contributions for each atom
1710 12 : auto options = torch::TensorOptions().device(device_t_).dtype(torch::kFloat32);
1711 12 : torch::Tensor atoms_der_gpu = torch::zeros({3,natoms}, options);
1712 24 : atoms_der_gpu.index_add_(1, nl_im_gpu_, der_gpu);
1713 :
1714 : // FINAL STUFF
1715 : //
1716 : // 1) communicate total energy to CPU
1717 36 : torch::Tensor ene_cpu = ene.detach().to(torch::kCPU).to(torch::kFloat64);
1718 12 : ene_ = *ene_cpu.data_ptr<double>();
1719 : // with marginal, simply multiply by number of replicas!
1720 12 : if(!no_aver_ && nrep_>1) {
1721 0 : ene_ *= static_cast<double>(nrep_);
1722 : }
1723 : //
1724 : // 2) communicate derivatives to CPU
1725 36 : torch::Tensor atom_der_cpu = atoms_der_gpu.detach().to(torch::kCPU).to(torch::kFloat64);
1726 : // convert to std::vector<double>
1727 24 : std::vector<double> atom_der = std::vector<double>(atom_der_cpu.data_ptr<double>(), atom_der_cpu.data_ptr<double>() + atom_der_cpu.numel());
1728 : // and put in atom_der_
1729 12 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1730 : for(int i=0; i<natoms; ++i) {
1731 : atom_der_[i] = Vector(atom_der[i],atom_der[i+natoms],atom_der[i+2*natoms]);
1732 : }
1733 : //
1734 : // 3) calculate virial on CPU
1735 12 : Tensor virial;
1736 : // declare omp reduction for Tensors
1737 : #pragma omp declare reduction( sumTensor : Tensor : omp_out += omp_in )
1738 :
1739 12 : #pragma omp parallel for num_threads(OpenMP::getNumThreads()) reduction (sumTensor : virial)
1740 : for(int i=0; i<natoms; ++i) {
1741 : virial += Tensor(getPosition(i), -atom_der_[i]);
1742 : }
1743 : // store virial
1744 12 : virial_ = virial;
1745 12 : }
1746 :
1747 12 : void EMMIVOX::calculate() {
1748 : // get time step
1749 12 : long int step = getStep();
1750 :
1751 : // set temperature value
1752 12 : getPntrToComponent("kbt")->set(kbt_);
1753 :
1754 : // neighbor list update
1755 12 : if(first_time_ || getExchangeStep() || step%nl_stride_==0) {
1756 : // check if time to update neighbor sphere
1757 : bool update = false;
1758 9 : if(first_time_ || getExchangeStep()) {
1759 : update = true;
1760 : } else {
1761 7 : update = do_neighbor_sphere();
1762 : }
1763 : // update neighbor sphere
1764 7 : if(update) {
1765 4 : update_neighbor_sphere();
1766 : }
1767 : // update neighbor list
1768 9 : update_neighbor_list();
1769 : // set flag
1770 9 : first_time_=false;
1771 : }
1772 :
1773 : // calculate forward model
1774 12 : calculate_fmod();
1775 :
1776 : // Monte Carlo on bfactors
1777 12 : if(dbfact_>0) {
1778 : double acc = 0.0;
1779 : // do Monte Carlo
1780 6 : if(step%MCBstride_==0 && !getExchangeStep() && step>0) {
1781 2 : doMonteCarloBfact();
1782 : }
1783 : // calculate acceptance ratio
1784 6 : if(MCBtrials_>0) {
1785 4 : acc = MCBaccept_ / MCBtrials_;
1786 : }
1787 : // set value
1788 12 : getPntrToComponent("accB")->set(acc);
1789 : }
1790 :
1791 : // calculate score
1792 12 : calculate_score();
1793 :
1794 : // set score, virial, and derivatives
1795 12 : Value* score = getPntrToComponent("scoreb");
1796 12 : score->set(ene_);
1797 12 : setBoxDerivatives(score, virial_);
1798 12 : #pragma omp parallel for num_threads(OpenMP::getNumThreads())
1799 : for(unsigned i=0; i<atom_der_.size(); ++i) {
1800 : setAtomsDerivatives(score, i, atom_der_[i]);
1801 : }
1802 : // set scale and offset value
1803 12 : getPntrToComponent("scale")->set(scale_);
1804 12 : getPntrToComponent("offset")->set(offset_);
1805 : // calculate correlation coefficient
1806 12 : if(do_corr_) {
1807 6 : calculate_corr();
1808 : }
1809 : // PRINT other quantities to files
1810 : // - status file
1811 12 : if(step%statusstride_==0) {
1812 6 : print_status(step);
1813 : }
1814 : // - density file
1815 12 : if(mapstride_>0 && step%mapstride_==0) {
1816 2 : write_model_density(step);
1817 : }
1818 12 : }
1819 :
1820 6 : void EMMIVOX::calculate_corr() {
1821 : // number of data points
1822 6 : double nd = static_cast<double>(ovdd_.size());
1823 : // average ovmd_ and ovdd_
1824 6 : double ave_md = std::accumulate(ovmd_.begin(), ovmd_.end(), 0.) / nd;
1825 6 : double ave_dd = std::accumulate(ovdd_.begin(), ovdd_.end(), 0.) / nd;
1826 : // calculate correlation
1827 : double num = 0.;
1828 : double den1 = 0.;
1829 : double den2 = 0.;
1830 6 : #pragma omp parallel for num_threads(OpenMP::getNumThreads()) reduction( + : num, den1, den2)
1831 : for(unsigned i=0; i<ovdd_.size(); ++i) {
1832 : double md = ovmd_[i]-ave_md;
1833 : double dd = ovdd_[i]-ave_dd;
1834 : num += md*dd;
1835 : den1 += md*md;
1836 : den2 += dd*dd;
1837 : }
1838 : // correlation coefficient
1839 6 : double cc = num / sqrt(den1*den2);
1840 : // set plumed
1841 6 : getPntrToComponent("corr")->set(cc);
1842 6 : }
1843 :
1844 :
1845 : }
1846 : }
1847 :
1848 : #endif
|