1da9fd268afd3f4cf98d5ce676ffdbb536370872
[alexxy/gromacs.git] / src / gromacs / fileio / readinp.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
7  * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 #include "gmxpre.h"
39
40 #include "readinp.h"
41
42 #include <cstdlib>
43 #include <cstring>
44
45 #include <algorithm>
46
47 #include "gromacs/fileio/warninp.h"
48 #include "gromacs/utility/arrayref.h"
49 #include "gromacs/utility/binaryinformation.h"
50 #include "gromacs/utility/cstringutil.h"
51 #include "gromacs/utility/exceptions.h"
52 #include "gromacs/utility/fatalerror.h"
53 #include "gromacs/utility/keyvaluetreebuilder.h"
54 #include "gromacs/utility/niceheader.h"
55 #include "gromacs/utility/programcontext.h"
56 #include "gromacs/utility/smalloc.h"
57 #include "gromacs/utility/stringutil.h"
58 #include "gromacs/utility/textreader.h"
59 #include "gromacs/utility/textwriter.h"
60
61 std::vector<t_inpfile> read_inpfile(gmx::TextInputStream* stream, const char* fn, warninp_t wi)
62 {
63     std::vector<t_inpfile> inp;
64
65     if (debug)
66     {
67         fprintf(debug, "Reading MDP file %s\n", fn);
68     }
69
70     int             indexOfLineReadFromFile = 0;
71     std::string     line;
72     gmx::TextReader reader(stream);
73     reader.setTrimTrailingWhiteSpace(true);
74     reader.setTrimTrailingComment(true, ';');
75     while (reader.readLine(&line))
76     {
77         indexOfLineReadFromFile++;
78         set_warning_line(wi, fn, indexOfLineReadFromFile);
79
80         if (line.empty())
81         {
82             continue;
83         }
84
85         auto tokens = gmx::splitAndTrimDelimitedString(line, '=');
86         if (tokens.size() < 2)
87         {
88             auto message = gmx::formatString(
89                     "No '=' to separate .mdp parameter key and value was found on line:\n'%s'",
90                     line.c_str());
91             warning_error(wi, message);
92             continue;
93         }
94         if (tokens.size() > 2)
95         {
96             // More than one equals symbol in the original line is
97             // valid if the RHS is a free string, and needed for
98             // "define = -DBOOLVAR -DVAR=VALUE".
99             //
100             // First, drop all the fields on the RHS of the first equals symbol.
101             tokens.resize(1);
102             // This find cannot return std::string::npos.
103             auto firstEqualsPos = line.find('=');
104             tokens.emplace_back(gmx::stripString(line.substr(firstEqualsPos + 1)));
105         }
106         GMX_RELEASE_ASSERT(tokens.size() == 2, "Must have tokens for key and value");
107         if (tokens[0].empty() && tokens[1].empty())
108         {
109             auto message = gmx::formatString(
110                     "No .mdp parameter name or value was found on line:\n'%s'", line.c_str());
111             warning_error(wi, message);
112             continue;
113         }
114         if (tokens[0].empty())
115         {
116             auto message = gmx::formatString(
117                     "No .mdp parameter name was found on the left-hand side of '=' on line:\n'%s'",
118                     line.c_str());
119             warning_error(wi, message);
120             continue;
121         }
122         if (tokens[1].empty())
123         {
124             // Users are probably using this for lines like
125             //   tcoupl = ;v-rescale
126             //   comm-grps =
127             // so we accept their intent to use the default behavior.
128             continue;
129         }
130
131         /* Now finally something sensible; check for duplicates */
132         int found_index = search_einp(inp, tokens[0].c_str());
133
134         if (found_index == -1)
135         {
136             /* add a new item */
137             inp.emplace_back(0, 1, false, false, false, tokens[0], tokens[1]);
138         }
139         else
140         {
141             auto message = gmx::formatString("Parameter \"%s\" doubly defined\n", tokens[0].c_str());
142             warning_error(wi, message);
143         }
144     }
145     /* This preserves the behaviour of the old code, which issues some
146        warnings after completing parsing. Regenerating regressiontest
147        warning files is not worth the effort. */
148     indexOfLineReadFromFile++;
149     set_warning_line(wi, fn, indexOfLineReadFromFile);
150
151     if (debug)
152     {
153         fprintf(debug, "Done reading MDP file, there were %zu entries in there\n", inp.size());
154     }
155
156     return inp;
157 }
158
159 gmx::KeyValueTreeObject flatKeyValueTreeFromInpFile(gmx::ArrayRef<const t_inpfile> inp)
160 {
161     gmx::KeyValueTreeBuilder builder;
162     auto                     root = builder.rootObject();
163     for (auto& local : inp)
164     {
165         root.addValue<std::string>(local.name_, !local.value_.empty() ? local.value_ : "");
166     }
167     return builder.build();
168 }
169
170
171 struct inp_comp
172 {
173     bool operator()(t_inpfile const& a, t_inpfile const& b) { return a.count_ < b.count_; }
174 };
175
176 static void sort_inp(std::vector<t_inpfile>* inp)
177 {
178     std::vector<t_inpfile>& inpRef = *inp;
179     int                     mm;
180
181     mm = -1;
182     for (const auto& local : inpRef)
183     {
184         mm = std::max(mm, local.count_);
185     }
186     for (auto& local : inpRef)
187     {
188         if (local.count_ == 0)
189         {
190             local.count_ = mm++;
191         }
192     }
193     std::sort(inpRef.begin(), inpRef.end(), inp_comp());
194 }
195
196 void write_inpfile(gmx::TextOutputStream*  stream,
197                    const char*             fn,
198                    std::vector<t_inpfile>* inp,
199                    gmx_bool                bHaltOnUnknown,
200                    WriteMdpHeader          writeHeader,
201                    warninp_t               wi)
202 {
203     using gmx::formatString;
204
205     sort_inp(inp);
206
207     gmx::TextWriter writer(stream);
208     if (writeHeader == WriteMdpHeader::yes)
209     {
210         gmx::niceHeader(&writer, fn, ';');
211
212         gmx::BinaryInformationSettings settings;
213         settings.generatedByHeader(true);
214         settings.linePrefix(";\t");
215         gmx::printBinaryInformation(&writer, gmx::getProgramContext(), settings);
216     }
217     for (const auto& local : *inp)
218     {
219         if (local.bHandledAsKeyValueTree_) {}
220         else if (local.bSet_)
221         {
222             if (local.name_[0] == ';' || (local.name_.length() > 2 && local.name_[1] == ';'))
223             {
224                 writer.writeLine(formatString("%-24s", local.name_.c_str()));
225             }
226             else
227             {
228                 writer.writeLine(formatString("%-24s = %s",
229                                               local.name_.c_str(),
230                                               !local.value_.empty() ? local.value_.c_str() : ""));
231             }
232         }
233         else if (!local.bObsolete_)
234         {
235             auto message =
236                     formatString("Unknown left-hand '%s' in parameter file\n", local.name_.c_str());
237             if (bHaltOnUnknown)
238             {
239                 warning_error(wi, message.c_str());
240             }
241             else
242             {
243                 warning(wi, message.c_str());
244             }
245         }
246     }
247
248     check_warning_error(wi, FARGS);
249 }
250
251 void replace_inp_entry(gmx::ArrayRef<t_inpfile> inp, const char* old_entry, const char* new_entry)
252 {
253     for (auto& local : inp)
254     {
255         if (gmx_strcasecmp_min(old_entry, local.name_.c_str()) == 0)
256         {
257             if (new_entry)
258             {
259                 fprintf(stderr, "Replacing old mdp entry '%s' by '%s'\n", local.name_.c_str(), new_entry);
260
261                 int foundIndex = search_einp(inp, new_entry);
262                 if (foundIndex >= 0)
263                 {
264                     gmx_fatal(FARGS,
265                               "A parameter is present with both the old name '%s' and the new name "
266                               "'%s'.",
267                               local.name_.c_str(),
268                               inp[foundIndex].name_.c_str());
269                 }
270
271                 local.name_.assign(new_entry);
272             }
273             else
274             {
275                 fprintf(stderr, "Ignoring obsolete mdp entry '%s'\n", local.name_.c_str());
276                 local.bObsolete_ = TRUE;
277             }
278         }
279     }
280 }
281
282 int search_einp(gmx::ArrayRef<const t_inpfile> inp, const char* name)
283 {
284     if (inp.empty())
285     {
286         return -1;
287     }
288     for (gmx::index i = 0; i < inp.ssize(); i++)
289     {
290         if (gmx_strcasecmp_min(name, inp[i].name_.c_str()) == 0)
291         {
292             return i;
293         }
294     }
295     return -1;
296 }
297
298 void mark_einp_set(gmx::ArrayRef<t_inpfile> inp, const char* name)
299 {
300     int i = search_einp(inp, name);
301     if (i != -1)
302     {
303         inp[i].count_ = inp.front().inp_count_++;
304         inp[i].bSet_  = TRUE;
305         /* Prevent mdp lines being written twice for
306            options that are handled via key-value trees. */
307         inp[i].bHandledAsKeyValueTree_ = TRUE;
308     }
309 }
310
311 static int get_einp(std::vector<t_inpfile>* inp, const char* name)
312 {
313     std::vector<t_inpfile>& inpRef   = *inp;
314     bool                    notfound = false;
315
316     int i = search_einp(inpRef, name);
317
318     if (i == -1)
319     {
320         notfound = true;
321         inpRef.emplace_back(0, 0, false, true, false, name, "");
322         i = inpRef.size() - 1;
323         if (inpRef.size() == 1)
324         {
325             inpRef.front().inp_count_ = 1;
326         }
327     }
328     inpRef[i].count_ = inpRef.front().inp_count_++;
329     inpRef[i].bSet_  = TRUE;
330     if (debug)
331     {
332         fprintf(debug, "Inp %d = %s\n", inpRef[i].count_, inpRef[i].name_.c_str());
333     }
334
335     if (notfound)
336     {
337         return -1;
338     }
339     else
340     {
341         return i;
342     }
343 }
344
345 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
346 int get_eint(std::vector<t_inpfile>* inp, const char* name, int def, warninp_t wi)
347 {
348     std::vector<t_inpfile>& inpRef = *inp;
349     char                    buf[32], *ptr, warn_buf[STRLEN];
350
351     int ii = get_einp(inp, name);
352
353     if (ii == -1)
354     {
355         sprintf(buf, "%d", def);
356         inpRef.back().value_.assign(buf);
357
358         return def;
359     }
360     else
361     {
362         int ret = std::strtol(inpRef[ii].value_.c_str(), &ptr, 10);
363         if (*ptr != '\0')
364         {
365             sprintf(warn_buf,
366                     "Right hand side '%s' for parameter '%s' in parameter file is not an integer "
367                     "value\n",
368                     inpRef[ii].value_.c_str(),
369                     inpRef[ii].name_.c_str());
370             warning_error(wi, warn_buf);
371         }
372
373         return ret;
374     }
375 }
376
377 int get_eint(std::vector<t_inpfile>* inp, const std::string& name, int def, warninp_t wi)
378 {
379     return get_eint(inp, name.c_str(), def, wi);
380 }
381
382 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
383 int64_t get_eint64(std::vector<t_inpfile>* inp, const char* name, int64_t def, warninp_t wi)
384 {
385     std::vector<t_inpfile>& inpRef = *inp;
386     char                    buf[32], *ptr, warn_buf[STRLEN];
387
388     int ii = get_einp(inp, name);
389
390     if (ii == -1)
391     {
392         sprintf(buf, "%" PRId64, def);
393         inpRef.back().value_.assign(buf);
394
395         return def;
396     }
397     else
398     {
399         int64_t ret = str_to_int64_t(inpRef[ii].value_.c_str(), &ptr);
400         if (*ptr != '\0')
401         {
402             sprintf(warn_buf,
403                     "Right hand side '%s' for parameter '%s' in parameter file is not an integer "
404                     "value\n",
405                     inpRef[ii].value_.c_str(),
406                     inpRef[ii].name_.c_str());
407             warning_error(wi, warn_buf);
408         }
409
410         return ret;
411     }
412 }
413
414 int64_t get_eint64(std::vector<t_inpfile>* inp, const std::string& name, int64_t def, warninp_t wi)
415 {
416     return get_eint64(inp, name.c_str(), def, wi);
417 }
418
419 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
420 double get_ereal(std::vector<t_inpfile>* inp, const char* name, double def, warninp_t wi)
421 {
422     std::vector<t_inpfile>& inpRef = *inp;
423     char                    buf[32], *ptr, warn_buf[STRLEN];
424
425     int ii = get_einp(inp, name);
426
427     if (ii == -1)
428     {
429         sprintf(buf, "%g", def);
430         inpRef.back().value_.assign(buf);
431
432         return def;
433     }
434     else
435     {
436         double ret = strtod(inpRef[ii].value_.c_str(), &ptr);
437         if (*ptr != '\0')
438         {
439             sprintf(warn_buf,
440                     "Right hand side '%s' for parameter '%s' in parameter file is not a real "
441                     "value\n",
442                     inpRef[ii].value_.c_str(),
443                     inpRef[ii].name_.c_str());
444             warning_error(wi, warn_buf);
445         }
446
447         return ret;
448     }
449 }
450
451 double get_ereal(std::vector<t_inpfile>* inp, const std::string& name, double def, warninp_t wi)
452 {
453     return get_ereal(inp, name.c_str(), def, wi);
454 }
455
456 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
457 const char* get_estr(std::vector<t_inpfile>* inp, const char* name, const char* def)
458 {
459     std::vector<t_inpfile>& inpRef = *inp;
460
461     int ii = get_einp(inp, name);
462
463     if (ii == -1)
464     {
465         if (def)
466         {
467             inpRef.back().value_.assign(def);
468         }
469         else
470         {
471             inpRef.back().value_.clear();
472         }
473
474         return def;
475     }
476     else
477     {
478         return inpRef[ii].value_.c_str();
479     }
480 }
481
482 const char* get_estr(std::vector<t_inpfile>* inp, const std::string& name, const char* def)
483 {
484     return get_estr(inp, name.c_str(), def);
485 }
486
487 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
488 int get_eeenum(std::vector<t_inpfile>* inp, const char* name, const char** defs, warninp_t wi)
489 {
490     std::vector<t_inpfile>& inpRef = *inp;
491     int                     n      = 0;
492     char                    buf[STRLEN];
493
494     int ii = get_einp(inp, name);
495
496     if (ii == -1)
497     {
498         inpRef.back().value_.assign(defs[0]);
499
500         return 0;
501     }
502     int i = 0;
503     for (i = 0; (defs[i] != nullptr); i++)
504     {
505         if (gmx_strcasecmp_min(defs[i], inpRef[ii].value_.c_str()) == 0)
506         {
507             break;
508         }
509     }
510
511     if (defs[i] == nullptr)
512     {
513         n += sprintf(buf,
514                      "Invalid enum '%s' for variable %s, using '%s'\n",
515                      inpRef[ii].value_.c_str(),
516                      name,
517                      defs[0]);
518         n += sprintf(buf + n, "Next time use one of:");
519         int j = 0;
520         while (defs[j])
521         {
522             n += sprintf(buf + n, " '%s'", defs[j]);
523             j++;
524         }
525         if (wi != nullptr)
526         {
527             warning_error(wi, buf);
528         }
529         else
530         {
531             fprintf(stderr, "%s\n", buf);
532         }
533
534         inpRef[ii].value_ = gmx_strdup(defs[0]);
535
536         return 0;
537     }
538
539     return i;
540 }
541
542 int get_eeenum(std::vector<t_inpfile>* inp, const std::string& name, const char** defs, warninp_t wi)
543 {
544     return get_eeenum(inp, name.c_str(), defs, wi);
545 }
546
547 int get_eenum(std::vector<t_inpfile>* inp, const char* name, const char** defs)
548 {
549     return get_eeenum(inp, name, defs, nullptr);
550 }
551
552 void printStringNewline(std::vector<t_inpfile>* inp, const char* line)
553 {
554     std::string tmp("\n; ");
555     tmp.append(line);
556     get_estr(inp, tmp.c_str(), nullptr);
557 }
558
559 void printStringNoNewline(std::vector<t_inpfile>* inp, const char* line)
560 {
561     std::string tmp("; ");
562     tmp.append(line);
563     get_estr(inp, tmp.c_str(), nullptr);
564 }
565
566 void setStringEntry(std::vector<t_inpfile>* inp, const char* name, char* newName, const char* def)
567 {
568     GMX_RELEASE_ASSERT(newName != nullptr, "Need a valid char buffer");
569
570     const char* found = nullptr;
571     found             = get_estr(inp, name, def);
572     if (found != nullptr)
573     {
574         std::strcpy(newName, found);
575     }
576 }
577
578 std::string setStringEntry(std::vector<t_inpfile>* inp, const std::string& name, const std::string& def)
579 {
580     GMX_RELEASE_ASSERT(!name.empty(), "Need a valid string");
581
582     return get_estr(inp, name.c_str(), def.c_str());
583 }