diff --git a/Input/include/ParseParams.hpp b/Input/include/ParseParams.hpp index ac24428..21c3c7a 100644 --- a/Input/include/ParseParams.hpp +++ b/Input/include/ParseParams.hpp @@ -14,6 +14,8 @@ #include #include "ParamsCarrier.hpp" +#include "CLI/App.hpp" +#include "CLI/Option.hpp" /** * @brief ParseParams is responsible for parsing arguments from CLI. @@ -61,6 +63,109 @@ class ParseParams * @return Path where Geliosphere is located */ std::string getApplicationPath(char **argv); + + /** + * @brief Parse state enumeration for state machine pattern. + */ + enum class ParseState { + SETUP, + PARSING, + VALIDATION, + PROCESSING, + COMPLETE, + ERROR + }; + + /** + * @brief Configuration structure to hold parsing parameters. + */ + struct ParseConfig { + std::string inputFile; + std::string pathToLogFile; + float newDt, newK0, newV; + int month, year; + std::string newDestination, settings, customModelString; + int numberOfTestParticles; + std::string currentApplicationPath; + }; + + /** + * @brief Setup CLI options for the application. + * + * @param app CLI::App instance to setup options on + * @param config Configuration structure containing all parsing parameters + */ + void setupCliOptions(CLI::App& app, ParseConfig& config); + + /** + * @brief Setup option relationships (excludes, requires). + */ + void setupOptionRelationships(); + + /** + * @brief Execute state machine transition. + * + * @param currentState Current parsing state + * @param config Configuration structure + * @param app CLI app instance + * @param argc Argument count + * @param argv Argument values + * @return Next state + */ + ParseState executeState(ParseState currentState, ParseConfig& config, CLI::App& app, int argc, char** argv); + + /** + * @brief Setup state handler. + * + * @param config Configuration structure + * @param app CLI app instance + * @return Next state + */ + ParseState handleSetupState(ParseConfig& config, CLI::App& app); + + /** + * @brief Parsing state handler. + * + * @param app CLI app instance + * @param argc Argument count + * @param argv Argument values + * @return Next state + */ + ParseState handleParsingState(CLI::App& app, int argc, char** argv); + + /** + * @brief Validation state handler. + * + * @return Next state + */ + ParseState handleValidationState(); + + /** + * @brief Processing state handler. + * + * @param config Configuration structure + * @return Next state + */ + ParseState handleProcessingState(const ParseConfig& config); + + // CLI option pointers - stored as class members to be accessible across functions + CLI::Option *forwardModel; + CLI::Option *backwardModel; + CLI::Option *solarPropLikeModel; + CLI::Option *geliosphereModel; + CLI::Option *csv; + CLI::Option *run_simulation; + CLI::Option *cpuOnly; + CLI::Option *batchRun; + CLI::Option *dtset; + CLI::Option *kset; + CLI::Option *vset; + CLI::Option *destination; + CLI::Option *setNumberOfTestParticles; + CLI::Option *monthOption; + CLI::Option *yearOption; + CLI::Option *settingsOption; + CLI::Option *customModel; }; #endif \ No newline at end of file diff --git a/Input/src/InputValidation.cpp b/Input/src/InputValidation.cpp index 9293bf6..5d3f94b 100644 --- a/Input/src/InputValidation.cpp +++ b/Input/src/InputValidation.cpp @@ -11,8 +11,6 @@ #include #include -#include "CLI/App.hpp" -#include "CLI/Option.hpp" #include "spdlog/spdlog.h" #include "MeasureValuesTransformation.hpp" diff --git a/Input/src/ParseParams.cpp b/Input/src/ParseParams.cpp index 93ce72d..59c6736 100644 --- a/Input/src/ParseParams.cpp +++ b/Input/src/ParseParams.cpp @@ -15,192 +15,242 @@ int ParseParams::parseParams(int argc, char **argv) { - std::string inputFile; - std::string pathToLogFile; - float newDt, newK0, newV, newMu; - int month, year; - std::string newDestination, settings, customModelString; - int numberOfTestParticles; + ParseConfig config; singleTone = singleTone->instance(); - std::string currentApplicationPath = getApplicationPath(argv); - singleTone->putString("currentApplicationPath", currentApplicationPath); - InputValidation *inputValidation = new InputValidation(); + config.currentApplicationPath = getApplicationPath(argv); + singleTone->putString("currentApplicationPath", config.currentApplicationPath); + CLI::App app{"App description"}; - CLI::Option *forwardModel = app.add_flag("-F,--forward", "Run a 1D forward-in-time model")->group("models"); - CLI::Option *backwardModel = app.add_flag("-B,--backward", "Run a 1D backward-in-time model")->group("models"); - CLI::Option *solarPropLikeModel = app.add_flag("-E,--solarprop-like-model", "Run a SolarProp-like 2D backward model")->group("models"); - CLI::Option *geliosphereModel = app.add_flag("-T,--geliosphere-2d-model", "Run a Geliosphere 2D backward model")->group("models"); - CLI::Option *csv = app.add_flag("-c,--csv", "Output will be in .csv"); - CLI::Option *run_simulation = app.add_option("--evaluation", pathToLogFile,"Simulation excluded, run only evaluation "); -#if GPU_ENABLED == 1 - CLI::Option *cpuOnly = app.add_flag("--cpu-only", "Use only CPU for calculaions"); -#else - singleTone->putInt("isCpu", 1); -#endif - CLI::Option *batchRun = app.add_option("-b,--batchrun", inputFile,"Input batch file")->group("models"); - CLI::Option *dtset = app.add_option("-d,--dt", newDt, "Set dt to new value(s)"); - CLI::Option *kset = app.add_option("-K,--K0", newK0, "Set K to new value(cm^2/s)"); - CLI::Option *vset = app.add_option("-V,--V", newV, "Set V to new value(km/s)"); - CLI::Option *destination = app.add_option("-p,--path", newDestination, "Set destination folder name"); - CLI::Option *setNumberOfTestParticles = app.add_option("-N,--number-of-test-particles", numberOfTestParticles, "Set number of test particles in millions(round up due to GPU execution)"); - CLI::Option *monthOption = app.add_option("-m,--month", month, "Set month for using meassured values"); - CLI::Option *yearOption = app.add_option("-y,--year", year, "Set year for using meassured values"); - CLI::Option *settingsOption = app.add_option("-s,--settings", settings, "Path to .toml file"); - CLI::Option *customModel = app.add_option("--custom-model", customModelString, "Run custom user-implemented model."); + + // Initialize state machine + ParseState currentState = ParseState::SETUP; + + // Run state machine + while (currentState != ParseState::COMPLETE && currentState != ParseState::ERROR) { + currentState = executeState(currentState, config, app, argc, argv); + } + + if (currentState == ParseState::ERROR) { + return -1; + } + + printParameters(singleTone); + return 1; +} - kset->excludes(monthOption, yearOption); - vset->excludes(monthOption, yearOption); +ParamsCarrier *ParseParams::getParams() +{ + return singleTone; +} - backwardModel->excludes(forwardModel, solarPropLikeModel, geliosphereModel, customModel, batchRun); - forwardModel->excludes(backwardModel, solarPropLikeModel, geliosphereModel, customModel, batchRun); - solarPropLikeModel->excludes(backwardModel, forwardModel, geliosphereModel, customModel, batchRun); - geliosphereModel->excludes(backwardModel, forwardModel, solarPropLikeModel, customModel, batchRun); - customModel->excludes(backwardModel, forwardModel, solarPropLikeModel, geliosphereModel, batchRun); - batchRun->excludes(backwardModel, forwardModel, solarPropLikeModel, geliosphereModel, customModel, - dtset, setNumberOfTestParticles, kset, vset, monthOption, yearOption); - monthOption->requires(yearOption); +void ParseParams::printParameters(ParamsCarrier *params) +{ + InputValidation *inputValidation = new InputValidation(); + spdlog::info("Chosen model:" + singleTone->getString("model", "1D Fp")); + spdlog::info("K0:" + std::to_string(params->getFloat("K0", params->getFloat("K0_default", 5e22 * 4.4683705e-27))) + " au^2 / s"); + spdlog::info("V:" + std::to_string(params->getFloat("V", params->getFloat("V_default", 400 * 6.68458712e-9))) + " au / s"); + spdlog::info("dt:" + std::to_string(params->getFloat("dt", params->getFloat("dt_default", 5.0f))) + " s"); + if (inputValidation->isInputSolarPropLikeModel(singleTone->getString("model", "1D Fp")) || inputValidation->isInputGeliosphere2DModel(singleTone->getString("model", "1D Fp"))) + { + spdlog::info("tilt_angle:" + std::to_string(params->getFloat("tilt_angle", -1.0f))); + spdlog::info("polarity:" + std::to_string(params->getInt("polarity", -1.0f))); + } +} + +std::string ParseParams::getApplicationPath(char **argv) +{ + std::regex regexp(R"(.*\/)"); + std::cmatch m; + std::regex_search(argv[0], m, regexp); + return m[0]; +} +ParseParams::ParseState ParseParams::executeState(ParseState currentState, ParseConfig& config, CLI::App& app, int argc, char** argv) +{ + switch (currentState) { + case ParseState::SETUP: + return handleSetupState(config, app); + case ParseState::PARSING: + return handleParsingState(app, argc, argv); + case ParseState::VALIDATION: + return handleValidationState(); + case ParseState::PROCESSING: + return handleProcessingState(config); + default: + return ParseState::ERROR; + } +} + +ParseParams::ParseState ParseParams::handleSetupState(ParseConfig& config, CLI::App& app) +{ + // Setup CLI options + setupCliOptions(app, config); + + // Setup option relationships + setupOptionRelationships(); + + return ParseState::PARSING; +} + +ParseParams::ParseState ParseParams::handleParsingState(CLI::App& app, int argc, char** argv) +{ spdlog::info("Started to parsing input parameters"); - CLI11_PARSE(app, argc, argv); - if (!*forwardModel && !*backwardModel && !*solarPropLikeModel && !geliosphereModel && !batchRun) - { + try { + app.parse(argc, argv); + } catch (const CLI::ParseError &e) { + app.exit(e); + return ParseState::ERROR; + } + + return ParseState::VALIDATION; +} + +ParseParams::ParseState ParseParams::handleValidationState() +{ + // Validate that at least one model is selected + if (!*forwardModel && !*backwardModel && !*solarPropLikeModel && !*geliosphereModel && !*batchRun) { spdlog::error("At least one model must be selected!"); - return -1; + return ParseState::ERROR; } - if(*run_simulation){ + + return ParseState::PROCESSING; +} + +ParseParams::ParseState ParseParams::handleProcessingState(const ParseConfig& config) +{ + InputValidation *inputValidation = new InputValidation(); + + // Process general options + if (*run_simulation) { singleTone->putInt("run_simulation", 0); - singleTone->putString("pathToLogFile",pathToLogFile); - } - else{ + singleTone->putString("pathToLogFile", config.pathToLogFile); + } else { singleTone->putInt("run_simulation", 1); } - if (*csv) - { + + if (*csv) { singleTone->putInt("csv", 1); } - if (*dtset) - { - if (!inputValidation->checkDt(newDt)) - { + +#if GPU_ENABLED == 1 + if (*cpuOnly) { + singleTone->putInt("isCpu", 1); + } +#endif + + if (*destination) { + singleTone->putString("destination", config.newDestination); + } + + // Process value options with validation + if (*dtset) { + if (!inputValidation->checkDt(config.newDt)) { spdlog::error("dt is out of range!(3-5000)"); - return false; + return ParseState::ERROR; } - inputValidation->setDt(singleTone, newDt); + inputValidation->setDt(singleTone, config.newDt); } - if (*kset) - { - if (!inputValidation->checkK0(newK0)) - { + + if (*kset) { + if (!inputValidation->checkK0(config.newK0)) { spdlog::error("K0 is out of range!(>0)"); - return -1; + return ParseState::ERROR; } - if (newK0 < 1e19 || newK0 > 1e23) - { + if (config.newK0 < 1e19 || config.newK0 > 1e23) { spdlog::warn("K0 is out of recommended range!(1e19-1e23 cm^2/s)"); } - inputValidation->setK0(singleTone, newK0); + inputValidation->setK0(singleTone, config.newK0); } - if (*setNumberOfTestParticles) - { - if (!inputValidation->checkNumberOfTestParticles(numberOfTestParticles)) - { + + if (*setNumberOfTestParticles) { + if (!inputValidation->checkNumberOfTestParticles(config.numberOfTestParticles)) { spdlog::error("Number of test particles must be greater than 0!"); - return -1; + return ParseState::ERROR; } - inputValidation->setNumberOfTestParticles(singleTone, numberOfTestParticles); + inputValidation->setNumberOfTestParticles(singleTone, config.numberOfTestParticles); } - if (*vset) - { - if (!inputValidation->checkV(newV)) - { + + if (*vset) { + if (!inputValidation->checkV(config.newV)) { spdlog::error("V is out of range!(100-1500 km/s)"); - return -1; - } - inputValidation->setV(singleTone, newV); - } - if (*settingsOption) - { - inputValidation->newSettingsLocationCheck(singleTone, settings); - } - else - { - if (access(settings.c_str(), F_OK) == 0) { - TomlSettings *tomlSettings = new TomlSettings(currentApplicationPath + "Settings.toml"); - tomlSettings->parseFromSettings(singleTone); - } else { - spdlog::warn("No settings file exists on default path."); + return ParseState::ERROR; } + inputValidation->setV(singleTone, config.newV); } -#if GPU_ENABLED == 1 - if (*cpuOnly) - { - singleTone->putInt("isCpu", 1); - } -#endif - if (*destination) - { - singleTone->putString("destination", newDestination); - } - if (*forwardModel) - { + + // Process model options + if (*forwardModel) { singleTone->putString("model", "1D Fp"); - } - else if (*backwardModel) - { + } else if (*backwardModel) { singleTone->putString("model", "1D Bp"); - } - else if (*solarPropLikeModel) - { + } else if (*solarPropLikeModel) { singleTone->putString("model", "2D SolarProp-like"); - } - else if (*geliosphereModel) - { + } else if (*geliosphereModel) { singleTone->putString("model", "2D Geliosphere"); - } - else if (*customModel) - { - singleTone->putString("model", customModelString); - } - else if (*batchRun) - { + } else if (*customModel) { + singleTone->putString("model", config.customModelString); + } else if (*batchRun) { singleTone->putString("model", "batch run"); - singleTone->putString("inputBatchFile", inputFile); - return 1; + singleTone->putString("inputBatchFile", config.inputFile); + return ParseState::COMPLETE; // Special case - batch run returns early } - if (*monthOption && *yearOption) - { - inputValidation->monthYearCheck(singleTone, year, month, currentApplicationPath); + + // Process settings options + if (*settingsOption) { + inputValidation->newSettingsLocationCheck(singleTone, config.settings); + } else { + if (access(config.settings.c_str(), F_OK) == 0) { + TomlSettings *tomlSettings = new TomlSettings(config.currentApplicationPath + "Settings.toml"); + tomlSettings->parseFromSettings(singleTone); + } else { + spdlog::warn("No settings file exists on default path."); + } } - - printParameters(singleTone); - return 1; + + if (*monthOption && *yearOption) { + inputValidation->monthYearCheck(singleTone, config.year, config.month, config.currentApplicationPath); + } + + return ParseState::COMPLETE; } - -ParamsCarrier *ParseParams::getParams() +void ParseParams::setupCliOptions(CLI::App& app, ParseConfig& config) { - return singleTone; + forwardModel = app.add_flag("-F,--forward", "Run a 1D forward-in-time model")->group("models"); + backwardModel = app.add_flag("-B,--backward", "Run a 1D backward-in-time model")->group("models"); + solarPropLikeModel = app.add_flag("-E,--solarprop-like-model", "Run a SolarProp-like 2D backward model")->group("models"); + geliosphereModel = app.add_flag("-T,--geliosphere-2d-model", "Run a Geliosphere 2D backward model")->group("models"); + csv = app.add_flag("-c,--csv", "Output will be in .csv"); + run_simulation = app.add_option("--evaluation", config.pathToLogFile, "Simulation excluded, run only evaluation "); +#if GPU_ENABLED == 1 + cpuOnly = app.add_flag("--cpu-only", "Use only CPU for calculaions"); +#else + singleTone->putInt("isCpu", 1); +#endif + batchRun = app.add_option("-b,--batchrun", config.inputFile, "Input batch file")->group("models"); + dtset = app.add_option("-d,--dt", config.newDt, "Set dt to new value(s)"); + kset = app.add_option("-K,--K0", config.newK0, "Set K to new value(cm^2/s)"); + vset = app.add_option("-V,--V", config.newV, "Set V to new value(km/s)"); + destination = app.add_option("-p,--path", config.newDestination, "Set destination folder name"); + setNumberOfTestParticles = app.add_option("-N,--number-of-test-particles", config.numberOfTestParticles, "Set number of test particles in millions(round up due to GPU execution)"); + monthOption = app.add_option("-m,--month", config.month, "Set month for using meassured values"); + yearOption = app.add_option("-y,--year", config.year, "Set year for using meassured values"); + settingsOption = app.add_option("-s,--settings", config.settings, "Path to .toml file"); + customModel = app.add_option("--custom-model", config.customModelString, "Run custom user-implemented model."); } - -void ParseParams::printParameters(ParamsCarrier *params) +void ParseParams::setupOptionRelationships() { - InputValidation *inputValidation = new InputValidation(); - spdlog::info("Chosen model:" + singleTone->getString("model", "1D Fp")); - spdlog::info("K0:" + std::to_string(params->getFloat("K0", params->getFloat("K0_default", 5e22 * 4.4683705e-27))) + " au^2 / s"); - spdlog::info("V:" + std::to_string(params->getFloat("V", params->getFloat("V_default", 400 * 6.68458712e-9))) + " au / s"); - spdlog::info("dt:" + std::to_string(params->getFloat("dt", params->getFloat("dt_default", 5.0f))) + " s"); - if (inputValidation->isInputSolarPropLikeModel(singleTone->getString("model", "1D Fp")) || inputValidation->isInputGeliosphere2DModel(singleTone->getString("model", "1D Fp"))) - { - spdlog::info("tilt_angle:" + std::to_string(params->getFloat("tilt_angle", -1.0f))); - spdlog::info("polarity:" + std::to_string(params->getInt("polarity", -1.0f))); - } -} + kset->excludes(monthOption, yearOption); + vset->excludes(monthOption, yearOption); -std::string ParseParams::getApplicationPath(char **argv) -{ - std::regex regexp(R"(.*\/)"); - std::cmatch m; - std::regex_search(argv[0], m, regexp); - return m[0]; + backwardModel->excludes(forwardModel, solarPropLikeModel, geliosphereModel, customModel, batchRun); + forwardModel->excludes(backwardModel, solarPropLikeModel, geliosphereModel, customModel, batchRun); + solarPropLikeModel->excludes(backwardModel, forwardModel, geliosphereModel, customModel, batchRun); + geliosphereModel->excludes(backwardModel, forwardModel, solarPropLikeModel, customModel, batchRun); + customModel->excludes(backwardModel, forwardModel, solarPropLikeModel, geliosphereModel, batchRun); + batchRun->excludes(backwardModel, forwardModel, solarPropLikeModel, geliosphereModel, customModel, + dtset, setNumberOfTestParticles, kset, vset, monthOption, yearOption); + + monthOption->requires(yearOption); } \ No newline at end of file