Refactor md_enums
[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,2021, 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/enumerationhelpers.h"
52 #include "gromacs/utility/exceptions.h"
53 #include "gromacs/utility/fatalerror.h"
54 #include "gromacs/utility/keyvaluetreebuilder.h"
55 #include "gromacs/utility/niceheader.h"
56 #include "gromacs/utility/programcontext.h"
57 #include "gromacs/utility/smalloc.h"
58 #include "gromacs/utility/stringutil.h"
59 #include "gromacs/utility/textreader.h"
60 #include "gromacs/utility/textwriter.h"
61
62 std::vector<t_inpfile> read_inpfile(gmx::TextInputStream* stream, const char* fn, warninp_t wi)
63 {
64     std::vector<t_inpfile> inp;
65
66     if (debug)
67     {
68         fprintf(debug, "Reading MDP file %s\n", fn);
69     }
70
71     int             indexOfLineReadFromFile = 0;
72     std::string     line;
73     gmx::TextReader reader(stream);
74     reader.setTrimTrailingWhiteSpace(true);
75     reader.setTrimTrailingComment(true, ';');
76     while (reader.readLine(&line))
77     {
78         indexOfLineReadFromFile++;
79         set_warning_line(wi, fn, indexOfLineReadFromFile);
80
81         if (line.empty())
82         {
83             continue;
84         }
85
86         auto tokens = gmx::splitAndTrimDelimitedString(line, '=');
87         if (tokens.size() < 2)
88         {
89             auto message = gmx::formatString(
90                     "No '=' to separate .mdp parameter key and value was found on line:\n'%s'",
91                     line.c_str());
92             warning_error(wi, message);
93             continue;
94         }
95         if (tokens.size() > 2)
96         {
97             // More than one equals symbol in the original line is
98             // valid if the RHS is a free string, and needed for
99             // "define = -DBOOLVAR -DVAR=VALUE".
100             //
101             // First, drop all the fields on the RHS of the first equals symbol.
102             tokens.resize(1);
103             // This find cannot return std::string::npos.
104             auto firstEqualsPos = line.find('=');
105             tokens.emplace_back(gmx::stripString(line.substr(firstEqualsPos + 1)));
106         }
107         GMX_RELEASE_ASSERT(tokens.size() == 2, "Must have tokens for key and value");
108         if (tokens[0].empty() && tokens[1].empty())
109         {
110             auto message = gmx::formatString(
111                     "No .mdp parameter name or value was found on line:\n'%s'", line.c_str());
112             warning_error(wi, message);
113             continue;
114         }
115         if (tokens[0].empty())
116         {
117             auto message = gmx::formatString(
118                     "No .mdp parameter name was found on the left-hand side of '=' on line:\n'%s'",
119                     line.c_str());
120             warning_error(wi, message);
121             continue;
122         }
123         if (tokens[1].empty())
124         {
125             // Users are probably using this for lines like
126             //   tcoupl = ;v-rescale
127             //   comm-grps =
128             // so we accept their intent to use the default behavior.
129             continue;
130         }
131
132         /* Now finally something sensible; check for duplicates */
133         int found_index = search_einp(inp, tokens[0].c_str());
134
135         if (found_index == -1)
136         {
137             /* add a new item */
138             inp.emplace_back(0, 1, false, false, false, tokens[0], tokens[1]);
139         }
140         else
141         {
142             auto message = gmx::formatString("Parameter \"%s\" doubly defined\n", tokens[0].c_str());
143             warning_error(wi, message);
144         }
145     }
146     /* This preserves the behaviour of the old code, which issues some
147        warnings after completing parsing. Regenerating regressiontest
148        warning files is not worth the effort. */
149     indexOfLineReadFromFile++;
150     set_warning_line(wi, fn, indexOfLineReadFromFile);
151
152     if (debug)
153     {
154         fprintf(debug, "Done reading MDP file, there were %zu entries in there\n", inp.size());
155     }
156
157     return inp;
158 }
159
160 gmx::KeyValueTreeObject flatKeyValueTreeFromInpFile(gmx::ArrayRef<const t_inpfile> inp)
161 {
162     gmx::KeyValueTreeBuilder builder;
163     auto                     root = builder.rootObject();
164     for (auto& local : inp)
165     {
166         root.addValue<std::string>(local.name_, !local.value_.empty() ? local.value_ : "");
167     }
168     return builder.build();
169 }
170
171
172 struct inp_comp
173 {
174     bool operator()(t_inpfile const& a, t_inpfile const& b) { return a.count_ < b.count_; }
175 };
176
177 static void sort_inp(std::vector<t_inpfile>* inp)
178 {
179     std::vector<t_inpfile>& inpRef = *inp;
180     int                     mm;
181
182     mm = -1;
183     for (const auto& local : inpRef)
184     {
185         mm = std::max(mm, local.count_);
186     }
187     for (auto& local : inpRef)
188     {
189         if (local.count_ == 0)
190         {
191             local.count_ = mm++;
192         }
193     }
194     std::sort(inpRef.begin(), inpRef.end(), inp_comp());
195 }
196
197 void write_inpfile(gmx::TextOutputStream*  stream,
198                    const char*             fn,
199                    std::vector<t_inpfile>* inp,
200                    gmx_bool                bHaltOnUnknown,
201                    WriteMdpHeader          writeHeader,
202                    warninp_t               wi)
203 {
204     using gmx::formatString;
205
206     sort_inp(inp);
207
208     gmx::TextWriter writer(stream);
209     if (writeHeader == WriteMdpHeader::yes)
210     {
211         gmx::niceHeader(&writer, fn, ';');
212
213         gmx::BinaryInformationSettings settings;
214         settings.generatedByHeader(true);
215         settings.linePrefix(";\t");
216         gmx::printBinaryInformation(&writer, gmx::getProgramContext(), settings);
217     }
218     for (const auto& local : *inp)
219     {
220         if (local.bHandledAsKeyValueTree_) {}
221         else if (local.bSet_)
222         {
223             if (local.name_[0] == ';' || (local.name_.length() > 2 && local.name_[1] == ';'))
224             {
225                 writer.writeLine(formatString("%-24s", local.name_.c_str()));
226             }
227             else
228             {
229                 writer.writeLine(formatString("%-24s = %s",
230                                               local.name_.c_str(),
231                                               !local.value_.empty() ? local.value_.c_str() : ""));
232             }
233         }
234         else if (!local.bObsolete_)
235         {
236             auto message =
237                     formatString("Unknown left-hand '%s' in parameter file\n", local.name_.c_str());
238             if (bHaltOnUnknown)
239             {
240                 warning_error(wi, message.c_str());
241             }
242             else
243             {
244                 warning(wi, message.c_str());
245             }
246         }
247     }
248
249     check_warning_error(wi, FARGS);
250 }
251
252 void replace_inp_entry(gmx::ArrayRef<t_inpfile> inp, const char* old_entry, const char* new_entry)
253 {
254     for (auto& local : inp)
255     {
256         if (gmx_strcasecmp_min(old_entry, local.name_.c_str()) == 0)
257         {
258             if (new_entry)
259             {
260                 fprintf(stderr, "Replacing old mdp entry '%s' by '%s'\n", local.name_.c_str(), new_entry);
261
262                 int foundIndex = search_einp(inp, new_entry);
263                 if (foundIndex >= 0)
264                 {
265                     gmx_fatal(FARGS,
266                               "A parameter is present with both the old name '%s' and the new name "
267                               "'%s'.",
268                               local.name_.c_str(),
269                               inp[foundIndex].name_.c_str());
270                 }
271
272                 local.name_.assign(new_entry);
273             }
274             else
275             {
276                 fprintf(stderr, "Ignoring obsolete mdp entry '%s'\n", local.name_.c_str());
277                 local.bObsolete_ = TRUE;
278             }
279         }
280     }
281 }
282
283 int search_einp(gmx::ArrayRef<const t_inpfile> inp, const char* name)
284 {
285     if (inp.empty())
286     {
287         return -1;
288     }
289     for (gmx::index i = 0; i < inp.ssize(); i++)
290     {
291         if (gmx_strcasecmp_min(name, inp[i].name_.c_str()) == 0)
292         {
293             return i;
294         }
295     }
296     return -1;
297 }
298
299 void mark_einp_set(gmx::ArrayRef<t_inpfile> inp, const char* name)
300 {
301     int i = search_einp(inp, name);
302     if (i != -1)
303     {
304         inp[i].count_ = inp.front().inp_count_++;
305         inp[i].bSet_  = TRUE;
306         /* Prevent mdp lines being written twice for
307            options that are handled via key-value trees. */
308         inp[i].bHandledAsKeyValueTree_ = TRUE;
309     }
310 }
311
312 int get_einp(std::vector<t_inpfile>* inp, const char* name)
313 {
314     std::vector<t_inpfile>& inpRef   = *inp;
315     bool                    notfound = false;
316
317     int i = search_einp(inpRef, name);
318
319     if (i == -1)
320     {
321         notfound = true;
322         inpRef.emplace_back(0, 0, false, true, false, name, "");
323         i = inpRef.size() - 1;
324         if (inpRef.size() == 1)
325         {
326             inpRef.front().inp_count_ = 1;
327         }
328     }
329     inpRef[i].count_ = inpRef.front().inp_count_++;
330     inpRef[i].bSet_  = TRUE;
331     if (debug)
332     {
333         fprintf(debug, "Inp %d = %s\n", inpRef[i].count_, inpRef[i].name_.c_str());
334     }
335
336     if (notfound)
337     {
338         return -1;
339     }
340     else
341     {
342         return i;
343     }
344 }
345
346 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
347 int get_eint(std::vector<t_inpfile>* inp, const char* name, int def, warninp_t wi)
348 {
349     std::vector<t_inpfile>& inpRef = *inp;
350     char                    buf[32], *ptr, warn_buf[STRLEN];
351
352     int ii = get_einp(inp, name);
353
354     if (ii == -1)
355     {
356         sprintf(buf, "%d", def);
357         inpRef.back().value_.assign(buf);
358
359         return def;
360     }
361     else
362     {
363         int ret = std::strtol(inpRef[ii].value_.c_str(), &ptr, 10);
364         if (*ptr != '\0')
365         {
366             sprintf(warn_buf,
367                     "Right hand side '%s' for parameter '%s' in parameter file is not an integer "
368                     "value\n",
369                     inpRef[ii].value_.c_str(),
370                     inpRef[ii].name_.c_str());
371             warning_error(wi, warn_buf);
372         }
373
374         return ret;
375     }
376 }
377
378 int get_eint(std::vector<t_inpfile>* inp, const std::string& name, int def, warninp_t wi)
379 {
380     return get_eint(inp, name.c_str(), def, wi);
381 }
382
383 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
384 int64_t get_eint64(std::vector<t_inpfile>* inp, const char* name, int64_t def, warninp_t wi)
385 {
386     std::vector<t_inpfile>& inpRef = *inp;
387     char                    buf[32], *ptr, warn_buf[STRLEN];
388
389     int ii = get_einp(inp, name);
390
391     if (ii == -1)
392     {
393         sprintf(buf, "%" PRId64, def);
394         inpRef.back().value_.assign(buf);
395
396         return def;
397     }
398     else
399     {
400         int64_t ret = str_to_int64_t(inpRef[ii].value_.c_str(), &ptr);
401         if (*ptr != '\0')
402         {
403             sprintf(warn_buf,
404                     "Right hand side '%s' for parameter '%s' in parameter file is not an integer "
405                     "value\n",
406                     inpRef[ii].value_.c_str(),
407                     inpRef[ii].name_.c_str());
408             warning_error(wi, warn_buf);
409         }
410
411         return ret;
412     }
413 }
414
415 int64_t get_eint64(std::vector<t_inpfile>* inp, const std::string& name, int64_t def, warninp_t wi)
416 {
417     return get_eint64(inp, name.c_str(), def, wi);
418 }
419
420 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
421 double get_ereal(std::vector<t_inpfile>* inp, const char* name, double def, warninp_t wi)
422 {
423     std::vector<t_inpfile>& inpRef = *inp;
424     char                    buf[32], *ptr, warn_buf[STRLEN];
425
426     int ii = get_einp(inp, name);
427
428     if (ii == -1)
429     {
430         sprintf(buf, "%g", def);
431         inpRef.back().value_.assign(buf);
432
433         return def;
434     }
435     else
436     {
437         double ret = strtod(inpRef[ii].value_.c_str(), &ptr);
438         if (*ptr != '\0')
439         {
440             sprintf(warn_buf,
441                     "Right hand side '%s' for parameter '%s' in parameter file is not a real "
442                     "value\n",
443                     inpRef[ii].value_.c_str(),
444                     inpRef[ii].name_.c_str());
445             warning_error(wi, warn_buf);
446         }
447
448         return ret;
449     }
450 }
451
452 double get_ereal(std::vector<t_inpfile>* inp, const std::string& name, double def, warninp_t wi)
453 {
454     return get_ereal(inp, name.c_str(), def, wi);
455 }
456
457 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
458 const char* get_estr(std::vector<t_inpfile>* inp, const char* name, const char* def)
459 {
460     std::vector<t_inpfile>& inpRef = *inp;
461
462     int ii = get_einp(inp, name);
463
464     if (ii == -1)
465     {
466         if (def)
467         {
468             inpRef.back().value_.assign(def);
469         }
470         else
471         {
472             inpRef.back().value_.clear();
473         }
474
475         return def;
476     }
477     else
478     {
479         return inpRef[ii].value_.c_str();
480     }
481 }
482
483 const char* get_estr(std::vector<t_inpfile>* inp, const std::string& name, const char* def)
484 {
485     return get_estr(inp, name.c_str(), def);
486 }
487
488 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
489 int get_eeenum(std::vector<t_inpfile>* inp, const char* name, const char** defs, warninp_t wi)
490 {
491     std::vector<t_inpfile>& inpRef = *inp;
492     int                     n      = 0;
493     char                    buf[STRLEN];
494
495     int ii = get_einp(inp, name);
496
497     if (ii == -1)
498     {
499         inpRef.back().value_.assign(defs[0]);
500
501         return 0;
502     }
503     int i = 0;
504     for (i = 0; (defs[i] != nullptr); i++)
505     {
506         if (gmx_strcasecmp_min(defs[i], inpRef[ii].value_.c_str()) == 0)
507         {
508             break;
509         }
510     }
511
512     if (defs[i] == nullptr)
513     {
514         n += sprintf(buf,
515                      "Invalid enum '%s' for variable %s, using '%s'\n",
516                      inpRef[ii].value_.c_str(),
517                      name,
518                      defs[0]);
519         n += sprintf(buf + n, "Next time use one of:");
520         int j = 0;
521         while (defs[j])
522         {
523             n += sprintf(buf + n, " '%s'", defs[j]);
524             j++;
525         }
526         if (wi != nullptr)
527         {
528             warning_error(wi, buf);
529         }
530         else
531         {
532             fprintf(stderr, "%s\n", buf);
533         }
534
535         inpRef[ii].value_ = gmx_strdup(defs[0]);
536
537         return 0;
538     }
539
540     return i;
541 }
542
543 int get_eeenum(std::vector<t_inpfile>* inp, const std::string& name, const char** defs, warninp_t wi)
544 {
545     return get_eeenum(inp, name.c_str(), defs, wi);
546 }
547
548 int get_eenum(std::vector<t_inpfile>* inp, const char* name, const char** defs)
549 {
550     return get_eeenum(inp, name, defs, nullptr);
551 }
552
553 void printStringNewline(std::vector<t_inpfile>* inp, const char* line)
554 {
555     std::string tmp("\n; ");
556     tmp.append(line);
557     get_estr(inp, tmp.c_str(), nullptr);
558 }
559
560 void printStringNoNewline(std::vector<t_inpfile>* inp, const char* line)
561 {
562     std::string tmp("; ");
563     tmp.append(line);
564     get_estr(inp, tmp.c_str(), nullptr);
565 }
566
567 void setStringEntry(std::vector<t_inpfile>* inp, const char* name, char* newName, const char* def)
568 {
569     GMX_RELEASE_ASSERT(newName != nullptr, "Need a valid char buffer");
570
571     const char* found = nullptr;
572     found             = get_estr(inp, name, def);
573     if (found != nullptr)
574     {
575         std::strcpy(newName, found);
576     }
577 }
578
579 std::string setStringEntry(std::vector<t_inpfile>* inp, const std::string& name, const std::string& def)
580 {
581     GMX_RELEASE_ASSERT(!name.empty(), "Need a valid string");
582
583     return get_estr(inp, name.c_str(), def.c_str());
584 }