diff --git a/docs/quick_start/input.md b/docs/quick_start/input.md index 1f0ac33c640..5de3051d511 100644 --- a/docs/quick_start/input.md +++ b/docs/quick_start/input.md @@ -6,6 +6,23 @@ The following files are the central input files for ABACUS. Before executing the The `INPUT` file contains parameters that control the type of calculation as well as a variety of settings. +By default, ABACUS reads `INPUT` in the current working directory. Use +`abacus -in cases/INPUT.alt` (or `--input`) to select a different file, including +an absolute path. This selects only the INPUT file: relative `stru_file`, +`kpoint_file`, `pseudo_dir`, `orbital_dir`, restart paths, and output paths still +resolve from the working directory. For example, when run from a directory +containing `STRU` and `KPT`, `abacus -in cases/INPUT.alt` uses those working-directory +files unless the selected INPUT specifies other paths. The selected INPUT path is +recorded in the running log. Its parameter summary uses only the filename inside +the output directory, for example `OUT./INPUT.alt.info`. + +The repeatable `-p/--parameter ` option currently parses and stores +variables but **does not apply them to INPUT** (PART I). A run using this option +prints a warning. Duplicate names keep the last value; names must be nonempty +and must not start with a digit. Negative values are accepted. Variable +substitution is reserved for PART II; set calculation parameters in the selected +INPUT file in the meantime. + Below is an example `INPUT` file with some of the most important parameters that need to be set: ```text @@ -46,7 +63,7 @@ In the above example, the meanings of the parameters are: For a complete list of input parameters, please consult this [instruction](../advanced/input_files/input-main.md). -> **Note:** Users cannot change the filename "INPUT" to other names. Boolean paramerters such as `out_chg` can be set by using `True` and `False`, `1` and `0`, or `T` and `F`. It is case insensitive so that other preferences such as `true` and `false`, `TRUE` and `FALSE`, and `t` and `f` for setting boolean values are also supported. Specifically for the `out_chg`, `-1` option is also available, which means turn off the checkpoint of charge density in binary (always dumped in `OUT.{suffix}`, whose name ends with `CHARGE-DENSITY.restart`). Some parameters controlling the output also support a second option to control the output precision, e.g., `out_chg 1 8` will output the charge density on realspace grid with 8 digits after the decimal point. +> **Note:** Boolean parameters such as `out_chg` can be set by using `True` and `False`, `1` and `0`, or `T` and `F`. It is case insensitive so that other preferences such as `true` and `false`, `TRUE` and `FALSE`, and `t` and `f` for setting boolean values are also supported. Specifically for the `out_chg`, `-1` option is also available, which means turn off the checkpoint of charge density in binary (always dumped in `OUT.{suffix}`, whose name ends with `CHARGE-DENSITY.restart`). Some parameters controlling the output also support a second option to control the output precision, e.g., `out_chg 1 8` will output the charge density on realspace grid with 8 digits after the decimal point. ## Getting Help with Parameters diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 243b1ee57f4..31b60644e58 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -786,6 +786,7 @@ OBJS_IO=module_parameter/input_conv.o\ init_info.o\ output_info.o\ parse_args.o\ + parse_command_line.o\ module_parameter/availability.o\ module_parameter/availability_validator.o\ module_parameter/input_domain.o\ diff --git a/source/source_io/CMakeLists.txt b/source/source_io/CMakeLists.txt index e16e5b31e1c..32b24c3398b 100644 --- a/source/source_io/CMakeLists.txt +++ b/source/source_io/CMakeLists.txt @@ -45,6 +45,7 @@ list(APPEND objects module_json/para_json.cpp parse_args.cpp input_help.cpp + parse_command_line.cpp module_chgpot/get_pchg_pw.cpp module_wf/get_wf_pw.cpp ) diff --git a/source/source_io/input_help.cpp b/source/source_io/input_help.cpp index a50668b9cfb..d6466b7532e 100644 --- a/source/source_io/input_help.cpp +++ b/source/source_io/input_help.cpp @@ -470,6 +470,10 @@ void ParameterHelp::show_general_help(std::ostream& os) { os << " -i, -I, --info Display detailed build information\n"; os << " -h, --help [param] Display help for parameter (or this message)\n"; os << " -s, --search Search for parameters matching query\n"; + os << " -p, --parameter \n"; + os << " Parse a variable; warn that it is not applied in PART I.\n"; + os << " -in, --input Path to INPUT (default: INPUT)\n"; + os << " Relative resource/output paths remain based on the working directory.\n"; os << " --check-input Check input file syntax and exit\n"; os << " --generate-parameters-yaml\n"; os << " Dump all parameter metadata as YAML\n"; diff --git a/source/source_io/parse_args.cpp b/source/source_io/parse_args.cpp index 046bc527028..54b77c1bdb8 100644 --- a/source/source_io/parse_args.cpp +++ b/source/source_io/parse_args.cpp @@ -1,6 +1,7 @@ #include "parse_args.h" #include "build_info.h" #include "input_help.h" +#include "parse_command_line.h" #include #include @@ -274,6 +275,13 @@ void parse_args(int argc, char** argv) } else { + const int option_size = run_control_option_size(argc, argv, i); + if (option_size != 0) + { + i += option_size - 1; + continue; + } + // Error message goes to stderr std::cerr << "Error: Unknown argument: " << arg << std::endl; std::cerr << std::endl; diff --git a/source/source_io/parse_command_line.cpp b/source/source_io/parse_command_line.cpp new file mode 100644 index 00000000000..b5008c134e9 --- /dev/null +++ b/source/source_io/parse_command_line.cpp @@ -0,0 +1,96 @@ +#include "source_io/parse_command_line.h" + +#include +#include +#include + +namespace ModuleIO +{ + +int run_control_option_size(int argc, char* argv[], int index) +{ + const std::string option = argv[index]; + int size = 0; + std::string required; + if (option == "-p" || option == "--parameter") + { + size = 3; + required = " "; + } + else if (option == "-in" || option == "--input") + { + size = 2; + required = ""; + } + else if (option == "--check-input") + { + size = 1; + } + if (size > argc - index) + { + throw std::runtime_error("Option " + option + " requires " + required); + } + return size; +} + +CommandLineArgs parse_command_line(int argc, char* argv[]) +{ + CommandLineArgs args; + for (int i = 1; i < argc; ++i) + { + const std::string arg = argv[i]; + const int option_size = run_control_option_size(argc, argv, i); + + if (option_size == 3) + { + ++i; + const std::string name = argv[i]; + if (name.empty()) + { + const std::string msg = "Invalid variable name: (empty)"; + throw std::runtime_error(msg); + } + const unsigned char first_char + = static_cast(name[0]); + const bool starts_with_digit = (std::isdigit(first_char) != 0); + if (starts_with_digit) + { + const std::string msg = "Invalid variable name: " + name; + throw std::runtime_error(msg); + } + ++i; + const std::string value = argv[i]; + args.vars[name] = value; // later duplicates override earlier ones + } + else if (option_size == 2) + { + ++i; + args.input_file = argv[i]; + if (args.input_file.empty()) + { + throw std::runtime_error("Option " + arg + " requires a nonempty "); + } + } + else if (option_size == 1) + { + // parse_args has already enabled ReadInput's check mode. + continue; + } + else + { + // Unreachable in normal flow: parse_args rejects unknown + // flags before this function runs. Kept as a defensive check + // so parse_command_line stays self-contained for tests. + const std::string msg = "Unknown option: " + arg; + throw std::runtime_error(msg); + } + } + return args; +} + +void print_help(const std::string& bin) +{ + std::fprintf(stderr, "Run '%s -h' for usage.\n", bin.c_str()); +} + +} // namespace ModuleIO diff --git a/source/source_io/parse_command_line.h b/source/source_io/parse_command_line.h new file mode 100644 index 00000000000..6618cbe2cf2 --- /dev/null +++ b/source/source_io/parse_command_line.h @@ -0,0 +1,42 @@ +#ifndef ModuleIO_PARSE_COMMAND_LINE_H +#define ModuleIO_PARSE_COMMAND_LINE_H + +#include +#include + +namespace ModuleIO +{ + +/// Run-control options parsed after ModuleIO::parse_args. +/// +/// Flag ownership: +/// - Informational flags (-v/-i/-h/-s/--generate-parameters-yaml) +/// are owned by ModuleIO::parse_args and exit before this function. +/// - --check-input sets check mode in parse_args and is skipped here. +/// - -p/--parameter and -in/--input share token-count validation with +/// parse_args, then their values are parsed here, before MPI setup. +struct CommandLineArgs +{ + // -p / --parameter : stored variables (repeatable). + // PART I does not apply these values to INPUT; main emits a warning. + std::map vars; + + // -in / --input : explicit INPUT path, default "INPUT" + std::string input_file = "INPUT"; +}; + +/// Parses -p/--parameter and -in/--input options. +/// Throws std::runtime_error on malformed arguments (parse_args has +/// already filtered unknown flags before this function is called). +CommandLineArgs parse_command_line(int argc, char* argv[]); + +/// Number of tokens occupied by one run-control option, including its values. +/// Returns zero for other options; throws std::runtime_error for missing values. +int run_control_option_size(int argc, char* argv[], int index); + +/// Prints a short pointer to the canonical general help on standard error. +void print_help(const std::string& bin_name); + +} // namespace ModuleIO + +#endif // ModuleIO_PARSE_COMMAND_LINE_H diff --git a/source/source_io/unittests/CMakeLists.txt b/source/source_io/unittests/CMakeLists.txt index df0ca86d38b..693c44d8ed0 100644 --- a/source/source_io/unittests/CMakeLists.txt +++ b/source/source_io/unittests/CMakeLists.txt @@ -11,6 +11,11 @@ AddTest( ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) +AddTest( + TARGET MODULE_IO_parse_command_line_test + SOURCES test_parse_command_line.cpp ../parse_command_line.cpp +) + AddTest( TARGET MODULE_IO_spin_tag SOURCES test_spin_tag.cpp @@ -131,7 +136,7 @@ AddTest( AddTest( TARGET MODULE_IO_parse_args LIBS parameter base device io_input - SOURCES test_parse_args.cpp ../parse_args.cpp ../input_help.cpp + SOURCES test_parse_args.cpp ../parse_args.cpp ../parse_command_line.cpp ../input_help.cpp ) AddTest( diff --git a/source/source_io/unittests/test_parse_args.cpp b/source/source_io/unittests/test_parse_args.cpp index 09b69c74f9f..46c562ce82c 100644 --- a/source/source_io/unittests/test_parse_args.cpp +++ b/source/source_io/unittests/test_parse_args.cpp @@ -1,8 +1,12 @@ #include "source_io/parse_args.h" +#include "source_io/parse_command_line.h" #include "gtest/gtest.h" #include "source_io/module_parameter/read_input.h" #include "source_main/version.h" +#include +#include + // Already deal with Testing.cmake // #include "build_info.h" @@ -282,3 +286,49 @@ TEST_F(ParseArgsTest, HelpFollowedByFlag) { output.find("Usage:") != std::string::npos); } +TEST_F(ParseArgsTest, CombinedRunControlAndCheckInput) +{ + const std::vector> cases = { + {"abacus", "--check-input", "-in", "sub/INPUT", "-p", "a", "-1"}, + {"abacus", "--input", "sub/INPUT", "--parameter", "a", "-1", "--check-input"}}; + for (const auto& tokens : cases) + { + ModuleIO::ReadInput::check_mode = false; + std::vector argv; + for (const auto& token : tokens) + { + argv.push_back(const_cast(token.c_str())); + } + ModuleIO::parse_args(argv.size(), argv.data()); + const auto args = ModuleIO::parse_command_line(argv.size(), argv.data()); + EXPECT_TRUE(ModuleIO::ReadInput::check_mode); + EXPECT_EQ(args.input_file, "sub/INPUT"); + EXPECT_EQ(args.vars.at("a"), "-1"); + } +} + +TEST_F(ParseArgsTest, FlagLookingVariableValueIsConsumed) +{ + char* argv[] = {const_cast("abacus"), const_cast("-p"), + const_cast("a"), const_cast("--check-input")}; + ModuleIO::parse_args(4, argv); + const auto args = ModuleIO::parse_command_line(4, argv); + EXPECT_FALSE(ModuleIO::ReadInput::check_mode); + EXPECT_EQ(args.vars.at("a"), "--check-input"); +} + +TEST_F(ParseArgsTest, SharedRunControlArityValidation) +{ + char* argv[] = {const_cast("abacus"), const_cast("-p"), const_cast("a")}; + EXPECT_THROW(ModuleIO::parse_args(3, argv), std::runtime_error); + EXPECT_THROW(ModuleIO::parse_command_line(3, argv), std::runtime_error); +} + +TEST_F(ParseArgsTest, HelpStillPrecedesRunControlNameValidation) +{ + char* argv[] = {const_cast("abacus"), const_cast("-p"), + const_cast("1invalid"), const_cast("value"), + const_cast("--help")}; + EXPECT_EXIT(ModuleIO::parse_args(5, argv), ::testing::ExitedWithCode(0), ""); +} + diff --git a/source/source_io/unittests/test_parse_command_line.cpp b/source/source_io/unittests/test_parse_command_line.cpp new file mode 100644 index 00000000000..6ebe629dfe3 --- /dev/null +++ b/source/source_io/unittests/test_parse_command_line.cpp @@ -0,0 +1,161 @@ +#include "source_io/parse_command_line.h" + +#include +#include +#include + +namespace +{ +/// Builds an argv array from strings and returns the token count +/// (excluding the terminating nullptr) via the out-parameter. +/// Lifetime note: `storage` and `strings` must outlive the parse call. +int make_argv(std::vector& strings, + std::vector& storage) +{ + strings.insert(strings.begin(), "abacus"); + storage.clear(); + storage.reserve(strings.size()); + for (size_t i = 0; i < strings.size(); ++i) + { + storage.push_back(&strings[i][0]); + } + storage.push_back(nullptr); + return static_cast(strings.size()); +} +} // namespace + +TEST(ParseCommandLineTest, ShortFormSingleVar) +{ + char* argv[] = {(char*)"abacus", + (char*)"-p", (char*)"a", (char*)"1", nullptr}; + const ModuleIO::CommandLineArgs args + = ModuleIO::parse_command_line(4, argv); + ASSERT_EQ(args.vars.size(), 1u); + EXPECT_EQ(args.vars.at("a"), "1"); +} + +TEST(ParseCommandLineTest, LongFormSingleVar) +{ + char* argv[] = {(char*)"abacus", + (char*)"--parameter", (char*)"ecutwfc", (char*)"120", + nullptr}; + const ModuleIO::CommandLineArgs args + = ModuleIO::parse_command_line(4, argv); + EXPECT_EQ(args.vars.at("ecutwfc"), "120"); +} + +TEST(ParseCommandLineTest, MultipleVarsLastWins) +{ + std::vector tokens + = {"-p", "a", "1", "-p", "a", "2", "-p", "b", "x"}; + std::vector storage; + const int argc = make_argv(tokens, storage); + const ModuleIO::CommandLineArgs args + = ModuleIO::parse_command_line(argc, storage.data()); + EXPECT_EQ(args.vars.at("a"), "2"); + EXPECT_EQ(args.vars.at("b"), "x"); +} + +TEST(ParseCommandLineTest, VarPlusCustomInput) +{ + char* argv[] = {(char*)"abacus", + (char*)"-p", (char*)"suffix", (char*)"TestRun", + (char*)"-in", (char*)"myINPUT", + nullptr}; + const ModuleIO::CommandLineArgs args + = ModuleIO::parse_command_line(6, argv); + EXPECT_EQ(args.vars.at("suffix"), "TestRun"); + EXPECT_EQ(args.input_file, "myINPUT"); +} + +TEST(ParseCommandLineTest, DefaultInputPath) +{ + char* argv[] = {(char*)"abacus", nullptr}; + const ModuleIO::CommandLineArgs args + = ModuleIO::parse_command_line(1, argv); + EXPECT_EQ(args.input_file, "INPUT"); + EXPECT_TRUE(args.vars.empty()); +} + +TEST(ParseCommandLineTest, MissingValueThrows) +{ + char* argv[] = {(char*)"abacus", (char*)"-p", (char*)"a", nullptr}; + EXPECT_THROW(ModuleIO::parse_command_line(3, argv), std::runtime_error); +} + +TEST(ParseCommandLineTest, EmptyNameThrows) +{ + char* argv[] = {(char*)"abacus", + (char*)"-p", (char*)"", (char*)"x", nullptr}; + EXPECT_THROW(ModuleIO::parse_command_line(4, argv), std::runtime_error); +} + +TEST(ParseCommandLineTest, DigitLeadingNameThrows) +{ + char* argv[] = {(char*)"abacus", + (char*)"-p", (char*)"1abc", (char*)"x", nullptr}; + EXPECT_THROW(ModuleIO::parse_command_line(4, argv), std::runtime_error); +} + +TEST(ParseCommandLineTest, UnknownOptionThrows) +{ + char* argv[] = {(char*)"abacus", (char*)"--bogus", nullptr}; + EXPECT_THROW(ModuleIO::parse_command_line(2, argv), std::runtime_error); +} + +// The exact-argc contract: with argc == 9 the last pair is truncated +// and must throw (documents that argc counts tokens, not nullptr). +TEST(ParseCommandLineTest, TruncatedArgcThrows) +{ + char* argv[] = {(char*)"abacus", + (char*)"-p", (char*)"a", (char*)"1", + (char*)"-p", (char*)"a", (char*)"2", + (char*)"-p", (char*)"b", (char*)"x", + nullptr}; + EXPECT_THROW(ModuleIO::parse_command_line(9, argv), std::runtime_error); +} + +TEST(ParseCommandLineTest, CheckInputWithCustomPathInEitherOrder) +{ + const std::vector> cases = { + {"--check-input"}, + {"--check-input", "-in", "sub/INPUT"}, + {"--input", "sub/INPUT", "--check-input"}}; + for (auto tokens : cases) + { + std::vector storage; + const int argc = make_argv(tokens, storage); + const auto args = ModuleIO::parse_command_line(argc, storage.data()); + EXPECT_EQ(args.input_file, argc == 2 ? "INPUT" : "sub/INPUT"); + EXPECT_TRUE(args.vars.empty()); + } +} + +TEST(ParseCommandLineTest, RepeatedInputLastWins) +{ + std::vector tokens = {"-in", "first", "--input", "../cases/INPUT.alt"}; + std::vector storage; + const int argc = make_argv(tokens, storage); + EXPECT_EQ(ModuleIO::parse_command_line(argc, storage.data()).input_file, "../cases/INPUT.alt"); +} + +TEST(ParseCommandLineTest, ValuesMayStartWithDash) +{ + std::vector tokens = {"-p", "a", "-1", "--parameter", "b", "--check-input"}; + std::vector storage; + const int argc = make_argv(tokens, storage); + const auto args = ModuleIO::parse_command_line(argc, storage.data()); + EXPECT_EQ(args.vars.at("a"), "-1"); + EXPECT_EQ(args.vars.at("b"), "--check-input"); +} + +TEST(ParseCommandLineTest, MissingOrEmptyInputThrows) +{ + const std::vector> cases = {{"-in"}, {"--input"}, {"-in", ""}}; + for (auto tokens : cases) + { + std::vector storage; + const int argc = make_argv(tokens, storage); + EXPECT_THROW(ModuleIO::parse_command_line(argc, storage.data()), std::runtime_error); + } +} diff --git a/source/source_main/driver.cpp b/source/source_main/driver.cpp index bcd8f428e6d..37bd67189b4 100644 --- a/source/source_main/driver.cpp +++ b/source/source_main/driver.cpp @@ -1,5 +1,4 @@ #include "source_main/driver.h" - #include "source_base/global_file.h" #include "source_base/memory_recorder.h" #include "source_base/parallel_global.h" @@ -11,6 +10,7 @@ #include "source_io/module_parameter/input_conv.h" #include "source_io/module_parameter/parameter.h" #include "source_io/module_parameter/read_input.h" +#include "source_io/parse_command_line.h" #include "source_main/version.h" #ifdef __DSP #include "source_base/module_device/memory_op.h" @@ -25,7 +25,7 @@ Driver::~Driver() { } -void Driver::init() +void Driver::init(const ModuleIO::CommandLineArgs& cli) { // 1) Let's start by printing a title. ModuleBase::TITLE("Driver", "ABACUS_begins"); @@ -35,7 +35,7 @@ void Driver::init() ModuleBase::timer::start(); // 3) Welcome to the atomic world! Let's do some fancy stuff here. - this->atomic_world(); + this->atomic_world(cli); // 4) All timers recorders are printed. ModuleBase::timer::finish(GlobalV::ofs_running); @@ -51,7 +51,7 @@ void Driver::init() ModuleBase::Global_File::close_all_log(GlobalV::MY_RANK, PARAM.inp.out_alllog, PARAM.inp.calculation); } -void Driver::print_start_info() +void Driver::print_start_info(const std::string& input_card) { ModuleBase::TITLE("Driver", "print_start_info"); #ifdef VERSION @@ -107,10 +107,10 @@ void Driver::print_start_info() GlobalV::ofs_running << "\n READING GENERAL INFORMATION" << std::endl; ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "global_out_dir", PARAM.globalv.global_out_dir); - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "global_in_card", PARAM.globalv.global_in_card); + ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "global_in_card", input_card); } -void Driver::reading() +void Driver::reading(const ModuleIO::CommandLineArgs& cli) { ModuleBase::TITLE("Driver", "reading"); ModuleBase::timer::start("Driver", "reading"); @@ -120,7 +120,7 @@ void Driver::reading() // (1) read the input file ModuleIO::ReadInput input(PARAM.globalv.myrank); - input.read_parameters(PARAM, PARAM.globalv.global_in_card); + input.read_parameters(PARAM, cli.input_file); ModuleBase::set_quit_out_dir(PARAM.globalv.global_out_dir); ModuleBase::set_quit_calculation(PARAM.inp.calculation); @@ -140,11 +140,12 @@ void Driver::reading() // (2) create the output directory, running_*.log and print info input.create_directory(PARAM); - this->print_start_info(); + this->print_start_info(cli.input_file); // (3) write the input file std::stringstream ss1; - ss1 << PARAM.globalv.global_out_dir << PARAM.globalv.global_in_card << ".info"; + const std::string input_name = cli.input_file.substr(cli.input_file.find_last_of("/\\") + 1); + ss1 << PARAM.globalv.global_out_dir << input_name << ".info"; input.write_parameters(PARAM, ss1.str()); // (*temp*) copy the variables from INPUT to each class @@ -185,13 +186,13 @@ void Driver::reading() ModuleBase::timer::end("Driver", "reading"); } -void Driver::atomic_world() +void Driver::atomic_world(const ModuleIO::CommandLineArgs& cli) { ModuleBase::TITLE("Driver", "atomic_world"); ModuleBase::timer::start("Driver", "atomic_world"); // reading information - this->reading(); + this->reading(cli); // where the actual stuff is done this->driver_run(); diff --git a/source/source_main/driver.h b/source/source_main/driver.h index a0acf5e16ac..c652ab52fc8 100644 --- a/source/source_main/driver.h +++ b/source/source_main/driver.h @@ -1,6 +1,13 @@ #ifndef DRIVER_H #define DRIVER_H +#include + +namespace ModuleIO +{ +struct CommandLineArgs; +} + class Driver { public: @@ -10,28 +17,33 @@ class Driver /** * @brief An interface function. * This function calls "this->reading()", "this->atomic_world()" in order. + * + * The selected INPUT path is passed explicitly without global state. + * Parsed variables are reserved for PART II and are not applied here. */ - void init(); + void init(const ModuleIO::CommandLineArgs& cli); private: /** * @brief Print the start information. * */ - void print_start_info(); + void print_start_info(const std::string& input_card); /** * @brief Reading the parameters and split the MPI world. * This function read the parameter in "INPUT", "STRU" etc, * and split the MPI world into different groups. + * + * The selected INPUT path is forwarded to ReadInput here. */ - void reading(); + void reading(const ModuleIO::CommandLineArgs& cli); /** * @brief An interface function. * This function calls "this->driver_run()" to do calculation, * and log the time and memory consumed during calculation. */ - void atomic_world(); + void atomic_world(const ModuleIO::CommandLineArgs& cli); // the actual calculations void driver_run(); diff --git a/source/source_main/main.cpp b/source/source_main/main.cpp index a4ab067b384..c3a51c72c4e 100644 --- a/source/source_main/main.cpp +++ b/source/source_main/main.cpp @@ -7,6 +7,7 @@ #include "source_base/parallel_global.h" #include "source_io/module_parameter/parameter.h" #include "source_io/parse_args.h" +#include "source_io/parse_command_line.h" #include "source_main/driver.h" #include "source_main/version.h" #ifdef _OPENMP @@ -14,6 +15,7 @@ #endif #include +#include #include namespace @@ -54,12 +56,20 @@ void print_welcome_banner() int main(int argc, char** argv) { - /* - read the arguement in the command-line, - with "abacus -v", the program exit and returns version info, - with no arguments, the program continues. - */ - ModuleIO::parse_args(argc, argv); + // Finish all CLI validation before acquiring MPI or FFTW resources. + // Informational flags keep their existing early-exit precedence. + ModuleIO::CommandLineArgs cli; + try + { + ModuleIO::parse_args(argc, argv); + cli = ModuleIO::parse_command_line(argc, argv); + } + catch (const std::exception& error) + { + std::cerr << "Command line error: " << error.what() << std::endl; + ModuleIO::print_help(argv[0]); + return 1; + } /* read the mpi parameters in the command-line, @@ -80,11 +90,17 @@ int main(int argc, char** argv) #endif PARAM.set_pal_param(my_rank, nproc, nthread_per_proc); + if (my_rank == 0 && !cli.vars.empty()) + { + std::cerr << "Warning: -p/--parameter variables are parsed but are not applied to INPUT in PART I." + << std::endl; + } + /* main program for doing electronic structure calculations. */ Driver DD; - DD.init(); + DD.init(cli); /* After running mpi version of abacus, release the mpi resources.