diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 68f6e2347f..090a2921b0 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 diff --git a/docs/developers_guide/index.rst b/docs/developers_guide/index.rst index 8022424042..35b27bc838 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 0000000000..e1bb64736e --- /dev/null +++ b/docs/developers_guide/input_validation.md @@ -0,0 +1,72 @@ +# Shared INPUT validation + +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.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"); +``` + +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. + +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. + +## Preparation order + +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 + +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 +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 89a7452104..deff16ef74 100755 --- a/docs/generate_input_main.py +++ b/docs/generate_input_main.py @@ -174,6 +174,15 @@ 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}") + + 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/docs/test_generate_input_main.py b/docs/test_generate_input_main.py new file mode 100644 index 0000000000..0b8ab0ac3e --- /dev/null +++ b/docs/test_generate_input_main.py @@ -0,0 +1,31 @@ +"""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_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) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/Makefile.Objects b/source/Makefile.Objects index f393a5eb31..4c287d77ba 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -754,6 +754,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 101c4af261..32a97fbdb3 100644 --- a/source/source_io/CMakeLists.txt +++ b/source/source_io/CMakeLists.txt @@ -139,6 +139,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 9681f71784..a50668b9cf 100644 --- a/source/source_io/input_help.cpp +++ b/source/source_io/input_help.cpp @@ -371,6 +371,9 @@ 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(); + 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); @@ -399,6 +402,21 @@ 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()) { + 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()) { + 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"; } @@ -495,6 +513,28 @@ 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.choices().empty()) { + 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()) { + 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 375dbc513f..a0b41ce422 100644 --- a/source/source_io/input_help.h +++ b/source/source_io/input_help.h @@ -20,6 +20,9 @@ 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 new file mode 100644 index 0000000000..1b8e13aac3 --- /dev/null +++ b/source/source_io/module_parameter/input_domain.cpp @@ -0,0 +1,340 @@ +#include "input_domain.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ModuleIO +{ +namespace +{ +using Kind = AvailabilityValueKind; + +std::string join(const std::vector& values, const std::string& separator) +{ + std::ostringstream out; + for (size_t i = 0; i < values.size(); ++i) + { + if (i != 0) { out << separator; } + out << values[i]; + } + 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) + { + 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 compare(const InputValue& actual, const std::string& op, const std::string& literal) +{ + 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; +} + +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()) + { + references.insert(expression.condition.param); + } + for (const auto& child : expression.children) { collect_references(child, references); } +} + +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; + out << std::setprecision(std::numeric_limits::max_digits10); + for (size_t i = 0; i < numbers.size(); ++i) + { + if (i != 0) { out << ' '; } + if (kind == Kind::Boolean) { out << (numbers[i] != 0 ? "true" : "false"); } + else { out << numbers[i]; } + } + return out.str(); +} + +bool evaluate_input_condition(const AvailabilityExpr& expression, const InputValues& values) +{ + 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 input_manual_link(const std::string& label) +{ + std::string anchor = label; + std::replace(anchor.begin(), anchor.end(), '_', '-'); + return "https://abacus.deepmodeling.com/en/latest/advanced/input_files/input-main.html#" + anchor; +} + +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) +{ + 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()) + { + throw std::invalid_argument("INPUT choices must be nonempty and unique."); + } + 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_); } +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_, ", "); } +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_) + { + 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); + } + return join(rendered, "; "); +} + +void InputDomain::validate_schema(const std::string& label, const std::map& types) const +{ + if (scalar_) + { + if (!bound() || (kind_ != Kind::Integer && kind_ != Kind::Real && kind_ != Kind::String && kind_ != Kind::Boolean)) + { + throw std::invalid_argument("Missing scalar INPUT binding for " + label); + } + for (const auto& choice : choices_) { parse_scalar(kind_, choice); } + const std::map domain_types = {{"value", kind_}}; + validate_runtime_condition(label, rule_, domain_types); + } + validate_runtime_condition(label, active_when_, types); + for (const auto& rule : requirements_) + { + validate_runtime_condition(label, rule.when, types); + validate_runtime_condition(label, rule.require, types); + } +} + +bool InputDomain::accepts(const InputValue& value) const +{ + if (kind_ == Kind::Real && !std::isfinite(value.numbers.front())) { return false; } + if (!choices_.empty()) + { + bool found = false; + for (const auto& choice : choices_) { found = found || compare(value, "==", choice); } + if (!found) { return false; } + } + 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) + { + 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 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 new file mode 100644 index 0000000000..dbf0bf7c42 --- /dev/null +++ b/source/source_io/module_parameter/input_domain.h @@ -0,0 +1,83 @@ +#ifndef MODULE_IO_INPUT_DOMAIN_H +#define MODULE_IO_INPUT_DOMAIN_H + +#include "availability_validator.h" +#include +#include +#include +#include + +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; +}; + +class InputDomain +{ + public: + InputDomain() = default; + 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; + bool bound() const; + bool can_write(const Parameter& input) const; + AvailabilityValueKind kind() const; + InputValue value(const Parameter& input) const; + std::string format_choices() const; + 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 InputValues& values) const; + std::string validate_requirements(const std::string& label, const InputValues& values) const; + + private: + 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/input_item.h b/source/source_io/module_parameter/input_item.h index 0e78f4e32a..3b16860498 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_deepks.cpp b/source/source_io/module_parameter/read_inp_deepks.cpp index 335523ad26..8d3c4443b1 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 3c9052bbbb..cfea717beb 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 a5054cb2b0..8867fb9e26 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"; @@ -129,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 " @@ -233,6 +199,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 +207,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 +245,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 +264,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 +423,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"; @@ -477,17 +435,8 @@ 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.set_choices({"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."); - } - }; this->add_item(item); } { @@ -502,13 +451,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.set_choices({"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); } { @@ -585,6 +529,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"; @@ -622,6 +567,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"; @@ -847,6 +794,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"; @@ -891,6 +839,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"; @@ -932,6 +881,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"; @@ -939,12 +889,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); } { @@ -1037,6 +981,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"; @@ -1079,6 +1024,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"; @@ -1087,23 +1033,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); } { @@ -1144,6 +1073,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"; @@ -1151,12 +1081,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); } { @@ -1234,6 +1158,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"; @@ -1241,12 +1166,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") @@ -1260,6 +1179,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"; @@ -1267,16 +1187,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"; @@ -1284,12 +1199,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 cdfd4d2ebe..d5d4e71c23 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 68f17c0bdf..e1f8f83418 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 80a5531946..880234ed6d 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 0d1d22d50c..e68bf70aab 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"; @@ -27,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); } { @@ -97,6 +87,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 +118,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 +246,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 +271,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 +287,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 +301,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 04c4741837..79b933f7f3 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"; @@ -1020,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") { @@ -1047,6 +1011,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 +1023,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 +1119,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 +1148,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 +1365,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 +1391,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 b447557abb..484aedf833 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 2de46279d8..85f4b150df 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" @@ -85,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; } @@ -187,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); @@ -209,10 +216,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 +341,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 +391,25 @@ 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()) + // 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."; 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,12 +445,35 @@ 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) + { + 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) { 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) @@ -441,6 +487,35 @@ void ReadInput::read_txt_input(Parameter& param, const std::string& filename) 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, values); + } + if (!errors.empty()) { return errors; } + for (const auto& entry : this->input_lists) + { + errors += entry.second.domain.validate_requirements(entry.first, values); + } + return errors; } void ReadInput::normalize_occ_mat_options(Parameter& param) @@ -536,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.h b/source/source_io/module_parameter/read_input.h index 941dde0673..d41b725217 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/read_input_item_md.cpp b/source/source_io/module_parameter/read_input_item_md.cpp index d5f6dc6bd6..a41343fd26 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 6924fa8b30..2bb794c91f 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/read_input_tool.h b/source/source_io/module_parameter/read_input_tool.h index 9fe5109817..002178b7e8 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 6469e5b15c..5b0b6a5d9c 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 ../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 new file mode 100644 index 0000000000..bc7adbb2ad --- /dev/null +++ b/source/source_io/module_parameter/test/test_input_domain.cpp @@ -0,0 +1,144 @@ +#include "../input_domain.h" +#include "../input_parameter.h" + +#include +#include + +TEST(InputDomain, UndeclaredDomainKeepsLegacyValidation) +{ + const ModuleIO::InputDomain domain; + const ModuleIO::InputValues 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) +{ + 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) +{ + 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; + } + 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) +{ + 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", {{"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", {{"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.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()); +} + +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/read_input_ptest.cpp b/source/source_io/test/read_input_ptest.cpp index 90b92f50eb..2f57e82b2d 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, {{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_EXIT(item.check_value(item, param), testing::ExitedWithCode(1), ""); + 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/CMakeLists.txt b/source/source_io/test_serial/CMakeLists.txt index 0201fb14e0..637e3af4ab 100644 --- a/source/source_io/test_serial/CMakeLists.txt +++ b/source/source_io/test_serial/CMakeLists.txt @@ -26,6 +26,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}) @@ -44,6 +45,12 @@ AddTest( SOURCES read_input_item_test.cpp test_read_inp_sccs.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 57cdc38e87..7fd18f1d1f 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) @@ -49,6 +63,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); @@ -70,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"}, @@ -82,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()); } @@ -101,37 +140,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) @@ -157,18 +196,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); } @@ -212,26 +254,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")); } @@ -241,13 +283,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; } @@ -255,13 +297,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")); @@ -269,7 +311,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")); } @@ -277,16 +319,14 @@ 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, {{it->first, it->second.domain.value(param)}}); + EXPECT_THAT(output, testing::HasSubstr("Expected one Integer from {1, 2, 4}")); } { // smearing_method 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")); } @@ -305,13 +345,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")); } @@ -319,7 +359,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")); } @@ -358,14 +398,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")); } @@ -373,14 +413,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")); } @@ -389,17 +429,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")); @@ -407,7 +447,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")); } @@ -453,7 +493,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")); } @@ -469,7 +509,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")); } @@ -518,14 +558,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); @@ -594,14 +634,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"; @@ -620,7 +660,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")); } @@ -683,14 +723,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")); } @@ -714,7 +754,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")); } @@ -740,13 +780,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")); } @@ -776,7 +816,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")); } @@ -813,7 +853,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")); } @@ -833,7 +873,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")); } @@ -842,7 +882,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")); } @@ -851,7 +891,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")); } @@ -867,14 +907,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")); } @@ -890,14 +930,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")); } @@ -913,14 +953,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")); } @@ -948,9 +988,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"; @@ -977,28 +1017,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")); } @@ -1044,14 +1084,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")); } @@ -1060,14 +1100,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")); } @@ -1076,7 +1116,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")); } @@ -1135,7 +1175,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")); } @@ -1145,31 +1185,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); @@ -1178,7 +1218,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")); @@ -1201,7 +1241,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")); } @@ -1231,15 +1271,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; @@ -1277,11 +1317,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")); } @@ -1296,23 +1336,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 @@ -1489,7 +1529,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")); } @@ -1498,7 +1538,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")); @@ -1506,7 +1546,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")); @@ -1514,7 +1554,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 @@ -1528,14 +1568,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")); } @@ -1543,7 +1583,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")); @@ -1558,7 +1598,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")); } @@ -1566,7 +1606,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")); } @@ -1612,7 +1652,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")); } @@ -1622,7 +1662,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")); } @@ -1686,7 +1726,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")); } @@ -1694,7 +1734,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")); } @@ -1702,7 +1742,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")); } @@ -1731,7 +1771,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")); } @@ -1739,7 +1779,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")); } @@ -1747,7 +1787,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")); } @@ -1755,7 +1795,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")); } @@ -1763,7 +1803,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")); } @@ -1771,7 +1811,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")); } @@ -1791,7 +1831,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")); } @@ -1880,7 +1920,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")); } @@ -1964,7 +2004,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")); } @@ -1972,7 +2012,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")); } @@ -1980,7 +2020,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")); } @@ -1995,7 +2035,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")); } @@ -2011,14 +2051,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")); } @@ -2031,20 +2071,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 @@ -2054,14 +2094,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")); } @@ -2102,7 +2142,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")); @@ -2111,7 +2151,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")); @@ -2120,7 +2160,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")); } @@ -2150,17 +2190,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")); } @@ -2169,17 +2209,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")); } @@ -2192,7 +2232,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")); } @@ -2200,7 +2240,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")); } @@ -2213,7 +2253,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")); } @@ -2221,7 +2261,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")); } @@ -2229,13 +2269,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")); } @@ -2243,7 +2283,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")); } @@ -2251,7 +2291,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")); } @@ -2259,7 +2299,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")); } @@ -2267,7 +2307,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")); } @@ -2275,14 +2315,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); @@ -2344,13 +2384,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")); } @@ -2398,7 +2438,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")); } @@ -2409,14 +2449,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")); } @@ -2427,7 +2467,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")); } @@ -2438,7 +2478,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")); } @@ -2449,21 +2489,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 @@ -2471,7 +2511,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")); } @@ -2619,7 +2659,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); } } @@ -2670,7 +2710,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), ""); } } @@ -2699,17 +2739,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); } } } @@ -2726,16 +2766,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 e4390c60c2..059e52c462 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(); @@ -414,10 +413,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 new file mode 100644 index 0000000000..be1c52f1b2 --- /dev/null +++ b/source/source_io/test_serial/test_read_input.cpp @@ -0,0 +1,269 @@ +#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()); + const std::string written = filename + ".written"; + std::remove(written.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); +} + +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, 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"); + 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, RejectsMissingStringChoiceAtEndOfFile) +{ + 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.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, ChecksTypesRangesAndDependenciesThroughOnePath) +{ + 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) +{ + 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); + 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