1 /*
2 * This file is part of the GROMACS molecular simulation package.
3 *
4 * Copyright (c) 2010-2018, The GROMACS development team.
5 * Copyright (c) 2019, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
9 *
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
14 *
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 *
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
32 *
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
35 */
36 /*! \internal \file
37 * \brief
38 * Implements classes in basicoptions.h and basicoptionstorage.h.
39 *
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_options
42 */
43 #include "gmxpre.h"
44
45 #include "basicoptions.h"
46
47 #include <cerrno>
48 #include <cstdio>
49 #include <cstdlib>
50
51 #include <algorithm>
52 #include <limits>
53 #include <string>
54 #include <vector>
55
56 #include "gromacs/utility/cstringutil.h"
57 #include "gromacs/utility/exceptions.h"
58 #include "gromacs/utility/strconvert.h"
59 #include "gromacs/utility/stringutil.h"
60
61 #include "basicoptionstorage.h"
62
63 namespace
64 {
65
66 /*! \brief
67 * Expands a single value to a vector by copying the value.
68 *
69 * \tparam ValueType Type of values to process.
70 * \param[in] length Length of the resulting vector.
71 * \param[in,out] values Values to process.
72 * \throws std::bad_alloc if out of memory.
73 * \throws InvalidInputError if \p values has an invalid number of values.
74 *
75 * \p values should have 0, 1, or \p length values.
76 * If \p values has 1 value, it is expanded such that it has \p length
77 * identical values. In other valid cases, nothing is done.
78 *
79 * \ingroup module_options
80 */
81 template<typename ValueType>
expandVector(size_t length,std::vector<ValueType> * values)82 void expandVector(size_t length, std::vector<ValueType>* values)
83 {
84 if (length > 0 && !values->empty() && values->size() != length)
85 {
86 if (values->size() != 1)
87 {
88 GMX_THROW(gmx::InvalidInputError(
89 gmx::formatString("Expected 1 or %zu values, got %zu", length, values->size())));
90 }
91 const ValueType& value = (*values)[0];
92 values->resize(length, value);
93 }
94 }
95
96 /*! \brief
97 * Finds an enumerated value from the list of allowed values.
98 *
99 * \param[in] allowedValues List of allowed values.
100 * \param[in] value Value to search for.
101 * \throws gmx::InvalidInputError if \p value does not match anything in
102 * \p allowedValues.
103 * \returns Iterator to the found value.
104 *
105 * \ingroup module_options
106 */
findEnumValue(const std::vector<std::string> & allowedValues,const std::string & value)107 std::vector<std::string>::const_iterator findEnumValue(const std::vector<std::string>& allowedValues,
108 const std::string& value)
109 {
110 std::vector<std::string>::const_iterator i;
111 std::vector<std::string>::const_iterator match = allowedValues.end();
112 for (i = allowedValues.begin(); i != allowedValues.end(); ++i)
113 {
114 // TODO: Case independence.
115 if (gmx::startsWith(*i, value))
116 {
117 if (match == allowedValues.end() || i->size() < match->size())
118 {
119 match = i;
120 }
121 }
122 }
123 if (match == allowedValues.end())
124 {
125 GMX_THROW(gmx::InvalidInputError("Invalid value: " + value));
126 }
127 return match;
128 }
129
130 } // namespace
131
132 namespace gmx
133 {
134
135 /********************************************************************
136 * BooleanOptionStorage
137 */
138
formatSingleValue(const bool & value) const139 std::string BooleanOptionStorage::formatSingleValue(const bool& value) const
140 {
141 return value ? "yes" : "no";
142 }
143
initConverter(ConverterType * converter)144 void BooleanOptionStorage::initConverter(ConverterType* converter)
145 {
146 converter->addConverter<std::string>(&fromStdString<bool>);
147 }
148
149 /********************************************************************
150 * BooleanOptionInfo
151 */
152
BooleanOptionInfo(BooleanOptionStorage * option)153 BooleanOptionInfo::BooleanOptionInfo(BooleanOptionStorage* option) : OptionInfo(option) {}
154
option() const155 const BooleanOptionStorage& BooleanOptionInfo::option() const
156 {
157 return static_cast<const BooleanOptionStorage&>(OptionInfo::option());
158 }
159
defaultValue() const160 bool BooleanOptionInfo::defaultValue() const
161 {
162 return option().defaultValue();
163 }
164
165 /********************************************************************
166 * BooleanOption
167 */
168
createStorage(const OptionManagerContainer &) const169 AbstractOptionStorage* BooleanOption::createStorage(const OptionManagerContainer& /*managers*/) const
170 {
171 return new BooleanOptionStorage(*this);
172 }
173
174
175 /********************************************************************
176 * IntegerOptionStorage
177 */
178
formatSingleValue(const int & value) const179 std::string IntegerOptionStorage::formatSingleValue(const int& value) const
180 {
181 return toString(value);
182 }
183
initConverter(ConverterType * converter)184 void IntegerOptionStorage::initConverter(ConverterType* converter)
185 {
186 converter->addConverter<std::string>(&fromStdString<int>);
187 }
188
processSetValues(ValueList * values)189 void IntegerOptionStorage::processSetValues(ValueList* values)
190 {
191 if (isVector())
192 {
193 expandVector(maxValueCount(), values);
194 }
195 }
196
197 /********************************************************************
198 * IntegerOptionInfo
199 */
200
IntegerOptionInfo(IntegerOptionStorage * option)201 IntegerOptionInfo::IntegerOptionInfo(IntegerOptionStorage* option) : OptionInfo(option) {}
202
203 /********************************************************************
204 * IntegerOption
205 */
206
createStorage(const OptionManagerContainer &) const207 AbstractOptionStorage* IntegerOption::createStorage(const OptionManagerContainer& /*managers*/) const
208 {
209 return new IntegerOptionStorage(*this);
210 }
211
212
213 /********************************************************************
214 * Int64OptionStorage
215 */
216
formatSingleValue(const int64_t & value) const217 std::string Int64OptionStorage::formatSingleValue(const int64_t& value) const
218 {
219 return toString(value);
220 }
221
initConverter(ConverterType * converter)222 void Int64OptionStorage::initConverter(ConverterType* converter)
223 {
224 converter->addConverter<std::string>(&fromStdString<int64_t>);
225 }
226
227 /********************************************************************
228 * Int64OptionInfo
229 */
230
Int64OptionInfo(Int64OptionStorage * option)231 Int64OptionInfo::Int64OptionInfo(Int64OptionStorage* option) : OptionInfo(option) {}
232
233 /********************************************************************
234 * Int64Option
235 */
236
createStorage(const OptionManagerContainer &) const237 AbstractOptionStorage* Int64Option::createStorage(const OptionManagerContainer& /*managers*/) const
238 {
239 return new Int64OptionStorage(*this);
240 }
241
242
243 /********************************************************************
244 * DoubleOptionStorage
245 */
246
DoubleOptionStorage(const DoubleOption & settings)247 DoubleOptionStorage::DoubleOptionStorage(const DoubleOption& settings) :
248 MyBase(settings),
249 info_(this),
250 bTime_(settings.bTime_),
251 factor_(1.0)
252 {
253 }
254
typeString() const255 std::string DoubleOptionStorage::typeString() const
256 {
257 return isVector() ? "vector" : (isTime() ? "time" : "real");
258 }
259
formatSingleValue(const double & value) const260 std::string DoubleOptionStorage::formatSingleValue(const double& value) const
261 {
262 return toString(value / factor_);
263 }
264
initConverter(ConverterType * converter)265 void DoubleOptionStorage::initConverter(ConverterType* converter)
266 {
267 converter->addConverter<std::string>(&fromStdString<double>);
268 converter->addCastConversion<float>();
269 }
270
processValue(const double & value) const271 double DoubleOptionStorage::processValue(const double& value) const
272 {
273 // TODO: Consider testing for overflow when scaling with factor_.
274 return value * factor_;
275 }
276
processSetValues(ValueList * values)277 void DoubleOptionStorage::processSetValues(ValueList* values)
278 {
279 if (isVector())
280 {
281 expandVector(maxValueCount(), values);
282 }
283 }
284
setScaleFactor(double factor)285 void DoubleOptionStorage::setScaleFactor(double factor)
286 {
287 GMX_RELEASE_ASSERT(factor > 0.0, "Invalid scaling factor");
288 if (!hasFlag(efOption_HasDefaultValue))
289 {
290 double scale = factor / factor_;
291 for (double& value : values())
292 {
293 value *= scale;
294 }
295 }
296 factor_ = factor;
297 }
298
299 /********************************************************************
300 * DoubleOptionInfo
301 */
302
DoubleOptionInfo(DoubleOptionStorage * option)303 DoubleOptionInfo::DoubleOptionInfo(DoubleOptionStorage* option) : OptionInfo(option) {}
304
option()305 DoubleOptionStorage& DoubleOptionInfo::option()
306 {
307 return static_cast<DoubleOptionStorage&>(OptionInfo::option());
308 }
309
option() const310 const DoubleOptionStorage& DoubleOptionInfo::option() const
311 {
312 return static_cast<const DoubleOptionStorage&>(OptionInfo::option());
313 }
314
isTime() const315 bool DoubleOptionInfo::isTime() const
316 {
317 return option().isTime();
318 }
319
setScaleFactor(double factor)320 void DoubleOptionInfo::setScaleFactor(double factor)
321 {
322 option().setScaleFactor(factor);
323 }
324
325 /********************************************************************
326 * DoubleOption
327 */
328
createStorage(const OptionManagerContainer &) const329 AbstractOptionStorage* DoubleOption::createStorage(const OptionManagerContainer& /*managers*/) const
330 {
331 return new DoubleOptionStorage(*this);
332 }
333
334
335 /********************************************************************
336 * FloatOptionStorage
337 */
338
FloatOptionStorage(const FloatOption & settings)339 FloatOptionStorage::FloatOptionStorage(const FloatOption& settings) :
340 MyBase(settings),
341 info_(this),
342 bTime_(settings.bTime_),
343 factor_(1.0)
344 {
345 }
346
typeString() const347 std::string FloatOptionStorage::typeString() const
348 {
349 return isVector() ? "vector" : (isTime() ? "time" : "real");
350 }
351
formatSingleValue(const float & value) const352 std::string FloatOptionStorage::formatSingleValue(const float& value) const
353 {
354 return toString(value / factor_);
355 }
356
initConverter(ConverterType * converter)357 void FloatOptionStorage::initConverter(ConverterType* converter)
358 {
359 converter->addConverter<std::string>(&fromStdString<float>);
360 converter->addCastConversion<double>();
361 }
362
processValue(const float & value) const363 float FloatOptionStorage::processValue(const float& value) const
364 {
365 // TODO: Consider testing for overflow when scaling with factor_.
366 return value * factor_;
367 }
368
processSetValues(ValueList * values)369 void FloatOptionStorage::processSetValues(ValueList* values)
370 {
371 if (isVector())
372 {
373 expandVector(maxValueCount(), values);
374 }
375 }
376
setScaleFactor(double factor)377 void FloatOptionStorage::setScaleFactor(double factor)
378 {
379 GMX_RELEASE_ASSERT(factor > 0.0, "Invalid scaling factor");
380 if (!hasFlag(efOption_HasDefaultValue))
381 {
382 float scale = factor / factor_;
383 for (float& value : values())
384 {
385 value *= scale;
386 }
387 }
388 factor_ = factor;
389 }
390
391 /********************************************************************
392 * FloatOptionInfo
393 */
394
FloatOptionInfo(FloatOptionStorage * option)395 FloatOptionInfo::FloatOptionInfo(FloatOptionStorage* option) : OptionInfo(option) {}
396
option()397 FloatOptionStorage& FloatOptionInfo::option()
398 {
399 return static_cast<FloatOptionStorage&>(OptionInfo::option());
400 }
401
option() const402 const FloatOptionStorage& FloatOptionInfo::option() const
403 {
404 return static_cast<const FloatOptionStorage&>(OptionInfo::option());
405 }
406
isTime() const407 bool FloatOptionInfo::isTime() const
408 {
409 return option().isTime();
410 }
411
setScaleFactor(double factor)412 void FloatOptionInfo::setScaleFactor(double factor)
413 {
414 option().setScaleFactor(factor);
415 }
416
417 /********************************************************************
418 * FloatOption
419 */
420
createStorage(const OptionManagerContainer &) const421 AbstractOptionStorage* FloatOption::createStorage(const OptionManagerContainer& /*managers*/) const
422 {
423 return new FloatOptionStorage(*this);
424 }
425
426
427 /********************************************************************
428 * StringOptionStorage
429 */
430
StringOptionStorage(const StringOption & settings)431 StringOptionStorage::StringOptionStorage(const StringOption& settings) :
432 MyBase(settings),
433 info_(this)
434 {
435 if (settings.defaultEnumIndex_ >= 0 && settings.enumValues_ == nullptr)
436 {
437 GMX_THROW(APIError("Cannot set default enum index without enum values"));
438 }
439 if (settings.enumValues_ != nullptr)
440 {
441 int count = settings.enumValuesCount_;
442 if (count < 0)
443 {
444 count = 0;
445 while (settings.enumValues_[count] != nullptr)
446 {
447 ++count;
448 }
449 }
450 for (int i = 0; i < count; ++i)
451 {
452 if (settings.enumValues_[i] == nullptr)
453 {
454 GMX_THROW(APIError("Enumeration value cannot be NULL"));
455 }
456 allowed_.emplace_back(settings.enumValues_[i]);
457 }
458 if (settings.defaultEnumIndex_ >= 0)
459 {
460 if (settings.defaultEnumIndex_ >= count)
461 {
462 GMX_THROW(APIError("Default enumeration index is out of range"));
463 }
464 const std::string* defaultValue = settings.defaultValue();
465 if (defaultValue != nullptr && *defaultValue != allowed_[settings.defaultEnumIndex_])
466 {
467 GMX_THROW(APIError("Conflicting default values"));
468 }
469 setDefaultValue(allowed_[settings.defaultEnumIndex_]);
470 }
471 }
472 }
473
formatExtraDescription() const474 std::string StringOptionStorage::formatExtraDescription() const
475 {
476 std::string result;
477 if (!allowed_.empty())
478 {
479 result.append(": ");
480 result.append(joinStrings(allowed_, ", "));
481 }
482 return result;
483 }
484
formatSingleValue(const std::string & value) const485 std::string StringOptionStorage::formatSingleValue(const std::string& value) const
486 {
487 return value;
488 }
489
initConverter(ConverterType *)490 void StringOptionStorage::initConverter(ConverterType* /*converter*/) {}
491
processValue(const std::string & value) const492 std::string StringOptionStorage::processValue(const std::string& value) const
493 {
494 if (!allowed_.empty())
495 {
496 return *findEnumValue(this->allowed_, value);
497 }
498 return value;
499 }
500
501 /********************************************************************
502 * StringOptionInfo
503 */
504
StringOptionInfo(StringOptionStorage * option)505 StringOptionInfo::StringOptionInfo(StringOptionStorage* option) : OptionInfo(option) {}
506
option() const507 const StringOptionStorage& StringOptionInfo::option() const
508 {
509 return static_cast<const StringOptionStorage&>(OptionInfo::option());
510 }
511
isEnumerated() const512 bool StringOptionInfo::isEnumerated() const
513 {
514 return !allowedValues().empty();
515 }
516
allowedValues() const517 const std::vector<std::string>& StringOptionInfo::allowedValues() const
518 {
519 return option().allowedValues();
520 }
521
522 /********************************************************************
523 * StringOption
524 */
525
createStorage(const OptionManagerContainer &) const526 AbstractOptionStorage* StringOption::createStorage(const OptionManagerContainer& /*managers*/) const
527 {
528 return new StringOptionStorage(*this);
529 }
530
531
532 /********************************************************************
533 * EnumOptionStorage
534 */
535
EnumOptionStorage(const AbstractOption & settings,const char * const * enumValues,int count,int defaultValue,int defaultValueIfSet,StorePointer store)536 EnumOptionStorage::EnumOptionStorage(const AbstractOption& settings,
537 const char* const* enumValues,
538 int count,
539 int defaultValue,
540 int defaultValueIfSet,
541 StorePointer store) :
542 MyBase(settings, std::move(store)),
543 info_(this)
544 {
545 if (enumValues == nullptr)
546 {
547 GMX_THROW(APIError("Allowed values must be provided to EnumOption"));
548 }
549
550 if (count < 0)
551 {
552 count = 0;
553 while (enumValues[count] != nullptr)
554 {
555 ++count;
556 }
557 }
558 for (int i = 0; i < count; ++i)
559 {
560 if (enumValues[i] == nullptr)
561 {
562 GMX_THROW(APIError("Enumeration value cannot be NULL"));
563 }
564 allowed_.emplace_back(enumValues[i]);
565 }
566
567 GMX_ASSERT(defaultValue < count, "Default enumeration value is out of range");
568 GMX_ASSERT(defaultValueIfSet < count, "Default enumeration value is out of range");
569 setFlag(efOption_HasDefaultValue);
570 if (defaultValue >= 0)
571 {
572 setDefaultValue(defaultValue);
573 }
574 if (defaultValueIfSet >= 0)
575 {
576 setDefaultValueIfSet(defaultValueIfSet);
577 }
578 }
579
formatExtraDescription() const580 std::string EnumOptionStorage::formatExtraDescription() const
581 {
582 std::string result;
583 result.append(": ");
584 result.append(joinStrings(allowed_, ", "));
585 return result;
586 }
587
formatSingleValue(const int & value) const588 std::string EnumOptionStorage::formatSingleValue(const int& value) const
589 {
590 if (value < 0 || value >= ssize(allowed_))
591 {
592 return std::string();
593 }
594 return allowed_[value];
595 }
596
normalizeValue(const int & value) const597 Any EnumOptionStorage::normalizeValue(const int& value) const
598 {
599 return Any::create<std::string>(formatSingleValue(value));
600 }
601
initConverter(ConverterType * converter)602 void EnumOptionStorage::initConverter(ConverterType* converter)
603 {
604 converter->addConverter<std::string>([this](const std::string& value) {
605 return findEnumValue(this->allowed_, value) - this->allowed_.begin();
606 });
607 }
608
609 /********************************************************************
610 * EnumOptionInfo
611 */
612
EnumOptionInfo(EnumOptionStorage * option)613 EnumOptionInfo::EnumOptionInfo(EnumOptionStorage* option) : OptionInfo(option) {}
614
option() const615 const EnumOptionStorage& EnumOptionInfo::option() const
616 {
617 return static_cast<const EnumOptionStorage&>(OptionInfo::option());
618 }
619
allowedValues() const620 const std::vector<std::string>& EnumOptionInfo::allowedValues() const
621 {
622 return option().allowedValues();
623 }
624
625 /********************************************************************
626 * EnumOption helpers
627 */
628
629 namespace internal
630 {
631
632 //! \cond internal
createEnumOptionStorage(const AbstractOption & option,const char * const * enumValues,int count,int defaultValue,int defaultValueIfSet,std::unique_ptr<IOptionValueStore<int>> store)633 AbstractOptionStorage* createEnumOptionStorage(const AbstractOption& option,
634 const char* const* enumValues,
635 int count,
636 int defaultValue,
637 int defaultValueIfSet,
638 std::unique_ptr<IOptionValueStore<int>> store)
639 {
640 return new EnumOptionStorage(option, enumValues, count, defaultValue, defaultValueIfSet, move(store));
641 }
642 //! \endcond
643
644 } // namespace internal
645
646 } // namespace gmx
647