diff --git a/doc/src/Developer_code_design.rst b/doc/src/Developer_code_design.rst index e38f843cdc3..04ca8e7ad9e 100644 --- a/doc/src/Developer_code_design.rst +++ b/doc/src/Developer_code_design.rst @@ -278,10 +278,10 @@ I/O and output formatting C-style stdio versus C++ style iostreams ======================================== -LAMMPS uses the `stdio ` +LAMMPS uses the `stdio ` library of the standard C library for reading from and writing to files and console instead of C++ `iostreams -`_. This is mainly motivated +`_. This is mainly motivated by better performance, better control over formatting, and less effort to achieve specific formatting. @@ -294,36 +294,43 @@ Furthermore, output should generally only be done by MPI rank 0 ``logfile`` should use the :cpp:func:`utils::logmesg() convenience function `. -We discourage the use of `stringstreams -`_ because -the bundled {fmt} library and the customized tokenizer classes provide -the same functionality in a cleaner way with better performance. This -also helps maintain a consistent programming syntax with code from many -different contributors. - -Formatting with the {fmt} library -=================================== - -The LAMMPS source code includes a copy of the `{fmt} library -`_, which is preferred over formatting with the -"printf()" family of functions. The primary reason is that it allows a -typesafe default format for any type of supported data. This is -particularly useful for formatting integers of a given size (32-bit or -64-bit) which may require different format strings depending on compile -time settings or compilers/operating systems. Furthermore, {fmt} gives -better performance, has more functionality, a familiar formatting syntax -that has similarities to ``format()`` in Python, and provides a facility -that can be used to integrate format strings and a variable number of +We strongly discourage the use of `stringstreams +`_ because +the bundled `{fmt} library `_ or the `C++ format +library `_ (for +C++20 and later) and the customized tokenizer classes provide the same +functionality in a cleaner way with better performance. This also helps +maintain a consistent programming syntax with code from many different +contributors. + +Formatting with the {fmt} library and std::format +================================================= + +The LAMMPS source code currently includes a slightly modified copy of +the `{fmt} library `_, which is preferred over +formatting with the "printf()" family of functions. When compiling for +C++20 and later we switch to using the `C++ format library +`_. The namespace +prefix currently remains ``fmt::`` through a small wrapper. In the +future, this will be switched to ``std::`` when LAMMPS requires the +C++20 standard as the minimum C++ standard. Thus only functionality +compatible with the C++ format library for C++20 is accepted. Using +``std::format`` requires a fully C++20 compatible compiler (e.g. GCC 13 +and later, Clang 14 and later, or MSVC 16.10 and later) and we `use the +__cpp_lib_format feature test macro +`_ to confirm +the availability of ``std::format``. + +The primary reason for this choice is that it allows a typesafe default +format for any type of supported data. This is particularly useful for +formatting integers of a given size (32-bit or 64-bit) which may require +different format strings depending on compile time settings or +compilers/operating systems. Furthermore, {fmt} gives better +performance, has more functionality, a familiar formatting syntax that +has similarities to ``format()`` in Python, and provides a facility that +can be used to integrate format strings and a variable number of arguments into custom functions in a much simpler way than the varargs -mechanism of the C library. Finally, {fmt} has been included into the -C++20 language standard as ``std::format()``, so changes to adopt it are -future-proof, for as long as they are not using any extensions that are -not (yet) included into C++. - -The long-term plan is to switch to using ``std::format()`` instead of -``fmt::format()`` when the minimum C++ standard required for LAMMPS will -be set to C++20. See the :ref:`basic build instructions ` for -more details. +mechanism of the C library. Formatted strings are frequently created by calling the ``fmt::format()`` function, which will return a string as a @@ -334,15 +341,15 @@ choose the default format based on the data type of the argument. Otherwise, the :cpp:func:`utils::print() ` function may be used instead of ``printf()`` or ``fprintf()``. The equivalent `std::print() function -`_ will become +`_ will become available in C++ 23. In addition, several LAMMPS output functions, that originally accepted a single string as argument have been overloaded to accept a format string with optional arguments as well (e.g., ``Error::all()``, ``Error::one()``, :cpp:func:`utils::logmesg() `). -Summary of the {fmt} format syntax -================================== +Summary of the {fmt} format and std::format syntax +================================================== The syntax of the format string is "{[][:]}", where either the argument id or the format spec (separated by a colon @@ -404,11 +411,13 @@ value, for example "{:{}d}" will consume two integer arguments, the first will be the value shown and the second the minimum width. For more details and examples, please consult the `{fmt} syntax -documentation `_ website. Since we -plan to eventually transition from {fmt} to using ``std::format()`` +documentation `_ website or the +`corresponding C++ syntax reference +`_. Since +we plan to eventually transition from {fmt} to using ``std::format()`` of the C++ standard library, it is advisable to avoid using any extensions beyond what the `C++20 standard offers -`_. +`_. JSON format input and output ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/src/Developer_notes.rst b/doc/src/Developer_notes.rst index 7807a2b494b..6da4fd7395a 100644 --- a/doc/src/Developer_notes.rst +++ b/doc/src/Developer_notes.rst @@ -290,8 +290,10 @@ all three signatures, the single string "Error message" may be replaced with a format string using '{}' placeholders and followed by a variable number of arguments, one for each placeholder. This format string and the arguments are then handed for formatting to the `{fmt} library -`_ (which is bundled with LAMMPS) and thus allow -processing similar to the "format()" functionality in Python. +`_ (which is bundled with LAMMPS) or the `C++ format +library `_ (for +C++20 and later) and thus allow processing similar to the "format()" +functionality in Python. .. note:: diff --git a/doc/src/Developer_platform.rst b/doc/src/Developer_platform.rst index ad8899478aa..9c8fb601a69 100644 --- a/doc/src/Developer_platform.rst +++ b/doc/src/Developer_platform.rst @@ -51,12 +51,12 @@ File and path functions and global constants Since we are requiring C++17 to compile LAMMPS, you can also make use of the functionality of the `C++ filesystem library -`_. The following +`_. The following functions are in part convenience functions or emulate the behavior of similar Python functions or Unix shell commands. Please note that the you need to use the ``string()`` member function of the `std::filesystem::path class -`_ to get access +`_ to get access to the path as a C++ string class instance. .. doxygenvariable:: filepathsep diff --git a/doc/src/Developer_unittest.rst b/doc/src/Developer_unittest.rst index 2b0cdab3bb5..82ff9a6feca 100644 --- a/doc/src/Developer_unittest.rst +++ b/doc/src/Developer_unittest.rst @@ -55,7 +55,7 @@ available: - Tests for ``ArgInfo`` class used by LAMMPS * - ``test_fmtlib.cpp`` - FmtLib - - Tests for ``fmtlib::`` functions used by LAMMPS + - Tests for ``{fmt}`` or ``std::format`` functions used by LAMMPS * - ``test_math_eigen_impl.cpp`` - MathEigen - Tests for ``MathEigen::`` classes and functions diff --git a/doc/src/Developer_utils.rst b/doc/src/Developer_utils.rst index cd9073f43e8..de5b1dd3349 100644 --- a/doc/src/Developer_utils.rst +++ b/doc/src/Developer_utils.rst @@ -160,6 +160,9 @@ and parsing files or arguments. .. doxygenfunction:: trim_and_count_words :project: progguide +.. doxygenfunction:: join + :project: progguide + .. doxygenfunction:: join_words :project: progguide diff --git a/doc/src/Modify_requirements.rst b/doc/src/Modify_requirements.rst index 9495c9cb3aa..10d777e1f2d 100644 --- a/doc/src/Modify_requirements.rst +++ b/doc/src/Modify_requirements.rst @@ -313,10 +313,15 @@ so that one could look up the cause by reading the source code. The new policy encourages more specific error messages that ideally indicate the cause directly, and requiring no further lookup. This is -aided by the `{fmt} library `_ enabling Error class -methods that take a variable number of arguments and an error text that -will be treated like a {fmt} syntax format string. Error messages should -still preferably be kept to a single line or two lines at most. +aided by either the `{fmt} library `_ or the `C++ +format library +`_ (for C++20 +and later) enabling :cpp:class:`LAMMPS_NS::Error` class methods that +take a variable number of arguments and an error text that will be +treated like a `C++ format string +`_. Error +messages should still preferably be kept to a single line or two lines +at most. For more complex explanations or errors that have multiple possible reasons, a paragraph should be added to the `Error_details` page with an diff --git a/doc/src/Modify_style.rst b/doc/src/Modify_style.rst index 853a5c9d57d..d02a47e59f7 100644 --- a/doc/src/Modify_style.rst +++ b/doc/src/Modify_style.rst @@ -39,8 +39,9 @@ Include files (varied) derived from it), certain headers will *always* be included and thus do not need to be explicitly specified. These are: `mpi.h`, `cstddef`, `cstdio`, `cstdlib`, `string`, `utils.h`, `vector`, - `fmt/format.h`, `climits`, `cinttypes`. This also means any such file - can assume that `FILE`, `NULL`, and `INT_MAX` are defined. + `fmt/format.h` (or `format` when using C++20 and later), `climits`, + `cinttypes`. This also means any such file can assume that `FILE`, + `NULL`, and `INT_MAX` are defined. - Class members variables should not be initialized in the header file, but instead should be initialized either in the initializer list of diff --git a/src/APIP/pair_eam_fs_apip.cpp b/src/APIP/pair_eam_fs_apip.cpp index 11fffeca1f0..9ea38d0294a 100644 --- a/src/APIP/pair_eam_fs_apip.cpp +++ b/src/APIP/pair_eam_fs_apip.cpp @@ -24,6 +24,8 @@ #include "memory.h" #include "potential_file_reader.h" +#include + using namespace LAMMPS_NS; /* ---------------------------------------------------------------------- */ @@ -46,12 +48,8 @@ void PairEAMFSAPIP::coeff(int narg, char **arg) if (!allocated) allocate(); - if (narg != 3 + atom->ntypes) error->all(FLERR, "Incorrect args for pair coefficients"); - - // insure I,J args are * * - - if (strcmp(arg[0], "*") != 0 || strcmp(arg[1], "*") != 0) - error->all(FLERR, "Incorrect args for pair coefficients"); + if (narg != 3 + atom->ntypes) + error->all(FLERR, "Number of element to type mappings does not match number of atom types"); // read EAM Finnis-Sinclair file @@ -104,7 +102,7 @@ void PairEAMFSAPIP::coeff(int narg, char **arg) } } - if (count == 0) error->all(FLERR, "Incorrect args for pair coefficients"); + if (count == 0) error->all(FLERR, "Incorrect args for pair coefficients" + utils::errorurl(21)); } /* ---------------------------------------------------------------------- @@ -136,14 +134,8 @@ void PairEAMFSAPIP::read_file(char *filename) error->one(FLERR, "Incorrect element names in EAM potential file"); file->elements = new char *[file->nelements]; - for (int i = 0; i < file->nelements; i++) { - const std::string word = values.next_string(); - const int n = word.length() + 1; - file->elements[i] = new char[n]; - strcpy(file->elements[i], word.c_str()); - } - - // + for (int i = 0; i < file->nelements; i++) + file->elements[i] = utils::strdup(values.next_string()); if (he_flag) values = reader.next_values(6); diff --git a/src/EFF/pair_eff_inline.h b/src/EFF/pair_eff_inline.h index 2aaddbda442..698103e6319 100644 --- a/src/EFF/pair_eff_inline.h +++ b/src/EFF/pair_eff_inline.h @@ -15,6 +15,8 @@ Contributing authors: Andres Jaramillo-Botero, Hai Xiao, Julius Su (Caltech) ------------------------------------------------------------------------- */ +#include + namespace LAMMPS_NS { #define PAULI_RE 0.9 diff --git a/src/EXTRA-DUMP/dump_dcd.cpp b/src/EXTRA-DUMP/dump_dcd.cpp index fe2ca8b5ec6..7b31458c156 100644 --- a/src/EXTRA-DUMP/dump_dcd.cpp +++ b/src/EXTRA-DUMP/dump_dcd.cpp @@ -22,7 +22,6 @@ #include "atom.h" #include "domain.h" #include "error.h" -#include "fmt/chrono.h" #include "group.h" #include "memory.h" #include "output.h" @@ -318,7 +317,7 @@ void DumpDCD::write_dcd_header(const char *remarks) float out_float; char title_string[200]; time_t t; - std::tm current_time; + struct tm *current_time; int ntimestep = update->ntimestep; @@ -350,14 +349,19 @@ void DumpDCD::write_dcd_header(const char *remarks) fwrite_int32(fp,84); fwrite_int32(fp,164); fwrite_int32(fp,2); - strncpy(title_string,remarks,80); - title_string[79] = '\0'; + // pad string with blanks and truncate at 80 chars + snprintf(title_string, sizeof(title_string), + "%s " + " ", remarks); fwrite(title_string,80,1,fp); t = time(nullptr); - current_time = fmt::localtime(t); - std::string s = fmt::format("REMARKS Created {:%d %B,%Y at %H:%M}", current_time); - memset(title_string,' ',81); - memcpy(title_string, s.c_str(), s.size()); + current_time = localtime(&t); + // pad string with blanks and truncate at 80 chars + strftime(title_string, sizeof(title_string), + "REMARKS Created %d %B,%Y at %H:%M" + " " + " ", + current_time); fwrite(title_string,80,1,fp); fwrite_int32(fp,164); fwrite_int32(fp,4); diff --git a/src/EXTRA-FIX/fix_addtorque_group.cpp b/src/EXTRA-FIX/fix_addtorque_group.cpp index e0b77946084..0ad1e6a2d06 100644 --- a/src/EXTRA-FIX/fix_addtorque_group.cpp +++ b/src/EXTRA-FIX/fix_addtorque_group.cpp @@ -30,6 +30,8 @@ #include "update.h" #include "variable.h" +#include + using namespace LAMMPS_NS; using namespace FixConst; diff --git a/src/GPU/pair_amoeba_gpu.cpp b/src/GPU/pair_amoeba_gpu.cpp index 3f5c756fb01..d195278bd07 100644 --- a/src/GPU/pair_amoeba_gpu.cpp +++ b/src/GPU/pair_amoeba_gpu.cpp @@ -33,6 +33,7 @@ #include "suffix.h" #include +#include using namespace LAMMPS_NS; using namespace MathConst; diff --git a/src/INTEL/intel_preprocess.h b/src/INTEL/intel_preprocess.h index 50d0cd6b5dc..c5d2dd77aa1 100644 --- a/src/INTEL/intel_preprocess.h +++ b/src/INTEL/intel_preprocess.h @@ -18,6 +18,8 @@ #include "lmptype.h" +#include + #ifdef __INTEL_LLVM_COMPILER #define USE_OMP_SIMD #define __INTEL_COMPILER_BUILD_DATE __INTEL_LLVM_COMPILER diff --git a/src/INTEL/math_extra_intel.h b/src/INTEL/math_extra_intel.h index 556f3f45907..6312e7ff94a 100644 --- a/src/INTEL/math_extra_intel.h +++ b/src/INTEL/math_extra_intel.h @@ -19,6 +19,8 @@ #ifndef LMP_MATH_EXTRA_INTEL_H #define LMP_MATH_EXTRA_INTEL_H +#include + #define ME_quat_to_mat_trans(quat, mat) \ { \ flt_t quat_w = quat.w; \ diff --git a/src/INTEL/pair_dpd_intel.cpp b/src/INTEL/pair_dpd_intel.cpp index 7b11053d41e..23efc679e73 100644 --- a/src/INTEL/pair_dpd_intel.cpp +++ b/src/INTEL/pair_dpd_intel.cpp @@ -28,6 +28,7 @@ #include "suffix.h" #include +#include #if defined(_OPENMP) #include diff --git a/src/INTEL/pppm_intel.cpp b/src/INTEL/pppm_intel.cpp index 1ebfc29b99b..f0630fe9694 100644 --- a/src/INTEL/pppm_intel.cpp +++ b/src/INTEL/pppm_intel.cpp @@ -34,6 +34,7 @@ #include "suffix.h" #include +#include #include "omp_compat.h" diff --git a/src/KIM/kim_interactions.cpp b/src/KIM/kim_interactions.cpp index 3290aaa0816..2443ebe3bf6 100644 --- a/src/KIM/kim_interactions.cpp +++ b/src/KIM/kim_interactions.cpp @@ -70,8 +70,6 @@ #include "modify.h" #include "update.h" -#include "fmt/ranges.h" - #include #include @@ -130,8 +128,7 @@ void KimInteractions::do_setup(int narg, char **arg) if (simulatorModel) { auto first_visit = input->variable->find("kim_update"); if (!fixed_types) { - std::string atom_type_sym_list = - fmt::format("{}", fmt::join(arg, arg + narg, " ")); + std::string atom_type_sym_list = utils::join(std::vector(arg, arg + narg), " "); std::string atom_type_num_list = fmt::format("{}", species_to_atomic_no(arg[0])); @@ -250,7 +247,7 @@ void KimInteractions::do_setup(int narg, char **arg) auto cmd1 = fmt::format("pair_style kim {}", model_name); auto cmd2 = - fmt::format("pair_coeff * * {}", fmt::join(arg, arg + narg, " ")); + fmt::format("pair_coeff * * {}", utils::join(std::vector(arg, arg + narg), " ")); input->one(cmd1); input->one(cmd2); @@ -308,7 +305,7 @@ void KimInteractions::KIM_SET_TYPE_PARAMETERS(const std::string &input_line) con if (((species[ia] == words[0]) && (species[ib] == words[1])) || ((species[ib] == words[0]) && (species[ia] == words[1]))) input->one(fmt::format("pair_coeff {} {} {}", ia + 1, ib + 1, - fmt::join(words.begin() + 2, words.end(), " "))); + utils::join(std::vector(words.begin() + 2, words.end()), " "))); } } else { for (int ia = 0; ia < atom->ntypes; ++ia) diff --git a/src/KIM/kim_param.cpp b/src/KIM/kim_param.cpp index 7ea9b992016..78f14e59011 100644 --- a/src/KIM/kim_param.cpp +++ b/src/KIM/kim_param.cpp @@ -68,8 +68,6 @@ #include "pair_kim.h" #include "variable.h" -#include "fmt/ranges.h" - #include #include #include @@ -371,7 +369,7 @@ void KimParam::command(int narg, char **arg) // Set the parameters } else { auto setcmd = fmt::format("pair_coeff * * {} {}", atom_type_list, - fmt::join(arg + 1, arg + narg, " ")); + utils::join(std::vector(arg + 1, arg + narg), " ")); input->one(setcmd); } } else diff --git a/src/LEPTON/lepton_utils.cpp b/src/LEPTON/lepton_utils.cpp index d41157471f6..d02926bd6f8 100644 --- a/src/LEPTON/lepton_utils.cpp +++ b/src/LEPTON/lepton_utils.cpp @@ -17,13 +17,12 @@ #include "lepton_utils.h" +#include "error.h" #include "input.h" #include "lammps.h" #include "pair_zbl_const.h" #include "variable.h" -#include "fmt/args.h" - #include #include #include @@ -61,12 +60,9 @@ double Lepton::ZBLFunction::evaluate(const double *args) const double Lepton::ZBLFunction::evaluateDerivative(const double *args, const int *order) const { - if (order[0] > 0) - throw DerivativeException(order[0], "zbl()", "'zi'"); - if (order[1] > 0) - throw DerivativeException(order[0], "zbl()", "'zj'"); - if (order[2] > 1) - throw DerivativeException(order[0], "zbl()", "'r'"); + if (order[0] > 0) throw DerivativeException(order[0], "zbl()", "'zi'"); + if (order[1] > 0) throw DerivativeException(order[0], "zbl()", "'zj'"); + if (order[2] > 1) throw DerivativeException(order[0], "zbl()", "'r'"); if (order[2] == 1) { const double zi = args[0]; @@ -114,21 +110,24 @@ std::string LeptonUtils::condense(const std::string &in) /// substitute variable references with their values std::string LeptonUtils::substitute(const std::string &in, LAMMPS_NS::LAMMPS *lmp) { - std::string format, name; - std::unordered_set vars; + std::string output, name; bool in_var = false; char hold = ' '; + auto *variable = lmp->input->variable; for (const auto &c : in) { if (in_var) { if (isalnum(c) || (c == '_')) { - format.push_back(c); name.push_back(c); } else { in_var = false; - format.push_back('}'); - format.push_back(c); - vars.insert(name); + const char *val = variable->retrieve(name.c_str()); + if (val) + output.append(val); + else + lmp->error->all(FLERR, "Variable {} in expression {} does not exist", name, in); + + output.push_back(c); } } else { if (hold == 'v') { @@ -136,33 +135,26 @@ std::string LeptonUtils::substitute(const std::string &in, LAMMPS_NS::LAMMPS *lm in_var = true; hold = ' '; name.clear(); - format.push_back('{'); } else { - format.push_back(hold); + output.push_back(hold); hold = ' '; - format.push_back(c); + output.push_back(c); } } else { if (c == 'v') hold = c; else - format.push_back(c); + output.push_back(c); } } } if (in_var) { - format.push_back('}'); - vars.insert(name); - } - - auto *variable = lmp->input->variable; - fmt::dynamic_format_arg_store args; - for (const auto &v : vars) { - const char *val = variable->retrieve(v.c_str()); + const char *val = variable->retrieve(name.c_str()); if (val) - args.push_back(fmt::arg(v.c_str(), val)); + output.append(val); else - throw VariableException(v, in); + lmp->error->all(FLERR, "Variable {} in expression {} does not exist", name, in); } - return fmt::vformat(format, args); + + return output; } diff --git a/src/NETCDF/netcdf_units.cpp b/src/NETCDF/netcdf_units.cpp index e5a8417f456..1c415165766 100644 --- a/src/NETCDF/netcdf_units.cpp +++ b/src/NETCDF/netcdf_units.cpp @@ -21,6 +21,8 @@ #include "error.h" +#include + using namespace LAMMPS_NS; std::string NetCDFUnits::get_unit_for(const char *unit_style, int quantity, Error *error) diff --git a/src/OPENMP/npair_halffull_omp.cpp b/src/OPENMP/npair_halffull_omp.cpp index 44c354c0497..05f884ca2a1 100644 --- a/src/OPENMP/npair_halffull_omp.cpp +++ b/src/OPENMP/npair_halffull_omp.cpp @@ -22,6 +22,8 @@ #include "my_page.h" #include "neigh_list.h" +#include + using namespace LAMMPS_NS; /* ---------------------------------------------------------------------- */ diff --git a/src/REAXFF/reaxff_control.cpp b/src/REAXFF/reaxff_control.cpp index eed86fb09f2..d43a475817e 100644 --- a/src/REAXFF/reaxff_control.cpp +++ b/src/REAXFF/reaxff_control.cpp @@ -51,8 +51,8 @@ namespace ReaxFF { class control_parser_error : public std::exception { std::string message; public: - explicit control_parser_error(const std::string &format, const std::string &keyword) { - message = fmt::format(fmt::runtime(format), keyword); + explicit control_parser_error(const std::string &msg) { + message = msg; } [[nodiscard]] const char *what() const noexcept override { return message.c_str(); } }; @@ -86,11 +86,11 @@ namespace ReaxFF { auto keyword = values.next_string(); if (!values.has_next()) - throw control_parser_error("No value(s) for control parameter: {}\n", keyword); + throw control_parser_error(fmt::format("No value(s) for control parameter: {}\n", + keyword)); if (inactive_keywords.find(keyword) != inactive_keywords.end()) { - error->warning(FLERR, fmt::format(fmt::runtime("Ignoring inactive control parameter: {}"), - keyword)); + error->warning(FLERR, "Ignoring inactive control parameter: {}", keyword); } else if (keyword == "nbrhood_cutoff") { control->bond_cut = values.next_double(); } else if (keyword == "bond_graph_cutoff") { @@ -108,7 +108,7 @@ namespace ReaxFF { error->warning(FLERR,"Support for writing native trajectories has " "been removed after LAMMPS version 8 April 2021"); } else { - throw control_parser_error("Unknown parameter {} in control file", keyword); + throw control_parser_error(fmt::format("Unknown parameter {} in control file", keyword)); } } } catch (LAMMPS_NS::EOFException &) { diff --git a/src/compute_temp_deform.cpp b/src/compute_temp_deform.cpp index 0e1811b8802..3441b2b697e 100644 --- a/src/compute_temp_deform.cpp +++ b/src/compute_temp_deform.cpp @@ -30,6 +30,8 @@ #include "modify.h" #include "update.h" +#include + using namespace LAMMPS_NS; enum{NOBIAS,BIAS}; diff --git a/src/fix_wall_harmonic_outside.cpp b/src/fix_wall_harmonic_outside.cpp index a5d4a58559a..4edf75748d8 100644 --- a/src/fix_wall_harmonic_outside.cpp +++ b/src/fix_wall_harmonic_outside.cpp @@ -12,11 +12,13 @@ ------------------------------------------------------------------------- */ #include "fix_wall_harmonic_outside.h" + #include "atom.h" #include "comm.h" #include "citeme.h" #include "error.h" +#include using namespace LAMMPS_NS; /* ---------------------------------------------------------------------- */ diff --git a/src/fmt/format.h b/src/fmt/format.h index 8cdf95b7bdf..cdfbf5f6c2b 100644 --- a/src/fmt/format.h +++ b/src/fmt/format.h @@ -33,6 +33,38 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +// LAMMPS CUSTOMIZATION +#if __has_include() +#include +#endif +#if defined(__cpp_lib_format) && (__cpp_lib_format >= 201907L) + +// when compiling for C++20 or later we emulate +// the parts of fmt::format we use with std::format +// eventually, this emulation can be removed when +// we require C++20 as minimum C++ standard +// +// WARNING: checking for C++20 is not sufficient +// since several compilers with partial C++20 support +// do not contain std::format. We need to use the +// feature test macro like above. +// Known compatible compilers are: GCC 13+, Clang 14+, MSVC 16.10+ + +#include +#include + +namespace fmt +{ + using std::format; + using std::format_args; + using std::format_error; + using std::make_format_args; + using std::string_view; + using std::vformat; +} + +#else + #include // std::signbit #include // uint32_t #include // std::memcpy @@ -4514,5 +4546,5 @@ FMT_END_NAMESPACE #else # define FMT_FUNC #endif - +#endif // C++ < 20 #endif // FMT_FORMAT_H_ diff --git a/src/fmtlib_format.cpp b/src/fmtlib_format.cpp index fd51cb6468b..e9dbff99a2f 100644 --- a/src/fmtlib_format.cpp +++ b/src/fmtlib_format.cpp @@ -6,6 +6,11 @@ // // For the license information refer to format.h. +#if __has_include() +#include +#endif +#if !defined(__cpp_lib_format) || (__cpp_lib_format < 201907L) + #include "fmt/format-inl.h" FMT_BEGIN_NAMESPACE @@ -42,3 +47,4 @@ template FMT_API void buffer::append(const wchar_t*, const wchar_t*); } // namespace detail FMT_END_NAMESPACE +#endif // (__cplusplus < 202002L) diff --git a/src/fmtlib_os.cpp b/src/fmtlib_os.cpp index 3338d13cae8..b74fa0645e3 100644 --- a/src/fmtlib_os.cpp +++ b/src/fmtlib_os.cpp @@ -6,6 +6,11 @@ // // For the license information refer to format.h. +#if __has_include() +#include +#endif +#if !defined(__cpp_lib_format) || (__cpp_lib_format < 201907L) + // Disable bogus MSVC warnings. #if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) # define _CRT_SECURE_NO_WARNINGS @@ -396,3 +401,4 @@ file_buffer::~file_buffer() { ostream::~ostream() = default; #endif // FMT_USE_FCNTL FMT_END_NAMESPACE +#endif // (__cplusplus < 202002L) diff --git a/src/info.cpp b/src/info.cpp index 37f6b58f24a..8b83be4cd27 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -43,15 +43,15 @@ #include "region.h" #include "update.h" #include "variable.h" -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -#include "fmt/chrono.h" -#endif #include #include #include #include #include +#if __has_include() +#include +#endif #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -272,16 +272,9 @@ void Info::command(int narg, char **arg) if (out == nullptr) return; fputs("\nInfo-Info-Info-Info-Info-Info-Info-Info-Info-Info-Info\n",out); -#if defined(FMT_STATIC_THOUSANDS_SEPARATOR) - { - time_t tv = time(nullptr); - struct tm *now = localtime(&tv); - utils::print(out, "Printed on {}", asctime(now)); - } -#else - std::tm now = fmt::localtime(std::time(nullptr)); - utils::print(out,"Printed on {}", std::asctime(&now)); -#endif + time_t tv = time(nullptr); + struct tm *now = localtime(&tv); + utils::print(out, "Printed on {}", asctime(now)); if (flags & CONFIG) { utils::print(out,"\nLAMMPS version: {} / {}\n", lmp->version, lmp->num_ver); @@ -1334,6 +1327,7 @@ std::string Info::get_fft_info() } /* ---------------------------------------------------------------------- */ +#if !defined(__cpp_lib_format) || (__cpp_lib_format < 201907L) static constexpr int fmt_ver_major = FMT_VERSION / 10000; static constexpr int fmt_ver_minor = (FMT_VERSION % 10000) / 100; @@ -1344,6 +1338,12 @@ std::string Info::get_fmt_info() return fmt::format("Embedded fmt library version: {}.{}.{}\n", fmt_ver_major, fmt_ver_minor, fmt_ver_patch); } +#else +std::string Info::get_fmt_info() +{ + return "Using fmt library emulation with std::format\n"; +} +#endif /* ---------------------------------------------------------------------- */ diff --git a/src/timer.cpp b/src/timer.cpp index 3ba84d3b1a6..b3eb22fdb9f 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -15,12 +15,10 @@ #include "comm.h" #include "error.h" -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -#include "fmt/chrono.h" -#endif #include "tokenizer.h" #include +#include #include using namespace LAMMPS_NS; @@ -219,7 +217,7 @@ double Timer::get_timeout_remain() namespace { const std::array timer_style = {"off", "loop", "normal", "full"}; const std::array timer_mode = {"nosync", "(dummy)", "sync"}; -} +} // namespace void Timer::modify_params(int narg, char **arg) { @@ -269,15 +267,12 @@ void Timer::modify_params(int narg, char **arg) // format timeout setting std::string timeout = "off"; if (_timeout >= 0.0) { -#if defined(FMT_STATIC_THOUSANDS_SEPARATOR) - char outstr[200]; - struct tm *tv = gmtime(&((time_t) _timeout)); - strftime(outstr, 200, "%02d:%M:%S", tv); - timeout = outstr; -#else - std::tm tv = fmt::gmtime((std::time_t) _timeout); - timeout = fmt::format("{:02d}:{:%M:%S}", tv.tm_yday * 24 + tv.tm_hour, tv); -#endif + // round to seconds and break down to hours, minutes, and seconds + auto tmptime = lround(_timeout); + auto hours = tmptime / 3600L; + auto minutes = (tmptime % 3600L) / 60L; + auto seconds = tmptime % 60L; + timeout = fmt::format("{:02d}:{:02d}:{:02d}", hours, minutes, seconds); } utils::logmesg(lmp, "New timer settings: style={} mode={} timeout={}\n", timer_style[_level], diff --git a/src/utils.cpp b/src/utils.cpp index 9e52f9b949b..59cb3c4dc25 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -20,9 +20,6 @@ #include "domain.h" #include "error.h" #include "fix.h" -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -#include "fmt/chrono.h" -#endif #include "info.h" #include "input.h" #include "label_map.h" @@ -1575,6 +1572,71 @@ size_t utils::trim_and_count_words(const std::string &text, const std::string &s return utils::count_words(trim_comment(text), separators); } +/* ---------------------------------------------------------------------- + combine values in vector to single string with separator added between values +------------------------------------------------------------------------- */ +namespace { +template std::string join_impl(const std::vector &values, const std::string &sep) +{ + std::string result; + + if (values.size() > 0) result = fmt::format("{}", values[0]); + for (std::size_t i = 1; i < values.size(); ++i) result += sep + fmt::format("{}", values[i]); + + return result; +} +} // namespace + +// specializations +template <> std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> +std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> +std::string utils::join(const std::vector &values, + const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> +std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> +std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> +std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +template <> +std::string utils::join(const std::vector &values, const std::string &sep) +{ + return join_impl(values, sep); +} + +// clang-format on + /* ---------------------------------------------------------------------- combine words in vector to single string with separator added between words ------------------------------------------------------------------------- */ @@ -1966,21 +2028,16 @@ int utils::date2num(const std::string &date) } /* ---------------------------------------------------------------------- - get formatted string of current date from fmtlib + get formatted string of current date ------------------------------------------------------------------------- */ std::string utils::current_date() { time_t tv = time(nullptr); -#if defined(FMT_STATIC_THOUSANDS_SEPARATOR) - char outstr[200]; struct tm *today = localtime(&tv); - strftime(outstr, 200, "%Y-%m-%d", today); + char outstr[16]; + strftime(outstr, sizeof(outstr), "%Y-%m-%d", today); return std::string(outstr); -#else - std::tm today = fmt::localtime(tv); - return fmt::format("{:%Y-%m-%d}", today); -#endif } /* ---------------------------------------------------------------------- diff --git a/src/utils.h b/src/utils.h index c1388a6e5fc..a5c84ef3bb9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -707,10 +707,26 @@ size_t count_words(const char *text); size_t trim_and_count_words(const std::string &text, const std::string &separators = " \t\r\n\f"); +/*! Take list of values and join them with a given separator text. + * + * This is the inverse operation of what the Tokenizer classes do. + * This is a generalization of the join_words() function and similar + * to fmt::join() but only supports the vector STL container, and to + * use the begin/end iterator version you have to use: + * `utils::join(std::vector(x.begin(), x.end()), sep);`. + * This approach can also be used to support other STL containers. + * + * \param values STL vector with values + * \param sep separator string (may be empty) + * \return string with the concatenated values and separators */ + +template +std::string join(const std::vector &values, const std::string &sep); + /*! Take list of words and join them with a given separator text. * * This is the inverse operation of what the split_words() function - * Tokenizer classes do. + * and Tokenizer classes do. * * \param words STL vector with strings * \param sep separator string (may be empty) diff --git a/src/variable.cpp b/src/variable.cpp index 5b0492dd1c8..320e22e7321 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -39,8 +39,6 @@ #include "universe.h" #include "update.h" -#include "fmt/ranges.h" - #include #include #include @@ -557,7 +555,7 @@ void Variable::set(int narg, char **arg) vecs[ivar].dynamic = 0; parse_vector(ivar, data[ivar][0]); std::vector vec(vecs[ivar].values, vecs[ivar].values + vecs[ivar].n); - data[ivar][1] = utils::strdup(fmt::format("[{}]", fmt::join(vec, ","))); + data[ivar][1] = utils::strdup(fmt::format("[{}]", utils::join(vec, ","))); } eval_in_progress[ivar] = 0; replaceflag = 1; @@ -576,7 +574,7 @@ void Variable::set(int narg, char **arg) vecs[nvar].dynamic = 0; parse_vector(nvar, data[nvar][0]); std::vector vec(vecs[nvar].values, vecs[nvar].values + vecs[nvar].n); - data[nvar][1] = utils::strdup(fmt::format("[{}]", fmt::join(vec, ","))); + data[nvar][1] = utils::strdup(fmt::format("[{}]", utils::join(vec, ","))); } } @@ -1125,7 +1123,7 @@ char *Variable::retrieve(const char *name) compute_vector(ivar,&result); delete[] data[ivar][1]; std::vector vectmp(vecs[ivar].values,vecs[ivar].values + vecs[ivar].n); - std::string str = fmt::format("[{}]", fmt::join(vectmp,",")); + std::string str = fmt::format("[{}]", utils::join(vectmp,",")); data[ivar][1] = utils::strdup(str); } diff --git a/unittest/commands/test_simple_commands.cpp b/unittest/commands/test_simple_commands.cpp index a1a39d31462..6a369beff20 100644 --- a/unittest/commands/test_simple_commands.cpp +++ b/unittest/commands/test_simple_commands.cpp @@ -417,21 +417,23 @@ TEST_F(SimpleCommandsTest, Plugin) { const char *bindir = getenv("LAMMPS_PLUGIN_DIR"); if (!bindir) GTEST_SKIP() << "LAMMPS_PLUGIN_DIR not set"; - std::string loadfmt = "plugin load {}/{}plugin.so"; + auto loadcmd = fmt::format("plugin load {}/{}plugin.so", bindir, "hello"); ::testing::internal::CaptureStdout(); - lmp->input->one(fmt::format(fmt::runtime(loadfmt), bindir, "hello")); + lmp->input->one(loadcmd); auto text = ::testing::internal::GetCapturedStdout(); if (verbose) std::cout << text; ASSERT_THAT(text, ContainsRegex(".*\n.*Loading plugin: Hello world command.*")); ::testing::internal::CaptureStdout(); - lmp->input->one(fmt::format(fmt::runtime(loadfmt), bindir, "xxx")); + loadcmd = fmt::format("plugin load {}/{}plugin.so", bindir, "xxx"); + lmp->input->one(loadcmd); text = ::testing::internal::GetCapturedStdout(); if (verbose) std::cout << text; ASSERT_THAT(text, ContainsRegex(".*Open of file .*xxx.* failed.*")); ::testing::internal::CaptureStdout(); - lmp->input->one(fmt::format(fmt::runtime(loadfmt), bindir, "nve2")); + loadcmd = fmt::format("plugin load {}/{}plugin.so", bindir, "nve2"); + lmp->input->one(loadcmd); text = ::testing::internal::GetCapturedStdout(); if (verbose) std::cout << text; ASSERT_THAT(text, ContainsRegex(".*Loading plugin: NVE2 variant fix style.*")); @@ -443,7 +445,8 @@ TEST_F(SimpleCommandsTest, Plugin) ASSERT_THAT(text, ContainsRegex(".*2: fix style plugin nve2.*")); ::testing::internal::CaptureStdout(); - lmp->input->one(fmt::format(fmt::runtime(loadfmt), bindir, "hello")); + loadcmd = fmt::format("plugin load {}/{}plugin.so", bindir, "hello"); + lmp->input->one(loadcmd); text = ::testing::internal::GetCapturedStdout(); if (verbose) std::cout << text; ASSERT_THAT(text, ContainsRegex(".*Ignoring load of command style hello: " @@ -474,7 +477,8 @@ TEST_F(SimpleCommandsTest, Plugin) ASSERT_THAT(text, ContainsRegex(".*Ignoring unload of fix style nve: not from a plugin.*")); ::testing::internal::CaptureStdout(); - lmp->input->one(fmt::format(fmt::runtime(loadfmt), bindir, "hello")); + loadcmd = fmt::format("plugin load {}/{}plugin.so", bindir, "hello"); + lmp->input->one(loadcmd); text = ::testing::internal::GetCapturedStdout(); ::testing::internal::CaptureStdout(); lmp->input->one("plugin list"); @@ -491,7 +495,8 @@ TEST_F(SimpleCommandsTest, Plugin) ASSERT_THAT(text, ContainsRegex(".*Currently loaded plugins: 0\n$")); ::testing::internal::CaptureStdout(); - lmp->input->one(fmt::format(fmt::runtime(loadfmt), bindir, "no")); + loadcmd = fmt::format("plugin load {}/{}plugin.so", bindir, "no"); + lmp->input->one(loadcmd); lmp->input->one("plugin list"); text = ::testing::internal::GetCapturedStdout(); if (verbose) std::cout << text; diff --git a/unittest/utils/test_fmtlib.cpp b/unittest/utils/test_fmtlib.cpp index 11960dad4b4..638b956a12d 100644 --- a/unittest/utils/test_fmtlib.cpp +++ b/unittest/utils/test_fmtlib.cpp @@ -125,6 +125,8 @@ TEST(FmtLib, insert_neg_double) ASSERT_THAT(text, Eq("word -1.5")); } +// can check for runtime errors only with the real {fmt} lib +#if (__cplusplus < 202002L) TEST(FmtLib, int_for_double) { const double val = -1.5; @@ -148,3 +150,4 @@ TEST(FmtLib, int_for_string) ASSERT_THROW(auto text = fmt::format(fmt::runtime("word {:d}"), "I am not a number"), std::exception); } +#endif diff --git a/unittest/utils/test_lepton.cpp b/unittest/utils/test_lepton.cpp index bc436daddd1..021fcfae859 100644 --- a/unittest/utils/test_lepton.cpp +++ b/unittest/utils/test_lepton.cpp @@ -25,6 +25,7 @@ #include using LAMMPS_NS::utils::split_words; +using ::testing::ContainsRegex; using ::testing::StrEq; bool verbose = false; @@ -76,7 +77,8 @@ TEST_F(LeptonUtilsTest, substitute) try { LeptonUtils::substitute("v_none", lmp); } catch (std::exception &e) { - ASSERT_THAT(e.what(), StrEq("Variable none in expression v_none does not exist")); + ASSERT_THAT(e.what(), + ContainsRegex(".*Variable none in expression v_none does not exist.*")); caught = true; } ASSERT_TRUE(caught); diff --git a/unittest/utils/test_utils.cpp b/unittest/utils/test_utils.cpp index c1ed137511b..567c90fb364 100644 --- a/unittest/utils/test_utils.cpp +++ b/unittest/utils/test_utils.cpp @@ -180,10 +180,132 @@ TEST(Utils, count_words_with_extra_spaces) ASSERT_EQ(utils::count_words(" some text # comment "), 4); } +TEST(Utils, join) +{ + std::vector intvalues = {1, 2, 3}; + + auto combined = utils::join(intvalues, " "); + ASSERT_THAT(combined, StrEq("1 2 3")); + combined = utils::join(intvalues, ""); + ASSERT_THAT(combined, StrEq("123")); + intvalues[1] = 5; + combined = utils::join(intvalues, "__"); + ASSERT_THAT(combined, StrEq("1__5__3")); + intvalues.resize(1); + combined = utils::join(intvalues, "/"); + ASSERT_THAT(combined, StrEq("1")); + intvalues.emplace_back(4); + combined = utils::join(intvalues, "1"); + ASSERT_THAT(combined, StrEq("114")); + + std::vector longvalues = {1, 2, 3}; + + combined = utils::join(longvalues, " "); + ASSERT_THAT(combined, StrEq("1 2 3")); + combined = utils::join(longvalues, ""); + ASSERT_THAT(combined, StrEq("123")); + longvalues[1] = 5; + combined = utils::join(longvalues, "__"); + ASSERT_THAT(combined, StrEq("1__5__3")); + longvalues.resize(1); + combined = utils::join(longvalues, "/"); + ASSERT_THAT(combined, StrEq("1")); + longvalues.emplace_back(4); + combined = utils::join(longvalues, "1"); + ASSERT_THAT(combined, StrEq("114")); + + std::vector longlongvalues = {1, 2, 3}; + + combined = utils::join(longlongvalues, " "); + ASSERT_THAT(combined, StrEq("1 2 3")); + combined = utils::join(longlongvalues, ""); + ASSERT_THAT(combined, StrEq("123")); + longlongvalues[1] = 5; + combined = utils::join(longlongvalues, "__"); + ASSERT_THAT(combined, StrEq("1__5__3")); + longlongvalues.resize(1); + combined = utils::join(longlongvalues, "/"); + ASSERT_THAT(combined, StrEq("1")); + longlongvalues.emplace_back(4); + combined = utils::join(longlongvalues, "1"); + ASSERT_THAT(combined, StrEq("114")); + + std::vector floatvalues = {1.0, -2.000000001, 3.500001}; + + combined = utils::join(floatvalues, " "); + EXPECT_THAT(combined, StrEq("1 -2 3.500001")); + combined = utils::join(floatvalues, ""); + EXPECT_THAT(combined, StrEq("1-23.500001")); + floatvalues[1] = -5.05; + combined = utils::join(floatvalues, "__"); + EXPECT_THAT(combined, StrEq("1__-5.05__3.500001")); + floatvalues.resize(1); + combined = utils::join(floatvalues, "/"); + EXPECT_THAT(combined, StrEq("1")); + floatvalues.emplace_back(0.00000000004); + combined = utils::join(floatvalues, " "); + EXPECT_THAT(combined, StrEq("1 4e-11")); + + std::vector doublevalues = {1.0, -2.0000000000000002, 3.5000000000001}; + + combined = utils::join(doublevalues, " "); + EXPECT_THAT(combined, StrEq("1 -2 3.5000000000001")); + combined = utils::join(doublevalues, ""); + EXPECT_THAT(combined, StrEq("1-23.5000000000001")); + doublevalues[1] = -5.05; + combined = utils::join(doublevalues, "__"); + EXPECT_THAT(combined, StrEq("1__-5.05__3.5000000000001")); + doublevalues.resize(1); + combined = utils::join(doublevalues, "/"); + EXPECT_THAT(combined, StrEq("1")); + doublevalues.emplace_back(0.00000000004); + combined = utils::join(doublevalues, " "); + EXPECT_THAT(combined, StrEq("1 4e-11")); + + std::vector words = {"one", "two", "three"}; + + combined = utils::join(words, " "); + ASSERT_THAT(combined, StrEq("one two three")); + combined = utils::join(words, ""); + ASSERT_THAT(combined, StrEq("onetwothree")); + words[1] = "two "; + combined = utils::join(words, "__"); + ASSERT_THAT(combined, StrEq("one__two __three")); + words.resize(1); + combined = utils::join(words, "/"); + ASSERT_THAT(combined, StrEq("one")); + words.emplace_back(""); + combined = utils::join(words, "1"); + ASSERT_THAT(combined, StrEq("one1")); + words.clear(); + combined = utils::join(words, "."); + ASSERT_THAT(combined, StrEq("")); + + std::vector strings = {"one", "two", "three"}; + + combined = utils::join(strings, " "); + ASSERT_THAT(combined, StrEq("one two three")); + combined = utils::join(strings, ""); + ASSERT_THAT(combined, StrEq("onetwothree")); + strings[1] = "two "; + combined = utils::join(strings, "__"); + ASSERT_THAT(combined, StrEq("one__two __three")); + strings.resize(1); + combined = utils::join(strings, "/"); + ASSERT_THAT(combined, StrEq("one")); + strings.emplace_back(""); + combined = utils::join(strings, "1"); + ASSERT_THAT(combined, StrEq("one1")); + strings.clear(); + combined = utils::join(strings, "."); + ASSERT_THAT(combined, StrEq("")); +} + TEST(Utils, join_words) { std::vector words = {"one", "two", "three"}; - auto combined = utils::join_words(words, " "); + + auto combined = utils::join_words(words, " "); ASSERT_THAT(combined, StrEq("one two three")); combined = utils::join_words(words, ""); ASSERT_THAT(combined, StrEq("onetwothree")); @@ -196,6 +318,9 @@ TEST(Utils, join_words) words.emplace_back(""); combined = utils::join_words(words, "1"); ASSERT_THAT(combined, StrEq("one1")); + words.clear(); + combined = utils::join_words(words, "."); + ASSERT_THAT(combined, StrEq("")); } TEST(Utils, split_words_simple) @@ -1037,8 +1162,10 @@ TEST(Utils, parse_grid_id) TEST(Utils, errorurl) { - ASSERT_THAT(utils::errorurl(10), StrEq("\nFor more information see https://docs.lammps.org/err0010")); - ASSERT_THAT(utils::errorurl(0), StrEq("\nFor more information see https://docs.lammps.org/Errors_details.html")); + ASSERT_THAT(utils::errorurl(10), + StrEq("\nFor more information see https://docs.lammps.org/err0010")); + ASSERT_THAT(utils::errorurl(0), + StrEq("\nFor more information see https://docs.lammps.org/Errors_details.html")); ASSERT_THAT(utils::errorurl(-1), StrEq("")); }