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 <cctype>
28 : #include <charconv>
29 : #include <cstring>
30 : #include <filesystem>
31 : #include <limits>
32 : #include <type_traits>
33 : #include <iomanip>
34 : #include <sstream>
35 : #include <string_view>
36 :
37 : namespace PLMD {
38 :
39 : namespace {
40 : /// Removes leading and trailing blanks without allocating
41 26635161 : std::string_view trimBlanks(const std::string & str) {
42 : std::size_t b=0;
43 : std::size_t e=str.size();
44 39617209 : while(b<e && std::isspace(static_cast<unsigned char>(str[b]))) {
45 12982048 : ++b;
46 : }
47 26635180 : while(e>b && std::isspace(static_cast<unsigned char>(str[e-1]))) {
48 : --e;
49 : }
50 26635161 : return std::string_view(str).substr(b,e-b);
51 : }
52 :
53 : /// Fast conversion of the whole (trimmed) string with std::from_chars.
54 : /// Returns false if std::from_chars is not available for T, or if it fails,
55 : /// in which case the caller falls back to the slower conversions.
56 : template<class T>
57 26610440 : bool convertFromChars(std::string_view s,T & t) {
58 : #if defined(__cpp_lib_to_chars)
59 : // floating point std::from_chars is only available when __cpp_lib_to_chars is defined
60 : constexpr bool available=std::is_arithmetic_v<T>;
61 : #else
62 : constexpr bool available=std::is_integral_v<T>;
63 : #endif
64 : if constexpr (available && !std::is_same_v<T,bool>) {
65 26610440 : if(s.empty()) {
66 : return false;
67 : }
68 : T v;
69 26610439 : const auto [ptr,ec]=std::from_chars(s.data(),s.data()+s.size(),v);
70 26610439 : if(ec!=std::errc() || ptr!=s.data()+s.size()) {
71 : return false;
72 : }
73 24550162 : t=v;
74 24550162 : return true;
75 : } else {
76 : (void) s;
77 : (void) t;
78 : return false;
79 : }
80 : }
81 : }
82 :
83 : template<class T>
84 26610445 : bool Tools::convertToAny(const std::string & str,T & t) {
85 26610445 : const auto trimmed=trimBlanks(str);
86 : if constexpr (std::is_unsigned_v<T>) {
87 : // istream accepts a minus sign for unsigned types and wraps the number around
88 : // (e.g. "-1" becomes 4294967295): negative numbers are rejected here
89 7643437 : if(!trimmed.empty() && trimmed[0]=='-') {
90 : return false;
91 : }
92 : }
93 : // fast path: std::from_chars is about 20 times faster than an istringstream
94 26610440 : if(convertFromChars(trimmed,t)) {
95 : return true;
96 : }
97 2111689 : std::istringstream istr(str.c_str());
98 2060278 : bool ok=static_cast<bool>(istr>>t);
99 2060278 : if(!ok) {
100 : return false;
101 : }
102 : std::string remaining;
103 2008988 : istr>>remaining;
104 2008988 : return remaining.length()==0;
105 2060278 : }
106 :
107 2584535 : bool Tools::convertNoexcept(const std::string & str,int & t) {
108 2584535 : return convertToInt(str,t);
109 : }
110 :
111 3 : bool Tools::convertNoexcept(const std::string & str,long int & t) {
112 3 : return convertToInt(str,t);
113 : }
114 :
115 309 : bool Tools::convertNoexcept(const std::string & str,long long int & t) {
116 309 : return convertToInt(str,t);
117 : }
118 :
119 5989904 : bool Tools::convertNoexcept(const std::string & str,unsigned & t) {
120 5989904 : return convertToInt(str,t);
121 : }
122 :
123 15529 : bool Tools::convertNoexcept(const std::string & str,long unsigned & t) {
124 15529 : return convertToInt(str,t);
125 : }
126 :
127 87 : bool Tools::convertNoexcept(const std::string & str,long long unsigned & t) {
128 87 : return convertToInt(str,t);
129 : }
130 :
131 1637917 : bool Tools::convertNoexcept(const std::string & str,AtomNumber &a) {
132 : // Note: AtomNumber's are NOT converted as int, so as to
133 : // avoid using lepton conversions.
134 : unsigned i;
135 1637917 : bool r=convertToAny(str,i);
136 1637917 : if(r) {
137 1612153 : a.setSerial(i);
138 : }
139 1637917 : return r;
140 : }
141 :
142 : template<class T>
143 8590367 : bool Tools::convertToInt(const std::string & str,T & t) {
144 : // First try standard conversion
145 8590367 : if(convertToAny(str,t)) {
146 : return true;
147 : }
148 : // Then use lepton
149 : try {
150 2008043 : double r=lepton::Parser::parse(str).evaluate(lepton::Constants());
151 :
152 : // now sanity checks on the resulting number
153 :
154 : // it should not overflow the requested int type:
155 : // (see https://stackoverflow.com/a/526092)
156 2008042 : if(r>std::nextafter(std::numeric_limits<T>::max(), 0)) {
157 : return false;
158 : }
159 2008040 : if(r<std::nextafter(std::numeric_limits<T>::min(), 0)) {
160 : return false;
161 : }
162 :
163 : // do the actual conversion
164 2008036 : auto tmp=static_cast<T>(std::round(r));
165 :
166 : // it should be *very close* to itself if converted back to double
167 2008036 : double diff= r-static_cast<double>(tmp);
168 2008036 : if(diff*diff > 1e-20) {
169 : return false;
170 : }
171 : // this is to accomodate small numerical errors and allow e.g. exp(log(7)) to be integer
172 :
173 : // it should be change if incremented or decremented by one (see https://stackoverflow.com/a/43656140)
174 2008031 : if(r == static_cast<double>(tmp-1)) {
175 : return false;
176 : }
177 2008030 : if(r == static_cast<double>(tmp+1)) {
178 : return false;
179 : }
180 :
181 : // everything is fine, then store in t
182 2008030 : t=tmp;
183 2008030 : return true;
184 1 : } catch(const PLMD::lepton::Exception& exc) {
185 : }
186 1 : return false;
187 : }
188 :
189 :
190 : template<class T>
191 16376760 : bool Tools::convertToReal(const std::string & str,T & t) {
192 16376760 : if(convertToAny(str,t)) {
193 : return true;
194 : }
195 : {
196 : // nan and inf, as written by printf (OFile writes NaNs as "nan"),
197 : // case insensitive and with an optional sign
198 24716 : auto s=trimBlanks(str);
199 : bool negative=false;
200 24716 : if(!s.empty() && (s[0]=='+' || s[0]=='-')) {
201 12545 : negative=(s[0]=='-');
202 : s.remove_prefix(1);
203 : }
204 24716 : if(caseInSensStringCompare(s,"nan")) {
205 0 : t=std::numeric_limits<T>::quiet_NaN();
206 0 : return true;
207 : }
208 24716 : if(caseInSensStringCompare(s,"inf") || caseInSensStringCompare(s,"infinity")) {
209 1 : t=negative ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
210 1 : return true;
211 : }
212 : }
213 24715 : if(str=="PI" || str=="+PI" || str=="+pi" || str=="pi") {
214 12341 : t=pi;
215 12341 : return true;
216 12374 : } else if(str=="-PI" || str=="-pi") {
217 12293 : t=-pi;
218 12293 : return true;
219 : }
220 : try {
221 81 : t=lepton::Parser::parse(str).evaluate(lepton::Constants());
222 27 : return true;
223 54 : } catch(const PLMD::lepton::Exception& exc) {
224 : }
225 54 : if( str.find("PI")!=std::string::npos ) {
226 0 : std::size_t pi_start=str.find_first_of("PI");
227 0 : if(str.substr(pi_start)!="PI") {
228 : return false;
229 : }
230 0 : std::istringstream nstr(str.substr(0,pi_start));
231 0 : T ff=0.0;
232 0 : bool ok=static_cast<bool>(nstr>>ff);
233 0 : if(!ok) {
234 : return false;
235 : }
236 0 : t=ff*pi;
237 : std::string remains;
238 0 : nstr>>remains;
239 0 : return remains.length()==0;
240 54 : } else if( str.find("pi")!=std::string::npos ) {
241 14 : std::size_t pi_start=str.find_first_of("pi");
242 14 : if(str.substr(pi_start)!="pi") {
243 : return false;
244 : }
245 14 : std::istringstream nstr(str.substr(0,pi_start));
246 14 : T ff=0.0;
247 14 : bool ok=static_cast<bool>(nstr>>ff);
248 14 : if(!ok) {
249 : return false;
250 : }
251 14 : t=ff*pi;
252 : std::string remains;
253 14 : nstr>>remains;
254 14 : return remains.length()==0;
255 54 : } else if(str=="NAN") {
256 0 : t=std::numeric_limits<double>::quiet_NaN();
257 0 : return true;
258 : }
259 : return false;
260 : }
261 :
262 4 : bool Tools::convertNoexcept(const std::string & str,float & t) {
263 4 : return convertToReal(str,t);
264 : }
265 :
266 16375542 : bool Tools::convertNoexcept(const std::string & str,double & t) {
267 16375542 : return convertToReal(str,t);
268 : }
269 :
270 1214 : bool Tools::convertNoexcept(const std::string & str,long double & t) {
271 1214 : return convertToReal(str,t);
272 : }
273 :
274 873055 : bool Tools::convertNoexcept(const std::string & str,std::string & t) {
275 : t=str;
276 873055 : return true;
277 : }
278 :
279 3475271 : std::vector<std::string> Tools::getWords(std::string_view line,
280 : const char* separators,
281 : int * parlevel,
282 : const char* parenthesis,
283 : bool delete_parenthesis) {
284 3475271 : plumed_massert(std::strlen(parenthesis)==1,"multiple parenthesis type not available");
285 3475271 : plumed_massert(parenthesis[0]=='(' || parenthesis[0]=='[' || parenthesis[0]=='{',
286 : "only ( [ { allowed as parenthesis");
287 3475271 : if(!separators) {
288 : separators=" \t\n";
289 : }
290 3475271 : const std::string sep(separators);
291 3475271 : char openpar=parenthesis[0];
292 : char closepar;
293 : if(openpar=='(') {
294 : closepar=')';
295 : }
296 3475271 : if(openpar=='[') {
297 : closepar=']';
298 : }
299 3475271 : if(openpar=='{') {
300 : closepar='}';
301 : }
302 3475271 : std::vector<std::string> words;
303 : std::string word;
304 : int parenthesisLevel=0;
305 3475271 : if(parlevel) {
306 39991 : parenthesisLevel=*parlevel;
307 : }
308 308750245 : for(unsigned i=0; i<line.length(); i++) {
309 : bool found=false;
310 : bool onParenthesis=false;
311 305274974 : if( (line[i]==openpar || line[i]==closepar) && delete_parenthesis ) {
312 : onParenthesis=true;
313 : }
314 305274974 : if(line[i]==closepar) {
315 3763 : parenthesisLevel--;
316 3763 : plumed_assert(parenthesisLevel>=0) << "Extra closed parenthesis in '" << line << "'";
317 : }
318 305274974 : if(parenthesisLevel==0)
319 1220660719 : for(unsigned j=0; j<sep.length(); j++)
320 915498646 : if(line[i]==sep[j]) {
321 : found=true;
322 : }
323 : // If at parenthesis level zero (outer)
324 305162073 : if(!(parenthesisLevel==0 && (found||onParenthesis))) {
325 239257989 : word.push_back(line[i]);
326 : }
327 305274974 : if(line[i]==openpar) {
328 3763 : parenthesisLevel++;
329 : }
330 305274974 : if(found && word.length()>0) {
331 14827952 : if(!parlevel) {
332 14759684 : plumed_assert(parenthesisLevel==0) << "Unmatching parenthesis in '" << line << "'";
333 : }
334 14827952 : words.push_back(word);
335 : word.clear();
336 : }
337 : }
338 3475271 : if(word.length()>0) {
339 3350329 : if(!parlevel) {
340 3310644 : plumed_assert(parenthesisLevel==0) << "Unmatching parenthesis in '" << line << "'";
341 : }
342 3350329 : words.push_back(word);
343 : }
344 3475271 : if(parlevel) {
345 39991 : *parlevel=parenthesisLevel;
346 : }
347 3475271 : return words;
348 0 : }
349 :
350 2213548 : void Tools::getWordsSimple(gch::small_vector<std::string_view> & words,std::string_view line) {
351 : words.clear();
352 2213548 : auto ptr=line.data();
353 2213548 : std::size_t size=0;
354 22792990 : for(unsigned i=0; i<line.length(); i++) {
355 20579442 : const bool is_separator=(line[i]==' ');
356 20579442 : if(!is_separator) {
357 20500638 : size++;
358 78804 : } else if(size==0) {
359 0 : ptr++;
360 : } else {
361 : words.emplace_back(ptr,size);
362 78804 : ptr=&line[i]+1;
363 78804 : size=0;
364 : }
365 : }
366 2213548 : if(size>0) {
367 : words.emplace_back(ptr,size);
368 : }
369 2213548 : }
370 :
371 520081 : void Tools::getWordsSimple(gch::small_vector<std::string_view> & words,
372 : std::string_view line,
373 : const std::string_view separators) {
374 : words.clear();
375 : //NOTE::this function is written assuming that we have only a level of parenthesis
376 : // so "{a b x},{s}" is ok, "x,{{a,r}}" is not
377 : // unlike getWords it will not delete the parenthesis, in the spirit of returning a view
378 : // to remove the parenthesis you have to use a erase-remove idiom (see parseVector)
379 : constexpr char openPar='{';
380 : constexpr char closePar = '}';
381 : size_t init=0;
382 : std::size_t size=0;
383 : int parlevel=0;
384 33057229 : for(unsigned i=0; i<line.length(); i++) {
385 : //being between parenthesis "suspends" the separator check
386 32537148 : const bool is_separator=(separators.find(line[i])!=separators.npos)&&parlevel==0;
387 32537148 : const bool is_par = line[i]==openPar || line[i] == closePar;
388 32537148 : if(!is_separator && !is_par) {
389 24707086 : size++;
390 7830062 : } else if(line[i]==openPar) {
391 51 : ++parlevel;
392 51 : ++size;
393 7830011 : } else if(line[i] == closePar) {
394 51 : --parlevel;
395 51 : ++size;
396 : } else {
397 7829960 : if(size==0) {
398 21 : init++;
399 : } else {
400 7829939 : words.emplace_back(line.substr(init,size));
401 7829939 : init=i+1;
402 : size=0;
403 : }
404 : }
405 : }
406 520081 : if(size>0) {
407 1040136 : words.emplace_back(line.substr(init,size));
408 : }
409 :
410 520081 : plumed_assert(parlevel==0) << "Unmatching parenthesis in '" << line << "'";
411 520081 : }
412 :
413 22505 : bool Tools::getParsedLine(IFile& ifile,std::vector<std::string> & words, bool trimcomments) {
414 22505 : std::string line("");
415 22505 : words.clear();
416 : bool stat;
417 : bool inside=false;
418 22505 : int parlevel=0;
419 : bool mergenext=false;
420 49420 : while((stat=ifile.getline(line))) {
421 48038 : if(trimcomments) {
422 48016 : trimComments(line);
423 : }
424 48038 : trim(line);
425 48038 : if(line.length()==0) {
426 8047 : continue;
427 : }
428 39991 : std::vector<std::string> w=getWords(line,NULL,&parlevel,"{",trimcomments);
429 39991 : if(!w.empty()) {
430 39685 : if(inside && *(w.begin())=="...") {
431 : inside=false;
432 1588 : if(w.size()==2) {
433 1343 : 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]);
434 : }
435 1588 : plumed_massert(w.size()<=2,"terminating \"...\" lines cannot consist of more than two words");
436 1588 : w.clear();
437 1588 : if(!trimcomments) {
438 4 : words.push_back("...");
439 : }
440 38097 : } else if(*(w.end()-1)=="...") {
441 : inside=true;
442 : w.erase(w.end()-1);
443 : };
444 : int i0=0;
445 39685 : if(mergenext && words.size()>0 && w.size()>0) {
446 158 : words[words.size()-1]+=" "+w[0];
447 : i0=1;
448 : }
449 143040 : for(unsigned i=i0; i<w.size(); ++i) {
450 103355 : words.push_back(w[i]);
451 : }
452 : }
453 39991 : mergenext=(parlevel>0);
454 39991 : if(!inside) {
455 : break;
456 : }
457 18868 : if(!trimcomments && parlevel==0) {
458 8 : words.push_back("@newline");
459 : } else if(!trimcomments) {
460 : words[words.size()-1] += " @newline";
461 : }
462 39991 : }
463 22505 : plumed_massert(parlevel==0,"non matching parenthesis");
464 22505 : if(words.size()>0) {
465 20949 : return true;
466 : }
467 : return stat;
468 : }
469 :
470 :
471 3854307 : bool Tools::getline(FILE* fp,std::string & line) {
472 : line="";
473 : const int bufferlength=1024;
474 : char buffer[bufferlength];
475 : bool ret;
476 3950664675 : for(int i=0; i<bufferlength; i++) {
477 3946810368 : buffer[i]='\0';
478 : }
479 3854311 : while((ret=fgets(buffer,bufferlength,fp))) {
480 3852892 : line.append(buffer);
481 3852892 : unsigned ss=std::strlen(buffer);
482 3852892 : if(ss>0)
483 3852892 : if(buffer[ss-1]=='\n') {
484 : break;
485 : }
486 : };
487 3854307 : if(line.length()>0)
488 3852892 : if(*(line.end()-1)=='\n') {
489 3852888 : line.erase(line.end()-1);
490 : }
491 3854307 : if(line.length()>0)
492 3852872 : if(*(line.end()-1)=='\r') {
493 1180 : line.erase(line.end()-1);
494 : }
495 : // a last line without a newline is still a line
496 3854307 : return ret || line.length()>0;
497 : }
498 :
499 1674661 : void Tools::trim(std::string & s) {
500 1674661 : auto n=s.find_last_not_of(" \t");
501 1674661 : if(n!=std::string::npos) {
502 1659519 : s.resize(n+1);
503 : }
504 1674661 : }
505 :
506 1359 : void Tools::ltrim(std::string & s) {
507 1359 : auto n=s.find_first_not_of(" \t");
508 1359 : if(n!=std::string::npos) {
509 2718 : s = s.substr(n, s.length()-n);
510 : s.shrink_to_fit();
511 : }
512 1359 : }
513 :
514 1692822 : void Tools::trimComments(std::string & s) {
515 1692822 : auto n=s.find_first_of("#");
516 1692822 : if(n!=std::string::npos) {
517 : s.resize(n);
518 : }
519 1692822 : }
520 :
521 104837 : bool Tools::caseInSensStringCompare(std::string_view str1, std::string_view str2) {
522 : return ((str1.size() == str2.size())
523 104837 : && std::equal(str1.begin(), str1.end(), str2.begin(), [](char c1, char c2) {
524 73474 : return (std::toupper(c1) == std::toupper(c2));
525 104837 : }));
526 : }
527 :
528 17244 : bool Tools::getKey(std::vector<std::string>& line,
529 : const std::string & key,
530 : std::string & s,
531 : int rep) {
532 : s.clear();
533 38712 : for(auto pl=line.begin(); pl!=line.end(); ++pl) {
534 30564 : std::string_view p(*pl);
535 30564 : if(p.length()==0) {
536 0 : continue;
537 : }
538 30564 : auto x=p.substr(0,key.length());
539 30564 : if(caseInSensStringCompare(x,key)) {
540 9096 : if(p.length()==key.length()) {
541 : return false;
542 : }
543 9096 : auto tmp=p.substr(key.length(),p.length());
544 9096 : constexpr std::string_view multi("@replicas:");
545 9096 : if(rep>=0 && startWith(tmp,multi)) {
546 : //can be made faster with getWordSimple? the only string allocation will be the one in s=words[rep]
547 0 : auto words=getWords(tmp.substr(multi.length(),tmp.length()),"\t\n ,");
548 0 : plumed_massert(rep<static_cast<int>(words.size()),
549 : "Number of fields in " + s + " not consistent with number of replicas");
550 0 : s=words[rep];
551 0 : } else {
552 : s=tmp;
553 : }
554 : line.erase(pl);
555 9096 : return true;
556 : }
557 : };
558 : return false;
559 : }
560 :
561 :
562 27 : void getWords_replicas(gch::small_vector<std::string_view> & words,
563 : std::string_view line,
564 : const std::string_view separators) {
565 : words.clear();
566 : //NOTE: this function is tested in basic/rt-make-getwords
567 : // via unravelReplicas and parseVector
568 :
569 : //NOTE: this function ignores the first parenthesis because
570 : // expects something like "@replica:{a,b c}" or "@replica:a b c"
571 : // the user should cut the "@replica:" before running this.
572 : // in case of a '{' being present the vector will be returned as soon the '}' is found
573 : constexpr char openPar='{';
574 : constexpr char closePar = '}';
575 : size_t init=0;
576 : std::size_t size=0;
577 : int parlevel=0;
578 240 : for(unsigned i=0; i<line.length(); i++) {
579 : //being between parenthesis do not "suspends" the separator check
580 237 : const bool is_separator=(separators.find(line[i])!=separators.npos)
581 237 : && parlevel<=1;
582 237 : const bool is_par = line[i]==openPar || line[i] == closePar;
583 237 : if(!is_separator && !is_par) {
584 117 : size++;
585 120 : } else if(line[i]==openPar) {
586 33 : ++parlevel;
587 33 : if(parlevel == 1) {
588 24 : init = i+1;
589 : } else {
590 9 : ++size;
591 : }
592 87 : } else if(line[i] == closePar) {
593 33 : --parlevel;
594 33 : ++size;
595 33 : if(parlevel == 0) {
596 : --size;
597 : break;
598 : }
599 : } else {
600 54 : if(size==0) {
601 0 : init++;
602 : } else {
603 54 : words.emplace_back(line.substr(init,size));
604 54 : init=i+1;
605 : size=0;
606 : }
607 : }
608 : }
609 27 : if(size>0) {
610 54 : words.emplace_back(line.substr(init,size));
611 : }
612 :
613 27 : plumed_assert(parlevel==0) << "Unmatched parenthesis in '" << line << "'";
614 27 : }
615 :
616 8349960 : std::string_view Tools::unravelReplicas(std::string_view argument,
617 : int rep) {
618 : //NOTE: Tested in /basic/rt-make-getwords
619 :
620 : //NOTE: as now (exactly like before this PR) a vector will not accept only the first element as replicas:
621 : // - @replicas:{1,2,3},4,5 do not work (KEY={@replicas:{1,2,3},4,5})
622 : // - @replicas:{1,4,5},{2,4,5},{3,4,5} works (KEY=@replicas:{{1,4,5},{2,4,5},{3,4,5}}), it should be equivalent to the line above
623 : // - 1,@replicas:{2,3,4},5 works (KEY={1,@replicas{2,3,4},5})
624 8349960 : constexpr std::string_view multi("@replicas:");
625 8349960 : if(rep>=0 && startWith(argument,multi)) {
626 : gch::small_vector<std::string_view> replicaValues;
627 27 : getWords_replicas(replicaValues,argument.substr(multi.size()),"\t\n ,");
628 27 : plumed_massert(rep<static_cast<int>(replicaValues.size()),
629 : "Number of fields in " + std::string(argument) + " not consistent with number of replicas");
630 27 : return replicaValues[rep];
631 : } else {
632 8349933 : return argument;
633 : }
634 : }
635 :
636 250813 : void Tools::interpretRanges(std::vector<std::string>&s) {
637 250813 : std::vector<std::string> news;
638 1260020 : for(const auto & p :s) {
639 1009207 : news.push_back(p);
640 : // the separating dash is searched from the second character, so that the first
641 : // end of the range can be negative (e.g. -10-5 or -10--1, used for residue numbers)
642 1009207 : size_t dash=p.find('-',1);
643 1009207 : if(dash==std::string::npos) {
644 1006962 : continue;
645 : }
646 : int first;
647 6156 : if(!Tools::convertToAny(p.substr(0,dash),first)) {
648 833 : continue;
649 : }
650 2245 : int stride=1;
651 : int second;
652 2245 : size_t colon=p.substr(dash+1).find(":");
653 2245 : if(colon!=std::string::npos) {
654 234 : if(!Tools::convertToAny(p.substr(dash+1).substr(0,colon),second) ||
655 312 : !Tools::convertToAny(p.substr(dash+1).substr(colon+1),stride)) {
656 0 : continue;
657 : }
658 : } else {
659 4334 : if(!Tools::convertToAny(p.substr(dash+1),second)) {
660 0 : continue;
661 : }
662 : }
663 2245 : news.resize(news.size()-1);
664 2245 : if(first<=second) {
665 2244 : plumed_massert(stride>0,"interpreting ranges "+ p + ", stride should be positive");
666 625942 : for(int i=first; i<=second; i+=stride) {
667 : std::string ss;
668 623698 : convert(i,ss);
669 623698 : news.push_back(ss);
670 : }
671 : } else {
672 1 : plumed_massert(stride<0,"interpreting ranges "+ p + ", stride should be positive");
673 3 : for(int i=first; i>=second; i+=stride) {
674 : std::string ss;
675 2 : convert(i,ss);
676 2 : news.push_back(ss);
677 : }
678 : }
679 : }
680 250813 : s=news;
681 250813 : }
682 :
683 89363 : void Tools::interpretLabel(std::vector<std::string>&s) {
684 89363 : if(s.size()<2) {
685 459 : return;
686 : }
687 88904 : std::string s0=s[0];
688 88904 : unsigned l=s0.length();
689 88904 : if(l<1) {
690 : return;
691 : }
692 88904 : if(s0[l-1]==':') {
693 : s[0]=s[1];
694 119980 : s[1]="LABEL="+s0.substr(0,l-1);
695 : }
696 : std::transform(s[0].begin(), s[0].end(), s[0].begin(), ::toupper);
697 : }
698 :
699 12331 : std::vector<std::string> Tools::ls(const std::string&d) {
700 12331 : std::vector<std::string> result;
701 199110 : for (auto const& dir_entry : std::filesystem::directory_iterator{d}) {
702 324234 : result.push_back(dir_entry.path().filename().string());
703 : }
704 12331 : return result;
705 0 : }
706 :
707 4627 : void Tools::stripLeadingAndTrailingBlanks( std::string& str ) {
708 : std::size_t first=str.find_first_not_of(' ');
709 4627 : std::size_t last=str.find_last_not_of(' ');
710 4627 : if( first<=last && first!=std::string::npos) {
711 9180 : str=str.substr(first,last+1);
712 : }
713 4627 : }
714 :
715 22062 : std::string Tools::extension(const std::string&s) {
716 22062 : size_t n=s.find_last_of(".");
717 : std::string ext;
718 22062 : if(n!=std::string::npos && n+1<s.length() && n+5>=s.length()) {
719 15338 : ext=s.substr(n+1);
720 15338 : if(ext.find("/")!=std::string::npos) {
721 : ext="";
722 : }
723 15338 : std::string base=s.substr(0,n);
724 15338 : if(base.length()==0) {
725 : ext="";
726 : }
727 15338 : if(base.length()>0 && base[base.length()-1]=='/') {
728 : ext="";
729 : }
730 : }
731 22062 : return ext;
732 : }
733 :
734 0 : double Tools::bessel0( const double& val ) {
735 0 : if (std::abs(val)<3.75) {
736 0 : double y = Tools::fastpow( val/3.75, 2 );
737 0 : return 1 + y*(3.5156229 +y*(3.0899424 + y*(1.2067492+y*(0.2659732+y*(0.0360768+y*0.0045813)))));
738 : }
739 0 : double ax=std::abs(val), y=3.75/ax, bx=std::exp(ax)/std::sqrt(ax);
740 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)))))));
741 0 : return ax*bx;
742 : }
743 :
744 9801575 : bool Tools::startWith(std::string_view full, std::string_view start) {
745 9801575 : return 0==full.compare(0,start.size(),start);
746 : }
747 :
748 7494 : bool Tools::findKeyword(const std::vector<std::string>&line,const std::string&key) {
749 21188 : for(const auto & p : line) {
750 16956 : if(startWith(p,key)
751 16956 : &&p.size()>key.size()) {
752 : //this does not allocate a new string `key+"="`
753 3262 : if(p[key.size()]=='=') {
754 : return true;
755 : }
756 : }
757 : }
758 : return false;
759 : }
760 :
761 522 : Tools::DirectoryChanger::DirectoryChanger(const char*path):
762 522 : originalpath(std::filesystem::current_path()) {
763 522 : if(!path) {
764 : return;
765 : }
766 522 : if(std::strlen(path)==0) {
767 : return;
768 : }
769 : try {
770 4 : std::filesystem::current_path(path);
771 2 : } catch(std::filesystem::filesystem_error & e) {
772 4 : plumed_error() << "cannot change directory to " << path << ": " << e.code().message();
773 2 : }
774 : }
775 :
776 520 : Tools::DirectoryChanger::~DirectoryChanger() {
777 : try {
778 520 : std::filesystem::current_path(originalpath);
779 0 : } catch(std::filesystem::filesystem_error & e) {
780 0 : std::fprintf(stderr,"+++ WARNING: cannot cd back to directory %s\n",originalpath.c_str());
781 0 : }
782 520 : }
783 :
784 23928 : std::unique_ptr<std::lock_guard<std::mutex>> Tools::molfile_lock() {
785 : static std::mutex mtx;
786 23928 : return Tools::make_unique<std::lock_guard<std::mutex>>(mtx);
787 : }
788 :
789 : /// Internal tool, I am keeping it private for now
790 : namespace {
791 :
792 : class process_one_exception {
793 : //To future me/you: this needs to remain called "msg" because otherwise
794 : //it will break the call message processing in `process_all_exceptions`
795 : std::string & msg;
796 : bool first=true;
797 695 : void update() {
798 695 : if(!first) {
799 114 : msg+="\n\nThe above exception was the direct cause of the following exception:\n";
800 : }
801 695 : first=false;
802 695 : }
803 : public:
804 581 : process_one_exception(std::string & inmsg):
805 581 : msg(inmsg)
806 : {}
807 694 : void operator()(const std::exception & e) {
808 694 : update();
809 694 : msg+=e.what();
810 694 : }
811 0 : void operator()(const std::string & e) {
812 0 : update();
813 0 : msg+=e;
814 0 : }
815 1 : void operator()(const char* e) {
816 1 : update();
817 1 : msg+=e;
818 1 : }
819 : };
820 :
821 : template<class T>
822 695 : static void process_all_exceptions(T&& f) {
823 : try {
824 : // First throw the current exception
825 695 : throw;
826 809 : } catch(const std::nested_exception & e) {
827 : // If nested, we go recursive
828 : // notice that we apply function f only if exception is also a std::exception
829 : try {
830 114 : e.rethrow_nested();
831 228 : } catch(...) {
832 114 : process_all_exceptions(f);
833 : }
834 114 : auto d=dynamic_cast<const std::exception*>(&e);
835 114 : if(d) {
836 114 : f(*d);
837 : }
838 1160 : } catch(const std::exception &e) {
839 : // If not nested, we end recursion
840 580 : f(e);
841 0 : } catch(const std::string &e) {
842 : // If not nested, we end recursion
843 0 : f(e);
844 2 : } catch(const char* e) {
845 : // If not nested, we end recursion
846 1 : f(e);
847 0 : } catch(...) {
848 : // If not nested and of unknown type, we stop the chain
849 : }
850 695 : }
851 :
852 : }
853 :
854 581 : std::string Tools::concatenateExceptionMessages() {
855 : std::string msg;
856 581 : process_all_exceptions(process_one_exception(msg));
857 581 : return msg;
858 : }
859 :
860 50 : std::string Tools::convertRegexForJson (const std::string& command) {
861 50 : std::stringstream ss;
862 50 : ss << std::quoted(command);
863 : std::string modifiedCommand=ss.str();
864 : //std::quoted quotes, and I do not want the extra delimiters
865 100 : return modifiedCommand.substr(1,modifiedCommand.length()-2);
866 50 : }
867 : }
|