diff --git a/SConstruct b/SConstruct index 4eba7488b..9e8f23f82 100644 --- a/SConstruct +++ b/SConstruct @@ -78,6 +78,7 @@ if 'clean' in COMMAND_LINE_TARGETS: for name in os.listdir('interfaces/cython/cantera/data/'): if name != '__init__.py': removeFile('interfaces/cython/cantera/data/' + name) + removeDirectory('interfaces/cython/cantera/test/data/test_subdir') for name in os.listdir('interfaces/cython/cantera/test/data/'): if name != '__init__.py': removeFile('interfaces/cython/cantera/test/data/' + name) diff --git a/include/cantera/base/AnyMap.h b/include/cantera/base/AnyMap.h index 0aa0b44e8..1c9871b87 100644 --- a/include/cantera/base/AnyMap.h +++ b/include/cantera/base/AnyMap.h @@ -228,9 +228,12 @@ public: //! Create an AnyMap from a YAML file. /*! - * Searches the Cantera include path for the file. + * Searches the directory containing the optionally-specified parent file + * first, followed by the current working directory and the Cantera include + * path. */ - static AnyMap fromYamlFile(const std::string& name); + static AnyMap fromYamlFile(const std::string& name, + const std::string& parent_name=""); //! Create an AnyMap from a string containing a YAML document static AnyMap fromYamlString(const std::string& yaml); diff --git a/interfaces/cython/.gitignore b/interfaces/cython/.gitignore index c85f7d201..f919e2a5c 100644 --- a/interfaces/cython/.gitignore +++ b/interfaces/cython/.gitignore @@ -9,6 +9,7 @@ cantera/test/data/*.dat cantera/test/data/*.csv cantera/test/data/*.yaml cantera/test/data/*.yml +cantera/test/data/test_subdir setup.py scripts/ctml_writer.py scripts/ctml_writer diff --git a/src/base/AnyMap.cpp b/src/base/AnyMap.cpp index ee4052790..5af1c7ed7 100644 --- a/src/base/AnyMap.cpp +++ b/src/base/AnyMap.cpp @@ -749,18 +749,37 @@ AnyMap AnyMap::fromYamlString(const std::string& yaml) { return amap; } -AnyMap AnyMap::fromYamlFile(const std::string& name) { - std::string fullName = findInputFile(name); - int mtime = get_modified_time(fullName); +AnyMap AnyMap::fromYamlFile(const std::string& name, + const std::string& parent_name) +{ + std::string fullName; + // See if a file with this name exists in a path relative to the parent file + size_t islash = parent_name.find_last_of("/\\"); + if (islash != npos) { + std::string parent_path = parent_name.substr(0, islash); + if (std::ifstream(parent_path + "/" + name).good()) { + fullName = parent_path + "/" + name; + } + } + // Otherwise, search the Cantera include path for the file + if (fullName.empty()) { + fullName = findInputFile(name); + } // Check for an already-parsed YAML file with the same last-modified time, // and return that if possible + int mtime = get_modified_time(fullName); std::unique_lock lock(yaml_cache_mutex); auto iter = s_cache.find(fullName); if (iter != s_cache.end() && iter->second.second == mtime) { return iter->second.first; } + if (!std::ifstream(fullName).good()) { + throw CanteraError("AnyMap::fromYamlFile", "Input file '{}' not found " + "on the Cantera search path.", name); + } + // Generate an AnyMap from the YAML file and store it in the cache auto& cache_item = s_cache[fullName]; cache_item.second = mtime; @@ -777,6 +796,7 @@ AnyMap AnyMap::fromYamlFile(const std::string& name) { s_cache.erase(fullName); throw; } + cache_item.first["__file__"] = fullName; // Return a copy of the AnyMap return cache_item.first; diff --git a/src/kinetics/KineticsFactory.cpp b/src/kinetics/KineticsFactory.cpp index 269f4cea6..ca31a450c 100644 --- a/src/kinetics/KineticsFactory.cpp +++ b/src/kinetics/KineticsFactory.cpp @@ -111,12 +111,13 @@ void addReactions(Kinetics& kin, const AnyMap& phaseNode, const AnyMap& rootNode throw CanteraError("setupKinetics", "Unknown rule '{}' for adding " "species from the '{}' section.", rules[i], sections[i]); } - const auto& slash = boost::ifind_first(sections[i], "/"); + const auto& slash = boost::ifind_last(sections[i], "/"); if (slash) { // specified section is in a different file string fileName (sections[i].begin(), slash.begin()); string node(slash.end(), sections[i].end()); - AnyMap reactions = AnyMap::fromYamlFile(fileName); + AnyMap reactions = AnyMap::fromYamlFile(fileName, + rootNode.getString("__file__", "")); for (const auto& R : reactions[node].asVector()) { kin.addReaction(newReaction(R, kin)); } diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index a82fdd8a3..8cb87b8e2 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -431,11 +431,12 @@ void setupPhase(ThermoPhase& thermo, const AnyMap& phaseNode, for (const auto& elemNode : phaseNode["elements"].asVector()) { const string& source = elemNode.begin()->first; const auto& names = elemNode.begin()->second.asVector(); - const auto& slash = boost::ifind_first(source, "/"); + const auto& slash = boost::ifind_last(source, "/"); if (slash) { std::string fileName(source.begin(), slash.begin()); std::string node(slash.end(), source.end()); - const AnyMap elements = AnyMap::fromYamlFile(fileName); + const AnyMap elements = AnyMap::fromYamlFile(fileName, + rootNode.getString("__file__", "")); addElements(thermo, names, elements[node].asMap("symbol"), false); } else if (rootNode.hasKey(source)) { @@ -476,12 +477,13 @@ void setupPhase(ThermoPhase& thermo, const AnyMap& phaseNode, for (const auto& speciesNode : phaseNode["species"].asVector()) { const string& source = speciesNode.begin()->first; const auto& names = speciesNode.begin()->second; - const auto& slash = boost::ifind_first(source, "/"); + const auto& slash = boost::ifind_last(source, "/"); if (slash) { // source is a different input file std::string fileName(source.begin(), slash.begin()); std::string node(slash.end(), source.end()); - AnyMap species = AnyMap::fromYamlFile(fileName); + AnyMap species = AnyMap::fromYamlFile(fileName, + rootNode.getString("__file__", "")); addSpecies(thermo, names, species[node]); } else if (rootNode.hasKey(source)) { // source is in the current file diff --git a/test/data/ideal-gas.yaml b/test/data/ideal-gas.yaml index 660f70e11..2e6e7985f 100644 --- a/test/data/ideal-gas.yaml +++ b/test/data/ideal-gas.yaml @@ -24,7 +24,7 @@ phases: thermo: ideal-gas elements: - default: [N, O] - - species-elements.yaml/isotopes: [Ar] + - test_subdir/species-elements.yaml/isotopes: [Ar] species: [O2, NO, N2, AR] note: replace Argon with custom element from a different file state: {T: 900.0, P: 5 atm, Y: {O2: 0.4, N2: 0.4, AR: 0.2}} @@ -33,7 +33,7 @@ phases: thermo: ideal-gas species: - species: [O2, N2] - - species-elements.yaml/species: [NO2, N2O] + - ../../test/data/test_subdir/species-elements.yaml/species: [NO2, N2O] state: {T: 300.0, P: 1 atm, X: "N2O:0.3, O2:0.7"} - name: species-all @@ -50,8 +50,8 @@ phases: kinetics: gas species: - species: all - - species-elements.yaml/species: all - reactions: [reactions, species-elements.yaml/nox-reactions] + - test_subdir/species-elements.yaml/species: all + reactions: [reactions, test_subdir/species-elements.yaml/nox-reactions] state: {T: 300.0, P: 1 atm, X: {AR: 0.2, O2: 0.7, N2: 0.1}} diff --git a/test/data/species-elements.yaml b/test/data/test_subdir/species-elements.yaml similarity index 100% rename from test/data/species-elements.yaml rename to test/data/test_subdir/species-elements.yaml