Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2011-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 : #include "PlumedMain.h"
23 : #include "ActionAtomistic.h"
24 : #include "ActionPilot.h"
25 : #include "ActionForInterface.h"
26 : #include "ActionRegister.h"
27 : #include "ActionSet.h"
28 : #include "ActionWithValue.h"
29 : #include "ActionWithVirtualAtom.h"
30 : #include "ActionToGetData.h"
31 : #include "ActionToPutData.h"
32 : #include "CLToolMain.h"
33 : #include "ExchangePatterns.h"
34 : #include "GREX.h"
35 : #include "DomainDecomposition.h"
36 : #include "config/Config.h"
37 : #include "tools/Citations.h"
38 : #include "tools/Communicator.h"
39 : #include "tools/DLLoader.h"
40 : #include "tools/Exception.h"
41 : #include "tools/IFile.h"
42 : #include "tools/Log.h"
43 : #include "tools/OpenMP.h"
44 : #include "tools/Tools.h"
45 : #include "tools/Stopwatch.h"
46 : #include "tools/TypesafePtr.h"
47 : #include "lepton/Exception.h"
48 : #include "DataPassingTools.h"
49 : #include "small_vector/small_vector.h"
50 : #include <cstdlib>
51 : #include <cstdio>
52 : #include <cstring>
53 : #include <set>
54 : #include <exception>
55 : #include <stdexcept>
56 : #include <ios>
57 : #include <new>
58 : #include <typeinfo>
59 : #include <iostream>
60 : #include <algorithm>
61 : #include <system_error>
62 : #include <future>
63 : #include <memory>
64 : #include <functional>
65 : #include <regex>
66 : #include <any>
67 : #include <optional>
68 : #include <variant>
69 : #include <filesystem>
70 :
71 : namespace PLMD {
72 :
73 : /// Small utility just used in this file to throw arbitrary exceptions
74 61 : [[noreturn]] static void testThrow(const char* what) {
75 61 : auto words=Tools::getWords(what);
76 61 : plumed_assert(words.size()>0);
77 : #define __PLUMED_THROW_NOMSG(type) if(words[0]==#type) throw type()
78 : #define __PLUMED_THROW_MSG(type) if(words[0]==#type) throw type(what)
79 62 : __PLUMED_THROW_MSG(PLMD::ExceptionError);
80 61 : __PLUMED_THROW_MSG(PLMD::ExceptionDebug);
81 60 : __PLUMED_THROW_MSG(PLMD::Exception);
82 59 : __PLUMED_THROW_MSG(PLMD::lepton::Exception);
83 57 : __PLUMED_THROW_NOMSG(std::bad_exception);
84 56 : __PLUMED_THROW_NOMSG(std::bad_array_new_length);
85 55 : __PLUMED_THROW_NOMSG(std::bad_alloc);
86 54 : __PLUMED_THROW_NOMSG(std::bad_function_call);
87 53 : __PLUMED_THROW_NOMSG(std::bad_weak_ptr);
88 52 : __PLUMED_THROW_NOMSG(std::bad_cast);
89 51 : __PLUMED_THROW_NOMSG(std::bad_typeid);
90 50 : __PLUMED_THROW_NOMSG(std::bad_variant_access);
91 49 : __PLUMED_THROW_NOMSG(std::bad_optional_access);
92 48 : __PLUMED_THROW_NOMSG(std::bad_any_cast);
93 47 : __PLUMED_THROW_MSG(std::underflow_error);
94 46 : __PLUMED_THROW_MSG(std::overflow_error);
95 45 : __PLUMED_THROW_MSG(std::range_error);
96 44 : __PLUMED_THROW_MSG(std::runtime_error);
97 43 : __PLUMED_THROW_MSG(std::out_of_range);
98 42 : __PLUMED_THROW_MSG(std::length_error);
99 41 : __PLUMED_THROW_MSG(std::domain_error);
100 40 : __PLUMED_THROW_MSG(std::invalid_argument);
101 39 : __PLUMED_THROW_MSG(std::logic_error);
102 :
103 :
104 :
105 38 : if(words[0]=="std::system_error") {
106 4 : plumed_assert(words.size()>2);
107 : int error_code;
108 4 : Tools::convert(words[2],error_code);
109 4 : if(words[1]=="std::generic_category") {
110 1 : throw std::system_error(error_code,std::generic_category(),what);
111 : }
112 3 : if(words[1]=="std::system_category") {
113 1 : throw std::system_error(error_code,std::system_category(),what);
114 : }
115 2 : if(words[1]=="std::iostream_category") {
116 1 : throw std::system_error(error_code,std::iostream_category(),what);
117 : }
118 1 : if(words[1]=="std::future_category") {
119 1 : throw std::system_error(error_code,std::future_category(),what);
120 : }
121 : }
122 :
123 34 : if(words[0]=="std::filesystem::filesystem_error") {
124 : int error_code;
125 3 : plumed_assert(words.size()>2);
126 3 : Tools::convert(words[2],error_code);
127 : std::error_code x_error_code;
128 3 : if(words[1]=="std::generic_category") {
129 3 : x_error_code=::std::error_code(error_code,::std::generic_category());
130 : }
131 3 : if(words[1]=="std::system_category") {
132 0 : x_error_code=::std::error_code(error_code,::std::system_category());
133 : }
134 3 : if(words[1]=="std::iostream_category") {
135 0 : x_error_code=::std::error_code(error_code,::std::iostream_category());
136 : }
137 3 : if(words[1]=="std::future_category") {
138 0 : x_error_code=::std::error_code(error_code,::std::future_category());
139 : }
140 :
141 3 : if(words.size()<4) {
142 2 : throw std::filesystem::filesystem_error(what,x_error_code);
143 : }
144 2 : if(words.size()<5) {
145 3 : throw std::filesystem::filesystem_error(what,std::filesystem::path(words[3]),x_error_code);
146 : }
147 4 : throw std::filesystem::filesystem_error(what,std::filesystem::path(words[3]),std::filesystem::path(words[4]),x_error_code);
148 : }
149 :
150 : #define __PLUMED_THROW_REGEX(name) if(words[1]=="std::regex_constants::error_" #name) throw std::regex_error(std::regex_constants::error_ ##name)
151 31 : if(words[0]=="std::regex_error") {
152 13 : plumed_assert(words.size()>1);
153 13 : __PLUMED_THROW_REGEX(collate);
154 12 : __PLUMED_THROW_REGEX(ctype);
155 11 : __PLUMED_THROW_REGEX(escape);
156 10 : __PLUMED_THROW_REGEX(backref);
157 9 : __PLUMED_THROW_REGEX(brack);
158 8 : __PLUMED_THROW_REGEX(paren);
159 7 : __PLUMED_THROW_REGEX(brace);
160 6 : __PLUMED_THROW_REGEX(badbrace);
161 5 : __PLUMED_THROW_REGEX(range);
162 4 : __PLUMED_THROW_REGEX(space);
163 3 : __PLUMED_THROW_REGEX(badrepeat);
164 2 : __PLUMED_THROW_REGEX(complexity);
165 1 : __PLUMED_THROW_REGEX(stack);
166 : }
167 :
168 : #define __PLUMED_THROW_FUTURE(name) if(words[1]=="std::future_errc::" #name) throw std::future_error(::std::future_errc::name)
169 18 : if(words[0]=="std::future_error") {
170 4 : plumed_assert(words.size()>1);
171 4 : __PLUMED_THROW_FUTURE(broken_promise);
172 3 : __PLUMED_THROW_FUTURE(future_already_retrieved);
173 2 : __PLUMED_THROW_FUTURE(promise_already_satisfied);
174 1 : __PLUMED_THROW_FUTURE(no_state);
175 : }
176 :
177 14 : if(words[0]=="std::ios_base::failure") {
178 1 : int error_code=0;
179 1 : if(words.size()>2) {
180 0 : Tools::convert(words[2],error_code);
181 : }
182 1 : if(words.size()>1 && words[1]=="std::generic_category") {
183 0 : throw std::ios_base::failure(what,std::error_code(error_code,std::generic_category()));
184 : }
185 1 : if(words.size()>1 && words[1]=="std::system_category") {
186 0 : throw std::ios_base::failure(what,std::error_code(error_code,std::system_category()));
187 : }
188 1 : if(words.size()>1 && words[1]=="std::iostream_category") {
189 0 : throw std::ios_base::failure(what,std::error_code(error_code,std::iostream_category()));
190 : }
191 1 : if(words.size()>1 && words[1]=="std::future_category") {
192 0 : throw std::ios_base::failure(what,std::error_code(error_code,std::future_category()));
193 : }
194 2 : throw std::ios_base::failure(what);
195 : }
196 :
197 13 : if(words[0]=="int") {
198 1 : int value=0;
199 1 : if(words.size()>1) {
200 0 : Tools::convert(words[1],value);
201 : }
202 1 : throw value;
203 : }
204 :
205 12 : if(words[0]=="test_nested1") {
206 : try {
207 12 : throw Exception(std::string("inner ")+what);
208 6 : } catch(...) {
209 : try {
210 18 : std::throw_with_nested(Exception(std::string("middle ")+what));
211 6 : } catch(...) {
212 18 : std::throw_with_nested(Exception(std::string("outer ")+what));
213 6 : }
214 6 : }
215 : }
216 :
217 6 : if(words[0]=="test_nested2") {
218 : try {
219 3 : throw std::bad_alloc();
220 3 : } catch(...) {
221 : try {
222 9 : std::throw_with_nested(Exception(std::string("middle ")+what));
223 3 : } catch(...) {
224 9 : std::throw_with_nested(Exception(std::string("outer ")+what));
225 3 : }
226 3 : }
227 : }
228 :
229 3 : if(words[0]=="test_nested3") {
230 : try {
231 2 : throw "inner";
232 2 : } catch(...) {
233 : try {
234 6 : std::throw_with_nested(Exception(std::string("middle ")+what));
235 2 : } catch(...) {
236 6 : std::throw_with_nested(Exception(std::string("outer ")+what));
237 2 : }
238 2 : }
239 : }
240 :
241 2 : plumed_error() << "unknown exception " << what;
242 61 : }
243 :
244 : namespace {
245 : /// This is an internal tool used to count how many PlumedMain objects have been created
246 : /// and if they were correctly destroyed.
247 : /// When using debug options, it leads to a crash
248 : /// Otherwise, it just prints a message
249 : class CountInstances {
250 : std::atomic<int> counter{};
251 : // private constructor to avoid direct usage
252 5866 : CountInstances() noexcept {}
253 5866 : ~CountInstances() {
254 5866 : if(counter!=0) {
255 0 : std::cerr<<"WARNING: internal inconsistency in allocated PlumedMain instances (" <<counter<< ")\n";
256 0 : std::cerr<<"Might be a consequence of incorrectly paired plumed_create/plumed_finalize in the C interface\n";
257 0 : std::cerr<<"Or it could be due to incorrect calls to std::exit, without properly destroying all PlumedMain objects\n";
258 : #ifndef NDEBUG
259 : std::cerr<<"This is a debug build, so the warning will make PLUMED abort\n";
260 : std::abort();
261 : #endif
262 : }
263 5866 : }
264 1614600 : static CountInstances & instance() {
265 1614600 : static CountInstances counter;
266 1614600 : return counter;
267 : }
268 : public:
269 : /// Only access through these static functions
270 : /// The first call to increase() ensures the instance is constructed
271 : /// This should provide the correct construction and destruction order
272 : /// also in cases where the PlumedMain object is constructed in the
273 : /// constructor of a static object
274 807300 : static void increase() noexcept {
275 807300 : ++instance().counter;
276 807300 : }
277 : /// See increase()
278 807300 : static void decrease() noexcept {
279 807300 : --instance().counter;
280 807300 : }
281 : };
282 :
283 : }
284 :
285 :
286 807300 : PlumedMain::PlumedMain():
287 807300 : datoms_fwd(*this),
288 : // automatically write on log in destructor
289 807300 : stopwatch_fwd(log),
290 807300 : actionSet_fwd(*this),
291 807300 : passtools(DataPassingTools::create(sizeof(double))) {
292 807300 : passtools->usingNaturalUnits=false;
293 807300 : increaseReferenceCounter();
294 807300 : log.link(comm);
295 807300 : log.setLinePrefix("PLUMED: ");
296 : // this is at last so as to avoid inconsistencies if an exception is thrown
297 807300 : CountInstances::increase(); // noexcept
298 807300 : }
299 :
300 : // destructor needed to delete forward declarated objects
301 1613598 : PlumedMain::~PlumedMain() {
302 807300 : CountInstances::decrease();
303 4842798 : }
304 :
305 : /////////////////////////////////////////////////////////////
306 : // MAIN INTERPRETER
307 :
308 : #define CHECK_INIT(ini,word) plumed_assert(ini)<<"cmd(\"" << word << "\") should be only used after plumed initialization"
309 : #define CHECK_NOTINIT(ini,word) plumed_assert(!(ini))<<"cmd(\"" << word << "\") should be only used before plumed initialization"
310 : #define CHECK_NOTNULL(val,word) plumed_assert(val)<<"NULL pointer received in cmd(\"" << word << "\")"
311 :
312 :
313 1352223 : void PlumedMain::cmd(std::string_view word,const TypesafePtr & val) {
314 :
315 : // Enumerate all possible commands:
316 : enum {
317 : #include "PlumedMainEnum.inc"
318 : };
319 :
320 : // Static object (initialized once) containing the map of commands:
321 : const static Tools::FastStringUnorderedMap<int> word_map = {
322 : #include "PlumedMainMap.inc"
323 1352223 : };
324 :
325 : try {
326 :
327 1352223 : auto ss=stopwatch.startPause();
328 :
329 : gch::small_vector<std::string_view> words;
330 1352223 : Tools::getWordsSimple(words,word);
331 :
332 1352223 : unsigned nw=words.size();
333 1352223 : if(nw==0) {
334 : // do nothing
335 : } else {
336 : int iword=-1;
337 : const auto it=word_map.find(words[0]);
338 1352222 : if(it!=word_map.end()) {
339 1352215 : iword=it->second;
340 : }
341 :
342 1352215 : switch(iword) {
343 70601 : case cmd_setBox:
344 70601 : CHECK_INIT(initialized,word);
345 70601 : CHECK_NOTNULL(val,word);
346 70601 : setInputValue( "Box", 0, 1, val );
347 70600 : break;
348 74778 : case cmd_setPositions:
349 74778 : CHECK_INIT(initialized,word);
350 74778 : setInputValue("posx", 0, 3, val );
351 74778 : setInputValue("posy", 1, 3, val );
352 74778 : setInputValue("posz", 2, 3, val );
353 74778 : break;
354 74741 : case cmd_setMasses:
355 74741 : CHECK_INIT(initialized,word);
356 74741 : setInputValue("Masses", 0, 1, val );
357 74738 : break;
358 66662 : case cmd_setCharges:
359 66662 : CHECK_INIT(initialized,word);
360 66662 : setInputValue("Charges", 0, 1, val);
361 66662 : break;
362 1 : case cmd_setPositionsX:
363 1 : CHECK_INIT(initialized,word);
364 1 : setInputValue("posx", 0, 1, val);
365 1 : break;
366 1 : case cmd_setPositionsY:
367 1 : CHECK_INIT(initialized,word);
368 1 : setInputValue("posy", 0, 1, val);
369 1 : break;
370 1 : case cmd_setPositionsZ:
371 1 : CHECK_INIT(initialized,word);
372 1 : setInputValue("posz", 0, 1, val);
373 1 : break;
374 66915 : case cmd_setVirial:
375 66915 : CHECK_INIT(initialized,word);
376 66915 : CHECK_NOTNULL(val,word);
377 66915 : setInputForce("Box",val);
378 66897 : break;
379 7842 : case cmd_setEnergy:
380 7842 : CHECK_INIT(initialized,word);
381 7842 : CHECK_NOTNULL(val,word);
382 7842 : if( name_of_energy!="" ) {
383 3989 : setInputValue( name_of_energy, 0, 1, val );
384 : }
385 : break;
386 74745 : case cmd_setForces:
387 74745 : CHECK_INIT(initialized,word);
388 74745 : setInputForce("posx",val);
389 74745 : setInputForce("posy",val);
390 74745 : setInputForce("posz",val);
391 74745 : break;
392 1 : case cmd_setForcesX:
393 1 : CHECK_INIT(initialized,word);
394 1 : setInputForce("posx",val);
395 1 : break;
396 1 : case cmd_setForcesY:
397 1 : CHECK_INIT(initialized,word);
398 1 : setInputForce("posy",val);
399 1 : break;
400 1 : case cmd_setForcesZ:
401 1 : CHECK_INIT(initialized,word);
402 1 : setInputForce("posz",val);
403 1 : break;
404 280128 : case cmd_calc:
405 280128 : CHECK_INIT(initialized,word);
406 280128 : calc();
407 : break;
408 99 : case cmd_prepareDependencies:
409 99 : CHECK_INIT(initialized,word);
410 99 : prepareDependencies();
411 : break;
412 84 : case cmd_shareData:
413 84 : CHECK_INIT(initialized,word);
414 84 : shareData();
415 : break;
416 6951 : case cmd_prepareCalc:
417 6951 : CHECK_INIT(initialized,word);
418 6951 : prepareCalc();
419 : break;
420 10 : case cmd_performCalc:
421 10 : CHECK_INIT(initialized,word);
422 10 : performCalc();
423 : break;
424 7011 : case cmd_performCalcNoUpdate:
425 7011 : CHECK_INIT(initialized,word);
426 7011 : performCalcNoUpdate();
427 : break;
428 10 : case cmd_performCalcNoForces:
429 10 : CHECK_INIT(initialized,word);
430 10 : performCalcNoForces();
431 : break;
432 79 : case cmd_update:
433 79 : CHECK_INIT(initialized,word);
434 79 : update();
435 : break;
436 16013 : case cmd_setStep:
437 16013 : CHECK_INIT(initialized,word);
438 16016 : CHECK_NOTNULL(val,word);
439 16010 : step=val.get<int>();
440 16009 : startStep();
441 : break;
442 0 : case cmd_setStepLong:
443 0 : CHECK_INIT(initialized,word);
444 0 : CHECK_NOTNULL(val,word);
445 0 : step=val.get<long int>();
446 0 : startStep();
447 : break;
448 271067 : case cmd_setStepLongLong:
449 271067 : CHECK_INIT(initialized,word);
450 271067 : CHECK_NOTNULL(val,word);
451 271067 : step=val.get<long long int>();
452 271067 : startStep();
453 : break;
454 23456 : case cmd_setValue: {
455 23456 : CHECK_INIT(initialized,words[0]);
456 23456 : plumed_assert(nw==2);
457 23456 : setInputValue( std::string(words[1]), 0, 1, val );
458 : }
459 23456 : break;
460 : /* ADDED WITH API=7 */
461 0 : case cmd_setValueForces: {
462 0 : CHECK_INIT(initialized,words[0]);
463 0 : plumed_assert(nw==2);
464 0 : setInputForce( std::string(words[1]), val );
465 : }
466 0 : break;
467 : // words used less frequently:
468 1142 : case cmd_setAtomsNlocal:
469 1142 : CHECK_INIT(initialized,word);
470 1142 : CHECK_NOTNULL(val,word);
471 4588 : for(const auto & pp : inputs ) {
472 3446 : plumed_assert(pp);
473 3446 : DomainDecomposition* dd=pp->castToDomainDecomposition();
474 3446 : if( dd ) {
475 1142 : dd->setAtomsNlocal(val.get<int>());
476 : }
477 : }
478 : break;
479 988 : case cmd_setAtomsGatindex:
480 988 : CHECK_INIT(initialized,word);
481 3974 : for(const auto & pp : inputs ) {
482 2986 : plumed_assert(pp);
483 2986 : DomainDecomposition* dd=pp->castToDomainDecomposition();
484 2986 : if( dd ) {
485 988 : dd->setAtomsGatindex(val,false);
486 : }
487 : }
488 : break;
489 2 : case cmd_setAtomsFGatindex:
490 2 : CHECK_INIT(initialized,word);
491 6 : for(const auto & pp : inputs ) {
492 4 : plumed_assert(pp);
493 4 : DomainDecomposition* dd=pp->castToDomainDecomposition();
494 4 : if( dd ) {
495 2 : dd->setAtomsGatindex(val,true);
496 : }
497 : }
498 : break;
499 152 : case cmd_setAtomsContiguous:
500 152 : CHECK_INIT(initialized,word);
501 152 : CHECK_NOTNULL(val,word);
502 608 : for(const auto & pp : inputs ) {
503 456 : plumed_assert(pp);
504 456 : DomainDecomposition* dd=pp->castToDomainDecomposition();
505 456 : if( dd ) {
506 152 : dd->setAtomsContiguous(val.get<int>());
507 : }
508 : }
509 : break;
510 116 : case cmd_createFullList:
511 116 : CHECK_INIT(initialized,word);
512 116 : CHECK_NOTNULL(val,word);
513 553 : for(const auto & pp : inputs ) {
514 437 : plumed_assert(pp);
515 437 : DomainDecomposition* dd=pp->castToDomainDecomposition();
516 437 : if( dd ) {
517 116 : dd->createFullList(val);
518 : }
519 : }
520 : break;
521 116 : case cmd_getFullList: {
522 116 : CHECK_INIT(initialized,word);
523 116 : CHECK_NOTNULL(val,word);
524 : unsigned nlists=0;
525 553 : for(const auto & pp : inputs ) {
526 437 : plumed_assert(pp);
527 437 : DomainDecomposition* dd=pp->castToDomainDecomposition();
528 437 : if( dd ) {
529 116 : dd->getFullList(val);
530 116 : nlists++;
531 : }
532 : }
533 116 : plumed_assert( nlists==1 );
534 : }
535 : break;
536 116 : case cmd_clearFullList:
537 116 : CHECK_INIT(initialized,word);
538 553 : for(const auto & pp : inputs ) {
539 437 : plumed_assert(pp);
540 437 : DomainDecomposition* dd=pp->castToDomainDecomposition();
541 437 : if( dd ) {
542 116 : dd->clearFullList();
543 : }
544 : }
545 : break;
546 : /* ADDED WITH API==6 */
547 115 : case cmd_getDataRank: {
548 115 : CHECK_INIT(initialized,words[0]);
549 115 : plumed_assert(nw==2 || nw==3);
550 115 : std::string vtype="";
551 115 : if( nw==3 ) {
552 0 : vtype=" TYPE="+std::string(words[2]);
553 : }
554 345 : readInputLine( "grab_" + std::string(words[1]) + ": GET ARG=" + std::string(words[1]) + vtype );
555 230 : ActionToGetData* as=actionSet.selectWithLabel<ActionToGetData*>("grab_"+std::string(words[1]));
556 115 : plumed_assert( as );
557 115 : as->get_rank( val );
558 : }
559 115 : break;
560 : /* ADDED WITH API==6 */
561 51 : case cmd_getDataShape: {
562 51 : CHECK_INIT(initialized,std::string(words[0]));
563 102 : ActionToGetData* as1=actionSet.selectWithLabel<ActionToGetData*>("grab_"+std::string(words[1]));
564 51 : plumed_assert( as1 );
565 51 : as1->get_shape( val );
566 : }
567 : break;
568 : /* ADDED WITH API==6 */
569 115 : case cmd_setMemoryForData: {
570 115 : CHECK_INIT(initialized,words[0]);
571 115 : plumed_assert(nw==2 || nw==3);
572 230 : ActionToGetData* as2=actionSet.selectWithLabel<ActionToGetData*>("grab_"+std::string(words[1]));
573 115 : plumed_assert( as2 );
574 115 : as2->set_memory( val );
575 : }
576 : break;
577 : /* ADDED WITH API==6 */
578 : case cmd_setErrorHandler: {
579 0 : if(val) {
580 0 : error_handler=*static_cast<const plumed_error_handler*>(val.get<const void*>());
581 : } else {
582 0 : error_handler.handler=NULL;
583 : }
584 : }
585 : break;
586 0 : case cmd_read:
587 0 : CHECK_INIT(initialized,word);
588 0 : if(val) {
589 0 : readInputFile(val.getCString());
590 : } else {
591 0 : readInputFile("plumed.dat");
592 : }
593 : break;
594 821 : case cmd_readInputLine:
595 821 : CHECK_INIT(initialized,word);
596 821 : CHECK_NOTNULL(val,word);
597 821 : readInputLine(val.getCString());
598 779 : break;
599 4 : case cmd_readInputLines:
600 4 : CHECK_INIT(initialized,word);
601 4 : CHECK_NOTNULL(val,word);
602 4 : readInputLines(val.getCString());
603 4 : break;
604 3 : case cmd_clear: {
605 3 : CHECK_INIT(initialized,word);
606 : std::vector<int> natoms;
607 7 : for(const auto & pp : inputs ) {
608 4 : plumed_assert(pp);
609 4 : DomainDecomposition* dd=pp->castToDomainDecomposition();
610 4 : if ( dd ) {
611 1 : natoms.push_back( dd->getNumberOfAtoms() );
612 : }
613 : }
614 3 : actionSet.clearDelete();
615 : inputs.clear();
616 4 : for(unsigned i=0; i<natoms.size(); ++i) {
617 : std::string str_natoms;
618 1 : Tools::convert( natoms[i], str_natoms );
619 3 : readInputLine( MDEngine + ": DOMAIN_DECOMPOSITION NATOMS=" + str_natoms +
620 2 : " VALUE1=posx UNIT1=length PERIODIC1=NO CONSTANT1=False ROLE1=x" +
621 2 : " VALUE2=posy UNIT2=length PERIODIC2=NO CONSTANT2=False ROLE2=y" +
622 2 : " VALUE3=posz UNIT3=length PERIODIC3=NO CONSTANT3=False ROLE3=z" +
623 2 : " VALUE4=Masses UNIT4=mass PERIODIC4=NO CONSTANT4=True ROLE4=m" +
624 : " VALUE5=Charges UNIT5=charge PERIODIC5=NO CONSTANT5=True ROLE5=q");
625 :
626 : }
627 3 : setUnits( passtools->usingNaturalUnits, passtools->units );
628 : }
629 3 : break;
630 : case cmd_getApiVersion:
631 45 : CHECK_NOTNULL(val,word);
632 45 : val.set(int(10));
633 : break;
634 : // commands which can be used only before initialization:
635 1318 : case cmd_init:
636 1318 : CHECK_NOTINIT(initialized,word);
637 1318 : init();
638 : break;
639 1072 : case cmd_setRealPrecision:
640 1072 : CHECK_NOTINIT(initialized,word);
641 1072 : CHECK_NOTNULL(val,word);
642 2143 : passtools=DataPassingTools::create(val.get<int>());
643 1071 : passtools->usingNaturalUnits=false;
644 1071 : break;
645 981 : case cmd_setMDLengthUnits:
646 981 : CHECK_NOTINIT(initialized,word);
647 981 : CHECK_NOTNULL(val,word);
648 981 : passtools->MDUnits.setLength(passtools->MD2double(val));
649 : break;
650 981 : case cmd_setMDChargeUnits:
651 981 : CHECK_NOTINIT(initialized,word);
652 981 : CHECK_NOTNULL(val,word);
653 981 : passtools->MDUnits.setCharge(passtools->MD2double(val));
654 : break;
655 981 : case cmd_setMDMassUnits:
656 981 : CHECK_NOTINIT(initialized,word);
657 981 : CHECK_NOTNULL(val,word);
658 981 : passtools->MDUnits.setMass(passtools->MD2double(val));
659 : break;
660 45 : case cmd_setMDEnergyUnits:
661 45 : CHECK_NOTINIT(initialized,word);
662 45 : CHECK_NOTNULL(val,word);
663 45 : passtools->MDUnits.setEnergy(passtools->MD2double(val));
664 : break;
665 6 : case cmd_setMDTimeUnits:
666 6 : CHECK_NOTINIT(initialized,word);
667 6 : CHECK_NOTNULL(val,word);
668 6 : passtools->MDUnits.setTime(passtools->MD2double(val));
669 : break;
670 0 : case cmd_setNaturalUnits:
671 : // set the boltzman constant for MD in natural units (kb=1)
672 : // only needed in LJ codes if the MD is passing temperatures to plumed (so, not yet...)
673 : // use as cmd("setNaturalUnits")
674 0 : CHECK_NOTINIT(initialized,word);
675 0 : passtools->usingNaturalUnits=true;
676 0 : break;
677 62 : case cmd_setNoVirial: {
678 62 : CHECK_NOTINIT(initialized,word);
679 62 : ActionToPutData* ap=actionSet.selectWithLabel<ActionToPutData*>("Box");
680 62 : if( ap ) {
681 52 : ap->noforce=true;
682 : } else {
683 10 : ActionForInterface* af = actionSet.selectWithLabel<ActionForInterface*>(MDEngine);
684 10 : if( af ) {
685 0 : plumed_merror("setNoVirial should be called after number of atoms have been set");
686 : }
687 : }
688 : }
689 : break;
690 1031 : case cmd_setPlumedDat:
691 1031 : CHECK_NOTINIT(initialized,word);
692 1031 : CHECK_NOTNULL(val,word);
693 1031 : plumedDat=val.getCString();
694 : break;
695 362 : case cmd_setMPIComm:
696 362 : CHECK_NOTINIT(initialized,word);
697 362 : comm.Set_comm(val);
698 364 : for(const auto & pp : inputs ) {
699 2 : pp->Set_comm(comm);
700 : }
701 : break;
702 0 : case cmd_setMPIFComm:
703 0 : CHECK_NOTINIT(initialized,word);
704 0 : comm.Set_fcomm(val);
705 0 : for(const auto & pp : inputs ) {
706 0 : pp->Set_comm(comm);
707 : }
708 : break;
709 0 : case cmd_setMPImultiSimComm:
710 0 : CHECK_NOTINIT(initialized,word);
711 0 : multi_sim_comm.Set_comm(val);
712 : break;
713 1302 : case cmd_setNatoms: {
714 1302 : CHECK_NOTINIT(initialized,word);
715 1302 : CHECK_NOTNULL(val,word);
716 1302 : int natoms = val.get<int>();
717 : std::string str_natoms;
718 1292 : Tools::convert( natoms, str_natoms );
719 1292 : ActionForInterface* dd=actionSet.selectWithLabel<ActionForInterface*>(MDEngine);
720 1292 : if( !dd && natoms>0 )
721 3669 : readInputLine( MDEngine + ": DOMAIN_DECOMPOSITION NATOMS=" + str_natoms + +
722 2446 : " VALUE1=posx UNIT1=length PERIODIC1=NO CONSTANT1=False ROLE1=x" +
723 2446 : " VALUE2=posy UNIT2=length PERIODIC2=NO CONSTANT2=False ROLE2=y" +
724 2446 : " VALUE3=posz UNIT3=length PERIODIC3=NO CONSTANT3=False ROLE3=z" +
725 2446 : " VALUE4=Masses UNIT4=mass PERIODIC4=NO CONSTANT4=True ROLE4=m" +
726 2446 : " VALUE5=Charges UNIT5=charge PERIODIC5=NO CONSTANT5=True ROLE5=q", true );
727 : }
728 1292 : break;
729 1051 : case cmd_setTimestep: {
730 1051 : CHECK_NOTINIT(initialized,word);
731 1051 : CHECK_NOTNULL(val,word);
732 1051 : ActionToPutData* ts = actionSet.selectWithLabel<ActionToPutData*>("timestep");
733 1051 : if( !ts ) {
734 1049 : readInputLine("timestep: PUT UNIT=time PERIODIC=NO CONSTANT", true);
735 2098 : ts = actionSet.selectWithLabel<ActionToPutData*>("timestep");
736 : }
737 2102 : if( !ts->setValuePointer("timestep", val ) ) {
738 0 : plumed_error();
739 : }
740 1051 : ts->updateUnits( passtools.get() );
741 : }
742 : break;
743 : /* ADDED WITH API==2 */
744 61 : case cmd_setKbT: {
745 61 : CHECK_NOTINIT(initialized,word);
746 61 : CHECK_NOTNULL(val,word);
747 61 : readInputLine("kBT: PUT CONSTANT PERIODIC=NO UNIT=energy", true);
748 61 : ActionToPutData* kb = actionSet.selectWithLabel<ActionToPutData*>("kBT");
749 122 : if( !kb->setValuePointer("kBT", val ) ) {
750 0 : plumed_error();
751 : }
752 61 : kb->updateUnits( passtools.get() );
753 : }
754 : break;
755 : /* ADDED WITH API==3 */
756 8 : case cmd_setRestart:
757 8 : CHECK_NOTINIT(initialized,word);
758 8 : CHECK_NOTNULL(val,word);
759 8 : if(val.get<int>()!=0) {
760 2 : restart=true;
761 : }
762 : break;
763 : /* ADDED WITH API==4 */
764 0 : case cmd_doCheckPoint:
765 0 : CHECK_INIT(initialized,word);
766 0 : CHECK_NOTNULL(val,word);
767 0 : doCheckPoint = false;
768 0 : if(val.get<int>()!=0) {
769 0 : doCheckPoint = true;
770 : }
771 : break;
772 : /* ADDED WITH API==6 */
773 : case cmd_setNumOMPthreads:
774 0 : CHECK_NOTNULL(val,word);
775 : {
776 0 : auto nt=val.get<unsigned>();
777 : if(nt==0) {
778 : nt=1;
779 : }
780 0 : OpenMP::setNumThreads(nt);
781 : }
782 : break;
783 : /* ADDED WITH API==10 */
784 : case cmd_setGpuDeviceId:
785 0 : CHECK_NOTNULL(val,word);
786 : {
787 0 : auto id=val.get<int>();
788 0 : if(id>=0) {
789 0 : gpuDeviceId=id;
790 : }
791 : }
792 : break;
793 : /* ADDED WITH API==6 */
794 : /* only used for testing */
795 : case cmd_throw:
796 61 : CHECK_NOTNULL(val,word);
797 61 : testThrow(val.getCString());
798 : /* ADDED WITH API==10 */
799 : case cmd_setNestedExceptions:
800 28 : CHECK_NOTNULL(val,word);
801 28 : if(val.get<int>()!=0) {
802 27 : nestedExceptions=true;
803 : } else {
804 1 : nestedExceptions=false;
805 : }
806 : break;
807 : /* STOP API */
808 1046 : case cmd_setMDEngine:
809 1046 : CHECK_NOTINIT(initialized,word);
810 1046 : CHECK_NOTNULL(val,word);
811 1046 : MDEngine=val.getCString();
812 : break;
813 1021 : case cmd_setLog:
814 1021 : CHECK_NOTINIT(initialized,word);
815 1021 : log.link(val.get<FILE*>());
816 : break;
817 169 : case cmd_setLogFile:
818 169 : CHECK_NOTINIT(initialized,word);
819 169 : CHECK_NOTNULL(val,word);
820 169 : log.open(val.getCString());
821 169 : break;
822 : // other commands that should be used after initialization:
823 268210 : case cmd_setStopFlag:
824 268210 : CHECK_INIT(initialized,word);
825 268210 : CHECK_NOTNULL(val,word);
826 268210 : val.get<int*>(); // just check type and discard pointer
827 268209 : stopFlag=val.copy();
828 268209 : break;
829 0 : case cmd_getExchangesFlag:
830 0 : CHECK_INIT(initialized,word);
831 0 : CHECK_NOTNULL(val,word);
832 0 : exchangePatterns.getFlag(*val.get<int*>()); // note: getFlag changes the value of the reference!
833 : break;
834 0 : case cmd_setExchangesSeed:
835 0 : CHECK_INIT(initialized,word);
836 0 : CHECK_NOTNULL(val,word);
837 0 : exchangePatterns.setSeed(val.get<int>());
838 : break;
839 0 : case cmd_setNumberOfReplicas:
840 0 : CHECK_INIT(initialized,word);
841 0 : CHECK_NOTNULL(val,word);
842 0 : exchangePatterns.setNofR(val.get<int>());
843 : break;
844 0 : case cmd_getExchangesList:
845 0 : CHECK_INIT(initialized,word);
846 0 : CHECK_NOTNULL(val,word);
847 0 : exchangePatterns.getList(val);
848 : break;
849 971 : case cmd_runFinalJobs:
850 971 : CHECK_INIT(initialized,word);
851 971 : runJobsAtEndOfCalculation();
852 : break;
853 96 : case cmd_isEnergyNeeded: {
854 96 : CHECK_INIT(initialized,word);
855 96 : CHECK_NOTNULL(val,word);
856 96 : if( name_of_energy =="" ) {
857 96 : val.set(int(0));
858 : } else {
859 0 : ActionToPutData* ap=actionSet.selectWithLabel<ActionToPutData*>(name_of_energy);
860 0 : if(ap->isActive()) {
861 0 : val.set(int(1));
862 : } else {
863 0 : val.set(int(0));
864 : }
865 : }
866 : }
867 : break;
868 7051 : case cmd_getBias:
869 7051 : CHECK_INIT(initialized,word);
870 7051 : CHECK_NOTNULL(val,word);
871 7051 : plumedQuantityToMD( "energy", getBias(), val );
872 7051 : break;
873 : case cmd_checkAction:
874 2 : CHECK_NOTNULL(val,word);
875 2 : plumed_assert(nw==2);
876 3 : val.set(int(actionRegister().check(dlloader.getHandles(), std::string(words[1])) ? 1:0));
877 2 : break;
878 : case cmd_setExtraCV: {
879 30 : CHECK_NOTNULL(val,word);
880 30 : plumed_assert(nw==2);
881 60 : if( valueExists(std::string(words[1])) ) {
882 60 : setInputValue( std::string(words[1]), 0, 1, val );
883 : }
884 : }
885 : break;
886 : case cmd_setExtraCVForce: {
887 30 : CHECK_NOTNULL(val,word);
888 30 : plumed_assert(nw==2);
889 60 : if( valueExists(std::string(words[1])) ) {
890 60 : setInputForce( std::string(words[1]), val );
891 : }
892 : }
893 : break;
894 : /* ADDED WITH API==10 */
895 : case cmd_isExtraCVNeeded:
896 10 : CHECK_NOTNULL(val,word);
897 10 : plumed_assert(nw==2);
898 10 : val.set(int(0));
899 56 : for(const auto & p : inputs) {
900 50 : if( p->getLabel()==words[1] && p->isActive() ) {
901 4 : val.set(int(1));
902 : break;
903 : }
904 : }
905 : break;
906 1089 : case cmd_GREX:
907 1089 : if(!grex) {
908 208 : grex=Tools::make_unique<GREX>(*this);
909 : }
910 1089 : plumed_massert(grex,"error allocating grex");
911 : {
912 1089 : std::string kk=std::string(words[1]);
913 1260 : for(unsigned i=2; i<words.size(); i++) {
914 342 : kk+=" "+std::string(words[i]);
915 : }
916 1089 : grex->cmd(kk.c_str(),val);
917 : }
918 1089 : break;
919 18099 : case cmd_CLTool:
920 18099 : CHECK_NOTINIT(initialized,word);
921 18099 : if(!cltool) {
922 5935 : cltool=Tools::make_unique<CLToolMain>();
923 : }
924 : {
925 18099 : std::string kk(words[1]);
926 18099 : for(unsigned i=2; i<words.size(); i++) {
927 0 : kk+=" "+std::string(words[i]);
928 : }
929 18099 : cltool->cmd(kk.c_str(),val);
930 : }
931 18099 : break;
932 : break;
933 : /* ADDED WITH API==7 */
934 : case cmd_convert: {
935 : double v;
936 57 : plumed_assert(words.size()==2);
937 280 : if(Tools::convertNoexcept(std::string(words[1]),v)) {
938 47 : passtools->double2MD(v,val);
939 : }
940 : }
941 57 : break;
942 7 : default:
943 14 : plumed_error() << "cannot interpret cmd(\"" << word << "\"). check plumed developers manual to see the available commands.";
944 : break;
945 : }
946 : }
947 :
948 1352389 : } catch (...) {
949 166 : if(log.isOpen()) {
950 : try {
951 92 : log<<"\n################################################################################\n";
952 92 : log<<Tools::concatenateExceptionMessages();
953 92 : log<<"\n################################################################################\n";
954 92 : log.flush();
955 0 : } catch(...) {
956 : // ignore errors here.
957 : // in any case, we are rethrowing this below
958 0 : }
959 : }
960 166 : throw;
961 166 : }
962 1352057 : }
963 :
964 : ////////////////////////////////////////////////////////////////////////
965 :
966 1318 : void PlumedMain::init() {
967 : // check that initialization just happens once
968 1318 : initialized=true;
969 1318 : if(!log.isOpen()) {
970 128 : log.link(stdout);
971 : }
972 1318 : log<<"PLUMED is starting\n";
973 3954 : log<<"Version: "<<config::getVersionLong()<<" (git: "<<config::getVersionGit()<<") "
974 5272 : <<"compiled on " <<config::getCompilationDate() << " at " << config::getCompilationTime() << "\n";
975 1318 : log<<"Please cite these papers when using PLUMED ";
976 2636 : log<<cite("The PLUMED consortium, Nat. Methods 16, 670 (2019)");
977 2636 : log<<cite("Tribello, Bonomi, Branduardi, Camilloni, and Bussi, Comput. Phys. Commun. 185, 604 (2014)");
978 1318 : log<<"\n";
979 1318 : log<<"For further information see the PLUMED web page at http://www.plumed.org\n";
980 1318 : log<<"Root: "<<config::getPlumedRoot()<<"\n";
981 1318 : log<<"LibraryPath: "<<config::getLibraryPath()<<"\n";
982 2636 : log<<"For installed feature, see "<<config::getPlumedRoot() + "/src/config/config.txt\n";
983 1318 : log.printf("Molecular dynamics engine: %s\n",MDEngine.c_str());
984 1318 : log.printf("Precision of reals: %d\n",passtools->getRealPrecision());
985 2390 : log.printf("Running over %d %s\n",comm.Get_size(),(comm.Get_size()>1?"nodes":"node"));
986 1318 : log<<"Number of threads: "<<OpenMP::getNumThreads()<<"\n";
987 1318 : log<<"Cache line size: "<<OpenMP::getCachelineSize()<<"\n";
988 4892 : for(const auto & pp : inputs ) {
989 3574 : plumed_assert(pp);
990 3574 : DomainDecomposition* dd=pp->castToDomainDecomposition();
991 3574 : if ( dd ) {
992 1222 : log.printf("Number of atoms: %d\n",dd->getNumberOfAtoms());
993 : }
994 : }
995 1318 : if(grex) {
996 208 : log.printf("GROMACS-like replica exchange is on\n");
997 : }
998 1318 : log.printf("File suffix: %s\n",getSuffix().c_str());
999 1318 : if(plumedDat.length()>0) {
1000 1031 : readInputFile(plumedDat);
1001 : plumedDat="";
1002 : }
1003 1318 : setUnits( passtools->usingNaturalUnits, passtools->units );
1004 1318 : ActionToPutData* ts = actionSet.selectWithLabel<ActionToPutData*>("timestep");
1005 1318 : if(ts) {
1006 1049 : log.printf("Timestep: %f\n",(ts->copyOutput(0))->get());
1007 : }
1008 1318 : ActionToPutData* kb = actionSet.selectWithLabel<ActionToPutData*>("kBT");
1009 1318 : if(kb) {
1010 61 : log.printf("KbT: %f\n",(kb->copyOutput(0))->get());
1011 : } else {
1012 1257 : log.printf("KbT has not been set by the MD engine\n");
1013 1257 : log.printf("It should be set by hand where needed\n");
1014 : }
1015 1318 : log<<"Relevant bibliography:\n";
1016 1318 : log<<citations;
1017 1318 : log<<"Please read and cite where appropriate!\n";
1018 1318 : log<<"Finished setup\n";
1019 1318 : }
1020 :
1021 52105 : void PlumedMain::setupInterfaceActions() {
1022 52105 : inputs.clear();
1023 52105 : std::vector<ActionForInterface*> ap=actionSet.select<ActionForInterface*>();
1024 421469 : for(unsigned i=0; i<ap.size(); ++i) {
1025 369364 : if( ap[i]->getName()=="ENERGY" || ap[i]->getDependencies().size()==0 ) {
1026 141169 : inputs.push_back( ap[i] );
1027 : }
1028 : }
1029 52105 : }
1030 :
1031 1054 : void PlumedMain::readInputFile(const std::string & str) {
1032 1054 : plumed_assert(initialized);
1033 1054 : log<<"FILE: "<<str<<"\n";
1034 1054 : IFile ifile;
1035 1054 : ifile.link(*this);
1036 1054 : ifile.open(str);
1037 1054 : ifile.allowNoEOL();
1038 1054 : readInputFile(ifile);
1039 1054 : log<<"END FILE: "<<str<<"\n";
1040 1054 : log.flush();
1041 :
1042 1054 : }
1043 :
1044 1058 : void PlumedMain::readInputFile(IFile & ifile) {
1045 : std::vector<std::string> words;
1046 19726 : while(Tools::getParsedLine(ifile,words) && !endPlumed) {
1047 18668 : readInputWords(words,false);
1048 : }
1049 1058 : endPlumed=false;
1050 1058 : pilots=actionSet.select<ActionPilot*>();
1051 1058 : setupInterfaceActions();
1052 1058 : }
1053 :
1054 10682 : void PlumedMain::readInputLine(const std::string & str, const bool& before_init) {
1055 10682 : if( !before_init ) {
1056 985 : plumed_assert(initialized);
1057 : }
1058 10682 : if(str.empty()) {
1059 0 : return;
1060 : }
1061 10682 : std::vector<std::string> words=Tools::getWords(str);
1062 10682 : if( before_init ) {
1063 9697 : plumed_assert( citations.empty() );
1064 : }
1065 10682 : citations.clear();
1066 10682 : readInputWords(words,before_init);
1067 10642 : if(!citations.empty()) {
1068 3 : log<<"Relevant bibliography:\n";
1069 3 : log<<citations;
1070 3 : log<<"Please read and cite where appropriate!\n";
1071 : }
1072 10682 : }
1073 :
1074 4 : void PlumedMain::readInputLines(const std::string & str) {
1075 4 : plumed_assert(initialized);
1076 4 : if(str.empty()) {
1077 0 : return;
1078 : }
1079 :
1080 4 : log<<"FILE: (temporary)\n";
1081 :
1082 : // Open a temporary file
1083 4 : auto fp=std::tmpfile();
1084 4 : plumed_assert(fp);
1085 :
1086 : // make sure file is closed (and thus deleted) also if an exception occurs
1087 : auto deleter=[](auto fp) {
1088 4 : std::fclose(fp);
1089 : };
1090 : std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
1091 :
1092 4 : auto ret=std::fputs(str.c_str(),fp);
1093 4 : plumed_assert(ret!=EOF);
1094 :
1095 4 : std::rewind(fp);
1096 :
1097 4 : IFile ifile;
1098 4 : ifile.link(*this);
1099 4 : ifile.link(fp);
1100 4 : ifile.allowNoEOL();
1101 :
1102 4 : readInputFile(ifile);
1103 4 : log<<"END FILE: (temporary)\n";
1104 4 : }
1105 :
1106 51274 : void PlumedMain::readInputWords(const std::vector<std::string> & words, const bool& before_init) {
1107 51274 : if( !before_init ) {
1108 41577 : plumed_assert(initialized);
1109 : }
1110 51274 : if(words.empty()) {
1111 : return;
1112 51102 : } else if(words[0]=="_SET_SUFFIX") {
1113 3 : plumed_assert(words.size()==2);
1114 : setSuffix(words[1]);
1115 : } else {
1116 51099 : std::vector<std::string> interpreted(words);
1117 51099 : Tools::interpretLabel(interpreted);
1118 102143 : auto action=actionRegister().create(dlloader.getHandles(),ActionOptions(*this,interpreted));
1119 51044 : if(!action) {
1120 : std::string msg;
1121 : msg ="ERROR\nI cannot understand line:";
1122 0 : for(unsigned i=0; i<interpreted.size(); ++i) {
1123 0 : msg+=" "+interpreted[i];
1124 : }
1125 : msg+="\nMaybe a missing space or a typo?";
1126 0 : log << msg;
1127 0 : log.flush();
1128 0 : plumed_merror(msg);
1129 : }
1130 51044 : action->checkRead();
1131 51044 : actionSet.emplace_back(std::move(action));
1132 51099 : };
1133 :
1134 51047 : pilots=actionSet.select<ActionPilot*>();
1135 51047 : setupInterfaceActions();
1136 : }
1137 :
1138 : ////////////////////////////////////////////////////////////////////////
1139 :
1140 0 : void PlumedMain::exit(int c) {
1141 0 : comm.Abort(c);
1142 0 : }
1143 :
1144 51097 : Log& PlumedMain::getLog() {
1145 51097 : return log;
1146 : }
1147 :
1148 280128 : void PlumedMain::calc() {
1149 280128 : prepareCalc();
1150 280112 : performCalc();
1151 280110 : }
1152 :
1153 287079 : void PlumedMain::prepareCalc() {
1154 287079 : prepareDependencies();
1155 287079 : shareData();
1156 287063 : }
1157 :
1158 : //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1159 : // here we have the main steps in "calc()"
1160 : // they can be called individually, but the standard thing is to
1161 : // traverse them in this order:
1162 287406 : void PlumedMain::prepareDependencies() {
1163 :
1164 : // Stopwatch is stopped when sw goes out of scope
1165 287406 : auto sw=stopwatch.startStop("1 Prepare dependencies");
1166 :
1167 : // activate all the actions which are on step
1168 : // activation is recursive and enables also the dependencies
1169 : // before doing that, the prepare() method is called to see if there is some
1170 : // new/changed dependency (up to now, only useful for dependences on virtual atoms,
1171 : // which can be dynamically changed).
1172 :
1173 : // First switch off all actions
1174 4677958 : for(const auto & p : actionSet) {
1175 4390552 : p->deactivate();
1176 : }
1177 :
1178 : // for optimization, an "active" flag remains false if no action at all is active
1179 287406 : active=false;
1180 1730965 : for(unsigned i=0; i<pilots.size(); ++i) {
1181 1443559 : if(pilots[i]->onStep()) {
1182 1350308 : pilots[i]->activate();
1183 1350308 : active=true;
1184 : }
1185 : };
1186 :
1187 : // This stops the driver calculation if there is not a read action
1188 287406 : if( !active && !inputsAreActive() ) {
1189 2 : stopFlag.set(int(1));
1190 : }
1191 :
1192 : // also, if one of them is the total energy, tell to atoms that energy should be collected
1193 4677958 : for(const auto & p : actionSet) {
1194 4390552 : if(p->isActive()) {
1195 2648577 : if(p->checkNeedsGradients()) {
1196 172 : p->setOption("GRADIENTS");
1197 : }
1198 : }
1199 : }
1200 :
1201 287406 : }
1202 :
1203 1763 : bool PlumedMain::inputsAreActive() const {
1204 5178 : for(const auto & ip : inputs) {
1205 5122 : if( ip->onStep() ) {
1206 : return true;
1207 : }
1208 : }
1209 : return false;
1210 : }
1211 :
1212 114 : void PlumedMain::shareAll() {
1213 456 : for(const auto & ip : inputs) {
1214 342 : ip->shareAll();
1215 : }
1216 114 : }
1217 :
1218 287163 : void PlumedMain::shareData() {
1219 : // atom positions are shared (but only if there is something to do)
1220 287163 : if(!active) {
1221 1703 : return;
1222 : }
1223 : // Stopwatch is stopped when sw goes out of scope
1224 285460 : auto sw=stopwatch.startStop("2 Sharing data");
1225 752260 : for(const auto & ip : inputs) {
1226 466816 : ip->share();
1227 : }
1228 285460 : }
1229 :
1230 7011 : void PlumedMain::performCalcNoUpdate() {
1231 7011 : waitData();
1232 7011 : justCalculate();
1233 7011 : backwardPropagate();
1234 7011 : resetInputs();
1235 7011 : }
1236 :
1237 10 : void PlumedMain::performCalcNoForces() {
1238 10 : waitData();
1239 10 : justCalculate();
1240 10 : }
1241 :
1242 280122 : void PlumedMain::performCalc() {
1243 280122 : waitData();
1244 280122 : justCalculate();
1245 280122 : backwardPropagate();
1246 280120 : update();
1247 280120 : resetInputs();
1248 280120 : }
1249 :
1250 287257 : void PlumedMain::waitData() {
1251 287257 : if(!active) {
1252 1703 : return;
1253 : }
1254 : // Stopwatch is stopped when sw goes out of scope
1255 285554 : auto sw=stopwatch.startStop("3 Waiting for data");
1256 752672 : for(const auto & ip : inputs) {
1257 467118 : if( ip->isActive() && ip->hasBeenSet() ) {
1258 168653 : ip->wait();
1259 298465 : } else if( ip->isActive() ) {
1260 8084 : ip->warning("input requested but this quantity has not been set");
1261 : }
1262 : }
1263 285554 : }
1264 :
1265 287257 : void PlumedMain::justCalculate() {
1266 287257 : if(!active) {
1267 1703 : return;
1268 : }
1269 : // Stopwatch is stopped when sw goes out of scope
1270 285554 : auto sw=stopwatch.startStop("4 Calculating (forward loop)");
1271 285554 : bias=0.0;
1272 285554 : work=0.0;
1273 :
1274 : // Check the input actions to determine if we need to calculate constants that
1275 : // depend on masses and charges
1276 : bool firststep=false;
1277 752672 : for(const auto & ip : inputs) {
1278 467118 : if( ip->firststep ) {
1279 : firststep=true;
1280 : }
1281 : }
1282 285554 : if( firststep ) {
1283 4578 : for(const auto & ip : inputs) {
1284 3392 : ip->firststep=false;
1285 : }
1286 : }
1287 :
1288 : int iaction=0;
1289 : // calculate the active actions in order (assuming *backward* dependence)
1290 4645108 : for(const auto & pp : actionSet) {
1291 : Action* p(pp.get());
1292 4359554 : plumed_assert(p);
1293 : try {
1294 4359554 : if(p->isActive()) {
1295 : // Stopwatch is stopped when sw goes out of scope.
1296 : // We explicitly declare a Stopwatch::Handler here to allow for conditional initialization.
1297 2646582 : Stopwatch::Handler sw;
1298 2646582 : if(detailedTimers) {
1299 2481 : auto actionNumberLabel=std::to_string(iaction);
1300 2481 : const unsigned m=actionSet.size();
1301 : unsigned k=0;
1302 : unsigned n=1;
1303 7443 : while(n<m) {
1304 4962 : n*=10;
1305 4962 : k++;
1306 : }
1307 2481 : auto spaces=std::string(k-actionNumberLabel.length(),' ');
1308 2481 : sw=stopwatch.startStop("4A " + spaces + actionNumberLabel+" "+p->getLabel());
1309 : }
1310 2646582 : ActionWithValue*av=p->castToActionWithValue();
1311 2646582 : ActionAtomistic*aa=p->castToActionAtomistic();
1312 : {
1313 2646582 : if(av) {
1314 2362861 : av->clearInputForces();
1315 : }
1316 2646582 : if(av) {
1317 2362861 : av->clearDerivatives();
1318 : }
1319 2646582 : if( av && av->calculateOnUpdate() ) {
1320 : continue ;
1321 : }
1322 : }
1323 : {
1324 2642567 : if(aa)
1325 483344 : if(aa->isActive()) {
1326 483344 : aa->retrieveAtoms();
1327 : }
1328 : }
1329 2642567 : if(p->checkNumericalDerivatives()) {
1330 256 : p->calculateNumericalDerivatives();
1331 : } else {
1332 2642311 : p->calculate();
1333 : }
1334 : // This retrieves components called bias
1335 2642567 : if(av) {
1336 2358846 : bias+=av->getOutputQuantity("bias");
1337 2358846 : work+=av->getOutputQuantity("work");
1338 2358846 : av->setGradientsIfNeeded();
1339 : }
1340 : // This makes all values that depend on the (fixed) masses and charges constant
1341 2642567 : if( firststep ) {
1342 28324 : p->setupConstantValues( true );
1343 : }
1344 2642567 : ActionWithVirtualAtom*avv=p->castToActionWithVirtualAtom();
1345 2642567 : if(avv) {
1346 35094 : avv->setGradientsIfNeeded();
1347 : }
1348 2646582 : }
1349 0 : } catch(...) {
1350 0 : plumed_error_nested() << "An error happened while calculating " << p->getLabel();
1351 0 : }
1352 4355539 : iaction++;
1353 : }
1354 285554 : }
1355 :
1356 0 : void PlumedMain::justApply() {
1357 0 : backwardPropagate();
1358 0 : update();
1359 0 : resetInputs();
1360 0 : }
1361 :
1362 287133 : void PlumedMain::backwardPropagate() {
1363 287133 : if(!active) {
1364 1703 : return;
1365 : }
1366 : int iaction=0;
1367 : // Stopwatch is stopped when sw goes out of scope
1368 285430 : auto sw=stopwatch.startStop("5 Applying (backward loop)");
1369 : // apply them in reverse order
1370 4642642 : for(auto pp=actionSet.rbegin(); pp!=actionSet.rend(); ++pp) {
1371 : const auto & p(pp->get());
1372 4357214 : if(p->isActive()) {
1373 :
1374 : // Stopwatch is stopped when sw goes out of scope.
1375 : // We explicitly declare a Stopwatch::Handler here to allow for conditional initialization.
1376 2644906 : Stopwatch::Handler sw;
1377 2644906 : if(detailedTimers) {
1378 2481 : auto actionNumberLabel=std::to_string(iaction);
1379 2481 : const unsigned m=actionSet.size();
1380 : unsigned k=0;
1381 : unsigned n=1;
1382 7443 : while(n<m) {
1383 4962 : n*=10;
1384 4962 : k++;
1385 : }
1386 2481 : auto spaces=std::string(k-actionNumberLabel.length(),' ');
1387 2481 : sw=stopwatch.startStop("5A " + spaces + actionNumberLabel+" "+p->getLabel());
1388 : }
1389 :
1390 2644906 : p->apply();
1391 2644906 : }
1392 4357212 : iaction++;
1393 : }
1394 :
1395 : // Stopwatch is stopped when sw goes out of scope.
1396 : // We explicitly declare a Stopwatch::Handler here to allow for conditional initialization.
1397 285428 : Stopwatch::Handler sw1;
1398 285428 : if(detailedTimers) {
1399 113 : sw1=stopwatch.startStop("5B Update forces");
1400 : }
1401 285430 : }
1402 :
1403 280199 : void PlumedMain::update() {
1404 280199 : if(!active) {
1405 1703 : return;
1406 : }
1407 :
1408 : // Stopwatch is stopped when sw goes out of scope
1409 278496 : auto sw=stopwatch.startStop("6 Update");
1410 :
1411 : // update step (for statistics, etc)
1412 278496 : updateFlags.push(true);
1413 4418423 : for(const auto & p : actionSet) {
1414 4139927 : p->beforeUpdate();
1415 6689824 : if(p->isActive() && p->checkUpdate() && updateFlagsTop()) {
1416 2549877 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(p.get());
1417 2549877 : if( av && av->calculateOnUpdate() ) {
1418 4015 : p->prepare();
1419 4015 : p->calculate();
1420 : } else {
1421 2545862 : p->update();
1422 : }
1423 : }
1424 : }
1425 556996 : while(!updateFlags.empty()) {
1426 : updateFlags.pop();
1427 : }
1428 : if(!updateFlags.empty()) {
1429 : plumed_merror("non matching changes in the update flags");
1430 : }
1431 : // Check that no action has told the calculation to stop
1432 278496 : if(stopNow) {
1433 65 : if(stopFlag) {
1434 65 : stopFlag.set(int(1));
1435 : } else {
1436 0 : plumed_merror("your md code cannot handle plumed stop events - add a call to plumed.comm(stopFlag,stopCondition)");
1437 : }
1438 : }
1439 :
1440 : // flush by default every 10000 steps
1441 : // hopefully will not affect performance
1442 : // also if receive checkpointing signal
1443 278496 : if(step%10000==0||doCheckPoint) {
1444 1231 : fflush();
1445 1231 : log.flush();
1446 53236 : for(const auto & p : actionSet) {
1447 52005 : p->fflush();
1448 : }
1449 : }
1450 278496 : }
1451 :
1452 42 : void PlumedMain::load(const std::string& fileName) {
1453 42 : if(DLLoader::installed()) {
1454 42 : std::string libName=fileName;
1455 42 : size_t n=libName.find_last_of(".");
1456 43 : std::string extension="";
1457 42 : std::string base=libName;
1458 42 : if(n!=std::string::npos && n<libName.length()-1) {
1459 84 : extension=libName.substr(n+1);
1460 : }
1461 42 : if(n!=std::string::npos && n<libName.length()) {
1462 84 : base=libName.substr(0,n);
1463 : }
1464 :
1465 42 : if(extension=="cpp") {
1466 46 : libName="./"+base+"."+config::getVersionLong()+"."+config::getSoExt();
1467 : // full path command, including environment setup
1468 : // this will work even if plumed is not in the execution path or if it has been
1469 : // installed with a name different from "plumed"
1470 46 : std::string cmd=config::getEnvCommand()+" \""+config::getPlumedRoot()+"\"/scripts/mklib.sh -n -o "+libName+" "+fileName;
1471 :
1472 23 : if(std::getenv("PLUMED_LOAD_ACTION_DEBUG")) {
1473 0 : log<<"Executing: "<<cmd;
1474 : } else {
1475 23 : log<<"Compiling: "<<fileName<<" to "<<libName;
1476 : }
1477 :
1478 23 : if(comm.Get_size()>0) {
1479 23 : log<<" (only on master node)";
1480 : }
1481 23 : log<<"\n";
1482 :
1483 : // On MPI process (intracomm), we use Get_rank to make sure a single process does the compilation
1484 : // Processes from multiple replicas might simultaneously do the compilation.
1485 23 : if(comm.Get_rank()==0) {
1486 23 : static Tools::CriticalSectionWithKey<std::string> section;
1487 : // This is only locking commands that are running with identical arguments.
1488 : // It is not necessary for correctness (a second mklib would just result in a no op since
1489 : // the library is already there, even if running simultaneously).
1490 : // It however decreases the system load if many threads are used.
1491 : auto s=section.startStop(cmd);
1492 23 : int ret=std::system(cmd.c_str());
1493 23 : if(ret!=0) {
1494 2 : plumed_error() <<"An error happened while executing command "<<cmd<<"\n";
1495 : }
1496 23 : }
1497 22 : comm.Barrier();
1498 : } else {
1499 38 : libName=base+"."+config::getSoExt();
1500 : }
1501 :
1502 : // If we have multiple threads (each holding a Plumed object), each of them
1503 : // will load the library, but each of them will only see actions registered
1504 : // from the owned library
1505 41 : auto *p=dlloader.load(libName);
1506 42 : log<<"Loading shared library "<<libName.c_str()<<" at "<<p<<"\n";
1507 41 : log<<"Here is the list of new actions\n";
1508 41 : log<<"\n";
1509 82 : for(const auto & a : actionRegister().getKeysWithDLHandle(p)) {
1510 41 : log<<a<<"\n";
1511 41 : }
1512 41 : log<<"\n";
1513 : } else {
1514 0 : plumed_error()<<"While loading library "<< fileName << " loading was not enabled, please check if dlopen was found at configure time";
1515 : }
1516 41 : }
1517 :
1518 287131 : void PlumedMain::resetInputs() {
1519 759008 : for(const auto & ip : inputs) {
1520 471877 : if( ip->isActive() && ip->hasBeenSet() ) {
1521 168377 : ip->reset();
1522 : }
1523 : }
1524 287131 : }
1525 :
1526 7684 : double PlumedMain::getBias() const {
1527 7684 : return bias;
1528 : }
1529 :
1530 450 : double PlumedMain::getWork() const {
1531 450 : return work;
1532 : }
1533 :
1534 82 : FILE* PlumedMain::fopen(const char *path, const char *mode) {
1535 82 : std::string mmode(mode);
1536 83 : std::string ppath(path);
1537 82 : std::string suffix(getSuffix());
1538 82 : std::string ppathsuf=ppath+suffix;
1539 82 : FILE*fp=std::fopen(const_cast<char*>(ppathsuf.c_str()),const_cast<char*>(mmode.c_str()));
1540 82 : if(!fp) {
1541 1 : fp=std::fopen(const_cast<char*>(ppath.c_str()),const_cast<char*>(mmode.c_str()));
1542 : }
1543 84 : plumed_massert(fp,"file " + ppath + " cannot be found");
1544 81 : return fp;
1545 : }
1546 :
1547 99 : int PlumedMain::fclose(FILE*fp) {
1548 99 : return std::fclose(fp);
1549 : }
1550 :
1551 4886 : std::string PlumedMain::cite(const std::string&item) {
1552 4886 : return citations.cite(item);
1553 : }
1554 :
1555 1792 : void PlumedMain::fflush() {
1556 5400 : for(const auto & p : files) {
1557 3608 : p->flush();
1558 : }
1559 1792 : }
1560 :
1561 5217 : void PlumedMain::insertFile(FileBase&f) {
1562 5217 : files.insert(&f);
1563 5217 : }
1564 :
1565 5502 : void PlumedMain::eraseFile(FileBase&f) {
1566 5502 : files.erase(&f);
1567 5502 : }
1568 :
1569 65 : void PlumedMain::stop() {
1570 65 : stopNow=true;
1571 65 : }
1572 :
1573 971 : void PlumedMain::runJobsAtEndOfCalculation() {
1574 47549 : for(const auto & p : actionSet) {
1575 46578 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(p.get());
1576 46578 : if( av && av->calculateOnUpdate() ) {
1577 414 : p->activate();
1578 : }
1579 : }
1580 47549 : for(const auto & p : actionSet) {
1581 46578 : ActionPilot* ap=dynamic_cast<ActionPilot*>(p.get());
1582 46578 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(p.get());
1583 46578 : if( av && av->calculateOnUpdate() ) {
1584 414 : p->calculate();
1585 46164 : } else if( ap && !av && ap->getStride()==0 ) {
1586 70 : p->update();
1587 : } else {
1588 46094 : p->runFinalJobs();
1589 : }
1590 : }
1591 971 : }
1592 :
1593 8807971 : unsigned PlumedMain::increaseReferenceCounter() noexcept {
1594 8807971 : return ++referenceCounter;
1595 : }
1596 :
1597 8806867 : unsigned PlumedMain::decreaseReferenceCounter() noexcept {
1598 8806867 : return --referenceCounter;
1599 : }
1600 :
1601 42 : unsigned PlumedMain::useCountReferenceCounter() const noexcept {
1602 42 : return referenceCounter;
1603 : }
1604 :
1605 60 : bool PlumedMain::valueExists( const std::string& name ) const {
1606 240 : for(const auto & p : inputs) {
1607 240 : if( p->getLabel()==name ) {
1608 : return true;
1609 : }
1610 : }
1611 : return false;
1612 : }
1613 :
1614 463816 : void PlumedMain::setInputValue( const std::string& name, const unsigned& start, const unsigned& stride, const TypesafePtr & val ) {
1615 : bool found=false;
1616 1257608 : for(const auto & pp : inputs) {
1617 1257608 : if( pp->setValuePointer( name, val ) ) {
1618 463812 : pp->setStart(name, start);
1619 463812 : pp->setStride(name, stride);
1620 : found=true;
1621 : break;
1622 : }
1623 : }
1624 0 : plumed_massert( found, "found no action to set named " + name );
1625 463812 : }
1626 :
1627 291183 : void PlumedMain::setInputForce( const std::string& name, const TypesafePtr & val ) {
1628 : bool found=false;
1629 782363 : for(const auto & pp : inputs) {
1630 782363 : if( pp->setForcePointer( name, val ) ) {
1631 : found=true;
1632 : break;
1633 : }
1634 : }
1635 291165 : plumed_massert( found, "found no action to set named " + name );
1636 291165 : }
1637 :
1638 1355 : void PlumedMain::setUnits( const bool& natural, const Units& u ) {
1639 1355 : passtools->usingNaturalUnits = natural;
1640 1355 : passtools->units=u;
1641 1355 : std::vector<ActionToPutData*> idata = actionSet.select<ActionToPutData*>();
1642 10103 : for(const auto & ip : idata) {
1643 8748 : ip->updateUnits( passtools.get() );
1644 : }
1645 50928 : for(const auto & p : actionSet ) {
1646 49573 : p->resetStoredTimestep();
1647 : }
1648 1355 : }
1649 :
1650 287076 : void PlumedMain::startStep() {
1651 758745 : for(const auto & ip : inputs) {
1652 471669 : ip->resetForStepStart();
1653 : }
1654 287076 : }
1655 :
1656 114 : void PlumedMain::writeBinary(std::ostream&o)const {
1657 456 : for(const auto & ip : inputs) {
1658 342 : ip->writeBinary(o);
1659 : }
1660 114 : }
1661 :
1662 114 : void PlumedMain::readBinary(std::istream&i) {
1663 456 : for(const auto & ip : inputs) {
1664 342 : ip->readBinary(i);
1665 : }
1666 114 : }
1667 :
1668 40 : void PlumedMain::setEnergyValue( const std::string& name ) {
1669 40 : name_of_energy = name;
1670 40 : }
1671 :
1672 8634 : int PlumedMain::getRealPrecision() const {
1673 8634 : return passtools->getRealPrecision();
1674 : }
1675 :
1676 5087 : bool PlumedMain::usingNaturalUnits() const {
1677 5087 : return passtools->usingNaturalUnits;
1678 : }
1679 :
1680 15620289 : const Units& PlumedMain::getUnits() {
1681 15620289 : return passtools->units;
1682 : }
1683 :
1684 4 : PlumedMain::DeprecatedAtoms& PlumedMain::getAtoms() {
1685 4 : return datoms;
1686 : }
1687 :
1688 7222 : void PlumedMain::plumedQuantityToMD( const std::string& unit, const double& eng, const TypesafePtr & m) const {
1689 7222 : passtools->double2MD( eng/passtools->getUnitConversion(unit),m );
1690 7222 : }
1691 :
1692 0 : double PlumedMain::MDQuantityToPLUMED( const std::string& unit, const TypesafePtr & m) const {
1693 0 : double x=passtools->MD2double(m);
1694 0 : return x*passtools->getUnitConversion(unit);
1695 : }
1696 :
1697 1 : double PlumedMain::DeprecatedAtoms::getKBoltzmann() const {
1698 1 : if( plumed.usingNaturalUnits() ) {
1699 : return 1.0;
1700 : }
1701 1 : return kBoltzmann/plumed.getUnits().getEnergy();
1702 : }
1703 :
1704 1 : double PlumedMain::DeprecatedAtoms::getKbT() const {
1705 1 : ActionForInterface* kb=plumed.getActionSet().selectWithLabel<ActionForInterface*>("kBT");
1706 1 : if( kb ) {
1707 1 : return (kb->copyOutput(0))->get();
1708 : }
1709 : return 0.0;
1710 : }
1711 :
1712 1 : int PlumedMain::DeprecatedAtoms::getNatoms() const {
1713 1 : std::vector<ActionToPutData*> inputs=plumed.getActionSet().select<ActionToPutData*>();
1714 1 : for(const auto & pp : inputs ) {
1715 2 : if( pp->getRole()=="x" ) {
1716 1 : return (pp->copyOutput(0))->getShape()[0];
1717 : }
1718 : }
1719 : return 0;
1720 : }
1721 :
1722 1 : bool PlumedMain::DeprecatedAtoms::usingNaturalUnits() const {
1723 1 : return plumed.usingNaturalUnits();
1724 : }
1725 :
1726 0 : void PlumedMain::DeprecatedAtoms::setCollectEnergy(bool b) const {
1727 0 : plumed.readInputWords( Tools::getWords(plumed.MDEngine + "_energy: ENERGY"), false );
1728 0 : plumed.setEnergyValue( plumed.MDEngine + "_energy" );
1729 0 : }
1730 :
1731 0 : double PlumedMain::DeprecatedAtoms::getEnergy() const {
1732 0 : ActionToPutData* av = plumed.getActionSet().selectWithLabel<ActionToPutData*>( plumed.MDEngine + "_energy" );
1733 0 : return (av->copyOutput(0))->get();
1734 : }
1735 :
1736 5 : void PlumedMain::activateParseOnlyMode() {
1737 5 : doParseOnly=true;
1738 5 : }
1739 :
1740 2260 : bool PlumedMain::parseOnlyMode() const {
1741 2260 : return doParseOnly;
1742 : }
1743 :
1744 97 : void PlumedMain::getKeywordsForAction( const std::string& action, Keywords& keys ) const {
1745 97 : actionRegister().getKeywords( dlloader.getHandles(), action, keys );
1746 97 : }
1747 :
1748 : #ifdef __PLUMED_HAS_PYTHON
1749 : // This is here to stop cppcheck throwing an error
1750 : #endif
1751 :
1752 : #ifdef __PLUMED_HAS_DLADDR
1753 : // This is here to stop cppcheck throwing an error
1754 : #endif
1755 :
1756 : }
1757 :
1758 : //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|