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 "Tools.h"
23 : #include "AtomNumber.h"
24 : #include "Exception.h"
25 : #include "IFile.h"
26 : #include "lepton/Lepton.h"
27 : #include <cstring>
28 : #include <iostream>
29 : #include <map>
30 : #include <iomanip>
31 : #include <filesystem>
32 :
33 : namespace PLMD {
34 :
35 : template<class T>
36 21985274 : bool Tools::convertToAny(const std::string & str,T & t) {
37 41484550 : std::istringstream istr(str.c_str());
38 21985274 : bool ok=static_cast<bool>(istr>>t);
39 21985274 : if(!ok) return false;
40 : std::string remaining;
41 21942574 : istr>>remaining;
42 21942574 : return remaining.length()==0;
43 21985274 : }
44 :
45 2480526 : bool Tools::convertNoexcept(const std::string & str,int & t) {
46 2480526 : return convertToInt(str,t);
47 : }
48 :
49 3 : bool Tools::convertNoexcept(const std::string & str,long int & t) {
50 3 : return convertToInt(str,t);
51 : }
52 :
53 87 : bool Tools::convertNoexcept(const std::string & str,long long int & t) {
54 87 : return convertToInt(str,t);
55 : }
56 :
57 3989224 : bool Tools::convertNoexcept(const std::string & str,unsigned & t) {
58 3989224 : return convertToInt(str,t);
59 : }
60 :
61 93 : bool Tools::convertNoexcept(const std::string & str,long unsigned & t) {
62 93 : return convertToInt(str,t);
63 : }
64 :
65 54 : bool Tools::convertNoexcept(const std::string & str,long long unsigned & t) {
66 54 : return convertToInt(str,t);
67 : }
68 :
69 1117690 : bool Tools::convertNoexcept(const std::string & str,AtomNumber &a) {
70 : // Note: AtomNumber's are NOT converted as int, so as to
71 : // avoid using lepton conversions.
72 : unsigned i;
73 1117690 : bool r=convertToAny(str,i);
74 1117690 : if(r) a.setSerial(i);
75 1117690 : return r;
76 : }
77 :
78 : template<class T>
79 6469987 : bool Tools::convertToInt(const std::string & str,T & t) {
80 : // First try standard conversion
81 6469987 : if(convertToAny(str,t)) return true;
82 : // Then use lepton
83 : try {
84 2008031 : double r=lepton::Parser::parse(str).evaluate(lepton::Constants());
85 :
86 : // now sanity checks on the resulting number
87 :
88 : // it should not overflow the requested int type:
89 : // (see https://stackoverflow.com/a/526092)
90 2008031 : if(r>std::nextafter(std::numeric_limits<T>::max(), 0)) return false;
91 2008031 : if(r<std::nextafter(std::numeric_limits<T>::min(), 0)) return false;
92 :
93 : // do the actual conversion
94 2008031 : auto tmp=static_cast<T>(std::round(r));
95 :
96 : // it should be *very close* to itself if converted back to double
97 2008031 : double diff= r-static_cast<double>(tmp);
98 2008031 : if(diff*diff > 1e-20) return false;
99 : // this is to accomodate small numerical errors and allow e.g. exp(log(7)) to be integer
100 :
101 : // it should be change if incremented or decremented by one (see https://stackoverflow.com/a/43656140)
102 2008027 : if(r == static_cast<double>(tmp-1)) return false;
103 2008026 : if(r == static_cast<double>(tmp+1)) return false;
104 :
105 : // everything is fine, then store in t
106 2008026 : t=tmp;
107 2008026 : return true;
108 0 : } catch(const PLMD::lepton::Exception& exc) {
109 : }
110 0 : return false;
111 : }
112 :
113 :
114 : template<class T>
115 14392125 : bool Tools::convertToReal(const std::string & str,T & t) {
116 14392125 : if(convertToAny(str,t)) return true;
117 68222 : if(str=="PI" || str=="+PI" || str=="+pi" || str=="pi") {
118 8573 : t=pi; return true;
119 17090 : } else if(str=="-PI" || str=="-pi") {
120 8486 : t=-pi; return true;
121 : }
122 : try {
123 59 : t=lepton::Parser::parse(str).evaluate(lepton::Constants());
124 24 : return true;
125 35 : } catch(const PLMD::lepton::Exception& exc) {
126 : }
127 35 : if( str.find("PI")!=std::string::npos ) {
128 0 : std::size_t pi_start=str.find_first_of("PI");
129 0 : if(str.substr(pi_start)!="PI") return false;
130 0 : std::istringstream nstr(str.substr(0,pi_start));
131 0 : T ff=0.0; bool ok=static_cast<bool>(nstr>>ff);
132 0 : if(!ok) return false;
133 0 : t=ff*pi;
134 0 : std::string remains; nstr>>remains;
135 0 : return remains.length()==0;
136 35 : } else if( str.find("pi")!=std::string::npos ) {
137 13 : std::size_t pi_start=str.find_first_of("pi");
138 26 : if(str.substr(pi_start)!="pi") return false;
139 13 : std::istringstream nstr(str.substr(0,pi_start));
140 13 : T ff=0.0; bool ok=static_cast<bool>(nstr>>ff);
141 13 : if(!ok) return false;
142 13 : t=ff*pi;
143 13 : std::string remains; nstr>>remains;
144 13 : return remains.length()==0;
145 35 : } else if(str=="NAN") {
146 0 : t=std::numeric_limits<double>::quiet_NaN();
147 0 : return true;
148 : }
149 : return false;
150 : }
151 :
152 0 : bool Tools::convertNoexcept(const std::string & str,float & t) {
153 0 : return convertToReal(str,t);
154 : }
155 :
156 14390917 : bool Tools::convertNoexcept(const std::string & str,double & t) {
157 14390917 : return convertToReal(str,t);
158 : }
159 :
160 1208 : bool Tools::convertNoexcept(const std::string & str,long double & t) {
161 1208 : return convertToReal(str,t);
162 : }
163 :
164 549107 : bool Tools::convertNoexcept(const std::string & str,std::string & t) {
165 : t=str;
166 549107 : return true;
167 : }
168 :
169 3541148 : std::vector<std::string> Tools::getWords(std::string_view line,const char* separators,int * parlevel,const char* parenthesis, const bool& delete_parenthesis) {
170 3541148 : plumed_massert(std::strlen(parenthesis)==1,"multiple parenthesis type not available");
171 3541148 : plumed_massert(parenthesis[0]=='(' || parenthesis[0]=='[' || parenthesis[0]=='{',
172 : "only ( [ { allowed as parenthesis");
173 3541148 : if(!separators) separators=" \t\n";
174 3541148 : const std::string sep(separators);
175 3541148 : char openpar=parenthesis[0];
176 : char closepar;
177 : if(openpar=='(') closepar=')';
178 3541148 : if(openpar=='[') closepar=']';
179 3541148 : if(openpar=='{') closepar='}';
180 : std::vector<std::string> words;
181 : std::string word;
182 : int parenthesisLevel=0;
183 3541148 : if(parlevel) parenthesisLevel=*parlevel;
184 290398576 : for(unsigned i=0; i<line.length(); i++) {
185 : bool found=false;
186 : bool onParenthesis=false;
187 286857428 : if( (line[i]==openpar || line[i]==closepar) && delete_parenthesis ) onParenthesis=true;
188 286857428 : if(line[i]==closepar) {
189 4120 : parenthesisLevel--;
190 4120 : plumed_assert(parenthesisLevel>=0) << "Extra closed parenthesis in '" << line << "'";
191 : }
192 1164124805 : if(parenthesisLevel==0) for(unsigned j=0; j<sep.length(); j++) if(line[i]==sep[j]) found=true;
193 : // If at parenthesis level zero (outer)
194 286857428 : if(!(parenthesisLevel==0 && (found||onParenthesis))) word.push_back(line[i]);
195 : //if(onParenthesis) word.push_back(' ');
196 286857428 : if(line[i]==openpar) parenthesisLevel++;
197 286857428 : if(found && word.length()>0) {
198 17609051 : if(!parlevel) plumed_assert(parenthesisLevel==0) << "Unmatching parenthesis in '" << line << "'";
199 17609051 : words.push_back(word);
200 : word.clear();
201 : }
202 : }
203 3541148 : if(word.length()>0) {
204 3419445 : if(!parlevel) plumed_assert(parenthesisLevel==0) << "Unmatching parenthesis in '" << line << "'";
205 3419445 : words.push_back(word);
206 : }
207 3541148 : if(parlevel) *parlevel=parenthesisLevel;
208 3541148 : return words;
209 0 : }
210 :
211 1348558 : void Tools::getWordsSimple(gch::small_vector<std::string_view> & words,std::string_view line) {
212 : words.clear();
213 1348558 : auto ptr=line.data();
214 1348558 : std::size_t size=0;
215 14520417 : for(unsigned i=0; i<line.length(); i++) {
216 13171859 : const bool is_separator=(line[i]==' ');
217 13171859 : if(!is_separator) {
218 13130198 : size++;
219 41661 : } else if(size==0) {
220 0 : ptr++;
221 : } else {
222 : words.emplace_back(ptr,size);
223 41661 : ptr=&line[i]+1;
224 41661 : size=0;
225 : }
226 : }
227 1348558 : if(size>0) {
228 : words.emplace_back(ptr,size);
229 : }
230 1348558 : }
231 :
232 19581 : bool Tools::getParsedLine(IFile& ifile,std::vector<std::string> & words, bool trimcomments) {
233 19581 : std::string line("");
234 : words.clear();
235 : bool stat;
236 : bool inside=false;
237 19581 : int parlevel=0;
238 : bool mergenext=false;
239 40104 : while((stat=ifile.getline(line))) {
240 39081 : if(trimcomments) trimComments(line);
241 39081 : trim(line);
242 39081 : if(line.length()==0) continue;
243 32549 : std::vector<std::string> w=getWords(line,NULL,&parlevel,"{",trimcomments);
244 32549 : if(!w.empty()) {
245 46134 : if(inside && *(w.begin())=="...") {
246 : inside=false;
247 1273 : if(w.size()==2) plumed_massert(w[1]==words[0],"second word in terminating \"...\" "+w[1]+" line, if present, should be equal to first word of directive: "+words[0]);
248 1273 : plumed_massert(w.size()<=2,"terminating \"...\" lines cannot consist of more than two words");
249 1273 : w.clear(); if(!trimcomments) words.push_back("...");
250 30987 : } else if(*(w.end()-1)=="...") {
251 : inside=true;
252 : w.erase(w.end()-1);
253 : };
254 : int i0=0;
255 32260 : if(mergenext && words.size()>0 && w.size()>0) {
256 128 : words[words.size()-1]+=" "+w[0];
257 : i0=1;
258 : }
259 118546 : for(unsigned i=i0; i<w.size(); ++i) words.push_back(w[i]);
260 : }
261 32549 : mergenext=(parlevel>0);
262 32549 : if(!inside)break;
263 13991 : if(!trimcomments && parlevel==0) words.push_back("@newline");
264 13991 : else if(!trimcomments) words[words.size()-1] += " @newline";
265 32549 : }
266 19581 : plumed_massert(parlevel==0,"non matching parenthesis");
267 19581 : if(words.size()>0) return true;
268 : return stat;
269 : }
270 :
271 :
272 3319889 : bool Tools::getline(FILE* fp,std::string & line) {
273 : line="";
274 : const int bufferlength=1024;
275 : char buffer[bufferlength];
276 : bool ret;
277 3402886225 : for(int i=0; i<bufferlength; i++) buffer[i]='\0';
278 3319889 : while((ret=fgets(buffer,bufferlength,fp))) {
279 3318788 : line.append(buffer);
280 3318788 : unsigned ss=std::strlen(buffer);
281 3318788 : if(ss>0) if(buffer[ss-1]=='\n') break;
282 : };
283 3319889 : if(line.length()>0) if(*(line.end()-1)=='\n') line.erase(line.end()-1);
284 3319889 : if(line.length()>0) if(*(line.end()-1)=='\r') line.erase(line.end()-1);
285 3319889 : return ret;
286 : }
287 :
288 1489989 : void Tools::trim(std::string & s) {
289 1489989 : auto n=s.find_last_not_of(" \t");
290 1489989 : if(n!=std::string::npos) s.resize(n+1);
291 1489989 : }
292 :
293 1080 : void Tools::ltrim(std::string & s) {
294 1080 : auto n=s.find_first_not_of(" \t");
295 1080 : if(n!=std::string::npos) {
296 2160 : s = s.substr(n, s.length()-n);
297 : s.shrink_to_fit();
298 : }
299 1080 : }
300 :
301 1613038 : void Tools::trimComments(std::string & s) {
302 1613038 : auto n=s.find_first_of("#");
303 1613038 : if(n!=std::string::npos) s.resize(n);
304 1613038 : }
305 :
306 2416415 : bool Tools::caseInSensStringCompare(const std::string & str1, const std::string &str2)
307 : {
308 2416415 : return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), [](char c1, char c2) {
309 7131406 : return (c1 == c2 || std::toupper(c1) == std::toupper(c2));
310 2416415 : }));
311 : }
312 :
313 615595 : bool Tools::getKey(std::vector<std::string>& line,const std::string & key,std::string & s,int rep) {
314 : s.clear();
315 2517088 : for(auto p=line.begin(); p!=line.end(); ++p) {
316 2416349 : if((*p).length()==0) continue;
317 2416349 : std::string x=(*p).substr(0,key.length());
318 2416349 : if(caseInSensStringCompare(x,key)) {
319 514856 : if((*p).length()==key.length())return false;
320 514855 : std::string tmp=(*p).substr(key.length(),(*p).length());
321 : line.erase(p);
322 : s=tmp;
323 514855 : const std::string multi("@replicas:");
324 514855 : if(rep>=0 && startWith(s,multi)) {
325 24 : s=s.substr(multi.length(),s.length());
326 24 : std::vector<std::string> words=getWords(s,"\t\n ,");
327 24 : plumed_massert(rep<static_cast<int>(words.size()),"Number of fields in " + s + " not consistent with number of replicas");
328 24 : s=words[rep];
329 24 : }
330 : return true;
331 : }
332 : };
333 : return false;
334 : }
335 :
336 48279 : void Tools::interpretRanges(std::vector<std::string>&s) {
337 : std::vector<std::string> news;
338 612316 : for(const auto & p :s) {
339 564037 : news.push_back(p);
340 564037 : size_t dash=p.find("-");
341 564708 : if(dash==std::string::npos) continue;
342 : int first;
343 6078 : if(!Tools::convertToAny(p.substr(0,dash),first)) continue;
344 2368 : int stride=1;
345 : int second;
346 2368 : size_t colon=p.substr(dash+1).find(":");
347 2368 : if(colon!=std::string::npos) {
348 195 : if(!Tools::convertToAny(p.substr(dash+1).substr(0,colon),second) ||
349 260 : !Tools::convertToAny(p.substr(dash+1).substr(colon+1),stride)) continue;
350 : } else {
351 4606 : if(!Tools::convertToAny(p.substr(dash+1),second)) continue;
352 : }
353 2368 : news.resize(news.size()-1);
354 2368 : if(first<=second) {
355 2367 : plumed_massert(stride>0,"interpreting ranges "+ p + ", stride should be positive");
356 566151 : for(int i=first; i<=second; i+=stride) {
357 : std::string ss;
358 563784 : convert(i,ss);
359 563784 : news.push_back(ss);
360 : }
361 : } else {
362 1 : plumed_massert(stride<0,"interpreting ranges "+ p + ", stride should be positive");
363 3 : for(int i=first; i>=second; i+=stride) {
364 : std::string ss;
365 2 : convert(i,ss);
366 2 : news.push_back(ss);
367 : }
368 : }
369 : }
370 48279 : s=news;
371 48279 : }
372 :
373 72909 : void Tools::interpretLabel(std::vector<std::string>&s) {
374 72909 : if(s.size()<2)return;
375 72570 : std::string s0=s[0];
376 72570 : unsigned l=s0.length();
377 72570 : if(l<1) return;
378 72570 : if(s0[l-1]==':') {
379 : s[0]=s[1];
380 136080 : s[1]="LABEL="+s0.substr(0,l-1);
381 : }
382 72570 : std::transform(s[0].begin(), s[0].end(), s[0].begin(), ::toupper);
383 : }
384 :
385 9142 : std::vector<std::string> Tools::ls(const std::string&d) {
386 : std::vector<std::string> result;
387 146770 : for (auto const& dir_entry : std::filesystem::directory_iterator{d}) {
388 358032 : result.push_back(dir_entry.path().filename());
389 : }
390 9142 : return result;
391 0 : }
392 :
393 4362 : void Tools::stripLeadingAndTrailingBlanks( std::string& str ) {
394 4362 : std::size_t first=str.find_first_not_of(' ');
395 4362 : std::size_t last=str.find_last_not_of(' ');
396 8687 : if( first<=last && first!=std::string::npos) str=str.substr(first,last+1);
397 4362 : }
398 :
399 13069 : std::string Tools::extension(const std::string&s) {
400 13069 : size_t n=s.find_last_of(".");
401 : std::string ext;
402 13069 : if(n!=std::string::npos && n+1<s.length() && n+5>=s.length()) {
403 9289 : ext=s.substr(n+1);
404 9289 : if(ext.find("/")!=std::string::npos) ext="";
405 9289 : std::string base=s.substr(0,n);
406 9289 : if(base.length()==0) ext="";
407 9289 : if(base.length()>0 && base[base.length()-1]=='/') ext="";
408 : }
409 13069 : return ext;
410 : }
411 :
412 0 : double Tools::bessel0( const double& val ) {
413 0 : if (std::abs(val)<3.75) {
414 0 : double y = Tools::fastpow( val/3.75, 2 );
415 0 : return 1 + y*(3.5156229 +y*(3.0899424 + y*(1.2067492+y*(0.2659732+y*(0.0360768+y*0.0045813)))));
416 : }
417 0 : double ax=std::abs(val), y=3.75/ax, bx=std::exp(ax)/std::sqrt(ax);
418 0 : ax=0.39894228+y*(0.01328592+y*(0.00225319+y*(-0.00157565+y*(0.00916281+y*(-0.02057706+y*(0.02635537+y*(-0.01647633+y*0.00392377)))))));
419 0 : return ax*bx;
420 : }
421 :
422 437878862 : bool Tools::startWith(const std::string & full,const std::string &start) {
423 437878862 : return (full.substr(0,start.length())==start);
424 : }
425 :
426 395237 : bool Tools::findKeyword(const std::vector<std::string>&line,const std::string&key) {
427 395237 : const std::string search(key+"=");
428 433045734 : for(const auto & p : line) {
429 432798877 : if(startWith(p,search)) return true;
430 : }
431 : return false;
432 : }
433 :
434 521 : Tools::DirectoryChanger::DirectoryChanger(const char*path):
435 521 : path(std::filesystem::current_path())
436 : {
437 521 : if(!path) return;
438 521 : if(std::strlen(path)==0) return;
439 4 : std::filesystem::current_path(path);
440 : }
441 :
442 520 : Tools::DirectoryChanger::~DirectoryChanger() {
443 : try {
444 520 : std::filesystem::current_path(path);
445 0 : } catch(std::filesystem::filesystem_error & e) {
446 0 : std::fprintf(stderr,"+++ WARNING: cannot cd back to directory %s\n",path.c_str());
447 0 : }
448 520 : }
449 :
450 19176 : std::unique_ptr<std::lock_guard<std::mutex>> Tools::molfile_lock() {
451 : static std::mutex mtx;
452 19176 : return Tools::make_unique<std::lock_guard<std::mutex>>(mtx);
453 : }
454 :
455 : /// Internal tool, I am keeping it private for now
456 : namespace {
457 :
458 : class process_one_exception {
459 : std::string & msg;
460 : bool first=true;
461 216 : void update() {
462 216 : if(!first) msg+="\n\nThe above exception was the direct cause of the following exception:\n";
463 216 : first=false;
464 216 : }
465 : public:
466 202 : process_one_exception(std::string & msg):
467 202 : msg(msg)
468 : {}
469 215 : void operator()(const std::exception & e) {
470 215 : update();
471 215 : msg+=e.what();
472 215 : }
473 0 : void operator()(const std::string & e) {
474 0 : update();
475 0 : msg+=e;
476 0 : }
477 1 : void operator()(const char* e) {
478 1 : update();
479 1 : msg+=e;
480 1 : }
481 : };
482 :
483 : template<class T>
484 216 : static void process_all_exceptions(T&& f) {
485 : try {
486 : // First throw the current exception
487 216 : throw;
488 230 : } catch(const std::nested_exception & e) {
489 : // If nested, we go recursive
490 : // notice that we apply function f only if exception is also a std::exception
491 : try {
492 14 : e.rethrow_nested();
493 28 : } catch(...) {
494 14 : process_all_exceptions(f);
495 : }
496 14 : auto d=dynamic_cast<const std::exception*>(&e);
497 14 : if(d) f(*d);
498 402 : } catch(const std::exception &e) {
499 : // If not nested, we end recursion
500 201 : f(e);
501 0 : } catch(const std::string &e) {
502 : // If not nested, we end recursion
503 0 : f(e);
504 2 : } catch(const char* e) {
505 : // If not nested, we end recursion
506 1 : f(e);
507 0 : } catch(...) {
508 : // If not nested and of unknown type, we stop the chain
509 : }
510 216 : }
511 :
512 : }
513 :
514 202 : std::string Tools::concatenateExceptionMessages() {
515 : std::string msg;
516 202 : process_all_exceptions(process_one_exception(msg));
517 202 : return msg;
518 : }
519 :
520 : }
|