From 306eecc6d1348c150d90b5106b87dc50022b7124 Mon Sep 17 00:00:00 2001 From: Jean-Paul Pelteret Date: Mon, 1 Apr 2019 21:59:29 +0200 Subject: [PATCH] Add a class to perform GeneralDataStorage --- .../minor/20190402LucaHeltaiJean-PaulPelteret | 5 + .../deal.II/algorithms/general_data_storage.h | 473 ++++++++++++++++++ source/algorithms/CMakeLists.txt | 1 + source/algorithms/general_data_storage.cc | 61 +++ tests/algorithms/general_data_storage_01.cc | 228 +++++++++ .../general_data_storage_01.debug.output | 74 +++ .../general_data_storage_01.release.output | 24 + 7 files changed, 866 insertions(+) create mode 100644 doc/news/changes/minor/20190402LucaHeltaiJean-PaulPelteret create mode 100644 include/deal.II/algorithms/general_data_storage.h create mode 100644 source/algorithms/general_data_storage.cc create mode 100644 tests/algorithms/general_data_storage_01.cc create mode 100644 tests/algorithms/general_data_storage_01.debug.output create mode 100644 tests/algorithms/general_data_storage_01.release.output diff --git a/doc/news/changes/minor/20190402LucaHeltaiJean-PaulPelteret b/doc/news/changes/minor/20190402LucaHeltaiJean-PaulPelteret new file mode 100644 index 0000000000..942968300a --- /dev/null +++ b/doc/news/changes/minor/20190402LucaHeltaiJean-PaulPelteret @@ -0,0 +1,5 @@ +New: The GeneralDataStorage class facilitates the storage of any general data. +It offers the ability to store any amount of data, of any type, which is then +made accessible by an identifier string. +
+(Luca Heltai, Jean-Paul Pelteret, 2019/04/02) diff --git a/include/deal.II/algorithms/general_data_storage.h b/include/deal.II/algorithms/general_data_storage.h new file mode 100644 index 0000000000..ed29057761 --- /dev/null +++ b/include/deal.II/algorithms/general_data_storage.h @@ -0,0 +1,473 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 2019 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + +#ifndef dealii_algorithms_general_data_storage_h +#define dealii_algorithms_general_data_storage_h + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +DEAL_II_NAMESPACE_OPEN + + +/** + * This class facilitates the storage of any general data. + * It offers a mechanism to store any amount of data, of any type, + * which is then made accessible by an identifier string. + * + * When using this class, please cite + * + * @code{.bib} + * @article{SartoriGiulianiBardelloni-2018-a, + * Author = {Sartori, Alberto and Giuliani, Nicola and + * Bardelloni, Mauro and Heltai, Luca}, + * Journal = {SoftwareX}, + * Pages = {318--327}, + * Title = {{deal2lkit: A toolkit library for high performance + * programming in deal.II}}, + * Volume = {7}, + * Year = {2018}} + * @endcode + * + * @author Luca Heltai, Jean-Paul Pelteret, 2019 + */ +class GeneralDataStorage : public Subscriptor +{ +public: + /** + * Default constructor. + */ + GeneralDataStorage() = default; + + /** + * Copy constructor. + */ + GeneralDataStorage(const GeneralDataStorage &) = default; + + /** + * Move constructor. + */ + GeneralDataStorage(GeneralDataStorage &&) = default; + + /** + * Number of objects stored by this class instance. + */ + std::size_t + size() const; + + /** + * Merge the contents of @p other_data with this object. + */ + void + merge(const GeneralDataStorage &other_data); + + /** + * Print the contents of the internal cache to the @p stream. + * + * Each key and value pair in the @p any_data map are printed on an + * individual line, with the std::string<\tt> key listed first + * followed by the demangled type_id of the associated mapped + * type. + */ + template + void + print_info(Stream &stream); + + /** + * Clear all data stored in this class instance. + * + * After this function is called, all copied data owned by this class will + * go out of scope. Furthermore, all scoping requirements for data referenced + * by this class instance will be lifted. + * + * To clarify this point, consider the following small example: + * + * @code + * GeneralDataStorage data; + * + * { + * const double some_number = ...; + * data.add_unique_reference("value", some_number); + * + * // Adding either of these next two lines could fix the + * // issue, by removing the association of some_number with data: + * // data.remove_object_with_name("value"); + * // data.reset(); + * } // some_number goes out of scope here + * + * const double some_other_number + * = data.get_object_with_name("value"); // Invalid call + * @endcode + * + * In the code above, the @p data object has a longer scope than + * some_number. By the time we fetch the "value" from + * @p data , the reference to @p some_number is no longer valid. + * + * Similarly, for data copied into a GeneralDataStorage object one should + * consider the scope under which it remains valid: + * + * @code + * double* ptr_to_some_number = null_ptr; + * + * { + * GeneralDataStorage data; + * const double some_number = ...; + * data.add_unique_copy("value", some_number); + * + * ptr_to_some_number = &(data.get_object_with_name("value")); + * } // The copy to some_number goes out of scope here + * + * const double some_other_number + * = *ptr_to_some_number; // Invalid call + * @endcode + * + * Similar to the first example, we must be concious of the fact that the + * copies of any @p Type stored by @p data only remains valid while the + * GeneralDataStorage instance in which it is stored is alive. + * + * Furthermore, as elucidated in the last example, the copy of the + * class instance (owned by GeneralDataStorage) that is being pointed to + * is no longer alive when the reset() function is called, or when it is + * removed via a call to remove_object_with_name(). + * + * @code + * GeneralDataStorage data; + * double* ptr_to_some_number = null_ptr; + * + * { + * const double some_number = ...; + * data.add_unique_copy("value", some_number); + * + * ptr_to_some_number = &(data.get_object_with_name("value")); + * + * // The copy to some_number would go out of scope when either of + * // following two calls are made: + * data.remove_object_with_name("value"); + * data.reset(); + * } + * + * const double some_other_number + * = *ptr_to_some_number; // Invalid call + * @endcode + */ + void + reset(); + + /** + * @name Data storage and access + */ + //@{ + + /** + * Store internally a copy of the given object. The copied object is + * owned by this class, and is accessible via reference through the + * get_object_with_name() method. + * + * This function ensures that no @p entry with the given @name is + * already stored by this class instance. + */ + template + void + add_unique_copy(const std::string &name, const Type &entry); + + /** + * Store internally a copy of the given object. The copied object is + * owned by this class, and is accessible via reference through the + * get_object_with_name() method. + * + * This function does not perform any checks to ensure that the @p entry + * with the given @name is already stored by this class instance. If the + * @p name does in fact point to existing data, then this is overwritten. + */ + template + void + add_or_overwrite_copy(const std::string &name, const Type &entry); + + /** + * Add a reference to an already existing object. The object is not + * owned by this class, and the user has to guarantee that the + * referenced object lives longer than this class instance. The stored + * reference is accessible through the get_object_with_name() method. + * + * This function ensures that no @p entry with the given @name is + * already stored by this class instance. + */ + template + void + add_unique_reference(const std::string &name, Type &entry); + + /** + * Add a reference to an already existing object. The object is not + * owned by this class, and the user has to guarantee that the + * referenced object lives longer than this class instance. The stored + * reference is accessible through the get_object_with_name() method. + * + * This function does not perform any checks to ensure that the @p entry + * with the given @name is already stored by this class instance. If the + * @p name does in fact point to existing data, then this is overwritten. + */ + template + void + add_or_overwrite_reference(const std::string &name, Type &entry); + + /** + * Return a reference to the object with given name. If the object does + * not exist, then the input @p arguments will be used to construct an object + * of the given @p Type and a reference to this new object then be returned. + * + * A copy of an object of type @p Type , which is owned by this class + * instance, is generated by calling its constructor with the given set of + * arguments. For this function, the @p arguments are passed as + * lvalue references. + */ + template + Type & + get_or_add_object_with_name(const std::string &name, Args &... arguments); + + /** + * Return a reference to the object with given name. If the object does + * not exist, then the input @p arguments will be used to construct an object + * of the given @p Type and a reference to this new object then be returned. + * + * A copy of an object of type @p Type , which is owned by this class + * instance, is generated by calling its constructor with the given set of + * arguments. In contrast to the previous function of the same name, for + * this function the @p arguments are passed as rvalue references. + */ + template + Type & + get_or_add_object_with_name(const std::string &name, Args &&... arguments); + + /** + * Return a reference to the object with given name. + * + * This function throws an exception if either an object with the given name + * is not stored in this class, or if the object with the given name is + * neither of the exact specified @p Type nor a pointer to the @p Type. + */ + template + Type & + get_object_with_name(const std::string &name); + + /** + * Return a constant reference to the object with the given name. + * + * This function throws an exception if either an object with the given name + * is not stored in this class, or if the object with the given name is + * neither of the exact specified @p Type nor a pointer to the @p Type. + */ + template + const Type & + get_object_with_name(const std::string &name) const; + + /** + * Find out if we store an object with given name. + */ + bool + stores_object_with_name(const std::string &name) const; + + /** + * Find out if we store an object with given name. + */ + void + remove_object_with_name(const std::string &name); + + //@} + + /** + * An entry with this name does not exist in the internal boost::any map. + */ + DeclException1(ExcNameNotFound, + std::string, + << "No entry with the name " << arg1 << " exists."); + + /** + * An entry with this name does not exist in the internal boost::any map. + */ + DeclException1(ExcNameHasBeenFound, + std::string, + << "An entry with the name " << arg1 << " already exists."); + + /** + * The requested type and the stored type are different. + */ + DeclException3(ExcTypeMismatch, + std::string, + const char *, + const char *, + << "The stored type for entry with name \"" << arg1 << "\" is " + << arg2 << " but you requested type " << arg3 << "."); + +private: + /** + * Arbitrary user data, identified by a string. + */ + std::map any_data; +}; + + +/*----------------- Inline and template methods -----------------*/ + + +#ifndef DOXYGEN + + +template +void +GeneralDataStorage::print_info(Stream &os) +{ + for (auto it : any_data) + { + os << it.first << '\t' << '\t' + << boost::core::demangle(it.second.type().name()) << std::endl; + } +} + + +template +void +GeneralDataStorage::add_unique_copy(const std::string &name, const Type &entry) +{ + Assert(!stores_object_with_name(name), ExcNameHasBeenFound(name)); + add_or_overwrite_copy(name, entry); +} + + +template +void +GeneralDataStorage::add_or_overwrite_copy(const std::string &name, + const Type & entry) +{ + any_data[name] = entry; +} + + +template +void +GeneralDataStorage::add_unique_reference(const std::string &name, Type &entry) +{ + Assert(!stores_object_with_name(name), ExcNameHasBeenFound(name)); + add_or_overwrite_reference(name, entry); +} + + +template +void +GeneralDataStorage::add_or_overwrite_reference(const std::string &name, + Type & entry) +{ + Type *ptr = &entry; + any_data[name] = ptr; +} + + +template +Type & +GeneralDataStorage::get_object_with_name(const std::string &name) +{ + Assert(stores_object_with_name(name), ExcNameNotFound(name)); + + Type *p = nullptr; + + if (any_data[name].type() == typeid(Type *)) + { + p = boost::any_cast(any_data[name]); + } + else if (any_data[name].type() == typeid(Type)) + { + p = boost::any_cast(&any_data[name]); + } + else + { + Assert(false, + ExcTypeMismatch(name, + any_data[name].type().name(), + typeid(Type).name())); + } + + return *p; +} + + +template +const Type & +GeneralDataStorage::get_object_with_name(const std::string &name) const +{ + AssertThrow(stores_object_with_name(name), ExcNameNotFound(name)); + + const auto it = any_data.find(name); + + if (it->second.type() == typeid(Type *)) + { + const Type *p = boost::any_cast(it->second); + return *p; + } + else if (it->second.type() == typeid(Type)) + { + const Type *p = boost::any_cast(&it->second); + return *p; + } + else + { + Assert(false, + ExcTypeMismatch(name, + it->second.type().name(), + typeid(Type).name())); + const Type *p = nullptr; + return *p; + } +} + + +template +Type & +GeneralDataStorage::get_or_add_object_with_name(const std::string &name, + Args &... arguments) +{ + if (!stores_object_with_name(name)) + add_unique_copy(name, Type(arguments...)); + + return get_object_with_name(name); +} + + +template +Type & +GeneralDataStorage::get_or_add_object_with_name(const std::string &name, + Args &&... arguments) +{ + if (!stores_object_with_name(name)) + add_unique_copy(name, Type(std::forward(arguments)...)); + + return get_object_with_name(name); +} + + +#endif // DOXYGEN + + +DEAL_II_NAMESPACE_CLOSE + +#endif // dealii_algorithms_general_data_storage_h diff --git a/source/algorithms/CMakeLists.txt b/source/algorithms/CMakeLists.txt index 2eb6e268db..030b6f36cd 100644 --- a/source/algorithms/CMakeLists.txt +++ b/source/algorithms/CMakeLists.txt @@ -16,6 +16,7 @@ INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_BINARY_DIR}) SET(_src + general_data_storage.cc operator.cc timestep_control.cc ) diff --git a/source/algorithms/general_data_storage.cc b/source/algorithms/general_data_storage.cc new file mode 100644 index 0000000000..85ff371299 --- /dev/null +++ b/source/algorithms/general_data_storage.cc @@ -0,0 +1,61 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 2019 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + + +#include + + +DEAL_II_NAMESPACE_OPEN + + +std::size_t +GeneralDataStorage::size() const +{ + return any_data.size(); +} + + +void +GeneralDataStorage::merge(const GeneralDataStorage &other) +{ + any_data.insert(other.any_data.begin(), other.any_data.end()); +} + + +void +GeneralDataStorage::reset() +{ + any_data.clear(); +} + + +bool +GeneralDataStorage::stores_object_with_name(const std::string &name) const +{ + return any_data.find(name) != any_data.end(); +} + + +void +GeneralDataStorage::remove_object_with_name(const std::string &name) +{ + const auto it = any_data.find(name); + if (it != any_data.end()) + any_data.erase(it); +} + + + +DEAL_II_NAMESPACE_CLOSE diff --git a/tests/algorithms/general_data_storage_01.cc b/tests/algorithms/general_data_storage_01.cc new file mode 100644 index 0000000000..fc421e7895 --- /dev/null +++ b/tests/algorithms/general_data_storage_01.cc @@ -0,0 +1,228 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 2019 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + + +// Test the core functionality of the GeneralDataStorage class + + +#include + +#include + +#include "../tests.h" + +int +main() +{ + initlog(); + + GeneralDataStorage data; + + deallog << "Add by copy" << std::endl; + { + data.reset(); + + // Create new data instance + const double val_1 = 1.0; + data.add_unique_copy("value", val_1); + const double &val_2 = data.get_object_with_name("value"); + Assert(data.stores_object_with_name("value"), ExcInternalError()); + Assert(val_2 == val_1, ExcInternalError()); + Assert(&val_2 != &val_1, ExcInternalError()); + + // Allowed overwrite of existing data + const double val_3 = 2.0; + data.add_or_overwrite_copy("value", val_3); + const double &val_4 = data.get_object_with_name("value"); + Assert(data.stores_object_with_name("value"), ExcInternalError()); + Assert(val_4 == val_3, ExcInternalError()); + Assert(&val_4 != &val_3, ExcInternalError()); + + // Create new data instance using alternative function + const double val_5 = 3.0; + data.add_or_overwrite_copy("value_2", val_5); + const double &val_6 = data.get_object_with_name("value_2"); + Assert(data.stores_object_with_name("value_2"), ExcInternalError()); + Assert(val_6 == val_5, ExcInternalError()); + Assert(&val_6 != &val_5, ExcInternalError()); + + deallog << "Size: " << data.size() << std::endl; + } + + deallog << "Add by reference" << std::endl; + { + data.reset(); + + // Create new data instance + const double val_1 = 1.0; + data.add_unique_reference("value", val_1); + const double &val_2 = data.get_object_with_name("value"); + Assert(data.stores_object_with_name("value"), ExcInternalError()); + Assert(val_2 == val_1, ExcInternalError()); + Assert(&val_2 == &val_1, ExcInternalError()); + + // Allowed overwrite of existing data + const double val_3 = 2.0; + data.add_or_overwrite_reference("value", val_3); + const double &val_4 = data.get_object_with_name("value"); + Assert(data.stores_object_with_name("value"), ExcInternalError()); + Assert(val_4 == val_3, ExcInternalError()); + Assert(&val_4 == &val_3, ExcInternalError()); + + // Create new data instance using alternative function + const double val_5 = 3.0; + data.add_or_overwrite_reference("value_2", val_5); + const double &val_6 = data.get_object_with_name("value_2"); + Assert(data.stores_object_with_name("value_2"), ExcInternalError()); + Assert(val_6 == val_5, ExcInternalError()); + Assert(&val_6 == &val_5, ExcInternalError()); + + deallog << "Size: " << data.size() << std::endl; + } + + deallog << "Add or construct" << std::endl; + { + data.reset(); + + using Type = std::pair; + + // Create new data instance + const Type &val_1 = + data.get_or_add_object_with_name("value", 1.0, 2.0); + Assert(data.stores_object_with_name("value"), ExcInternalError()); + Assert(val_1 == Type({1.0, 2.0}), ExcInternalError()); + + // Should not overwrite existing data + const Type &val_2 = + data.get_or_add_object_with_name("value", Type(3.0, 4.0)); + Assert(data.stores_object_with_name("value"), ExcInternalError()); + Assert(val_2 == Type({1.0, 2.0}), ExcInternalError()); + + deallog << "Size: " << data.size() << std::endl; + } + + deallog << "Merge" << std::endl; + { + data.reset(); + + const double val_1 = 1.0; + data.add_unique_copy("value", val_1); + + GeneralDataStorage data_2; + data_2.add_unique_copy("value", 2.0); // Duplicate + data_2.add_unique_copy("value_2", 3.0); + + deallog << "Data pre-merge:" << std::endl; + data.print_info(deallog); + deallog << "Size: " << data.size() << std::endl; + deallog << "Data 2 pre-merge:" << std::endl; + data_2.print_info(deallog); + deallog << "Size: " << data_2.size() << std::endl; + + data.merge(data_2); + + deallog << "Data post-merge:" << std::endl; + data.print_info(deallog); + deallog << "Size: " << data.size() << std::endl; + } + + + deal_II_exceptions::disable_abort_on_exception(); + + deallog << "Try to overwrite existing entry: Copy" << std::endl; + { + data.reset(); + + const double val_1 = 1.0; + data.add_unique_copy("value", val_1); + + try + { + const double val_2 = 1.0; + data.add_unique_copy("value", val_2); + } + catch (const GeneralDataStorage::ExcNameHasBeenFound &exc) + { + deallog << exc.what() << std::endl; + } + } + + deallog << "Try to overwrite existing entry: Reference" << std::endl; + { + data.reset(); + + const double val_1 = 1.0; + data.add_unique_reference("value", val_1); + + try + { + const double val_2 = 2.0; + data.add_unique_reference("value", val_2); + } + catch (const GeneralDataStorage::ExcNameHasBeenFound &exc) + { + deallog << exc.what() << std::endl; + } + } + + deallog << "Fetch non-existing entry" << std::endl; + { + data.reset(); + + try + { + data.get_object_with_name("value"); + } + catch (const GeneralDataStorage::ExcNameNotFound &exc) + { + deallog << exc.what() << std::endl; + } + } + + deallog << "Access removed entry (reference)" << std::endl; + { + data.reset(); + + const double val_1 = 1.0; + data.add_unique_reference("value", val_1); + data.remove_object_with_name("value"); + + try + { + data.get_object_with_name("value"); + } + catch (const GeneralDataStorage::ExcNameNotFound &exc) + { + deallog << exc.what() << std::endl; + } + } + + deallog << "Access removed entry (copy)" << std::endl; + { + data.reset(); + + data.add_unique_copy("value", 1.0); + data.remove_object_with_name("value"); + + try + { + data.get_object_with_name("value"); + } + catch (const GeneralDataStorage::ExcNameNotFound &exc) + { + deallog << exc.what() << std::endl; + } + } +} diff --git a/tests/algorithms/general_data_storage_01.debug.output b/tests/algorithms/general_data_storage_01.debug.output new file mode 100644 index 0000000000..4583c5e216 --- /dev/null +++ b/tests/algorithms/general_data_storage_01.debug.output @@ -0,0 +1,74 @@ + +DEAL::Add by copy +DEAL::Size: 2 +DEAL::Add by reference +DEAL::Size: 2 +DEAL::Add or construct +DEAL::Size: 1 +DEAL::Merge +DEAL::Data pre-merge: +DEAL::value double +DEAL::Size: 1 +DEAL::Data 2 pre-merge: +DEAL::value double +DEAL::value_2 double +DEAL::Size: 2 +DEAL::Data post-merge: +DEAL::value double +DEAL::value_2 double +DEAL::Size: 2 +DEAL::Try to overwrite existing entry: Copy +DEAL:: +-------------------------------------------------------- +An error occurred in file in function + void dealii::GeneralDataStorage::add_unique_copy(const std::string &, const Type &) [Type = double] +The violated condition was: + !stores_object_with_name(name) +Additional information: + An entry with the name value already exists. +-------------------------------------------------------- + +DEAL::Try to overwrite existing entry: Reference +DEAL:: +-------------------------------------------------------- +An error occurred in file in function + void dealii::GeneralDataStorage::add_unique_reference(const std::string &, Type &) [Type = const double] +The violated condition was: + !stores_object_with_name(name) +Additional information: + An entry with the name value already exists. +-------------------------------------------------------- + +DEAL::Fetch non-existing entry +DEAL:: +-------------------------------------------------------- +An error occurred in file in function + Type &dealii::GeneralDataStorage::get_object_with_name(const std::string &) [Type = double] +The violated condition was: + stores_object_with_name(name) +Additional information: + No entry with the name value exists. +-------------------------------------------------------- + +DEAL::Access removed entry (reference) +DEAL:: +-------------------------------------------------------- +An error occurred in file in function + Type &dealii::GeneralDataStorage::get_object_with_name(const std::string &) [Type = double] +The violated condition was: + stores_object_with_name(name) +Additional information: + No entry with the name value exists. +-------------------------------------------------------- + +DEAL::Access removed entry (copy) +DEAL:: +-------------------------------------------------------- +An error occurred in file in function + Type &dealii::GeneralDataStorage::get_object_with_name(const std::string &) [Type = double] +The violated condition was: + stores_object_with_name(name) +Additional information: + No entry with the name value exists. +-------------------------------------------------------- + diff --git a/tests/algorithms/general_data_storage_01.release.output b/tests/algorithms/general_data_storage_01.release.output new file mode 100644 index 0000000000..b10d09135f --- /dev/null +++ b/tests/algorithms/general_data_storage_01.release.output @@ -0,0 +1,24 @@ + +DEAL::Add by copy +DEAL::Size: 2 +DEAL::Add by reference +DEAL::Size: 2 +DEAL::Add or construct +DEAL::Size: 1 +DEAL::Merge +DEAL::Data pre-merge: +DEAL::value double +DEAL::Size: 1 +DEAL::Data 2 pre-merge: +DEAL::value double +DEAL::value_2 double +DEAL::Size: 2 +DEAL::Data post-merge: +DEAL::value double +DEAL::value_2 double +DEAL::Size: 2 +DEAL::Try to overwrite existing entry: Copy +DEAL::Try to overwrite existing entry: Reference +DEAL::Fetch non-existing entry +DEAL::Access removed entry (reference) +DEAL::Access removed entry (copy) -- 2.39.5