Remove gmx custom fixed int (e.g. gmx_int64_t) types
[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,2018, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gmxpre.h"
38
39 #include "readinp.h"
40
41 #include <cstdlib>
42 #include <cstring>
43
44 #include <algorithm>
45
46 #include "gromacs/fileio/warninp.h"
47 #include "gromacs/utility/binaryinformation.h"
48 #include "gromacs/utility/cstringutil.h"
49 #include "gromacs/utility/exceptions.h"
50 #include "gromacs/utility/fatalerror.h"
51 #include "gromacs/utility/keyvaluetreebuilder.h"
52 #include "gromacs/utility/niceheader.h"
53 #include "gromacs/utility/programcontext.h"
54 #include "gromacs/utility/qsort_threadsafe.h"
55 #include "gromacs/utility/smalloc.h"
56 #include "gromacs/utility/stringutil.h"
57 #include "gromacs/utility/textreader.h"
58 #include "gromacs/utility/textwriter.h"
59
60 std::vector<t_inpfile>
61 read_inpfile(gmx::TextInputStream *stream, const char *fn,
62              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             // TODO this seems like it silently ignores the user accidentally deleting an equals sign...
90             if (debug)
91             {
92                 fprintf(debug, "No = on line %d in file %s, ignored\n", indexOfLineReadFromFile, fn);
93             }
94             continue;
95         }
96         if (tokens.size() > 2)
97         {
98             // More than one equals symbol in the original line is
99             // valid if the RHS is a free string, and needed for
100             // "define = -DBOOLVAR -DVAR=VALUE".
101             //
102             // First, drop all the fields on the RHS of the first equals symbol.
103             tokens.resize(1);
104             // This find cannot return std::string::npos.
105             auto firstEqualsPos = line.find('=');
106             tokens.emplace_back(gmx::stripString(line.substr(firstEqualsPos + 1)));
107         }
108         if (tokens[0].empty())
109         {
110             // TODO ignoring such lines does not seem like good behaviour
111             if (debug)
112             {
113                 fprintf(debug, "Empty left hand side on line %d in file %s, ignored\n", indexOfLineReadFromFile, fn);
114             }
115             continue;
116         }
117         if (tokens[1].empty())
118         {
119             // TODO ignoring such lines does not seem like good behaviour
120             if (debug)
121             {
122                 fprintf(debug, "Empty right hand side on line %d in file %s, ignored\n", indexOfLineReadFromFile, fn);
123             }
124             continue;
125         }
126
127         /* Now finally something sensible; check for duplicates */
128         int found_index = search_einp(inp, tokens[0].c_str());
129
130         if (found_index == -1)
131         {
132             /* add a new item */
133             inp.emplace_back(0, 1, false, false, false,
134                              tokens[0], tokens[1]);
135         }
136         else
137         {
138             auto message = gmx::formatString("Parameter \"%s\" doubly defined\n",
139                                              tokens[0].c_str());
140             warning_error(wi, message.c_str());
141         }
142     }
143     /* This preserves the behaviour of the old code, which issues some
144        warnings after completing parsing. Regenerating regressiontest
145        warning files is not worth the effort. */
146     indexOfLineReadFromFile++;
147     set_warning_line(wi, fn, indexOfLineReadFromFile);
148
149     if (debug)
150     {
151         fprintf(debug, "Done reading MDP file, there were %zu entries in there\n",
152                 inp.size());
153     }
154
155     return inp;
156 }
157
158 gmx::KeyValueTreeObject flatKeyValueTreeFromInpFile(gmx::ArrayRef<const t_inpfile> inp)
159 {
160     gmx::KeyValueTreeBuilder  builder;
161     auto                      root = builder.rootObject();
162     for (auto &local : inp)
163     {
164         root.addValue<std::string>(local.name_, !local.value_.empty() ? local.value_ : "");
165     }
166     return builder.build();
167 }
168
169
170 struct inp_comp
171 {
172     bool operator()(t_inpfile const &a, t_inpfile const &b)
173     {
174         return a.count_ < b.count_;
175     }
176 };
177
178 static void sort_inp(std::vector<t_inpfile> *inp)
179 {
180     std::vector<t_inpfile> &inpRef = *inp;
181     int                     mm;
182
183     mm = -1;
184     for (const auto &local : inpRef)
185     {
186         mm = std::max(mm, local.count_);
187     }
188     for (auto &local : inpRef)
189     {
190         if (local.count_ == 0)
191         {
192             local.count_ = mm++;
193         }
194     }
195     std::sort(inpRef.begin(), inpRef.end(), inp_comp());
196 }
197
198 void write_inpfile(gmx::TextOutputStream *stream, const char *fn, 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         {
221         }
222         else if (local.bSet_)
223         {
224             if (local.name_[0] == ';' || (local.name_.length() > 2 && local.name_[1] == ';'))
225             {
226                 writer.writeLine(formatString("%-24s", local.name_.c_str()));
227             }
228             else
229             {
230                 writer.writeLine(formatString("%-24s = %s", local.name_.c_str(), !local.value_.empty() ? local.value_.c_str() : ""));
231             }
232         }
233         else if (!local.bObsolete_)
234         {
235             auto message = formatString("Unknown left-hand '%s' in parameter file\n",
236                                         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",
260                         local.name_.c_str(), new_entry);
261
262                 int foundIndex = search_einp(inp, new_entry);
263                 if (foundIndex >= 0)
264                 {
265                     gmx_fatal(FARGS, "A parameter is present with both the old name '%s' and the new name '%s'.", local.name_.c_str(), inp[foundIndex].name_.c_str());
266                 }
267
268                 local.name_.assign(new_entry);
269             }
270             else
271             {
272                 fprintf(stderr, "Ignoring obsolete mdp entry '%s'\n",
273                         local.name_.c_str());
274                 local.bObsolete_ = TRUE;
275             }
276         }
277     }
278 }
279
280 int search_einp(gmx::ArrayRef<const t_inpfile> inp, const char *name)
281 {
282     if (inp.empty())
283     {
284         return -1;
285     }
286     for (gmx::index i = 0; i < inp.size(); i++)
287     {
288         if (gmx_strcasecmp_min(name, inp[i].name_.c_str()) == 0)
289         {
290             return i;
291         }
292     }
293     return -1;
294 }
295
296 void mark_einp_set(gmx::ArrayRef<t_inpfile> inp, const char *name)
297 {
298     int                     i      = search_einp(inp, name);
299     if (i != -1)
300     {
301         inp[i].count_ = inp.front().inp_count_++;
302         inp[i].bSet_  = TRUE;
303         /* Prevent mdp lines being written twice for
304            options that are handled via key-value trees. */
305         inp[i].bHandledAsKeyValueTree_ = TRUE;
306     }
307 }
308
309 static int get_einp(std::vector<t_inpfile> *inp, const char *name)
310 {
311     std::vector<t_inpfile> &inpRef   = *inp;
312     bool                    notfound = false;
313
314     int                     i = search_einp(inpRef, name);
315
316     if (i == -1)
317     {
318         notfound = true;
319         inpRef.emplace_back(0, 0, false, true, false,
320                             name, "");
321         i = inpRef.size() - 1;
322         if (inpRef.size()  == 1)
323         {
324             inpRef.front().inp_count_ = 1;
325         }
326     }
327     inpRef[i].count_ = inpRef.front().inp_count_++;
328     inpRef[i].bSet_  = TRUE;
329     if (debug)
330     {
331         fprintf(debug, "Inp %d = %s\n", inpRef[i].count_, inpRef[i].name_.c_str());
332     }
333
334     if (notfound)
335     {
336         return -1;
337     }
338     else
339     {
340         return i;
341     }
342 }
343
344 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
345 int get_eint(std::vector<t_inpfile> *inp, const char *name, int def,
346              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, "Right hand side '%s' for parameter '%s' in parameter file is not an integer value\n", inpRef[ii].value_.c_str(), inpRef[ii].name_.c_str());
366             warning_error(wi, warn_buf);
367         }
368
369         return ret;
370     }
371 }
372
373 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
374 int64_t get_eint64(std::vector<t_inpfile> *inp,
375                    const char *name, int64_t def,
376                    warninp_t wi)
377 {
378     std::vector<t_inpfile> &inpRef = *inp;
379     char                    buf[32], *ptr, warn_buf[STRLEN];
380
381     int                     ii = get_einp(inp, name);
382
383     if (ii == -1)
384     {
385         sprintf(buf, "%" PRId64, def);
386         inpRef.back().value_.assign(buf);
387
388         return def;
389     }
390     else
391     {
392         int64_t ret = str_to_int64_t(inpRef[ii].value_.c_str(), &ptr);
393         if (*ptr != '\0')
394         {
395             sprintf(warn_buf, "Right hand side '%s' for parameter '%s' in parameter file is not an integer value\n", inpRef[ii].value_.c_str(), inpRef[ii].name_.c_str());
396             warning_error(wi, warn_buf);
397         }
398
399         return ret;
400     }
401 }
402
403 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
404 double get_ereal(std::vector<t_inpfile> *inp, const char *name, double def,
405                  warninp_t wi)
406 {
407     std::vector<t_inpfile> &inpRef = *inp;
408     char                    buf[32], *ptr, warn_buf[STRLEN];
409
410     int                     ii = get_einp(inp, name);
411
412     if (ii == -1)
413     {
414         sprintf(buf, "%g", def);
415         inpRef.back().value_.assign(buf);
416
417         return def;
418     }
419     else
420     {
421         double ret = strtod(inpRef[ii].value_.c_str(), &ptr);
422         if (*ptr != '\0')
423         {
424             sprintf(warn_buf, "Right hand side '%s' for parameter '%s' in parameter file is not a real value\n", inpRef[ii].value_.c_str(), inpRef[ii].name_.c_str());
425             warning_error(wi, warn_buf);
426         }
427
428         return ret;
429     }
430 }
431
432 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
433 const char *get_estr(std::vector<t_inpfile> *inp, const char *name, const char *def)
434 {
435     std::vector<t_inpfile> &inpRef = *inp;
436     char                    buf[32];
437
438     int                     ii = get_einp(inp, name);
439
440     if (ii == -1)
441     {
442         if (def)
443         {
444             sprintf(buf, "%s", def);
445             inpRef.back().value_.assign(buf);
446         }
447         else
448         {
449             inpRef.back().value_.clear();
450         }
451
452         return def;
453     }
454     else
455     {
456         return inpRef[ii].value_.c_str();
457     }
458 }
459
460 /* Note that sanitizing the trailing part of inp[ii].value was the responsibility of read_inpfile() */
461 int get_eeenum(std::vector<t_inpfile> *inp, const char *name, const char **defs,
462                warninp_t wi)
463 {
464     std::vector<t_inpfile> &inpRef = *inp;
465     int                     n      = 0;
466     char                    buf[STRLEN];
467
468     int                     ii = get_einp(inp, name);
469
470     if (ii == -1)
471     {
472         inpRef.back().value_.assign(defs[0]);
473
474         return 0;
475     }
476     int i = 0;
477     for (i = 0; (defs[i] != nullptr); i++)
478     {
479         if (gmx_strcasecmp_min(defs[i], inpRef[ii].value_.c_str()) == 0)
480         {
481             break;
482         }
483     }
484
485     if (defs[i] == nullptr)
486     {
487         n += sprintf(buf, "Invalid enum '%s' for variable %s, using '%s'\n",
488                      inpRef[ii].value_.c_str(), name, defs[0]);
489         n += sprintf(buf+n, "Next time use one of:");
490         int j  = 0;
491         while (defs[j])
492         {
493             n += sprintf(buf+n, " '%s'", defs[j]);
494             j++;
495         }
496         if (wi != nullptr)
497         {
498             warning_error(wi, buf);
499         }
500         else
501         {
502             fprintf(stderr, "%s\n", buf);
503         }
504
505         inpRef[ii].value_ = gmx_strdup(defs[0]);
506
507         return 0;
508     }
509
510     return i;
511 }
512
513 int get_eenum(std::vector<t_inpfile> *inp, const char *name, const char **defs)
514 {
515     return get_eeenum(inp, name, defs, nullptr);
516 }
517
518 void
519 printStringNewline(std::vector<t_inpfile> *inp, const char *line)
520 {
521     std::string tmp("\n; ");
522     tmp.append(line);
523     get_estr(inp, tmp.c_str(), nullptr);
524 }
525
526 void
527 printStringNoNewline(std::vector<t_inpfile> *inp, const char *line)
528 {
529     std::string tmp("; ");
530     tmp.append(line);
531     get_estr(inp, tmp.c_str(), nullptr);
532 }
533 void
534 setStringEntry(std::vector<t_inpfile> *inp, const char *name, char *newName, const char *def)
535 {
536     const char *found = nullptr;
537     found = get_estr(inp, name, def);
538     if (found != nullptr)
539     {
540         std::strcpy(newName, found);
541     }
542 }