From 7e70a04b9834ea7d04c1b7dcbc5fe78d00686b7d Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 11:20:58 -0500 Subject: [PATCH 01/10] params_parser: +has --- src/psc_bgk_util/params_parser.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index 767ddcb26..dcc195aee 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -39,6 +39,8 @@ public: } } + bool has(const std::string paramName) { return params.count(paramName) == 1; } + template T get(const std::string paramName); From 604343a997650fb5e148f9814e69fa322a25baf0 Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 11:25:15 -0500 Subject: [PATCH 02/10] params_parser: mv non-template impls into class --- src/psc_bgk_util/params_parser.hxx | 87 ++++++++++++++---------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index dcc195aee..c7b4206bb 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -45,60 +45,61 @@ public: T get(const std::string paramName); template - T getOrDefault(const std::string paramName, T deflt); - - template - T getAndWarnOrDefault(const std::string paramName, T deflt); - - // return true and display warning iff paramName is present - bool warnIfPresent(const std::string paramName, const std::string advice); - -private: - /// @brief Wrapper for retrieving an unparsed value with an enhanced error - /// message. - /// @param paramName the name of the parameter to fetch - /// @throws std::out_of_range (with an error message that specifies - /// `paramName`) if the parameter is not present - /// @return the unparsed parameter value (i.e., as a string) - std::string _get_inner(const std::string paramName); -}; + T getOrDefault(const std::string paramName, T deflt) + { + if (has(paramName)) { + return get(paramName); + } -// implementations + std::cout << "Warning: using default value for parameter '" << paramName + << "': " << deflt << "\n"; + return deflt; + } -template -T ParsedParams::getOrDefault(const std::string paramName, T deflt) -{ - if (params.count(paramName) == 1) - return get(paramName); - std::cout << "Warning: using default value for parameter '" << paramName - << "': " << deflt << "\n"; - return deflt; -} + template + T getAndWarnOrDefault(const std::string paramName, T deflt) + { + if (!has(paramName)) { + return deflt; + } -template -T ParsedParams::getAndWarnOrDefault(const std::string paramName, T deflt) -{ - if (params.count(paramName) == 1) { T val = get(paramName); std::cout << "Warning: using non-default value for parameter '" << paramName << "': " << val << "\nDefault value of '" << deflt << "' is recommended.\n"; return val; } - return deflt; -} -bool ParsedParams::warnIfPresent(const std::string paramName, - const std::string advice) -{ - if (params.count(paramName) == 1) { + // return true and display warning iff paramName is present + bool warnIfPresent(const std::string paramName, const std::string advice) + { + if (!has(paramName)) { + return false; + } std::cout << "Warning: parameter " << paramName << " is deprecated.\n" << advice << "\n"; return true; } - return false; -} + +private: + /// @brief Wrapper for retrieving an unparsed value with an enhanced error + /// message. + /// @param paramName the name of the parameter to fetch + /// @throws std::out_of_range (with an error message that specifies + /// `paramName`) if the parameter is not present + /// @return the unparsed parameter value (i.e., as a string) + std::string _get_inner(const std::string paramName) + { + if (has(paramName)) { + return params.at(paramName); + } + + throw std::out_of_range("missing required input parameter: " + paramName); + } +}; + +// get implementations template <> bool ParsedParams::get(const std::string paramName) @@ -131,11 +132,3 @@ std::string ParsedParams::get(const std::string paramName) { return _get_inner(paramName); } - -std::string ParsedParams::_get_inner(const std::string paramName) -{ - if (params.count(paramName) == 0) { - throw std::out_of_range("missing required input parameter: " + paramName); - } - return params.at(paramName); -} \ No newline at end of file From 39a29f91f72188d1bcca377ee9f2d1f134566d2b Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 11:33:36 -0500 Subject: [PATCH 03/10] params_parser: rename to _getUnparsed --- src/psc_bgk_util/params_parser.hxx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index c7b4206bb..3d82004f1 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -83,13 +83,9 @@ public: } private: - /// @brief Wrapper for retrieving an unparsed value with an enhanced error - /// message. - /// @param paramName the name of the parameter to fetch - /// @throws std::out_of_range (with an error message that specifies - /// `paramName`) if the parameter is not present - /// @return the unparsed parameter value (i.e., as a string) - std::string _get_inner(const std::string paramName) + /// Retrieves an unparsed value, throwing a helpful error if the parameter is + /// missing. + std::string _getUnparsed(const std::string paramName) { if (has(paramName)) { return params.at(paramName); @@ -105,30 +101,30 @@ template <> bool ParsedParams::get(const std::string paramName) { bool b; - std::istringstream(_get_inner(paramName)) >> std::boolalpha >> b; + std::istringstream(_getUnparsed(paramName)) >> std::boolalpha >> b; return b; } template <> double ParsedParams::get(const std::string paramName) { - return std::stod(_get_inner(paramName)); + return std::stod(_getUnparsed(paramName)); } template <> int ParsedParams::get(const std::string paramName) { - return std::stoi(_get_inner(paramName)); + return std::stoi(_getUnparsed(paramName)); } template <> float ParsedParams::get(const std::string paramName) { - return std::stof(_get_inner(paramName)); + return std::stof(_getUnparsed(paramName)); } template <> std::string ParsedParams::get(const std::string paramName) { - return _get_inner(paramName); + return _getUnparsed(paramName); } From c880abbbba48a0a10747c7d5826e993569c3e984 Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 13:07:11 -0500 Subject: [PATCH 04/10] params_parser: more documentation --- src/psc_bgk_util/params_parser.hxx | 45 ++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index 3d82004f1..fc81a66bf 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -6,11 +6,22 @@ #include #include -// ====================================================================== -// ParsedParams -// A simple parser intended for reading run parameters from a file, rather than -// hard-coding them. See psc_bgk_util/sample_bgk_params.txt for an example. - +/// @brief A parser that reads a dict-like map of parameter names to +/// parameter values from a single input file. The input file syntax is +/// extremely simple: +/// ```txt +/// param1 val1 +/// param2 val2 thiswordisignored andsoisthis +/// ``` +/// The first (space-separated) word in each line is interpreted as a parameter +/// name, and the second word is interpreted as a value. Words after the first +/// two are silently ignored, allowing for comments. If a parameter is +/// duplicated, all values but the last are silently ignored. +/// +/// This class has no knowledge about what parameters should or shouldn't be +/// present, nor does it know what types its values should have. A value isn't +/// parsed to a specific type (e.g. `double`) until it is actually accessed as +/// that type by a user. class ParsedParams { private: @@ -39,11 +50,24 @@ public: } } + /// @brief Check if the parameter is present. + /// @param paramName name of parameter + /// @return whether or not the parameter is present bool has(const std::string paramName) { return params.count(paramName) == 1; } + /// @brief Get a parameter, parsing it to the given type. + /// @tparam T type of parameter + /// @param paramName name of parameter + /// @return the parameter template T get(const std::string paramName); + /// @brief Get a parameter if it's there, otherwise + /// return the given default value. + /// @tparam T type of parameter + /// @param paramName name of parameter + /// @param deflt default value of parameter + /// @return the parameter template T getOrDefault(const std::string paramName, T deflt) { @@ -56,6 +80,12 @@ public: return deflt; } + /// @brief Get a parameter and display a warning if it's there, otherwise + /// return the given default value. + /// @tparam T type of parameter + /// @param paramName name of parameter + /// @param deflt default value of parameter + /// @return the parameter template T getAndWarnOrDefault(const std::string paramName, T deflt) { @@ -70,7 +100,10 @@ public: return val; } - // return true and display warning iff paramName is present + /// @brief Display a warning if a parameter is present. + /// @param paramName name of parameter + /// @param advice user-friendly instructions on what to do instead + /// @return whether or not the parameter was present bool warnIfPresent(const std::string paramName, const std::string advice) { if (!has(paramName)) { From be31e71afb2806d10359508a0a38685090f150de Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 13:50:37 -0500 Subject: [PATCH 05/10] params_parser: better bool parse --- src/psc_bgk_util/params_parser.hxx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index fc81a66bf..cebca4b45 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -133,9 +133,17 @@ private: template <> bool ParsedParams::get(const std::string paramName) { - bool b; - std::istringstream(_getUnparsed(paramName)) >> std::boolalpha >> b; - return b; + auto lowercase = _getUnparsed(paramName); + std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), + [](unsigned char c) { return std::tolower(c); }); + + if (lowercase == "true") { + return true; + } else if (lowercase == "false") { + return false; + } else { + throw std::invalid_argument(_getUnparsed(paramName)); + } } template <> From 780f1e46724d729b8e187d7ff1bef75beb9e52b2 Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 13:52:25 -0500 Subject: [PATCH 06/10] params_parser: add todos --- src/psc_bgk_util/params_parser.hxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index cebca4b45..25e95ecff 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -6,6 +6,9 @@ #include #include +// TODO in c++20, use std::format +// TODO use LOG_WARNING and LOG_ERROR, but ideally only on 1 proc + /// @brief A parser that reads a dict-like map of parameter names to /// parameter values from a single input file. The input file syntax is /// extremely simple: From 6fd2039ac639a63827784a2d2dff50959c5a46dd Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 14:29:31 -0500 Subject: [PATCH 07/10] params_parser: wrap get in nicer error --- src/psc_bgk_util/params_parser.hxx | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index 25e95ecff..44c6d5ac4 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -63,7 +63,19 @@ public: /// @param paramName name of parameter /// @return the parameter template - T get(const std::string paramName); + T get(const std::string paramName) + { + try { + return _getParsed(paramName); + } catch (const std::invalid_argument& e) { + std::string unparsed = _getUnparsed(paramName); + // TODO ensure human-readable type name + std::cerr << "ERROR Unable to parse parameter '" << paramName + << "', which has value '" << unparsed << "', to type " + << typeid(T).name() << "\n"; + abort(); + } + } /// @brief Get a parameter if it's there, otherwise /// return the given default value. @@ -119,8 +131,8 @@ public: } private: - /// Retrieves an unparsed value, throwing a helpful error if the parameter is - /// missing. + /// Retrieves an unparsed value, throwing a helpful error if the parameter + /// is missing. std::string _getUnparsed(const std::string paramName) { if (has(paramName)) { @@ -129,12 +141,16 @@ private: throw std::out_of_range("missing required input parameter: " + paramName); } + + /// Retrieve and parse a value, possibly throwing std::invalid_argument + template + T _getParsed(const std::string paramName); }; // get implementations template <> -bool ParsedParams::get(const std::string paramName) +bool ParsedParams::_getParsed(const std::string paramName) { auto lowercase = _getUnparsed(paramName); std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), @@ -150,25 +166,25 @@ bool ParsedParams::get(const std::string paramName) } template <> -double ParsedParams::get(const std::string paramName) +double ParsedParams::_getParsed(const std::string paramName) { return std::stod(_getUnparsed(paramName)); } template <> -int ParsedParams::get(const std::string paramName) +int ParsedParams::_getParsed(const std::string paramName) { return std::stoi(_getUnparsed(paramName)); } template <> -float ParsedParams::get(const std::string paramName) +float ParsedParams::_getParsed(const std::string paramName) { return std::stof(_getUnparsed(paramName)); } template <> -std::string ParsedParams::get(const std::string paramName) +std::string ParsedParams::_getParsed(const std::string paramName) { return _getUnparsed(paramName); } From 0cc89ca861f313235efe7cc78525c2afe5251881 Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 14:34:18 -0500 Subject: [PATCH 08/10] params_parser; *: rename to InputParams --- src/psc_bgk.cxx | 2 +- src/psc_bgk_util/bgk_params.hxx | 2 +- src/psc_bgk_util/params_parser.hxx | 14 +++++++------- src/psc_shock.cxx | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/psc_bgk.cxx b/src/psc_bgk.cxx index b58190eb6..8b0228d72 100644 --- a/src/psc_bgk.cxx +++ b/src/psc_bgk.cxx @@ -66,7 +66,7 @@ void setupParameters(int argc, char** argv) exit(1); } std::string path_to_params(argv[1]); - ParsedParams parsedParams(path_to_params); + InputParams parsedParams(path_to_params); ic_table = new Table(parsedParams.get("path_to_data")); g.loadParams(parsedParams, *ic_table); diff --git a/src/psc_bgk_util/bgk_params.hxx b/src/psc_bgk_util/bgk_params.hxx index 71ab27ac8..d85a5430f 100644 --- a/src/psc_bgk_util/bgk_params.hxx +++ b/src/psc_bgk_util/bgk_params.hxx @@ -106,7 +106,7 @@ struct PscBgkParams double rel_box_size_3; // length of 3rd dimension in calculated units int n_patches_3; // number of patches in 3rd dimension - void loadParams(ParsedParams parsedParams, Table& ic_table) + void loadParams(InputParams parsedParams, Table& ic_table) { box_size = parsedParams.getAndWarnOrDefault("box_size", -1); rel_box_size = parsedParams.getOrDefault("rel_box_size", 1); diff --git a/src/psc_bgk_util/params_parser.hxx b/src/psc_bgk_util/params_parser.hxx index 44c6d5ac4..a80499620 100644 --- a/src/psc_bgk_util/params_parser.hxx +++ b/src/psc_bgk_util/params_parser.hxx @@ -25,13 +25,13 @@ /// present, nor does it know what types its values should have. A value isn't /// parsed to a specific type (e.g. `double`) until it is actually accessed as /// that type by a user. -class ParsedParams +class InputParams { private: std::unordered_map params; public: - ParsedParams(const std::string file_path) + InputParams(const std::string file_path) { // iterate over each line std::ifstream ifs(file_path); @@ -150,7 +150,7 @@ private: // get implementations template <> -bool ParsedParams::_getParsed(const std::string paramName) +bool InputParams::_getParsed(const std::string paramName) { auto lowercase = _getUnparsed(paramName); std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), @@ -166,25 +166,25 @@ bool ParsedParams::_getParsed(const std::string paramName) } template <> -double ParsedParams::_getParsed(const std::string paramName) +double InputParams::_getParsed(const std::string paramName) { return std::stod(_getUnparsed(paramName)); } template <> -int ParsedParams::_getParsed(const std::string paramName) +int InputParams::_getParsed(const std::string paramName) { return std::stoi(_getUnparsed(paramName)); } template <> -float ParsedParams::_getParsed(const std::string paramName) +float InputParams::_getParsed(const std::string paramName) { return std::stof(_getUnparsed(paramName)); } template <> -std::string ParsedParams::_getParsed(const std::string paramName) +std::string InputParams::_getParsed(const std::string paramName) { return _getUnparsed(paramName); } diff --git a/src/psc_shock.cxx b/src/psc_shock.cxx index 85874b9e7..55dbdf116 100644 --- a/src/psc_shock.cxx +++ b/src/psc_shock.cxx @@ -87,7 +87,7 @@ void setupParameters(int argc, char** argv) exit(1); } std::string path_to_params(argv[1]); - ParsedParams parsedParams(path_to_params); + InputParams parsedParams(path_to_params); psc_params.stats_every = 1000; psc_params.cfl = parsedParams.getOrDefault("cfl", .75); From 3d1dd91d0b9a0630aaa0f8777d6049badbf7b6a5 Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 14:37:49 -0500 Subject: [PATCH 09/10] input_params; *: mv to input_params --- .../params_parser.hxx => include/input_params.hxx} | 0 src/psc_bgk.cxx | 2 +- src/psc_bgk_util/bgk_params.hxx | 4 +++- src/psc_shock.cxx | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) rename src/{psc_bgk_util/params_parser.hxx => include/input_params.hxx} (100%) diff --git a/src/psc_bgk_util/params_parser.hxx b/src/include/input_params.hxx similarity index 100% rename from src/psc_bgk_util/params_parser.hxx rename to src/include/input_params.hxx diff --git a/src/psc_bgk.cxx b/src/psc_bgk.cxx index 8b0228d72..0141ec7c2 100644 --- a/src/psc_bgk.cxx +++ b/src/psc_bgk.cxx @@ -6,10 +6,10 @@ #include "DiagnosticsDefault.h" #include "OutputFieldsDefault.h" #include "psc_config.hxx" +#include "input_params.hxx" #include "psc_bgk_util/bgk_params.hxx" #include "psc_bgk_util/table.hxx" -#include "psc_bgk_util/params_parser.hxx" // ====================================================================== // PSC configuration diff --git a/src/psc_bgk_util/bgk_params.hxx b/src/psc_bgk_util/bgk_params.hxx index d85a5430f..ee5f6d3f2 100644 --- a/src/psc_bgk_util/bgk_params.hxx +++ b/src/psc_bgk_util/bgk_params.hxx @@ -1,7 +1,9 @@ #pragma once #include -#include "params_parser.hxx" + +#include "../include/input_params.hxx" + #include "table.hxx" // ====================================================================== diff --git a/src/psc_shock.cxx b/src/psc_shock.cxx index 55dbdf116..f89761745 100644 --- a/src/psc_shock.cxx +++ b/src/psc_shock.cxx @@ -5,8 +5,7 @@ #include "DiagnosticsDefault.h" #include "OutputFieldsDefault.h" #include "psc_config.hxx" - -#include "psc_bgk_util/params_parser.hxx" +#include "input_params.hxx" // ====================================================================== // PSC configuration From 07dd4e7b6314542a954ea84744b516654131e265 Mon Sep 17 00:00:00 2001 From: James McClung Date: Mon, 29 Dec 2025 14:51:22 -0500 Subject: [PATCH 10/10] docs: note InputParams as an alternative --- docs/guide/code/case.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide/code/case.rst b/docs/guide/code/case.rst index bb4e10253..223d9d0c2 100644 --- a/docs/guide/code/case.rst +++ b/docs/guide/code/case.rst @@ -10,6 +10,8 @@ The goal is to have PSC cases to be self-contained, ie., everything related to p * One cannot just change a parameter in the case and run it -- the code needs to be recompiled first. +Alternatively, use an InputParams object to load parameters from an input file at runtime. See ``src/include/input_params.hxx``. + Changing / adding a case ========================