From 0376dce1a75b3d2d3e1176dfd17ca7bc7f1dbd3e Mon Sep 17 00:00:00 2001 From: Logan Harbour Date: Wed, 27 Nov 2024 19:24:20 -0700 Subject: [PATCH] Set DataFileName params on construction and add --show-data-files refs #29141 --- .../source/interfaces/DataFileInterface.md | 44 +++++----- .../include/interfaces/DataFileInterface.h | 14 +--- framework/include/outputs/ConsoleUtils.h | 5 ++ framework/include/utils/InputParameters.h | 8 ++ framework/src/base/MooseApp.C | 5 ++ framework/src/interfaces/DataFileInterface.C | 54 +++++------- framework/src/outputs/Console.C | 3 + framework/src/outputs/ConsoleUtils.C | 24 ++++++ framework/src/utils/DataFileUtils.C | 4 +- framework/src/utils/InputParameters.C | 34 ++++++++ test/src/userobjects/DataFileNameTest.C | 30 +++++-- test/tests/misc/data_file_name/test.i | 1 - test/tests/misc/data_file_name/tests | 83 ++++++++++++++++--- .../workshop/systems/inputparameters.md | 1 + 14 files changed, 225 insertions(+), 85 deletions(-) diff --git a/framework/doc/content/source/interfaces/DataFileInterface.md b/framework/doc/content/source/interfaces/DataFileInterface.md index 8bc64a3f8ff5..bdde9a065653 100644 --- a/framework/doc/content/source/interfaces/DataFileInterface.md +++ b/framework/doc/content/source/interfaces/DataFileInterface.md @@ -1,36 +1,34 @@ # DataFileInterface -This class provides API for resolving paths to data files distributed alongside +This class provides an API for resolving paths to data files distributed alongside MOOSE based apps and modules. | Method | Description | | - | - | -getDataFileName | Finds a data file given a `FileName` input parameter -getDataFileNameByName | Finds a data file given a relative path +getDataFileNameByPath | Finds a data file given a relative path -## `getDataFileName` +Files located in `/data` can be registered as data paths for use in installed and in-tree +builds of applications. The MOOSE framework and MOOSE module data directories +of `moose/framework/data` and `moose/modules/*/data` are already registered. These +data directories (located at the root of an application) are installed automatically using the standard `make install`. -Files located in `moose/framework/data`, the `moose/modules/*/data`, or -`/data` directories can be retrieved using the `getDataFileName(const -std::string & param)` function, where `param` is an input parameter of type -`DataFileName` +To make your data available for searching with this interface, register it with the following: -If the provided path is absolute, no searching will take place and the absolute -path will be used. Otherwise, `getDataFileName` will search (in this order) +- the `registerAppDataFilePath` macro in `Registry.h`, where an applications data in its root `data` directory is registered (ex: `registerAppDataFilePath("foo_bar")` for `FooBarApp`) +- the `registerNonAppDataFilePath` macro in `Registry.h`, where a general data directory is registered -- relative to the input file -- relative to all installed and registered data file directories (for an installed application) -- relative to all in-tree registered data file directories (for an in-tree build) +Once a data path is registered, it can be searched using this interface and via `DataFileName` +parameters. This search is consistent between both in-tree and installed builds of an application. -The "registered" data file directories are directories that are registered via: +When a parameter is specified as `DataFileName` type, the corresponding value that you get +via `getParam` is the searched value (the user's input is used for the search). +You may also utilize the `getDataFileNameByPath()` method within this interface to manually +search for a relative path in the data without the use of a parameter (for hard-coded data). The search order for these path is the following: -- the `registerAppDataFilePath` macro in `Registry.h`, where an applications data in its root `data` directory is registered -- the `registerNonAppDataFilePath` macro in `Registry.h`, where a general data directory is registered - -## `getDataFileNameByPath` +- if the path is absolute, use the absolute path +- relative to the input file (only for `DataFileName` parameters, not `getDataFileNameByPath()`) +- relative to all installed and registered data file directories (for an installed application) +- relative to all in-tree registered data file directories (for an in-tree build) -The `getDataFileNameByPath` can be used for hard coded data file names. e.g. -data files that are tied to a specific model and are not meant to be user -replaceable. This method will not search relative to the input file location and -will only find data files in source repositories and installed binary -distributions. +You can see the file paths that were found in the search for all `DataFileName` parameters +via the `--show-data-files` command line option. diff --git a/framework/include/interfaces/DataFileInterface.h b/framework/include/interfaces/DataFileInterface.h index ea182d08d02a..98091c8c10d9 100644 --- a/framework/include/interfaces/DataFileInterface.h +++ b/framework/include/interfaces/DataFileInterface.h @@ -33,11 +33,10 @@ class DataFileInterface DataFileInterface(const ParallelParamObject & parent); /** - * Returns the path of a data file for a given FileName type parameter, searching - * (in the following order) - * - relative to the input file directory - * - relative to the running binary (assuming the application is installed) - * - relative to all registered data file directories + * Deprecated method. + * + * The data file paths are now automatically set within the InputParameters + * object, so using getParam("param_name") is now sufficient. */ std::string getDataFileName(const std::string & param) const; @@ -49,10 +48,5 @@ class DataFileInterface std::string getDataFileNameByPath(const std::string & path) const; private: - /** - * Internal helper for getting a data file name. - */ - std::string getDataFileNameInternal(const std::string & path, const std::string * param) const; - const ParallelParamObject & _parent; }; diff --git a/framework/include/outputs/ConsoleUtils.h b/framework/include/outputs/ConsoleUtils.h index 12f141ceff86..c897f3861c20 100644 --- a/framework/include/outputs/ConsoleUtils.h +++ b/framework/include/outputs/ConsoleUtils.h @@ -87,6 +87,11 @@ std::string outputSystemInformationHelper(System & system); */ std::string outputLegacyInformation(MooseApp & app); +/** + * Output the (param path = value) pairs for each DataFileName parameter + */ +std::string outputDataFileParams(MooseApp & app); + /** * Helper function function for stringstream formatting */ diff --git a/framework/include/utils/InputParameters.h b/framework/include/utils/InputParameters.h index 2fdf4157bce1..b8ca1a58f41d 100644 --- a/framework/include/utils/InputParameters.h +++ b/framework/include/utils/InputParameters.h @@ -16,6 +16,7 @@ #include "MultiMooseEnum.h" #include "ExecFlagEnum.h" #include "Conversion.h" +#include "DataFileUtils.h" #include "libmesh/parameters.h" @@ -1112,6 +1113,11 @@ class InputParameters : public Parameters */ bool isFinalized() const { return _finalized; } + /** + * @return The DataFileName path for the parameter \p name (if any). + */ + std::optional queryDataFileNamePath(const std::string & name) const; + private: // Private constructor so that InputParameters can only be created in certain places. InputParameters(); @@ -1152,6 +1158,8 @@ class InputParameters : public Parameters std::string _custom_type; /// The data pertaining to a command line parameter (empty if not a command line param) std::optional _cl_data; + /// The searched path information pertaining to a DataFileName parameter + std::optional _data_file_name_path; /// The names of the parameters organized into groups std::string _group; /// The map of functions used for range checked parameters diff --git a/framework/src/base/MooseApp.C b/framework/src/base/MooseApp.C index 3d9217234d91..51351b28cd85 100644 --- a/framework/src/base/MooseApp.C +++ b/framework/src/base/MooseApp.C @@ -334,6 +334,11 @@ MooseApp::validParams() "To generate profiling report only on comma-separated list of MPI ranks."); #endif + params.addCommandLineParam("show_data_files", + "--show-data-files", + false, + "Show found paths for all DataFileName parameters"); + params.addPrivateParam("_app_name"); // the name passed to AppFactory::create params.addPrivateParam("_type"); params.addPrivateParam("_argc"); diff --git a/framework/src/interfaces/DataFileInterface.C b/framework/src/interfaces/DataFileInterface.C index 9e41ff71d07a..6c9d12d286d3 100644 --- a/framework/src/interfaces/DataFileInterface.C +++ b/framework/src/interfaces/DataFileInterface.C @@ -12,64 +12,54 @@ #include "ParallelParamObject.h" #include "DataFileUtils.h" +#include + DataFileInterface::DataFileInterface(const ParallelParamObject & parent) : _parent(parent) {} std::string DataFileInterface::getDataFileName(const std::string & param) const { - // The path from the parameters, which has not been modified because it is a DataFileName - const auto & path = _parent.template getParam(param); - if (path.empty()) - _parent.paramInfo(param, "Data file name is empty"); - - return getDataFileNameInternal(path, ¶m); + _parent.mooseDeprecated("getDataFileName() is deprecated. The file path is now directly set " + "within the InputParameters.\nUse getParam(\"", + param, + "\") instead."); + return _parent.parameters().get(param); } std::string DataFileInterface::getDataFileNameByPath(const std::string & path) const { - return getDataFileNameInternal(path, nullptr); -} - -std::string -DataFileInterface::getDataFileNameInternal(const std::string & path, - const std::string * param) const -{ - const std::string base = - param ? _parent.parameters().getFileBase(*param) : _parent.parameters().getFileBase(); + // Throw on error so that if getPath() fails, we can throw an error + // with the context of _parent.mooseError() + const auto throw_on_error_before = Moose::_throw_on_error; + Moose::_throw_on_error = true; + std::optional error; Moose::DataFileUtils::Path found_path; try { - found_path = Moose::DataFileUtils::getPath(path, base); + found_path = Moose::DataFileUtils::getPath(path); } catch (MooseException & e) { - if (param) - _parent.paramError(*param, e.what()); - else - _parent.mooseError(e.what()); + error = e.what(); } - if (found_path.context == Moose::DataFileUtils::Context::RELATIVE) - { - mooseAssert(!found_path.data_name, "Should not be set"); - mooseAssert(param, "Should only hit when param is set"); - _parent.paramInfo(*param, "Data file '", path, "' found relative to the input file."); - } - else if (found_path.context == Moose::DataFileUtils::Context::DATA) + Moose::_throw_on_error = throw_on_error_before; + if (error) + _parent.mooseError(*error); + + if (found_path.context == Moose::DataFileUtils::Context::DATA) { mooseAssert(found_path.data_name, "Should be set"); const std::string msg = "Using data file '" + found_path.path + "' from " + *found_path.data_name + " data"; - if (param) - _parent.paramInfo(*param, msg); - else - _parent.mooseInfo(msg); + _parent.mooseInfo(msg); } else { - mooseAssert(found_path.context == Moose::DataFileUtils::Context::ABSOLUTE, "Missing branch"); + mooseAssert(found_path.context == Moose::DataFileUtils::Context::ABSOLUTE, + "Missing branch and relative should not be hit"); mooseAssert(!found_path.data_name, "Should not be set"); } diff --git a/framework/src/outputs/Console.C b/framework/src/outputs/Console.C index 70a63d42c302..f887801891e6 100644 --- a/framework/src/outputs/Console.C +++ b/framework/src/outputs/Console.C @@ -736,6 +736,9 @@ Console::outputSystemInformation() if (_system_info_flags.isValueSet("execution")) _console << ConsoleUtils::outputExecutionInformation(_app, *_problem_ptr); + if (_app.getParam("show_data_files")) + _console << ConsoleUtils::outputDataFileParams(_app); + if (_system_info_flags.isValueSet("output")) _console << ConsoleUtils::outputOutputInformation(_app); diff --git a/framework/src/outputs/ConsoleUtils.C b/framework/src/outputs/ConsoleUtils.C index 7828d8e67a8f..4a601ece7340 100644 --- a/framework/src/outputs/ConsoleUtils.C +++ b/framework/src/outputs/ConsoleUtils.C @@ -22,6 +22,7 @@ #include "OutputWarehouse.h" #include "SystemInfo.h" #include "Checkpoint.h" +#include "InputParameterWarehouse.h" #include "libmesh/string_to_enum.h" @@ -431,6 +432,29 @@ outputLegacyInformation(MooseApp & app) return oss.str(); } +std::string +outputDataFileParams(MooseApp & app) +{ + std::map values; // for A-Z sort + for (const auto & object_name_params_pair : app.getInputParameterWarehouse().getInputParameters()) + { + const auto & params = object_name_params_pair.second; + for (const auto & name_value_pair : *params) + { + const auto & name = name_value_pair.first; + if (const auto path = params->queryDataFileNamePath(name)) + if (params->getHitNode(name)) + values.emplace(params->paramFullpath(name), path->path); + } + } + + std::stringstream oss; + oss << "Data File Parameters:\n"; + for (const auto & [param, value] : values) + oss << " " << param << " = " << value << "\n"; + return oss.str() + '\n'; +} + void insertNewline(std::stringstream & oss, std::streampos & begin, std::streampos & curr) { diff --git a/framework/src/utils/DataFileUtils.C b/framework/src/utils/DataFileUtils.C index 7d7f97fcacac..5f2b14f1c2e6 100644 --- a/framework/src/utils/DataFileUtils.C +++ b/framework/src/utils/DataFileUtils.C @@ -76,10 +76,10 @@ getPath(const std::string & path, // Found none else { - oss << "Unable to find the data file '" << path << "' anywhere."; + oss << "Unable to find the data file '" << path << "' anywhere.\n\n"; if (not_found.size()) { - oss << " Paths searched:\n"; + oss << "Paths searched:\n"; for (const auto & [name, data_path] : not_found) oss << " " << name << ": " << data_path << "\n"; } diff --git a/framework/src/utils/InputParameters.C b/framework/src/utils/InputParameters.C index 5d9b168377da..a3fae8b2fc55 100644 --- a/framework/src/utils/InputParameters.C +++ b/framework/src/utils/InputParameters.C @@ -686,6 +686,34 @@ InputParameters::finalize(const std::string & parsing_syntax) set_if_filename(MeshFileName); set_if_filename(MatrixFileName); #undef set_if_filename + // Set paths for data files + else if (auto data_file_name = + dynamic_cast *>(param_value.get())) + { + Moose::DataFileUtils::Path found_path; + std::optional error; + + // Catch this so that we can add additional error context if it fails (the param path) + const auto throw_on_error_before = Moose::_throw_on_error; + Moose::_throw_on_error = true; + try + { + found_path = Moose::DataFileUtils::getPath(data_file_name->get(), getFileBase(param_name)); + } + catch (std::exception & e) + { + error = errorPrefix(param_name) + " " + e.what(); + } + Moose::_throw_on_error = throw_on_error_before; + + if (error) + mooseError(*error); + + // Set the value to the absolute searched path + data_file_name->set() = found_path.path; + // And store the path in metadata so that we can dump it later + at(param_name)._data_file_name_path = found_path; + } } _finalized = true; @@ -1688,6 +1716,12 @@ InputParameters::paramAliases(const std::string & param_name) const return aliases; } +std::optional +InputParameters::queryDataFileNamePath(const std::string & name) const +{ + return at(checkForRename(name))._data_file_name_path; +} + void InputParameters::callMooseErrorHelper(const MooseBase & moose_base, const std::string & error) { diff --git a/test/src/userobjects/DataFileNameTest.C b/test/src/userobjects/DataFileNameTest.C index 3b474af079c8..519b66e74f1e 100644 --- a/test/src/userobjects/DataFileNameTest.C +++ b/test/src/userobjects/DataFileNameTest.C @@ -9,22 +9,40 @@ #include "DataFileNameTest.h" +#include "ExecutablePath.h" + +#include + registerMooseObject("MooseTestApp", DataFileNameTest); InputParameters DataFileNameTest::validParams() { InputParameters params = GeneralUserObject::validParams(); - params.addRequiredParam("data_file", "Data file to look up"); + params.addParam("data_file", + "Data file to look up that is loaded from the params)"); + params.addParam("data_file_by_path", "Data file to look up by path (not param)"); + params.addParam("data_file_deprecated", + "Data file to look up that is loaded from the deprecated method"); return params; } DataFileNameTest::DataFileNameTest(const InputParameters & parameters) : GeneralUserObject(parameters) { - // a data file name supplied through an input parameter - mooseInfo(getDataFileName("data_file")); - - // a hard coded data file name - mooseInfo(getDataFileNameByPath("README.md")); + const auto print_path = [&](const std::string & name, const std::string & path) + { + _console << name + << "_relative=" << std::filesystem::relative(path, std::filesystem::current_path()) + << std::endl; + _console << name << "_relative_to_binary=" + << std::filesystem::relative(path, Moose::getExecutablePath()) << std::endl; + }; + if (isParamSetByUser("data_file")) + print_path("data_file", getParam("data_file")); + if (isParamSetByUser("data_file_by_path")) + print_path("data_file_by_path", + getDataFileNameByPath(getParam("data_file_by_path"))); + if (isParamSetByUser("data_file_deprecated")) + print_path("data_file_deprecated", getDataFileName("data_file_deprecated")); } diff --git a/test/tests/misc/data_file_name/test.i b/test/tests/misc/data_file_name/test.i index 670487b48492..d24a674c9a0c 100644 --- a/test/tests/misc/data_file_name/test.i +++ b/test/tests/misc/data_file_name/test.i @@ -8,7 +8,6 @@ [UserObjects] [data_file] type = DataFileNameTest - data_file = README.md [] [] diff --git a/test/tests/misc/data_file_name/tests b/test/tests/misc/data_file_name/tests index e593c13a12f0..d9f513497d79 100644 --- a/test/tests/misc/data_file_name/tests +++ b/test/tests/misc/data_file_name/tests @@ -1,18 +1,79 @@ [Tests] - [exists] - type = RunApp - input = test.i - design = 'MooseObject.md' - issues = '#20839' - requirement = 'The system shall be to find data files in designated directories, regardless of the source repository or install location.' + design = 'MooseObject.md DataFileInterface.md' + issues = '#20839 #29141' + + [param] + requirement = 'The system shall be to find data files via a parameter in designated directories for' + [in_tree] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file=README.md' + expect_out = 'data_file_relative="../../../../framework/data/README.md"' + detail = 'in-tree builds' + installation_type = in_tree + [] + [relocated] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file=README.md' + expect_out = 'data_file_relative_to_binary="../share/moose/data/README.md"' + requirement = 'The system shall be to find data files via a parameter in designated directories for installed builds' + detail = 'and in installed builds' + installation_type = relocated + [] [] - [error] + [param_error] type = RunException input = test.i cli_args = 'UserObjects/data_file/data_file=this/file/does/not/exist/anywhere.dat' - expect_err = 'Unable to find the data file \'this/file/does/not/exist/anywhere.dat\' anywhere. Paths searched:\s+moose:(.*)' - design = 'MooseObject.md' - issues = '#20839' - requirement = 'The system shall throw an exception if a data file cannot be found in any of the designated directories.' + expect_err = 'CLI_ARGS:1: \(UserObjects/data_file/data_file\) Unable to find the data file \'this/file/does/not/exist/anywhere.dat\' anywhere.\s+Paths searched:\s+moose:(.*)' + requirement = 'The system shall throw an exception if a data file by parameter cannot be found in any of the designated directories.' + [] + [by_path] + requirement = 'The system shall be to find data files via a path in designated directories for' + [in_tree] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file_by_path=README.md' + expect_out = 'Using data file \'(.*)/framework/data/README.md\' from moose data(.*)data_file_by_path_relative="../../../../framework/data/README.md"' + detail = 'in-tree builds' + installation_type = in_tree + [] + [relocated] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file_by_path=README.md' + expect_out = 'Using data file \'(.*)/framework/data/README.md\' from moose data(.*)data_file_by_path_relative_to_binary="../share/moose/data/README.md"' + detail = 'and in installed builds builds' + installation_type = relocated + [] + [] + [param_deprecated] + requirement = 'The system shall be to find data files via a parameter in designated directories using the deprecated interface for' + [in_tree] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file_deprecated=README.md' + expect_out = 'data_file_deprecated_relative="../../../../framework/data/README.md"' + detail = 'in-tree builds' + installation_type = in_tree + [] + [relocated] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file=README.md' + expect_out = data_file_deprecated_relative_to_binary="../share/moose/data/README.md"' + requirement = 'The system shall be to find data files via a parameter in designated directories for installed builds' + detail = 'and in installed builds' + installation_type = relocated + [] + [] + [show_data_files] + type = RunApp + input = test.i + cli_args = 'UserObjects/data_file/data_file=README.md --show-data-files' + expect_out = 'UserObjects/data_file/data_file = (.*)framework/data/README.md' + installation_type = in_tree + requirement = 'The system shall support the output of all consumed data files via a parameter in designated data directories' [] [] diff --git a/tutorials/darcy_thermo_mech/doc/content/workshop/systems/inputparameters.md b/tutorials/darcy_thermo_mech/doc/content/workshop/systems/inputparameters.md index 3e13cb1e0870..88ee53339325 100644 --- a/tutorials/darcy_thermo_mech/doc/content/workshop/systems/inputparameters.md +++ b/tutorials/darcy_thermo_mech/doc/content/workshop/systems/inputparameters.md @@ -182,6 +182,7 @@ functions. - SubdomainName - BoundaryName - FileName +- DataFileName - VariableName - FunctionName - UserObjectName