From 483e880002beec76ef447d0c4b032363a2530a81 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 13 Feb 2026 22:59:46 -0500 Subject: [PATCH 01/15] add missing include --- src/compute_temp_deform.cpp | 2 ++ src/fix_wall_harmonic_outside.cpp | 2 ++ 2 files changed, 4 insertions(+) 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; /* ---------------------------------------------------------------------- */ From da88de06be28ee85a6d810a6d6229f73c22149e0 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 13:46:58 -0500 Subject: [PATCH 02/15] implement an utils::join() function to replace fmt::join() --- doc/src/Developer_utils.rst | 3 + src/utils.cpp | 65 +++++++++++++++++ src/utils.h | 18 ++++- unittest/utils/test_utils.cpp | 133 +++++++++++++++++++++++++++++++++- 4 files changed, 215 insertions(+), 4 deletions(-) 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/src/utils.cpp b/src/utils.cpp index 9e52f9b949b..827e0920ebe 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1575,6 +1575,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 ------------------------------------------------------------------------- */ 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/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("")); } From bc34ce43197f7344a010af7c0b20e5ba658ac437 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 13:48:22 -0500 Subject: [PATCH 03/15] replace use of fmt::join() with new utils::join() function --- src/KIM/kim_interactions.cpp | 9 +++------ src/KIM/kim_param.cpp | 4 +--- src/variable.cpp | 8 +++----- 3 files changed, 7 insertions(+), 14 deletions(-) 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/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); } From 4ae6fb24e8a16de317bb103bdfe302d1184ab4d9 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 19:11:28 -0500 Subject: [PATCH 04/15] get rid of any uses of fmt::localtime and use of the fmt/chrono.h header --- src/EXTRA-DUMP/dump_dcd.cpp | 20 ++++++++++++-------- src/info.cpp | 16 +++------------- src/timer.cpp | 18 ++++++------------ src/utils.cpp | 14 +++----------- 4 files changed, 24 insertions(+), 44 deletions(-) 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/info.cpp b/src/info.cpp index 37f6b58f24a..a6b35e76449 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -43,9 +43,6 @@ #include "region.h" #include "update.h" #include "variable.h" -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -#include "fmt/chrono.h" -#endif #include #include @@ -272,16 +269,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); diff --git a/src/timer.cpp b/src/timer.cpp index 3ba84d3b1a6..0e456339877 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -15,9 +15,6 @@ #include "comm.h" #include "error.h" -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -#include "fmt/chrono.h" -#endif #include "tokenizer.h" #include @@ -269,15 +266,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 / 60 / 24; + auto minutes = (tmptime / 60) - (hours * 24); + auto seconds = tmptime - ((hours * 24) + minutes) * 60; + 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 827e0920ebe..126f78d0112 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" @@ -2031,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 } /* ---------------------------------------------------------------------- From 3ec72358d21880f38858c807605b0567f9704b7f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 20:44:06 -0500 Subject: [PATCH 05/15] sync with pair_eam_fs.cpp --- src/APIP/pair_eam_fs_apip.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) 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); From 6ce656635e45eefbb3688409ecb26e1e9951d235 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 20:45:32 -0500 Subject: [PATCH 06/15] when using std::format() math.h and string.h are no longer implicitly included --- src/EFF/pair_eff_inline.h | 2 ++ src/EXTRA-FIX/fix_addtorque_group.cpp | 2 ++ src/GPU/pair_amoeba_gpu.cpp | 1 + src/INTEL/intel_preprocess.h | 2 ++ src/INTEL/math_extra_intel.h | 2 ++ src/INTEL/pair_dpd_intel.cpp | 1 + src/INTEL/pppm_intel.cpp | 1 + src/NETCDF/netcdf_units.cpp | 2 ++ src/OPENMP/npair_halffull_omp.cpp | 2 ++ src/timer.cpp | 3 ++- 10 files changed, 17 insertions(+), 1 deletion(-) 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-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/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/timer.cpp b/src/timer.cpp index 0e456339877..6aa30854a68 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -18,6 +18,7 @@ #include "tokenizer.h" #include +#include #include using namespace LAMMPS_NS; @@ -216,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) { From ae3ea14661714089216a82acce61a3322e23f7f7 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 20:47:23 -0500 Subject: [PATCH 07/15] emulate fmt::format with std::format when using C++20 and later --- src/LEPTON/lepton_utils.cpp | 36 ++++++++-------------- src/REAXFF/reaxff_control.cpp | 12 ++++---- src/fmt/format.h | 24 ++++++++++++++- src/fmtlib_format.cpp | 3 ++ src/fmtlib_os.cpp | 3 ++ src/info.cpp | 8 ++++- src/utils.cpp | 4 +-- unittest/commands/test_simple_commands.cpp | 19 +++++++----- unittest/utils/test_fmtlib.cpp | 3 ++ 9 files changed, 71 insertions(+), 41 deletions(-) diff --git a/src/LEPTON/lepton_utils.cpp b/src/LEPTON/lepton_utils.cpp index d41157471f6..889f37a52c3 100644 --- a/src/LEPTON/lepton_utils.cpp +++ b/src/LEPTON/lepton_utils.cpp @@ -22,8 +22,6 @@ #include "pair_zbl_const.h" #include "variable.h" -#include "fmt/args.h" - #include #include #include @@ -114,21 +112,21 @@ 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); + output.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()); + output.append(val); + output.push_back(c); } } else { if (hold == 'v') { @@ -136,33 +134,23 @@ 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); + const char *val = variable->retrieve(name.c_str()); + output.append(val); } - auto *variable = lmp->input->variable; - fmt::dynamic_format_arg_store args; - for (const auto &v : vars) { - const char *val = variable->retrieve(v.c_str()); - if (val) - args.push_back(fmt::arg(v.c_str(), val)); - else - throw VariableException(v, in); - } - return fmt::vformat(format, args); + return output; } 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/fmt/format.h b/src/fmt/format.h index 8cdf95b7bdf..66ef0e6a6c3 100644 --- a/src/fmt/format.h +++ b/src/fmt/format.h @@ -33,6 +33,28 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +#if (__cplusplus >= 202002L) + +// 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 + +#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 +4536,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..d4ea2845adb 100644 --- a/src/fmtlib_format.cpp +++ b/src/fmtlib_format.cpp @@ -6,6 +6,8 @@ // // For the license information refer to format.h. +#if (__cplusplus < 202002L) + #include "fmt/format-inl.h" FMT_BEGIN_NAMESPACE @@ -42,3 +44,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..fdc87203703 100644 --- a/src/fmtlib_os.cpp +++ b/src/fmtlib_os.cpp @@ -6,6 +6,8 @@ // // For the license information refer to format.h. +#if (__cplusplus < 202002L) + // Disable bogus MSVC warnings. #if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) # define _CRT_SECURE_NO_WARNINGS @@ -396,3 +398,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 a6b35e76449..1df02110df7 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -1324,7 +1324,7 @@ std::string Info::get_fft_info() } /* ---------------------------------------------------------------------- */ - +#if (__cplusplus < 202002L) static constexpr int fmt_ver_major = FMT_VERSION / 10000; static constexpr int fmt_ver_minor = (FMT_VERSION % 10000) / 100; static constexpr int fmt_ver_patch = FMT_VERSION % 100; @@ -1334,6 +1334,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/utils.cpp b/src/utils.cpp index 126f78d0112..59cb3c4dc25 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1580,8 +1580,8 @@ template std::string join_impl(const std::vector &values, const { 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]); + 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; } 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 From 0e98b986de83b45658b6364a73efa1d8238da7bc Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 22:26:06 -0500 Subject: [PATCH 08/15] throw error on non-existing variable --- src/LEPTON/lepton_utils.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/LEPTON/lepton_utils.cpp b/src/LEPTON/lepton_utils.cpp index 889f37a52c3..d02926bd6f8 100644 --- a/src/LEPTON/lepton_utils.cpp +++ b/src/LEPTON/lepton_utils.cpp @@ -17,6 +17,7 @@ #include "lepton_utils.h" +#include "error.h" #include "input.h" #include "lammps.h" #include "pair_zbl_const.h" @@ -59,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]; @@ -120,12 +118,15 @@ std::string LeptonUtils::substitute(const std::string &in, LAMMPS_NS::LAMMPS *lm for (const auto &c : in) { if (in_var) { if (isalnum(c) || (c == '_')) { - output.push_back(c); name.push_back(c); } else { in_var = false; const char *val = variable->retrieve(name.c_str()); - output.append(val); + if (val) + output.append(val); + else + lmp->error->all(FLERR, "Variable {} in expression {} does not exist", name, in); + output.push_back(c); } } else { @@ -149,8 +150,11 @@ std::string LeptonUtils::substitute(const std::string &in, LAMMPS_NS::LAMMPS *lm } if (in_var) { const char *val = variable->retrieve(name.c_str()); - output.append(val); + if (val) + output.append(val); + else + lmp->error->all(FLERR, "Variable {} in expression {} does not exist", name, in); } - return output; + return output; } From cbdb8ad4fc302f69f388d26f1bb56881e00aa98f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 22:26:30 -0500 Subject: [PATCH 09/15] use regex instead of string compare to check exception --- unittest/utils/test_lepton.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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); From 2d44a215d9a7c88ef1e9b4465ce4ff1844f8d926 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 22:49:16 -0500 Subject: [PATCH 10/15] update docs for transition from fmt::format to std::format --- doc/src/Developer_code_design.rst | 68 ++++++++++++++++--------------- doc/src/Developer_notes.rst | 6 ++- doc/src/Developer_unittest.rst | 2 +- doc/src/Modify_requirements.rst | 13 ++++-- doc/src/Modify_style.rst | 5 ++- 5 files changed, 53 insertions(+), 41 deletions(-) diff --git a/doc/src/Developer_code_design.rst b/doc/src/Developer_code_design.rst index e38f843cdc3..a580d9ec928 100644 --- a/doc/src/Developer_code_design.rst +++ b/doc/src/Developer_code_design.rst @@ -294,36 +294,38 @@ 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 +We strongly 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 +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. + +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 @@ -341,8 +343,8 @@ 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,8 +406,10 @@ 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 `_. diff --git a/doc/src/Developer_notes.rst b/doc/src/Developer_notes.rst index 7807a2b494b..38ce18bdc48 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_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/Modify_requirements.rst b/doc/src/Modify_requirements.rst index 9495c9cb3aa..b16bf522bee 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 From c4040aca7030783b9e3d14d8619d443aead084ab Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 23:13:40 -0500 Subject: [PATCH 11/15] correct output format conversion code --- src/timer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/timer.cpp b/src/timer.cpp index 6aa30854a68..b3eb22fdb9f 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -269,9 +269,9 @@ void Timer::modify_params(int narg, char **arg) if (_timeout >= 0.0) { // round to seconds and break down to hours, minutes, and seconds auto tmptime = lround(_timeout); - auto hours = tmptime / 60 / 24; - auto minutes = (tmptime / 60) - (hours * 24); - auto seconds = tmptime - ((hours * 24) + minutes) * 60; + auto hours = tmptime / 3600L; + auto minutes = (tmptime % 3600L) / 60L; + auto seconds = tmptime % 60L; timeout = fmt::format("{:02d}:{:02d}:{:02d}", hours, minutes, seconds); } From 44a79db04c98939e57ffb931254747046106584e Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Feb 2026 23:14:28 -0500 Subject: [PATCH 12/15] consistently link to https://cppreference.com/ URLs --- doc/src/Developer_code_design.rst | 16 ++++++++-------- doc/src/Developer_notes.rst | 2 +- doc/src/Developer_platform.rst | 4 ++-- doc/src/Modify_requirements.rst | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/src/Developer_code_design.rst b/doc/src/Developer_code_design.rst index a580d9ec928..f3ba0694fed 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. @@ -295,9 +295,9 @@ Furthermore, output should generally only be done by MPI rank 0 function `. We strongly discourage the use of `stringstreams -`_ because +`_ because the bundled `{fmt} library `_ or the `C++ format -library `_ (for +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 @@ -310,7 +310,7 @@ 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 +`_. 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 @@ -336,7 +336,7 @@ 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., @@ -408,11 +408,11 @@ first will be the value shown and the second the minimum width. For more details and examples, please consult the `{fmt} syntax documentation `_ website or the `corresponding C++ syntax reference -`_. Since +`_. 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 38ce18bdc48..6da4fd7395a 100644 --- a/doc/src/Developer_notes.rst +++ b/doc/src/Developer_notes.rst @@ -291,7 +291,7 @@ 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) or the `C++ format -library `_ (for +library `_ (for C++20 and later) and thus allow processing similar to the "format()" functionality in Python. 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/Modify_requirements.rst b/doc/src/Modify_requirements.rst index b16bf522bee..10d777e1f2d 100644 --- a/doc/src/Modify_requirements.rst +++ b/doc/src/Modify_requirements.rst @@ -315,11 +315,11 @@ The new policy encourages more specific error messages that ideally indicate the cause directly, and requiring no further lookup. This is aided by either the `{fmt} library `_ or the `C++ format library -`_ (for C++20 +`_ (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 +`_. Error messages should still preferably be kept to a single line or two lines at most. From 3a95fcb473f52a622f775d894a169efc19066c50 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 16 Feb 2026 19:27:51 -0500 Subject: [PATCH 13/15] check for __cpp_lib_format specifically instead of __cplusplus --- src/fmt/format.h | 2 +- src/fmtlib_format.cpp | 2 +- src/fmtlib_os.cpp | 2 +- src/info.cpp | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/fmt/format.h b/src/fmt/format.h index 66ef0e6a6c3..325fb545080 100644 --- a/src/fmt/format.h +++ b/src/fmt/format.h @@ -33,7 +33,7 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#if (__cplusplus >= 202002L) +#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 diff --git a/src/fmtlib_format.cpp b/src/fmtlib_format.cpp index d4ea2845adb..3475e80f609 100644 --- a/src/fmtlib_format.cpp +++ b/src/fmtlib_format.cpp @@ -6,7 +6,7 @@ // // For the license information refer to format.h. -#if (__cplusplus < 202002L) +#if !defined(__cpp_lib_format) || (__cpp_lib_format < 201907L) #include "fmt/format-inl.h" diff --git a/src/fmtlib_os.cpp b/src/fmtlib_os.cpp index fdc87203703..b7bc48036c1 100644 --- a/src/fmtlib_os.cpp +++ b/src/fmtlib_os.cpp @@ -6,7 +6,7 @@ // // For the license information refer to format.h. -#if (__cplusplus < 202002L) +#if !defined(__cpp_lib_format) || (__cpp_lib_format < 201907L) // Disable bogus MSVC warnings. #if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) diff --git a/src/info.cpp b/src/info.cpp index 1df02110df7..ff6494b0d66 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -1324,7 +1324,8 @@ std::string Info::get_fft_info() } /* ---------------------------------------------------------------------- */ -#if (__cplusplus < 202002L) +#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; static constexpr int fmt_ver_patch = FMT_VERSION % 100; From d3dca676d161f04ca3012ef445181fd8892abb54 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 16 Feb 2026 19:56:54 -0500 Subject: [PATCH 14/15] be more paranoid and include if available which should define __cpp_lib_xxx --- src/fmt/format.h | 3 +++ src/fmtlib_format.cpp | 3 +++ src/fmtlib_os.cpp | 3 +++ src/info.cpp | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/fmt/format.h b/src/fmt/format.h index 325fb545080..d695a489a1d 100644 --- a/src/fmt/format.h +++ b/src/fmt/format.h @@ -33,6 +33,9 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +#if __has_include() +#include +#endif #if defined(__cpp_lib_format) && (__cpp_lib_format >= 201907L) // when compiling for C++20 or later we emulate diff --git a/src/fmtlib_format.cpp b/src/fmtlib_format.cpp index 3475e80f609..e9dbff99a2f 100644 --- a/src/fmtlib_format.cpp +++ b/src/fmtlib_format.cpp @@ -6,6 +6,9 @@ // // 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" diff --git a/src/fmtlib_os.cpp b/src/fmtlib_os.cpp index b7bc48036c1..b74fa0645e3 100644 --- a/src/fmtlib_os.cpp +++ b/src/fmtlib_os.cpp @@ -6,6 +6,9 @@ // // 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. diff --git a/src/info.cpp b/src/info.cpp index ff6494b0d66..8b83be4cd27 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -49,6 +49,9 @@ #include #include #include +#if __has_include() +#include +#endif #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN From da63e59e2f51aaed584eec13cc58cdb7268abaea Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 16 Feb 2026 20:17:19 -0500 Subject: [PATCH 15/15] add notes about compiler compatibility --- doc/src/Developer_code_design.rst | 15 ++++++++++----- src/fmt/format.h | 7 +++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/doc/src/Developer_code_design.rst b/doc/src/Developer_code_design.rst index f3ba0694fed..04ca8e7ad9e 100644 --- a/doc/src/Developer_code_design.rst +++ b/doc/src/Developer_code_design.rst @@ -310,11 +310,16 @@ 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. +`_. 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 diff --git a/src/fmt/format.h b/src/fmt/format.h index d695a489a1d..cdfbf5f6c2b 100644 --- a/src/fmt/format.h +++ b/src/fmt/format.h @@ -33,6 +33,7 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +// LAMMPS CUSTOMIZATION #if __has_include() #include #endif @@ -42,6 +43,12 @@ // 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