diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index aa6e2d520..c404ccc13 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -403,6 +403,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_partitionCells.cpp tests/test_preconditionerfactory.cpp tests/test_privarspacking.cpp + tests/test_propertytree.cpp tests/test_region_phase_pvaverage.cpp tests/test_relpermdiagnostics.cpp tests/test_RestartSerialization.cpp diff --git a/opm/simulators/linalg/PropertyTree.cpp b/opm/simulators/linalg/PropertyTree.cpp index 96d43e674..81952839d 100644 --- a/opm/simulators/linalg/PropertyTree.cpp +++ b/opm/simulators/linalg/PropertyTree.cpp @@ -23,8 +23,14 @@ #include -namespace Opm -{ +#include +#include +#include +#include + +#include + +namespace Opm { PropertyTree::PropertyTree() : tree_(std::make_unique()) @@ -96,23 +102,23 @@ PropertyTree& PropertyTree::operator=(const PropertyTree& tree) return *this; } -template std::string PropertyTree::get(const std::string& key) const; -template std::string PropertyTree::get(const std::string& key, const std::string& defValue) const; -template double PropertyTree::get(const std::string& key) const; -template double PropertyTree::get(const std::string& key, const double& defValue) const; -template float PropertyTree::get(const std::string& key) const; -template float PropertyTree::get(const std::string& key, const float& defValue) const; -template int PropertyTree::get(const std::string& key) const; -template int PropertyTree::get(const std::string& key, const int& defValue) const; -template size_t PropertyTree::get(const std::string& key) const; -template size_t PropertyTree::get(const std::string& key, const size_t& defValue) const; -template bool PropertyTree::get(const std::string& key) const; -template bool PropertyTree::get(const std::string& key, const bool& defValue) const; +template std::string PropertyTree::get(const std::string& key) const; +template double PropertyTree::get(const std::string& key) const; +template float PropertyTree::get(const std::string& key) const; +template int PropertyTree::get(const std::string& key) const; +template size_t PropertyTree::get(const std::string& key) const; +template bool PropertyTree::get(const std::string& key) const; -template void PropertyTree::put(const std::string& key, const std::string& value); -template void PropertyTree::put(const std::string& key, const float& value); -template void PropertyTree::put(const std::string& key, const double& value); -template void PropertyTree::put(const std::string& key, const int& value); +template std::string PropertyTree::get(const std::string& key, const std::string& defValue) const; +template double PropertyTree::get(const std::string& key, const double& defValue) const; +template float PropertyTree::get(const std::string& key, const float& defValue) const; +template int PropertyTree::get(const std::string& key, const int& defValue) const; +template size_t PropertyTree::get(const std::string& key, const size_t& defValue) const; +template bool PropertyTree::get(const std::string& key, const bool& defValue) const; +template void PropertyTree::put(const std::string& key, const std::string& value); +template void PropertyTree::put(const std::string& key, const float& value); +template void PropertyTree::put(const std::string& key, const double& value); +template void PropertyTree::put(const std::string& key, const int& value); } // namespace Opm diff --git a/opm/simulators/linalg/PropertyTree.hpp b/opm/simulators/linalg/PropertyTree.hpp index 3c32b2227..2799eac16 100644 --- a/opm/simulators/linalg/PropertyTree.hpp +++ b/opm/simulators/linalg/PropertyTree.hpp @@ -20,51 +20,136 @@ #ifndef OPM_PROPERTYTREE_HEADER_INCLUDED #define OPM_PROPERTYTREE_HEADER_INCLUDED +#include #include #include #include +#include -namespace boost { -namespace property_tree { +namespace boost::property_tree { template class basic_ptree; - using ptree = basic_ptree>; -} -} + using ptree = basic_ptree>; +} // namespace boost::property_tree -namespace Opm +namespace Opm { + +/// Hierarchical collection of key/value pairs +class PropertyTree { - -class PropertyTree { public: + /// Default constructor. + /// + /// Should typically be populated in put() before use. PropertyTree(); + + /// Constructor + /// + /// Loads a property tree from an external source expected to be a text + /// file in JSON. + /// + /// \param[in] jsonFile Name of file containing external property tree, + /// linearised into JSON format. explicit PropertyTree(const std::string& jsonFile); + + /// Copy constructor. + /// + /// \param[in] tree Source object. PropertyTree(const PropertyTree& tree); + + /// Destructor. ~PropertyTree(); + /// Assignment operator + /// + /// \param[in] tree Source object. + /// + /// \return \code *this \endcode. + PropertyTree& operator=(const PropertyTree& tree); + + /// Insert key/value pair into property tree + /// + /// \tparam T Value type + /// + /// \param[in] key Property key. Expected to be in hierarchical + /// notation for subtrees--i.e., using periods ('.') to separate + /// hierarchy levels. + /// + /// \param[in] data Property value corresponding to \p key. template void put(const std::string& key, const T& data); + /// Retrieve property value given hierarchical property key. + /// + /// \tparam T Value type + /// + /// \param[in] key Property key. Expected to be in hierarchical + /// notation for subtrees--i.e., using periods ('.') to separate + /// hierarchy levels. + /// + /// \return Copy of internal property value for \p key. template T get(const std::string& key) const; + /// Retrieve property value given hierarchical property key. + /// + /// \tparam T Value type + /// + /// \param[in] key Property key. Expected to be in hierarchical + /// notation for subtrees--i.e., using periods ('.') to separate + /// hierarchy levels. + /// + /// \param[in] defValue Default value for when \p key is not in the + /// property tree. + /// + /// \return Copy of internal property value for \p key, or a copy of \p + /// defValue if the \p key is not in the property tree. template T get(const std::string& key, const T& defValue) const; + /// Retrieve copy of sub tree rooted at node. + /// + /// Throws an exception if no sub tree exists at given root. + /// + /// \param[in] Property key. Expected to be in hierarchical + /// notation for subtrees--i.e., using periods ('.') to separate + /// hierarchy levels. + /// + /// \return Copy of property sub tree rooted at \p key. PropertyTree get_child(const std::string& key) const; - std::optional get_child_optional(const std::string& key) const; - - PropertyTree& operator=(const PropertyTree& tree); + /// Retrieve copy of sub tree rooted at node. + /// + /// \param[in] Property key. Expected to be in hierarchical + /// notation for subtrees--i.e., using periods ('.') to separate + /// hierarchy levels. + /// + /// \return Copy of property sub tree rooted at \p key. Nullopt if no + /// sub tree exists that is rooted at \p key. + std::optional + get_child_optional(const std::string& key) const; + /// Emit a textual representation of the property tree in JSON form + /// + /// \param[in,out] os Output stream. Typically a stream opened on a + /// file. + /// + /// \param[in] pretty Whether or not to pretty-print the JSON + /// output--i.e., whether or not to insert new lines and spaces for + /// human readability. void write_json(std::ostream& os, bool pretty) const; protected: + /// Converting constructor. + /// + /// Forms a property tree object from a Boost ptree. + /// + /// \param[in] tree Source object represented as a Boost ptree. PropertyTree(const boost::property_tree::ptree& tree); + /// Internal representation of the property tree. std::unique_ptr tree_; }; - } // namespace Opm #endif // OPM_PROPERTYTREE_HEADER_INCLUDED diff --git a/tests/test_propertytree.cpp b/tests/test_propertytree.cpp new file mode 100644 index 000000000..ac8ab04f6 --- /dev/null +++ b/tests/test_propertytree.cpp @@ -0,0 +1,245 @@ +/* + Copyright 2025 SINTEF Digital, Mathematics and Cybernetics. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include + +#define BOOST_TEST_MODULE TestPropertyTree + +#ifndef HAVE_MPI +// Suppress GCC diagnostics of the form +// +// warning: "HAVE_MPI" is not defined, evaluates to 0 +// +// when compiling with "-Wundef". +#define HAVE_MPI 0 +#endif // HAVE_MPI + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(Put_And_Get) + +BOOST_AUTO_TEST_CASE(Top_Node_Only) +{ + auto t = Opm::PropertyTree{}; + + t.put("a", 1234); + t.put("b", 123.4); + t.put("c", std::string { "hello" }); + t.put("d", 12.34f); + + { + const auto a = t.get("a"); + BOOST_CHECK_EQUAL(a, 1234); + + const auto aa = t.get("aa", 42); + BOOST_CHECK_EQUAL(aa, 42); + } + + { + const auto b = t.get("b"); + BOOST_CHECK_CLOSE(b, 123.4, 1.0e-8); + + const auto bb = t.get("bb", 2.71828); + BOOST_CHECK_CLOSE(bb, 2.71828, 1.0e-8); + } + + { + const auto c = t.get("c"); + BOOST_CHECK_EQUAL(c, "hello"); + + const auto cc = t.get("cc", std::string { "world" }); + BOOST_CHECK_EQUAL(cc, "world"); + } + + { + const auto d = t.get("d"); + BOOST_CHECK_CLOSE(d, 12.34f, 1.0e-6f); + + const auto dd = t.get("dd", -1.618f); + BOOST_CHECK_CLOSE(dd, -1.618f, 1.0e-6f); + } +} + +BOOST_AUTO_TEST_CASE(Missing_Keys) +{ + auto t = Opm::PropertyTree{}; + + BOOST_CHECK_THROW(t.get("a"), std::exception); +} + +BOOST_AUTO_TEST_CASE(Hierarchy) +{ + auto t = Opm::PropertyTree{}; + + t.put("a.b.c", 123); + + { + const auto c = t.get("a.b.c"); + BOOST_CHECK_EQUAL(c, 123); + } + + { + const auto a = t.get_child("a"); + const auto c = a.get("b.c"); + BOOST_CHECK_EQUAL(c, 123); + } + + { + const auto a = t.get_child("a"); + const auto b = a.get_child("b"); + const auto c = b.get("c"); + BOOST_CHECK_EQUAL(c, 123); + BOOST_CHECK_CLOSE(a.get("b.d", 3.1415), 3.1415, 1.0e-8); + } + + { + const auto f = t.get_child_optional("d.e.f"); + BOOST_CHECK_MESSAGE(! f.has_value(), R"(Node "f" must not exist)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // Put_And_Get + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(Load_From_File) + +namespace { + + class TempFile + { + public: + TempFile() + : fname_ { std::filesystem::temp_directory_path() / + Opm::unique_path("wrk-%%%%") } + {} + + ~TempFile() + { + std::filesystem::remove_all(this->fname_); + } + + void append(std::string_view s) + { + std::ofstream { this->fname_, std::ios::app } << s; + } + + std::string name() const + { + return this->fname_.generic_string(); + } + + private: + std::filesystem::path fname_; + }; + +} // Anonymous namespace + +BOOST_AUTO_TEST_CASE(Top_Node_Only) +{ + auto f = TempFile{}; + f.append(R"({ + "a" : 1234, + "b" : 123.4, + "c" : "hello", + "d" : 12.34 +} +)"); + + const auto t = Opm::PropertyTree { f.name() }; + { + const auto a = t.get("a"); + BOOST_CHECK_EQUAL(a, 1234); + + const auto aa = t.get("aa", 42); + BOOST_CHECK_EQUAL(aa, 42); + } + + { + const auto b = t.get("b"); + BOOST_CHECK_CLOSE(b, 123.4, 1.0e-8); + + const auto bb = t.get("bb", 2.71828); + BOOST_CHECK_CLOSE(bb, 2.71828, 1.0e-8); + } + + { + const auto c = t.get("c"); + BOOST_CHECK_EQUAL(c, "hello"); + + const auto cc = t.get("cc", std::string { "world" }); + BOOST_CHECK_EQUAL(cc, "world"); + } + + { + const auto d = t.get("d"); + BOOST_CHECK_CLOSE(d, 12.34f, 1.0e-6f); + + const auto dd = t.get("dd", -1.618f); + BOOST_CHECK_CLOSE(dd, -1.618f, 1.0e-6f); + } +} + +BOOST_AUTO_TEST_CASE(Hierarchy) +{ + auto f = TempFile{}; + f.append(R"({ + "a" : { "b" : { "c" : 123 } } +} +)"); + + const auto t = Opm::PropertyTree { f.name() }; + { + const auto c = t.get("a.b.c"); + BOOST_CHECK_EQUAL(c, 123); + } + + { + const auto a = t.get_child("a"); + const auto c = a.get("b.c"); + BOOST_CHECK_EQUAL(c, 123); + } + + { + const auto a = t.get_child("a"); + const auto b = a.get_child("b"); + const auto c = b.get("c"); + BOOST_CHECK_EQUAL(c, 123); + BOOST_CHECK_CLOSE(a.get("b.d", 3.1415), 3.1415, 1.0e-8); + } + + { + const auto d_e_f = t.get_child_optional("d.e.f"); + BOOST_CHECK_MESSAGE(! d_e_f.has_value(), R"(Node "f" must not exist)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // Load_From_File