diff --git a/src/common/statefield.hh b/src/common/statefield.hh index 1358356..e9295d0 100644 --- a/src/common/statefield.hh +++ b/src/common/statefield.hh @@ -1,459 +1,487 @@ /** * file statefield.hh * * @author Till Junge * * @date 28 Feb 2018 * * @brief A state field is an abstraction of a field that can hold * current, as well as a chosen number of previous values. This is * useful for instance for internal state variables in plastic laws, * where a current, new, or trial state is computed based on its * previous state, and at convergence, this new state gets cycled into * the old, the old into the old-1 etc. The state field abstraction * helps doing this safely (i.e. only const references to the old * states are available, while the current state can be assigned * to/modified), and efficiently (i.e., no need to copy values from * new to old, we just cycle the labels). This file implements the * state field as well as state maps using the Field, FieldCollection * and FieldMap abstractions of µSpectre * * @section LICENSE * * Copyright © 2018 Till Junge * * µSpectre 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, or (at * your option) any later version. * * µSpectre 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 GNU Emacs; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef STATEFIELD_H #define STATEFIELD_H #include "common/field.hh" #include "common/utilities.hh" #include #include #include namespace muSpectre { template class StateFieldBase { public: using index_t = std::array; inline const index_t & get_indices() const {return this->indices;} protected: StateFieldBase(index_t indices):indices{indices}{}; index_t indices; ///< these are cycled through }; + // early declaration + template + class StateFieldMap; + namespace internal { template inline decltype(auto) build_fields_helper(std::string prefix, typename Field::Base::collection_t & collection, std::index_sequence) { auto get_field{[&prefix, &collection](size_t i) -> Field& { std::stringstream name_stream{}; name_stream << prefix << ", sub_field index " << i; return make_field(name_stream.str(), collection); }}; return std::tie(get_field(I)...); } /* ---------------------------------------------------------------------- */ template decltype(auto) build_indices(std::index_sequence) { return std::array{I...}; } } // internal /* ---------------------------------------------------------------------- */ template class StateField: public StateFieldBase { public: using Base = StateFieldBase; using FieldCollection = typename Field_t::Base::collection_t; using Field_p = typename Field_t::Field_p; using Fields_t = tuple_array; //! Default constructor StateField() = delete; /** * Constructor. The @param unique_prefix is used to create the * names of the fields that this abstraction creates in the * background */ inline StateField(std::string unique_prefix, FieldCollection & collection) : Base{internal::build_indices(std::make_index_sequence{})}, prefix{unique_prefix}, fields{internal::build_fields_helper (unique_prefix, collection, std::make_index_sequence{})} {} //! Copy constructor StateField(const StateField &other) = delete; //! Move constructor StateField(StateField &&other) = delete; //! Destructor virtual ~StateField() = default; //! Copy assignment operator StateField& operator=(const StateField &other) = delete; //! Move assignment operator StateField& operator=(StateField &&other) = delete; //! get (modifiable) current field inline StateField& current() { return this->fields[this->indices[0]]; } //! get (constant) previous field template inline const StateField& old() { static_assert(nb_steps_ago <= nb_memory, "you can't go that far inte the past"); static_assert(nb_steps_ago > 0, "Did you mean to call current()?"); return this->fields[this->indices[nb_steps_ago]]; } // //! factory function // template // friend StateFieldType& make_statefield(std::string unique_prefix, // CollectionType & collection, // Args&&... args); //! returns a `StateField` reference if `other is a compatible state field inline static StateField& check_ref(Base& other) { // the following triggers and exception if the fields are incompatible Field_t::check_ref(other.fields[0]); return static_cast (other); } //! returns a const `StateField` reference if `other` is a compatible state field inline static const StateField& check_ref(const Base& other) { // the following triggers and exception if the fields are incompatible Field_t::check_ref(other.fields[0]); return static_cast (other); } //! get naming prefix const std::string & get_prefix() const {return this->prefix;} //! get a ref to the `StateField` 's field collection const FieldCollection & get_collection() const { return std::get<0>(this->fields).get_collection();} //! get a ref to the `StateField` 's fields Fields_t & get_fields() { return this->fields; } + /** + * Pure convenience functions to get a MatrixFieldMap of + * appropriate dimensions mapped to this field. You can also + * create other types of maps, as long as they have the right + * fundamental type (T), the correct size (nbComponents), and + * memory (nb_memory). + */ + inline decltype(auto) get_map() { + using FieldMap = decltype(std::get<0>(this->fields).get_map()); + return StateFieldMap(*this); + } + + /** + * Pure convenience functions to get a MatrixFieldMap of + * appropriate dimensions mapped to this field. You can also + * create other types of maps, as long as they have the right + * fundamental type (T), the correct size (nbComponents), and + * memory (nb_memory). + */ + inline decltype(auto) get_const_map() { + using FieldMap = decltype(std::get<0>(this->fields).get_const_map()); + return StateFieldMap(*this); + } + /** * cycle the fields (current becomes old, old becomes older, * oldest becomes current) */ inline void cycle() { for (auto & val: this->indices) { val = (val+1)%(nb_memory+1); } } protected: std::string prefix; Fields_t fields; //!< container for the states private: }; namespace internal { template inline decltype(auto) build_maps_helper(Fields & fields, std::index_sequence) { return std::array{FieldMap(std::get(fields))...}; } } // internal /* ---------------------------------------------------------------------- */ template class StateFieldMap { public: /** * iterates over all pixels in the `muSpectre::FieldCollection` and * dereferences to a proxy giving access to the appropriate iterates * of the underlying `FieldMap` type. */ class iterator; using FieldCollection= typename FieldMap::Field::collection_t; using StateFieldBase_t = StateFieldBase; using ConstFieldMap_t = typename FieldMap::ConstMap; //! Default constructor StateFieldMap() = delete; //! constructor using a StateField template StateFieldMap(StateField & statefield) :collection{statefield.get_collection()}, statefield{statefield}, maps{internal::build_maps_helper (statefield.get_fields(), std::make_index_sequence{})}, const_maps{internal::build_maps_helper (statefield.get_fields(), std::make_index_sequence{})} { static_assert(std::is_base_of::value, "Not the right type of StateField ref"); } //! Copy constructor StateFieldMap(const StateFieldMap &other) = delete; //! Move constructor - StateFieldMap(StateFieldMap &&other) = delete; + StateFieldMap(StateFieldMap &&other) = default; //! Destructor virtual ~StateFieldMap() = default; //! Copy assignment operator StateFieldMap& operator=(const StateFieldMap &other) = delete; //! Move assignment operator StateFieldMap& operator=(StateFieldMap &&other) = delete; iterator begin() { return iterator(*this, 0);} iterator end() { return iterator(*this, this->collection.size());} protected: const FieldCollection & collection; //!< collection holding the field StateFieldBase_t & statefield; //!< ref to the field itself std::array maps;//!< refs to the addressable maps; //! const refs to the addressable maps; std::array const_maps; private: }; template class StateFieldMap::iterator { public: class StateWrapper; using Ccoord = typename FieldMap::Ccoord; using value_type = StateWrapper; using const_value_type = value_type; using pointer_type = value_type*; using difference_type = std::ptrdiff_t; using iterator_category = std::random_access_iterator_tag; using reference = StateWrapper; //! Default constructor iterator() = delete; iterator(StateFieldMap& map, size_t index = 0) :index{index}, map{map} {}; //! Copy constructor iterator(const iterator &other) = default; //! Move constructor iterator(iterator &&other) = default; //! Destructor virtual ~iterator() = default; //! Copy assignment operator iterator& operator=(const iterator &other) = default; //! Move assignment operator iterator& operator=(iterator &&other) = default; //! pre-increment inline iterator & operator++() { this->index++; return *this;} //! post-increment inline iterator operator++(int) { iterator curr{*this}; this->index++; return curr;} //! dereference inline value_type operator*() { return value_type(*this);} //! pre-decrement inline iterator & operator--() { this->index--; return *this;} //! post-decrement inline iterator operator--(int) { iterator curr{*this}; this->index--; return curr;} //! access subscripting inline value_type operator[](difference_type diff) { return value_type{iterator{this->map, this->index+diff}};} //! equality inline bool operator==(const iterator & other) const { return this->index == other.index; } //! inequality inline bool operator!=(const iterator & other) const { return this->index != other.index;} //! div. comparisons inline bool operator<(const iterator & other) const { return this->index < other.index; } //! div. comparisons inline bool operator<=(const iterator & other) const { return this->index <= other.index; } //! div. comparisons inline bool operator>(const iterator & other) const { return this->index > other.index; } //! div. comparisons inline bool operator>=(const iterator & other) const { return this->index >= other.index; } //! additions, subtractions and corresponding assignments inline iterator operator+(difference_type diff) const { return iterator{this->map, this-index + diff}; } //! additions, subtractions and corresponding assignments inline iterator operator-(difference_type diff) const { return iterator{this->map, this-index - diff};} //! additions, subtractions and corresponding assignments inline iterator& operator+=(difference_type diff) { this->index += diff; return *this;} //! additions, subtractions and corresponding assignments inline iterator& operator-=(difference_type diff) { this->index -= diff; return *this; } //! get pixel coordinates inline Ccoord get_ccoord() const { return this->map.collection.get_ccoord(this->index); } //! access the index inline const size_t & get_index() const {return this->index;} protected: size_t index; StateFieldMap& map; private: }; namespace internal { //! FieldMap is an `Eigen::Map` or `Eigen::TensorMap` here template std::array build_old_vals_helper(iterator& it, maps_t & maps, indices_t & indices, std::index_sequence) { return std::array{ maps[indices[I+1]][it.get_index()]...}; } template inline std::array build_old_vals(iterator& it, maps_t & maps, indices_t & indices) { return std::array{build_old_vals_helper (it, maps, indices, std::make_index_sequence{})}; } } // internal /* ---------------------------------------------------------------------- */ template class StateFieldMap::iterator::StateWrapper { public: using iterator = typename StateFieldMap::iterator; using Ccoord = typename iterator::Ccoord; using Map = typename FieldMap::reference; using ConstMap = typename FieldMap::const_reference; //! Default constructor StateWrapper() = delete; //! Copy constructor StateWrapper(const StateWrapper &other) = delete; //! Move constructor StateWrapper(StateWrapper &&other) = default; //! construct with `StateFieldMap::iterator` StateWrapper(iterator & it) :it{it}, current_val{it.map.maps[it.map.statefield.get_indices()[0]][it.index]}, old_vals(internal::build_old_vals (it, it.map.const_maps, it.map.statefield.get_indices())) { } //! Destructor virtual ~StateWrapper() = default; //! Copy assignment operator StateWrapper& operator=(const StateWrapper &other) = delete; //! Move assignment operator StateWrapper& operator=(StateWrapper &&other) = delete; inline Map& current() { return this->current_val; } template inline const ConstMap & old() const{ static_assert (nb_steps_ago <= nb_memory, "You have not stored that time step"); static_assert (nb_steps_ago > 0, "Did you mean to access the current value? If so, use " "current()"); return this->old_vals[nb_memory - nb_steps_ago]; } inline Ccoord get_ccoord() const { return this->it.get_ccoord(); } protected: iterator& it; Map current_val; std::array old_vals; private: }; } // muSpectre #endif /* STATEFIELD_H */ diff --git a/tests/test_statefields.cc b/tests/test_statefields.cc index 03f69f1..3edb450 100644 --- a/tests/test_statefields.cc +++ b/tests/test_statefields.cc @@ -1,131 +1,168 @@ /** * file test_statefields.cc * * @author Till Junge * * @date 01 Mar 2018 * * @brief Test the StateField abstraction and the associated maps * * @section LICENSE * * Copyright © 2018 Till Junge * * µSpectre 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, or (at * your option) any later version. * * µSpectre 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 GNU Emacs; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "common/field.hh" #include "common/field_collection.hh" #include "common/statefield.hh" #include "common/ccoord_operations.hh" #include "tests.hh" #include #include namespace muSpectre { template struct SF_Fixture { using FC_t = std::conditional_t, LocalFieldCollection>; using Field_t = TensorField; constexpr static size_t nb_mem{2}; constexpr static Dim_t sdim{DimS}; constexpr static Dim_t mdim{DimM}; constexpr static bool global{Global}; SF_Fixture() :fc{}, sf("prefix", fc), self{*this} {} FC_t fc; StateField sf; SF_Fixture & self; }; using typelist = boost::mpl::list, SF_Fixture< twoD, threeD, false>, SF_Fixture, SF_Fixture< twoD, twoD, true>, SF_Fixture< twoD, threeD, true>, SF_Fixture>; BOOST_FIXTURE_TEST_CASE_TEMPLATE(constructor_test, Fix, typelist, Fix) { BOOST_CHECK_EQUAL("prefix", Fix::sf.get_prefix()); } namespace internal { template struct init{ static void run(Fixture_t & fix) { constexpr Dim_t dim{std::remove_reference_t::sdim}; fix.fc.initialise(CcoordOps::get_cube(3)); } }; template struct init{ static void run(Fixture_t & fix) { constexpr Dim_t dim{std::remove_reference_t::sdim}; CcoordOps::Pixels pixels(CcoordOps::get_cube(3)); for (auto && pix: pixels) { fix.fc.add_pixel(pix); } fix.fc.initialise(); } }; } // internal + BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_iteration, Fix, typelist, Fix) { internal::init::run(Fix::self); constexpr Dim_t mdim{Fix::mdim}; constexpr bool verbose{false}; using StateFMap = StateFieldMap< MatrixFieldMap, Fix::nb_mem>; StateFMap matrix_map(Fix::sf); for (size_t i = 0; i < Fix::nb_mem+1; ++i) { for (auto && wrapper: matrix_map) { wrapper.current() += (i+1)*wrapper.current().Identity(); if (verbose) { std::cout << "pixel " << wrapper.get_ccoord() << ", memory cycle " << i << std::endl; std::cout << wrapper.current() << std::endl; std::cout << wrapper.old() << std::endl; std::cout << wrapper.template old<2>() << std::endl << std::endl; } } Fix::sf.cycle(); } for (auto && wrapper: matrix_map) { auto I{wrapper.current().Identity()}; Real error{(wrapper.current() - I).norm()}; BOOST_CHECK_LT(error, tol); error = (wrapper.old() - 3*I).norm(); BOOST_CHECK_LT(error, tol); error = (wrapper.template old<2>() - 2* I).norm(); BOOST_CHECK_LT(error, tol); } } + BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_default_map, Fix, typelist, Fix) { + internal::init::run(Fix::self); + + constexpr bool verbose{false}; + auto matrix_map{Fix::sf.get_map()}; + + for (size_t i = 0; i < Fix::nb_mem+1; ++i) { + for (auto && wrapper: matrix_map) { + wrapper.current() += (i+1)*wrapper.current().Identity(); + if (verbose) { + std::cout << "pixel " << wrapper.get_ccoord() << ", memory cycle " << i << std::endl; + std::cout << wrapper.current() << std::endl; + std::cout << wrapper.old() << std::endl; + std::cout << wrapper.template old<2>() << std::endl << std::endl; + } + } + Fix::sf.cycle(); + } + + auto matrix_const_map{Fix::sf.get_const_map()}; + + for (auto && wrapper: matrix_const_map) { + auto I{wrapper.current().Identity()}; + Real error{(wrapper.current() - I).norm()}; + BOOST_CHECK_LT(error, tol); + + error = (wrapper.old() - 3*I).norm(); + BOOST_CHECK_LT(error, tol); + + error = (wrapper.template old<2>() - 2* I).norm(); + BOOST_CHECK_LT(error, tol); + + } + + } + } // muSpectre