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