From 79edb4a3bf46cec2041437402bfb6ff9af1e62ed Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Tue, 20 Oct 2026 21:54:21 +0800 Subject: [PATCH 1/8] feat(input): validate integer choices from shared parameter metadata --- docs/developers_guide/index.rst | 1 + docs/developers_guide/input_validation.md | 69 ++++++++++ docs/generate_input_main.py | 5 + docs/test_generate_input_main.py | 21 +++ source/Makefile.Objects | 1 + source/source_io/CMakeLists.txt | 1 + source/source_io/input_help.cpp | 9 ++ source/source_io/input_help.h | 1 + .../module_parameter/input_domain.cpp | 120 ++++++++++++++++ .../source_io/module_parameter/input_domain.h | 42 ++++++ .../source_io/module_parameter/input_item.h | 3 + .../module_parameter/read_inp_estruc.cpp | 12 +- .../source_io/module_parameter/read_input.cpp | 61 +++++++- .../source_io/module_parameter/read_input.h | 6 +- .../module_parameter/test/CMakeLists.txt | 5 + .../test/test_input_domain.cpp | 60 ++++++++ source/source_io/test/read_input_ptest.cpp | 4 +- source/source_io/test_serial/CMakeLists.txt | 7 + .../test_serial/read_input_item_test.cpp | 25 +++- .../source_io/test_serial/test_read_input.cpp | 130 ++++++++++++++++++ 20 files changed, 560 insertions(+), 23 deletions(-) create mode 100644 docs/developers_guide/input_validation.md create mode 100644 docs/test_generate_input_main.py create mode 100644 source/source_io/module_parameter/input_domain.cpp create mode 100644 source/source_io/module_parameter/input_domain.h create mode 100644 source/source_io/module_parameter/test/test_input_domain.cpp create mode 100644 source/source_io/test_serial/test_read_input.cpp diff --git a/docs/developers_guide/index.rst b/docs/developers_guide/index.rst index 80224240428..35b27bc838e 100644 --- a/docs/developers_guide/index.rst +++ b/docs/developers_guide/index.rst @@ -11,4 +11,5 @@ This section provides guidelines and resources for developers working on the ABA basic_types_class.md input_availability.md + input_validation.md agent_governance.md diff --git a/docs/developers_guide/input_validation.md b/docs/developers_guide/input_validation.md new file mode 100644 index 00000000000..2b76abca651 --- /dev/null +++ b/docs/developers_guide/input_validation.md @@ -0,0 +1,69 @@ +# Shared INPUT domain validation + +`Input_Item::domain` declares legal values once for runtime checks, built-in +help and the generated INPUT reference. The initial operator supports scalar +integer choices. For example: + +```cpp +item.type = "Integer"; +item.domain = InputDomain(&Input_para::nspin, {1, 2, 4}); +``` + +The field binding is typed and read-only. The validator does not branch on +parameter names and does not format final values and parse them back. Adding +another integer-choice parameter requires its binding and choices, not a new +validation callback. A domain must have a non-null binding, at least one choice, +no duplicate choices and matching `Integer` metadata. + +## Preparation order + +After collecting INPUT tokens, the reader calls `prepare_input` once: + +1. Reject missing values for supplied keywords. Validate all explicitly supplied + domains, including complete integer tokens and exactly one value. Collect + independent errors before running any value reader or default/reset hook. +2. Run the existing readers, contextual default/reset hooks and alias + normalization. +3. Validate the effective values, including omitted parameters' defaults, + through the same domains. + +Errors identify the file, parameter, supplied or resolved value, legal choices +and manual section. An invalid explicit value cannot be made acceptable by a +later reset. The domain validator never changes values. Domain errors are +broadcast before all ranks stop, ahead of parameter broadcasts. Normal startup +and `--check-input` use this same path. + +`nspin` and `gga_grad` are the first migrated parameters. Their legal choices +and defaults are unchanged. Inputs such as `nspin 2abc`, `nspin 2 4` and a bare +`nspin` are now rejected instead of accepting a numeric prefix, ignoring extra +tokens or overlooking a final keyword without a value. Legal explicit zero +remains a value, not a request for a default. + +## Documentation + +The C++ registration is the source of truth. `--generate-parameters-yaml` +exports a typed `allowed_values` sequence, and `generate_input_main.py` renders +it as **Allowed values**. `abacus -h ` uses the same domain. Generated +YAML and Markdown must not be committed. + +Only hard legal values belong in the domain. Recommended values and bounds +appearing in default formulas must not be converted into restrictions without +checking their documented meaning. + +## Incremental scope + +This first slice establishes shared domain checks, not a replacement for all +existing input processing. Parameters without a declared domain retain their +existing behavior. Legacy cross-parameter checks, including the requirement +for `nspin=4` with `noncolin` or `lspinorb`, still run after the required context +is available. [Availability](input_availability.md) describes applicability; +it is not automatically a hard runtime constraint. + +Further migrations should extend common domain/condition operators and add +rule data. They should also separate explicit inputs from computed defaults so +that defaults can only fill omitted fields; the existing reset callbacks are +not yet restricted by this PR. In particular, this initial domain check does +not detect a legacy reset changing one legal value into a different legal value. +Ranges, conditional requirements, vector lengths and structured source-line +diagnostics are subsequent extensions of the shared engine, not reasons to +introduce parameter-specific validators. diff --git a/docs/generate_input_main.py b/docs/generate_input_main.py index 89a74521045..370adaf5d45 100755 --- a/docs/generate_input_main.py +++ b/docs/generate_input_main.py @@ -174,6 +174,11 @@ def generate_parameter_markdown(param: Dict[str, str], type_text = escape_md_text(normalize_type(str(param['type']))) lines.append(f"- **Type**: {type_text}") + # Legal values from the same domain used by runtime validation. + if param.get('allowed_values'): + choices = ', '.join(escape_md_text(value) for value in param['allowed_values']) + lines.append(f"- **Allowed values**: {choices}") + # Availability (before description, as in original format) if param.get('availability', '') != '': lines.append("- **Availability**: *" diff --git a/docs/test_generate_input_main.py b/docs/test_generate_input_main.py new file mode 100644 index 00000000000..463b6115755 --- /dev/null +++ b/docs/test_generate_input_main.py @@ -0,0 +1,21 @@ +"""Checks for legal-value metadata in the generated INPUT reference.""" + +import unittest + +from generate_input_main import generate_parameter_markdown + + +class InputDomainDocumentationTest(unittest.TestCase): + def test_integer_choices_include_zero(self): + rendered = generate_parameter_markdown( + {"name": "gga_grad", "type": "Integer", "allowed_values": [0, 1, 2]}, {} + ) + self.assertIn("- **Allowed values**: 0, 1, 2", rendered) + + def test_undeclared_domain_is_not_an_empty_restriction(self): + rendered = generate_parameter_markdown({"name": "legacy", "type": "Real"}, {}) + self.assertNotIn("Allowed values", rendered) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 191f5e4b781..3beb3b0916e 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -743,6 +743,7 @@ OBJS_IO=module_parameter/input_conv.o\ parse_args.o\ module_parameter/availability.o\ module_parameter/availability_validator.o\ + module_parameter/input_domain.o\ module_parameter/read_input.o\ module_parameter/read_inp_sys.o\ module_parameter/read_inp_estruc.o\ diff --git a/source/source_io/CMakeLists.txt b/source/source_io/CMakeLists.txt index cca93932acc..5b22db52022 100644 --- a/source/source_io/CMakeLists.txt +++ b/source/source_io/CMakeLists.txt @@ -138,6 +138,7 @@ add_library( module_parameter/read_inp_out.cpp module_parameter/availability.cpp module_parameter/availability_validator.cpp + module_parameter/input_domain.cpp module_parameter/read_input.cpp module_parameter/read_set_globalv.cpp ) diff --git a/source/source_io/input_help.cpp b/source/source_io/input_help.cpp index 9681f717841..a0ad430b27b 100644 --- a/source/source_io/input_help.cpp +++ b/source/source_io/input_help.cpp @@ -371,6 +371,7 @@ void ParameterHelp::build_registry() { meta.default_value = item.default_value; meta.unit = item.unit; meta.availability = item.get_availability(); + meta.allowed_values = item.domain.format_choices(); // Pre-compute lowercase name for fast fuzzy matching meta.name_lowercase = to_lowercase(item.label); @@ -399,6 +400,10 @@ bool ParameterHelp::show_parameter_help(const std::string& key, std::ostream& os os << "Parameter: " << meta.name << "\n"; os << "Type: " << meta.type << "\n"; + if (!meta.allowed_values.empty()) { + os << "Allowed values: " << meta.allowed_values << "\n"; + } + if (!meta.default_value.empty()) { os << "Default: " << meta.default_value << "\n"; } @@ -495,6 +500,10 @@ void ParameterHelp::generate_yaml(std::ostream& os) { os << " category: " << yaml_quote_if_needed(item.category) << "\n"; os << " type: " << yaml_quote_if_needed(item.type) << "\n"; + if (!item.domain.empty()) { + os << " allowed_values: [" << item.domain.format_choices() << "]\n"; + } + // Description uses block scalar os << " description: "; if (item.description.empty()) { diff --git a/source/source_io/input_help.h b/source/source_io/input_help.h index 375dbc513fd..85f3f469552 100644 --- a/source/source_io/input_help.h +++ b/source/source_io/input_help.h @@ -20,6 +20,7 @@ struct ParameterMetadata { std::string category; std::string unit; // Empty string if no unit std::string availability; // Empty string if always available + std::string allowed_values; // Rendered from the runtime domain; empty if not declared std::string name_lowercase; // Pre-computed lowercase for fast fuzzy matching }; diff --git a/source/source_io/module_parameter/input_domain.cpp b/source/source_io/module_parameter/input_domain.cpp new file mode 100644 index 00000000000..3ee3bc507a8 --- /dev/null +++ b/source/source_io/module_parameter/input_domain.cpp @@ -0,0 +1,120 @@ +#include "input_domain.h" +#include "input_parameter.h" + +#include +#include +#include + +namespace ModuleIO +{ + +InputDomain::InputDomain(int Input_para::* member, const std::vector& choices) + : member_(member), choices_(choices) +{ + if (member_ == nullptr || choices_.empty()) + { + throw std::invalid_argument("An integer INPUT domain requires a field binding and nonempty legal values."); + } + std::vector unique_choices = choices_; + std::sort(unique_choices.begin(), unique_choices.end()); + if (std::adjacent_find(unique_choices.begin(), unique_choices.end()) != unique_choices.end()) + { + throw std::invalid_argument("An integer INPUT domain must not contain duplicate legal values."); + } +} + +bool InputDomain::empty() const +{ + return member_ == nullptr; +} + +const std::vector& InputDomain::integer_choices() const +{ + return choices_; +} + +std::string InputDomain::format_choices() const +{ + std::ostringstream out; + for (size_t i = 0; i < choices_.size(); ++i) + { + if (i != 0) + { + out << ", "; + } + out << choices_[i]; + } + return out.str(); +} + +bool InputDomain::contains(int value) const +{ + return std::find(choices_.begin(), choices_.end(), value) != choices_.end(); +} + +std::string InputDomain::diagnostic(const std::string& label, + const std::string& value, + const std::string& origin) const +{ + std::string anchor = label; + std::replace(anchor.begin(), anchor.end(), '_', '-'); + return "Invalid INPUT parameter '" + label + "': " + origin + " '" + value + + "'. Expected one Integer from {" + format_choices() + "}.\n" + + "See https://abacus.deepmodeling.com/en/latest/advanced/input_files/input-main.html#" + anchor + "\n"; +} + +std::string InputDomain::validate_explicit(const std::string& label, const std::vector& tokens) const +{ + if (empty()) + { + return ""; + } + if (tokens.size() == 1) + { + try + { + size_t consumed = 0; + const int value = std::stoi(tokens.front(), &consumed); + if (consumed == tokens.front().size() && contains(value)) + { + return ""; + } + } + catch (const std::invalid_argument&) + { + // Report malformed input using the same domain diagnostic. + } + catch (const std::out_of_range&) + { + // An integer overflow is an input error, not a default request. + } + } + std::ostringstream supplied; + for (size_t i = 0; i < tokens.size(); ++i) + { + if (i != 0) + { + supplied << ' '; + } + supplied << tokens[i]; + } + const std::string value = supplied.str(); + return diagnostic(label, value, "supplied"); +} + +std::string InputDomain::validate_resolved(const std::string& label, const Input_para& input) const +{ + if (empty()) + { + return ""; + } + const int value = input.*member_; + if (contains(value)) + { + return ""; + } + const std::string rendered = std::to_string(value); + return diagnostic(label, rendered, "resolved value"); +} + +} // namespace ModuleIO diff --git a/source/source_io/module_parameter/input_domain.h b/source/source_io/module_parameter/input_domain.h new file mode 100644 index 00000000000..3e930fe7879 --- /dev/null +++ b/source/source_io/module_parameter/input_domain.h @@ -0,0 +1,42 @@ +#ifndef MODULE_IO_INPUT_DOMAIN_H +#define MODULE_IO_INPUT_DOMAIN_H + +#include +#include + +struct Input_para; + +namespace ModuleIO +{ + +/** + * Declarative legal values and a typed, read-only binding to an INPUT field. + * The initial domain operator is membership in a set of scalar integers. + * An empty domain leaves a parameter on its existing validation path. + */ +class InputDomain +{ + public: + InputDomain() = default; + InputDomain(int Input_para::* member, const std::vector& choices); + + bool empty() const; + const std::vector& integer_choices() const; + std::string format_choices() const; + + // Return an empty string on success, otherwise a diagnostic with legal + // values and a manual link. Neither operation modifies input values. + std::string validate_explicit(const std::string& label, const std::vector& tokens) const; + std::string validate_resolved(const std::string& label, const Input_para& input) const; + + private: + int Input_para::* member_ = nullptr; + std::vector choices_; + + bool contains(int value) const; + std::string diagnostic(const std::string& label, const std::string& value, const std::string& origin) const; +}; + +} // namespace ModuleIO + +#endif // MODULE_IO_INPUT_DOMAIN_H diff --git a/source/source_io/module_parameter/input_item.h b/source/source_io/module_parameter/input_item.h index 0e78f4e32a2..3b168604980 100644 --- a/source/source_io/module_parameter/input_item.h +++ b/source/source_io/module_parameter/input_item.h @@ -8,6 +8,7 @@ #include "source_io/module_parameter/parameter.h" #include "source_io/module_parameter/availability.h" +#include "source_io/module_parameter/input_domain.h" namespace ModuleIO { class Input_Item @@ -31,6 +32,7 @@ class Input_Item description = item.description; default_value = item.default_value; unit = item.unit; + domain = item.domain; availability_expr_ = item.availability_expr_; annotation = item.annotation; read_value = item.read_value; @@ -49,6 +51,7 @@ class Input_Item std::string description; ///< full description (supports multi-line, lists, notes) std::string default_value; ///< default value as string std::string unit; ///< unit of measurement (empty if none) + InputDomain domain; ///< shared runtime validation and legal-value metadata /// Set and validate the canonical availability expression. An empty value /// means that the item is always available. void set_availability(const std::string& value) diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index b5841a81e31..9f36d687843 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -477,12 +477,9 @@ The other way is only available when compiling with LIBXC, and it allows for sup * Note: With nspin=4 no moment is ever autoset. The calculation starts from zero magnetic moment and a warning is printed; set 'mag' explicitly in STRU for the magnetic atoms if a magnetic ground state is expected.)"; item.default_value = "1"; item.unit = ""; + item.domain = InputDomain(&Input_para::nspin, {1, 2, 4}); read_sync_int(input.nspin); item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.nspin != 1 && para.input.nspin != 2 && para.input.nspin != 4) - { - ModuleBase::WARNING_QUIT("ReadInput", "nspin should be 1, 2 or 4."); - } if ((para.input.noncolin || para.input.lspinorb) && para.input.nspin != 4) { ModuleBase::WARNING_QUIT("ReadInput", "nspin must be 4 when noncolin or lspinorb is enabled."); @@ -502,13 +499,8 @@ The other way is only available when compiling with LIBXC, and it allows for sup For r = |m| and x = r/eta, S_eta = eta*x^3*(3*x^2 - 8*x + 6) for r < eta, and S_eta = r otherwise. The regularization is part of the functional definition, including its first and second derivatives. Mode 2 also uses this local map for the LDA contribution. Other spin configurations retain their existing behavior.)"; item.default_value = "0"; + item.domain = InputDomain(&Input_para::gga_grad, {0, 1, 2}); read_sync_int(input.gga_grad); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.gga_grad < 0 || para.input.gga_grad > 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "gga_grad must be 0, 1, or 2."); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 2de46279d8b..a5018b244b6 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -1,5 +1,8 @@ #include "read_input.h" #include "availability_validator.h" +#ifdef __MPI +#include "source_base/parallel_common.h" +#endif #include @@ -12,6 +15,7 @@ #include #include #include +#include #include "source_base/formatter.h" #include "source_base/global_file.h" #include "source_base/global_function.h" @@ -209,10 +213,21 @@ void ReadInput::read_parameters(Parameter& param, const std::string& filename_in ModuleBase::TITLE("ReadInput", "read_parameters"); // 1. only rank 0 read the input file + std::string input_error; if (this->rank == 0) { // We can also easily add other input file formats here - this->read_txt_input(param, filename_in); + input_error = this->read_txt_input(param, filename_in); + } +#ifdef __MPI + // All ranks must leave together before the parameter broadcasts below. + Parallel_Common::bcast_string(input_error); +#endif + if (!input_error.empty()) + { + const std::string message = "Invalid parameters in " + filename_in + ":\n" + input_error; + ModuleBase::WARNING_QUIT("ReadInput", message); + return; } // 2. check the number of atom types from STRU file @@ -323,7 +338,7 @@ void ReadInput::write_parameters(const Parameter& param, const std::string& file } } -void ReadInput::read_txt_input(Parameter& param, const std::string& filename) +std::string ReadInput::read_txt_input(Parameter& param, const std::string& filename) { ModuleBase::TITLE("ReadInput", "read_txt_input"); @@ -373,20 +388,19 @@ void ReadInput::read_txt_input(Parameter& param, const std::string& filename) while (ascii_stream.good()) { std::string word; // temporary variable to store the keyword read-in - ascii_stream >> word; - if (ascii_stream.eof()) { break; } + if (!(ascii_stream >> word)) { break; } word = FmtCore::lower(word); // the lowercase of the keyword auto it = std::find_if(input_lists.begin(), input_lists.end(), [&word](const std::pair& item) { return item.first == word; }); if (it != this->input_lists.end()) // find the keyword { Input_Item* p_item = &(it->second); - this->readvalue_items.push_back(p_item); - if(p_item->is_read()) + if (std::find(readvalue_items.begin(), readvalue_items.end(), p_item) != readvalue_items.end()) { std::string warningstr = "The parameter " + p_item->label + " has been read twice."; ModuleBase::WARNING_QUIT("ReadInput", warningstr); } + this->readvalue_items.push_back(p_item); // qianrui delete '/' 2024-07-10, because path has '/' head. read_information(ascii_stream, p_item->str_values, "#!"); } @@ -422,6 +436,30 @@ void ReadInput::read_txt_input(Parameter& param, const std::string& filename) } } + return this->prepare_input(param); +} + +std::string ReadInput::prepare_input(Parameter& param) +{ + // Validate original tokens before any reader or default hook can discard + // an invalid value. In particular, std::stoi alone accepts numeric prefixes. + std::string errors; + for (const auto* item : this->readvalue_items) + { + if (item->str_values.empty() && item->domain.empty()) + { + errors += "Invalid INPUT parameter '" + item->label + "': missing value (" + item->type + ").\n"; + } + else + { + errors += item->domain.validate_explicit(item->label, item->str_values); + } + } + if (!errors.empty()) + { + return errors; + } + // 1) read the value of the parameters for (auto& readvalue_item: this->readvalue_items) { @@ -441,6 +479,13 @@ void ReadInput::read_txt_input(Parameter& param, const std::string& filename) this->normalize_hs_output_options(param); this->normalize_occ_mat_options(param); + + // Check effective values, including defaults, through the same domains. + for (const auto& entry : this->input_lists) + { + errors += entry.second.domain.validate_resolved(entry.first, param.inp); + } + return errors; } void ReadInput::normalize_occ_mat_options(Parameter& param) @@ -692,6 +737,10 @@ int ReadInput::current_md_step(const std::string& file_dir) void ReadInput::add_item(const Input_Item& item) { + if (!item.domain.empty() && item.type != "Integer") + { + throw std::invalid_argument("Integer domain attached to non-Integer INPUT parameter: " + item.label); + } // Normally only rank 0 reads the input file // But rank -1 is used for help system mode where items should also be added if (this->rank == 0 || this->rank == -1) diff --git a/source/source_io/module_parameter/read_input.h b/source/source_io/module_parameter/read_input.h index ac180dcd4ea..13d4e102fbe 100644 --- a/source/source_io/module_parameter/read_input.h +++ b/source/source_io/module_parameter/read_input.h @@ -67,8 +67,12 @@ class ReadInput * * @param param parameters of ABACUS * @param filename INPUT + * @return Empty on success, otherwise shared input-validation diagnostics. */ - void read_txt_input(Parameter& param, const std::string& filename); + std::string read_txt_input(Parameter& param, const std::string& filename); + /// Validate explicit domains, read values, resolve defaults and validate + /// resolved domains. Return diagnostics before entering the calculation. + std::string prepare_input(Parameter& param); /** * @brief Resolve the primary H/S output options and their legacy aliases. * diff --git a/source/source_io/module_parameter/test/CMakeLists.txt b/source/source_io/module_parameter/test/CMakeLists.txt index 6469e5b15c6..4f2e370c7e0 100644 --- a/source/source_io/module_parameter/test/CMakeLists.txt +++ b/source/source_io/module_parameter/test/CMakeLists.txt @@ -6,3 +6,8 @@ AddTest( ../availability.cpp ../availability_validator.cpp ) + +AddTest( + TARGET MODULE_IO_input_domain + SOURCES test_input_domain.cpp ../input_domain.cpp +) diff --git a/source/source_io/module_parameter/test/test_input_domain.cpp b/source/source_io/module_parameter/test/test_input_domain.cpp new file mode 100644 index 00000000000..bbd5961c706 --- /dev/null +++ b/source/source_io/module_parameter/test/test_input_domain.cpp @@ -0,0 +1,60 @@ +#include "../input_domain.h" +#include "../input_parameter.h" + +#include +#include + +TEST(InputDomain, UndeclaredDomainKeepsLegacyValidation) +{ + const ModuleIO::InputDomain domain; + const Input_para input; + EXPECT_TRUE(domain.empty()); + EXPECT_TRUE(domain.format_choices().empty()); + EXPECT_TRUE(domain.validate_explicit("legacy", {"any", "tokens"}).empty()); + EXPECT_TRUE(domain.validate_resolved("legacy", input).empty()); +} + +TEST(InputDomain, RejectsInvalidSchema) +{ + EXPECT_THROW(ModuleIO::InputDomain(nullptr, {1}), std::invalid_argument); + EXPECT_THROW(ModuleIO::InputDomain(&Input_para::nspin, {}), std::invalid_argument); + EXPECT_THROW(ModuleIO::InputDomain(&Input_para::nspin, {1, 2, 1}), std::invalid_argument); +} + +TEST(InputDomain, ParsesCompleteScalarIntegers) +{ + const ModuleIO::InputDomain domain(&Input_para::nspin, {1, 2, 4}); + for (const std::string token : {"1", "2", "4", "+2", "02"}) + { + EXPECT_TRUE(domain.validate_explicit("nspin", {token}).empty()) << token; + } + for (const std::vector tokens : { + std::vector{}, {""}, {"3"}, {"-1"}, {"2abc"}, {"2.0"}, {"2e0"}, + {"999999999999999999999999"}, {"2", "4"}}) + { + const std::string error = domain.validate_explicit("nspin", tokens); + EXPECT_NE(error.find("Invalid INPUT parameter 'nspin'"), std::string::npos); + EXPECT_NE(error.find("Expected one Integer from {1, 2, 4}"), std::string::npos); + EXPECT_NE(error.find("input-main.html#nspin"), std::string::npos); + } +} + +TEST(InputDomain, ValuesAreReadFromTypedBindingWithoutMutation) +{ + const ModuleIO::InputDomain domain(&Input_para::gga_grad, {-1, 0, 2}); + const ModuleIO::InputDomain copy = domain; + Input_para input; + for (const int value : {-1, 0, 2}) + { + input.gga_grad = value; + EXPECT_TRUE(copy.validate_resolved("gga_grad", input).empty()); + EXPECT_EQ(input.gga_grad, value); + } + input.gga_grad = 1; + const std::string error = copy.validate_resolved("gga_grad", input); + EXPECT_NE(error.find("resolved value '1'"), std::string::npos); + EXPECT_NE(error.find("input-main.html#gga-grad"), std::string::npos); + EXPECT_EQ(input.gga_grad, 1); + EXPECT_EQ(copy.integer_choices(), (std::vector{-1, 0, 2})); + EXPECT_EQ(copy.format_choices(), "-1, 0, 2"); +} diff --git a/source/source_io/test/read_input_ptest.cpp b/source/source_io/test/read_input_ptest.cpp index 90b92f50eb2..4e72ead7e47 100644 --- a/source/source_io/test/read_input_ptest.cpp +++ b/source/source_io/test/read_input_ptest.cpp @@ -558,14 +558,14 @@ TEST_F(InputParaTest, GgaGradAcceptedRange) item.str_values = {std::to_string(mode)}; item.read_value(item, param); EXPECT_EQ(param.inp.gga_grad, mode); - item.check_value(item, param); + EXPECT_TRUE(item.domain.validate_resolved(item.label, param.inp).empty()); } for (const int mode: {-1, 3}) { Parameter param; item.str_values = {std::to_string(mode)}; item.read_value(item, param); - EXPECT_EXIT(item.check_value(item, param), testing::ExitedWithCode(1), ""); + EXPECT_FALSE(item.domain.validate_resolved(item.label, param.inp).empty()); } } EXPECT_TRUE(found); diff --git a/source/source_io/test_serial/CMakeLists.txt b/source/source_io/test_serial/CMakeLists.txt index 3238fb743d4..b9a50c3ffee 100644 --- a/source/source_io/test_serial/CMakeLists.txt +++ b/source/source_io/test_serial/CMakeLists.txt @@ -25,6 +25,7 @@ add_library( ../module_parameter/read_set_globalv.cpp ../module_parameter/availability.cpp ../module_parameter/availability_validator.cpp + ../module_parameter/input_domain.cpp ) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/support DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) @@ -43,6 +44,12 @@ AddTest( SOURCES read_input_item_test.cpp ) +AddTest( + TARGET MODULE_IO_input_domain_reader + LIBS parameter base device io_input_serial + SOURCES test_read_input.cpp ../input_help.cpp +) + AddTest( TARGET MODULE_IO_read_input_tool SOURCES read_input_tool_test.cpp diff --git a/source/source_io/test_serial/read_input_item_test.cpp b/source/source_io/test_serial/read_input_item_test.cpp index 096b5c45150..73b30b1f594 100644 --- a/source/source_io/test_serial/read_input_item_test.cpp +++ b/source/source_io/test_serial/read_input_item_test.cpp @@ -49,6 +49,25 @@ class InputTest : public testing::Test } }; +TEST_F(InputTest, InvalidExplicitDomainDoesNotReachReadOrResetHooks) +{ + ModuleIO::ReadInput reader(0); + auto item = find_label("nspin", reader.input_lists); + ASSERT_NE(item, reader.input_lists.end()); + item->second.str_values = {"3"}; + bool reset_called = false; + item->second.reset_value = [&reset_called](const ModuleIO::Input_Item&, Parameter& input) { + reset_called = true; + TestParameters::input(input).nspin = 1; + }; + reader.readvalue_items.push_back(&item->second); + Parameter input; + const std::string error = reader.prepare_input(input); + EXPECT_THAT(error, testing::HasSubstr("supplied '3'")); + EXPECT_EQ(input.inp.nspin, 1); + EXPECT_FALSE(reset_called); +} + TEST_F(InputTest, RelaxMethod) { ModuleIO::ReadInput readinput(0); @@ -212,10 +231,8 @@ TEST_F(InputTest, Item_test) auto it = find_label("nspin", readinput.input_lists); TestParameters::input(param).noncolin = false; TestParameters::input(param).nspin = 3; - testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); - output = testing::internal::GetCapturedStdout(); - EXPECT_THAT(output, testing::HasSubstr("NOTICE")); + output = it->second.domain.validate_resolved(it->first, param.inp); + EXPECT_THAT(output, testing::HasSubstr("Expected one Integer from {1, 2, 4}")); } { // smearing_method auto it = find_label("smearing_method", readinput.input_lists); diff --git a/source/source_io/test_serial/test_read_input.cpp b/source/source_io/test_serial/test_read_input.cpp new file mode 100644 index 00000000000..66a0a1a962c --- /dev/null +++ b/source/source_io/test_serial/test_read_input.cpp @@ -0,0 +1,130 @@ +#include "source_io/module_parameter/read_input.h" +#include "source_io/input_help.h" + +#include +#include +#include +#include + +namespace +{ + +class InputDomainReaderTest : public testing::Test +{ + protected: + std::string filename; + + void SetUp() override + { + filename = testing::TempDir() + "abacus_input_domain_INPUT"; + } + + void TearDown() override + { + std::remove(filename.c_str()); + } + + void write_input(const std::string& values) + { + std::ofstream out(filename); + out << "INPUT_PARAMETERS\nbasis_type pw\nntype 1\necutwfc 20\n" << values; + ASSERT_TRUE(out.good()); + } + + std::string reject_input(const std::string& values) + { + write_input(values); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + testing::internal::CaptureStdout(); + EXPECT_EXIT(reader.read_parameters(input, filename), testing::ExitedWithCode(1), ""); + return testing::internal::GetCapturedStdout(); + } +}; + +TEST_F(InputDomainReaderTest, KeepsExplicitValuesAndContextualDefaults) +{ + for (const int spin : {1, 2, 4}) + { + for (const int grad : {0, 1, 2}) + { + const std::string values = "gga_grad " + std::to_string(grad) + "\nnspin " + std::to_string(spin) + "\n"; + write_input(values); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + EXPECT_EQ(input.inp.nspin, spin); + EXPECT_EQ(input.inp.gga_grad, grad); + const double expected_beta = spin == 1 ? 0.8 : 0.4; + EXPECT_DOUBLE_EQ(input.inp.mixing_beta, expected_beta); + } + } + + write_input(""); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + EXPECT_EQ(input.inp.nspin, 1); + EXPECT_EQ(input.inp.gga_grad, 0); +} + +TEST_F(InputDomainReaderTest, RejectsMalformedAndOutOfDomainExplicitValues) +{ + for (const std::string value : {"3", "0", "-1", "2abc", "2.0", "2 4", "999999999999999999999"}) + { + const std::string values = "nspin " + value + "\n"; + const std::string output = reject_input(values); + EXPECT_NE(output.find("supplied '" + value + "'"), std::string::npos); + EXPECT_NE(output.find("Expected one Integer from {1, 2, 4}"), std::string::npos); + EXPECT_NE(output.find("input-main.html#nspin"), std::string::npos); + EXPECT_NE(output.find(filename), std::string::npos); + } + const std::string output = reject_input("gga_grad 3\n"); + EXPECT_NE(output.find("Expected one Integer from {0, 1, 2}"), std::string::npos); + EXPECT_NE(output.find("input-main.html#gga-grad"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, ReportsIndependentErrorsTogether) +{ + const std::string output = reject_input("nspin 3\ngga_grad -1\n"); + EXPECT_NE(output.find("Invalid INPUT parameter 'nspin'"), std::string::npos); + EXPECT_NE(output.find("Invalid INPUT parameter 'gga_grad'"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, RejectsMissingValuesIncludingLastLineWithoutNewline) +{ + for (const std::string values : {"nspin\n", "nspin", "gga_grad # missing value\n"}) + { + const std::string output = reject_input(values); + EXPECT_NE(output.find("supplied ''"), std::string::npos); + } + const std::string output = reject_input("nspin\nnspin 2\n"); + EXPECT_NE(output.find("has been read twice"), std::string::npos); + const std::string legacy_output = reject_input("calculation"); + EXPECT_NE(legacy_output.find("'calculation': missing value (String)"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, RetainsExistingSpinDependencyCheck) +{ + const std::string output = reject_input("nspin 2\nnoncolin 1\n"); + EXPECT_NE(output.find("nspin must be 4 when noncolin or lspinorb is enabled"), std::string::npos); +} + +TEST(InputDomainHelp, ExportsTheRuntimeRules) +{ + const auto spin = ModuleIO::ParameterHelp::get_metadata("nspin"); + EXPECT_EQ(spin.allowed_values, "1, 2, 4"); + std::ostringstream help; + ASSERT_TRUE(ModuleIO::ParameterHelp::show_parameter_help("gga_grad", help)); + EXPECT_NE(help.str().find("Allowed values: 0, 1, 2"), std::string::npos); + std::ostringstream yaml; + ModuleIO::ParameterHelp::generate_yaml(yaml); + const std::string dump = yaml.str(); + EXPECT_NE(dump.find("allowed_values: [1, 2, 4]"), std::string::npos); + EXPECT_NE(dump.find("allowed_values: [0, 1, 2]"), std::string::npos); +} + +} // namespace From cee756dbef23cb16a19941732b8415844ecd12d9 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Tue, 20 Oct 2026 22:22:20 +0800 Subject: [PATCH 2/8] fix(input): preserve empty values for unmigrated parameters --- docs/developers_guide/input_validation.md | 8 ++-- .../source_io/module_parameter/read_input.cpp | 15 +++--- .../source_io/test_serial/read_input_test.cpp | 9 +++- .../source_io/test_serial/test_read_input.cpp | 48 ++++++++++++++++++- 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/docs/developers_guide/input_validation.md b/docs/developers_guide/input_validation.md index 2b76abca651..443706cef4d 100644 --- a/docs/developers_guide/input_validation.md +++ b/docs/developers_guide/input_validation.md @@ -19,8 +19,8 @@ no duplicate choices and matching `Integer` metadata. After collecting INPUT tokens, the reader calls `prepare_input` once: -1. Reject missing values for supplied keywords. Validate all explicitly supplied - domains, including complete integer tokens and exactly one value. Collect +1. Validate explicitly supplied parameters that have a declared domain, + including complete integer tokens and exactly one value. Collect independent errors before running any value reader or default/reset hook. 2. Run the existing readers, contextual default/reset hooks and alias normalization. @@ -54,7 +54,9 @@ checking their documented meaning. This first slice establishes shared domain checks, not a replacement for all existing input processing. Parameters without a declared domain retain their -existing behavior. Legacy cross-parameter checks, including the requirement +existing reader and EOF behavior. Their empty strings or vectors can be valid; +the shared checker does not reject empty values without a domain that requires +a value. Legacy cross-parameter checks, including the requirement for `nspin=4` with `noncolin` or `lspinorb`, still run after the required context is available. [Availability](input_availability.md) describes applicability; it is not automatically a hard runtime constraint. diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index a5018b244b6..b35cf71f537 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -395,6 +395,12 @@ std::string ReadInput::read_txt_input(Parameter& param, const std::string& filen if (it != this->input_lists.end()) // find the keyword { Input_Item* p_item = &(it->second); + // Preserve the legacy EOF handling until a domain declares the + // required token count. Some legacy readers accept empty values. + if (ascii_stream.eof() && p_item->domain.empty()) + { + break; + } if (std::find(readvalue_items.begin(), readvalue_items.end(), p_item) != readvalue_items.end()) { std::string warningstr = "The parameter " + p_item->label + " has been read twice."; @@ -446,14 +452,7 @@ std::string ReadInput::prepare_input(Parameter& param) std::string errors; for (const auto* item : this->readvalue_items) { - if (item->str_values.empty() && item->domain.empty()) - { - errors += "Invalid INPUT parameter '" + item->label + "': missing value (" + item->type + ").\n"; - } - else - { - errors += item->domain.validate_explicit(item->label, item->str_values); - } + errors += item->domain.validate_explicit(item->label, item->str_values); } if (!errors.empty()) { diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index e4390c60c29..1c4364f325b 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -414,10 +414,15 @@ TEST_F(InputTest, Check) readinput.clear(); } - ModuleIO::ReadInput::check_mode = true; Parameter param; testing::internal::CaptureStdout(); - EXPECT_EXIT(readinput.read_parameters(param, "./INPUT.ref"), ::testing::ExitedWithCode(0), ""); + EXPECT_EXIT( + { + ModuleIO::ReadInput::check_mode = true; + readinput.read_parameters(param, "./INPUT.ref"); + }, + ::testing::ExitedWithCode(0), + ""); std::string output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("INPUT parameters have been successfully checked!")); EXPECT_TRUE(std::remove("./INPUT.ref") == 0); diff --git a/source/source_io/test_serial/test_read_input.cpp b/source/source_io/test_serial/test_read_input.cpp index 66a0a1a962c..2c647bc29a8 100644 --- a/source/source_io/test_serial/test_read_input.cpp +++ b/source/source_io/test_serial/test_read_input.cpp @@ -22,6 +22,8 @@ class InputDomainReaderTest : public testing::Test void TearDown() override { std::remove(filename.c_str()); + const std::string written = filename + ".written"; + std::remove(written.c_str()); } void write_input(const std::string& values) @@ -103,8 +105,50 @@ TEST_F(InputDomainReaderTest, RejectsMissingValuesIncludingLastLineWithoutNewlin } const std::string output = reject_input("nspin\nnspin 2\n"); EXPECT_NE(output.find("has been read twice"), std::string::npos); - const std::string legacy_output = reject_input("calculation"); - EXPECT_NE(legacy_output.find("'calculation': missing value (String)"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, PreservesLegacyEmptyValues) +{ + write_input("pseudo_dir\norbital_dir\ndfpt_qfile\nout_pchg\n"); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + EXPECT_TRUE(input.inp.pseudo_dir.empty()); + EXPECT_TRUE(input.inp.orbital_dir.empty()); + EXPECT_TRUE(input.inp.dfpt_qfile.empty()); + EXPECT_TRUE(input.inp.out_pchg.empty()); +} + +TEST_F(InputDomainReaderTest, ReadsWriterOutputWithEmptyOptionalValues) +{ + write_input("nspin 2\ngga_grad 0\n"); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + const std::string written = filename + ".written"; + reader.write_parameters(input, written); + + ModuleIO::ReadInput roundtrip_reader(0); + roundtrip_reader.check_ntype_flag = false; + Parameter roundtrip; + roundtrip_reader.read_parameters(roundtrip, written); + EXPECT_EQ(roundtrip.inp.nspin, 2); + EXPECT_EQ(roundtrip.inp.gga_grad, 0); + EXPECT_EQ(roundtrip.inp.pseudo_dir, input.inp.pseudo_dir); + EXPECT_EQ(roundtrip.inp.dfpt_qfile, input.inp.dfpt_qfile); + EXPECT_EQ(roundtrip.inp.out_pchg, input.inp.out_pchg); +} + +TEST_F(InputDomainReaderTest, PreservesUnmigratedEndOfFileHandling) +{ + write_input("calculation"); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + EXPECT_EQ(input.inp.calculation, "scf"); } TEST_F(InputDomainReaderTest, RetainsExistingSpinDependencyCheck) From e04e56807da4b24819dd9e6641d5561add94dea9 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Thu, 22 Oct 2026 14:40:18 +0800 Subject: [PATCH 3/8] feat(input): share typed scalar and conditional validation operators --- docs/developers_guide/input_validation.md | 118 +++---- docs/generate_input_main.py | 4 + source/source_io/input_help.cpp | 14 +- source/source_io/input_help.h | 2 + .../module_parameter/input_domain.cpp | 323 ++++++++++++++---- .../source_io/module_parameter/input_domain.h | 78 +++-- .../module_parameter/read_inp_estruc.cpp | 4 +- .../source_io/module_parameter/read_input.cpp | 33 +- .../module_parameter/read_input_tool.h | 10 + .../module_parameter/test/CMakeLists.txt | 2 +- .../test/test_input_domain.cpp | 90 ++++- source/source_io/test/read_input_ptest.cpp | 4 +- .../test_serial/read_input_item_test.cpp | 2 +- 13 files changed, 516 insertions(+), 168 deletions(-) diff --git a/docs/developers_guide/input_validation.md b/docs/developers_guide/input_validation.md index 443706cef4d..d52d56f40b9 100644 --- a/docs/developers_guide/input_validation.md +++ b/docs/developers_guide/input_validation.md @@ -1,71 +1,63 @@ -# Shared INPUT domain validation +# Shared INPUT validation -`Input_Item::domain` declares legal values once for runtime checks, built-in -help and the generated INPUT reference. The initial operator supports scalar -integer choices. For example: +INPUT contracts are declared on `Input_Item::domain`. The common engine checks +scalar integer, real, Boolean and string values, finite real numbers, choices, +numerical bounds, and conditional requirements. It never branches on parameter +names. For example: ```cpp -item.type = "Integer"; -item.domain = InputDomain(&Input_para::nspin, {1, 2, 4}); +item.domain.set_choices({"1", "2"}); +item.domain.set_rule("value>0 and value<=1"); +item.domain.add_requirement("noncolin==true or lspinorb==true", "nspin==4"); ``` -The field binding is typed and read-only. The validator does not branch on -parameter names and does not format final values and parse them back. Adding -another integer-choice parameter requires its binding and choices, not a new -validation callback. A domain must have a non-null binding, at least one choice, -no duplicate choices and matching `Integer` metadata. +These are independent examples of declarations. Scalar readers register their +actual numeric/Boolean type automatically. The existing `sync_*` registrations +supply typed, read-only value access, including nested MD fields and vectors. +The checker does not infer token cardinality from a prose documentation type, +or format final values and parse them back. Custom input grammars remain in +their readers until their contracts are declared; legal empty strings and +vectors must remain accepted. -## Preparation order - -After collecting INPUT tokens, the reader calls `prepare_input` once: - -1. Validate explicitly supplied parameters that have a declared domain, - including complete integer tokens and exactly one value. Collect - independent errors before running any value reader or default/reset hook. -2. Run the existing readers, contextual default/reset hooks and alias - normalization. -3. Validate the effective values, including omitted parameters' defaults, - through the same domains. - -Errors identify the file, parameter, supplied or resolved value, legal choices -and manual section. An invalid explicit value cannot be made acceptable by a -later reset. The domain validator never changes values. Domain errors are -broadcast before all ranks stop, ahead of parameter broadcasts. Normal startup -and `--check-input` use this same path. - -`nspin` and `gga_grad` are the first migrated parameters. Their legal choices -and defaults are unchanged. Inputs such as `nspin 2abc`, `nspin 2 4` and a bare -`nspin` are now rejected instead of accepting a numeric prefix, ignoring extra -tokens or overlooking a final keyword without a value. Legal explicit zero -remains a value, not a request for a default. +Conditions reuse the parsed availability grammar (`and`, `or`, comparisons, +`in`, `contains`). Runtime requirements are explicit declarations: descriptive +availability alone does not imply that a disabled switch is an error. +References and operand types are checked when registrations are assembled. -## Documentation - -The C++ registration is the source of truth. `--generate-parameters-yaml` -exports a typed `allowed_values` sequence, and `generate_input_main.py` renders -it as **Allowed values**. `abacus -h ` uses the same domain. Generated -YAML and Markdown must not be committed. - -Only hard legal values belong in the domain. Recommended values and bounds -appearing in default formulas must not be converted into restrictions without -checking their documented meaning. - -## Incremental scope - -This first slice establishes shared domain checks, not a replacement for all -existing input processing. Parameters without a declared domain retain their -existing reader and EOF behavior. Their empty strings or vectors can be valid; -the shared checker does not reject empty values without a domain that requires -a value. Legacy cross-parameter checks, including the requirement -for `nspin=4` with `noncolin` or `lspinorb`, still run after the required context -is available. [Availability](input_availability.md) describes applicability; -it is not automatically a hard runtime constraint. +## Preparation order -Further migrations should extend common domain/condition operators and add -rule data. They should also separate explicit inputs from computed defaults so -that defaults can only fill omitted fields; the existing reset callbacks are -not yet restricted by this PR. In particular, this initial domain check does -not detect a legacy reset changing one legal value into a different legal value. -Ranges, conditional requirements, vector lengths and structured source-line -diagnostics are subsequent extensions of the shared engine, not reasons to -introduce parameter-specific validators. +1. Check all explicit domains before readers or reset hooks can hide invalid + input. Complete tokens and scalar cardinality are checked; real numbers + must be finite. Independent errors are collected. +2. Read typed values, retain snapshots of supplied values, and run existing + default/reset hooks and canonicalization. +3. Evaluate conditional requirements with supplied values taking precedence + over prepared defaults, so reset hooks cannot hide a conflicting request. +4. Check resolved values and requirements using the same engine. + +Errors identify the file, parameter, offending value, legal choices/rules, and +its manual URL. All ranks receive the error before parameter broadcasts. +Normal execution and `--check-input` share this preparation boundary. Existing +checks needing structure, build capabilities or calculation context continue +at their existing boundary. + +A private unset initializer does not belong to the public domain. For example, +a `{1, 2}` domain rejects explicit `-1` before defaults; omission skips the +first check and the derived value must satisfy the same domain. A documented +negative disabled mode or negative physical value has different semantics. +An effective domain can have an activation condition for unused defaults; +explicit supplied values are still checked. + +## Documentation and defaults + +Choices, value predicates and conditional requirements are exported to built-in +help, generated YAML and the INPUT manual from these declarations. Generated +YAML and Markdown are build artifacts and must not be committed. Recommended +values and bounds in a default formula are not automatically legal limits. + +Validation does not itself resolve defaults or make every legacy reset safe. +Default resolution should distinguish presence from numerical value, and fill +only omitted inputs. In particular `0`, `false` and a legal negative value are +explicit values. Defaults requiring structure/orbitals must be resolved when +those dependencies exist. Runtime numeric fields need not become nullable: +a separate input-preparation state can represent missing and pending values. diff --git a/docs/generate_input_main.py b/docs/generate_input_main.py index 370adaf5d45..deff16ef74c 100755 --- a/docs/generate_input_main.py +++ b/docs/generate_input_main.py @@ -179,6 +179,10 @@ def generate_parameter_markdown(param: Dict[str, str], choices = ', '.join(escape_md_text(value) for value in param['allowed_values']) lines.append(f"- **Allowed values**: {choices}") + for key, title in [('value_rule', 'Legal range'), ('requirements', 'Requirements')]: + if param.get(key): + lines.append(f"- **{title}**: " + link_availability(str(param[key]), name_anchors)) + # Availability (before description, as in original format) if param.get('availability', '') != '': lines.append("- **Availability**: *" diff --git a/source/source_io/input_help.cpp b/source/source_io/input_help.cpp index a0ad430b27b..c9910584935 100644 --- a/source/source_io/input_help.cpp +++ b/source/source_io/input_help.cpp @@ -372,6 +372,8 @@ void ParameterHelp::build_registry() { meta.unit = item.unit; meta.availability = item.get_availability(); meta.allowed_values = item.domain.format_choices(); + meta.value_rule = item.domain.format_rule(); + meta.requirements = item.domain.format_requirements(); // Pre-compute lowercase name for fast fuzzy matching meta.name_lowercase = to_lowercase(item.label); @@ -404,6 +406,9 @@ bool ParameterHelp::show_parameter_help(const std::string& key, std::ostream& os os << "Allowed values: " << meta.allowed_values << "\n"; } + if (!meta.value_rule.empty()) { os << "Legal range: " << meta.value_rule << "\n"; } + if (!meta.requirements.empty()) { os << "Requirements: " << meta.requirements << "\n"; } + if (!meta.default_value.empty()) { os << "Default: " << meta.default_value << "\n"; } @@ -500,10 +505,17 @@ void ParameterHelp::generate_yaml(std::ostream& os) { os << " category: " << yaml_quote_if_needed(item.category) << "\n"; os << " type: " << yaml_quote_if_needed(item.type) << "\n"; - if (!item.domain.empty()) { + if (!item.domain.choices().empty()) { os << " allowed_values: [" << item.domain.format_choices() << "]\n"; } + if (!item.domain.format_rule().empty()) { + os << " value_rule: " << yaml_quote_if_needed(item.domain.format_rule()) << "\n"; + } + if (!item.domain.format_requirements().empty()) { + os << " requirements: " << yaml_quote_if_needed(item.domain.format_requirements()) << "\n"; + } + // Description uses block scalar os << " description: "; if (item.description.empty()) { diff --git a/source/source_io/input_help.h b/source/source_io/input_help.h index 85f3f469552..a0b41ce4225 100644 --- a/source/source_io/input_help.h +++ b/source/source_io/input_help.h @@ -20,6 +20,8 @@ struct ParameterMetadata { std::string category; std::string unit; // Empty string if no unit std::string availability; // Empty string if always available + std::string value_rule; + std::string requirements; std::string allowed_values; // Rendered from the runtime domain; empty if not declared std::string name_lowercase; // Pre-computed lowercase for fast fuzzy matching }; diff --git a/source/source_io/module_parameter/input_domain.cpp b/source/source_io/module_parameter/input_domain.cpp index 3ee3bc507a8..f86bc5bde6f 100644 --- a/source/source_io/module_parameter/input_domain.cpp +++ b/source/source_io/module_parameter/input_domain.cpp @@ -1,120 +1,315 @@ #include "input_domain.h" -#include "input_parameter.h" #include +#include +#include +#include +#include +#include #include #include namespace ModuleIO { +namespace +{ +using Kind = AvailabilityValueKind; -InputDomain::InputDomain(int Input_para::* member, const std::vector& choices) - : member_(member), choices_(choices) +std::string join(const std::vector& values, const std::string& separator) { - if (member_ == nullptr || choices_.empty()) + std::ostringstream out; + for (size_t i = 0; i < values.size(); ++i) { - throw std::invalid_argument("An integer INPUT domain requires a field binding and nonempty legal values."); + if (i != 0) { out << separator; } + out << values[i]; } - std::vector unique_choices = choices_; - std::sort(unique_choices.begin(), unique_choices.end()); - if (std::adjacent_find(unique_choices.begin(), unique_choices.end()) != unique_choices.end()) + return out.str(); +} + +InputValue parse_scalar(const Kind kind, const std::string& token) +{ + size_t consumed = 0; + if (kind == Kind::String) { return InputValue(token); } + if (kind == Kind::Boolean) { - throw std::invalid_argument("An integer INPUT domain must not contain duplicate legal values."); + std::string lower = token; + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); + const std::vector yes = {"true", "1", "t", "yes", "y", "on", ".true."}; + const std::vector no = {"false", "0", "f", "no", "n", "off", ".false."}; + if (std::find(yes.begin(), yes.end(), lower) != yes.end()) { return InputValue(true); } + if (std::find(no.begin(), no.end(), lower) != no.end()) { return InputValue(false); } } + else if (kind == Kind::Integer) + { + const int parsed = std::stoi(token, &consumed); + if (consumed == token.size()) { return InputValue(parsed); } + } + else if (kind == Kind::Real) + { + const double parsed = std::stod(token, &consumed); + if (consumed == token.size() && std::isfinite(parsed)) { return InputValue(parsed); } + } + throw std::invalid_argument("Invalid scalar INPUT value: " + token); +} + +Kind element_kind(const Kind kind) +{ + if (kind == Kind::IntegerVector) { return Kind::Integer; } + if (kind == Kind::RealVector) { return Kind::Real; } + if (kind == Kind::StringVector) { return Kind::String; } + return kind; } -bool InputDomain::empty() const +bool compare(const InputValue& actual, const std::string& op, const std::string& literal) { - return member_ == nullptr; + const Kind scalar_kind = element_kind(actual.kind); + const InputValue rhs = parse_scalar(scalar_kind, literal); + if (scalar_kind == Kind::String) + { + if (op == "contains") + { + return std::find(actual.strings.begin(), actual.strings.end(), literal) != actual.strings.end(); + } + const bool equal = actual.strings.size() == 1 && actual.strings.front() == literal; + return op == "!=" ? !equal : equal; + } + const double wanted = rhs.numbers.front(); + if (op == "contains") + { + return std::find(actual.numbers.begin(), actual.numbers.end(), wanted) != actual.numbers.end(); + } + if (actual.numbers.size() != 1) { throw std::invalid_argument("Scalar INPUT comparison requires one value."); } + const double number = actual.numbers.front(); + if (op == ">") { return number > wanted; } + if (op == ">=") { return number >= wanted; } + if (op == "<") { return number < wanted; } + if (op == "<=") { return number <= wanted; } + if (op == "!=") { return number != wanted; } + return number == wanted; } -const std::vector& InputDomain::integer_choices() const +void collect_references(const AvailabilityExpr& expression, std::set& references) { - return choices_; + if (expression.is_leaf() && !expression.condition.param.empty()) + { + references.insert(expression.condition.param); + } + for (const auto& child : expression.children) { collect_references(child, references); } } -std::string InputDomain::format_choices() const +std::string describe_values(const InputValues& values, const InputRequirement& rule) { + std::set references; + collect_references(rule.when, references); + collect_references(rule.require, references); + std::vector rendered; + for (const auto& name : references) { rendered.push_back(name + "=" + values.at(name).format()); } + return join(rendered, ", "); +} +} // namespace + +InputValue::InputValue(int value) : kind(Kind::Integer) +{ + numbers.push_back(static_cast(value)); +} +InputValue::InputValue(double value) : kind(Kind::Real), numbers{value} {} +InputValue::InputValue(bool value) : kind(Kind::Boolean) +{ + numbers.push_back(value ? 1 : 0); +} +InputValue::InputValue(const std::string& value) : kind(Kind::String), strings{value} {} +InputValue::InputValue(const std::vector& value) : kind(Kind::IntegerVector) +{ + numbers.assign(value.begin(), value.end()); +} +InputValue::InputValue(const std::vector& value) : kind(Kind::RealVector), numbers(value) {} +InputValue::InputValue(const std::vector& value) : kind(Kind::StringVector), strings(value) {} + +std::string InputValue::format() const +{ + if (kind == Kind::String || kind == Kind::StringVector) { return join(strings, " "); } std::ostringstream out; - for (size_t i = 0; i < choices_.size(); ++i) + out << std::setprecision(std::numeric_limits::max_digits10); + for (size_t i = 0; i < numbers.size(); ++i) { - if (i != 0) - { - out << ", "; - } - out << choices_[i]; + if (i != 0) { out << ' '; } + if (kind == Kind::Boolean) { out << (numbers[i] != 0 ? "true" : "false"); } + else { out << numbers[i]; } } return out.str(); } -bool InputDomain::contains(int value) const +bool evaluate_input_condition(const AvailabilityExpr& expression, const InputValues& values) { - return std::find(choices_.begin(), choices_.end(), value) != choices_.end(); + if (expression.is_leaf()) + { + const auto& condition = expression.condition; + if (condition.param.empty()) { return true; } + const auto& actual = values.at(condition.param); + for (const auto& literal : condition.values) + { + if (compare(actual, condition.op, literal)) { return true; } + } + return false; + } + for (const auto& child : expression.children) + { + const bool result = evaluate_input_condition(child, values); + if (expression.op == "and" && !result) { return false; } + if (expression.op == "or" && result) { return true; } + } + return expression.op == "and"; } -std::string InputDomain::diagnostic(const std::string& label, - const std::string& value, - const std::string& origin) const +std::string input_manual_link(const std::string& label) { std::string anchor = label; std::replace(anchor.begin(), anchor.end(), '_', '-'); - return "Invalid INPUT parameter '" + label + "': " + origin + " '" + value - + "'. Expected one Integer from {" + format_choices() + "}.\n" - + "See https://abacus.deepmodeling.com/en/latest/advanced/input_files/input-main.html#" + anchor + "\n"; + return "https://abacus.deepmodeling.com/en/latest/advanced/input_files/input-main.html#" + anchor; } -std::string InputDomain::validate_explicit(const std::string& label, const std::vector& tokens) const +std::string input_diagnostic(const std::string& label, const std::string& value, + const std::string& origin, const std::string& expected) +{ + return "Invalid INPUT parameter '" + label + "': " + origin + " '" + value + "'. " + expected + + "\nSee " + input_manual_link(label) + "\n"; +} + +void InputDomain::bind(const Kind kind, const std::function& getter) { - if (empty()) + kind_ = kind; + getter_ = getter; +} +void InputDomain::scalar() { scalar_ = true; } +void InputDomain::set_choices(const std::vector& choices) +{ + const std::set unique(choices.begin(), choices.end()); + if (choices.empty() || unique.size() != choices.size()) { - return ""; + throw std::invalid_argument("INPUT choices must be nonempty and unique."); } - if (tokens.size() == 1) + choices_ = choices; + scalar_ = true; +} +void InputDomain::set_rule(const std::string& rule) +{ + rule_ = parse_availability(rule); + scalar_ = true; +} +void InputDomain::set_active_when(const std::string& condition) { active_when_ = parse_availability(condition); } +void InputDomain::add_requirement(const std::string& when, const std::string& require) +{ + InputRequirement rule; + rule.when = parse_availability(when); + rule.require = parse_availability(require); + requirements_.push_back(rule); +} +bool InputDomain::empty() const { return !scalar_; } +bool InputDomain::bound() const { return static_cast(getter_); } +Kind InputDomain::kind() const { return kind_; } +InputValue InputDomain::value(const Parameter& input) const { return getter_(input); } +std::string InputDomain::format_choices() const { return join(choices_, ", "); } +std::string InputDomain::format_rule() const { return rule_.to_string(); } +const std::vector& InputDomain::choices() const { return choices_; } + +std::string InputDomain::format_requirements() const +{ + std::vector rendered; + for (const auto& rule : requirements_) { - try - { - size_t consumed = 0; - const int value = std::stoi(tokens.front(), &consumed); - if (consumed == tokens.front().size() && contains(value)) - { - return ""; - } - } - catch (const std::invalid_argument&) - { - // Report malformed input using the same domain diagnostic. - } - catch (const std::out_of_range&) - { - // An integer overflow is an input error, not a default request. - } + const std::string when = rule.when.to_string(); + const std::string condition = rule.require.to_string(); + rendered.push_back(when.empty() ? condition : "When " + when + ": require " + condition); } - std::ostringstream supplied; - for (size_t i = 0; i < tokens.size(); ++i) + return join(rendered, "; "); +} + +void InputDomain::validate_schema(const std::string& label, const std::map& types) const +{ + if (scalar_) { - if (i != 0) + if (!bound() || (kind_ != Kind::Integer && kind_ != Kind::Real && kind_ != Kind::String && kind_ != Kind::Boolean)) { - supplied << ' '; + throw std::invalid_argument("Missing scalar INPUT binding for " + label); } - supplied << tokens[i]; + for (const auto& choice : choices_) { parse_scalar(kind_, choice); } + const std::map domain_types = {{"value", kind_}}; + validate_availability_expr(label, rule_, domain_types); + } + validate_availability_expr(label, active_when_, types); + for (const auto& rule : requirements_) + { + validate_availability_expr(label, rule.when, types); + validate_availability_expr(label, rule.require, types); } - const std::string value = supplied.str(); - return diagnostic(label, value, "supplied"); } -std::string InputDomain::validate_resolved(const std::string& label, const Input_para& input) const +bool InputDomain::accepts(const InputValue& value) const { - if (empty()) + if (kind_ == Kind::Real && !std::isfinite(value.numbers.front())) { return false; } + if (!choices_.empty()) { - return ""; + bool found = false; + for (const auto& choice : choices_) { found = found || compare(value, "==", choice); } + if (!found) { return false; } } - const int value = input.*member_; - if (contains(value)) + const InputValues values = {{"value", value}}; + return evaluate_input_condition(rule_, values); +} + +std::string InputDomain::expected() const +{ + std::string type; + if (kind_ == Kind::Integer) { type = "Integer"; } + else if (kind_ == Kind::Real) { type = "finite Real"; } + else if (kind_ == Kind::Boolean) { type = "Boolean (true/false, 1/0, t/f, yes/no, y/n, on/off, .true./.false.)"; } + else { type = "String"; } + std::string result = "Expected one " + type; + if (!choices_.empty()) { result += " from {" + format_choices() + "}"; } + if (!format_rule().empty()) { result += " satisfying " + format_rule(); } + return result + "."; +} + +std::string InputDomain::validate_explicit(const std::string& label, const std::vector& tokens) const +{ + if (empty()) { return ""; } + if (tokens.size() == 1) { - return ""; + try + { + const InputValue parsed = parse_scalar(kind_, tokens.front()); + if (accepts(parsed)) { return ""; } + } + catch (const std::invalid_argument&) {} + catch (const std::out_of_range&) {} } - const std::string rendered = std::to_string(value); - return diagnostic(label, rendered, "resolved value"); + const std::string supplied = join(tokens, " "); + const std::string requirement = expected(); + return input_diagnostic(label, supplied, "supplied", requirement); } +std::string InputDomain::validate_resolved(const std::string& label, const InputValues& values) const +{ + if (empty() || !evaluate_input_condition(active_when_, values)) { return ""; } + const auto& actual = values.at(label); + if (accepts(actual)) { return ""; } + const std::string rendered = actual.format(); + const std::string requirement = expected(); + return input_diagnostic(label, rendered, "resolved value", requirement); +} + +std::string InputDomain::validate_requirements(const std::string& label, const InputValues& values) const +{ + std::string errors; + for (const auto& rule : requirements_) + { + if (evaluate_input_condition(rule.when, values) && !evaluate_input_condition(rule.require, values)) + { + const std::string actual = describe_values(values, rule); + const std::string expected = "Requires " + rule.require.to_string() + "."; + errors += input_diagnostic(label, actual, "configuration", expected); + } + } + return errors; +} } // namespace ModuleIO diff --git a/source/source_io/module_parameter/input_domain.h b/source/source_io/module_parameter/input_domain.h index 3e930fe7879..195b1a44ff4 100644 --- a/source/source_io/module_parameter/input_domain.h +++ b/source/source_io/module_parameter/input_domain.h @@ -1,42 +1,82 @@ #ifndef MODULE_IO_INPUT_DOMAIN_H #define MODULE_IO_INPUT_DOMAIN_H +#include "availability_validator.h" +#include +#include #include #include -struct Input_para; +class Parameter; namespace ModuleIO { +// Read-only typed snapshots. Integer INPUT fields fit exactly in double storage. +struct InputValue +{ + AvailabilityValueKind kind = AvailabilityValueKind::Unknown; + std::vector numbers; + std::vector strings; + InputValue() = default; + explicit InputValue(int value); + explicit InputValue(double value); + explicit InputValue(bool value); + explicit InputValue(const std::string& value); + explicit InputValue(const std::vector& value); + explicit InputValue(const std::vector& value); + explicit InputValue(const std::vector& value); + std::string format() const; +}; +using InputValues = std::map; + +bool evaluate_input_condition(const AvailabilityExpr& expression, const InputValues& values); +std::string input_manual_link(const std::string& label); +std::string input_diagnostic(const std::string& label, const std::string& value, + const std::string& origin, const std::string& expected); + +struct InputRequirement +{ + AvailabilityExpr when; + AvailabilityExpr require; +}; -/** - * Declarative legal values and a typed, read-only binding to an INPUT field. - * The initial domain operator is membership in a set of scalar integers. - * An empty domain leaves a parameter on its existing validation path. - */ class InputDomain { public: InputDomain() = default; - InputDomain(int Input_para::* member, const std::vector& choices); + void bind(AvailabilityValueKind kind, const std::function& getter); + void scalar(); + void set_choices(const std::vector& choices); + // Predicates use "value", e.g. "value>0 and value<=1". + void set_rule(const std::string& rule); + // Only effective defaults may be inactive; explicit input is always checked. + void set_active_when(const std::string& condition); + void add_requirement(const std::string& when, const std::string& require); bool empty() const; - const std::vector& integer_choices() const; + bool bound() const; + AvailabilityValueKind kind() const; + InputValue value(const Parameter& input) const; std::string format_choices() const; - - // Return an empty string on success, otherwise a diagnostic with legal - // values and a manual link. Neither operation modifies input values. + std::string format_rule() const; + std::string format_requirements() const; + const std::vector& choices() const; + void validate_schema(const std::string& label, + const std::map& types) const; std::string validate_explicit(const std::string& label, const std::vector& tokens) const; - std::string validate_resolved(const std::string& label, const Input_para& input) const; + std::string validate_resolved(const std::string& label, const InputValues& values) const; + std::string validate_requirements(const std::string& label, const InputValues& values) const; private: - int Input_para::* member_ = nullptr; - std::vector choices_; - - bool contains(int value) const; - std::string diagnostic(const std::string& label, const std::string& value, const std::string& origin) const; + AvailabilityValueKind kind_ = AvailabilityValueKind::Unknown; + std::function getter_; + bool scalar_ = false; + std::vector choices_; + AvailabilityExpr rule_; + AvailabilityExpr active_when_; + std::vector requirements_; + bool accepts(const InputValue& value) const; + std::string expected() const; }; - } // namespace ModuleIO - #endif // MODULE_IO_INPUT_DOMAIN_H diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index 72706dfbe58..989ac23e4fd 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -477,7 +477,7 @@ The other way is only available when compiling with LIBXC, and it allows for sup * Note: With nspin=4 no moment is ever autoset. The calculation starts from zero magnetic moment and a warning is printed; set 'mag' explicitly in STRU for the magnetic atoms if a magnetic ground state is expected.)"; item.default_value = "1"; item.unit = ""; - item.domain = InputDomain(&Input_para::nspin, {1, 2, 4}); + item.domain.set_choices({"1", "2", "4"}); read_sync_int(input.nspin); item.check_value = [](const Input_Item& item, const Parameter& para) { if ((para.input.noncolin || para.input.lspinorb) && para.input.nspin != 4) @@ -499,7 +499,7 @@ The other way is only available when compiling with LIBXC, and it allows for sup For r = |m| and x = r/eta, S_eta = eta*x^3*(3*x^2 - 8*x + 6) for r < eta, and S_eta = r otherwise. The regularization is part of the functional definition, including its first and second derivatives. Mode 2 also uses this local map for the LDA contribution. Other spin configurations retain their existing behavior.)"; item.default_value = "0"; - item.domain = InputDomain(&Input_para::gga_grad, {0, 1, 2}); + item.domain.set_choices({"0", "1", "2"}); read_sync_int(input.gga_grad); this->add_item(item); } diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index b35cf71f537..45b0d4d4401 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -191,13 +191,16 @@ ReadInput::ReadInput(const int& rank) { std::map parameter_types; std::map expressions; + std::map bound_types; for (const auto& entry : this->input_lists) { parameter_types[entry.first] = availability_value_kind(entry.second.type); expressions[entry.first] = entry.second.get_availability_expr(); + if (entry.second.domain.bound()) { bound_types[entry.first] = entry.second.domain.kind(); } } for (const auto& entry : this->input_lists) { + entry.second.domain.validate_schema(entry.first, bound_types); validate_availability_expr(entry.first, entry.second.get_availability_expr(), parameter_types); @@ -465,6 +468,12 @@ std::string ReadInput::prepare_input(Parameter& param) readvalue_item->read_value(*readvalue_item, param); } + InputValues supplied_values; + for (const auto* item : this->readvalue_items) + { + if (item->domain.bound()) { supplied_values[item->label] = item->domain.value(param); } + } + // 2) reset this value when some conditions are met // e.g. if (calulation_type == "nscf") then set "init_chg" to "file". for (auto& input_item: this->input_lists) @@ -479,10 +488,28 @@ std::string ReadInput::prepare_input(Parameter& param) this->normalize_hs_output_options(param); this->normalize_occ_mat_options(param); + // Snapshot typed effective values once for shared domain and dependency checks. + InputValues values; + for (const auto& entry : this->input_lists) + { + if (entry.second.domain.bound()) { values[entry.first] = entry.second.domain.value(param); } + } + + // Evaluate requirements with explicit values taking precedence over defaults, + // so a reset cannot hide a conflicting user request. + InputValues requested_values = values; + for (const auto& supplied : supplied_values) { requested_values[supplied.first] = supplied.second; } + for (const auto& entry : this->input_lists) + { + errors += entry.second.domain.validate_requirements(entry.first, requested_values); + } + if (!errors.empty()) { return errors; } + // Check effective values, including defaults, through the same domains. for (const auto& entry : this->input_lists) { - errors += entry.second.domain.validate_resolved(entry.first, param.inp); + errors += entry.second.domain.validate_resolved(entry.first, values); + errors += entry.second.domain.validate_requirements(entry.first, values); } return errors; } @@ -736,10 +763,6 @@ int ReadInput::current_md_step(const std::string& file_dir) void ReadInput::add_item(const Input_Item& item) { - if (!item.domain.empty() && item.type != "Integer") - { - throw std::invalid_argument("Integer domain attached to non-Integer INPUT parameter: " + item.label); - } // Normally only rank 0 reads the input file // But rank -1 is used for help system mode where items should also be added if (this->rank == 0 || this->rank == -1) diff --git a/source/source_io/module_parameter/read_input_tool.h b/source/source_io/module_parameter/read_input_tool.h index 9fe5109817f..002178b7e8f 100644 --- a/source/source_io/module_parameter/read_input_tool.h +++ b/source/source_io/module_parameter/read_input_tool.h @@ -80,26 +80,31 @@ #define sync_string(PARAMETER) \ { \ + item.domain.bind(AvailabilityValueKind::String, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { item.final_value << para.PARAMETER; }; \ add_string_bcast(PARAMETER); \ } #define sync_int(PARAMETER) \ { \ + item.domain.bind(AvailabilityValueKind::Integer, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { item.final_value << para.PARAMETER; }; \ add_int_bcast(PARAMETER); \ } #define sync_double(PARAMETER) \ { \ + item.domain.bind(AvailabilityValueKind::Real, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { item.final_value << para.PARAMETER; }; \ add_double_bcast(PARAMETER); \ } #define sync_bool(PARAMETER) \ { \ + item.domain.bind(AvailabilityValueKind::Boolean, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { item.final_value << para.PARAMETER; }; \ add_bool_bcast(PARAMETER); \ } #define sync_doublevec(PARAMETER, N, FILL) \ { \ + item.domain.bind(AvailabilityValueKind::RealVector, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { \ for (int i = 0; i < N; i++) \ { \ @@ -110,6 +115,7 @@ } #define sync_intvec(PARAMETER, N, FILL) \ { \ + item.domain.bind(AvailabilityValueKind::IntegerVector, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { \ for (int i = 0; i < N; i++) \ { \ @@ -120,6 +126,7 @@ } #define sync_stringvec(PARAMETER, N, FILL) \ { \ + item.domain.bind(AvailabilityValueKind::StringVector, [](const Parameter& para) { return InputValue(para.PARAMETER); }); \ item.get_final_value = [](Input_Item& item, const Parameter& para) { \ for (int i = 0; i < N; i++) \ { \ @@ -136,16 +143,19 @@ } #define read_sync_int(PARAMETER) \ { \ + item.domain.scalar(); \ item.read_value = [](const Input_Item& item, Parameter& para) { para.PARAMETER = intvalue; }; \ sync_int(PARAMETER); \ } #define read_sync_double(PARAMETER) \ { \ + item.domain.scalar(); \ item.read_value = [](const Input_Item& item, Parameter& para) { para.PARAMETER = doublevalue; }; \ sync_double(PARAMETER); \ } #define read_sync_bool(PARAMETER) \ { \ + item.domain.scalar(); \ item.read_value = [](const Input_Item& item, Parameter& para) { para.PARAMETER = boolvalue; }; \ sync_bool(PARAMETER); \ } diff --git a/source/source_io/module_parameter/test/CMakeLists.txt b/source/source_io/module_parameter/test/CMakeLists.txt index 4f2e370c7e0..5b0b6a5d9cc 100644 --- a/source/source_io/module_parameter/test/CMakeLists.txt +++ b/source/source_io/module_parameter/test/CMakeLists.txt @@ -9,5 +9,5 @@ AddTest( AddTest( TARGET MODULE_IO_input_domain - SOURCES test_input_domain.cpp ../input_domain.cpp + SOURCES test_input_domain.cpp ../input_domain.cpp ../availability.cpp ../availability_validator.cpp ) diff --git a/source/source_io/module_parameter/test/test_input_domain.cpp b/source/source_io/module_parameter/test/test_input_domain.cpp index bbd5961c706..f3e49a446a8 100644 --- a/source/source_io/module_parameter/test/test_input_domain.cpp +++ b/source/source_io/module_parameter/test/test_input_domain.cpp @@ -7,7 +7,7 @@ TEST(InputDomain, UndeclaredDomainKeepsLegacyValidation) { const ModuleIO::InputDomain domain; - const Input_para input; + const ModuleIO::InputValues input; EXPECT_TRUE(domain.empty()); EXPECT_TRUE(domain.format_choices().empty()); EXPECT_TRUE(domain.validate_explicit("legacy", {"any", "tokens"}).empty()); @@ -16,14 +16,18 @@ TEST(InputDomain, UndeclaredDomainKeepsLegacyValidation) TEST(InputDomain, RejectsInvalidSchema) { - EXPECT_THROW(ModuleIO::InputDomain(nullptr, {1}), std::invalid_argument); - EXPECT_THROW(ModuleIO::InputDomain(&Input_para::nspin, {}), std::invalid_argument); - EXPECT_THROW(ModuleIO::InputDomain(&Input_para::nspin, {1, 2, 1}), std::invalid_argument); + ModuleIO::InputDomain domain; + EXPECT_THROW(domain.set_choices({}), std::invalid_argument); + EXPECT_THROW(domain.set_choices({"1", "2", "1"}), std::invalid_argument); + domain.set_choices({"1"}); + EXPECT_THROW(domain.validate_schema("unbound", {}), std::invalid_argument); } TEST(InputDomain, ParsesCompleteScalarIntegers) { - const ModuleIO::InputDomain domain(&Input_para::nspin, {1, 2, 4}); + ModuleIO::InputDomain domain; + domain.bind(ModuleIO::AvailabilityValueKind::Integer, [](const Parameter&) { return ModuleIO::InputValue(1); }); + domain.set_choices({"1", "2", "4"}); for (const std::string token : {"1", "2", "4", "+2", "02"}) { EXPECT_TRUE(domain.validate_explicit("nspin", {token}).empty()) << token; @@ -41,20 +45,86 @@ TEST(InputDomain, ParsesCompleteScalarIntegers) TEST(InputDomain, ValuesAreReadFromTypedBindingWithoutMutation) { - const ModuleIO::InputDomain domain(&Input_para::gga_grad, {-1, 0, 2}); - const ModuleIO::InputDomain copy = domain; Input_para input; + ModuleIO::InputDomain domain; + domain.bind(ModuleIO::AvailabilityValueKind::Integer, [&input](const Parameter&) { return ModuleIO::InputValue(input.gga_grad); }); + domain.set_choices({"-1", "0", "2"}); + const ModuleIO::InputDomain copy = domain; for (const int value : {-1, 0, 2}) { input.gga_grad = value; - EXPECT_TRUE(copy.validate_resolved("gga_grad", input).empty()); + EXPECT_TRUE(copy.validate_resolved("gga_grad", {{"gga_grad", ModuleIO::InputValue(input.gga_grad)}}).empty()); EXPECT_EQ(input.gga_grad, value); } input.gga_grad = 1; - const std::string error = copy.validate_resolved("gga_grad", input); + const std::string error = copy.validate_resolved("gga_grad", {{"gga_grad", ModuleIO::InputValue(input.gga_grad)}}); EXPECT_NE(error.find("resolved value '1'"), std::string::npos); EXPECT_NE(error.find("input-main.html#gga-grad"), std::string::npos); EXPECT_EQ(input.gga_grad, 1); - EXPECT_EQ(copy.integer_choices(), (std::vector{-1, 0, 2})); + EXPECT_EQ(copy.choices(), (std::vector{"-1", "0", "2"})); EXPECT_EQ(copy.format_choices(), "-1, 0, 2"); } + +TEST(InputDomain, NumericBoundsAndNonfiniteValues) +{ + ModuleIO::InputDomain domain; + domain.bind(ModuleIO::AvailabilityValueKind::Real, [](const Parameter&) { return ModuleIO::InputValue(0.5); }); + domain.set_rule("value>=0 and value<1"); + for (const std::string token : {"0", "0.5", "5e-1", "-0"}) + { + EXPECT_TRUE(domain.validate_explicit("weight", {token}).empty()); + } + for (const std::string token : {"-0.1", "1", "nan", "inf", "-inf", "1e999", "0.5garbage", "null"}) + { + EXPECT_FALSE(domain.validate_explicit("weight", {token}).empty()) << token; + } +} + +TEST(InputDomain, BooleanAliasesAndExactCardinality) +{ + ModuleIO::InputDomain domain; + domain.bind(ModuleIO::AvailabilityValueKind::Boolean, [](const Parameter&) { return ModuleIO::InputValue(false); }); + domain.scalar(); + for (const std::string token : {"true", "FALSE", "0", "1", "Y", "n", "on", "off", ".true.", ".FALSE."}) + { + EXPECT_TRUE(domain.validate_explicit("flag", {token}).empty()); + } + EXPECT_FALSE(domain.validate_explicit("flag", {"2"}).empty()); + EXPECT_FALSE(domain.validate_explicit("flag", {"true", "false"}).empty()); +} + +TEST(InputDomain, SharedConditionsAndDependencyDiagnostics) +{ + const ModuleIO::InputValues values = { + {"nspin", ModuleIO::InputValue(2)}, {"noncolin", ModuleIO::InputValue(true)}, + {"basis_type", ModuleIO::InputValue(std::string("pw"))}, + {"directions", ModuleIO::InputValue(std::vector{0, 2})}, + {"threshold", ModuleIO::InputValue(0.5)}}; + for (const std::string predicate : {"nspin in [1, 2, 4]", "threshold>0 and threshold<=0.5", + "basis_type!=lcao", "directions contains 2", + "nspin==4 or noncolin==true"}) + { + const auto expression = ModuleIO::parse_availability(predicate); + EXPECT_TRUE(ModuleIO::evaluate_input_condition(expression, values)) << predicate; + } + ModuleIO::InputDomain domain; + domain.add_requirement("noncolin==true", "nspin==4"); + const auto error = domain.validate_requirements("nspin", values); + EXPECT_NE(error.find("noncolin=true"), std::string::npos); + EXPECT_NE(error.find("nspin=2"), std::string::npos); + EXPECT_NE(error.find("Requires nspin==4"), std::string::npos); + EXPECT_NE(error.find("input-main.html#nspin"), std::string::npos); + EXPECT_THROW(domain.validate_schema("nspin", {}), std::invalid_argument); +} + +TEST(InputDomain, InternalUnsetValueIsNotAPublicChoice) +{ + ModuleIO::InputDomain domain; + domain.bind(ModuleIO::AvailabilityValueKind::Integer, [](const Parameter&) { return ModuleIO::InputValue(1); }); + domain.set_choices({"1", "2"}); + EXPECT_FALSE(domain.validate_explicit("criterion", {"-1"}).empty()); + const ModuleIO::InputValues unset = {{"criterion", ModuleIO::InputValue(-1)}}; + const ModuleIO::InputValues resolved = {{"criterion", ModuleIO::InputValue(2)}}; + EXPECT_FALSE(domain.validate_resolved("criterion", unset).empty()); + EXPECT_TRUE(domain.validate_resolved("criterion", resolved).empty()); +} diff --git a/source/source_io/test/read_input_ptest.cpp b/source/source_io/test/read_input_ptest.cpp index 4e72ead7e47..2f57e82b2d7 100644 --- a/source/source_io/test/read_input_ptest.cpp +++ b/source/source_io/test/read_input_ptest.cpp @@ -558,14 +558,14 @@ TEST_F(InputParaTest, GgaGradAcceptedRange) item.str_values = {std::to_string(mode)}; item.read_value(item, param); EXPECT_EQ(param.inp.gga_grad, mode); - EXPECT_TRUE(item.domain.validate_resolved(item.label, param.inp).empty()); + EXPECT_TRUE(item.domain.validate_resolved(item.label, {{item.label, item.domain.value(param)}}).empty()); } for (const int mode: {-1, 3}) { Parameter param; item.str_values = {std::to_string(mode)}; item.read_value(item, param); - EXPECT_FALSE(item.domain.validate_resolved(item.label, param.inp).empty()); + EXPECT_FALSE(item.domain.validate_resolved(item.label, {{item.label, item.domain.value(param)}}).empty()); } } EXPECT_TRUE(found); diff --git a/source/source_io/test_serial/read_input_item_test.cpp b/source/source_io/test_serial/read_input_item_test.cpp index d9c2a008c8e..391844eb04c 100644 --- a/source/source_io/test_serial/read_input_item_test.cpp +++ b/source/source_io/test_serial/read_input_item_test.cpp @@ -296,7 +296,7 @@ TEST_F(InputTest, Item_test) auto it = find_label("nspin", readinput.input_lists); TestParameters::input(param).noncolin = false; TestParameters::input(param).nspin = 3; - output = it->second.domain.validate_resolved(it->first, param.inp); + output = it->second.domain.validate_resolved(it->first, {{it->first, it->second.domain.value(param)}}); EXPECT_THAT(output, testing::HasSubstr("Expected one Integer from {1, 2, 4}")); } { // smearing_method From ac7d0f0de027f9d26eec25a68ecec3d81a1fc150 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Thu, 22 Oct 2026 15:25:07 +0800 Subject: [PATCH 4/8] feat(input): declare shared value and dependency contracts across parameters --- docs/developers_guide/input_validation.md | 9 + docs/test_generate_input_main.py | 10 + source/source_io/input_help.cpp | 13 +- .../module_parameter/input_domain.cpp | 33 +- .../source_io/module_parameter/input_domain.h | 1 + .../module_parameter/read_inp_deepks.cpp | 44 +-- .../module_parameter/read_inp_dfpt.cpp | 7 +- .../module_parameter/read_inp_estruc.cpp | 94 ++--- .../module_parameter/read_inp_model.cpp | 89 +---- .../module_parameter/read_inp_other.cpp | 88 +---- .../module_parameter/read_inp_out.cpp | 32 +- .../module_parameter/read_inp_relax.cpp | 29 +- .../module_parameter/read_inp_sys.cpp | 78 +--- .../module_parameter/read_inp_tddft.cpp | 15 +- .../source_io/module_parameter/read_input.cpp | 8 +- .../module_parameter/read_input_item_md.cpp | 10 +- .../module_parameter/read_input_item_sdft.cpp | 10 +- .../test/test_input_domain.cpp | 14 + .../test_serial/read_input_item_test.cpp | 345 +++++++++--------- .../source_io/test_serial/read_input_test.cpp | 13 +- .../source_io/test_serial/test_read_input.cpp | 107 +++++- 21 files changed, 460 insertions(+), 589 deletions(-) diff --git a/docs/developers_guide/input_validation.md b/docs/developers_guide/input_validation.md index d52d56f40b9..e1bb64736e6 100644 --- a/docs/developers_guide/input_validation.md +++ b/docs/developers_guide/input_validation.md @@ -50,6 +50,15 @@ explicit supplied values are still checked. ## Documentation and defaults +The normal numeric/Boolean reader registrations enforce scalar type contracts +automatically. Explicit choice/range/requirement declarations cover system, +electronic, relaxation, MD, vdW, output, DeePKS, DFPT and TDDFT inputs, including +`scf_thr_type`, `symmetry`, `mem_saver` and the spin dependency. + +When writing reusable INPUT, an inactive value outside its public domain is +omitted: internal unset markers must not be presented as legal explicit input. +The value in memory is not changed. + Choices, value predicates and conditional requirements are exported to built-in help, generated YAML and the INPUT manual from these declarations. Generated YAML and Markdown are build artifacts and must not be committed. Recommended diff --git a/docs/test_generate_input_main.py b/docs/test_generate_input_main.py index 463b6115755..0b8ab0ac3ef 100644 --- a/docs/test_generate_input_main.py +++ b/docs/test_generate_input_main.py @@ -12,6 +12,16 @@ def test_integer_choices_include_zero(self): ) self.assertIn("- **Allowed values**: 0, 1, 2", rendered) + def test_ranges_and_requirements_share_reference_links(self): + rendered = generate_parameter_markdown( + {"name": "mixing_beta", "type": "Real", "value_rule": "value>=0", + "requirements": "When noncolin==true: require nspin==4"}, + {"noncolin": "#noncolin", "nspin": "#nspin"}, + ) + self.assertIn("**Legal range**", rendered) + self.assertIn("**Requirements**", rendered) + self.assertIn("[`nspin`](#nspin)", rendered) + def test_undeclared_domain_is_not_an_empty_restriction(self): rendered = generate_parameter_markdown({"name": "legacy", "type": "Real"}, {}) self.assertNotIn("Allowed values", rendered) diff --git a/source/source_io/input_help.cpp b/source/source_io/input_help.cpp index c9910584935..eacf4d36493 100644 --- a/source/source_io/input_help.cpp +++ b/source/source_io/input_help.cpp @@ -506,7 +506,18 @@ void ParameterHelp::generate_yaml(std::ostream& os) { os << " type: " << yaml_quote_if_needed(item.type) << "\n"; if (!item.domain.choices().empty()) { - os << " allowed_values: [" << item.domain.format_choices() << "]\n"; + os << " allowed_values: ["; + const auto& choices = item.domain.choices(); + for (size_t i = 0; i < choices.size(); ++i) + { + if (i != 0) { os << ", "; } + if (item.domain.kind() == AvailabilityValueKind::String) + { + os << yaml_quote_if_needed(choices[i]); + } + else { os << choices[i]; } + } + os << "]\n"; } if (!item.domain.format_rule().empty()) { diff --git a/source/source_io/module_parameter/input_domain.cpp b/source/source_io/module_parameter/input_domain.cpp index f86bc5bde6f..1b8e13aac3d 100644 --- a/source/source_io/module_parameter/input_domain.cpp +++ b/source/source_io/module_parameter/input_domain.cpp @@ -88,6 +88,25 @@ bool compare(const InputValue& actual, const std::string& op, const std::string& return number == wanted; } +void validate_runtime_condition(const std::string& owner, const AvailabilityExpr& expression, + const std::map& types) +{ + validate_availability_expr(owner, expression, types); + if (expression.is_leaf()) + { + const auto& condition = expression.condition; + if (condition.param.empty()) { return; } + const Kind kind = types.at(condition.param); + const Kind scalar_kind = element_kind(kind); + if (kind != scalar_kind && condition.op != "contains") + { + throw std::invalid_argument("Runtime INPUT vector conditions require contains: " + owner); + } + for (const auto& literal : condition.values) { parse_scalar(scalar_kind, literal); } + } + for (const auto& child : expression.children) { validate_runtime_condition(owner, child, types); } +} + void collect_references(const AvailabilityExpr& expression, std::set& references) { if (expression.is_leaf() && !expression.condition.param.empty()) @@ -206,6 +225,12 @@ void InputDomain::add_requirement(const std::string& when, const std::string& re } bool InputDomain::empty() const { return !scalar_; } bool InputDomain::bound() const { return static_cast(getter_); } +bool InputDomain::can_write(const Parameter& input) const +{ + if (empty() || !bound()) { return true; } + const InputValue resolved = value(input); + return accepts(resolved); +} Kind InputDomain::kind() const { return kind_; } InputValue InputDomain::value(const Parameter& input) const { return getter_(input); } std::string InputDomain::format_choices() const { return join(choices_, ", "); } @@ -234,13 +259,13 @@ void InputDomain::validate_schema(const std::string& label, const std::map domain_types = {{"value", kind_}}; - validate_availability_expr(label, rule_, domain_types); + validate_runtime_condition(label, rule_, domain_types); } - validate_availability_expr(label, active_when_, types); + validate_runtime_condition(label, active_when_, types); for (const auto& rule : requirements_) { - validate_availability_expr(label, rule.when, types); - validate_availability_expr(label, rule.require, types); + validate_runtime_condition(label, rule.when, types); + validate_runtime_condition(label, rule.require, types); } } diff --git a/source/source_io/module_parameter/input_domain.h b/source/source_io/module_parameter/input_domain.h index 195b1a44ff4..dbf0bf7c42e 100644 --- a/source/source_io/module_parameter/input_domain.h +++ b/source/source_io/module_parameter/input_domain.h @@ -55,6 +55,7 @@ class InputDomain bool empty() const; bool bound() const; + bool can_write(const Parameter& input) const; AvailabilityValueKind kind() const; InputValue value(const Parameter& input) const; std::string format_choices() const; diff --git a/source/source_io/module_parameter/read_inp_deepks.cpp b/source/source_io/module_parameter/read_inp_deepks.cpp index 335523ad265..8d3c4443b18 100644 --- a/source/source_io/module_parameter/read_inp_deepks.cpp +++ b/source/source_io/module_parameter/read_inp_deepks.cpp @@ -29,6 +29,8 @@ void ReadInput::item_deepks() } { Input_Item item("deepks_out_freq_elec"); + item.domain.set_rule("value>=0"); + item.domain.add_requirement("deepks_out_freq_elec>0", "deepks_out_base!=none"); item.annotation = ">0 frequency of electronic iteration to output descriptors and labels for deepks."; item.category = "DeePKS"; item.type = "Integer"; @@ -37,20 +39,11 @@ void ReadInput::item_deepks() item.unit = ""; item.set_availability("basis_type==lcao"); read_sync_int(input.deepks_out_freq_elec); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.deepks_out_freq_elec < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "deepks_out_freq_elec must not be negative"); - } - if (para.input.deepks_out_freq_elec > 0 && para.input.deepks_out_base == "none") - { - ModuleBase::WARNING_QUIT("ReadInput", "to use deepks_out_freq_elec, please set deepks_out_base "); - } - }; this->add_item(item); } { Input_Item item("deepks_out_base"); + item.domain.add_requirement("deepks_out_base!=none", "deepks_out_labels>0 and deepks_bandgap==0"); item.annotation = "base functional for output files, with dft_functional as target functional"; item.category = "DeePKS"; item.type = "String"; @@ -59,16 +52,6 @@ void ReadInput::item_deepks() item.unit = ""; item.set_availability("basis_type==lcao and deepks_out_freq_elec>0"); read_sync_string(input.deepks_out_base); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.deepks_out_base != "none" && para.input.deepks_out_labels == 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "to use deepks_out_base, please set deepks_out_labels > 0 "); - } - if (para.input.deepks_out_base != "none" && para.input.deepks_bandgap > 0 ) - { - ModuleBase::WARNING_QUIT("ReadInput", "outputting bandgap labels during electronic steps is not implemented yet "); - } - }; this->add_item(item); } { @@ -179,6 +162,7 @@ void ReadInput::item_deepks() } { Input_Item item("bessel_descriptor_rcut"); + item.domain.set_rule("value>=0"); item.annotation = "radial cutoff for spherical bessel functions(a.u.)"; item.category = "DeePKS"; item.type = "Real"; @@ -187,12 +171,6 @@ void ReadInput::item_deepks() item.unit = "Bohr"; item.set_availability("calculation==gen_bessel"); read_sync_double(input.bessel_descriptor_rcut); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.bessel_descriptor_rcut < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "bessel_descriptor_rcut must >= 0"); - } - }; this->add_item(item); } { @@ -221,6 +199,7 @@ void ReadInput::item_deepks() } { Input_Item item("deepks_bandgap"); + item.domain.set_choices({"0", "1", "2", "3"}); item.annotation = ">0 for bandgap label"; item.category = "DeePKS"; item.type = "Integer"; @@ -233,12 +212,6 @@ void ReadInput::item_deepks() item.unit = ""; item.set_availability("basis_type==lcao and deepks_scf==true"); read_sync_int(input.deepks_bandgap); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.deepks_bandgap < 0 || para.input.deepks_bandgap > 3) - { - ModuleBase::WARNING_QUIT("ReadInput", "deepks_bandgap must be integer in [0, 3]"); - } - }; this->add_item(item); } { @@ -284,6 +257,7 @@ void ReadInput::item_deepks() } { Input_Item item("deepks_v_delta"); + item.domain.set_choices({"-2", "-1", "0", "1", "2"}); item.annotation = "!=0 for v_delta/v_delta_R label. when output, 1 for vdpre, 2 for phialpha and grad_evdm (can save memory )" " -1 for vdrpre, -2 for phialpha_r and gevdm (can save memory)"; item.category = "DeePKS"; @@ -297,12 +271,6 @@ void ReadInput::item_deepks() item.unit = ""; item.set_availability("basis_type==lcao"); read_sync_int(input.deepks_v_delta); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.deepks_v_delta < -2 || para.input.deepks_v_delta > 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "deepks_v_delta must be integer in [-2, 2]"); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_inp_dfpt.cpp b/source/source_io/module_parameter/read_inp_dfpt.cpp index 3c9052bbbb6..cfea717beb5 100644 --- a/source/source_io/module_parameter/read_inp_dfpt.cpp +++ b/source/source_io/module_parameter/read_inp_dfpt.cpp @@ -132,6 +132,7 @@ void ReadInput::item_dfpt() } { Input_Item item("dfpt_max_iter"); + item.domain.set_rule("value>=1"); item.annotation = "max number of the DFPT first-order density mixing iterations"; item.category = category; item.type = "Integer"; @@ -139,12 +140,6 @@ void ReadInput::item_dfpt() "each atomic displacement."; item.default_value = "100"; read_sync_int(input.dfpt_max_iter); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.dfpt_max_iter < 1) - { - ModuleBase::WARNING_QUIT("ReadInput", "dfpt_max_iter must be >= 1."); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index 989ac23e4fd..195208bbf69 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -12,6 +12,7 @@ void ReadInput::item_elec_stru() // Please preserve this ordering when adding new parameters. { Input_Item item("basis_type"); + item.domain.set_choices({"pw", "lcao", "lcao_in_pw"}); item.annotation = "PW; LCAO in pw; LCAO"; item.category = "Electronic structure"; item.type = "String"; @@ -31,19 +32,15 @@ void ReadInput::item_elec_stru() } } }; - item.check_value = [](const Input_Item& item, const Parameter& para) { - const std::vector basis_types = {"pw", "lcao_in_pw", "lcao"}; - if (std::find(basis_types.begin(), basis_types.end(), para.input.basis_type) == basis_types.end()) - { - const std::string warningstr = nofound_str(basis_types, "basis_type"); - ModuleBase::WARNING_QUIT("ReadInput", warningstr); - } - }; this->add_item(item); } // Electronic Structure { Input_Item item("ks_solver"); + item.domain.set_choices({"cg", "dav", "bpcg", "dav_subspace", "genelpa", "elpa", "lapack", "scalapack_gvx", "cusolver", "cusolvermp", "pexsi"}); + item.domain.add_requirement("basis_type==pw", "ks_solver in [cg, dav, bpcg, dav_subspace]"); + item.domain.add_requirement("basis_type==lcao", "ks_solver in [genelpa, elpa, lapack, scalapack_gvx, cusolver, cusolvermp, pexsi]"); + item.domain.add_requirement("ks_solver==genelpa", "device==cpu"); item.annotation = "cg; dav; lapack; genelpa; elpa; scalapack_gvx; cusolver"; item.category = "Electronic structure"; item.type = "String"; @@ -233,6 +230,7 @@ Then the user has to correct the input file and restart the calculation.)"; } { Input_Item item("nbands"); + item.domain.set_rule("value>=0"); item.annotation = "number of bands"; item.category = "Electronic structure"; item.type = "Integer"; @@ -240,16 +238,11 @@ Then the user has to correct the input file and restart the calculation.)"; item.default_value = ""; item.unit = ""; read_sync_int(input.nbands); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.nbands < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "nbands should be greater than 0."); - } - }; this->add_item(item); } { Input_Item item("nelec"); + item.domain.set_rule("value>=0"); item.annotation = "input number of electrons"; item.category = "Electronic structure"; item.type = "Real"; @@ -283,6 +276,7 @@ Then the user has to correct the input file and restart the calculation.)"; } { Input_Item item("nupdown"); + item.domain.add_requirement("nspin==1", "nupdown==0"); item.annotation = "the difference number of electrons between spin-up " "and spin-down"; item.category = "Electronic structure"; @@ -301,12 +295,6 @@ Then the user has to correct the input file and restart the calculation.)"; para.sys.two_fermi = false; } }; - item.check_value = [](const Input_Item&, const Parameter& para) { - if (para.input.nspin == 1 && para.input.nupdown != 0.0) - { - ModuleBase::WARNING_QUIT("ReadInput", "nupdown mustn't have a non-zero value for spin-unpolarized calculations."); - } - }; sync_double(input.nupdown); add_bool_bcast(sys.two_fermi); this->add_item(item); @@ -466,6 +454,7 @@ The other way is only available when compiling with LIBXC, and it allows for sup } { Input_Item item("nspin"); + item.domain.add_requirement("noncolin==true or lspinorb==true", "nspin==4"); item.annotation = "1: single spin; 2: up and down spin; 4: noncollinear spin"; item.category = "Electronic structure"; item.type = "Integer"; @@ -479,12 +468,6 @@ The other way is only available when compiling with LIBXC, and it allows for sup item.unit = ""; item.domain.set_choices({"1", "2", "4"}); read_sync_int(input.nspin); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if ((para.input.noncolin || para.input.lspinorb) && para.input.nspin != 4) - { - ModuleBase::WARNING_QUIT("ReadInput", "nspin must be 4 when noncolin or lspinorb is enabled."); - } - }; this->add_item(item); } { @@ -577,6 +560,7 @@ In general, the convergence of the Broyden method is slightly faster than that o } { Input_Item item("mixing_beta"); + item.domain.set_rule("value>=0"); item.annotation = "mixing parameter: 0 means no new charge"; item.category = "Electronic structure"; item.type = "Real"; @@ -614,6 +598,8 @@ For spin-polarized calculations (nspin=2 or nspin=4) that are difficult to conve } { Input_Item item("mixing_beta_mag"); + item.domain.set_rule("value>=0"); + item.domain.set_active_when("nspin in [2, 4]"); item.annotation = "mixing parameter for magnetic density"; item.category = "Electronic structure"; item.type = "Real"; @@ -839,6 +825,7 @@ Note: If gamma_only is set to 1, the KPT file will be overwritten. So make sure } { Input_Item item("scf_thr"); + item.domain.set_rule("value>=0"); item.annotation = "charge density error"; item.category = "Electronic structure"; item.type = "Real"; @@ -883,6 +870,7 @@ Note: If gamma_only is set to 1, the KPT file will be overwritten. So make sure } { Input_Item item("scf_thr_type"); + item.domain.set_choices({"1", "2"}); item.annotation = "type of the criterion of scf_thr, 1: reci drho for " "pw, 2: real drho for lcao"; item.category = "Electronic structure"; @@ -924,6 +912,7 @@ Note: If gamma_only is set to 1, the KPT file will be overwritten. So make sure } { Input_Item item("scf_os_thr"); + item.domain.set_rule("value<0"); item.annotation = "charge density threshold for oscillation"; item.category = "Electronic structure"; item.type = "Real"; @@ -931,12 +920,6 @@ Note: If gamma_only is set to 1, the KPT file will be overwritten. So make sure item.default_value = "-0.01"; item.unit = ""; read_sync_double(input.scf_os_thr); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.scf_os_thr >= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "scf_os_thr should be negative"); - } - }; this->add_item(item); } { @@ -1029,6 +1012,7 @@ Use case: When experimental or high-level theoretical results suggest that the S } { Input_Item item("dfthalf_type"); + item.domain.set_choices({"0", "1"}); item.annotation = "DFT-1/2 type, 0:off; 1:shell DFT-1/2"; item.category = "Electronic structure"; item.type = "Integer"; @@ -1071,6 +1055,7 @@ Use case: When experimental or high-level theoretical results suggest that the S } { Input_Item item("use_k_continuity"); + item.domain.add_requirement("use_k_continuity==true", "basis_type==pw and calculation!=nscf and kpar==1 and nspin!=2 and esolver_type!=sdft"); item.annotation = "whether to use k-point continuity for initializing wave functions"; item.category = "Plane wave related variables"; item.type = "Boolean"; @@ -1079,23 +1064,6 @@ Use case: When experimental or high-level theoretical results suggest that the S item.unit = ""; item.set_availability("basis_type==pw"); read_sync_bool(input.use_k_continuity); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.use_k_continuity && para.input.basis_type != "pw") { - ModuleBase::WARNING_QUIT("ReadInput", "use_k_continuity only works for PW basis"); - } - if (para.input.use_k_continuity && para.input.calculation == "nscf") { - ModuleBase::WARNING_QUIT("ReadInput", "use_k_continuity cannot work for NSCF calculation"); - } - if (para.input.use_k_continuity && para.input.kpar > 1) { - ModuleBase::WARNING_QUIT("ReadInput", "use_k_continuity cannot work for k-point parallel calculation"); - } - if (para.input.use_k_continuity && para.input.nspin == 2) { - ModuleBase::WARNING_QUIT("ReadInput", "use_k_continuity cannot work for spin-polarized calculation"); - } - if (para.input.use_k_continuity && para.input.esolver_type == "sdft") { - ModuleBase::WARNING_QUIT("ReadInput", "use_k_continuity cannot work for SDFT calculation"); - } - }; this->add_item(item); } { @@ -1136,6 +1104,7 @@ Use case: When experimental or high-level theoretical results suggest that the S // LCAO { Input_Item item("nb2d"); + item.domain.set_rule("value>=0"); item.annotation = "matrix 2d division"; item.category = "System variables"; item.type = "Integer"; @@ -1143,12 +1112,6 @@ Use case: When experimental or high-level theoretical results suggest that the S item.default_value = "0"; item.unit = ""; read_sync_int(input.nb2d); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.nb2d < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "nb2d should be greater than 0"); - } - }; this->add_item(item); } { @@ -1226,6 +1189,7 @@ Use case: When experimental or high-level theoretical results suggest that the S } { Input_Item item("bx"); + item.domain.set_rule("value>=0 and value<=10"); item.annotation = "division of an element grid in FFT grid along x"; item.category = "Numerical atomic orbitals related variables"; item.type = "Integer"; @@ -1233,12 +1197,6 @@ Use case: When experimental or high-level theoretical results suggest that the S item.default_value = "0"; item.unit = ""; read_sync_int(input.bx); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.bx > 10) - { - ModuleBase::WARNING_QUIT("ReadInput", "bx should be no more than 10"); - } - }; item.reset_value = [](const Input_Item& item, Parameter& para) { if (para.input.basis_type == "pw" || para.input.basis_type == "lcao_in_pw" || para.input.calculation == "get_wf") @@ -1252,6 +1210,7 @@ Use case: When experimental or high-level theoretical results suggest that the S } { Input_Item item("by"); + item.domain.set_rule("value>=0 and value<=10"); item.annotation = "division of an element grid in FFT grid along y"; item.category = "Numerical atomic orbitals related variables"; item.type = "Integer"; @@ -1259,16 +1218,11 @@ Use case: When experimental or high-level theoretical results suggest that the S item.default_value = "0"; item.unit = ""; read_sync_int(input.by); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.by > 10) - { - ModuleBase::WARNING_QUIT("ReadInput", "by should be no more than 10"); - } - }; this->add_item(item); } { Input_Item item("bz"); + item.domain.set_rule("value>=0 and value<=10"); item.annotation = "division of an element grid in FFT grid along z"; item.category = "Numerical atomic orbitals related variables"; item.type = "Integer"; @@ -1276,12 +1230,6 @@ Use case: When experimental or high-level theoretical results suggest that the S item.default_value = "0"; item.unit = ""; read_sync_int(input.bz); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.bz > 10) - { - ModuleBase::WARNING_QUIT("ReadInput", "bz should be no more than 10"); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_inp_model.cpp b/source/source_io/module_parameter/read_inp_model.cpp index cdfd4d2ebed..d5d4e71c232 100644 --- a/source/source_io/module_parameter/read_inp_model.cpp +++ b/source/source_io/module_parameter/read_inp_model.cpp @@ -38,6 +38,7 @@ void ReadInput::item_model() } { Input_Item item("dip_cor_flag"); + item.domain.add_requirement("dip_cor_flag==true", "efield_flag==true"); item.annotation = "dipole correction"; item.category = "Electric field and dipole correction"; item.type = "Boolean"; @@ -49,17 +50,12 @@ void ReadInput::item_model() item.default_value = "False"; item.unit = ""; item.set_availability("efield_flag==true"); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.dip_cor_flag && !para.input.efield_flag) - { - ModuleBase::WARNING_QUIT("ReadInput", "dipole correction is not active if efield_flag=false !"); - } - }; read_sync_bool(input.dip_cor_flag); this->add_item(item); } { Input_Item item("efield_dir"); + item.domain.add_requirement("gate_flag==true and efield_flag==true", "dip_cor_flag==true"); item.annotation = "the direction of the electric field or dipole correction"; item.category = "Electric field and dipole correction"; item.type = "Integer"; @@ -70,14 +66,6 @@ void ReadInput::item_model() item.default_value = "2"; item.unit = ""; item.set_availability("efield_flag==true"); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.gate_flag && para.input.efield_flag && !para.input.dip_cor_flag) - { - ModuleBase::WARNING_QUIT("ReadInput", - "gate field cannot be used with " - "efield if dip_cor_flag=false !"); - } - }; read_sync_int(input.efield_dir); this->add_item(item); } @@ -274,6 +262,7 @@ void ReadInput::item_model() // vdW Correction { Input_Item item("vdw_method"); + item.domain.set_choices({"none", "d2", "d3_0", "d3_bj", "d4"}); item.annotation = "the method of calculating vdw (none ; d2 ; d3_0 ; d3_bj ; d4)"; item.category = "vdW correction"; item.type = "String"; @@ -305,6 +294,7 @@ If set to default, ABACUS infers the functional name from dft_functional or pseu } { Input_Item item("vdw_d4_model"); + item.domain.set_choices({"d4", "d4s", "D4", "D4S"}); item.annotation = "DFT-D4 dispersion model"; item.category = "vdW correction"; item.type = "String"; @@ -316,13 +306,6 @@ Available options are: item.unit = ""; item.set_availability("vdw_method==d4"); read_sync_string(input.vdw_d4_model); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.vdw_d4_model != "d4" && para.input.vdw_d4_model != "d4s" - && para.input.vdw_d4_model != "D4" && para.input.vdw_d4_model != "D4S") - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_d4_model must be d4 or d4s"); - } - }; this->add_item(item); } { @@ -469,6 +452,7 @@ Namely, each line contains the element name and the corresponding parameter.)"; } { Input_Item item("vdw_c6_unit"); + item.domain.set_choices({"Jnm6/mol", "eVA6"}); item.annotation = "unit of C6, Jnm6/mol or eVA6"; item.category = "vdW correction"; item.type = "String"; @@ -479,12 +463,6 @@ Namely, each line contains the element name and the corresponding parameter.)"; item.unit = ""; item.set_availability("vdw_method==d2 and vdw_c6_file!=default"); read_sync_string(input.vdw_C6_unit); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if ((para.input.vdw_C6_unit != "Jnm6/mol") && (para.input.vdw_C6_unit != "eVA6")) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_C6_unit must be Jnm6/mol or eVA6"); - } - }; this->add_item(item); } { @@ -505,6 +483,7 @@ Namely, each line contains the element name and the corresponding parameter.)"; } { Input_Item item("vdw_r0_unit"); + item.domain.set_choices({"A", "Bohr"}); item.annotation = "unit of R0, A or Bohr"; item.category = "vdW correction"; item.type = "String"; @@ -515,16 +494,12 @@ Namely, each line contains the element name and the corresponding parameter.)"; item.unit = ""; item.set_availability("vdw_method==d2 and vdw_r0_file!=default"); read_sync_string(input.vdw_R0_unit); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if ((para.input.vdw_R0_unit != "A") && (para.input.vdw_R0_unit != "Bohr")) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_R0_unit must be A or Bohr"); - } - }; this->add_item(item); } { Input_Item item("vdw_cutoff_type"); + item.domain.set_choices({"radius", "period"}); + item.domain.add_requirement("vdw_method in [d3_0, d3_bj, d4]", "vdw_cutoff_type==radius"); item.annotation = "expression model of periodic structure, radius or period"; item.category = "vdW correction"; item.type = "String"; @@ -533,19 +508,6 @@ Namely, each line contains the element name and the corresponding parameter.)"; * period: The extent of the D2 supercell is explicitly specified using the vdw_cutoff_period keyword. DFT-D3 and DFT-D4 require radius.)"; item.default_value = "radius"; item.unit = ""; - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.vdw_cutoff_type != "radius" && para.input.vdw_cutoff_type != "period") - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_cutoff_type must be radius or period"); - } - if (para.input.vdw_cutoff_type == "period" - && (para.input.vdw_method == "d3_0" || para.input.vdw_method == "d3_bj" - || para.input.vdw_method == "d4")) - { - ModuleBase::WARNING_QUIT("ReadInput", - "DFT-D3 and DFT-D4 require vdw_cutoff_type=radius"); - } - }; read_sync_string(input.vdw_cutoff_type); this->add_item(item); } @@ -594,6 +556,7 @@ Namely, each line contains the element name and the corresponding parameter.)"; } { Input_Item item("vdw_radius_unit"); + item.domain.set_choices({"A", "Bohr"}); item.annotation = "unit of radius cutoff for periodic structure"; item.category = "vdW correction"; item.type = "String"; @@ -604,16 +567,11 @@ Namely, each line contains the element name and the corresponding parameter.)"; item.unit = ""; item.set_availability("vdw_cutoff_type==radius"); read_sync_string(input.vdw_radius_unit); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if ((para.input.vdw_radius_unit != "A") && (para.input.vdw_radius_unit != "Bohr")) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_radius_unit must be A or Bohr"); - } - }; this->add_item(item); } { Input_Item item("vdw_cutoff_width2"); + item.domain.set_rule("value>=0"); item.annotation = "smooth cutoff width for two-body dispersion"; item.category = "vdW correction"; item.type = "Real"; @@ -623,16 +581,11 @@ A value of zero disables smoothing for the two-body contribution.)"; item.unit = "Bohr"; item.set_availability("vdw_method in [d3_0, d3_bj, d4]"); read_sync_double(input.vdw_cutoff_width2); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.vdw_cutoff_width2 < 0.0) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_cutoff_width2 must be non-negative"); - } - }; this->add_item(item); } { Input_Item item("vdw_cutoff_width3"); + item.domain.set_rule("value>=0"); item.annotation = "smooth cutoff width for three-body dispersion"; item.category = "vdW correction"; item.type = "Real"; @@ -642,12 +595,6 @@ A value of zero disables smoothing for the three-body contribution.)"; item.unit = "Bohr"; item.set_availability("vdw_method in [d3_0, d3_bj, d4]"); read_sync_double(input.vdw_cutoff_width3); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.vdw_cutoff_width3 < 0.0) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_cutoff_width3 must be non-negative"); - } - }; this->add_item(item); } { @@ -691,6 +638,7 @@ A value of zero disables smoothing for the three-body contribution.)"; } { Input_Item item("vdw_cn_thr"); + item.domain.set_rule("value>0"); item.annotation = "radius cutoff for cn"; item.category = "vdW correction"; item.type = "Real"; @@ -706,16 +654,11 @@ A value of zero disables smoothing for the three-body contribution.)"; } }; read_sync_double(input.vdw_cn_thr); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.vdw_cn_thr <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_cn_thr <= 0 is not allowd"); - } - }; this->add_item(item); } { Input_Item item("vdw_cn_thr_unit"); + item.domain.set_choices({"A", "Bohr"}); item.annotation = "unit of cn_thr, Bohr or Angstrom"; item.category = "vdW correction"; item.type = "String"; @@ -725,12 +668,6 @@ A value of zero disables smoothing for the three-body contribution.)"; item.default_value = "Bohr"; item.unit = ""; read_sync_string(input.vdw_cn_thr_unit); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if ((para.input.vdw_cn_thr_unit != "A") && (para.input.vdw_cn_thr_unit != "Bohr")) - { - ModuleBase::WARNING_QUIT("ReadInput", "vdw_cn_thr_unit must be A or Bohr"); - } - }; this->add_item(item); } } diff --git a/source/source_io/module_parameter/read_inp_other.cpp b/source/source_io/module_parameter/read_inp_other.cpp index 68f17c0bdf9..e1f8f83418c 100644 --- a/source/source_io/module_parameter/read_inp_other.cpp +++ b/source/source_io/module_parameter/read_inp_other.cpp @@ -17,6 +17,7 @@ void ReadInput::item_others() // non-collinear spin-constrained { Input_Item item("sc_mag_switch"); + item.domain.add_requirement("sc_mag_switch==true", "nspin in [2, 4]"); item.annotation = "switch to control spin-constrained DFT"; item.category = "Spin-Constrained DFT"; item.type = "Boolean"; @@ -24,15 +25,6 @@ void ReadInput::item_others() item.default_value = "False"; item.unit = ""; read_sync_bool(input.sc_mag_switch); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.sc_mag_switch) - { - if (para.input.nspin != 4 && para.input.nspin != 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "nspin must be 2 or 4 when sc_mag_switch is true"); - } - } - }; this->add_item(item); } { @@ -48,6 +40,7 @@ void ReadInput::item_others() } { Input_Item item("sc_thr"); + item.domain.set_rule("value>=0"); item.annotation = "Convergence criterion of spin-constrained iteration (RMS) in uB"; item.category = "Spin-Constrained DFT"; item.type = "Real"; @@ -56,16 +49,11 @@ void ReadInput::item_others() item.unit = "uB"; item.set_availability("sc_mag_switch==true"); read_sync_double(input.sc_thr); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.sc_thr < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "sc_thr must >= 0"); - } - }; this->add_item(item); } { Input_Item item("nsc"); + item.domain.set_rule("value>0"); item.annotation = "Maximal number of spin-constrained iteration"; item.category = "Spin-Constrained DFT"; item.type = "Integer"; @@ -74,16 +62,11 @@ void ReadInput::item_others() item.unit = ""; item.set_availability("sc_mag_switch==true"); read_sync_int(input.nsc); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.nsc <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "nsc must > 0"); - } - }; this->add_item(item); } { Input_Item item("nsc_min"); + item.domain.set_rule("value>0"); item.annotation = "Minimum number of spin-constrained iteration"; item.category = "Spin-Constrained DFT"; item.type = "Integer"; @@ -92,16 +75,11 @@ void ReadInput::item_others() item.unit = ""; item.set_availability("sc_mag_switch==true"); read_sync_int(input.nsc_min); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.nsc_min <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "nsc_min must > 0"); - } - }; this->add_item(item); } { Input_Item item("alpha_trial"); + item.domain.set_rule("value>0"); item.annotation = "Initial trial step size for lambda in eV/uB^2"; item.category = "Spin-Constrained DFT"; item.type = "Real"; @@ -110,16 +88,11 @@ void ReadInput::item_others() item.unit = "eV/uB^2"; item.set_availability("sc_mag_switch==true"); read_sync_double(input.alpha_trial); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.alpha_trial <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "alpha_trial must > 0"); - } - }; this->add_item(item); } { Input_Item item("sccut"); + item.domain.set_rule("value>0"); item.annotation = "Maximal step size for lambda in eV/uB"; item.category = "Spin-Constrained DFT"; item.type = "Real"; @@ -128,12 +101,6 @@ void ReadInput::item_others() item.unit = "eV/uB"; item.set_availability("sc_mag_switch==true"); read_sync_double(input.sccut); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.sccut <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "sccut must > 0"); - } - }; this->add_item(item); } { @@ -150,6 +117,7 @@ void ReadInput::item_others() } { Input_Item item("sc_scf_thr"); + item.domain.set_rule("value>0"); item.annotation = "Density error threshold for inner loop of spin-constrained SCF"; item.category = "Spin-Constrained DFT"; item.type = "Real"; @@ -158,12 +126,6 @@ void ReadInput::item_others() item.unit = ""; item.set_availability("sc_mag_switch==true"); read_sync_double(input.sc_scf_thr); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.sc_scf_thr <= 0.0) - { - ModuleBase::WARNING_QUIT("ReadInput", "sc_scf_thr must > 0.0"); - } - }; this->add_item(item); } { @@ -182,6 +144,7 @@ When false (default), both the direction and magnitude of the magnetic moment ar } { Input_Item item("sc_lambda_strategy"); + item.domain.set_choices({"bfgs", "bfgs2", "linear_response", "augmented_lagrangian", "hybrid_delayed", "linear_scan"}); item.annotation = "lambda update strategy for spin-constrained DFT"; item.category = "Spin-Constrained DFT"; item.type = "String"; @@ -195,13 +158,6 @@ When false (default), both the direction and magnitude of the magnetic moment ar item.unit = ""; item.set_availability("sc_mag_switch==true"); read_sync_string(input.sc_lambda_strategy); - item.check_value = [](const Input_Item& item, const Parameter& para) { - const std::vector valid = {"bfgs", "bfgs2", "linear_response", "augmented_lagrangian", "hybrid_delayed", "linear_scan"}; - if (std::find(valid.begin(), valid.end(), para.input.sc_lambda_strategy) == valid.end()) - { - ModuleBase::WARNING_QUIT("ReadInput", "sc_lambda_strategy must be bfgs, bfgs2, linear_response, augmented_lagrangian, hybrid_delayed, or linear_scan"); - } - }; this->add_item(item); } { @@ -839,6 +795,7 @@ When false (default), both the direction and magnitude of the magnetic moment ar } { Input_Item item("rdmft_power_alpha"); + item.domain.set_rule("value>=0 and value<=1"); item.annotation = "the alpha parameter of power-functional, g(occ_number) = occ_number^alpha" " used in exx-type functionals such as muller and power"; item.category = "Reduced Density Matrix Functional Theory"; @@ -857,12 +814,6 @@ When false (default), both the direction and magnitude of the magnetic moment ar para.input.rdmft_power_alpha = 0.5; } }; - item.check_value = [](const Input_Item& item, const Parameter& para) { - if( (para.input.rdmft_power_alpha < 0) || (para.input.rdmft_power_alpha > 1) ) - { - ModuleBase::WARNING_QUIT("ReadInput", "rdmft_power_alpha should be greater than 0.0 and less than 1.0"); - } - }; this->add_item(item); } @@ -894,6 +845,7 @@ When false (default), both the direction and magnitude of the magnetic moment ar } { Input_Item item("ecutexx"); + item.domain.set_rule("value>=0"); item.annotation = "energy cutoff for exx calculation, Ry"; item.category = "Exact Exchange (PW)"; item.type = "Real"; @@ -901,29 +853,18 @@ When false (default), both the direction and magnitude of the magnetic moment ar item.default_value = "same as ecutrho"; item.unit = "Ry"; read_sync_double(input.ecutexx); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.ecutexx < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "ecutexx must >= 0"); - } - }; this->add_item(item); } { Input_Item item("exx_batch_size"); + item.domain.set_rule("value>=0"); item.annotation = "band chunk width of the EXX batched path, 0 = all bands"; item.category = "Exact Exchange (PW)"; item.type = "Integer"; item.description = "Number of bands processed per round of the EXX batched FFT path. 0 (the default) processes all bands in one round, which is fastest but needs nbands * nxyz work buffers; a positive value processes the bands in chunks of that width, trading some performance for a proportionally smaller memory footprint. The result is independent of the chunking."; item.default_value = "0"; read_sync_int(input.exx_batch_size); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.exx_batch_size < 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "exx_batch_size must >= 0"); - } - }; this->add_item(item); } @@ -950,6 +891,7 @@ When false (default), both the direction and magnitude of the magnetic moment ar } { Input_Item item("exx_ene_thr"); + item.domain.set_rule("value>0"); item.annotation = "threshold for exx outer loop when exx_thr_type = energy"; item.category = "Exact Exchange (PW)"; item.type = "Real"; @@ -958,12 +900,6 @@ When false (default), both the direction and magnitude of the magnetic moment ar item.unit = "Ry"; item.set_availability("exx_thr_type==energy"); read_sync_double(input.exx_ene_thr); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.exx_ene_thr <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "exx_ene_thr must > 0"); - } - }; this->add_item(item); } } diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 80a55319466..880234ed6da 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -259,6 +259,8 @@ In molecular dynamics calculations, the output frequency is controlled by out_fr } { Input_Item item("out_wfc_pw"); + item.domain.set_choices({"0", "1", "2"}); + item.domain.add_requirement("out_wfc_pw!=0", "basis_type==pw"); item.annotation = "output wave functions"; item.category = "Output information"; item.type = "Integer"; @@ -280,20 +282,12 @@ With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` fil item.unit = ""; item.set_availability("basis_type==pw and esolver_type==ksdft"); read_sync_int(input.out_wfc_pw); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_wfc_pw < 0 || para.input.out_wfc_pw > 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "out_wfc_pw should be 0, 1, or 2"); - } - if (para.input.basis_type != "pw" && para.input.out_wfc_pw != 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "out_wfc_pw is only available for basis_type = pw"); - } - }; this->add_item(item); } { Input_Item item("out_wfc_lcao"); + item.domain.set_choices({"0", "1", "2"}); + item.domain.add_requirement("out_wfc_lcao!=0", "basis_type==lcao"); item.annotation = "ouput LCAO wave functions, 0, no output 1: text, 2: binary"; item.category = "Output information"; item.type = "Integer"; @@ -321,16 +315,6 @@ Also controled by out_freq_ion and out_app_flag. para.input.out_wfc_lcao = 1; } }; - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_wfc_lcao < 0 || para.input.out_wfc_lcao > 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "out_wfc_lcao should be 0, 1, or 2"); - } - if (para.input.basis_type != "lcao" && para.input.out_wfc_lcao != 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "out_wfc_lcao is only available for basis_type = lcao"); - } - }; this->add_item(item); } { @@ -1485,6 +1469,7 @@ If EXX(exact exchange) is calculated (i.e. dft_fuctional==hse/hf/pbe0/scan0 or r } { Input_Item item("out_librpa_ver"); + item.domain.set_choices({"0", "1"}); item.annotation = "LibRPA output reader version"; item.category = "Output information"; item.type = "Integer"; @@ -1493,13 +1478,6 @@ If EXX(exact exchange) is calculated (i.e. dft_fuctional==hse/hf/pbe0/scan0 or r item.unit = ""; item.set_availability("basis_type==lcao"); read_sync_int(input.out_librpa_ver); - item.check_value = [](const Input_Item&, const Parameter& para) { - const int value = para.input.out_librpa_ver; - if (value != 0 && value != 1) - { - ModuleBase::WARNING_QUIT("INPUT", "out_librpa_ver must be 0 or 1."); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_inp_relax.cpp b/source/source_io/module_parameter/read_inp_relax.cpp index 0d1d22d50c8..414f5221941 100644 --- a/source/source_io/module_parameter/read_inp_relax.cpp +++ b/source/source_io/module_parameter/read_inp_relax.cpp @@ -13,6 +13,7 @@ void ReadInput::item_relax() // Please preserve this ordering when adding new parameters. { Input_Item item("relax_method"); + item.domain.set_choices({"cg", "cg_trad", "bfgs", "bfgs_trad", "sd", "cg_bfgs", "lbfgs"}); item.annotation = "cg; cg_trad; bfgs; bfgs_trad; sd; cg_bfgs; lbfgs"; item.category = "Geometry relaxation"; item.type = "String"; @@ -97,6 +98,7 @@ void ReadInput::item_relax() } { Input_Item item("force_thr"); + item.domain.set_rule("value>=0"); item.annotation = "force threshold, unit: Ry/Bohr"; item.category = "Geometry relaxation"; item.type = "Real"; @@ -127,6 +129,7 @@ void ReadInput::item_relax() } { Input_Item item("force_thr_ev"); + item.domain.set_rule("value>=0"); item.annotation = "force threshold, unit: eV/Angstrom"; item.category = "Geometry relaxation"; item.type = "Real"; @@ -254,6 +257,7 @@ void ReadInput::item_relax() } { Input_Item item("fixed_axes"); + item.domain.add_requirement("fixed_axes in [shape, volume]", "relax_method==cg"); item.annotation = "which axes are fixed"; item.category = "Geometry relaxation"; item.type = "String"; @@ -278,17 +282,11 @@ With relax_method set to cg_trad, bfgs, bfgs_trad, lbfgs, sd, or cg_bfgs, None a item.unit = ""; item.set_availability("calculation==cell-relax"); read_sync_string(input.fixed_axes); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if ((para.input.fixed_axes == "shape" || para.input.fixed_axes == "volume") - && !para.input.uses_simultaneous_relaxation()) - { - ModuleBase::WARNING_QUIT("ReadInput", "fixed shape and fixed volume require relax_method = cg"); - } - }; this->add_item(item); } { Input_Item item("fixed_ibrav"); + item.domain.add_requirement("fixed_ibrav==true", "relax_method==cg and latname!=none"); item.annotation = "whether to preseve lattice type during relaxation"; item.category = "Geometry relaxation"; item.type = "Boolean"; @@ -300,20 +298,11 @@ With relax_method set to cg_trad, bfgs, bfgs_trad, lbfgs, sd, or cg_bfgs, None a item.unit = ""; item.set_availability("relax_method==cg and latname!=none"); read_sync_bool(input.fixed_ibrav); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.fixed_ibrav && !para.input.uses_simultaneous_relaxation()) - { - ModuleBase::WARNING_QUIT("ReadInput", "fixed_ibrav requires relax_method = cg"); - } - if (para.input.latname == "none" && para.input.fixed_ibrav) - { - ModuleBase::WARNING_QUIT("ReadInput", "to use fixed_ibrav, latname must be provided"); - } - }; this->add_item(item); } { Input_Item item("fixed_atoms"); + item.domain.add_requirement("fixed_atoms==true", "calculation!=relax"); item.annotation = "whether to preseve direct coordinates of atoms " "during relaxation"; item.category = "Geometry relaxation"; @@ -323,12 +312,6 @@ With relax_method set to cg_trad, bfgs, bfgs_trad, lbfgs, sd, or cg_bfgs, None a item.default_value = "False"; item.unit = ""; read_sync_bool(input.fixed_atoms); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.fixed_atoms && para.input.calculation == "relax") - { - ModuleBase::WARNING_QUIT("ReadInput", "fixed_atoms is not meant to be used for calculation = relax"); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_inp_sys.cpp b/source/source_io/module_parameter/read_inp_sys.cpp index 04c4741837c..58fefafff87 100644 --- a/source/source_io/module_parameter/read_inp_sys.cpp +++ b/source/source_io/module_parameter/read_inp_sys.cpp @@ -145,6 +145,9 @@ void ReadInput::item_system() } { Input_Item item("calculation"); + item.domain.set_choices({"scf", "relax", "md", "cell-relax", "nscf", "get_s", "get_wf", "get_pchg", "gen_bessel", "gen_opt_abfs", "test_memory", "test_neighbour"}); + item.domain.add_requirement("calculation in [get_pchg, get_wf]", "basis_type!=pw"); + item.domain.add_requirement("calculation==gen_bessel", "basis_type==pw"); item.annotation = "scf; relax; md; cell-relax; nscf; get_s; get_wf; get_pchg; gen_bessel; gen_opt_abfs; test_memory; test_neighbour"; item.category = "System variables"; item.type = "String"; @@ -318,6 +321,7 @@ Socket mode always computes energy. Force and stress extraction follows cal_forc } { Input_Item item("symmetry"); + item.domain.set_choices({"-1", "0", "1"}); item.annotation = "the control of symmetry"; item.category = "System variables"; item.type = "String"; @@ -327,7 +331,7 @@ Socket mode always computes energy. Force and stress extraction follows cal_forc * 1: Symmetry analysis will be performed to determine the type of Bravais lattice and associated symmetry operations (point groups, space groups, primitive cells, and irreducible k-points). For a magnetic system, the symmetry of the initial magnetic structure will be analyzed and preserved. [NOTE] When symmetry is enabled (value 1), k-points are reduced to the irreducible Brillouin zone (IBZ). For explicit k-point lists with custom weights (see KPT file), the custom weights are preserved during symmetry reduction. For Monkhorst-Pack grids, uniform weights are used.)"; - item.default_value = "default"; + item.default_value = "1 normally; 0 or -1 depending on the calculation and enabled features."; read_sync_string(input.symmetry); item.reset_value = [](const Input_Item& item, Parameter& para) { if (para.input.symmetry == "default") @@ -606,6 +610,7 @@ Theory: G. Makov and M. C. Payne, Phys. Rev. B 51, 4014 (1995); PCC: O. Andreuss } { Input_Item item("pcc_2d_axis"); + item.domain.set_choices({"0", "1", "2"}); item.annotation = "open lattice vector of assume_isolated pcc_2d"; item.category = "System variables"; item.type = "Integer"; @@ -613,12 +618,6 @@ Theory: G. Makov and M. C. Payne, Phys. Rev. B 51, 4014 (1995); PCC: O. Andreuss item.default_value = "2"; item.set_availability("assume_isolated==pcc_2d"); read_sync_int(input.pcc_2d_axis); - item.check_value = [](const Input_Item&, const Parameter& para) { - if (para.input.pcc_2d_axis < 0 || para.input.pcc_2d_axis > 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "pcc_2d_axis must be 0, 1 or 2"); - } - }; this->add_item(item); } { @@ -769,6 +768,8 @@ For `basis_type=lcao_in_pw`, `init_wfc` is automatically set to `nao`. } { Input_Item item("mem_saver"); + item.domain.set_choices({"0", "1"}); + item.domain.add_requirement("calculation in [scf, relax]", "mem_saver==0"); item.annotation = "Only for nscf calculations. if set to 1, then a " "memory saving technique will be used for " "many k point calculations."; @@ -782,26 +783,6 @@ Legal values are 0 and 1. For calculation=scf or relax, mem_saver must be 0; set item.default_value = "0"; item.set_availability("calculation==nscf and basis_type==pw"); read_sync_int(input.mem_saver); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.mem_saver != 0 && para.input.mem_saver != 1) - { - const std::string message - = "Invalid INPUT mem_saver=" + longstring(item.str_values) - + ". Legal values are 0 or 1.\n" - "See https://abacus.deepmodeling.com/en/latest/advanced/input_files/input-main.html#mem-saver"; - ModuleBase::WARNING_QUIT("ReadInput", message); - } - if (para.input.mem_saver == 1 - && (para.input.calculation == "scf" || para.input.calculation == "relax")) - { - const std::string message - = "Invalid INPUT mem_saver=" + longstring(item.str_values) - + " with calculation=" + para.input.calculation - + ". Use mem_saver=0 for this calculation; memory saving with mem_saver=1 is intended for nscf.\n" - "See https://abacus.deepmodeling.com/en/latest/advanced/input_files/input-main.html#mem-saver"; - ModuleBase::WARNING_QUIT("ReadInput", message); - } - }; this->add_item(item); } { @@ -982,6 +963,7 @@ In socket_driver mode, this flag independently controls whether the returned fra } { Input_Item item("device"); + item.domain.set_choices({"cpu", "gpu"}); item.annotation = "the computing device for ABACUS"; item.category = "System variables"; item.type = "String"; @@ -998,18 +980,12 @@ Available options are: para.input.device=base_device::information::get_device_flag( para.inp.device, para.inp.basis_type); }; - item.check_value = [](const Input_Item& item, const Parameter& para) { - std::vector avail_list = {"cpu", "gpu"}; - if (std::find(avail_list.begin(), avail_list.end(), para.input.device) == avail_list.end()) - { - const std::string warningstr = nofound_str(avail_list, "device"); - ModuleBase::WARNING_QUIT("ReadInput", warningstr); - } - }; this->add_item(item); } { Input_Item item("precision"); + item.domain.set_choices({"single", "double"}); + item.domain.add_requirement("precision==single", "basis_type!=lcao"); item.annotation = "the computing precision for ABACUS"; item.category = "System variables"; item.type = "String"; @@ -1047,6 +1023,8 @@ Available options are: } { Input_Item item("gint_precision"); + item.domain.set_choices({"single", "double", "mix"}); + item.domain.add_requirement("gint_precision!=double", "basis_type==lcao and nspin!=4"); item.annotation = "the computing precision for LCAO grid integral"; item.category = "System variables"; item.type = "String"; @@ -1057,26 +1035,6 @@ Available options are: item.default_value = "double"; item.set_availability("basis_type==lcao"); read_sync_string(input.gint_precision); - item.check_value = [](const Input_Item& item, const Parameter& para) { - std::vector avail_list = {"single", "double", "mix"}; - if (std::find(avail_list.begin(), avail_list.end(), para.input.gint_precision) == avail_list.end()) - { - const std::string warningstr = nofound_str(avail_list, "gint_precision"); - ModuleBase::WARNING_QUIT("ReadInput", warningstr); - } - if (para.inp.gint_precision != "double" && para.inp.basis_type != "lcao") - { - ModuleBase::WARNING_QUIT( - "ReadInput", - "gint_precision = single or mix is currently supported only for LCAO calculations.\n"); - } - if (para.inp.gint_precision != "double" && para.inp.nspin == 4) - { - ModuleBase::WARNING_QUIT( - "ReadInput", - "gint_precision = single or mix is not supported for nspin = 4 (noncollinear/SOC) calculations.\n"); - } - }; this->add_item(item); } { @@ -1173,6 +1131,7 @@ updates structures smoothly enough for second-order extrapolation.)"; } { Input_Item item("ecutwfc"); + item.domain.set_rule("value>0"); item.annotation = "energy cutoff for wave functions"; item.category = "Plane wave related variables"; item.type = "Real"; @@ -1201,16 +1160,11 @@ updates structures smoothly enough for second-order extrapolation.)"; } } }; - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.ecutwfc <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "ecutwfc should be positive"); - } - }; this->add_item(item); } { Input_Item item("ecutrho"); + item.domain.set_rule("value>0"); item.annotation = "energy cutoff for charge density and potential"; item.category = "Plane wave related variables"; item.type = "Real"; @@ -1423,6 +1377,7 @@ updates structures smoothly enough for second-order extrapolation.)"; } { Input_Item item("diag_subspace"); + item.domain.set_choices({"0", "1", "2"}); item.annotation = "method of subspace diagonalization in dav_subspace. 0:LaPack; 1:genelpa, 2:scalapack"; item.category = "Plane wave related variables"; item.type = "Integer"; @@ -1448,6 +1403,7 @@ updates structures smoothly enough for second-order extrapolation.)"; } { Input_Item item("fft_mode"); + item.domain.set_choices({"0", "1", "2", "3"}); item.annotation = "mode of FFTW"; item.category = "Plane wave related variables"; item.type = "Integer"; diff --git a/source/source_io/module_parameter/read_inp_tddft.cpp b/source/source_io/module_parameter/read_inp_tddft.cpp index b447557abbb..484aedf8331 100644 --- a/source/source_io/module_parameter/read_inp_tddft.cpp +++ b/source/source_io/module_parameter/read_inp_tddft.cpp @@ -324,6 +324,7 @@ All methods check the residual at the end of the linear solve, before orthonorma } { Input_Item item("lin_maxiter"); + item.domain.set_rule("value>0"); item.annotation = "maximum iterations per linear solve"; item.category = "Real-Time TDDFT (PW)"; item.type = "Integer"; @@ -335,16 +336,11 @@ All methods check the residual at the end of the linear solve, before orthonorma para.input.lin_maxiter = parse_td_integer(item); }; sync_int(input.lin_maxiter); - item.check_value = [](const Input_Item&, const Parameter& para) { - if (para.inp.lin_maxiter <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "lin_maxiter must be a positive integer."); - } - }; this->add_item(item); } { Input_Item item("lin_gmres_restart"); + item.domain.set_rule("value>0"); item.annotation = "Arnoldi dimension of restarted GMRES"; item.category = "Real-Time TDDFT (PW)"; item.type = "Integer"; @@ -356,12 +352,6 @@ All methods check the residual at the end of the linear solve, before orthonorma para.input.lin_gmres_restart = parse_td_integer(item); }; sync_int(input.lin_gmres_restart); - item.check_value = [](const Input_Item&, const Parameter& para) { - if (para.inp.lin_gmres_restart <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "lin_gmres_restart must be positive."); - } - }; this->add_item(item); } { @@ -413,6 +403,7 @@ Only effective for `lin_solver=gmres`; ignored otherwise.)"; // real time TDDFT { Input_Item item("td_dt"); + item.domain.set_rule("value>0"); item.annotation = "time step for evolving wavefunction"; item.category = "Real-Time TDDFT (Common)"; item.type = "Real"; diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 45b0d4d4401..85f4b150df9 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -89,7 +89,7 @@ void read_information(std::stringstream& ifs, std::vector& output, std::string word; while (iss >> word) { - if (delimiters.find(word[0]) != std::string::npos) + if (delimiters.find(word[0]) != std::string::npos || (word == "//" && !output.empty())) { break; } @@ -509,6 +509,10 @@ std::string ReadInput::prepare_input(Parameter& param) for (const auto& entry : this->input_lists) { errors += entry.second.domain.validate_resolved(entry.first, values); + } + if (!errors.empty()) { return errors; } + for (const auto& entry : this->input_lists) + { errors += entry.second.domain.validate_requirements(entry.first, values); } return errors; @@ -607,6 +611,8 @@ void ReadInput::write_txt_input(const Parameter& param, const std::string& filen for (auto& item: this->input_lists) { Input_Item* p_item = &(item.second); + // Do not serialize an inactive internal marker as a public INPUT value. + if (!p_item->domain.can_write(param)) { continue; } if (p_item->get_final_value == nullptr) { continue; } diff --git a/source/source_io/module_parameter/read_input_item_md.cpp b/source/source_io/module_parameter/read_input_item_md.cpp index d5f6dc6bd67..a41343fd261 100644 --- a/source/source_io/module_parameter/read_input_item_md.cpp +++ b/source/source_io/module_parameter/read_input_item_md.cpp @@ -12,6 +12,7 @@ void ReadInput::item_md() // 9. Molecular dynamics { Input_Item item("md_type"); + item.domain.set_choices({"fire", "nve", "nvt", "npt", "langevin", "msst"}); item.annotation = "choose ensemble"; item.category = "Molecular dynamics"; item.type = "String"; @@ -71,6 +72,7 @@ void ReadInput::item_md() } { Input_Item item("md_thermostat"); + item.domain.set_choices({"nhc", "anderson", "berendsen", "rescaling", "rescale_v", "csvr"}); item.annotation = "choose thermostat"; item.category = "Molecular dynamics"; item.type = "String"; @@ -338,6 +340,7 @@ Note: It is a system-dependent empirical parameter, ranging from 1/(40*md_dt) to } { Input_Item item("md_pcouple"); + item.domain.set_choices({"none", "xyz", "xy", "xz", "yz"}); item.annotation = "whether couple different components: xyz, xy, yz, xz, none"; item.category = "Molecular dynamics"; item.type = "String"; @@ -650,18 +653,13 @@ Note: It is a system-dependent empirical parameter. An improper choice might lea } { Input_Item item("msst_qmass"); + item.domain.set_rule("value>0"); item.annotation = "mass of thermostat"; item.category = "Molecular dynamics"; item.type = "Real"; item.description = "Inertia of the extended system variable. You should set a number larger than 0."; item.default_value = "No default"; item.unit = ""; - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.mdp.msst_qmass <= 0) - { - ModuleBase::WARNING_QUIT("ReadInput", "msst_qmass must be greater than 0!"); - } - }; read_sync_double(input.mdp.msst_qmass); this->add_item(item); } diff --git a/source/source_io/module_parameter/read_input_item_sdft.cpp b/source/source_io/module_parameter/read_input_item_sdft.cpp index 6924fa8b300..2bb794c91f5 100644 --- a/source/source_io/module_parameter/read_input_item_sdft.cpp +++ b/source/source_io/module_parameter/read_input_item_sdft.cpp @@ -13,23 +13,17 @@ void ReadInput::item_sdft() // Please preserve this ordering when adding new parameters. { Input_Item item("method_sto"); + item.domain.set_choices({"1", "2"}); item.annotation = "1: slow and save memory, 2: fast and waste memory"; item.category = "Electronic structure (SDFT)"; item.type = "Integer"; item.description = R"(Different methods to do stochastic DFT * 1: Calculate twice, this method cost less memory but is slower. -* 2: Calculate once but needs much more memory. This method is much faster. Besides, it calculates with a smaller nche_sto. However, when the memory is not enough, only method 1 can be used. -* other: use 2)"; +* 2: Calculate once but needs much more memory. This method is much faster. Besides, it calculates with a smaller nche_sto. However, when the memory is not enough, only method 1 can be used.)"; item.default_value = "2"; item.unit = ""; item.set_availability("esolver_type==sdft"); read_sync_int(input.method_sto); - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.method_sto != 1 && para.input.method_sto != 2) - { - ModuleBase::WARNING_QUIT("ReadInput", "method_sto should be 1 or 2"); - } - }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/test/test_input_domain.cpp b/source/source_io/module_parameter/test/test_input_domain.cpp index f3e49a446a8..bc7adbb2ad6 100644 --- a/source/source_io/module_parameter/test/test_input_domain.cpp +++ b/source/source_io/module_parameter/test/test_input_domain.cpp @@ -128,3 +128,17 @@ TEST(InputDomain, InternalUnsetValueIsNotAPublicChoice) EXPECT_FALSE(domain.validate_resolved("criterion", unset).empty()); EXPECT_TRUE(domain.validate_resolved("criterion", resolved).empty()); } + +TEST(InputDomain, RejectsNonfiniteRuleLiteralsAndVectorScalarComparisons) +{ + ModuleIO::InputDomain domain; + domain.add_requirement("threshold>nan", "flag==true"); + const std::map types = { + {"threshold", ModuleIO::AvailabilityValueKind::Real}, + {"flag", ModuleIO::AvailabilityValueKind::Boolean}, + {"axes", ModuleIO::AvailabilityValueKind::IntegerVector}}; + EXPECT_THROW(domain.validate_schema("bad_threshold", types), std::invalid_argument); + ModuleIO::InputDomain vector_domain; + vector_domain.add_requirement("axes==1", "flag==true"); + EXPECT_THROW(vector_domain.validate_schema("bad_vector", types), std::invalid_argument); +} diff --git a/source/source_io/test_serial/read_input_item_test.cpp b/source/source_io/test_serial/read_input_item_test.cpp index 391844eb04c..ab1f0705661 100644 --- a/source/source_io/test_serial/read_input_item_test.cpp +++ b/source/source_io/test_serial/read_input_item_test.cpp @@ -37,6 +37,20 @@ class TestParameters class InputTest : public testing::Test { protected: + void check_item(const ModuleIO::Input_Item& item, const Parameter& param) + { + ModuleIO::ReadInput schema(-1); + ModuleIO::InputValues values; + for (const auto& entry : schema.get_input_lists()) + { + if (entry.second.domain.bound()) { values[entry.first] = entry.second.domain.value(param); } + } + std::string error = item.domain.validate_resolved(item.label, values); + error += item.domain.validate_requirements(item.label, values); + if (!error.empty()) { ModuleBase::WARNING_QUIT("ReadInput", error); } + if (item.check_value) { item.check_value(item, param); } + } + std::vector>::iterator find_label( const std::string& label, std::vector>& input_lists) @@ -120,37 +134,37 @@ TEST_F(InputTest, PccSelectionAndUnsupportedCombinations) it->second.str_values = {"pcc_0d"}; it->second.read_value(it->second, param); EXPECT_EQ(input.assume_isolated, "pcc_0d"); - it->second.check_value(it->second, param); + check_item(it->second, param); input.basis_type = "lcao"; input.calculation = "relax"; - it->second.check_value(it->second, param); + check_item(it->second, param); const Input_para valid = input; input.nspin = 4; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input = valid; input.device = "gpu"; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input = valid; input.cal_stress = true; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input = valid; input.calculation = "cell-relax"; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input = valid; input.assume_isolated = "pcc_2d"; - it->second.check_value(it->second, param); + check_item(it->second, param); auto axis = find_label("pcc_2d_axis", readinput.input_lists); ASSERT_NE(axis, readinput.input_lists.end()); for (int value = 0; value < 3; ++value) { input.pcc_2d_axis = value; - axis->second.check_value(axis->second, param); + check_item(axis->second, param); } input.pcc_2d_axis = 3; - EXPECT_EXIT(axis->second.check_value(axis->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(axis->second, param), testing::ExitedWithCode(1), ""); input.assume_isolated = "pcc_invalid"; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); } TEST_F(InputTest, RejectInvalidMemSaver) @@ -176,18 +190,21 @@ TEST_F(InputTest, RejectInvalidMemSaver) testing::internal::CaptureStdout(); EXPECT_EXIT(reader.read_parameters(param, filename), testing::ExitedWithCode(1), ""); const std::string diagnostic = testing::internal::GetCapturedStdout(); - const std::string invalid_value = "Invalid INPUT mem_saver=" + std::to_string(value); + const std::string invalid_value = "Invalid INPUT parameter 'mem_saver'"; EXPECT_THAT(diagnostic, testing::HasSubstr(invalid_value)); EXPECT_THAT(diagnostic, testing::HasSubstr("input-main.html#mem-saver")); if (value == 1) { + EXPECT_THAT(diagnostic, testing::HasSubstr("mem_saver=1")); const std::string invalid_mode = "calculation=" + calculation; EXPECT_THAT(diagnostic, testing::HasSubstr(invalid_mode)); - EXPECT_THAT(diagnostic, testing::HasSubstr("mem_saver=0")); + EXPECT_THAT(diagnostic, testing::HasSubstr("Requires mem_saver==0")); } else { - EXPECT_THAT(diagnostic, testing::HasSubstr("0 or 1")); + const std::string supplied = "supplied '" + std::to_string(value) + "'"; + EXPECT_THAT(diagnostic, testing::HasSubstr(supplied)); + EXPECT_THAT(diagnostic, testing::HasSubstr("from {0, 1}")); } EXPECT_EQ(std::remove(filename.c_str()), 0); } @@ -231,26 +248,26 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).calculation = "get_pchg"; TestParameters::input(param).basis_type = "pw"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).calculation = "gen_bessel"; TestParameters::input(param).basis_type = "lcao"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).calculation = "none"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).calculation = "socket"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -260,13 +277,13 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).socket_driver = true; TestParameters::input(param).calculation = "nscf"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).socket_driver = true; TestParameters::input(param).calculation = "scf"; - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); TestParameters::input(param).socket_driver = false; } @@ -274,13 +291,13 @@ TEST_F(InputTest, Item_test) auto it = find_label("esolver_type", readinput.input_lists); TestParameters::input(param).esolver_type = "none"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).esolver_type = "dp"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -288,7 +305,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).calculation = "scf"; it = find_label("esolver_type", readinput.input_lists); testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("esolver_type=lr requires calculation=nscf")); } @@ -303,7 +320,7 @@ TEST_F(InputTest, Item_test) auto it = find_label("smearing_method", readinput.input_lists); TestParameters::input(param).smearing_method = "fix"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -322,13 +339,13 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).kspacing = {0, -1, 1}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).kspacing = {0, 1, 2}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -336,7 +353,7 @@ TEST_F(InputTest, Item_test) auto it = find_label("nbands", readinput.input_lists); TestParameters::input(param).nbands = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -375,14 +392,14 @@ TEST_F(InputTest, Item_test) auto it = find_label("nelec", readinput.input_lists); TestParameters::input(param).nelec = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).nelec = 100; TestParameters::input(param).nbands = 5; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -390,14 +407,14 @@ TEST_F(InputTest, Item_test) auto it = find_label("bndpar", readinput.input_lists); TestParameters::input(param).esolver_type = "sdft"; TestParameters::input(param).bndpar = 1; - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); } { // dft_plus_dmft auto it = find_label("dft_plus_dmft", readinput.input_lists); TestParameters::input(param).basis_type = "pw"; TestParameters::input(param).dft_plus_dmft = true; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -406,17 +423,17 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).mem_saver = 1; it->second.str_values = {"1"}; TestParameters::input(param).calculation = "scf"; - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); TestParameters::input(param).calculation = "relax"; - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); EXPECT_EQ(TestParameters::input(param).mem_saver, 1); } { // gint_precision auto it = find_label("gint_precision", readinput.input_lists); TestParameters::input(param).gint_precision = "invalid"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -424,7 +441,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).basis_type = "pw"; TestParameters::input(param).device = "cpu"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -470,7 +487,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).ecutwfc = 1; TestParameters::input(param).ecutrho = 1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -486,7 +503,7 @@ TEST_F(InputTest, Item_test) auto it = find_label("nb2d", readinput.input_lists); TestParameters::input(param).nb2d = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -535,14 +552,14 @@ TEST_F(InputTest, Item_test) it->second.reset_value(it->second, param); EXPECT_EQ(TestParameters::input(param).init_wfc, "file"); EXPECT_EQ(TestParameters::input(param).init_wfc_file_format, "txt"); - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); it->second.str_values = {"file", "binary"}; it->second.read_value(it->second, param); it->second.reset_value(it->second, param); EXPECT_EQ(TestParameters::input(param).init_wfc, "file"); EXPECT_EQ(TestParameters::input(param).init_wfc_file_format, "binary"); - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); it->second.str_values = {"file"}; it->second.read_value(it->second, param); @@ -611,14 +628,14 @@ TEST_F(InputTest, Item_test) it->second.read_value(it->second, param); it->second.reset_value(it->second, param); testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("not supported for basis_type=pw")); it->second.str_values = {"file", "binary"}; it->second.read_value(it->second, param); it->second.reset_value(it->second, param); - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); EXPECT_EQ(find_label("read_wfc_lcao", readinput.input_lists), readinput.input_lists.end()); TestParameters::input(param).calculation = "get_wf"; @@ -637,7 +654,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).init_chg = "none"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -700,14 +717,14 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).out_dos = 3; TestParameters::input(param).symmetry = "1"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).basis_type = "pw"; TestParameters::input(param).out_dos = 3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -731,7 +748,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).out_ldos = {5, 5}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -757,13 +774,13 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).stm_bias = {1.0, 0.1, 0.0}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).stm_bias = {1.0, 0.0, 2.0}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -793,7 +810,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).ldos_line = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -830,7 +847,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).basis_type = "pw"; TestParameters::input(param).out_proj_band = true; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -850,7 +867,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).nx = 1; TestParameters::input(param).ny = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -859,7 +876,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).ny = 1; TestParameters::input(param).nz = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -868,7 +885,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).nz = 1; TestParameters::input(param).nx = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -884,14 +901,14 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).ndy = 0; it->second.str_values = {"1"}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ndx = 1; TestParameters::input(param).nx = 2; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -907,14 +924,14 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).ndz = 0; it->second.str_values = {"1"}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ndy = 1; TestParameters::input(param).ny = 2; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -930,14 +947,14 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).nz = 2; it->second.str_values = {"1"}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ndz = 1; TestParameters::input(param).nz = 2; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -965,9 +982,9 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).basis_type = "lcao"; TestParameters::input(param).device = "gpu"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); - EXPECT_THAT(output, testing::HasSubstr("Please use ks_solver = elpa with device = gpu")); + EXPECT_THAT(output, testing::HasSubstr("Requires device==cpu")); #ifdef __ELPA TestParameters::input(param).towannier90 = true; TestParameters::input(param).basis_type = "lcao_in_pw"; @@ -994,28 +1011,28 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).ks_solver = "genelpa"; TestParameters::input(param).basis_type = "pw"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ks_solver = "cg"; TestParameters::input(param).basis_type = "lcao"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ks_solver = "scalapack_gvx"; TestParameters::input(param).basis_type = "lcao"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ks_solver = "cg"; TestParameters::input(param).basis_type = "lcao_in_pw"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1061,14 +1078,14 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).fixed_axes = "shape"; TestParameters::input(param).relax_method = "cg_trad"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).fixed_axes = "volume"; TestParameters::input(param).relax_method = "cg_trad"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1077,14 +1094,14 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).fixed_ibrav = true; TestParameters::input(param).relax_method = "cg_trad"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).fixed_ibrav = true; TestParameters::input(param).latname = "none"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1093,7 +1110,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).fixed_atoms = true; TestParameters::input(param).calculation = "relax"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1152,7 +1169,7 @@ TEST_F(InputTest, Item_test) auto it = find_label("method_sto", readinput.input_lists); TestParameters::input(param).method_sto = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1162,31 +1179,31 @@ TEST_F(InputTest, Item_test) it->second.str_values = {"all"}; it->second.read_value(it->second, param); - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); EXPECT_EQ(TestParameters::input(param).nbands_sto, 0); EXPECT_EQ(TestParameters::input(param).esolver_type, "sdft"); it->second.str_values = {"8"}; it->second.read_value(it->second, param); - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); EXPECT_EQ(TestParameters::input(param).nbands_sto, 8); EXPECT_EQ(TestParameters::input(param).esolver_type, "sdft"); it->second.str_values = {"1000000"}; it->second.read_value(it->second, param); - EXPECT_NO_THROW(it->second.check_value(it->second, param)); + EXPECT_NO_THROW(check_item(it->second, param)); it->second.str_values = {"1000001"}; it->second.read_value(it->second, param); testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); it->second.str_values = {"0"}; it->second.read_value(it->second, param); testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); EXPECT_EQ(TestParameters::input(param).nbands_sto, 0); @@ -1195,7 +1212,7 @@ TEST_F(InputTest, Item_test) it->second.str_values = {"-1"}; TestParameters::input(param).nbands_sto = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -1218,7 +1235,7 @@ TEST_F(InputTest, Item_test) TestParameters::input(param).basis_type = "gauss"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1248,15 +1265,15 @@ TEST_F(InputTest, Item_test) auto it = find_label("out_mat_r", readinput.input_lists); TestParameters::input(param).out_hsr[0] = 1; TestParameters::sys(param).gamma_only_local = true; - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).out_hsr[0] = 3; - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).out_hsr[0] = 0; TestParameters::input(param).esolver_type = "lcao"; TestParameters::input(param).out_mat_r[0] = 1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("available")); TestParameters::input(param).out_mat_r[0] = 0; @@ -1294,11 +1311,11 @@ TEST_F(InputTest, Item_test) it->second.read_value(it->second, param); EXPECT_EQ(TestParameters::input(param).out_hsk[0], 2); EXPECT_EQ(TestParameters::input(param).out_hsk[1], 12); - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).out_hsk[0] = 3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NPZ output is not implemented")); } @@ -1313,23 +1330,23 @@ TEST_F(InputTest, Item_test) it->second.read_value(it->second, param); EXPECT_EQ(TestParameters::input(param).out_hsr[0], 2); EXPECT_EQ(TestParameters::input(param).out_hsr[1], 12); - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).out_hsr[0] = 4; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("format must be 0, 1, 2, or 3")); #ifndef __CNPY TestParameters::input(param).out_hsr[0] = 3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("ENABLE_CNPY")); #else TestParameters::input(param).out_hsr[0] = 3; - it->second.check_value(it->second, param); + check_item(it->second, param); #endif } { // out_hr_npz @@ -1506,7 +1523,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).out_mat_dh[0] = 1; TestParameters::input(param).nspin = 4; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1515,7 +1532,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).dm_to_rho = true; GlobalV::NPROC = 2; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -1523,7 +1540,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).gamma_only = true; GlobalV::NPROC = 1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -1531,7 +1548,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).dm_to_rho = true; GlobalV::NPROC = 1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); #endif @@ -1545,14 +1562,14 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).out_wfc_lcao = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).out_wfc_lcao = 1; TestParameters::input(param).basis_type = "pw"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1560,7 +1577,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("bx", readinput.input_lists); TestParameters::input(param).bx = 11; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -1575,7 +1592,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("by", readinput.input_lists); TestParameters::input(param).by = 11; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1583,7 +1600,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("bz", readinput.input_lists); TestParameters::input(param).bz = 11; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1629,7 +1646,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).dip_cor_flag = true; TestParameters::input(param).efield_flag = false; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1639,7 +1656,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).efield_flag = true; TestParameters::input(param).dip_cor_flag = false; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1703,7 +1720,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_c6_unit", readinput.input_lists); TestParameters::input(param).vdw_C6_unit = "test"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1711,7 +1728,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_r0_unit", readinput.input_lists); TestParameters::input(param).vdw_R0_unit = "test"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1719,7 +1736,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_cutoff_type", readinput.input_lists); TestParameters::input(param).vdw_cutoff_type = "test"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1748,7 +1765,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).vdw_cutoff_radius = "-1"; TestParameters::input(param).vdw_method = "d2"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1756,7 +1773,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_radius_unit", readinput.input_lists); TestParameters::input(param).vdw_radius_unit = "test"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1764,7 +1781,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_cutoff_width2", readinput.input_lists); TestParameters::input(param).vdw_cutoff_width2 = -1.0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1772,7 +1789,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_cutoff_width3", readinput.input_lists); TestParameters::input(param).vdw_cutoff_width3 = -1.0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1780,7 +1797,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_cn_thr", readinput.input_lists); TestParameters::input(param).vdw_cn_thr = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1788,7 +1805,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("vdw_cn_thr_unit", readinput.input_lists); TestParameters::input(param).vdw_cn_thr_unit = "test"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1808,7 +1825,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).vdw_cutoff_period = {-1, 1, 1}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1897,7 +1914,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("exx_hybrid_step", readinput.input_lists); TestParameters::input(param).exx_hybrid_step = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1981,7 +1998,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).exx_ccp_rmesh_times = "-1"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1989,7 +2006,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("exx_opt_orb_ecut", readinput.input_lists); TestParameters::input(param).exx_opt_orb_ecut = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -1997,7 +2014,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("exx_opt_orb_tolerence", readinput.input_lists); TestParameters::input(param).exx_opt_orb_tolerence = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2012,7 +2029,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("rpa_ccp_rmesh_times", readinput.input_lists); TestParameters::input(param).rpa_ccp_rmesh_times = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2028,14 +2045,14 @@ TEST_F(InputTest, Item_test2) for (const std::string symmetry : {"-1", "0", "1"}) { TestParameters::input(param).symmetry = symmetry; - it->second.check_value(it->second, param); + check_item(it->second, param); } TestParameters::input(param).out_librpa_ver = 0; - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).out_librpa_ver = 2; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2048,20 +2065,20 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).gdir = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).calculation = "scf"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).basis_type = "lcao_in_pw"; TestParameters::input(param).calculation = "nscf"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); } { // towannier90 @@ -2071,14 +2088,14 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).nspin = 2; TestParameters::input(param).wannier_spin = "none"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("towannier90")); TestParameters::input(param).towannier90 = true; TestParameters::input(param).calculation = "scf"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("towannier90")); } @@ -2119,7 +2136,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).basis_type = "pw"; GlobalV::NPROC = 1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -2128,7 +2145,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).basis_type = "lcao"; GlobalV::NPROC = 1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); @@ -2137,7 +2154,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).basis_type = "pw"; GlobalV::NPROC = 2; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2167,17 +2184,17 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).ntype = 2; it->second.str_values = {"1.0", "2.0"}; TestParameters::sys(param).hubbard_u = {1.0, 2.0}; - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).ntype = 3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ntype = 2; TestParameters::sys(param).hubbard_u = {1.0, -1.0}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2186,17 +2203,17 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).ntype = 2; it->second.str_values = {"1", "2"}; TestParameters::input(param).l_channel = {1, 2}; - it->second.check_value(it->second, param); + check_item(it->second, param); TestParameters::input(param).ntype = 3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).ntype = 2; TestParameters::input(param).l_channel = {1, 4}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2209,7 +2226,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).bessel_nao_ecut = "-1"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2217,7 +2234,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("bessel_nao_rcut", readinput.input_lists); TestParameters::input(param).bessel_nao_rcuts = {-1}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2230,7 +2247,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).bessel_descriptor_ecut = "-1"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2238,7 +2255,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("bessel_descriptor_rcut", readinput.input_lists); TestParameters::input(param).bessel_descriptor_rcut = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2246,13 +2263,13 @@ TEST_F(InputTest, Item_test2) auto it = find_label("sc_mag_switch", readinput.input_lists); TestParameters::input(param).sc_mag_switch = true; // Since sc_mag_switch check is disabled, just call the function without expecting exit - it->second.check_value(it->second, param); + check_item(it->second, param); } { // sc_thr auto it = find_label("sc_thr", readinput.input_lists); TestParameters::input(param).sc_thr = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2260,7 +2277,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("nsc", readinput.input_lists); TestParameters::input(param).nsc = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2268,7 +2285,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("nsc_min", readinput.input_lists); TestParameters::input(param).nsc_min = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2276,7 +2293,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("alpha_trial", readinput.input_lists); TestParameters::input(param).alpha_trial = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2284,7 +2301,7 @@ TEST_F(InputTest, Item_test2) auto it = find_label("sccut", readinput.input_lists); TestParameters::input(param).sccut = -1; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2292,14 +2309,14 @@ TEST_F(InputTest, Item_test2) auto it = find_label("sc_scf_thr", readinput.input_lists); TestParameters::input(param).sc_scf_thr = -1e-3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } { // qo_thr auto it = find_label("qo_thr", readinput.input_lists); TestParameters::input(param).qo_thr = 1e-5; - it->second.check_value(it->second, param); + check_item(it->second, param); } { // qo_strategy auto it = find_label("qo_strategy", readinput.input_lists); @@ -2361,13 +2378,13 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).qo_screening_coeff = {0.2, -0.1}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).qo_screening_coeff = {0.2, 1e-8}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2415,7 +2432,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).esolver_type = "lj"; TestParameters::input(param).mdp.lj_rule = 3; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2426,14 +2443,14 @@ TEST_F(InputTest, Item_test2) it->second.str_values = {"1.0", "2.0"}; TestParameters::input(param).mdp.lj_rcut = {1.0, 2.0}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); TestParameters::input(param).mdp.lj_rcut = {1.0, 2.0, -1.0}; it->second.str_values = {"1.0", "2.0", "-1.0"}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2444,7 +2461,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).mdp.lj_epsilon = {1.0}; it->second.str_values = {"1.0"}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2455,7 +2472,7 @@ TEST_F(InputTest, Item_test2) TestParameters::input(param).mdp.lj_sigma = {1.0}; it->second.str_values = {"1.0"}; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } @@ -2466,21 +2483,21 @@ TEST_F(InputTest, Item_test2) for (const std::string solver : { "cg", "lapack" }) { TestParameters::input(param).lr_grad_solver = solver; - it->second.check_value(it->second, param); // accepted in every build + check_item(it->second, param); // accepted in every build } TestParameters::input(param).lr_grad_solver = "gmres"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("lr_grad_solver must be cg, lapack, scalapack or scalapack_chol")); for (const std::string solver : { "scalapack", "scalapack_chol" }) { TestParameters::input(param).lr_grad_solver = solver; #ifdef __MPI - it->second.check_value(it->second, param); + check_item(it->second, param); #else testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("needs an MPI build")); #endif @@ -2488,7 +2505,7 @@ TEST_F(InputTest, Item_test2) // Removed solver names are rejected independently of MPI/ELPA build options. TestParameters::input(param).lr_grad_solver = "elpa"; testing::internal::CaptureStdout(); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("elpa has been removed")); } @@ -2636,7 +2653,7 @@ TEST_F(InputTest, OutStru) TestParameters::input(param).out_stru = v; // Expect no exit / no crash; check_value is a void function that only // calls WARNING_QUIT on bad input. - it->second.check_value(it->second, param); + check_item(it->second, param); } } @@ -2687,7 +2704,7 @@ TEST_F(InputTest, OutStru) { it->second.str_values = {s}; it->second.read_value(it->second, param); - EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), ::testing::ExitedWithCode(1), ""); } } @@ -2716,17 +2733,17 @@ TEST_F(InputTest, LRExternalKernelSupportsSpectraOnly) input.lr_init_xc_kernel = {mode, "external.cube"}; input.calculation = "scf"; input.cal_force = false; - it->second.check_value(it->second, param); + check_item(it->second, param); input.cal_force = true; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input.cal_force = false; input.calculation = "relax"; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input.lr_init_xc_kernel = {"default"}; - it->second.check_value(it->second, param); + check_item(it->second, param); input.esolver_type = "ksdft"; input.lr_init_xc_kernel = {mode, "external.cube"}; - it->second.check_value(it->second, param); + check_item(it->second, param); } } } @@ -2743,16 +2760,16 @@ TEST_F(InputTest, LRFixedFieldRejectsForcesAndRelaxation) input.efield_flag = true; input.calculation = "scf"; input.cal_force = false; - it->second.check_value(it->second, param); + check_item(it->second, param); input.cal_force = true; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input.cal_force = false; input.calculation = "relax"; - EXPECT_EXIT(it->second.check_value(it->second, param), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(check_item(it->second, param), testing::ExitedWithCode(1), ""); input.efield_flag = false; - it->second.check_value(it->second, param); + check_item(it->second, param); input.esolver_type = "ksdft"; input.efield_flag = true; - it->second.check_value(it->second, param); + check_item(it->second, param); } } diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index 1c4364f325b..059e52c4627 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -232,8 +232,7 @@ TEST_F(InputTest, ValidateRelaxMethodScalar) EXPECT_EQ(std::remove(output.c_str()), 0); } const std::string invalid_method_message - = "relax_method requires exactly one scalar option. " - "The parameter relax_method must be 'cg', 'cg_trad', 'bfgs', 'bfgs_trad', 'sd', 'cg_bfgs' or 'lbfgs'!"; + = "Expected one String from {cg, cg_trad, bfgs, bfgs_trad, sd, cg_bfgs, lbfgs}."; for (const std::string invalid : {"", "none", "cg 1", "cg 2", "bfgs 1", "bfgs 2", "sd 1", "cg_trad 1", "cg 2 extra"}) { expect_invalid_input("relax_invalid_INPUT", "relax_method " + invalid + "\n", @@ -249,13 +248,13 @@ TEST_F(InputTest, ValidateNoncollinearSpin) expect_invalid_input("noncolin_missing_nspin_INPUT", "noncolin 1\n", - "nspin must be 4 when noncolin or lspinorb is enabled"); + "Requires nspin==4"); expect_invalid_input("noncolin_invalid_nspin_INPUT", "noncolin 1\nnspin 2\n", - "nspin must be 4 when noncolin or lspinorb is enabled"); + "Requires nspin==4"); expect_invalid_input("soc_missing_nspin_INPUT", "lspinorb 1\n", - "nspin must be 4 when noncolin or lspinorb is enabled"); + "Requires nspin==4"); } TEST_F(InputTest, ValidateSdftStochasticBands) @@ -362,10 +361,10 @@ TEST_F(InputTest, ValidateDeepksOutputFrequency) expect_invalid_input("deepks_freq_negative_INPUT", "deepks_out_freq_elec -1\n", - "deepks_out_freq_elec must not be negative"); + "Invalid INPUT parameter 'deepks_out_freq_elec': supplied '-1'. Expected one Integer satisfying value>=0."); expect_invalid_input("deepks_freq_missing_base_INPUT", "deepks_out_freq_elec 2\n", - "to use deepks_out_freq_elec, please set deepks_out_base"); + "Requires deepks_out_base!=none"); Parameter enabled_param; testing::internal::CaptureStdout(); diff --git a/source/source_io/test_serial/test_read_input.cpp b/source/source_io/test_serial/test_read_input.cpp index 2c647bc29a8..be1c52f1b28 100644 --- a/source/source_io/test_serial/test_read_input.cpp +++ b/source/source_io/test_serial/test_read_input.cpp @@ -120,6 +120,21 @@ TEST_F(InputDomainReaderTest, PreservesLegacyEmptyValues) EXPECT_TRUE(input.inp.out_pchg.empty()); } +TEST_F(InputDomainReaderTest, KeepsTrailingCommentsAndAbsolutePaths) +{ + write_input("scf_thr 1e-7 // about iteration\n" + "nspin 2 // magnetic calculation\n" + "pseudo_dir /tmp//pseudo\norbital_dir //server/orbitals\n"); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + EXPECT_DOUBLE_EQ(input.inp.scf_thr, 1e-7); + EXPECT_EQ(input.inp.nspin, 2); + EXPECT_EQ(input.inp.pseudo_dir, "/tmp//pseudo/"); + EXPECT_EQ(input.inp.orbital_dir, "//server/orbitals/"); +} + TEST_F(InputDomainReaderTest, ReadsWriterOutputWithEmptyOptionalValues) { write_input("nspin 2\ngga_grad 0\n"); @@ -141,20 +156,94 @@ TEST_F(InputDomainReaderTest, ReadsWriterOutputWithEmptyOptionalValues) EXPECT_EQ(roundtrip.inp.out_pchg, input.inp.out_pchg); } -TEST_F(InputDomainReaderTest, PreservesUnmigratedEndOfFileHandling) +TEST_F(InputDomainReaderTest, RejectsMissingStringChoiceAtEndOfFile) { - write_input("calculation"); + const std::string output = reject_input("calculation"); + EXPECT_NE(output.find("Invalid INPUT parameter 'calculation': supplied ''"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, RetainsExistingSpinDependencyCheck) +{ + const std::string output = reject_input("nspin 2\nnoncolin 1\n"); + EXPECT_NE(output.find("Requires nspin==4"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, RejectsPrivateSentinelsBeforeDefaultResolution) +{ + for (const std::string value : {"-1", "0", "3", "1abc"}) + { + const std::string output = reject_input("scf_thr_type " + value + "\n"); + EXPECT_NE(output.find("supplied '" + value + "'"), std::string::npos); + EXPECT_NE(output.find("from {1, 2}"), std::string::npos); + EXPECT_NE(output.find("input-main.html#scf-thr-type"), std::string::npos); + } + for (const std::string basis : {"pw", "lcao"}) + { + std::ofstream out(filename); + out << "INPUT_PARAMETERS\nbasis_type " << basis << "\nntype 1\n"; + out.close(); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + const int expected = basis == "pw" ? 1 : 2; + EXPECT_EQ(input.inp.scf_thr_type, expected); + } + write_input("scf_thr_type 2\nmixing_beta 0\nmixing_beta_mag 0.2\nmixing_gg0_mag 0.7\n"); ModuleIO::ReadInput reader(0); reader.check_ntype_flag = false; Parameter input; reader.read_parameters(input, filename); - EXPECT_EQ(input.inp.calculation, "scf"); + EXPECT_EQ(input.inp.scf_thr_type, 2); + EXPECT_EQ(input.inp.mixing_beta, 0); + EXPECT_EQ(input.inp.mixing_beta_mag, 0.2); + EXPECT_EQ(input.inp.mixing_gg0_mag, 0.7); } -TEST_F(InputDomainReaderTest, RetainsExistingSpinDependencyCheck) +TEST_F(InputDomainReaderTest, ChecksTypesRangesAndDependenciesThroughOnePath) { - const std::string output = reject_input("nspin 2\nnoncolin 1\n"); - EXPECT_NE(output.find("nspin must be 4 when noncolin or lspinorb is enabled"), std::string::npos); + const std::vector> invalid = { + {"scf_nmax 2oops", "scf-nmax"}, {"mixing_beta nan", "mixing-beta"}, + {"mixing_beta -0.5", "mixing-beta"}, {"cal_force maybe", "cal-force"}, + {"symmetry 2", "symmetry"}, {"fft_mode 4", "fft-mode"}, + {"md_thermostat unknown", "md-thermostat"}, {"lin_maxiter 0", "lin-maxiter"}, + {"out_wfc_lcao 1", "out-wfc-lcao"}, {"dip_cor_flag true", "dip-cor-flag"}}; + for (const auto& example : invalid) + { + const std::string output = reject_input(example.first + "\n"); + EXPECT_NE(output.find("Invalid INPUT parameter"), std::string::npos) << example.first; + EXPECT_NE(output.find("input-main.html#" + example.second), std::string::npos) << example.first; + } + // The legacy nupdown reset used to erase this conflicting explicit value. + const std::string output = reject_input("nupdown 1\nnspin 1\n"); + EXPECT_NE(output.find("nupdown=1"), std::string::npos); + EXPECT_NE(output.find("Requires nupdown==0"), std::string::npos); +} + +TEST_F(InputDomainReaderTest, PreservesDisabledModesNegativePhysicsAndInactiveSwitches) +{ + write_input("scf_ene_thr -1\nnbspline -1\nmixing_angle -10\n" + "md_pfirst -2\nmd_plast -1\nout_wfc_lcao 0\ncal_force false\n"); + ModuleIO::ReadInput reader(0); + reader.check_ntype_flag = false; + Parameter input; + reader.read_parameters(input, filename); + EXPECT_EQ(input.inp.scf_ene_thr, -1); + EXPECT_EQ(input.inp.nbspline, -1); + EXPECT_EQ(input.inp.mixing_angle, -10); + EXPECT_EQ(input.mdp.md_pfirst, -2); + EXPECT_EQ(input.mdp.md_plast, -1); + EXPECT_EQ(input.inp.out_wfc_lcao, 0); + EXPECT_FALSE(input.inp.cal_force); + const std::string written = filename + ".written"; + reader.write_parameters(input, written); + ModuleIO::ReadInput rereader(0); + rereader.check_ntype_flag = false; + Parameter restored; + rereader.read_parameters(restored, written); + EXPECT_EQ(restored.inp.nspin, 1); + EXPECT_EQ(restored.inp.mixing_beta_mag, input.inp.mixing_beta_mag); + EXPECT_EQ(restored.mdp.md_plast, -1); } TEST(InputDomainHelp, ExportsTheRuntimeRules) @@ -169,6 +258,12 @@ TEST(InputDomainHelp, ExportsTheRuntimeRules) const std::string dump = yaml.str(); EXPECT_NE(dump.find("allowed_values: [1, 2, 4]"), std::string::npos); EXPECT_NE(dump.find("allowed_values: [0, 1, 2]"), std::string::npos); + EXPECT_NE(dump.find("allowed_values: [\"-1\", \"0\", \"1\"]"), std::string::npos); + EXPECT_NE(dump.find("value_rule:"), std::string::npos); + EXPECT_NE(dump.find("requirements:"), std::string::npos); + const auto criterion = ModuleIO::ParameterHelp::get_metadata("scf_thr_type"); + EXPECT_EQ(criterion.allowed_values, "1, 2"); + EXPECT_NE(spin.requirements.find("nspin==4"), std::string::npos); } } // namespace From 5ed7a675931de038ef50fca945876c2fa6886f33 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Thu, 22 Oct 2026 15:43:24 +0800 Subject: [PATCH 5/8] fix(input): wrap validation metadata in parameter help --- source/source_io/input_help.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/source/source_io/input_help.cpp b/source/source_io/input_help.cpp index eacf4d36493..a50668b9cfb 100644 --- a/source/source_io/input_help.cpp +++ b/source/source_io/input_help.cpp @@ -403,11 +403,19 @@ bool ParameterHelp::show_parameter_help(const std::string& key, std::ostream& os os << "Type: " << meta.type << "\n"; if (!meta.allowed_values.empty()) { - os << "Allowed values: " << meta.allowed_values << "\n"; + const std::string allowed_values = "Allowed values: " + meta.allowed_values; + os << word_wrap(allowed_values, MAX_WIDTH, INDENT_SIZE, 0) << "\n"; } - if (!meta.value_rule.empty()) { os << "Legal range: " << meta.value_rule << "\n"; } - if (!meta.requirements.empty()) { os << "Requirements: " << meta.requirements << "\n"; } + if (!meta.value_rule.empty()) { + const std::string value_rule = "Legal range: " + meta.value_rule; + os << word_wrap(value_rule, MAX_WIDTH, INDENT_SIZE, 0) << "\n"; + } + + if (!meta.requirements.empty()) { + const std::string requirements = "Requirements: " + meta.requirements; + os << word_wrap(requirements, MAX_WIDTH, INDENT_SIZE, 0) << "\n"; + } if (!meta.default_value.empty()) { os << "Default: " << meta.default_value << "\n"; From bd61ef67080a0e35bb363007791a21407cfa5378 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Thu, 22 Oct 2026 15:50:39 +0800 Subject: [PATCH 6/8] fix(ci): separate appended GPU options from the last INPUT line --- .github/workflows/cuda.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 68f6e2347fd..090a2921b03 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -129,7 +129,7 @@ jobs: if grep -qE '^[[:space:]]*device[[:space:]]+' "$f"; then sed -i -E 's/^[[:space:]]*device[[:space:]]+.*/device gpu/' "$f" else - echo "device gpu" >> "$f" + printf '\ndevice gpu\n' >> "$f" fi done bash ../integrate/Autotest.sh -n 2 -j "$GPU_TEST_JOBS" -f CASES_GPU.txt @@ -141,7 +141,7 @@ jobs: if grep -qE '^[[:space:]]*device[[:space:]]+' "$f"; then sed -i -E 's/^[[:space:]]*device[[:space:]]+.*/device gpu/' "$f" else - echo "device gpu" >> "$f" + printf '\ndevice gpu\n' >> "$f" fi done bash ../integrate/Autotest.sh -n 2 -j "$GPU_TEST_JOBS" -f CASES_GPU.txt @@ -153,7 +153,7 @@ jobs: if grep -qE '^[[:space:]]*device[[:space:]]+' "$f"; then sed -i -E 's/^[[:space:]]*device[[:space:]]+.*/device gpu/' "$f" else - echo "device gpu" >> "$f" + printf '\ndevice gpu\n' >> "$f" fi done bash ../integrate/Autotest.sh -n 2 -j "$GPU_TEST_JOBS" -f CASES_GPU.txt @@ -170,7 +170,7 @@ jobs: if grep -q "^device" "$f"; then sed -i 's/^device.*/device gpu/' "$f" else - echo "device gpu" >> "$f" + printf '\ndevice gpu\n' >> "$f" fi done bash ../integrate/Autotest.sh -n 1 -j "$GPU_TEST_JOBS" -a abacus -f CASES_GPU.txt From 0896ac539665ee2b1df693f8539ae278e3b4c410 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Thu, 22 Oct 2026 21:22:51 +0800 Subject: [PATCH 7/8] refactor(input): remove duplicate precision checks InputDomain already checks the allowed precision values and the LCAO restriction. Keep the CPU single-precision FFTW build check in the legacy callback. --- source/source_io/module_parameter/read_inp_sys.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_sys.cpp b/source/source_io/module_parameter/read_inp_sys.cpp index 58fefafff87..79b933f7f3b 100644 --- a/source/source_io/module_parameter/read_inp_sys.cpp +++ b/source/source_io/module_parameter/read_inp_sys.cpp @@ -996,18 +996,6 @@ Available options are: item.set_availability("basis_type==pw"); read_sync_string(input.precision); item.check_value = [](const Input_Item& item, const Parameter& para) { - std::vector avail_list = {"single", "double"}; - if (std::find(avail_list.begin(), avail_list.end(), para.input.precision) == avail_list.end()) - { - const std::string warningstr = nofound_str(avail_list, "precision"); - ModuleBase::WARNING_QUIT("ReadInput", warningstr); - } - if (para.inp.precision == "single" && para.inp.basis_type == "lcao") - { - ModuleBase::WARNING_QUIT( - "ReadInput", - "Single precision is not supported for NAO basis,\nPlease use double precision for NAO basis.\n"); - } // cpu single precision is not supported while float_fftw lib is not available if (para.inp.device == "cpu" && para.inp.precision == "single") { From f87735bb84f44208f6b9075684f8edbab4e41517 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu <13862180016@163.com> Date: Thu, 22 Oct 2026 22:08:07 +0800 Subject: [PATCH 8/8] refactor(input): deduplicate solver and relaxation validation --- .../module_parameter/read_inp_estruc.cpp | 33 +------------------ .../module_parameter/read_inp_relax.cpp | 13 +------- .../test_serial/read_input_item_test.cpp | 8 ++++- 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index 195208bbf69..8867fb9e268 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -126,41 +126,10 @@ Then the user has to correct the input file and restart the calculation.)"; }; item.check_value = [](const Input_Item& item, const Parameter& para) { const std::string& ks_solver = para.input.ks_solver; - const std::vector pw_solvers = {"cg", "dav", "bpcg", "dav_subspace"}; - const std::vector lcao_solvers = { - "genelpa", - "elpa", - "lapack", - "scalapack_gvx", - "cusolver", - "cusolvermp", - "pexsi", - }; - - if (para.input.basis_type == "pw") - { - if (std::find(pw_solvers.begin(), pw_solvers.end(), ks_solver) == pw_solvers.end()) - { - const std::string warningstr = "For PW basis: " + nofound_str(pw_solvers, "ks_solver"); - ModuleBase::WARNING_QUIT("ReadInput", warningstr); - } - } - else if (para.input.basis_type == "lcao") + if (para.input.basis_type == "lcao") { - if (std::find(lcao_solvers.begin(), lcao_solvers.end(), ks_solver) == lcao_solvers.end()) - { - const std::string warningstr = "For LCAO basis: " + nofound_str(lcao_solvers, "ks_solver"); - ModuleBase::WARNING_QUIT("ReadInput", warningstr); - } if (ks_solver == "genelpa") { - if (para.input.device == "gpu") - { - ModuleBase::WARNING_QUIT( - "ReadInput", - "ks_solver = genelpa does not support GPU acceleration. " - "Please use ks_solver = elpa with device = gpu."); - } #ifndef __ELPA ModuleBase::WARNING_QUIT("Input", "Can not use genelpa if abacus is not compiled with " diff --git a/source/source_io/module_parameter/read_inp_relax.cpp b/source/source_io/module_parameter/read_inp_relax.cpp index 414f5221941..e68bf70aabb 100644 --- a/source/source_io/module_parameter/read_inp_relax.cpp +++ b/source/source_io/module_parameter/read_inp_relax.cpp @@ -28,18 +28,7 @@ void ReadInput::item_relax() * sd: Steepest descent algorithm. Not recommended for production use.)"; item.default_value = "cg"; item.unit = ""; - item.read_value = [](const Input_Item& item, Parameter& para) { - const std::vector methods - = {"cg", "cg_trad", "bfgs", "bfgs_trad", "sd", "cg_bfgs", "lbfgs"}; - if (item.str_values.size() != 1 || std::find(methods.begin(), methods.end(), strvalue) == methods.end()) - { - const std::string message = "relax_method requires exactly one scalar option. " - + nofound_str(methods, "relax_method"); - ModuleBase::WARNING_QUIT("ReadInput", message); - } - para.input.relax_method = strvalue; - }; - sync_string(input.relax_method); + read_sync_string(input.relax_method); this->add_item(item); } { diff --git a/source/source_io/test_serial/read_input_item_test.cpp b/source/source_io/test_serial/read_input_item_test.cpp index ab1f0705661..7fd18f1d1f9 100644 --- a/source/source_io/test_serial/read_input_item_test.cpp +++ b/source/source_io/test_serial/read_input_item_test.cpp @@ -103,6 +103,8 @@ TEST_F(InputTest, RelaxMethod) EXPECT_EQ(it->second.final_value.str(), method); } + readinput.readvalue_items.push_back(&it->second); + const std::string previous_method = param.inp.relax_method; for (const std::vector& invalid : { std::vector{}, std::vector{"none"}, @@ -115,7 +117,11 @@ TEST_F(InputTest, RelaxMethod) std::vector{"cg", "2", "extra"}}) { it->second.str_values = invalid; - EXPECT_EXIT(it->second.read_value(it->second, param), ::testing::ExitedWithCode(1), ""); + const std::string error = readinput.prepare_input(param); + EXPECT_THAT(error, testing::HasSubstr("Invalid INPUT parameter 'relax_method'")); + EXPECT_THAT(error, testing::HasSubstr("Expected one String from {cg, cg_trad, bfgs, bfgs_trad, sd, cg_bfgs, lbfgs}")); + EXPECT_THAT(error, testing::HasSubstr("input-main.html#relax-method")); + EXPECT_EQ(param.inp.relax_method, previous_method); } EXPECT_EQ(find_label("relax_new", readinput.input_lists), readinput.input_lists.end()); }