2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
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"
60 t_inpfile
*read_inpfile(gmx::TextInputStream
*stream
, const char *fn
, int *ninp
,
63 t_inpfile
*inp
= nullptr;
67 fprintf(debug
, "Reading MDP file %s\n", fn
);
70 int indexOfLineReadFromFile
= 0;
71 int countOfUniqueKeysFound
= 0;
73 gmx::TextReader
reader(stream
);
74 reader
.setTrimTrailingWhiteSpace(true);
75 reader
.setTrimTrailingComment(true, ';');
76 while (reader
.readLine(&line
))
78 indexOfLineReadFromFile
++;
79 set_warning_line(wi
, fn
, indexOfLineReadFromFile
);
86 auto tokens
= gmx::splitAndTrimDelimitedString(line
, '=');
87 if (tokens
.size() < 2)
89 // TODO this seems like it silently ignores the user accidentally deleting an equals sign...
92 fprintf(debug
, "No = on line %d in file %s, ignored\n", indexOfLineReadFromFile
, fn
);
96 if (tokens
.size() > 2)
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".
102 // First, drop all the fields on the RHS of the first equals symbol.
104 // This find cannot return std::string::npos.
105 auto firstEqualsPos
= line
.find('=');
106 tokens
.emplace_back(gmx::stripString(line
.substr(firstEqualsPos
+ 1)));
108 if (tokens
[0].empty())
110 // TODO ignoring such lines does not seem like good behaviour
113 fprintf(debug
, "Empty left hand side on line %d in file %s, ignored\n", indexOfLineReadFromFile
, fn
);
117 if (tokens
[1].empty())
119 // TODO ignoring such lines does not seem like good behaviour
122 fprintf(debug
, "Empty right hand side on line %d in file %s, ignored\n", indexOfLineReadFromFile
, fn
);
127 /* Now finally something sensible; check for duplicates */
128 int found_index
= search_einp(countOfUniqueKeysFound
, inp
, tokens
[0].c_str());
130 if (found_index
== -1)
133 srenew(inp
, ++countOfUniqueKeysFound
);
134 inp
[countOfUniqueKeysFound
-1].inp_count
= 1;
135 inp
[countOfUniqueKeysFound
-1].count
= 0;
136 inp
[countOfUniqueKeysFound
-1].bObsolete
= FALSE
;
137 inp
[countOfUniqueKeysFound
-1].bHandledAsKeyValueTree
= FALSE
;
138 inp
[countOfUniqueKeysFound
-1].bSet
= FALSE
;
139 inp
[countOfUniqueKeysFound
-1].name
= gmx_strdup(tokens
[0].c_str());
140 inp
[countOfUniqueKeysFound
-1].value
= gmx_strdup(tokens
[1].c_str());
144 auto message
= gmx::formatString("Parameter \"%s\" doubly defined\n",
146 warning_error(wi
, message
.c_str());
149 /* This preserves the behaviour of the old code, which issues some
150 warnings after completing parsing. Regenerating regressiontest
151 warning files is not worth the effort. */
152 indexOfLineReadFromFile
++;
153 set_warning_line(wi
, fn
, indexOfLineReadFromFile
);
157 fprintf(debug
, "Done reading MDP file, there were %d entries in there\n",
158 countOfUniqueKeysFound
);
161 *ninp
= countOfUniqueKeysFound
;
166 gmx::KeyValueTreeObject
flatKeyValueTreeFromInpFile(int ninp
, t_inpfile inp
[])
168 gmx::KeyValueTreeBuilder builder
;
169 auto root
= builder
.rootObject();
170 for (int i
= 0; i
< ninp
; ++i
)
172 const char *value
= inp
[i
].value
;
173 root
.addValue
<std::string
>(inp
[i
].name
, value
!= nullptr ? value
: "");
175 return builder
.build();
179 static int inp_comp(const void *a
, const void *b
)
181 return (reinterpret_cast<const t_inpfile
*>(a
))->count
- (reinterpret_cast<const t_inpfile
*>(b
))->count
;
184 static void sort_inp(int ninp
, t_inpfile inp
[])
189 for (i
= 0; (i
< ninp
); i
++)
191 mm
= std::max(mm
, inp
[i
].count
);
193 for (i
= 0; (i
< ninp
); i
++)
195 if (inp
[i
].count
== 0)
200 gmx_qsort(inp
, ninp
, static_cast<size_t>(sizeof(inp
[0])), inp_comp
);
203 void write_inpfile(gmx::TextOutputStream
*stream
, const char *fn
, int ninp
, t_inpfile inp
[],
204 gmx_bool bHaltOnUnknown
,
205 WriteMdpHeader writeHeader
,
208 using gmx::formatString
;
212 gmx::TextWriter
writer(stream
);
213 if (writeHeader
== WriteMdpHeader::yes
)
215 gmx::niceHeader(&writer
, fn
, ';');
217 gmx::BinaryInformationSettings settings
;
218 settings
.generatedByHeader(true);
219 settings
.linePrefix(";\t");
220 gmx::printBinaryInformation(&writer
, gmx::getProgramContext(), settings
);
223 for (int i
= 0; (i
< ninp
); i
++)
225 if (inp
[i
].bHandledAsKeyValueTree
)
228 else if (inp
[i
].bSet
)
230 if (inp
[i
].name
[0] == ';' || (strlen(inp
[i
].name
) > 2 && inp
[i
].name
[1] == ';'))
232 writer
.writeLine(formatString("%-24s", inp
[i
].name
));
236 writer
.writeLine(formatString("%-24s = %s", inp
[i
].name
, inp
[i
].value
? inp
[i
].value
: ""));
239 else if (!inp
[i
].bObsolete
)
241 auto message
= formatString("Unknown left-hand '%s' in parameter file\n",
245 warning_error(wi
, message
.c_str());
249 warning(wi
, message
.c_str());
254 check_warning_error(wi
, FARGS
);
257 void replace_inp_entry(int ninp
, t_inpfile
*inp
, const char *old_entry
, const char *new_entry
)
261 for (i
= 0; (i
< ninp
); i
++)
263 if (gmx_strcasecmp_min(old_entry
, inp
[i
].name
) == 0)
267 fprintf(stderr
, "Replacing old mdp entry '%s' by '%s'\n",
268 inp
[i
].name
, new_entry
);
270 int foundIndex
= search_einp(ninp
, inp
, new_entry
);
273 gmx_fatal(FARGS
, "A parameter is present with both the old name '%s' and the new name '%s'.", inp
[i
].name
, inp
[foundIndex
].name
);
277 inp
[i
].name
= gmx_strdup(new_entry
);
281 fprintf(stderr
, "Ignoring obsolete mdp entry '%s'\n",
283 inp
[i
].bObsolete
= TRUE
;
289 int search_einp(int ninp
, const t_inpfile
*inp
, const char *name
)
297 for (i
= 0; i
< ninp
; i
++)
299 if (gmx_strcasecmp_min(name
, inp
[i
].name
) == 0)
307 void mark_einp_set(int ninp
, t_inpfile
*inp
, const char *name
)
309 int i
= search_einp(ninp
, inp
, name
);
312 inp
[i
].count
= inp
[0].inp_count
++;
314 /* Prevent mdp lines being written twice for
315 options that are handled via key-value trees. */
316 inp
[i
].bHandledAsKeyValueTree
= TRUE
;
321 static int get_einp(int *ninp
, t_inpfile
**inp
, const char *name
)
324 int notfound
= FALSE
;
326 i
= search_einp(*ninp
, *inp
, name
);
331 srenew(*inp
, (*ninp
));
332 (*inp
)[i
].name
= gmx_strdup(name
);
333 (*inp
)[i
].bSet
= TRUE
;
334 (*inp
)[i
].bHandledAsKeyValueTree
= FALSE
;
337 (*inp
)[i
].inp_count
= 1;
340 (*inp
)[i
].count
= (*inp
)[0].inp_count
++;
341 (*inp
)[i
].bSet
= TRUE
;
344 fprintf(debug
, "Inp %d = %s\n", (*inp
)[i
].count
, (*inp
)[i
].name
);
347 /*if (i == (*ninp)-1)*/
358 /* Note that sanitizing the trailing part of (*inp)[ii].value was the responsibility of read_inpfile() */
359 int get_eint(int *ninp
, t_inpfile
**inp
, const char *name
, int def
,
362 char buf
[32], *ptr
, warn_buf
[STRLEN
];
366 ii
= get_einp(ninp
, inp
, name
);
370 sprintf(buf
, "%d", def
);
371 (*inp
)[(*ninp
)-1].value
= gmx_strdup(buf
);
377 ret
= std::strtol((*inp
)[ii
].value
, &ptr
, 10);
380 sprintf(warn_buf
, "Right hand side '%s' for parameter '%s' in parameter file is not an integer value\n", (*inp
)[ii
].value
, (*inp
)[ii
].name
);
381 warning_error(wi
, warn_buf
);
388 /* Note that sanitizing the trailing part of (*inp)[ii].value was the responsibility of read_inpfile() */
389 gmx_int64_t
get_eint64(int *ninp
, t_inpfile
**inp
,
390 const char *name
, gmx_int64_t def
,
393 char buf
[32], *ptr
, warn_buf
[STRLEN
];
397 ii
= get_einp(ninp
, inp
, name
);
401 sprintf(buf
, "%" GMX_PRId64
, def
);
402 (*inp
)[(*ninp
)-1].value
= gmx_strdup(buf
);
408 ret
= str_to_int64_t((*inp
)[ii
].value
, &ptr
);
411 sprintf(warn_buf
, "Right hand side '%s' for parameter '%s' in parameter file is not an integer value\n", (*inp
)[ii
].value
, (*inp
)[ii
].name
);
412 warning_error(wi
, warn_buf
);
419 /* Note that sanitizing the trailing part of (*inp)[ii].value was the responsibility of read_inpfile() */
420 double get_ereal(int *ninp
, t_inpfile
**inp
, const char *name
, double def
,
423 char buf
[32], *ptr
, warn_buf
[STRLEN
];
427 ii
= get_einp(ninp
, inp
, name
);
431 sprintf(buf
, "%g", def
);
432 (*inp
)[(*ninp
)-1].value
= gmx_strdup(buf
);
438 ret
= strtod((*inp
)[ii
].value
, &ptr
);
441 sprintf(warn_buf
, "Right hand side '%s' for parameter '%s' in parameter file is not a real value\n", (*inp
)[ii
].value
, (*inp
)[ii
].name
);
442 warning_error(wi
, warn_buf
);
449 /* Note that sanitizing the trailing part of (*inp)[ii].value was the responsibility of read_inpfile() */
450 const char *get_estr(int *ninp
, t_inpfile
**inp
, const char *name
, const char *def
)
455 ii
= get_einp(ninp
, inp
, name
);
461 sprintf(buf
, "%s", def
);
462 (*inp
)[(*ninp
)-1].value
= gmx_strdup(buf
);
466 (*inp
)[(*ninp
)-1].value
= nullptr;
473 return (*inp
)[ii
].value
;
477 /* Note that sanitizing the trailing part of (*inp)[ii].value was the responsibility of read_inpfile() */
478 int get_eeenum(int *ninp
, t_inpfile
**inp
, const char *name
, const char **defs
,
485 ii
= get_einp(ninp
, inp
, name
);
489 (*inp
)[(*ninp
)-1].value
= gmx_strdup(defs
[0]);
494 for (i
= 0; (defs
[i
] != nullptr); i
++)
496 if (gmx_strcasecmp_min(defs
[i
], (*inp
)[ii
].value
) == 0)
502 if (defs
[i
] == nullptr)
504 n
+= sprintf(buf
, "Invalid enum '%s' for variable %s, using '%s'\n",
505 (*inp
)[ii
].value
, name
, defs
[0]);
506 n
+= sprintf(buf
+n
, "Next time use one of:");
510 n
+= sprintf(buf
+n
, " '%s'", defs
[j
]);
515 warning_error(wi
, buf
);
519 fprintf(stderr
, "%s\n", buf
);
522 (*inp
)[ii
].value
= gmx_strdup(defs
[0]);
530 int get_eenum(int *ninp
, t_inpfile
**inp
, const char *name
, const char **defs
)
532 return get_eeenum(ninp
, inp
, name
, defs
, nullptr);