diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c91f85..0fc3b64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,50 +1,55 @@ repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.10.0 hooks: - id: black args: [--safe, --quiet, --line-length=100] - exclude: ^docs/doxystyle - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: trailing-whitespace - exclude: ^docs/doxystyle - id: end-of-file-fixer - exclude: ^docs/doxystyle - id: check-yaml - id: debug-statements - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.3.0 + rev: v2.4.0 hooks: - id: pretty-format-yaml args: [--autofix, --indent, '2'] - repo: https://github.com/humitos/mirrors-autoflake.git rev: v1.1 hooks: - id: autoflake args: [--in-place, --remove-unused-variable, --remove-all-unused-imports] - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.8.5 hooks: - id: reorder-python-imports - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v3.1.0 hooks: - id: pyupgrade args: [--py36-plus] - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 5.0.4 hooks: - id: flake8 args: [--max-line-length=100] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.1 + rev: v2.1.0 hooks: - id: setup-cfg-fmt +- repo: https://github.com/tdegeus/cpp_comment_format + rev: v0.2.0 + hooks: + - id: cpp_comment_format - repo: https://github.com/pocc/pre-commit-hooks rev: v1.3.5 hooks: - id: clang-format args: [-i] - exclude: ^docs/doxystyle +- repo: https://github.com/tdegeus/conda_envfile + rev: v0.4.1 + hooks: + - id: conda_envfile_parse + files: environment.yaml diff --git a/include/GooseFEM/Allocate.h b/include/GooseFEM/Allocate.h index d1115b1..5095de3 100644 --- a/include/GooseFEM/Allocate.h +++ b/include/GooseFEM/Allocate.h @@ -1,247 +1,247 @@ /** -Common allocation methods. - -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Common allocation methods. + * + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ALLOCATE_H #define GOOSEFEM_ALLOCATE_H #include "config.h" namespace GooseFEM { template inline void asTensor(const T& arg, R& ret); namespace detail { /** -Check that two shapes partly overlap. If `s` has more dimensions that `t` the excess dimensions -of `s` are ignored and the first `t.size()` dimensions are checked for equality. -*/ + * Check that two shapes partly overlap. If `s` has more dimensions that `t` the excess dimensions + * of `s` are ignored and the first `t.size()` dimensions are checked for equality. + */ template inline bool has_shape_begin(const T& t, const S& s) { return s.dimension() >= t.dimension() && std::equal(t.shape().cbegin(), t.shape().cend(), s.shape().begin()); } /** -Static identification of an std::array -*/ + * Static identification of an std::array + */ template struct is_std_array : std::false_type { }; template struct is_std_array> : std::true_type { }; /** -Helper for std_array_size -*/ + * Helper for std_array_size + */ template auto std_array_size_impl(const std::array&) -> std::integral_constant; /** -Get the size of an std:array (T::size is not static) -*/ + * Get the size of an std:array (T::size is not static) + */ template using std_array_size = decltype(std_array_size_impl(std::declval())); /** -Return as std::array. -*/ + * Return as std::array. + */ template std::array to_std_array(const I (&shape)[L]) { std::array r; std::copy(&shape[0], &shape[0] + L, r.begin()); return r; } /** -asTensor for array_type::array. -*/ + * asTensor for array_type::array. + */ template struct asTensor_write { static void impl(const T& arg, R& ret) { GOOSEFEM_ASSERT(arg.dimension() <= ret.dimension()); GOOSEFEM_ASSERT(detail::has_shape_begin(arg, ret)); using strides_type = typename T::strides_type::value_type; std::vector ret_strides(ret.dimension()); std::copy(arg.strides().begin(), arg.strides().end(), ret_strides.begin()); std::fill(ret_strides.begin() + arg.dimension(), ret_strides.end(), 0); ret = xt::strided_view( arg, ret.shape(), std::move(ret_strides), 0ul, xt::layout_type::dynamic); } }; /** -asTensor for xt::tensor. -*/ + * asTensor for xt::tensor. + */ template struct asTensor_write< T, R, typename std::enable_if_t::value && xt::has_fixed_rank_t::value>> { static void impl(const T& arg, R& ret) { static_assert(T::rank <= R::rank, "Return must be fixed rank too"); GOOSEFEM_ASSERT(detail::has_shape_begin(arg, ret)); using strides_type = typename T::strides_type::value_type; std::array ret_strides; std::copy(arg.strides().begin(), arg.strides().end(), ret_strides.begin()); std::fill(ret_strides.begin() + T::rank, ret_strides.end(), 0); ret = xt::strided_view( arg, ret.shape(), std::move(ret_strides), 0ul, xt::layout_type::dynamic); } }; /** -AsTensor for array_type::array. -*/ + * AsTensor for array_type::array. + */ template struct asTensor_allocate { static auto impl(const T& arg, const S& shape) { using value_type = typename T::value_type; size_t dim = arg.dimension(); size_t rank = shape.size(); std::vector ret_shape(dim + rank); std::copy(arg.shape().begin(), arg.shape().end(), ret_shape.begin()); std::copy(shape.begin(), shape.end(), ret_shape.begin() + dim); xt::xarray ret(ret_shape); GooseFEM::asTensor(arg, ret); return ret; } }; /** -AsTensor for xt::tensor. -*/ + * AsTensor for xt::tensor. + */ template struct asTensor_allocate::value>> { static auto impl(const T& arg, const S& shape) { using value_type = typename T::value_type; static constexpr size_t dim = T::rank; static constexpr size_t rank = std_array_size::value; std::array ret_shape; std::copy(arg.shape().begin(), arg.shape().end(), ret_shape.begin()); std::copy(shape.begin(), shape.end(), ret_shape.begin() + dim); array_type::tensor ret = xt::empty(ret_shape); detail::asTensor_write, decltype(ret)>::impl(arg, ret); return ret; } }; } // namespace detail /** -"Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all -tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, i, j]``). - -\param arg An array with scalars. -\param ret Corresponding array with tensors. -*/ + * "Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all + * tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, i, j]``). + * + * @param arg An array with scalars. + * @param ret Corresponding array with tensors. + */ template inline void asTensor(const T& arg, R& ret) { detail::asTensor_write, std::decay_t>::impl(arg, ret); } /** -"Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all -tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, i, j]``). - -\param arg An array with scalars. -\param shape The shape of the added tensor dimensions (e.g.: ``[i, j]``). -\return Corresponding array with tensors. -*/ + * "Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all + * tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, i, j]``). + * + * @param arg An array with scalars. + * @param shape The shape of the added tensor dimensions (e.g.: ``[i, j]``). + * @return Corresponding array with tensors. + */ template inline auto AsTensor(const T& arg, const S& shape) { return detail::asTensor_allocate, std::decay_t>::impl(arg, shape); } /** -\copydoc AsTensor(const T& arg, const S& shape) -*/ + * @copydoc AsTensor(const T& arg, const S& shape) + */ template inline auto AsTensor(const T& arg, const I (&shape)[L]) { auto s = detail::to_std_array(shape); return detail::asTensor_allocate, decltype(s)>::impl(arg, s); } /** -"Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all -tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, n, n]``). - -\tparam rank Number of tensor dimensions (number of dimensions to add to the input). -\param arg An array with scalars. -\param n The shape along each of the added dimensions. -\return Corresponding array with tensors. -*/ + * "Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all + * tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, n, n]``). + * + * @tparam rank Number of tensor dimensions (number of dimensions to add to the input). + * @param arg An array with scalars. + * @param n The shape along each of the added dimensions. + * @return Corresponding array with tensors. + */ template inline auto AsTensor(const T& arg, size_t n) { std::array shape; std::fill(shape.begin(), shape.end(), n); return detail::asTensor_allocate, decltype(shape)>::impl(arg, shape); } /** -"Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all -tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, n, n]``). - -\param rank Number of tensor dimensions (number of dimensions to add to the input). -\param arg An array with scalars. -\param n The shape along each of the added dimensions. -\return Corresponding array with tensors. -*/ + * "Broadcast" a scalar stored in an array (e.g. ``[r, s]``) to the same scalar of all + * tensor components of a tensor of certain rank (e.g. for rank 2: ``[r, s, n, n]``). + * + * @param rank Number of tensor dimensions (number of dimensions to add to the input). + * @param arg An array with scalars. + * @param n The shape along each of the added dimensions. + * @return Corresponding array with tensors. + */ template inline auto AsTensor(size_t rank, const T& arg, size_t n) { std::vector shape(rank); std::fill(shape.begin(), shape.end(), n); return detail::asTensor_allocate, decltype(shape)>::impl(arg, shape); } /** -Zero-pad columns to a matrix until is that shape ``[m, 3]``. - -\param arg A "nodevec" (``arg.shape(1) <= 3``). -\return Corresponding "nodevec" in 3-d (``ret.shape(1) == 3``) -*/ + * Zero-pad columns to a matrix until is that shape ``[m, 3]``. + * + * @param arg A "nodevec" (``arg.shape(1) <= 3``). + * @return Corresponding "nodevec" in 3-d (``ret.shape(1) == 3``) + */ template inline T as3d(const T& arg) { GOOSEFEM_ASSERT(arg.dimension() == 2); GOOSEFEM_ASSERT(arg.shape(1) > 0 && arg.shape(1) < 4); if (arg.shape(1) == 3ul) { return arg; } T ret = xt::zeros(std::array{arg.shape(0), 3ul}); if (arg.shape(1) == 2ul) { xt::view(ret, xt::all(), xt::keep(0, 1)) = arg; } if (arg.shape(1) == 1ul) { xt::view(ret, xt::all(), xt::keep(0)) = arg; } return ret; } } // namespace GooseFEM #endif diff --git a/include/GooseFEM/Element.h b/include/GooseFEM/Element.h index 4acb7c5..3839d7d 100644 --- a/include/GooseFEM/Element.h +++ b/include/GooseFEM/Element.h @@ -1,1233 +1,1236 @@ /** -Convenience methods for integration point data. - -\file Element.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Convenience methods for integration point data. + * + * @file Element.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ELEMENT_H #define GOOSEFEM_ELEMENT_H #include "Allocate.h" #include "config.h" #include "detail.h" namespace GooseFEM { /** -Element quadrature and interpolation. -*/ + * Element quadrature and interpolation. + */ namespace Element { /** -Convert nodal vector with ("nodevec", shape:``[nnode, ndim]``) to nodal vector stored per element -("elemvec", shape: ``[nelem, nne, ndim]``). - -\param conn Connectivity. -\param nodevec "nodevec". -\return "elemvec". -*/ + * Convert nodal vector with ("nodevec", shape:``[nnode, ndim]``) to nodal vector stored per element + * ("elemvec", shape: ``[nelem, nne, ndim]``). + * + * @param conn Connectivity. + * @param nodevec "nodevec". + * @return "elemvec". + */ inline array_type::tensor asElementVector( const array_type::tensor& conn, const array_type::tensor& nodevec) { size_t nelem = conn.shape(0); size_t nne = conn.shape(1); size_t ndim = nodevec.shape(1); array_type::tensor elemvec = xt::empty({nelem, nne, ndim}); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { for (size_t m = 0; m < nne; ++m) { for (size_t i = 0; i < ndim; ++i) { elemvec(e, m, i) = nodevec(conn(e, m), i); } } } return elemvec; } /** -Assemble nodal vector stored per element ("elemvec", shape ``[nelem, nne, ndim]``) to nodal vector -("nodevec", shape ``[nnode, ndim]``). - -\param conn Connectivity. -\param elemvec "elemvec". -\return "nodevec". -*/ + * Assemble nodal vector stored per element ("elemvec", shape ``[nelem, nne, ndim]``) to nodal + * vector + * ("nodevec", shape ``[nnode, ndim]``). + * + * @param conn Connectivity. + * @param elemvec "elemvec". + * @return "nodevec". + */ inline array_type::tensor assembleNodeVector( const array_type::tensor& conn, const array_type::tensor& elemvec) { size_t nelem = conn.shape(0); size_t nne = conn.shape(1); size_t ndim = elemvec.shape(2); size_t nnode = xt::amax(conn)() + 1; GOOSEFEM_ASSERT(elemvec.shape(0) == nelem); GOOSEFEM_ASSERT(elemvec.shape(1) == nne); array_type::tensor nodevec = xt::zeros({nnode, ndim}); for (size_t e = 0; e < nelem; ++e) { for (size_t m = 0; m < nne; ++m) { for (size_t i = 0; i < ndim; ++i) { nodevec(conn(e, m), i) += elemvec(e, m, i); } } } return nodevec; } /** -Check that DOFs leave no holes. - -\param dofs DOFs ("nodevec") -\return ``true`` if there are no holds. -*/ + * Check that DOFs leave no holes. + * + * @param dofs DOFs ("nodevec") + * @return ``true`` if there are no holds. + */ template inline bool isSequential(const E& dofs) { size_t ndof = xt::amax(dofs)() + 1; array_type::tensor exists = xt::zeros({ndof}); for (auto& i : dofs) { exists[i]++; } for (auto& i : dofs) { if (exists[i] == 0) { return false; } } return true; } /** -Check that all of the matrices stored per elemmat (shape: ``[nelem, nne * ndim, nne * ndim]``) -are diagonal. - -\param elemmat Element-vectors ("elemmat") -\return ``true`` if all element matrices are diagonal. -*/ + * Check that all of the matrices stored per elemmat (shape: ``[nelem, nne * ndim, nne * ndim]``) + * are diagonal. + * + * @param elemmat Element-vectors ("elemmat") + * @return ``true`` if all element matrices are diagonal. + */ bool isDiagonal(const array_type::tensor& elemmat) { GOOSEFEM_ASSERT(elemmat.shape(1) == elemmat.shape(2)); size_t nelem = elemmat.shape(0); size_t N = elemmat.shape(1); double eps = std::numeric_limits::epsilon(); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { for (size_t i = 0; i < N; ++i) { for (size_t j = 0; j < N; ++j) { if (i != j) { if (std::abs(elemmat(e, i, j)) > eps) { return false; } } } } } return true; } /** -CRTP base class for quadrature. -*/ + * CRTP base class for quadrature. + */ template class QuadratureBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; /** - Number of elements. - - \return Scalar. - */ + * Number of elements. + * + * @return Scalar. + */ auto nelem() const { return derived_cast().m_nelem; } /** - Number of nodes per element. - - \return Scalar. - */ + * Number of nodes per element. + * + * @return Scalar. + */ auto nne() const { return D::s_nne; } /** - Number of dimensions for node vectors. - - \return Scalar. - */ + * Number of dimensions for node vectors. + * + * @return Scalar. + */ auto ndim() const { return D::s_ndim; } /** - Number of dimensions for integration point tensors. - - \return Scalar. - */ + * Number of dimensions for integration point tensors. + * + * @return Scalar. + */ auto tdim() const { return D::s_tdim; } /** - Number of integration points. - - \return Scalar. - */ + * Number of integration points. + * + * @return Scalar. + */ auto nip() const { return derived_cast().m_nip; } /** - Convert "qscalar" to "qtensor" of certain rank. - Fully allocated output passed as reference, use AsTensor to allocate and return data. - - \param arg A "qscalar". - \param ret A "qtensor". - */ + * Convert "qscalar" to "qtensor" of certain rank. + * Fully allocated output passed as reference, use AsTensor to allocate and return data. + * + * @param arg A "qscalar". + * @param ret A "qtensor". + */ template void asTensor(const T& arg, R& ret) const { GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_qscalar())); GooseFEM::asTensor(arg, ret); } /** - Convert "qscalar" to "qtensor" of certain rank. - - \param arg A "qscalar". - \return "qtensor". - */ + * Convert "qscalar" to "qtensor" of certain rank. + * + * @param arg A "qscalar". + * @return "qtensor". + */ template auto AsTensor(const T& arg) const { return GooseFEM::AsTensor(arg, derived_cast().m_tdim); } /** - Convert "qscalar" to "qtensor" of certain rank. - - \param rank Tensor rank. - \param arg A "qscalar". - \return "qtensor". - */ + * Convert "qscalar" to "qtensor" of certain rank. + * + * @param rank Tensor rank. + * @param arg A "qscalar". + * @return "qtensor". + */ template auto AsTensor(size_t rank, const T& arg) const { return GooseFEM::AsTensor(rank, arg, derived_cast().m_tdim); } /** - Get the shape of an "elemvec". - - \returns [#nelem, #nne, #ndim]. - */ + * Get the shape of an "elemvec". + * + * @returns [#nelem, #nne, #ndim]. + */ auto shape_elemvec() const -> std::array { return std::array{derived_cast().m_nelem, D::s_nne, D::s_ndim}; } /** - Get the shape of an "elemvec". - - \param arg The vector dimension. - \returns [#nelem, #nne, tdim]. - */ + * Get the shape of an "elemvec". + * + * @param arg The vector dimension. + * @returns [#nelem, #nne, tdim]. + */ auto shape_elemvec(size_t arg) const -> std::array { return std::array{derived_cast().m_nelem, D::s_nne, arg}; } /** - Get the shape of an "elemmat". - - \returns [#nelem, #nne * #ndim, #nne * #ndim]. - */ + * Get the shape of an "elemmat". + * + * @returns [#nelem, #nne * #ndim, #nne * #ndim]. + */ auto shape_elemmat() const -> std::array { return std::array{ derived_cast().m_nelem, D::s_nne * D::s_ndim, D::s_nne * D::s_ndim}; } /** - Get the shape of a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - - \tparam rank The rank of the tensor. - Since this function is templated, the output is fixed-size of type `std::array`. - - \returns [#nelem, #nip, #tdim, ...]. - */ + * Get the shape of a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * + * @tparam rank The rank of the tensor. + * Since this function is templated, the output is fixed-size of type `std::array`. + * + * @returns [#nelem, #nip, #tdim, ...]. + */ template auto shape_qtensor() const -> std::array { std::array shape; shape[0] = derived_cast().m_nelem; shape[1] = derived_cast().m_nip; std::fill(shape.begin() + 2, shape.end(), derived_cast().m_tdim); return shape; } /** - Get the shape of a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - - \param rank The tensor rank. - \returns [#nelem, #nip, #tdim, ...]. - */ + * Get the shape of a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * + * @param rank The tensor rank. + * @returns [#nelem, #nip, #tdim, ...]. + */ auto shape_qtensor(size_t rank) const -> std::vector { std::vector shape(2 + rank); shape[0] = derived_cast().m_nelem; shape[1] = derived_cast().m_nip; std::fill(shape.begin() + 2, shape.end(), derived_cast().m_tdim); return shape; } /** - Get the shape of a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - - \tparam rank The rank of the tensor. - Since this function is templated, the output is fixed-size of type `std::array`. - - \param rank The tensor rank. - Effectively useless, but is there to distinguish from the dynamic-sized overloads. - \param arg The tensor dimension. - \returns [#nelem, #nip, tdim, ...]. - */ + * Get the shape of a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * + * @tparam rank The rank of the tensor. + * Since this function is templated, the output is fixed-size of type `std::array`. + * + * @param rank The tensor rank. + * Effectively useless, but is there to distinguish from the dynamic-sized overloads. + * @param arg The tensor dimension. + * @returns [#nelem, #nip, tdim, ...]. + */ template auto shape_qtensor(size_t rank, size_t arg) const -> std::array { GOOSEFEM_ASSERT(trank == rank); std::array shape; shape[0] = derived_cast().m_nelem; shape[1] = derived_cast().m_nip; std::fill(shape.begin() + 2, shape.end(), arg); return shape; } /** - Get the shape of a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - - \param rank The tensor rank. - \param arg The tensor dimension. - \returns [#nelem, #nip, tdim, ...]. - */ + * Get the shape of a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * + * @param rank The tensor rank. + * @param arg The tensor dimension. + * @returns [#nelem, #nip, tdim, ...]. + */ auto shape_qtensor(size_t rank, size_t arg) const -> std::vector { std::vector shape(2 + rank); shape[0] = derived_cast().m_nelem; shape[1] = derived_cast().m_nip; std::fill(shape.begin() + 2, shape.end(), arg); return shape; } /** - Get the shape of a "qscalar" (a "qtensor" of rank 0) - \returns [#nelem, #nip]. - */ + * Get the shape of a "qscalar" (a "qtensor" of rank 0) + * @returns [#nelem, #nip]. + */ auto shape_qscalar() const -> std::array { return std::array{derived_cast().m_nelem, derived_cast().m_nip}; } /** - Get the shape of a "qvector" (a "qtensor" of rank 1) - \returns [#nelem, #nip, #tdim]. - */ + * Get the shape of a "qvector" (a "qtensor" of rank 1) + * @returns [#nelem, #nip, #tdim]. + */ auto shape_qvector() const -> std::array { return std::array{derived_cast().m_nelem, derived_cast().m_nip, D::s_tdim}; } /** - Get the shape of a "qvector" (a "qtensor" of rank 1) - \param arg Tensor dimension. - \returns [#nelem, #nip, #tdim]. - */ + * Get the shape of a "qvector" (a "qtensor" of rank 1) + * @param arg Tensor dimension. + * @returns [#nelem, #nip, #tdim]. + */ auto shape_qvector(size_t arg) const -> std::array { return std::array{derived_cast().m_nelem, derived_cast().m_nip, arg}; } /** - Get an allocated `array_type::tensor` to store a "elemvec". - Note: the container is not (zero-)initialised. - - \tparam R value-type of the array, e.g. `double`. - \returns `xt::xarray` container of the correct shape. - */ + * Get an allocated `array_type::tensor` to store a "elemvec". + * Note: the container is not (zero-)initialised. + * + * @tparam R value-type of the array, e.g. `double`. + * @returns `xt::xarray` container of the correct shape. + */ template auto allocate_elemvec() const { return array_type::tensor::from_shape(this->shape_elemvec()); } /** - Get an allocated and initialised `xt::xarray` to store a "elemvec". - - \tparam R value-type of the array, e.g. `double`. - \param val The value to which to initialise all items. - \returns `array_type::tensor` container of the correct shape. - */ + * Get an allocated and initialised `xt::xarray` to store a "elemvec". + * + * @tparam R value-type of the array, e.g. `double`. + * @param val The value to which to initialise all items. + * @returns `array_type::tensor` container of the correct shape. + */ template auto allocate_elemvec(R val) const { auto ret = array_type::tensor::from_shape(this->shape_elemvec()); ret.fill(val); return ret; } /** - Get an allocated `array_type::tensor` to store a "elemmat". - Note: the container is not (zero-)initialised. - - \tparam R value-type of the array, e.g. `double`. - \returns `xt::xarray` container of the correct shape. - */ + * Get an allocated `array_type::tensor` to store a "elemmat". + * Note: the container is not (zero-)initialised. + * + * @tparam R value-type of the array, e.g. `double`. + * @returns `xt::xarray` container of the correct shape. + */ template auto allocate_elemmat() const { return array_type::tensor::from_shape(this->shape_elemmat()); } /** - Get an allocated and initialised `xt::xarray` to store a "elemmat". - - \tparam R value-type of the array, e.g. `double`. - \param val The value to which to initialise all items. - \returns `array_type::tensor` container of the correct shape. - */ + * Get an allocated and initialised `xt::xarray` to store a "elemmat". + * + * @tparam R value-type of the array, e.g. `double`. + * @param val The value to which to initialise all items. + * @returns `array_type::tensor` container of the correct shape. + */ template auto allocate_elemmat(R val) const { auto ret = array_type::tensor::from_shape(this->shape_elemmat()); ret.fill(val); return ret; } /** - Get an allocated `array_type::tensor` to store a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - Default: rank = 0, a.k.a. scalar. - Note: the container is not (zero-)initialised. - - \tparam R value-type of the array, e.g. `double`. - \returns [#nelem, #nip]. - */ + * Get an allocated `array_type::tensor` to store a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * Default: rank = 0, a.k.a. scalar. + * Note: the container is not (zero-)initialised. + * + * @tparam R value-type of the array, e.g. `double`. + * @returns [#nelem, #nip]. + */ template auto allocate_qtensor() const { return array_type::tensor::from_shape(this->shape_qtensor()); } /** - Get an allocated and initialised `array_type::tensor` to store a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - Default: rank = 0, a.k.a. scalar. - - \tparam R value-type of the array, e.g. `double`. - \param val The value to which to initialise all items. - \returns `array_type::tensor` container of the correct shape (and rank). - */ + * Get an allocated and initialised `array_type::tensor` to store a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * Default: rank = 0, a.k.a. scalar. + * + * @tparam R value-type of the array, e.g. `double`. + * @param val The value to which to initialise all items. + * @returns `array_type::tensor` container of the correct shape (and rank). + */ template auto allocate_qtensor(R val) const { auto ret = array_type::tensor::from_shape(this->shape_qtensor()); ret.fill(val); return ret; } /** - Get an allocated `xt::xarray` to store a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - Note: the container is not (zero-)initialised. - - \tparam R value-type of the array, e.g. `double`. - \param rank The tensor rank. - \returns `xt::xarray` container of the correct shape. - */ + * Get an allocated `xt::xarray` to store a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * Note: the container is not (zero-)initialised. + * + * @tparam R value-type of the array, e.g. `double`. + * @param rank The tensor rank. + * @returns `xt::xarray` container of the correct shape. + */ template auto allocate_qtensor(size_t rank) const { return xt::xarray::from_shape(this->shape_qtensor(rank)); } /** - Get an allocated and initialised `xt::xarray` to store a "qtensor" of a certain rank - (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). - - \tparam R value-type of the array, e.g. `double`. - \param rank The tensor rank. - \param val The value to which to initialise all items. - \returns `array_type::tensor` container of the correct shape (and rank). - */ + * Get an allocated and initialised `xt::xarray` to store a "qtensor" of a certain rank + * (0 = scalar, 1, vector, 2 = 2nd-order tensor, etc.). + * + * @tparam R value-type of the array, e.g. `double`. + * @param rank The tensor rank. + * @param val The value to which to initialise all items. + * @returns `array_type::tensor` container of the correct shape (and rank). + */ template auto allocate_qtensor(size_t rank, R val) const { auto ret = xt::xarray::from_shape(this->shape_qtensor(rank)); ret.fill(val); return ret; } /** - Get an allocated `array_type::tensor` to store a "qscalar" (a "qtensor" of rank 0). - Note: the container is not (zero-)initialised. - - \tparam R value-type of the array, e.g. `double`. - \returns `xt::xarray` container of the correct shape. - */ + * Get an allocated `array_type::tensor` to store a "qscalar" (a "qtensor" of rank 0). + * Note: the container is not (zero-)initialised. + * + * @tparam R value-type of the array, e.g. `double`. + * @returns `xt::xarray` container of the correct shape. + */ template auto allocate_qscalar() const { return this->allocate_qtensor<0, R>(); } /** - Get an allocated and initialised `xt::xarray` to store a "qscalar" (a "qtensor" of rank 0). - - \tparam R value-type of the array, e.g. `double`. - \param val The value to which to initialise all items. - \returns `array_type::tensor` container of the correct shape (and rank). - */ + * Get an allocated and initialised `xt::xarray` to store a "qscalar" (a "qtensor" of rank 0). + * + * @tparam R value-type of the array, e.g. `double`. + * @param val The value to which to initialise all items. + * @returns `array_type::tensor` container of the correct shape (and rank). + */ template auto allocate_qscalar(R val) const { return this->allocate_qtensor<0, R>(val); } private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } }; /** -CRTP base class for interpolation and quadrature for a generic element in Cartesian coordinates. - -Naming convention: -- ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] -- ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] -- ``qtensor``: integration point tensor, [#nelem, #nip, #tdim, #tdim] -- ``qscalar``: integration point scalar, [#nelem, #nip] -*/ + * CRTP base class for interpolation and quadrature for a generic element in Cartesian coordinates. + * + * Naming convention: + * - ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] + * - ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] + * - ``qtensor``: integration point tensor, [#nelem, #nip, #tdim, #tdim] + * - ``qscalar``: integration point scalar, [#nelem, #nip] + */ template class QuadratureBaseCartesian : public QuadratureBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; /** - Update the nodal positions. - This recomputes: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - Under the small deformations assumption this function should not be called. - - \param x nodal coordinates (``elemvec``). Shape should match the earlier definition. - */ + * Update the nodal positions. + * This recomputes: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * Under the small deformations assumption this function should not be called. + * + * @param x nodal coordinates (``elemvec``). Shape should match the earlier definition. + */ template void update_x(const T& x) { GOOSEFEM_ASSERT(xt::has_shape(x, derived_cast().m_x.shape())); xt::noalias(derived_cast().m_x) = x; derived_cast().compute_dN_impl(); } /** - Shape function gradients (in global coordinates). - \return ``gradN`` stored per element, per integration point [#nelem, #nip, #nne, #ndim]. - */ + * Shape function gradients (in global coordinates). + * @return ``gradN`` stored per element, per integration point [#nelem, #nip, #nne, #ndim]. + */ auto GradN() const -> const array_type::tensor& { return derived_cast().m_dNx; } /** - Integration volume. - \return volume stored per element, per integration point [#nelem, #nip]. - */ + * Integration volume. + * @return volume stored per element, per integration point [#nelem, #nip]. + */ auto dV() const -> const array_type::tensor& { return derived_cast().m_vol; } /** - Interpolate element vector and evaluate at each quadrature point. - - \f$ \vec{u}(\vec{x}_q) = N_i^e(\vec{x}) \vec{u}_i^e \f$ - - \param elemvec nodal vector stored per element [#nelem, #nne, #ndim]. - \return qvector [#nelem, #nip, #ndim]. - */ + * Interpolate element vector and evaluate at each quadrature point. + * + * \f$ \vec{u}(\vec{x}_q) = N_i^e(\vec{x}) \vec{u}_i^e \f$ + * + * @param elemvec nodal vector stored per element [#nelem, #nne, #ndim]. + * @return qvector [#nelem, #nip, #ndim]. + */ template auto InterpQuad_vector(const T& elemvec) const -> array_type::tensor { size_t n = elemvec.shape(2); auto qvector = array_type::tensor::from_shape(this->shape_qvector(n)); derived_cast().interpQuad_vector_impl(elemvec, qvector); return qvector; } /** - Same as InterpQuad_vector(), but writing to preallocated return. - - \param elemvec nodal vector stored per element [#nelem, #nne, #ndim]. - \param qvector [#nelem, #nip, #ndim]. - */ + * Same as InterpQuad_vector(), but writing to preallocated return. + * + * @param elemvec nodal vector stored per element [#nelem, #nne, #ndim]. + * @param qvector [#nelem, #nip, #ndim]. + */ template void interpQuad_vector(const T& elemvec, R& qvector) const { derived_cast().interpQuad_vector_impl(elemvec, qvector); } /** - Element-by-element: dyadic product of the shape function gradients and a nodal vector. - Typical input: nodal displacements. Typical output: quadrature point strains. - Within one element:: - - for e in range(nelem): - for q in range(nip): - for m in range(nne): - qtensor(e, q, i, j) += dNdx(e, q, m, i) * elemvec(e, m, j) - - Note that the functions and their gradients are precomputed upon construction, - or updated when calling update_x(). - - \param elemvec [#nelem, #nne, #ndim] - \return qtensor [#nelem, #nip, #tdim, #tdim] - */ + * Element-by-element: dyadic product of the shape function gradients and a nodal vector. + * Typical input: nodal displacements. Typical output: quadrature point strains. + * Within one element:: + * + * for e in range(nelem): + * for q in range(nip): + * for m in range(nne): + * qtensor(e, q, i, j) += dNdx(e, q, m, i) * elemvec(e, m, j) + * + * Note that the functions and their gradients are precomputed upon construction, + * or updated when calling update_x(). + * + * @param elemvec [#nelem, #nne, #ndim] + * @return qtensor [#nelem, #nip, #tdim, #tdim] + */ template auto GradN_vector(const T& elemvec) const -> array_type::tensor { auto qtensor = array_type::tensor::from_shape(this->template shape_qtensor<2>()); derived_cast().gradN_vector_impl(elemvec, qtensor); return qtensor; } /** - Same as GradN_vector(), but writing to preallocated return. - - \param elemvec [#nelem, #nne, #ndim] - \param qtensor overwritten [#nelem, #nip, #tdim, #tdim] - */ + * Same as GradN_vector(), but writing to preallocated return. + * + * @param elemvec [#nelem, #nne, #ndim] + * @param qtensor overwritten [#nelem, #nip, #tdim, #tdim] + */ template void gradN_vector(const T& elemvec, R& qtensor) const { derived_cast().gradN_vector_impl(elemvec, qtensor); } /** - The transposed output of GradN_vector(). - Within one element:: - - for e in range(nelem): - for q in range(nip): - for m in range(nne): - qtensor(e, q, j, i) += dNdx(e, q, m, i) * elemvec(e, m, j) - - \param elemvec [#nelem, #nne, #ndim] - \return qtensor [#nelem, #nip, #tdim, #tdim] - */ + * The transposed output of GradN_vector(). + * Within one element:: + * + * for e in range(nelem): + * for q in range(nip): + * for m in range(nne): + * qtensor(e, q, j, i) += dNdx(e, q, m, i) * elemvec(e, m, j) + * + * @param elemvec [#nelem, #nne, #ndim] + * @return qtensor [#nelem, #nip, #tdim, #tdim] + */ template auto GradN_vector_T(const T& elemvec) const -> array_type::tensor { auto qtensor = array_type::tensor::from_shape(this->template shape_qtensor<2>()); derived_cast().gradN_vector_T_impl(elemvec, qtensor); return qtensor; } /** - Same as GradN_vector_T(), but writing to preallocated return. - - \param elemvec [#nelem, #nne, #ndim] - \param qtensor overwritten [#nelem, #nip, #tdim, #tdim] - */ + * Same as GradN_vector_T(), but writing to preallocated return. + * + * @param elemvec [#nelem, #nne, #ndim] + * @param qtensor overwritten [#nelem, #nip, #tdim, #tdim] + */ template void gradN_vector_T(const T& elemvec, R& qtensor) const { derived_cast().gradN_vector_T_impl(elemvec, qtensor); } /** - The symmetric output of GradN_vector(). - Without one element:: - - for e in range(nelem): - for q in range(nip): - for m in range(nne): - qtensor(e, q, i, j) += 0.5 * dNdx(e, q, m, i) * elemvec(e, m, j) - qtensor(e, q, j, i) += 0.5 * dNdx(e, q, m, i) * elemvec(e, m, j) - - \param elemvec [#nelem, #nne, #ndim] - \return qtensor [#nelem, #nip, #tdim, #tdim] - */ + * The symmetric output of GradN_vector(). + * Without one element:: + * + * for e in range(nelem): + * for q in range(nip): + * for m in range(nne): + * qtensor(e, q, i, j) += 0.5 * dNdx(e, q, m, i) * elemvec(e, m, j) + * qtensor(e, q, j, i) += 0.5 * dNdx(e, q, m, i) * elemvec(e, m, j) + * + * @param elemvec [#nelem, #nne, #ndim] + * @return qtensor [#nelem, #nip, #tdim, #tdim] + */ template auto SymGradN_vector(const T& elemvec) const -> array_type::tensor { auto qtensor = array_type::tensor::from_shape(this->template shape_qtensor<2>()); derived_cast().symGradN_vector_impl(elemvec, qtensor); return qtensor; } /** - Same as SymGradN_vector(), but writing to preallocated return. - - \param elemvec [#nelem, #nne, #ndim] - \param qtensor overwritten [#nelem, #nip, #tdim, #tdim] - */ + * Same as SymGradN_vector(), but writing to preallocated return. + * + * @param elemvec [#nelem, #nne, #ndim] + * @param qtensor overwritten [#nelem, #nip, #tdim, #tdim] + */ template void symGradN_vector(const T& elemvec, R& qtensor) const { derived_cast().symGradN_vector_impl(elemvec, qtensor); } /** - Element-by-element: integral of a continuous vector-field. - - \f$ \vec{f}_i^e = \int N_i^e(\vec{x}) \vec{f}(\vec{x}) d\Omega_e \f$ - - which is integration numerically as follows - - \f$ \vec{f}_i^e = \sum\limits_q N_i^e(\vec{x}_q) \vec{f}(\vec{x}_q) \f$ - - \param qvector [#nelem, #nip. #ndim] - \return elemvec [#nelem, #nne. #ndim] - */ + * Element-by-element: integral of a continuous vector-field. + * + * \f$ \vec{f}_i^e = \int N_i^e(\vec{x}) \vec{f}(\vec{x}) d\Omega_e \f$ + * + * which is integration numerically as follows + * + * \f$ \vec{f}_i^e = \sum\limits_q N_i^e(\vec{x}_q) \vec{f}(\vec{x}_q) \f$ + * + * @param qvector [#nelem, #nip. #ndim] + * @return elemvec [#nelem, #nne. #ndim] + */ template auto Int_N_vector_dV(const T& qvector) const -> array_type::tensor { size_t n = qvector.shape(2); auto elemvec = array_type::tensor::from_shape(this->shape_elemvec(n)); derived_cast().int_N_vector_dV_impl(qvector, elemvec); return elemvec; } /** - Same as Int_N_vector_dV(), but writing to preallocated return. - - \param qvector [#nelem, #nip. #ndim] - \param elemvec overwritten [#nelem, #nne. #ndim] - */ + * Same as Int_N_vector_dV(), but writing to preallocated return. + * + * @param qvector [#nelem, #nip. #ndim] + * @param elemvec overwritten [#nelem, #nne. #ndim] + */ template void int_N_vector_dV(const T& qvector, R& elemvec) const { derived_cast().int_N_vector_dV_impl(qvector, elemvec); } /** - Element-by-element: integral of the scalar product of the shape function with a scalar. - Within one one element:: - - for e in range(nelem): - for q in range(nip): - for m in range(nne): - for n in range(nne): - elemmat(e, m * ndim + i, n * ndim + i) += - N(e, q, m) * qscalar(e, q) * N(e, q, n) * dV(e, q) - - with ``i`` a tensor dimension. - Note that the functions and their gradients are precomputed upon construction, - or updated when calling update_x(). - - \param qscalar [#nelem, #nip] - \return elemmat [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Element-by-element: integral of the scalar product of the shape function with a scalar. + * Within one one element:: + * + * for e in range(nelem): + * for q in range(nip): + * for m in range(nne): + * for n in range(nne): + * elemmat(e, m * ndim + i, n * ndim + i) += + * N(e, q, m) * qscalar(e, q) * N(e, q, n) * dV(e, q) + * + * with ``i`` a tensor dimension. + * Note that the functions and their gradients are precomputed upon construction, + * or updated when calling update_x(). + * + * @param qscalar [#nelem, #nip] + * @return elemmat [#nelem, #nne * #ndim, #nne * #ndim] + */ template auto Int_N_scalar_NT_dV(const T& qscalar) const -> array_type::tensor { auto elemmat = array_type::tensor::from_shape(this->shape_elemmat()); derived_cast().int_N_scalar_NT_dV_impl(qscalar, elemmat); return elemmat; } /** - Same as Int_N_scalar_NT_dV(), but writing to preallocated return. - - \param qscalar [#nelem, #nip] - \param elemmat overwritten [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Same as Int_N_scalar_NT_dV(), but writing to preallocated return. + * + * @param qscalar [#nelem, #nip] + * @param elemmat overwritten [#nelem, #nne * #ndim, #nne * #ndim] + */ template void int_N_scalar_NT_dV(const T& qscalar, R& elemmat) const { derived_cast().int_N_scalar_NT_dV_impl(qscalar, elemmat); } /** - Element-by-element: integral of the dot product of the shape function gradients with - a second order tensor. Typical input: stress. Typical output: nodal force. - Within one one element:: - - for e in range(nelem): - for q in range(nip): - for m in range(nne): - elemvec(e, m, j) += dNdx(e, q, m, i) * qtensor(e, q, i, j) * dV(e, q) - - with ``i`` and ``j`` tensor dimensions. - Note that the functions and their gradients are precomputed upon construction, - or updated when calling update_x(). - - \param qtensor [#nelem, #nip, #ndim, #ndim] - \return elemvec [#nelem, #nne. #ndim] - */ + * Element-by-element: integral of the dot product of the shape function gradients with + * a second order tensor. Typical input: stress. Typical output: nodal force. + * Within one one element:: + * + * for e in range(nelem): + * for q in range(nip): + * for m in range(nne): + * elemvec(e, m, j) += dNdx(e, q, m, i) * qtensor(e, q, i, j) * dV(e, q) + * + * with ``i`` and ``j`` tensor dimensions. + * Note that the functions and their gradients are precomputed upon construction, + * or updated when calling update_x(). + * + * @param qtensor [#nelem, #nip, #ndim, #ndim] + * @return elemvec [#nelem, #nne. #ndim] + */ template auto Int_gradN_dot_tensor2_dV(const T& qtensor) const -> array_type::tensor { auto elemvec = array_type::tensor::from_shape(this->shape_elemvec()); derived_cast().int_gradN_dot_tensor2_dV_impl(qtensor, elemvec); return elemvec; } /** - Same as Int_gradN_dot_tensor2_dV(), but writing to preallocated return. - - \param qtensor [#nelem, #nip, #ndim, #ndim] - \param elemvec overwritten [#nelem, #nne. #ndim] - */ + * Same as Int_gradN_dot_tensor2_dV(), but writing to preallocated return. + * + * @param qtensor [#nelem, #nip, #ndim, #ndim] + * @param elemvec overwritten [#nelem, #nne. #ndim] + */ template void int_gradN_dot_tensor2_dV(const T& qtensor, R& elemvec) const { derived_cast().int_gradN_dot_tensor2_dV_impl(qtensor, elemvec); } // Integral of the dot product // elemmat(m*2+j, n*2+k) += dNdx(m,i) * qtensor(i,j,k,l) * dNdx(n,l) * dV /** - Element-by-element: integral of the dot products of the shape function gradients with - a fourth order tensor. Typical input: stiffness tensor. Typical output: stiffness matrix. - Within one one element:: - - for e in range(nelem): - for q in range(nip): - for m in range(nne): - for n in range(nne): - elemmat(e, m * ndim + j, n * ndim + k) += - dNdx(e,q,m,i) * qtensor(e,q,i,j,k,l) * dNdx(e,q,n,l) * dV(e,q) - - with ``i``, ``j``, ``k``, and ``l`` tensor dimensions. - Note that the functions and their gradients are precomputed upon construction, - or updated when calling update_x(). - - \param qtensor [#nelem, #nip, #ndim, #ndim, #ndim, #ndim] - \return elemmat [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Element-by-element: integral of the dot products of the shape function gradients with + * a fourth order tensor. Typical input: stiffness tensor. Typical output: stiffness matrix. + * Within one one element:: + * + * for e in range(nelem): + * for q in range(nip): + * for m in range(nne): + * for n in range(nne): + * elemmat(e, m * ndim + j, n * ndim + k) += + * dNdx(e,q,m,i) * qtensor(e,q,i,j,k,l) * dNdx(e,q,n,l) * dV(e,q) + * + * with ``i``, ``j``, ``k``, and ``l`` tensor dimensions. + * Note that the functions and their gradients are precomputed upon construction, + * or updated when calling update_x(). + * + * @param qtensor [#nelem, #nip, #ndim, #ndim, #ndim, #ndim] + * @return elemmat [#nelem, #nne * #ndim, #nne * #ndim] + */ template auto Int_gradN_dot_tensor4_dot_gradNT_dV(const T& qtensor) const -> array_type::tensor { auto elemmat = array_type::tensor::from_shape(this->shape_elemmat()); derived_cast().int_gradN_dot_tensor4_dot_gradNT_dV_impl(qtensor, elemmat); return elemmat; } /** - Same as Int_gradN_dot_tensor4_dot_gradNT_dV(), but writing to preallocated return. - - \param qtensor [#nelem, #nip, #ndim, #ndim, #ndim, #ndim] - \param elemmat overwritten [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Same as Int_gradN_dot_tensor4_dot_gradNT_dV(), but writing to preallocated return. + * + * @param qtensor [#nelem, #nip, #ndim, #ndim, #ndim, #ndim] + * @param elemmat overwritten [#nelem, #nne * #ndim, #nne * #ndim] + */ template void int_gradN_dot_tensor4_dot_gradNT_dV(const T& qtensor, R& elemmat) const { derived_cast().int_gradN_dot_tensor4_dot_gradNT_dV_impl(qtensor, elemmat); } protected: /** - Update the shape function gradients (called when the nodal positions are updated). - */ + * Update the shape function gradients (called when the nodal positions are updated). + */ void compute_dN() { derived_cast().compute_dN_impl(); } private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } friend class QuadratureBase; template void interpQuad_vector_impl(const T& elemvec, R& qvector) const { size_t n = elemvec.shape(2); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec(n))); GOOSEFEM_ASSERT(xt::has_shape(qvector, this->shape_qvector(n))); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& N = derived_cast().m_N; qvector.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto fq = &elemvec(e, 0, 0); for (size_t q = 0; q < nip; ++q) { auto Nq = &N(q, 0); auto tq = &qvector(e, q, 0); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < n; ++i) { tq[i] += Nq[m] * fq[m * n + i]; } } } } } template void gradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->template shape_qtensor<2>())); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& dNx = derived_cast().m_dNx; qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto ue = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto dNxq = xt::adapt(&dNx(e, q, 0, 0), xt::xshape()); auto graduq = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { graduq(i, j) += dNxq(m, i) * ue(m, j); } } } } } } template void gradN_vector_T_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->template shape_qtensor<2>())); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& dNx = derived_cast().m_dNx; qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto ue = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto dNxq = xt::adapt(&dNx(e, q, 0, 0), xt::xshape()); auto graduq = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { graduq(j, i) += dNxq(m, i) * ue(m, j); } } } } } } template void symGradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->template shape_qtensor<2>())); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& dNx = derived_cast().m_dNx; qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto ue = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto dNxq = xt::adapt(&dNx(e, q, 0, 0), xt::xshape()); auto epsq = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { epsq(i, j) += 0.5 * dNxq(m, i) * ue(m, j); epsq(j, i) += 0.5 * dNxq(m, i) * ue(m, j); } } } } } } template void int_N_vector_dV_impl(const T& qvector, R& elemvec) const { size_t n = qvector.shape(2); GOOSEFEM_ASSERT(xt::has_shape(qvector, this->shape_qvector(n))); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec(n))); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& N = derived_cast().m_N; auto& vol = derived_cast().m_vol; elemvec.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto f = &elemvec(e, 0, 0); for (size_t q = 0; q < nip; ++q) { auto Ne = &N(q, 0); auto tq = &qvector(e, q, 0); auto& volq = vol(e, q); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < n; ++i) { f[m * n + i] += Ne[m] * tq[i] * volq; } } } } } template void int_N_scalar_NT_dV_impl(const T& qscalar, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qscalar, this->shape_qscalar())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& N = derived_cast().m_N; auto& vol = derived_cast().m_vol; elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto Me = xt::adapt( &elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto Ne = xt::adapt(&N(q, 0), xt::xshape()); auto& volq = vol(e, q); auto& rho = qscalar(e, q); // M(m * D::s_ndim + i, n * D::s_ndim + i) += N(m) * scalar * N(n) * dV for (size_t m = 0; m < D::s_nne; ++m) { for (size_t n = 0; n < D::s_nne; ++n) { for (size_t i = 0; i < D::s_ndim; ++i) { Me(m * D::s_ndim + i, n * D::s_ndim + i) += Ne(m) * rho * Ne(n) * volq; } } } } } } template void int_gradN_dot_tensor2_dV_impl(const T& qtensor, R& elemvec) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->template shape_qtensor<2>())); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& dNx = derived_cast().m_m_dNx; auto& vol = derived_cast().m_vol; elemvec.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto fe = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto dNxq = xt::adapt(&dNx(e, q, 0, 0), xt::xshape()); auto sigq = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); auto& volq = vol(e, q); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { fe(m, j) += dNxq(m, i) * sigq(i, j) * volq; } } } } } } template void int_gradN_dot_tensor4_dot_gradNT_dV_impl(const T& qtensor, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->template shape_qtensor<4>())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& dNx = derived_cast().m_dNx; auto& vol = derived_cast().m_vol; elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < nelem; ++e) { auto K = xt::adapt( &elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto dNxq = xt::adapt(&dNx(e, q, 0, 0), xt::xshape()); auto Cq = xt::adapt( &qtensor(e, q, 0, 0, 0, 0), xt::xshape()); auto& volq = vol(e, q); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t n = 0; n < D::s_nne; ++n) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { for (size_t k = 0; k < D::s_ndim; ++k) { for (size_t l = 0; l < D::s_ndim; ++l) { K(m * D::s_ndim + j, n * D::s_ndim + k) += dNxq(m, i) * Cq(i, j, k, l) * dNxq(n, l) * volq; } } } } } } } } } void compute_dN_impl() { auto nelem = derived_cast().m_nelem; auto nip = derived_cast().m_nip; auto& vol = derived_cast().m_vol; auto& w = derived_cast().m_w; auto& dNxi = derived_cast().m_dNxi; auto& dNx = derived_cast().m_dNx; auto& x = derived_cast().m_x; dNx.fill(0.0); #pragma omp parallel { auto J = array_type::tensor::from_shape({D::s_ndim, D::s_ndim}); auto Jinv = array_type::tensor::from_shape({D::s_ndim, D::s_ndim}); #pragma omp for for (size_t e = 0; e < nelem; ++e) { auto xe = xt::adapt(&x(e, 0, 0), xt::xshape()); for (size_t q = 0; q < nip; ++q) { auto dNxiq = xt::adapt(&dNxi(q, 0, 0), xt::xshape()); auto dNxq = xt::adapt(&dNx(e, q, 0, 0), xt::xshape()); J.fill(0.0); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { J(i, j) += dNxiq(m, i) * xe(m, j); } } } double Jdet = detail::tensor::inv(J, Jinv); for (size_t m = 0; m < D::s_nne; ++m) { for (size_t i = 0; i < D::s_ndim; ++i) { for (size_t j = 0; j < D::s_ndim; ++j) { dNxq(m, i) += Jinv(i, j) * dNxiq(m, i); } } } vol(e, q) = w(q) * Jdet; } } } } }; } // namespace Element } // namespace GooseFEM #endif diff --git a/include/GooseFEM/ElementHex8.h b/include/GooseFEM/ElementHex8.h index e9063d6..b335c19 100644 --- a/include/GooseFEM/ElementHex8.h +++ b/include/GooseFEM/ElementHex8.h @@ -1,404 +1,404 @@ /** -Quadrature for 8-noded hexahedral element in 3d (GooseFEM::Mesh::ElementType::Hex8), -in a Cartesian coordinate system. - -\file ElementHex8.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Quadrature for 8-noded hexahedral element in 3d (GooseFEM::Mesh::ElementType::Hex8), + * in a Cartesian coordinate system. + * + * @file ElementHex8.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ELEMENTHEX8_H #define GOOSEFEM_ELEMENTHEX8_H #include "config.h" namespace GooseFEM { namespace Element { /** -8-noded hexahedral element in 3d (GooseFEM::Mesh::ElementType::Hex8). -*/ + * 8-noded hexahedral element in 3d (GooseFEM::Mesh::ElementType::Hex8). + */ namespace Hex8 { /** -gauss quadrature: quadrature points such that integration is exact for these bi-linear elements:: -*/ + * gauss quadrature: quadrature points such that integration is exact for these bi-linear elements:: + */ namespace Gauss { /** -Number of integration points: - - nip = nne = 8 - -\return unsigned int -*/ + * Number of integration points: + * + * nip = nne = 8 + * + * @return unsigned int + */ inline size_t nip() { return 8; } /** -Integration point coordinates (local coordinates). - -\return Coordinates [#nip, ndim], with `ndim = 3`. -*/ + * Integration point coordinates (local coordinates). + * + * @return Coordinates [#nip, ndim], with `ndim = 3`. + */ inline array_type::tensor xi() { size_t nip = 8; size_t ndim = 3; array_type::tensor xi = xt::empty({nip, ndim}); xi(0, 0) = -1.0 / std::sqrt(3.0); xi(0, 1) = -1.0 / std::sqrt(3.0); xi(0, 2) = -1.0 / std::sqrt(3.0); xi(1, 0) = +1.0 / std::sqrt(3.0); xi(1, 1) = -1.0 / std::sqrt(3.0); xi(1, 2) = -1.0 / std::sqrt(3.0); xi(2, 0) = +1.0 / std::sqrt(3.0); xi(2, 1) = +1.0 / std::sqrt(3.0); xi(2, 2) = -1.0 / std::sqrt(3.0); xi(3, 0) = -1.0 / std::sqrt(3.0); xi(3, 1) = +1.0 / std::sqrt(3.0); xi(3, 2) = -1.0 / std::sqrt(3.0); xi(4, 0) = -1.0 / std::sqrt(3.0); xi(4, 1) = -1.0 / std::sqrt(3.0); xi(4, 2) = +1.0 / std::sqrt(3.0); xi(5, 0) = +1.0 / std::sqrt(3.0); xi(5, 1) = -1.0 / std::sqrt(3.0); xi(5, 2) = +1.0 / std::sqrt(3.0); xi(6, 0) = +1.0 / std::sqrt(3.0); xi(6, 1) = +1.0 / std::sqrt(3.0); xi(6, 2) = +1.0 / std::sqrt(3.0); xi(7, 0) = -1.0 / std::sqrt(3.0); xi(7, 1) = +1.0 / std::sqrt(3.0); xi(7, 2) = +1.0 / std::sqrt(3.0); return xi; } /** -Integration point weights. - -\return Coordinates [#nip]. -*/ + * Integration point weights. + * + * @return Coordinates [#nip]. + */ inline array_type::tensor w() { size_t nip = 8; array_type::tensor w = xt::empty({nip}); w(0) = 1.0; w(1) = 1.0; w(2) = 1.0; w(3) = 1.0; w(4) = 1.0; w(5) = 1.0; w(6) = 1.0; w(7) = 1.0; return w; } } // namespace Gauss /** -nodal quadrature: quadrature points coincide with the nodes. -The order is the same as in the connectivity. -*/ + * nodal quadrature: quadrature points coincide with the nodes. + * The order is the same as in the connectivity. + */ namespace Nodal { /** -Number of integration points: - - nip = nne = 8 - -\return unsigned int -*/ + * Number of integration points: + * + * nip = nne = 8 + * + * @return unsigned int + */ inline size_t nip() { return 8; } /** -Integration point coordinates (local coordinates). - -\return Coordinates [#nip, `ndim`], with ``ndim = 3``. -*/ + * Integration point coordinates (local coordinates). + * + * @return Coordinates [#nip, `ndim`], with ``ndim = 3``. + */ inline array_type::tensor xi() { size_t nip = 8; size_t ndim = 3; array_type::tensor xi = xt::empty({nip, ndim}); xi(0, 0) = -1.0; xi(0, 1) = -1.0; xi(0, 2) = -1.0; xi(1, 0) = +1.0; xi(1, 1) = -1.0; xi(1, 2) = -1.0; xi(2, 0) = +1.0; xi(2, 1) = +1.0; xi(2, 2) = -1.0; xi(3, 0) = -1.0; xi(3, 1) = +1.0; xi(3, 2) = -1.0; xi(4, 0) = -1.0; xi(4, 1) = -1.0; xi(4, 2) = +1.0; xi(5, 0) = +1.0; xi(5, 1) = -1.0; xi(5, 2) = +1.0; xi(6, 0) = +1.0; xi(6, 1) = +1.0; xi(6, 2) = +1.0; xi(7, 0) = -1.0; xi(7, 1) = +1.0; xi(7, 2) = +1.0; return xi; } /** -Integration point weights. - -\return Coordinates [#nip]. -*/ + * Integration point weights. + * + * @return Coordinates [#nip]. + */ inline array_type::tensor w() { size_t nip = 8; array_type::tensor w = xt::empty({nip}); w(0) = 1.0; w(1) = 1.0; w(2) = 1.0; w(3) = 1.0; w(4) = 1.0; w(5) = 1.0; w(6) = 1.0; w(7) = 1.0; return w; } } // namespace Nodal /** -Interpolation and quadrature. - -Fixed dimensions: -- ``ndim = 3``: number of dimensions. -- ``nne = 8``: number of nodes per element. - -Naming convention: -- ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] -- ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] -- ``qtensor``: integration point tensor, [#nelem, #nip, #ndim, #ndim] -- ``qscalar``: integration point scalar, [#nelem, #nip] -*/ + * Interpolation and quadrature. + * + * Fixed dimensions: + * - ``ndim = 3``: number of dimensions. + * - ``nne = 8``: number of nodes per element. + * + * Naming convention: + * - ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] + * - ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] + * - ``qtensor``: integration point tensor, [#nelem, #nip, #ndim, #ndim] + * - ``qscalar``: integration point scalar, [#nelem, #nip] + */ class Quadrature : public QuadratureBaseCartesian { public: Quadrature() = default; /** - Constructor: use default Gauss integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - */ + * Constructor: use default Gauss integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + */ template Quadrature(const T& x) : Quadrature(x, Gauss::xi(), Gauss::w()) { } /** - Constructor with custom integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - \param xi Integration point coordinates (local coordinates) [#nip]. - \param w Integration point weights [#nip]. - */ + * Constructor with custom integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + * @param xi Integration point coordinates (local coordinates) [#nip]. + * @param w Integration point weights [#nip]. + */ template Quadrature(const T& x, const X& xi, const W& w) { m_x = x; m_w = w; m_xi = xi; m_nip = w.size(); m_nelem = m_x.shape(0); m_N = xt::empty({m_nip, s_nne}); m_dNxi = xt::empty({m_nip, s_nne, s_ndim}); for (size_t q = 0; q < m_nip; ++q) { m_N(q, 0) = 0.125 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 1)) * (1.0 - xi(q, 2)); m_N(q, 1) = 0.125 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 1)) * (1.0 - xi(q, 2)); m_N(q, 2) = 0.125 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 1)) * (1.0 - xi(q, 2)); m_N(q, 3) = 0.125 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 1)) * (1.0 - xi(q, 2)); m_N(q, 4) = 0.125 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 1)) * (1.0 + xi(q, 2)); m_N(q, 5) = 0.125 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 1)) * (1.0 + xi(q, 2)); m_N(q, 6) = 0.125 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 1)) * (1.0 + xi(q, 2)); m_N(q, 7) = 0.125 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 1)) * (1.0 + xi(q, 2)); } for (size_t q = 0; q < m_nip; ++q) { // - dN / dxi_0 m_dNxi(q, 0, 0) = -0.125 * (1.0 - xi(q, 1)) * (1.0 - xi(q, 2)); m_dNxi(q, 1, 0) = +0.125 * (1.0 - xi(q, 1)) * (1.0 - xi(q, 2)); m_dNxi(q, 2, 0) = +0.125 * (1.0 + xi(q, 1)) * (1.0 - xi(q, 2)); m_dNxi(q, 3, 0) = -0.125 * (1.0 + xi(q, 1)) * (1.0 - xi(q, 2)); m_dNxi(q, 4, 0) = -0.125 * (1.0 - xi(q, 1)) * (1.0 + xi(q, 2)); m_dNxi(q, 5, 0) = +0.125 * (1.0 - xi(q, 1)) * (1.0 + xi(q, 2)); m_dNxi(q, 6, 0) = +0.125 * (1.0 + xi(q, 1)) * (1.0 + xi(q, 2)); m_dNxi(q, 7, 0) = -0.125 * (1.0 + xi(q, 1)) * (1.0 + xi(q, 2)); // - dN / dxi_1 m_dNxi(q, 0, 1) = -0.125 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 2)); m_dNxi(q, 1, 1) = -0.125 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 2)); m_dNxi(q, 2, 1) = +0.125 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 2)); m_dNxi(q, 3, 1) = +0.125 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 2)); m_dNxi(q, 4, 1) = -0.125 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 2)); m_dNxi(q, 5, 1) = -0.125 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 2)); m_dNxi(q, 6, 1) = +0.125 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 2)); m_dNxi(q, 7, 1) = +0.125 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 2)); // - dN / dxi_2 m_dNxi(q, 0, 2) = -0.125 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 1)); m_dNxi(q, 1, 2) = -0.125 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 1)); m_dNxi(q, 2, 2) = -0.125 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 1)); m_dNxi(q, 3, 2) = -0.125 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 1)); m_dNxi(q, 4, 2) = +0.125 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 1)); m_dNxi(q, 5, 2) = +0.125 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 1)); m_dNxi(q, 6, 2) = +0.125 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 1)); m_dNxi(q, 7, 2) = +0.125 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 1)); } GOOSEFEM_ASSERT(m_x.shape(1) == s_nne); GOOSEFEM_ASSERT(m_x.shape(2) == s_ndim); GOOSEFEM_ASSERT(xt::has_shape(m_xi, {m_nip, s_ndim})); GOOSEFEM_ASSERT(xt::has_shape(m_w, {m_nip})); GOOSEFEM_ASSERT(xt::has_shape(m_N, {m_nip, s_nne})); GOOSEFEM_ASSERT(xt::has_shape(m_dNxi, {m_nip, s_nne, s_ndim})); m_dNx = xt::empty({m_nelem, m_nip, s_nne, s_ndim}); m_vol = xt::empty(this->shape_qscalar()); this->compute_dN(); } private: friend QuadratureBase; friend QuadratureBaseCartesian; template void int_N_scalar_NT_dV_impl(const T& qscalar, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qscalar, this->shape_qscalar())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto M = xt::adapt(&elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto N = xt::adapt(&m_N(q, 0), xt::xshape()); auto& vol = m_vol(e, q); auto& rho = qscalar(e, q); // M(m * ndim + i, n * ndim + i) += N(m) * scalar * N(n) * dV for (size_t m = 0; m < s_nne; ++m) { for (size_t n = 0; n < s_nne; ++n) { M(m * s_ndim + 0, n * s_ndim + 0) += N(m) * rho * N(n) * vol; M(m * s_ndim + 1, n * s_ndim + 1) += N(m) * rho * N(n) * vol; M(m * s_ndim + 2, n * s_ndim + 2) += N(m) * rho * N(n) * vol; } } } } } template void int_gradN_dot_tensor2_dV_impl(const T& qtensor, R& elemvec) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); elemvec.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto f = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto sig = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); auto& v = m_vol(e, q); for (size_t m = 0; m < s_nne; ++m) { f(m, 0) += (dNx(m, 0) * sig(0, 0) + dNx(m, 1) * sig(1, 0) + dNx(m, 2) * sig(2, 0)) * v; f(m, 1) += (dNx(m, 0) * sig(0, 1) + dNx(m, 1) * sig(1, 1) + dNx(m, 2) * sig(2, 1)) * v; f(m, 2) += (dNx(m, 0) * sig(0, 2) + dNx(m, 1) * sig(1, 2) + dNx(m, 2) * sig(2, 2)) * v; } } } } constexpr static size_t s_nne = 8; ///< Number of nodes per element. constexpr static size_t s_ndim = 3; ///< Number of dimensions for nodal vectors. constexpr static size_t s_tdim = 3; ///< Number of dimensions for tensors. size_t m_tdim = 3; ///< Dynamic alias of s_tdim (remove in C++17) size_t m_nelem; ///< Number of elements. size_t m_nip; ///< Number of integration points per element. array_type::tensor m_x; ///< nodal positions stored per element [#nelem, #nne, #ndim] array_type::tensor m_w; ///< weight of each integration point [nip] array_type::tensor m_xi; ///< local coordinate per integration point [#nip, #ndim] array_type::tensor m_N; ///< shape functions [#nip, #nne] array_type::tensor m_dNxi; ///< local shape func grad [#nip, #nne, #ndim] array_type::tensor m_dNx; ///< global shape func grad [#nelem, #nip, #nne, #ndim] array_type::tensor m_vol; ///< integration point volume [#nelem, #nip] }; } // namespace Hex8 } // namespace Element } // namespace GooseFEM #endif diff --git a/include/GooseFEM/ElementQuad4.h b/include/GooseFEM/ElementQuad4.h index 59370bf..38e116f 100644 --- a/include/GooseFEM/ElementQuad4.h +++ b/include/GooseFEM/ElementQuad4.h @@ -1,553 +1,553 @@ /** -Quadrature for 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4), -in a Cartesian coordinate system. - -\file ElementQuad4.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Quadrature for 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4), + * in a Cartesian coordinate system. + * + * @file ElementQuad4.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ELEMENTQUAD4_H #define GOOSEFEM_ELEMENTQUAD4_H #include "Element.h" #include "config.h" #include "detail.h" namespace GooseFEM { namespace Element { /** -4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4). -*/ + * 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4). + */ namespace Quad4 { /** -Gauss quadrature: quadrature points such that integration is exact for this bi-linear element:: - - + ----------- + - | | - | 3 2 | - | | - | 0 1 | - | | - + ----------- + -*/ + * Gauss quadrature: quadrature points such that integration is exact for this bi-linear element:: + * + * + ----------- + + * | | + * | 3 2 | + * | | + * | 0 1 | + * | | + * + ----------- + + */ namespace Gauss { /** -Number of integration points: - - nip = nne = 4 - -\return unsigned int -*/ + * Number of integration points: + * + * nip = nne = 4 + * + * @return unsigned int + */ inline size_t nip() { return 4; } /** -Integration point coordinates (local coordinates). - -\return Coordinates [#nip, `ndim`], with `ndim = 2`. -*/ + * Integration point coordinates (local coordinates). + * + * @return Coordinates [#nip, `ndim`], with `ndim = 2`. + */ inline array_type::tensor xi() { size_t nip = 4; size_t ndim = 2; array_type::tensor xi = xt::empty({nip, ndim}); xi(0, 0) = -1.0 / std::sqrt(3.0); xi(0, 1) = -1.0 / std::sqrt(3.0); xi(1, 0) = +1.0 / std::sqrt(3.0); xi(1, 1) = -1.0 / std::sqrt(3.0); xi(2, 0) = +1.0 / std::sqrt(3.0); xi(2, 1) = +1.0 / std::sqrt(3.0); xi(3, 0) = -1.0 / std::sqrt(3.0); xi(3, 1) = +1.0 / std::sqrt(3.0); return xi; } /** -Integration point weights. - -\return Coordinates [#nip]. -*/ + * Integration point weights. + * + * @return Coordinates [#nip]. + */ inline array_type::tensor w() { size_t nip = 4; array_type::tensor w = xt::empty({nip}); w(0) = 1.0; w(1) = 1.0; w(2) = 1.0; w(3) = 1.0; return w; } } // namespace Gauss /** -nodal quadrature: quadrature points coincide with the nodes. -The order is the same as in the connectivity: - - 3 -- 2 - | | - 0 -- 1 -*/ + * nodal quadrature: quadrature points coincide with the nodes. + * The order is the same as in the connectivity: + * + * 3 -- 2 + * | | + * 0 -- 1 + */ namespace Nodal { /** -Number of integration points:: - - nip = nne = 4 - -\return unsigned int -*/ + * Number of integration points:: + * + * nip = nne = 4 + * + * @return unsigned int + */ inline size_t nip() { return 4; } /** -Integration point coordinates (local coordinates). - -\return Coordinates [#nip, `ndim`], with ``ndim = 2``. -*/ + * Integration point coordinates (local coordinates). + * + * @return Coordinates [#nip, `ndim`], with ``ndim = 2``. + */ inline array_type::tensor xi() { size_t nip = 4; size_t ndim = 2; array_type::tensor xi = xt::empty({nip, ndim}); xi(0, 0) = -1.0; xi(0, 1) = -1.0; xi(1, 0) = +1.0; xi(1, 1) = -1.0; xi(2, 0) = +1.0; xi(2, 1) = +1.0; xi(3, 0) = -1.0; xi(3, 1) = +1.0; return xi; } /** -Integration point weights. - -\return Coordinates [#nip]. -*/ + * Integration point weights. + * + * @return Coordinates [#nip]. + */ inline array_type::tensor w() { size_t nip = 4; array_type::tensor w = xt::empty({nip}); w(0) = 1.0; w(1) = 1.0; w(2) = 1.0; w(3) = 1.0; return w; } } // namespace Nodal /** -midpoint quadrature: quadrature points in the middle of the element:: - - + ------- + - | | - | 0 | - | | - + ------- + -*/ + * midpoint quadrature: quadrature points in the middle of the element:: + * + * + ------- + + * | | + * | 0 | + * | | + * + ------- + + */ namespace MidPoint { /** -Number of integration points:: - - nip = 1 - -\return unsigned int -*/ + * Number of integration points:: + * + * nip = 1 + * + * @return unsigned int + */ inline size_t nip() { return 1; } /** -Integration point coordinates (local coordinates). - -\return Coordinates [#nip, ``ndim``], with ``ndim = 2``. -*/ + * Integration point coordinates (local coordinates). + * + * @return Coordinates [#nip, ``ndim``], with ``ndim = 2``. + */ inline array_type::tensor xi() { size_t nip = 1; size_t ndim = 2; array_type::tensor xi = xt::empty({nip, ndim}); xi(0, 0) = 0.0; xi(0, 1) = 0.0; return xi; } /** -Integration point weights. - -\return Coordinates [#nip]. -*/ + * Integration point weights. + * + * @return Coordinates [#nip]. + */ inline array_type::tensor w() { size_t nip = 1; array_type::tensor w = xt::empty({nip}); w(0) = 1.0; return w; } } // namespace MidPoint /** -Interpolation and quadrature. - -Fixed dimensions: -- ``ndim = 2``: number of dimensions. -- ``nne = 4``: number of nodes per element. - -Naming convention: -- ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] -- ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] -- ``qtensor``: integration point tensor, [#nelem, #nip, #ndim, #ndim] -- ``qscalar``: integration point scalar, [#nelem, #nip] -*/ + * Interpolation and quadrature. + * + * Fixed dimensions: + * - ``ndim = 2``: number of dimensions. + * - ``nne = 4``: number of nodes per element. + * + * Naming convention: + * - ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] + * - ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] + * - ``qtensor``: integration point tensor, [#nelem, #nip, #ndim, #ndim] + * - ``qscalar``: integration point scalar, [#nelem, #nip] + */ class Quadrature : public QuadratureBaseCartesian { public: Quadrature() = default; /** - Constructor: use default Gauss integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - */ + * Constructor: use default Gauss integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + */ template Quadrature(const T& x) : Quadrature(x, Gauss::xi(), Gauss::w()) { } /** - Constructor with custom integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - \param xi Integration point coordinates (local coordinates) [#nip]. - \param w Integration point weights [#nip]. - */ + * Constructor with custom integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + * @param xi Integration point coordinates (local coordinates) [#nip]. + * @param w Integration point weights [#nip]. + */ template Quadrature(const T& x, const X& xi, const W& w) { m_x = x; m_w = w; m_xi = xi; m_nip = w.size(); m_nelem = m_x.shape(0); m_N = xt::empty({m_nip, s_nne}); m_dNxi = xt::empty({m_nip, s_nne, s_ndim}); for (size_t q = 0; q < m_nip; ++q) { m_N(q, 0) = 0.25 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 1)); m_N(q, 1) = 0.25 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 1)); m_N(q, 2) = 0.25 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 1)); m_N(q, 3) = 0.25 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 1)); } for (size_t q = 0; q < m_nip; ++q) { // - dN / dxi_0 m_dNxi(q, 0, 0) = -0.25 * (1.0 - xi(q, 1)); m_dNxi(q, 1, 0) = +0.25 * (1.0 - xi(q, 1)); m_dNxi(q, 2, 0) = +0.25 * (1.0 + xi(q, 1)); m_dNxi(q, 3, 0) = -0.25 * (1.0 + xi(q, 1)); // - dN / dxi_1 m_dNxi(q, 0, 1) = -0.25 * (1.0 - xi(q, 0)); m_dNxi(q, 1, 1) = -0.25 * (1.0 + xi(q, 0)); m_dNxi(q, 2, 1) = +0.25 * (1.0 + xi(q, 0)); m_dNxi(q, 3, 1) = +0.25 * (1.0 - xi(q, 0)); } GOOSEFEM_ASSERT(m_x.shape(1) == s_nne); GOOSEFEM_ASSERT(m_x.shape(2) == s_ndim); GOOSEFEM_ASSERT(xt::has_shape(m_xi, {m_nip, s_ndim})); GOOSEFEM_ASSERT(xt::has_shape(m_w, {m_nip})); GOOSEFEM_ASSERT(xt::has_shape(m_N, {m_nip, s_nne})); GOOSEFEM_ASSERT(xt::has_shape(m_dNxi, {m_nip, s_nne, s_ndim})); m_dNx = xt::empty({m_nelem, m_nip, s_nne, s_ndim}); m_vol = xt::empty(this->shape_qscalar()); this->compute_dN_impl(); } private: friend QuadratureBase; friend QuadratureBaseCartesian; template void interpQuad_vector_impl(const T& elemvec, R& qvector) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qvector, this->shape_qvector())); qvector.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto N = xt::adapt(&m_N(q, 0), xt::xshape()); auto ui = xt::adapt(&qvector(e, q, 0), xt::xshape()); ui(0) = N(0) * u(0, 0) + N(1) * u(1, 0) + N(2) * u(2, 0) + N(3) * u(3, 0); ui(1) = N(0) * u(0, 1) + N(1) * u(1, 1) + N(2) * u(2, 1) + N(3) * u(3, 1); } } } template void gradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto gradu = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(i,j) += dNx(m,i) * u(m,j) gradu(0, 0) = dNx(0, 0) * u(0, 0) + dNx(1, 0) * u(1, 0) + dNx(2, 0) * u(2, 0) + dNx(3, 0) * u(3, 0); gradu(0, 1) = dNx(0, 0) * u(0, 1) + dNx(1, 0) * u(1, 1) + dNx(2, 0) * u(2, 1) + dNx(3, 0) * u(3, 1); gradu(1, 0) = dNx(0, 1) * u(0, 0) + dNx(1, 1) * u(1, 0) + dNx(2, 1) * u(2, 0) + dNx(3, 1) * u(3, 0); gradu(1, 1) = dNx(0, 1) * u(0, 1) + dNx(1, 1) * u(1, 1) + dNx(2, 1) * u(2, 1) + dNx(3, 1) * u(3, 1); } } } template void gradN_vector_T_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto gradu = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(j,i) += dNx(m,i) * u(m,j) gradu(0, 0) = dNx(0, 0) * u(0, 0) + dNx(1, 0) * u(1, 0) + dNx(2, 0) * u(2, 0) + dNx(3, 0) * u(3, 0); gradu(1, 0) = dNx(0, 0) * u(0, 1) + dNx(1, 0) * u(1, 1) + dNx(2, 0) * u(2, 1) + dNx(3, 0) * u(3, 1); gradu(0, 1) = dNx(0, 1) * u(0, 0) + dNx(1, 1) * u(1, 0) + dNx(2, 1) * u(2, 0) + dNx(3, 1) * u(3, 0); gradu(1, 1) = dNx(0, 1) * u(0, 1) + dNx(1, 1) * u(1, 1) + dNx(2, 1) * u(2, 1) + dNx(3, 1) * u(3, 1); } } } template void symGradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto eps = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(i,j) += dNx(m,i) * u(m,j) // eps(j,i) = 0.5 * (gradu(i,j) + gradu(j,i)) eps(0, 0) = dNx(0, 0) * u(0, 0) + dNx(1, 0) * u(1, 0) + dNx(2, 0) * u(2, 0) + dNx(3, 0) * u(3, 0); eps(1, 1) = dNx(0, 1) * u(0, 1) + dNx(1, 1) * u(1, 1) + dNx(2, 1) * u(2, 1) + dNx(3, 1) * u(3, 1); eps(0, 1) = 0.5 * (dNx(0, 0) * u(0, 1) + dNx(1, 0) * u(1, 1) + dNx(2, 0) * u(2, 1) + dNx(3, 0) * u(3, 1) + dNx(0, 1) * u(0, 0) + dNx(1, 1) * u(1, 0) + dNx(2, 1) * u(2, 0) + dNx(3, 1) * u(3, 0)); eps(1, 0) = eps(0, 1); } } } template void int_N_scalar_NT_dV_impl(const T& qscalar, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qscalar, this->shape_qscalar())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto M = xt::adapt(&elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto N = xt::adapt(&m_N(q, 0), xt::xshape()); auto& vol = m_vol(e, q); auto& rho = qscalar(e, q); // M(m*ndim+i,n*ndim+i) += N(m) * scalar * N(n) * dV for (size_t m = 0; m < s_nne; ++m) { for (size_t n = 0; n < s_nne; ++n) { M(m * s_ndim + 0, n * s_ndim + 0) += N(m) * rho * N(n) * vol; M(m * s_ndim + 1, n * s_ndim + 1) += N(m) * rho * N(n) * vol; } } } } } template void int_gradN_dot_tensor2_dV_impl(const T& qtensor, R& elemvec) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); elemvec.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto f = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto sig = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); auto& vol = m_vol(e, q); for (size_t m = 0; m < s_nne; ++m) { f(m, 0) += (dNx(m, 0) * sig(0, 0) + dNx(m, 1) * sig(1, 0)) * vol; f(m, 1) += (dNx(m, 0) * sig(0, 1) + dNx(m, 1) * sig(1, 1)) * vol; } } } } void compute_dN_impl() { #pragma omp parallel { array_type::tensor J = xt::empty({2, 2}); array_type::tensor Jinv = xt::empty({2, 2}); #pragma omp for for (size_t e = 0; e < m_nelem; ++e) { auto x = xt::adapt(&m_x(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNxi = xt::adapt(&m_dNxi(q, 0, 0), xt::xshape()); auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); // J(i,j) += dNxi(m,i) * x(m,j); J(0, 0) = dNxi(0, 0) * x(0, 0) + dNxi(1, 0) * x(1, 0) + dNxi(2, 0) * x(2, 0) + dNxi(3, 0) * x(3, 0); J(0, 1) = dNxi(0, 0) * x(0, 1) + dNxi(1, 0) * x(1, 1) + dNxi(2, 0) * x(2, 1) + dNxi(3, 0) * x(3, 1); J(1, 0) = dNxi(0, 1) * x(0, 0) + dNxi(1, 1) * x(1, 0) + dNxi(2, 1) * x(2, 0) + dNxi(3, 1) * x(3, 0); J(1, 1) = dNxi(0, 1) * x(0, 1) + dNxi(1, 1) * x(1, 1) + dNxi(2, 1) * x(2, 1) + dNxi(3, 1) * x(3, 1); double Jdet = detail::tensor<2>::inv(J, Jinv); // dNx(m,i) += Jinv(i,j) * dNxi(m,j); for (size_t m = 0; m < s_nne; ++m) { dNx(m, 0) = Jinv(0, 0) * dNxi(m, 0) + Jinv(0, 1) * dNxi(m, 1); dNx(m, 1) = Jinv(1, 0) * dNxi(m, 0) + Jinv(1, 1) * dNxi(m, 1); } m_vol(e, q) = m_w(q) * Jdet; } } } } constexpr static size_t s_nne = 4; ///< Number of nodes per element. constexpr static size_t s_ndim = 2; ///< Number of dimensions for nodal vectors. constexpr static size_t s_tdim = 2; ///< Number of dimensions for tensors. size_t m_tdim = 2; ///< Dynamic alias of s_tdim (remove in C++17) size_t m_nelem; ///< Number of elements. size_t m_nip; ///< Number of integration points per element. array_type::tensor m_x; ///< nodal positions stored per element [#nelem, #nne, #ndim] array_type::tensor m_w; ///< weight of each integration point [nip] array_type::tensor m_xi; ///< local coordinate per integration point [#nip, #ndim] array_type::tensor m_N; ///< shape functions [#nip, #nne] array_type::tensor m_dNxi; ///< local shape func grad [#nip, #nne, #ndim] array_type::tensor m_dNx; ///< global shape func grad [#nelem, #nip, #nne, #ndim] array_type::tensor m_vol; ///< integration point volume [#nelem, #nip] }; } // namespace Quad4 } // namespace Element } // namespace GooseFEM #endif diff --git a/include/GooseFEM/ElementQuad4Axisymmetric.h b/include/GooseFEM/ElementQuad4Axisymmetric.h index e15c81d..f9cd9a7 100644 --- a/include/GooseFEM/ElementQuad4Axisymmetric.h +++ b/include/GooseFEM/ElementQuad4Axisymmetric.h @@ -1,465 +1,465 @@ /** -Quadrature for 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4), -in an axisymmetric coordinated system. - -\file ElementQuad4Axisymmetric.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Quadrature for 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4), + * in an axisymmetric coordinated system. + * + * @file ElementQuad4Axisymmetric.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ELEMENTQUAD4AXISYMMETRIC_H #define GOOSEFEM_ELEMENTQUAD4AXISYMMETRIC_H #include "config.h" namespace GooseFEM { namespace Element { namespace Quad4 { /** -Interpolation and quadrature. - -Fixed dimensions: -- ``ndim = 2``: number of dimensions. -- ``tdim = 3``: number of dimensions or tensor. -- ``nne = 4``: number of nodes per element. - -Naming convention: -- ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] -- ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] -- ``qtensor``: integration point tensor, [#nelem, #nip, #tdim, #tdim] -- ``qscalar``: integration point scalar, [#nelem, #nip] -*/ + * Interpolation and quadrature. + * + * Fixed dimensions: + * - ``ndim = 2``: number of dimensions. + * - ``tdim = 3``: number of dimensions or tensor. + * - ``nne = 4``: number of nodes per element. + * + * Naming convention: + * - ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] + * - ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] + * - ``qtensor``: integration point tensor, [#nelem, #nip, #tdim, #tdim] + * - ``qscalar``: integration point scalar, [#nelem, #nip] + */ class QuadratureAxisymmetric : public QuadratureBaseCartesian { public: QuadratureAxisymmetric() = default; /** - Constructor: use default Gauss integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - */ + * Constructor: use default Gauss integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + */ template QuadratureAxisymmetric(const T& x) : QuadratureAxisymmetric(x, Gauss::xi(), Gauss::w()) { } /** - Constructor with custom integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - \param xi Integration point coordinates (local coordinates) [#nip]. - \param w Integration point weights [#nip]. - */ + * Constructor with custom integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + * @param xi Integration point coordinates (local coordinates) [#nip]. + * @param w Integration point weights [#nip]. + */ template QuadratureAxisymmetric(const T& x, const X& xi, const W& w) { m_x = x; m_w = w; m_xi = xi; m_nip = w.size(); m_nelem = m_x.shape(0); GOOSEFEM_ASSERT(m_x.shape(1) == s_nne); GOOSEFEM_ASSERT(m_x.shape(2) == s_ndim); GOOSEFEM_ASSERT(xt::has_shape(m_xi, {m_nip, s_ndim})); GOOSEFEM_ASSERT(xt::has_shape(m_w, {m_nip})); m_N = xt::empty({m_nip, s_nne}); m_dNxi = xt::empty({m_nip, s_nne, s_ndim}); m_B = xt::empty({m_nelem, m_nip, s_nne, s_tdim, s_tdim, s_tdim}); m_vol = xt::empty(this->shape_qscalar()); for (size_t q = 0; q < m_nip; ++q) { m_N(q, 0) = 0.25 * (1.0 - m_xi(q, 0)) * (1.0 - m_xi(q, 1)); m_N(q, 1) = 0.25 * (1.0 + m_xi(q, 0)) * (1.0 - m_xi(q, 1)); m_N(q, 2) = 0.25 * (1.0 + m_xi(q, 0)) * (1.0 + m_xi(q, 1)); m_N(q, 3) = 0.25 * (1.0 - m_xi(q, 0)) * (1.0 + m_xi(q, 1)); } for (size_t q = 0; q < m_nip; ++q) { // - dN / dxi_0 m_dNxi(q, 0, 0) = -0.25 * (1.0 - m_xi(q, 1)); m_dNxi(q, 1, 0) = +0.25 * (1.0 - m_xi(q, 1)); m_dNxi(q, 2, 0) = +0.25 * (1.0 + m_xi(q, 1)); m_dNxi(q, 3, 0) = -0.25 * (1.0 + m_xi(q, 1)); // - dN / dxi_1 m_dNxi(q, 0, 1) = -0.25 * (1.0 - m_xi(q, 0)); m_dNxi(q, 1, 1) = -0.25 * (1.0 + m_xi(q, 0)); m_dNxi(q, 2, 1) = +0.25 * (1.0 + m_xi(q, 0)); m_dNxi(q, 3, 1) = +0.25 * (1.0 - m_xi(q, 0)); } this->compute_dN_impl(); } /** - Get the B-matrix (shape function gradients) (in global coordinates). - Note that the functions and their gradients are precomputed upon construction, - or updated when calling update_x(). - - \return ``B`` matrix stored per element, per integration point [#nelem, #nne, #tdim, #tdim, - #tdim] - */ + * Get the B-matrix (shape function gradients) (in global coordinates). + * Note that the functions and their gradients are precomputed upon construction, + * or updated when calling update_x(). + * + * @return ``B`` matrix stored per element, per integration point [#nelem, #nne, #tdim, #tdim, + * #tdim] + */ const array_type::tensor& B() const { return m_B; } private: friend QuadratureBase; friend QuadratureBaseCartesian; // qtensor(e, q, i, j) += B(e, q, m, i, j, k) * elemvec(e, q, m, k) template void gradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto B = xt::adapt(&m_B(e, q, 0, 0, 0, 0), xt::xshape()); auto gradu = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(i,j) += B(m,i,j,k) * u(m,perm(k)) // (where perm(0) = 1, perm(2) = 0) gradu(0, 0) = B(0, 0, 0, 0) * u(0, 1) + B(1, 0, 0, 0) * u(1, 1) + B(2, 0, 0, 0) * u(2, 1) + B(3, 0, 0, 0) * u(3, 1); gradu(1, 1) = B(0, 1, 1, 0) * u(0, 1) + B(1, 1, 1, 0) * u(1, 1) + B(2, 1, 1, 0) * u(2, 1) + B(3, 1, 1, 0) * u(3, 1); gradu(2, 2) = B(0, 2, 2, 2) * u(0, 0) + B(1, 2, 2, 2) * u(1, 0) + B(2, 2, 2, 2) * u(2, 0) + B(3, 2, 2, 2) * u(3, 0); gradu(0, 2) = B(0, 0, 2, 2) * u(0, 0) + B(1, 0, 2, 2) * u(1, 0) + B(2, 0, 2, 2) * u(2, 0) + B(3, 0, 2, 2) * u(3, 0); gradu(2, 0) = B(0, 2, 0, 0) * u(0, 1) + B(1, 2, 0, 0) * u(1, 1) + B(2, 2, 0, 0) * u(2, 1) + B(3, 2, 0, 0) * u(3, 1); } } } template void gradN_vector_T_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto B = xt::adapt(&m_B(e, q, 0, 0, 0, 0), xt::xshape()); auto gradu = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(j,i) += B(m,i,j,k) * u(m,perm(k)) // (where perm(0) = 1, perm(2) = 0) gradu(0, 0) = B(0, 0, 0, 0) * u(0, 1) + B(1, 0, 0, 0) * u(1, 1) + B(2, 0, 0, 0) * u(2, 1) + B(3, 0, 0, 0) * u(3, 1); gradu(1, 1) = B(0, 1, 1, 0) * u(0, 1) + B(1, 1, 1, 0) * u(1, 1) + B(2, 1, 1, 0) * u(2, 1) + B(3, 1, 1, 0) * u(3, 1); gradu(2, 2) = B(0, 2, 2, 2) * u(0, 0) + B(1, 2, 2, 2) * u(1, 0) + B(2, 2, 2, 2) * u(2, 0) + B(3, 2, 2, 2) * u(3, 0); gradu(2, 0) = B(0, 0, 2, 2) * u(0, 0) + B(1, 0, 2, 2) * u(1, 0) + B(2, 0, 2, 2) * u(2, 0) + B(3, 0, 2, 2) * u(3, 0); gradu(0, 2) = B(0, 2, 0, 0) * u(0, 1) + B(1, 2, 0, 0) * u(1, 1) + B(2, 2, 0, 0) * u(2, 1) + B(3, 2, 0, 0) * u(3, 1); } } } template void symGradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto B = xt::adapt(&m_B(e, q, 0, 0, 0, 0), xt::xshape()); auto eps = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(j,i) += B(m,i,j,k) * u(m,perm(k)) // eps(j,i) = 0.5 * (gradu(i,j) + gradu(j,i)) // (where perm(0) = 1, perm(2) = 0) eps(0, 0) = B(0, 0, 0, 0) * u(0, 1) + B(1, 0, 0, 0) * u(1, 1) + B(2, 0, 0, 0) * u(2, 1) + B(3, 0, 0, 0) * u(3, 1); eps(1, 1) = B(0, 1, 1, 0) * u(0, 1) + B(1, 1, 1, 0) * u(1, 1) + B(2, 1, 1, 0) * u(2, 1) + B(3, 1, 1, 0) * u(3, 1); eps(2, 2) = B(0, 2, 2, 2) * u(0, 0) + B(1, 2, 2, 2) * u(1, 0) + B(2, 2, 2, 2) * u(2, 0) + B(3, 2, 2, 2) * u(3, 0); eps(2, 0) = 0.5 * (B(0, 0, 2, 2) * u(0, 0) + B(1, 0, 2, 2) * u(1, 0) + B(2, 0, 2, 2) * u(2, 0) + B(3, 0, 2, 2) * u(3, 0) + B(0, 2, 0, 0) * u(0, 1) + B(1, 2, 0, 0) * u(1, 1) + B(2, 2, 0, 0) * u(2, 1) + B(3, 2, 0, 0) * u(3, 1)); eps(0, 2) = eps(2, 0); } } } // elemmat(e, q, m * ndim + i, n * ndim + i) += // N(e, q, m) * qscalar(e, q) * N(e, q, n) * dV(e, q) template void int_N_scalar_NT_dV_impl(const T& qscalar, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qscalar, this->shape_qscalar())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto M = xt::adapt(&elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto N = xt::adapt(&m_N(q, 0), xt::xshape()); auto& vol = m_vol(e, q); auto& rho = qscalar(e, q); // M(m*ndim+i,n*ndim+i) += N(m) * scalar * N(n) * dV for (size_t m = 0; m < s_nne; ++m) { for (size_t n = 0; n < s_nne; ++n) { M(m * s_ndim + 0, n * s_ndim + 0) += N(m) * rho * N(n) * vol; M(m * s_ndim + 1, n * s_ndim + 1) += N(m) * rho * N(n) * vol; } } } } } // fm = ( Bm^T : qtensor ) dV template void int_gradN_dot_tensor2_dV_impl(const T& qtensor, R& elemvec) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); elemvec.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto f = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto B = xt::adapt(&m_B(e, q, 0, 0, 0, 0), xt::xshape()); auto sig = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); auto& vol = m_vol(e, q); // f(m,i) += B(m,i,j,perm(k)) * sig(i,j) * dV // (where perm(0) = 1, perm(2) = 0) for (size_t m = 0; m < s_nne; ++m) { f(m, 0) += vol * (B(m, 2, 2, 2) * sig(2, 2) + B(m, 0, 2, 2) * sig(0, 2)); f(m, 1) += vol * (B(m, 0, 0, 0) * sig(0, 0) + B(m, 1, 1, 0) * sig(1, 1) + B(m, 2, 0, 0) * sig(2, 0)); } } } } // Kmn = ( Bm^T : qtensor : Bn ) dV template void int_gradN_dot_tensor4_dot_gradNT_dV_impl(const T& qtensor, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<4>())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto K = xt::adapt(&elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto B = xt::adapt(&m_B(e, q, 0, 0, 0, 0), xt::xshape()); auto C = xt::adapt( &qtensor(e, q, 0, 0, 0, 0), xt::xshape()); auto& vol = m_vol(e, q); // K(m*s_ndim+perm(c), n*s_ndim+perm(f)) = B(m,a,b,c) * C(a,b,d,e) * B(n,e,d,f) * // vol; (where perm(0) = 1, perm(2) = 0) for (size_t m = 0; m < s_nne; ++m) { for (size_t n = 0; n < s_nne; ++n) { K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 0, 0, 0) * C(0, 0, 0, 0) * B(n, 0, 0, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 0, 0, 0) * C(0, 0, 1, 1) * B(n, 1, 1, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 0) += B(m, 0, 0, 0) * C(0, 0, 2, 2) * B(n, 2, 2, 2) * vol; K(m * s_ndim + 1, n * s_ndim + 0) += B(m, 0, 0, 0) * C(0, 0, 2, 0) * B(n, 0, 2, 2) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 0, 0, 0) * C(0, 0, 0, 2) * B(n, 2, 0, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 1, 1, 0) * C(1, 1, 0, 0) * B(n, 0, 0, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 1, 1, 0) * C(1, 1, 1, 1) * B(n, 1, 1, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 0) += B(m, 1, 1, 0) * C(1, 1, 2, 2) * B(n, 2, 2, 2) * vol; K(m * s_ndim + 1, n * s_ndim + 0) += B(m, 1, 1, 0) * C(1, 1, 2, 0) * B(n, 0, 2, 2) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 1, 1, 0) * C(1, 1, 0, 2) * B(n, 2, 0, 0) * vol; K(m * s_ndim + 0, n * s_ndim + 1) += B(m, 2, 2, 2) * C(2, 2, 0, 0) * B(n, 0, 0, 0) * vol; K(m * s_ndim + 0, n * s_ndim + 1) += B(m, 2, 2, 2) * C(2, 2, 1, 1) * B(n, 1, 1, 0) * vol; K(m * s_ndim + 0, n * s_ndim + 0) += B(m, 2, 2, 2) * C(2, 2, 2, 2) * B(n, 2, 2, 2) * vol; K(m * s_ndim + 0, n * s_ndim + 0) += B(m, 2, 2, 2) * C(2, 2, 2, 0) * B(n, 0, 2, 2) * vol; K(m * s_ndim + 0, n * s_ndim + 1) += B(m, 2, 2, 2) * C(2, 2, 0, 2) * B(n, 2, 0, 0) * vol; K(m * s_ndim + 0, n * s_ndim + 1) += B(m, 0, 2, 2) * C(0, 2, 0, 0) * B(n, 0, 0, 0) * vol; K(m * s_ndim + 0, n * s_ndim + 1) += B(m, 0, 2, 2) * C(0, 2, 1, 1) * B(n, 1, 1, 0) * vol; K(m * s_ndim + 0, n * s_ndim + 0) += B(m, 0, 2, 2) * C(0, 2, 2, 2) * B(n, 2, 2, 2) * vol; K(m * s_ndim + 0, n * s_ndim + 0) += B(m, 0, 2, 2) * C(0, 2, 2, 0) * B(n, 0, 2, 2) * vol; K(m * s_ndim + 0, n * s_ndim + 1) += B(m, 0, 2, 2) * C(0, 2, 0, 2) * B(n, 2, 0, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 2, 0, 0) * C(2, 0, 0, 0) * B(n, 0, 0, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 2, 0, 0) * C(2, 0, 1, 1) * B(n, 1, 1, 0) * vol; K(m * s_ndim + 1, n * s_ndim + 0) += B(m, 2, 0, 0) * C(2, 0, 2, 2) * B(n, 2, 2, 2) * vol; K(m * s_ndim + 1, n * s_ndim + 0) += B(m, 2, 0, 0) * C(2, 0, 2, 0) * B(n, 0, 2, 2) * vol; K(m * s_ndim + 1, n * s_ndim + 1) += B(m, 2, 0, 0) * C(2, 0, 0, 2) * B(n, 2, 0, 0) * vol; } } } } } void compute_dN_impl() { // most components remain zero, and are not written m_B.fill(0.0); #pragma omp parallel { array_type::tensor J = xt::empty({2, 2}); array_type::tensor Jinv = xt::empty({2, 2}); #pragma omp for for (size_t e = 0; e < m_nelem; ++e) { auto x = xt::adapt(&m_x(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNxi = xt::adapt(&m_dNxi(q, 0, 0), xt::xshape()); auto B = xt::adapt( &m_B(e, q, 0, 0, 0, 0), xt::xshape()); auto N = xt::adapt(&m_N(q, 0), xt::xshape()); // J(i,j) += dNxi(m,i) * x(m,j); J(0, 0) = dNxi(0, 0) * x(0, 0) + dNxi(1, 0) * x(1, 0) + dNxi(2, 0) * x(2, 0) + dNxi(3, 0) * x(3, 0); J(0, 1) = dNxi(0, 0) * x(0, 1) + dNxi(1, 0) * x(1, 1) + dNxi(2, 0) * x(2, 1) + dNxi(3, 0) * x(3, 1); J(1, 0) = dNxi(0, 1) * x(0, 0) + dNxi(1, 1) * x(1, 0) + dNxi(2, 1) * x(2, 0) + dNxi(3, 1) * x(3, 0); J(1, 1) = dNxi(0, 1) * x(0, 1) + dNxi(1, 1) * x(1, 1) + dNxi(2, 1) * x(2, 1) + dNxi(3, 1) * x(3, 1); double Jdet = detail::tensor<2>::inv(J, Jinv); // radius for computation of volume double rq = N(0) * x(0, 1) + N(1) * x(1, 1) + N(2) * x(2, 1) + N(3) * x(3, 1); // dNx(m,i) += Jinv(i,j) * dNxi(m,j) for (size_t m = 0; m < s_nne; ++m) { // B(m, r, r, r) = dNdx(m,1) B(m, 0, 0, 0) = Jinv(1, 0) * dNxi(m, 0) + Jinv(1, 1) * dNxi(m, 1); // B(m, r, z, z) = dNdx(m,1) B(m, 0, 2, 2) = Jinv(1, 0) * dNxi(m, 0) + Jinv(1, 1) * dNxi(m, 1); // B(m, t, t, r) B(m, 1, 1, 0) = 1.0 / rq * N(m); // B(m, z, r, r) = dNdx(m,0) B(m, 2, 0, 0) = Jinv(0, 0) * dNxi(m, 0) + Jinv(0, 1) * dNxi(m, 1); // B(m, z, z, z) = dNdx(m,0) B(m, 2, 2, 2) = Jinv(0, 0) * dNxi(m, 0) + Jinv(0, 1) * dNxi(m, 1); } m_vol(e, q) = m_w(q) * Jdet * 2.0 * M_PI * rq; } } } } constexpr static size_t s_nne = 4; ///< Number of nodes per element. constexpr static size_t s_ndim = 2; ///< Number of dimensions for nodal vectors. constexpr static size_t s_tdim = 3; ///< Number of dimensions for tensors. size_t m_tdim = 3; ///< Dynamic alias of s_tdim (remove in C++17) size_t m_nelem; ///< Number of elements. size_t m_nip; ///< Number of integration points per element. array_type::tensor m_x; ///< nodal positions stored per element [#nelem, #nne, #ndim] array_type::tensor m_w; ///< weight per integration point [nip] array_type::tensor m_xi; ///< local coordinate per integration point [#nip, #ndim] array_type::tensor m_N; ///< shape functions [#nip, #nne] array_type::tensor m_dNxi; ///< local shape func grad [#nip, #nne, #ndim] array_type::tensor m_vol; ///< integration point volume [#nelem, #nip] array_type::tensor m_B; ///< B-matrix [#nelem, #nne, #tdim, #tdim, #tdim] }; } // namespace Quad4 } // namespace Element } // namespace GooseFEM #endif diff --git a/include/GooseFEM/ElementQuad4Planar.h b/include/GooseFEM/ElementQuad4Planar.h index abacc63..f1a4d07 100644 --- a/include/GooseFEM/ElementQuad4Planar.h +++ b/include/GooseFEM/ElementQuad4Planar.h @@ -1,340 +1,341 @@ /** -Quadrature for 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4), -in a Cartesian coordinate system. -The different with ElementQuad4.h is that here the tensors live in 3d and are assumed plane strain. - -\file ElementQuad4Planar.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Quadrature for 4-noded quadrilateral element in 2d (GooseFEM::Mesh::ElementType::Quad4), + * in a Cartesian coordinate system. + * The different with ElementQuad4.h is that here the tensors live in 3d and are assumed plane + * strain. + * + * @file ElementQuad4Planar.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ELEMENTQUAD4PLANAR_H #define GOOSEFEM_ELEMENTQUAD4PLANAR_H #include "config.h" #include "detail.h" namespace GooseFEM { namespace Element { namespace Quad4 { /** -Interpolation and quadrature. -Similar to Element::Quad4::Quadrature with the only different that quadrature point tensors -are 3d ("plane strain") while the mesh is 2d. - -Fixed dimensions: -- ``ndim = 2``: number of dimensions. -- ``tdim = 3``: number of dimensions or tensor. -- ``nne = 4``: number of nodes per element. - -Naming convention: -- ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] -- ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] -- ``qtensor``: integration point tensor, [#nelem, #nip, #tdim, #tdim] -- ``qscalar``: integration point scalar, [#nelem, #nip] -*/ + * Interpolation and quadrature. + * Similar to Element::Quad4::Quadrature with the only different that quadrature point tensors + * are 3d ("plane strain") while the mesh is 2d. + * + * Fixed dimensions: + * - ``ndim = 2``: number of dimensions. + * - ``tdim = 3``: number of dimensions or tensor. + * - ``nne = 4``: number of nodes per element. + * + * Naming convention: + * - ``elemmat``: matrices stored per element, [#nelem, #nne * #ndim, #nne * #ndim] + * - ``elemvec``: nodal vectors stored per element, [#nelem, #nne, #ndim] + * - ``qtensor``: integration point tensor, [#nelem, #nip, #tdim, #tdim] + * - ``qscalar``: integration point scalar, [#nelem, #nip] + */ class QuadraturePlanar : public QuadratureBaseCartesian { public: QuadraturePlanar() = default; /** - Constructor: use default Gauss integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - \param thick out-of-plane thickness (incorporated in the element volume). - */ + * Constructor: use default Gauss integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + * @param thick out-of-plane thickness (incorporated in the element volume). + */ template QuadraturePlanar(const T& x, double thick = 1.0) : QuadraturePlanar(x, Gauss::xi(), Gauss::w(), thick) { } /** - Constructor with custom integration. - The following is pre-computed during construction: - - the shape functions, - - the shape function gradients (in local and global) coordinates, - - the integration points volumes. - They can be reused without any cost. - They only have to be recomputed when the nodal position changes - (note that they are assumed to be constant under a small-strain assumption). - In that case use update_x() to update the nodal positions and - to recompute the above listed quantities. - - \param x nodal coordinates (``elemvec``). - \param xi Integration point coordinates (local coordinates) [#nip]. - \param w Integration point weights [#nip]. - \param thick out-of-plane thickness (incorporated in the element volume). - */ + * Constructor with custom integration. + * The following is pre-computed during construction: + * - the shape functions, + * - the shape function gradients (in local and global) coordinates, + * - the integration points volumes. + * They can be reused without any cost. + * They only have to be recomputed when the nodal position changes + * (note that they are assumed to be constant under a small-strain assumption). + * In that case use update_x() to update the nodal positions and + * to recompute the above listed quantities. + * + * @param x nodal coordinates (``elemvec``). + * @param xi Integration point coordinates (local coordinates) [#nip]. + * @param w Integration point weights [#nip]. + * @param thick out-of-plane thickness (incorporated in the element volume). + */ template QuadraturePlanar(const T& x, const X& xi, const W& w, double thick = 1.0) { m_x = x; m_w = w; m_xi = xi; m_nip = w.size(); m_nelem = m_x.shape(0); m_N = xt::empty({m_nip, s_nne}); m_dNxi = xt::empty({m_nip, s_nne, s_ndim}); m_thick = thick; for (size_t q = 0; q < m_nip; ++q) { m_N(q, 0) = 0.25 * (1.0 - xi(q, 0)) * (1.0 - xi(q, 1)); m_N(q, 1) = 0.25 * (1.0 + xi(q, 0)) * (1.0 - xi(q, 1)); m_N(q, 2) = 0.25 * (1.0 + xi(q, 0)) * (1.0 + xi(q, 1)); m_N(q, 3) = 0.25 * (1.0 - xi(q, 0)) * (1.0 + xi(q, 1)); } for (size_t q = 0; q < m_nip; ++q) { // - dN / dxi_0 m_dNxi(q, 0, 0) = -0.25 * (1.0 - xi(q, 1)); m_dNxi(q, 1, 0) = +0.25 * (1.0 - xi(q, 1)); m_dNxi(q, 2, 0) = +0.25 * (1.0 + xi(q, 1)); m_dNxi(q, 3, 0) = -0.25 * (1.0 + xi(q, 1)); // - dN / dxi_1 m_dNxi(q, 0, 1) = -0.25 * (1.0 - xi(q, 0)); m_dNxi(q, 1, 1) = -0.25 * (1.0 + xi(q, 0)); m_dNxi(q, 2, 1) = +0.25 * (1.0 + xi(q, 0)); m_dNxi(q, 3, 1) = +0.25 * (1.0 - xi(q, 0)); } GOOSEFEM_ASSERT(m_x.shape(1) == s_nne); GOOSEFEM_ASSERT(m_x.shape(2) == s_ndim); GOOSEFEM_ASSERT(xt::has_shape(m_xi, {m_nip, s_ndim})); GOOSEFEM_ASSERT(xt::has_shape(m_w, {m_nip})); GOOSEFEM_ASSERT(xt::has_shape(m_N, {m_nip, s_nne})); GOOSEFEM_ASSERT(xt::has_shape(m_dNxi, {m_nip, s_nne, s_ndim})); m_dNx = xt::empty({m_nelem, m_nip, s_nne, s_ndim}); m_vol = xt::empty(this->shape_qscalar()); this->compute_dN_impl(); } private: friend QuadratureBase; friend QuadratureBaseCartesian; template void gradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto gradu = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(i,j) += dNx(m,i) * u(m,j) gradu(0, 0) = dNx(0, 0) * u(0, 0) + dNx(1, 0) * u(1, 0) + dNx(2, 0) * u(2, 0) + dNx(3, 0) * u(3, 0); gradu(0, 1) = dNx(0, 0) * u(0, 1) + dNx(1, 0) * u(1, 1) + dNx(2, 0) * u(2, 1) + dNx(3, 0) * u(3, 1); gradu(1, 0) = dNx(0, 1) * u(0, 0) + dNx(1, 1) * u(1, 0) + dNx(2, 1) * u(2, 0) + dNx(3, 1) * u(3, 0); gradu(1, 1) = dNx(0, 1) * u(0, 1) + dNx(1, 1) * u(1, 1) + dNx(2, 1) * u(2, 1) + dNx(3, 1) * u(3, 1); } } } template void gradN_vector_T_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto gradu = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(j,i) += dNx(m,i) * u(m,j) gradu(0, 0) = dNx(0, 0) * u(0, 0) + dNx(1, 0) * u(1, 0) + dNx(2, 0) * u(2, 0) + dNx(3, 0) * u(3, 0); gradu(1, 0) = dNx(0, 0) * u(0, 1) + dNx(1, 0) * u(1, 1) + dNx(2, 0) * u(2, 1) + dNx(3, 0) * u(3, 1); gradu(0, 1) = dNx(0, 1) * u(0, 0) + dNx(1, 1) * u(1, 0) + dNx(2, 1) * u(2, 0) + dNx(3, 1) * u(3, 0); gradu(1, 1) = dNx(0, 1) * u(0, 1) + dNx(1, 1) * u(1, 1) + dNx(2, 1) * u(2, 1) + dNx(3, 1) * u(3, 1); } } } template void symGradN_vector_impl(const T& elemvec, R& qtensor) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); qtensor.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto u = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto eps = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); // gradu(i,j) += dNx(m,i) * u(m,j) // eps(j,i) = 0.5 * (gradu(i,j) + gradu(j,i)) eps(0, 0) = dNx(0, 0) * u(0, 0) + dNx(1, 0) * u(1, 0) + dNx(2, 0) * u(2, 0) + dNx(3, 0) * u(3, 0); eps(1, 1) = dNx(0, 1) * u(0, 1) + dNx(1, 1) * u(1, 1) + dNx(2, 1) * u(2, 1) + dNx(3, 1) * u(3, 1); eps(0, 1) = 0.5 * (dNx(0, 0) * u(0, 1) + dNx(1, 0) * u(1, 1) + dNx(2, 0) * u(2, 1) + dNx(3, 0) * u(3, 1) + dNx(0, 1) * u(0, 0) + dNx(1, 1) * u(1, 0) + dNx(2, 1) * u(2, 0) + dNx(3, 1) * u(3, 0)); eps(1, 0) = eps(0, 1); } } } template void int_N_scalar_NT_dV_impl(const T& qscalar, R& elemmat) const { GOOSEFEM_ASSERT(xt::has_shape(qscalar, this->shape_qscalar())); GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); elemmat.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto M = xt::adapt(&elemmat(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto N = xt::adapt(&m_N(q, 0), xt::xshape()); auto& vol = m_vol(e, q); auto& rho = qscalar(e, q); // M(m*ndim+i,n*ndim+i) += N(m) * scalar * N(n) * dV for (size_t m = 0; m < s_nne; ++m) { for (size_t n = 0; n < s_nne; ++n) { M(m * s_ndim + 0, n * s_ndim + 0) += N(m) * rho * N(n) * vol; M(m * s_ndim + 1, n * s_ndim + 1) += N(m) * rho * N(n) * vol; } } } } } template void int_gradN_dot_tensor2_dV_impl(const T& qtensor, R& elemvec) const { GOOSEFEM_ASSERT(xt::has_shape(qtensor, this->shape_qtensor<2>())); GOOSEFEM_ASSERT(xt::has_shape(elemvec, this->shape_elemvec())); elemvec.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { auto f = xt::adapt(&elemvec(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); auto sig = xt::adapt(&qtensor(e, q, 0, 0), xt::xshape()); auto& vol = m_vol(e, q); for (size_t m = 0; m < s_nne; ++m) { f(m, 0) += (dNx(m, 0) * sig(0, 0) + dNx(m, 1) * sig(1, 0)) * vol; f(m, 1) += (dNx(m, 0) * sig(0, 1) + dNx(m, 1) * sig(1, 1)) * vol; } } } } void compute_dN_impl() { #pragma omp parallel { array_type::tensor J = xt::empty({2, 2}); array_type::tensor Jinv = xt::empty({2, 2}); #pragma omp for for (size_t e = 0; e < m_nelem; ++e) { auto x = xt::adapt(&m_x(e, 0, 0), xt::xshape()); for (size_t q = 0; q < m_nip; ++q) { auto dNxi = xt::adapt(&m_dNxi(q, 0, 0), xt::xshape()); auto dNx = xt::adapt(&m_dNx(e, q, 0, 0), xt::xshape()); // J(i,j) += dNxi(m,i) * x(m,j); J(0, 0) = dNxi(0, 0) * x(0, 0) + dNxi(1, 0) * x(1, 0) + dNxi(2, 0) * x(2, 0) + dNxi(3, 0) * x(3, 0); J(0, 1) = dNxi(0, 0) * x(0, 1) + dNxi(1, 0) * x(1, 1) + dNxi(2, 0) * x(2, 1) + dNxi(3, 0) * x(3, 1); J(1, 0) = dNxi(0, 1) * x(0, 0) + dNxi(1, 1) * x(1, 0) + dNxi(2, 1) * x(2, 0) + dNxi(3, 1) * x(3, 0); J(1, 1) = dNxi(0, 1) * x(0, 1) + dNxi(1, 1) * x(1, 1) + dNxi(2, 1) * x(2, 1) + dNxi(3, 1) * x(3, 1); double Jdet = detail::tensor<2>::inv(J, Jinv); // dNx(m,i) += Jinv(i,j) * dNxi(m,j); for (size_t m = 0; m < s_nne; ++m) { dNx(m, 0) = Jinv(0, 0) * dNxi(m, 0) + Jinv(0, 1) * dNxi(m, 1); dNx(m, 1) = Jinv(1, 0) * dNxi(m, 0) + Jinv(1, 1) * dNxi(m, 1); } m_vol(e, q) = m_w(q) * Jdet * m_thick; } } } } constexpr static size_t s_nne = 4; ///< Number of nodes per element. constexpr static size_t s_ndim = 2; ///< Number of dimensions for nodal vectors. constexpr static size_t s_tdim = 3; ///< Dynamic alias of s_tdim (remove in C++17) size_t m_tdim = 3; ///< Number of dimensions for tensors. size_t m_nelem; ///< Number of elements. size_t m_nip; ///< Number of integration points per element. array_type::tensor m_x; ///< nodal positions stored per element [#nelem, #nne, #ndim] array_type::tensor m_w; ///< weight of each integration point [nip] array_type::tensor m_xi; ///< local coordinate per integration point [#nip, #ndim] array_type::tensor m_N; ///< shape functions [#nip, #nne] array_type::tensor m_dNxi; ///< local shape func grad [#nip, #nne, #ndim] array_type::tensor m_dNx; ///< global shape func grad [#nelem, #nip, #nne, #ndim] array_type::tensor m_vol; ///< integration point volume [#nelem, #nip] double m_thick; ///< out-of-plane thickness }; } // namespace Quad4 } // namespace Element } // namespace GooseFEM #endif diff --git a/include/GooseFEM/GooseFEM.h b/include/GooseFEM/GooseFEM.h index 13e87c4..d2ec195 100644 --- a/include/GooseFEM/GooseFEM.h +++ b/include/GooseFEM/GooseFEM.h @@ -1,43 +1,43 @@ /** -Basic include of common methods. - -\file GooseFEM.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Basic include of common methods. + * + * @file GooseFEM.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_H #define GOOSEFEM_H #ifdef EIGEN_WORLD_VERSION #define GOOSEFEM_EIGEN #endif #include "assertions.h" #include "version.h" #include "Allocate.h" #include "Element.h" #include "ElementHex8.h" #include "ElementQuad4.h" #include "ElementQuad4Axisymmetric.h" #include "ElementQuad4Planar.h" #include "Iterate.h" #include "MatrixDiagonal.h" #include "MatrixDiagonalPartitioned.h" #include "Mesh.h" #include "MeshHex8.h" #include "MeshQuad4.h" #include "MeshTri3.h" #include "Vector.h" #include "VectorPartitioned.h" #ifdef GOOSEFEM_EIGEN #include "Matrix.h" #include "MatrixPartitioned.h" #include "MatrixPartitionedTyings.h" #include "TyingsPeriodic.h" #include "VectorPartitionedTyings.h" #endif #endif diff --git a/include/GooseFEM/Iterate.h b/include/GooseFEM/Iterate.h index 6a5403f..f566f99 100644 --- a/include/GooseFEM/Iterate.h +++ b/include/GooseFEM/Iterate.h @@ -1,119 +1,119 @@ /** -Support function for iterations. - -\file Iterate.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Support function for iterations. + * + * @file Iterate.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ITERATE_H #define GOOSEFEM_ITERATE_H #include "config.h" namespace GooseFEM { /** -Support function for iterations in end-user programs. -*/ + * Support function for iterations in end-user programs. + */ namespace Iterate { /** -Class to perform a residual check based on the last "n" iterations. -A typical usage is in dynamic simulations where equilibrium is checked based on a force residual. -Fluctuations could however be responsible for this criterion to be triggered too early. -By checking several time-steps such case can be avoided. -*/ + * Class to perform a residual check based on the last "n" iterations. + * A typical usage is in dynamic simulations where equilibrium is checked based on a force residual. + * Fluctuations could however be responsible for this criterion to be triggered too early. + * By checking several time-steps such case can be avoided. + */ class StopList { public: /** - Constructor. - - \param n Number of consecutive iterations to consider. - */ + * Constructor. + * + * @param n Number of consecutive iterations to consider. + */ StopList(size_t n = 1) { m_res.resize(n); reset(); } /** - Reset all residuals to infinity. - */ + * Reset all residuals to infinity. + */ void reset() { std::fill(m_res.begin(), m_res.end(), std::numeric_limits::infinity()); } /** - Reset all residuals to infinity, and change the number of residuals to check. - - \param n Number of consecutive iterations to consider. - */ + * Reset all residuals to infinity, and change the number of residuals to check. + * + * @param n Number of consecutive iterations to consider. + */ void reset(size_t n) { m_res.resize(n); reset(); } /** - Roll the list with the residuals, and add a new residual to the end. - In Python code this function corresponds to:: - - residuals = residuals[1:] + [new_residual] - - I.e. the residual of `n` iterations ago will be forgotten. - - \param res New residual to add to the list of residuals. - */ + * Roll the list with the residuals, and add a new residual to the end. + * In Python code this function corresponds to:: + * + * residuals = residuals[1:] + [new_residual] + * + * I.e. the residual of `n` iterations ago will be forgotten. + * + * @param res New residual to add to the list of residuals. + */ void roll_insert(double res) { std::rotate(m_res.begin(), m_res.begin() + 1, m_res.end()); m_res.back() = res; } /** - Check of the sequence of `n` residuals is in descending order. - - \return `true` if the `n` residuals are in descending order. - */ + * Check of the sequence of `n` residuals is in descending order. + * + * @return `true` if the `n` residuals are in descending order. + */ bool descending() const { return std::is_sorted(m_res.cbegin(), m_res.cend(), std::greater()); } /** - Check of the sequence of `n` residuals are all below a tolerance. - - \param tol Tolerance. - \return `true` if all `n` residuals are less than the tolerance. - */ + * Check of the sequence of `n` residuals are all below a tolerance. + * + * @param tol Tolerance. + * @return `true` if all `n` residuals are less than the tolerance. + */ bool all_less(double tol) const { return !std::any_of(m_res.cbegin(), m_res.cend(), [=](const auto& i) { return i >= tol; }); } /** - Get the historic residuals. - */ + * Get the historic residuals. + */ const std::vector& data() const { return m_res; } /** - Get the historic residuals. - */ + * Get the historic residuals. + */ [[deprecated]] const std::vector& get() const { return m_res; } private: std::vector m_res; ///< List with residuals. }; } // namespace Iterate } // namespace GooseFEM #endif diff --git a/include/GooseFEM/Matrix.h b/include/GooseFEM/Matrix.h index 1bdaa9a..5b4551a 100644 --- a/include/GooseFEM/Matrix.h +++ b/include/GooseFEM/Matrix.h @@ -1,909 +1,909 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MATRIX_H #define GOOSEFEM_MATRIX_H #include "config.h" #include #include #include namespace GooseFEM { // forward declaration template class MatrixSolver; /** -CRTP base class for a solver class. -*/ + * CRTP base class for a solver class. + */ template class MatrixSolverBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). - \param b nodevec [nelem, ndim]. - \param x (overwritten) nodevec [nelem, ndim]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). + * @param b nodevec [nelem, ndim]. + * @param x (overwritten) nodevec [nelem, ndim]. + */ template void solve(M& A, const array_type::tensor& b, array_type::tensor& x) { GOOSEFEM_ASSERT(xt::has_shape(b, A.shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(x, A.shape_nodevec())); derived_cast().solve_nodevec_impl(A, b, x); } /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). - \param b dofval [ndof]. - \param x (overwritten) dofval [ndof]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). + * @param b dofval [ndof]. + * @param x (overwritten) dofval [ndof]. + */ template void solve(M& A, const array_type::tensor& b, array_type::tensor& x) { GOOSEFEM_ASSERT(xt::has_shape(b, A.shape_dofval())); GOOSEFEM_ASSERT(xt::has_shape(x, A.shape_dofval())); derived_cast().solve_dofval_impl(A, b, x); } }; /** -CRTP base class for a solver class. -*/ + * CRTP base class for a solver class. + */ template class MatrixSolverSingleBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). - \param b nodevec [nelem, ndim]. - \return x nodevec [nelem, ndim]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). + * @param b nodevec [nelem, ndim]. + * @return x nodevec [nelem, ndim]. + */ template array_type::tensor Solve(M& A, const array_type::tensor& b) { GOOSEFEM_ASSERT(xt::has_shape(b, A.shape_nodevec())); array_type::tensor x = xt::empty_like(b); derived_cast().solve_nodevec_impl(A, b, x); return x; } /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). - \param b dofval [ndof]. - \return x dofval [ndof]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). + * @param b dofval [ndof]. + * @return x dofval [ndof]. + */ template array_type::tensor Solve(M& A, const array_type::tensor& b) { GOOSEFEM_ASSERT(xt::has_shape(b, A.shape_dofval())); array_type::tensor x = xt::empty_like(b); derived_cast().solve_dofval_impl(A, b, x); return x; } }; /** -CRTP base class for a extra functions for a partitioned solver class. -*/ + * CRTP base class for a extra functions for a partitioned solver class. + */ template class MatrixSolverPartitionedBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). - \param b nodevec [nelem, ndim]. - \param x nodevec [nelem, ndim]. - \return x nodevec [nelem, ndim]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). + * @param b nodevec [nelem, ndim]. + * @param x nodevec [nelem, ndim]. + * @return x nodevec [nelem, ndim]. + */ template array_type::tensor Solve(M& A, const array_type::tensor& b, const array_type::tensor& x) { GOOSEFEM_ASSERT(xt::has_shape(b, A.shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(x, A.shape_nodevec())); array_type::tensor ret = x; derived_cast().solve_nodevec_impl(A, b, ret); return ret; } /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). - \param b dofval [ndof]. - \param x nodevec [nelem, ndim]. - \return x dofval [ndof]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::Matrix(). + * @param b dofval [ndof]. + * @param x nodevec [nelem, ndim]. + * @return x dofval [ndof]. + */ template array_type::tensor Solve(M& A, const array_type::tensor& b, const array_type::tensor& x) { GOOSEFEM_ASSERT(xt::has_shape(b, A.shape_dofval())); GOOSEFEM_ASSERT(xt::has_shape(x, A.shape_dofval())); array_type::tensor ret = x; derived_cast().solve_dofval_impl(A, b, ret); return ret; } }; /** -CRTP base class for a matrix. -*/ + * CRTP base class for a matrix. + */ template class MatrixBase { protected: array_type::tensor m_conn; ///< Connectivity [#nelem, #nne]. array_type::tensor m_dofs; ///< DOF-numbers per node [#nnode, #ndim]. size_t m_nelem; ///< See nelem(). size_t m_nne; ///< See nne(). size_t m_nnode; ///< See nnode(). size_t m_ndim; ///< See ndim(). size_t m_ndof; ///< See ndof(). bool m_changed = true; ///< Signal changes to data. public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Number of elements. - \return Unsigned integer. - */ + * Number of elements. + * @return Unsigned integer. + */ size_t nelem() const { return derived_cast().m_nelem; } /** - Number of nodes per element. - \return Unsigned integer. - */ + * Number of nodes per element. + * @return Unsigned integer. + */ size_t nne() const { return derived_cast().m_nne; } /** - Number of nodes. - \return Unsigned integer. - */ + * Number of nodes. + * @return Unsigned integer. + */ size_t nnode() const { return derived_cast().m_nnode; } /** - Number of dimensions. - \return Unsigned integer. - */ + * Number of dimensions. + * @return Unsigned integer. + */ size_t ndim() const { return derived_cast().m_ndim; } /** - Number of DOFs. - \return Unsigned integer. - */ + * Number of DOFs. + * @return Unsigned integer. + */ size_t ndof() const { return derived_cast().m_ndof; } /** - DOFs per node. - \return [#nnode, #ndim]. - */ + * DOFs per node. + * @return [#nnode, #ndim]. + */ const array_type::tensor& dofs() const { return derived_cast().m_dofs; } /** - Connectivity. - \return [#nelem, #nne]. - */ + * Connectivity. + * @return [#nelem, #nne]. + */ const array_type::tensor& conn() const { return derived_cast().m_conn; } /** - Shape of "dofval". - \return [#ndof]. - */ + * Shape of "dofval". + * @return [#ndof]. + */ std::array shape_dofval() const { return std::array{derived_cast().m_ndof}; } /** - Shape of "nodevec". - \return [#nnode, #ndim]. - */ + * Shape of "nodevec". + * @return [#nnode, #ndim]. + */ std::array shape_nodevec() const { return std::array{derived_cast().m_nnode, derived_cast().m_ndim}; } /** - Shape of "elemmat". - \return [#nelem, #nne * #ndim, #nne * #ndim]. - */ + * Shape of "elemmat". + * @return [#nelem, #nne * #ndim, #nne * #ndim]. + */ std::array shape_elemmat() const { return std::array{ derived_cast().m_nelem, derived_cast().m_nne * derived_cast().m_ndim, derived_cast().m_nne * derived_cast().m_ndim}; } /** - Assemble from "elemmat". - \param elemmat [#nelem, #nne * #ndim, #nne * #ndim]. - */ + * Assemble from "elemmat". + * @param elemmat [#nelem, #nne * #ndim, #nne * #ndim]. + */ template void assemble(const T& elemmat) { GOOSEFEM_ASSERT(xt::has_shape(elemmat, this->shape_elemmat())); derived_cast().assemble_impl(elemmat); } /** - Copy as dense matrix. - \return [#ndof, #ndof]. - */ + * Copy as dense matrix. + * @return [#ndof, #ndof]. + */ array_type::tensor Todense() const { size_t ndof = derived_cast().m_ndof; array_type::tensor ret = xt::empty({ndof, ndof}); derived_cast().todense_impl(ret); return ret; } /** - Copy to dense matrix. - \param ret overwritten [#ndof, #ndof]. - */ + * Copy to dense matrix. + * @param ret overwritten [#ndof, #ndof]. + */ template void todense(T& ret) const { GOOSEFEM_ASSERT(xt::has_shape(ret, {derived_cast().m_ndof, derived_cast().m_ndof})); derived_cast().todense_impl(ret); } /** - Dot-product \f$ b_i = A_{ij} x_j \f$. - - \param x nodevec [#nelem, #ndim]. - \return b nodevec [#nelem, #ndim]. - */ + * Dot-product \f$ b_i = A_{ij} x_j \f$. + * + * @param x nodevec [#nelem, #ndim]. + * @return b nodevec [#nelem, #ndim]. + */ array_type::tensor Dot(const array_type::tensor& x) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_nodevec())); array_type::tensor b = xt::empty_like(x); derived_cast().dot_nodevec_impl(x, b); return b; } /** - Dot-product \f$ b_i = A_{ij} x_j \f$. - - \param x dofval [#ndof]. - \return b dofval [#ndof]. - */ + * Dot-product \f$ b_i = A_{ij} x_j \f$. + * + * @param x dofval [#ndof]. + * @return b dofval [#ndof]. + */ array_type::tensor Dot(const array_type::tensor& x) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_dofval())); array_type::tensor b = xt::empty_like(x); derived_cast().dot_dofval_impl(x, b); return b; } /** - Dot-product \f$ b_i = A_{ij} x_j \f$. - - \param x nodevec [#nelem, #ndim]. - \param b (overwritten) nodevec [#nelem, #ndim]. - */ + * Dot-product \f$ b_i = A_{ij} x_j \f$. + * + * @param x nodevec [#nelem, #ndim]. + * @param b (overwritten) nodevec [#nelem, #ndim]. + */ void dot(const array_type::tensor& x, array_type::tensor& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(b, this->shape_nodevec())); derived_cast().dot_nodevec_impl(x, b); } /** - Dot-product \f$ b_i = A_{ij} x_j \f$. - - \param x dofval [#ndof]. - \param b (overwritten) dofval [#ndof]. - */ + * Dot-product \f$ b_i = A_{ij} x_j \f$. + * + * @param x dofval [#ndof]. + * @param b (overwritten) dofval [#ndof]. + */ void dot(const array_type::tensor& x, array_type::tensor& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_dofval())); GOOSEFEM_ASSERT(xt::has_shape(b, this->shape_dofval())); derived_cast().dot_dofval_impl(x, b); } }; /** -CRTP base class for a partitioned matrix. -*/ + * CRTP base class for a partitioned matrix. + */ template class MatrixPartitionedBase : public MatrixBase { protected: array_type::tensor m_iiu; ///< See iiu() array_type::tensor m_iip; ///< See iip() size_t m_nnu; ///< See #nnu size_t m_nnp; ///< See #nnp public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Number of unknown DOFs. - \return Unsigned integer. - */ + * Number of unknown DOFs. + * @return Unsigned integer. + */ size_t nnu() const { return derived_cast().m_nnu; } /** - Number of prescribed DOFs. - \return Unsigned integer. - */ + * Number of prescribed DOFs. + * @return Unsigned integer. + */ size_t nnp() const { return derived_cast().m_nnp; } /** - Unknown DOFs. - \return [#nnu]. - */ + * Unknown DOFs. + * @return [#nnu]. + */ const array_type::tensor& iiu() const { return derived_cast().m_iiu; } /** - Prescribed DOFs. - \return [#nnp]. - */ + * Prescribed DOFs. + * @return [#nnp]. + */ const array_type::tensor& iip() const { return derived_cast().m_iip; } /** - Right-hand-size for corresponding to the prescribed DOFs: - - \f$ b_p = A_{pu} * x_u + A_{pp} * x_p \f$ - - and assemble them to the appropriate places in "nodevec". - - \param x "nodevec" [#nnode, #ndim]. - \param b "nodevec" [#nnode, #ndim]. - \return Copy of `b` with \f$ b_p \f$ overwritten. - */ + * Right-hand-size for corresponding to the prescribed DOFs: + * + * \f$ b_p = A_{pu} * x_u + A_{pp} * x_p \f$ + * + * and assemble them to the appropriate places in "nodevec". + * + * @param x "nodevec" [#nnode, #ndim]. + * @param b "nodevec" [#nnode, #ndim]. + * @return Copy of `b` with \f$ b_p \f$ overwritten. + */ array_type::tensor Reaction(const array_type::tensor& x, const array_type::tensor& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(b, this->shape_nodevec())); array_type::tensor ret = b; derived_cast().reaction_nodevec_impl(x, ret); return ret; } /** - Same as Reaction(const array_type::tensor&, const array_type::tensor&), - but of "dofval" input and output. - - \param x "dofval" [#ndof]. - \param b "dofval" [#ndof]. - \return Copy of `b` with \f$ b_p \f$ overwritten. - */ + * Same as Reaction(const array_type::tensor&, const array_type::tensor&), + * but of "dofval" input and output. + * + * @param x "dofval" [#ndof]. + * @param b "dofval" [#ndof]. + * @return Copy of `b` with \f$ b_p \f$ overwritten. + */ array_type::tensor Reaction(const array_type::tensor& x, const array_type::tensor& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_dofval())); GOOSEFEM_ASSERT(xt::has_shape(b, this->shape_dofval())); array_type::tensor ret = b; derived_cast().reaction_dofval_impl(x, ret); return ret; } /** - Same as Reaction(const array_type::tensor&, const array_type::tensor&), - but inserting in-place. - - \param x "nodevec" [#nnode, #ndim]. - \param b "nodevec" [#nnode, #ndim], \f$ b_p \f$ overwritten. - */ + * Same as Reaction(const array_type::tensor&, const array_type::tensor&), + * but inserting in-place. + * + * @param x "nodevec" [#nnode, #ndim]. + * @param b "nodevec" [#nnode, #ndim], \f$ b_p \f$ overwritten. + */ void reaction(const array_type::tensor& x, array_type::tensor& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(b, this->shape_nodevec())); derived_cast().reaction_nodevec_impl(x, b); } /** - Same as Reaction(const array_type::tensor&, const array_type::tensor&), - but inserting in-place. - - \param x "dofval" [#ndof]. - \param b "dofval" [#ndof], \f$ b_p \f$ overwritten. - */ + * Same as Reaction(const array_type::tensor&, const array_type::tensor&), + * but inserting in-place. + * + * @param x "dofval" [#ndof]. + * @param b "dofval" [#ndof], \f$ b_p \f$ overwritten. + */ void reaction(const array_type::tensor& x, array_type::tensor& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, this->shape_dofval())); GOOSEFEM_ASSERT(xt::has_shape(b, this->shape_dofval())); derived_cast().reaction_dofval_impl(x, b); } /** - Same as Reaction(const array_type::tensor&, const array_type::tensor&), - but with partitioned input and output. - - \param x_u unknown "dofval" [#nnu]. - \param x_p prescribed "dofval" [#nnp]. - \return b_p prescribed "dofval" [#nnp]. - */ + * Same as Reaction(const array_type::tensor&, const array_type::tensor&), + * but with partitioned input and output. + * + * @param x_u unknown "dofval" [#nnu]. + * @param x_p prescribed "dofval" [#nnp]. + * @return b_p prescribed "dofval" [#nnp]. + */ array_type::tensor Reaction_p( const array_type::tensor& x_u, const array_type::tensor& x_p) const { array_type::tensor b_p = xt::empty({m_nnp}); derived_cast().reaction_p_impl(x_u, x_p, b_p); return b_p; } /** - Same as Reaction_p(const array_type::tensor&, const array_type::tensor&), - but writing to preallocated output. - - \param x_u unknown "dofval" [#nnu]. - \param x_p prescribed "dofval" [#nnp]. - \param b_p (overwritten) prescribed "dofval" [#nnp]. - */ + * Same as Reaction_p(const array_type::tensor&, const array_type::tensor&), but writing to preallocated output. + * + * @param x_u unknown "dofval" [#nnu]. + * @param x_p prescribed "dofval" [#nnp]. + * @param b_p (overwritten) prescribed "dofval" [#nnp]. + */ void reaction_p( const array_type::tensor& x_u, const array_type::tensor& x_p, array_type::tensor& b_p) const { derived_cast().reaction_p_impl(x_u, x_p, b_p); } }; /** -CRTP base class for a partitioned matrix with tying. -*/ + * CRTP base class for a partitioned matrix with tying. + */ template class MatrixPartitionedTyingsBase : public MatrixPartitionedBase { protected: array_type::tensor m_iii; ///< See iii() array_type::tensor m_iid; ///< See iid() size_t m_nni; ///< See #nni size_t m_nnd; ///< See #nnd public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Number of independent DOFs. - \return Unsigned integer. - */ + * Number of independent DOFs. + * @return Unsigned integer. + */ size_t nni() const { return derived_cast().m_nni; } /** - Number of dependent DOFs. - \return Unsigned integer. - */ + * Number of dependent DOFs. + * @return Unsigned integer. + */ size_t nnd() const { return derived_cast().m_nnd; } /** - Independent DOFs. - \return [#nnu]. - */ + * Independent DOFs. + * @return [#nnu]. + */ const array_type::tensor& iii() const { return derived_cast().m_iii; } /** - Dependent DOFs. - \return [#nnp]. - */ + * Dependent DOFs. + * @return [#nnp]. + */ const array_type::tensor& iid() const { return derived_cast().m_iid; } }; /** -Sparse matrix. - -See GooseFEM::Vector() for bookkeeping definitions. -*/ + * Sparse matrix. + * + * See GooseFEM::Vector() for bookkeeping definitions. + */ class Matrix : public MatrixBase { private: friend MatrixBase; private: bool m_changed = true; ///< Signal changes to data. Eigen::SparseMatrix m_A; ///< The matrix. std::vector> m_T; ///< Matrix entries. /** - Class to solve the system (allowing single factorisation for multiple right-hand-sides). - */ + * Class to solve the system (allowing single factorisation for multiple right-hand-sides). + */ template friend class MatrixSolver; public: Matrix() = default; /** - Constructor. - - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - */ + * Constructor. + * + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + */ Matrix(const array_type::tensor& conn, const array_type::tensor& dofs) { m_conn = conn; m_dofs = dofs; m_nelem = m_conn.shape(0); m_nne = m_conn.shape(1); m_nnode = m_dofs.shape(0); m_ndim = m_dofs.shape(1); m_ndof = xt::amax(m_dofs)() + 1; m_T.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_A.resize(m_ndof, m_ndof); GOOSEFEM_ASSERT(xt::amax(m_conn)() + 1 <= m_nnode); GOOSEFEM_ASSERT(m_ndof <= m_nnode * m_ndim); } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data() const { return m_A; } private: template void assemble_impl(const T& elemmat) { m_T.clear(); for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { for (size_t n = 0; n < m_nne; ++n) { for (size_t j = 0; j < m_ndim; ++j) { m_T.push_back(Eigen::Triplet( m_dofs(m_conn(e, m), i), m_dofs(m_conn(e, n), j), elemmat(e, m * m_ndim + i, n * m_ndim + j))); } } } } } m_A.setFromTriplets(m_T.begin(), m_T.end()); m_changed = true; } public: /** - Overwrite matrix. - - \param rows Row numbers [m]. - \param cols Column numbers [n]. - \param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. - */ + * Overwrite matrix. + * + * @param rows Row numbers [m]. + * @param cols Column numbers [n]. + * @param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. + */ void set(const array_type::tensor& rows, const array_type::tensor& cols, const array_type::tensor& matrix) { GOOSEFEM_ASSERT(rows.size() == matrix.shape(0)); GOOSEFEM_ASSERT(cols.size() == matrix.shape(1)); GOOSEFEM_ASSERT(xt::amax(cols)() < m_ndof); GOOSEFEM_ASSERT(xt::amax(rows)() < m_ndof); std::vector> T; for (size_t i = 0; i < rows.size(); ++i) { for (size_t j = 0; j < cols.size(); ++j) { T.push_back(Eigen::Triplet(rows(i), cols(j), matrix(i, j))); } } m_A.setFromTriplets(T.begin(), T.end()); m_changed = true; } /** - Add matrix. - - \param rows Row numbers [m]. - \param cols Column numbers [n]. - \param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. - */ + * Add matrix. + * + * @param rows Row numbers [m]. + * @param cols Column numbers [n]. + * @param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. + */ void add(const array_type::tensor& rows, const array_type::tensor& cols, const array_type::tensor& matrix) { GOOSEFEM_ASSERT(rows.size() == matrix.shape(0)); GOOSEFEM_ASSERT(cols.size() == matrix.shape(1)); GOOSEFEM_ASSERT(xt::amax(cols)() < m_ndof); GOOSEFEM_ASSERT(xt::amax(rows)() < m_ndof); std::vector> T; Eigen::SparseMatrix A(m_ndof, m_ndof); for (size_t i = 0; i < rows.size(); ++i) { for (size_t j = 0; j < cols.size(); ++j) { T.push_back(Eigen::Triplet(rows(i), cols(j), matrix(i, j))); } } A.setFromTriplets(T.begin(), T.end()); m_A += A; m_changed = true; } private: template void todense_impl(T& ret) const { ret.fill(0.0); for (int k = 0; k < m_A.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_A, k); it; ++it) { ret(it.row(), it.col()) = it.value(); } } } template void dot_nodevec_impl(const T& x, T& b) const { this->Eigen_asNode_dofval_nodevec(m_A * this->Eigen_AsDofs_nodevec(x), b); } template void dot_dofval_impl(const T& x, T& b) const { Eigen::Map(b.data(), b.size()).noalias() = m_A * Eigen::Map(x.data(), x.size()); } private: /** - Convert "nodevec" to "dofval" (overwrite entries that occur more than once). - - \param nodevec input [#nnode, #ndim] - \return dofval output [#ndof] - */ + * Convert "nodevec" to "dofval" (overwrite entries that occur more than once). + * + * @param nodevec input [#nnode, #ndim] + * @return dofval output [#ndof] + */ template Eigen::VectorXd Eigen_AsDofs_nodevec(const T& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval = Eigen::VectorXd::Zero(m_ndof, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { dofval(m_dofs(m, i)) = nodevec(m, i); } } return dofval; } /** - Convert "dofval" to "nodevec" (overwrite entries that occur more than once). - - \param dofval input [#ndof] - \param nodevec output [#nnode, #ndim] - */ + * Convert "dofval" to "nodevec" (overwrite entries that occur more than once). + * + * @param dofval input [#ndof] + * @param nodevec output [#nnode, #ndim] + */ template void Eigen_asNode_dofval_nodevec(const Eigen::VectorXd& dofval, T& nodevec) const { GOOSEFEM_ASSERT(static_cast(dofval.size()) == m_ndof); GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { nodevec(m, i) = dofval(m_dofs(m, i)); } } } }; /** -Solve \f$ x = A^{-1} b \f$, for `A` of the GooseFEM::Matrix() class. -You can solve for multiple right-hand-sides using one factorisation. -*/ + * Solve \f$ x = A^{-1} b \f$, for `A` of the GooseFEM::Matrix() class. + * You can solve for multiple right-hand-sides using one factorisation. + */ template >> class MatrixSolver : public MatrixSolverBase>, public MatrixSolverSingleBase> { private: friend MatrixSolverBase>; friend MatrixSolverSingleBase>; public: MatrixSolver() = default; private: template void solve_nodevec_impl(Matrix& A, const T& b, T& x) { this->factorize(A); Eigen::VectorXd X = m_solver.solve(A.Eigen_AsDofs_nodevec(b)); A.Eigen_asNode_dofval_nodevec(X, x); } template void solve_dofval_impl(Matrix& A, const T& b, T& x) { this->factorize(A); Eigen::Map(x.data(), x.size()).noalias() = m_solver.solve(Eigen::Map(b.data(), A.m_ndof)); } private: Solver m_solver; ///< Solver. bool m_factor = true; ///< Signal to force factorization. /** - Compute inverse (evaluated by "solve"). - */ + * Compute inverse (evaluated by "solve"). + */ void factorize(Matrix& A) { if (!A.m_changed && !m_factor) { return; } m_solver.compute(A.m_A); m_factor = false; A.m_changed = false; } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MatrixDiagonal.h b/include/GooseFEM/MatrixDiagonal.h index 268c47c..1659f88 100644 --- a/include/GooseFEM/MatrixDiagonal.h +++ b/include/GooseFEM/MatrixDiagonal.h @@ -1,282 +1,282 @@ /** -Diagonal matrix. - -\file MatrixDiagonal.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Diagonal matrix. + * + * @file MatrixDiagonal.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MATRIXDIAGONAL_H #define GOOSEFEM_MATRIXDIAGONAL_H #include "Element.h" #include "Matrix.h" #include "config.h" namespace GooseFEM { /** -CRTP base class for a partitioned matrix with tying. -*/ + * CRTP base class for a partitioned matrix with tying. + */ template class MatrixDiagonalBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } public: /** - Solve \f$ x = A^{-1} b \f$. - - For #GooseFEM::MatrixDiagonalPartitioned under the hood solved - \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. - Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. - - \param b nodevec [nelem, ndim]. - \return x nodevec [nelem, ndim]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * For #GooseFEM::MatrixDiagonalPartitioned under the hood solved + * \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. + * Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. + * + * @param b nodevec [nelem, ndim]. + * @return x nodevec [nelem, ndim]. + */ array_type::tensor Solve(const array_type::tensor& b) { array_type::tensor x = xt::empty_like(b); derived_cast().solve_nodevec_impl(b, x); return x; } /** - Solve \f$ x = A^{-1} b \f$. - - For #GooseFEM::MatrixDiagonalPartitioned under the hood solved - \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. - Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. - - \param b dofval [ndof]. - \return x dofval [ndof]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * For #GooseFEM::MatrixDiagonalPartitioned under the hood solved + * \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. + * Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. + * + * @param b dofval [ndof]. + * @return x dofval [ndof]. + */ array_type::tensor Solve(const array_type::tensor& b) { array_type::tensor x = xt::empty_like(b); derived_cast().solve_dofval_impl(b, x); return x; } /** - Solve \f$ x = A^{-1} b \f$. - - For #GooseFEM::MatrixDiagonalPartitioned under the hood solved - \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. - Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. - - \param b nodevec [nelem, ndim]. - \param x (overwritten) nodevec [nelem, ndim]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * For #GooseFEM::MatrixDiagonalPartitioned under the hood solved + * \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. + * Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. + * + * @param b nodevec [nelem, ndim]. + * @param x (overwritten) nodevec [nelem, ndim]. + */ void solve(const array_type::tensor& b, array_type::tensor& x) { derived_cast().solve_nodevec_impl(b, x); } /** - Solve \f$ x = A^{-1} b \f$. - - For #GooseFEM::MatrixDiagonalPartitioned under the hood solved - \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. - Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. - - \param b nodevec [nelem, ndim]. - \param x (overwritten) nodevec [nelem, ndim]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * For #GooseFEM::MatrixDiagonalPartitioned under the hood solved + * \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \equiv A_{uu}^{-1} b_u \f$. + * Use GooseFEM::MatrixDiagonalPartitioned::Reaction() to get reaction forces. + * + * @param b nodevec [nelem, ndim]. + * @param x (overwritten) nodevec [nelem, ndim]. + */ void solve(const array_type::tensor& b, array_type::tensor& x) { derived_cast().solve_dofval_impl(b, x); } }; /** -Diagonal matrix. - -Warning: assemble() ignores all off-diagonal terms. - -See Vector() for bookkeeping definitions. -*/ + * Diagonal matrix. + * + * Warning: assemble() ignores all off-diagonal terms. + * + * See Vector() for bookkeeping definitions. + */ class MatrixDiagonal : public MatrixBase, public MatrixDiagonalBase { private: friend MatrixBase; friend MatrixDiagonalBase; public: MatrixDiagonal() = default; /** - Constructor. - - \tparam C e.g. `array_type::tensor` - \tparam D e.g. `array_type::tensor` - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - */ + * Constructor. + * + * @tparam C e.g. `array_type::tensor` + * @tparam D e.g. `array_type::tensor` + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + */ template MatrixDiagonal(const C& conn, const D& dofs) { m_conn = conn; m_dofs = dofs; m_nelem = m_conn.shape(0); m_nne = m_conn.shape(1); m_nnode = m_dofs.shape(0); m_ndim = m_dofs.shape(1); m_ndof = xt::amax(m_dofs)() + 1; m_A = xt::empty({m_ndof}); m_inv = xt::empty({m_ndof}); GOOSEFEM_ASSERT(xt::amax(m_conn)() + 1 <= m_nnode); GOOSEFEM_ASSERT(m_ndof <= m_nnode * m_ndim); } private: template void assemble_impl(const T& elemmat) { GOOSEFEM_ASSERT(xt::has_shape(elemmat, {m_nelem, m_nne * m_ndim, m_nne * m_ndim})); GOOSEFEM_ASSERT(Element::isDiagonal(elemmat)); m_A.fill(0.0); for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { m_A(m_dofs(m_conn(e, m), i)) += elemmat(e, m * m_ndim + i, m * m_ndim + i); } } } m_changed = true; } template void todense_impl(T& ret) const { ret.fill(0.0); #pragma omp parallel for for (size_t d = 0; d < m_ndof; ++d) { ret(d, d) = m_A(d); } } public: /** - Set all (diagonal) matrix components. - \param A The matrix [#ndof]. - */ + * Set all (diagonal) matrix components. + * @param A The matrix [#ndof]. + */ void set(const array_type::tensor& A) { GOOSEFEM_ASSERT(A.size() == m_ndof); std::copy(A.begin(), A.end(), m_A.begin()); m_changed = true; } /** - Copy as diagonal matrix. - \return [#ndof]. - */ + * Copy as diagonal matrix. + * @return [#ndof]. + */ [[deprecated]] const array_type::tensor& Todiagonal() const { return m_A; } /** - Underlying matrix - \return [#ndof]. - */ + * Underlying matrix + * @return [#ndof]. + */ const array_type::tensor& data() const { return m_A; } private: template void dot_nodevec_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { b(m, i) = m_A(m_dofs(m, i)) * x(m, i); } } } template void dot_dofval_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(x.size() == m_ndof); GOOSEFEM_ASSERT(b.size() == m_ndof); xt::noalias(b) = m_A * x; } private: template void solve_nodevec_impl(const T& b, T& x) { GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); this->factorize(); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { x(m, i) = m_inv(m_dofs(m, i)) * b(m, i); } } } template void solve_dofval_impl(const T& b, T& x) { GOOSEFEM_ASSERT(b.size() == m_ndof); GOOSEFEM_ASSERT(x.size() == m_ndof); this->factorize(); xt::noalias(x) = m_inv * b; } private: array_type::tensor m_A; ///< The matrix. array_type::tensor m_inv; /// Inverse of the matrix. /** - Compute inverse (automatically evaluated by "solve"). - */ + * Compute inverse (automatically evaluated by "solve"). + */ void factorize() { if (!m_changed) { return; } #pragma omp parallel for for (size_t d = 0; d < m_ndof; ++d) { m_inv(d) = 1.0 / m_A(d); } m_changed = false; } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MatrixDiagonalPartitioned.h b/include/GooseFEM/MatrixDiagonalPartitioned.h index 8776246..cf5fb19 100644 --- a/include/GooseFEM/MatrixDiagonalPartitioned.h +++ b/include/GooseFEM/MatrixDiagonalPartitioned.h @@ -1,450 +1,450 @@ /** -Diagonal matrix that is partitioned in: -- unknown DOFs -- prescribed DOFs - -\file MatrixDiagonalPartitioned.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Diagonal matrix that is partitioned in: + * - unknown DOFs + * - prescribed DOFs + * + * @file MatrixDiagonalPartitioned.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MATRIXDIAGONALPARTITIONED_H #define GOOSEFEM_MATRIXDIAGONALPARTITIONED_H #include "MatrixDiagonal.h" #include "Mesh.h" #include "config.h" namespace GooseFEM { /** -Diagonal and partitioned matrix. - -See Vector() for bookkeeping definitions. -*/ + * Diagonal and partitioned matrix. + * + * See Vector() for bookkeeping definitions. + */ class MatrixDiagonalPartitioned : public MatrixPartitionedBase, public MatrixDiagonalBase { private: friend MatrixBase; friend MatrixPartitionedBase; friend MatrixDiagonalBase; public: MatrixDiagonalPartitioned() = default; /** - Constructor. - - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - \param iip prescribed DOFs [#nnp]. - */ + * Constructor. + * + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + * @param iip prescribed DOFs [#nnp]. + */ MatrixDiagonalPartitioned( const array_type::tensor& conn, const array_type::tensor& dofs, const array_type::tensor& iip) { m_conn = conn; m_dofs = dofs; m_nelem = m_conn.shape(0); m_nne = m_conn.shape(1); m_nnode = m_dofs.shape(0); m_ndim = m_dofs.shape(1); m_ndof = xt::amax(m_dofs)() + 1; GOOSEFEM_ASSERT(is_unique(iip)); GOOSEFEM_ASSERT(xt::amax(conn)() + 1 <= m_nnode); GOOSEFEM_ASSERT(m_ndof <= m_nnode * m_ndim); GOOSEFEM_ASSERT(xt::amax(iip)() <= xt::amax(dofs)()); m_iip = iip; m_iiu = xt::setdiff1d(dofs, iip); m_nnp = m_iip.size(); m_nnu = m_iiu.size(); m_part = Mesh::Reorder({m_iiu, m_iip}).apply(m_dofs); m_Auu = xt::empty({m_nnu}); m_App = xt::empty({m_nnp}); m_inv_uu = xt::empty({m_nnu}); } private: template void assemble_impl(const T& elemmat) { GOOSEFEM_ASSERT(xt::has_shape(elemmat, {m_nelem, m_nne * m_ndim, m_nne * m_ndim})); GOOSEFEM_ASSERT(Element::isDiagonal(elemmat)); m_Auu.fill(0.0); m_App.fill(0.0); for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { size_t d = m_part(m_conn(e, m), i); if (d < m_nnu) { m_Auu(d) += elemmat(e, m * m_ndim + i, m * m_ndim + i); } else { m_App(d - m_nnu) += elemmat(e, m * m_ndim + i, m * m_ndim + i); } } } } m_changed = true; } template void todense_impl(T& ret) const { ret.fill(0.0); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { ret(m_iiu(d), m_iiu(d)) = m_Auu(d); } #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { ret(m_iip(d), m_iip(d)) = m_App(d); } } public: /** - Set all (diagonal) matrix components. - \param A The matrix [#ndof]. - */ + * Set all (diagonal) matrix components. + * @param A The matrix [#ndof]. + */ void set(const array_type::tensor& A) { GOOSEFEM_ASSERT(A.size() == m_ndof); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { m_Auu(d) = A(m_iiu(d)); } #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { m_App(d) = A(m_iip(d)); } m_changed = true; } /** - Assemble to diagonal matrix (involves copies). - \return [#ndof]. - */ + * Assemble to diagonal matrix (involves copies). + * @return [#ndof]. + */ array_type::tensor data() const { array_type::tensor ret = xt::zeros({m_ndof}); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { ret(m_iiu(d)) = m_Auu(d); } #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { ret(m_iip(d)) = m_App(d); } return ret; } /** - Pointer to data. - \return [#nnu]. - */ + * Pointer to data. + * @return [#nnu]. + */ const array_type::tensor& data_uu() const { return m_Auu; } /** - Pointer to data. - \return [#nnu]. - */ + * Pointer to data. + * @return [#nnu]. + */ const array_type::tensor& data_pp() const { return m_App; } /** - Pointer to data. - \return [#nnu]. - */ + * Pointer to data. + * @return [#nnu]. + */ [[deprecated]] array_type::tensor Todiagonal() const { return this->data(); } private: template void dot_nodevec_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { size_t d = m_part(m, i); if (d < m_nnu) { b(m, i) = m_Auu(d) * x(m, i); } else { b(m, i) = m_App(d - m_nnu) * x(m, i); } } } } template void dot_dofval_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(x.size() == m_ndof); GOOSEFEM_ASSERT(b.size() == m_ndof); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { b(m_iiu(d)) = m_Auu(d) * x(m_iiu(d)); } #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { b(m_iip(d)) = m_App(d) * x(m_iip(d)); } } public: /** - \todo Decide if this function should be kept. - \param x_u dofval [#nnu]. - \param x_p dofval [#nnp]. - \return b_u dofval [#nnu]. - */ + * \todo Decide if this function should be kept. + * @param x_u dofval [#nnu]. + * @param x_p dofval [#nnp]. + * @return b_u dofval [#nnu]. + */ array_type::tensor Dot_u(const array_type::tensor& x_u, const array_type::tensor& x_p) const { array_type::tensor b_u = xt::empty({m_nnu}); this->dot_u(x_u, x_p, b_u); return b_u; } /** - \todo Decide if this function should be kept. - \param x_u dofval [#nnu]. - \param x_p dofval [#nnp]. - \param b_u (overwritten) dofval [#nnu]. - */ + * \todo Decide if this function should be kept. + * @param x_u dofval [#nnu]. + * @param x_p dofval [#nnp]. + * @param b_u (overwritten) dofval [#nnu]. + */ void dot_u( const array_type::tensor& x_u, const array_type::tensor& x_p, array_type::tensor& b_u) const { UNUSED(x_p); GOOSEFEM_ASSERT(x_u.size() == m_nnu); GOOSEFEM_ASSERT(x_p.size() == m_nnp); GOOSEFEM_ASSERT(b_u.size() == m_nnu); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { b_u(d) = m_Auu(d) * x_u(d); } } /** - \todo Decide if this function should be kept. - \param x_u dofval [#nnu]. - \param x_p dofval [#nnp]. - \return b_p dofval [#nnp]. - */ + * \todo Decide if this function should be kept. + * @param x_u dofval [#nnu]. + * @param x_p dofval [#nnp]. + * @return b_p dofval [#nnp]. + */ array_type::tensor Dot_p(const array_type::tensor& x_u, const array_type::tensor& x_p) const { array_type::tensor b_p = xt::empty({m_nnp}); this->dot_p(x_u, x_p, b_p); return b_p; } /** - \todo Decide if this function should be kept. - \param x_u dofval [#nnu]. - \param x_p dofval [#nnp]. - \param b_p (overwritten) dofval [#nnp]. - */ + * \todo Decide if this function should be kept. + * @param x_u dofval [#nnu]. + * @param x_p dofval [#nnp]. + * @param b_p (overwritten) dofval [#nnp]. + */ void dot_p( const array_type::tensor& x_u, const array_type::tensor& x_p, array_type::tensor& b_p) const { UNUSED(x_u); GOOSEFEM_ASSERT(x_u.size() == m_nnu); GOOSEFEM_ASSERT(x_p.size() == m_nnp); GOOSEFEM_ASSERT(b_p.size() == m_nnp); #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { b_p(d) = m_App(d) * x_p(d); } } private: template void solve_nodevec_impl(const T& b, T& x) { GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); this->factorize(); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { x(m, i) = m_inv_uu(m_part(m, i)) * b(m, i); } } } } template void solve_dofval_impl(const T& b, T& x) { GOOSEFEM_ASSERT(b.size() == m_ndof); GOOSEFEM_ASSERT(x.size() == m_ndof); this->factorize(); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { x(m_iiu(d)) = m_inv_uu(d) * b(m_iiu(d)); } } public: /** - \param b_u dofval [#nnu]. - \param x_p dofval [#nnp]. - \return x_u dofval [#nnu]. - */ + * @param b_u dofval [#nnu]. + * @param x_p dofval [#nnp]. + * @return x_u dofval [#nnu]. + */ array_type::tensor Solve_u(const array_type::tensor& b_u, const array_type::tensor& x_p) { array_type::tensor x_u = xt::empty({m_nnu}); this->solve_u(b_u, x_p, x_u); return x_u; } /** - \param b_u dofval [#nnu]. - \param x_p dofval [#nnp]. - \param x_u (overwritten) dofval [#nnu]. - */ + * @param b_u dofval [#nnu]. + * @param x_p dofval [#nnp]. + * @param x_u (overwritten) dofval [#nnu]. + */ void solve_u( const array_type::tensor& b_u, const array_type::tensor& x_p, array_type::tensor& x_u) { UNUSED(x_p); GOOSEFEM_ASSERT(b_u.size() == m_nnu); GOOSEFEM_ASSERT(x_p.size() == m_nnp); GOOSEFEM_ASSERT(x_u.size() == m_nnu); this->factorize(); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { x_u(d) = m_inv_uu(d) * b_u(d); } } private: template void reaction_nodevec_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) >= m_nnu) { b(m, i) = m_App(m_part(m, i) - m_nnu) * x(m, i); } } } } template void reaction_dofval_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(x.size() == m_ndof); GOOSEFEM_ASSERT(b.size() == m_ndof); #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { b(m_iip(d)) = m_App(d) * x(m_iip(d)); } } void reaction_p_impl( const array_type::tensor& x_u, const array_type::tensor& x_p, array_type::tensor& b_p) const { UNUSED(x_u); GOOSEFEM_ASSERT(x_u.size() == m_nnu); GOOSEFEM_ASSERT(x_p.size() == m_nnp); GOOSEFEM_ASSERT(b_p.size() == m_nnp); #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { b_p(d) = m_App(d) * x_p(d); } } private: // The diagonal matrix, and its inverse (re-used to solve different RHS) array_type::tensor m_Auu; array_type::tensor m_App; array_type::tensor m_inv_uu; // Bookkeeping array_type::tensor m_part; // DOF-numbers per node, renumbered [nnode, ndim] array_type::tensor m_iiu; // DOF-numbers that are unknown [nnu] array_type::tensor m_iip; // DOF-numbers that are prescribed [nnp] // Dimensions size_t m_nnu; // number of unknown DOFs size_t m_nnp; // number of prescribed DOFs // Compute inverse (automatically evaluated by "solve") void factorize() { if (!m_changed) { return; } #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { m_inv_uu(d) = 1.0 / m_Auu(d); } m_changed = false; } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MatrixPartitioned.h b/include/GooseFEM/MatrixPartitioned.h index ce15ebb..b30aaa6 100644 --- a/include/GooseFEM/MatrixPartitioned.h +++ b/include/GooseFEM/MatrixPartitioned.h @@ -1,637 +1,637 @@ /** -Sparse matrix that is partitioned in: -- unknown DOFs -- prescribed DOFs - -\file MatrixPartitioned.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Sparse matrix that is partitioned in: + * - unknown DOFs + * - prescribed DOFs + * + * @file MatrixPartitioned.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MATRIXPARTITIONED_H #define GOOSEFEM_MATRIXPARTITIONED_H #include "Matrix.h" #include "config.h" #include #include #include namespace GooseFEM { // forward declaration template class MatrixPartitionedSolver; /** -Sparse matrix partitioned in an unknown and a prescribed part. -In particular: -\f$ \begin{bmatrix} A_{uu} & A_{up} \\ A_{pu} & A_{pp} \end{bmatrix} \f$ - -See VectorPartitioned() for bookkeeping definitions. -*/ + * Sparse matrix partitioned in an unknown and a prescribed part. + * In particular: + * \f$ \begin{bmatrix} A_{uu} & A_{up} \\ A_{pu} & A_{pp} \end{bmatrix} \f$ + * + * See VectorPartitioned() for bookkeeping definitions. + */ class MatrixPartitioned : public MatrixPartitionedBase { private: friend MatrixBase; friend MatrixPartitionedBase; protected: Eigen::SparseMatrix m_Auu; ///< The matrix. Eigen::SparseMatrix m_Aup; ///< The matrix. Eigen::SparseMatrix m_Apu; ///< The matrix. Eigen::SparseMatrix m_App; ///< The matrix. std::vector> m_Tuu; ///< Matrix entries. std::vector> m_Tup; ///< Matrix entries. std::vector> m_Tpu; ///< Matrix entries. std::vector> m_Tpp; ///< Matrix entries. /** - Renumbered DOFs per node, such that: - - iiu = arange(nnu) - iip = nnu + arange(nnp) - - making is much simpler to slice. - */ + * Renumbered DOFs per node, such that: + * + * iiu = arange(nnu) + * iip = nnu + arange(nnp) + * + * making is much simpler to slice. + */ array_type::tensor m_part; /** - Map real DOF to DOF in partitioned system. - The partitioned system is defined as: - - iiu = arange(nnu) - iip = nnu + arange(nnp) - - Similar to `m_part` but for a 1d sequential list of DOFs. - */ + * Map real DOF to DOF in partitioned system. + * The partitioned system is defined as: + * + * iiu = arange(nnu) + * iip = nnu + arange(nnp) + * + * Similar to `m_part` but for a 1d sequential list of DOFs. + */ array_type::tensor m_part1d; /** - Class to solve the system (allowing single factorisation for multiple right-hand-sides). - */ + * Class to solve the system (allowing single factorisation for multiple right-hand-sides). + */ template friend class MatrixPartitionedSolver; public: MatrixPartitioned() = default; /** - Constructor. - - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - \param iip prescribed DOFs [#nnp]. - */ + * Constructor. + * + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + * @param iip prescribed DOFs [#nnp]. + */ MatrixPartitioned( const array_type::tensor& conn, const array_type::tensor& dofs, const array_type::tensor& iip) { m_conn = conn; m_dofs = dofs; m_iip = iip; m_nelem = m_conn.shape(0); m_nne = m_conn.shape(1); m_nnode = m_dofs.shape(0); m_ndim = m_dofs.shape(1); m_ndof = xt::amax(m_dofs)() + 1; GOOSEFEM_ASSERT(is_unique(iip)); GOOSEFEM_ASSERT(xt::amax(conn)() + 1 <= m_nnode); GOOSEFEM_ASSERT(xt::amax(iip)() <= xt::amax(dofs)()); GOOSEFEM_ASSERT(m_ndof <= m_nnode * m_ndim); m_iiu = xt::setdiff1d(dofs, iip); m_nnp = m_iip.size(); m_nnu = m_iiu.size(); m_part = Mesh::Reorder({m_iiu, m_iip}).apply(m_dofs); m_part1d = Mesh::Reorder({m_iiu, m_iip}).apply(xt::eval(xt::arange(m_ndof))); m_Tuu.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tup.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tpu.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tpp.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Auu.resize(m_nnu, m_nnu); m_Aup.resize(m_nnu, m_nnp); m_Apu.resize(m_nnp, m_nnu); m_App.resize(m_nnp, m_nnp); } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_uu() const { return m_Auu; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_up() const { return m_Aup; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_pu() const { return m_Apu; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_pp() const { return m_App; } private: template void assemble_impl(const T& elemmat) { GOOSEFEM_ASSERT(xt::has_shape(elemmat, {m_nelem, m_nne * m_ndim, m_nne * m_ndim})); m_Tuu.clear(); m_Tup.clear(); m_Tpu.clear(); m_Tpp.clear(); for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { size_t di = m_part(m_conn(e, m), i); for (size_t n = 0; n < m_nne; ++n) { for (size_t j = 0; j < m_ndim; ++j) { size_t dj = m_part(m_conn(e, n), j); if (di < m_nnu && dj < m_nnu) { m_Tuu.push_back(Eigen::Triplet( di, dj, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (di < m_nnu) { m_Tup.push_back(Eigen::Triplet( di, dj - m_nnu, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (dj < m_nnu) { m_Tpu.push_back(Eigen::Triplet( di - m_nnu, dj, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else { m_Tpp.push_back(Eigen::Triplet( di - m_nnu, dj - m_nnu, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } } } } } } m_Auu.setFromTriplets(m_Tuu.begin(), m_Tuu.end()); m_Aup.setFromTriplets(m_Tup.begin(), m_Tup.end()); m_Apu.setFromTriplets(m_Tpu.begin(), m_Tpu.end()); m_App.setFromTriplets(m_Tpp.begin(), m_Tpp.end()); m_changed = true; } public: /** - Overwrite matrix. - - \param rows Row numbers [m]. - \param cols Column numbers [n]. - \param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. - */ + * Overwrite matrix. + * + * @param rows Row numbers [m]. + * @param cols Column numbers [n]. + * @param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. + */ void set(const array_type::tensor& rows, const array_type::tensor& cols, const array_type::tensor& matrix) { GOOSEFEM_ASSERT(rows.size() == matrix.shape(0)); GOOSEFEM_ASSERT(cols.size() == matrix.shape(1)); GOOSEFEM_ASSERT(xt::amax(cols)() < m_ndof); GOOSEFEM_ASSERT(xt::amax(rows)() < m_ndof); std::vector> Tuu; std::vector> Tup; std::vector> Tpu; std::vector> Tpp; for (size_t i = 0; i < rows.size(); ++i) { for (size_t j = 0; j < cols.size(); ++j) { size_t di = m_part1d(rows(i)); size_t dj = m_part1d(cols(j)); double v = matrix(i, j); if (di < m_nnu && dj < m_nnu) { Tuu.push_back(Eigen::Triplet(di, dj, v)); } else if (di < m_nnu) { Tup.push_back(Eigen::Triplet(di, dj - m_nnu, v)); } else if (dj < m_nnu) { Tpu.push_back(Eigen::Triplet(di - m_nnu, dj, v)); } else { Tpp.push_back(Eigen::Triplet(di - m_nnu, dj - m_nnu, v)); } } } m_Auu.setFromTriplets(Tuu.begin(), Tuu.end()); m_Aup.setFromTriplets(Tup.begin(), Tup.end()); m_Apu.setFromTriplets(Tpu.begin(), Tpu.end()); m_App.setFromTriplets(Tpp.begin(), Tpp.end()); m_changed = true; } /** - Add matrix. - - \param rows Row numbers [m]. - \param cols Column numbers [n]. - \param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. - */ + * Add matrix. + * + * @param rows Row numbers [m]. + * @param cols Column numbers [n]. + * @param matrix Data entries `matrix(i, j)` for `rows(i), cols(j)` [m, n]. + */ void add(const array_type::tensor& rows, const array_type::tensor& cols, const array_type::tensor& matrix) { GOOSEFEM_ASSERT(rows.size() == matrix.shape(0)); GOOSEFEM_ASSERT(cols.size() == matrix.shape(1)); GOOSEFEM_ASSERT(xt::amax(cols)() < m_ndof); GOOSEFEM_ASSERT(xt::amax(rows)() < m_ndof); std::vector> Tuu; std::vector> Tup; std::vector> Tpu; std::vector> Tpp; Eigen::SparseMatrix Auu(m_nnu, m_nnu); Eigen::SparseMatrix Aup(m_nnu, m_nnp); Eigen::SparseMatrix Apu(m_nnp, m_nnu); Eigen::SparseMatrix App(m_nnp, m_nnp); for (size_t i = 0; i < rows.size(); ++i) { for (size_t j = 0; j < cols.size(); ++j) { size_t di = m_part1d(rows(i)); size_t dj = m_part1d(cols(j)); double v = matrix(i, j); if (di < m_nnu && dj < m_nnu) { Tuu.push_back(Eigen::Triplet(di, dj, v)); } else if (di < m_nnu) { Tup.push_back(Eigen::Triplet(di, dj - m_nnu, v)); } else if (dj < m_nnu) { Tpu.push_back(Eigen::Triplet(di - m_nnu, dj, v)); } else { Tpp.push_back(Eigen::Triplet(di - m_nnu, dj - m_nnu, v)); } } } Auu.setFromTriplets(Tuu.begin(), Tuu.end()); Aup.setFromTriplets(Tup.begin(), Tup.end()); Apu.setFromTriplets(Tpu.begin(), Tpu.end()); App.setFromTriplets(Tpp.begin(), Tpp.end()); m_Auu += Auu; m_Aup += Aup; m_Apu += Apu; m_App += App; m_changed = true; } private: template void todense_impl(T& ret) const { ret.fill(0.0); for (int k = 0; k < m_Auu.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Auu, k); it; ++it) { ret(m_iiu(it.row()), m_iiu(it.col())) = it.value(); } } for (int k = 0; k < m_Aup.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Aup, k); it; ++it) { ret(m_iiu(it.row()), m_iip(it.col())) = it.value(); } } for (int k = 0; k < m_Apu.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Apu, k); it; ++it) { ret(m_iip(it.row()), m_iiu(it.col())) = it.value(); } } for (int k = 0; k < m_App.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_App, k); it; ++it) { ret(m_iip(it.row()), m_iip(it.col())) = it.value(); } } } template void dot_nodevec_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); Eigen::VectorXd X_u = this->AsDofs_u(x); Eigen::VectorXd X_p = this->AsDofs_p(x); Eigen::VectorXd B_u = m_Auu * X_u + m_Aup * X_p; Eigen::VectorXd B_p = m_Apu * X_u + m_App * X_p; #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { b(m, i) = B_u(m_part(m, i)); } else { b(m, i) = B_p(m_part(m, i) - m_nnu); } } } } template void dot_dofval_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(b.size() == m_ndof); GOOSEFEM_ASSERT(x.size() == m_ndof); Eigen::VectorXd X_u = this->AsDofs_u(x); Eigen::VectorXd X_p = this->AsDofs_p(x); Eigen::VectorXd B_u = m_Auu * X_u + m_Aup * X_p; Eigen::VectorXd B_p = m_Apu * X_u + m_App * X_p; #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { b(m_iiu(d)) = B_u(d); } #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { b(m_iip(d)) = B_p(d); } } template void reaction_nodevec_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(xt::has_shape(x, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(b, {m_nnode, m_ndim})); Eigen::VectorXd X_u = this->AsDofs_u(x); Eigen::VectorXd X_p = this->AsDofs_p(x); Eigen::VectorXd B_p = m_Apu * X_u + m_App * X_p; #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) >= m_nnu) { b(m, i) = B_p(m_part(m, i) - m_nnu); } } } } template void reaction_dofval_impl(const T& x, T& b) const { GOOSEFEM_ASSERT(x.size() == m_ndof); GOOSEFEM_ASSERT(b.size() == m_ndof); Eigen::VectorXd X_u = this->AsDofs_u(x); Eigen::VectorXd X_p = this->AsDofs_p(x); Eigen::VectorXd B_p = m_Apu * X_u + m_App * X_p; #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { b(m_iip(d)) = B_p(d); } } void reaction_p_impl( const array_type::tensor& x_u, const array_type::tensor& x_p, array_type::tensor& b_p) const { GOOSEFEM_ASSERT(x_u.size() == m_nnu); GOOSEFEM_ASSERT(x_p.size() == m_nnp); GOOSEFEM_ASSERT(b_p.size() == m_nnp); Eigen::Map(b_p.data(), b_p.size()).noalias() = m_Apu * Eigen::Map(x_u.data(), x_u.size()) + m_App * Eigen::Map(x_p.data(), x_p.size()); } private: // Convert arrays (Eigen version of VectorPartitioned, which contains public functions) Eigen::VectorXd AsDofs_u(const array_type::tensor& dofval) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); Eigen::VectorXd dofval_u(m_nnu, 1); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { dofval_u(d) = dofval(m_iiu(d)); } return dofval_u; } Eigen::VectorXd AsDofs_u(const array_type::tensor& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval_u = Eigen::VectorXd::Zero(m_nnu, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { dofval_u(m_part(m, i)) = nodevec(m, i); } } } return dofval_u; } Eigen::VectorXd AsDofs_p(const array_type::tensor& dofval) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); Eigen::VectorXd dofval_p(m_nnp, 1); #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { dofval_p(d) = dofval(m_iip(d)); } return dofval_p; } Eigen::VectorXd AsDofs_p(const array_type::tensor& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval_p = Eigen::VectorXd::Zero(m_nnp, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) >= m_nnu) { dofval_p(m_part(m, i) - m_nnu) = nodevec(m, i); } } } return dofval_p; } }; /** -Solve \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \f$ for `A` of the MatrixPartitioned() class. -You can solve for multiple right-hand-sides using one factorisation. - -For "nodevec" input `x` is used to read \f$ x_p \f$, while \f$ x_u \f$ is written. -See MatrixPartitioned::Reaction() to get \f$ b_p \f$. -*/ + * Solve \f$ x_u = A_{uu}^{-1} (b_u - A_{up} * x_p) \f$ for `A` of the MatrixPartitioned() class. + * You can solve for multiple right-hand-sides using one factorisation. + * + * For "nodevec" input `x` is used to read \f$ x_p \f$, while \f$ x_u \f$ is written. + * See MatrixPartitioned::Reaction() to get \f$ b_p \f$. + */ template >> class MatrixPartitionedSolver : public MatrixSolverBase>, public MatrixSolverPartitionedBase> { private: friend MatrixSolverBase>; friend MatrixSolverPartitionedBase>; public: MatrixPartitionedSolver() = default; /** - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::MatrixPartitioned(). - \param b_u unknown dofval [nnu]. - \param x_p prescribed dofval [nnp] - \return x_u unknown dofval [nnu]. - */ + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::MatrixPartitioned(). + * @param b_u unknown dofval [nnu]. + * @param x_p prescribed dofval [nnp] + * @return x_u unknown dofval [nnu]. + */ template array_type::tensor Solve_u( M& A, const array_type::tensor& b_u, const array_type::tensor& x_p) { GOOSEFEM_ASSERT(xt::has_shape(b_u, {A.nnu()})); GOOSEFEM_ASSERT(xt::has_shape(x_p, {A.nnp()})); array_type::tensor x_u = xt::empty_like(b_u); this->solve_u_impl(A, b_u, x_p, x_u); return x_u; } /** - Same as - Solve \f$ x = A^{-1} b \f$. - - \param A GooseFEM (sparse) matrix, see e.g. GooseFEM::MatrixPartitioned(). - \param b_u unknown dofval [nnu]. - \param x_p prescribed dofval [nnp] - \param x_u (overwritten) unknown dofval [nnu]. - */ + * Same as + * Solve \f$ x = A^{-1} b \f$. + * + * @param A GooseFEM (sparse) matrix, see e.g. GooseFEM::MatrixPartitioned(). + * @param b_u unknown dofval [nnu]. + * @param x_p prescribed dofval [nnp] + * @param x_u (overwritten) unknown dofval [nnu]. + */ template void solve_u( M& A, const array_type::tensor& b_u, const array_type::tensor& x_p, array_type::tensor& x_u) { GOOSEFEM_ASSERT(xt::has_shape(b_u, {A.nnu()})); GOOSEFEM_ASSERT(xt::has_shape(x_p, {A.nnp()})); GOOSEFEM_ASSERT(xt::has_shape(x_u, {A.nnu()})); this->solve_u_impl(A, b_u, x_p, x_u); } private: template void solve_nodevec_impl(MatrixPartitioned& A, const T& b, T& x) { this->factorize(A); Eigen::VectorXd B_u = A.AsDofs_u(b); Eigen::VectorXd X_p = A.AsDofs_p(x); Eigen::VectorXd X_u = m_solver.solve(Eigen::VectorXd(B_u - A.m_Aup * X_p)); #pragma omp parallel for for (size_t m = 0; m < A.m_nnode; ++m) { for (size_t i = 0; i < A.m_ndim; ++i) { if (A.m_part(m, i) < A.m_nnu) { x(m, i) = X_u(A.m_part(m, i)); } } } } template void solve_dofval_impl(MatrixPartitioned& A, const T& b, T& x) { this->factorize(A); Eigen::VectorXd B_u = A.AsDofs_u(b); Eigen::VectorXd X_p = A.AsDofs_p(x); Eigen::VectorXd X_u = m_solver.solve(Eigen::VectorXd(B_u - A.m_Aup * X_p)); #pragma omp parallel for for (size_t d = 0; d < A.m_nnu; ++d) { x(A.m_iiu(d)) = X_u(d); } } template void solve_u_impl(MatrixPartitioned& A, const T& b_u, const T& x_p, T& x_u) { this->factorize(A); Eigen::Map(x_u.data(), x_u.size()).noalias() = m_solver.solve(Eigen::VectorXd( Eigen::Map(b_u.data(), b_u.size()) - A.m_Aup * Eigen::Map(x_p.data(), x_p.size()))); } private: Solver m_solver; ///< solver bool m_factor = true; ///< signal to force factorization /** - compute inverse (evaluated by "solve") - */ + * compute inverse (evaluated by "solve") + */ void factorize(MatrixPartitioned& A) { if (!A.m_changed && !m_factor) { return; } m_solver.compute(A.m_Auu); m_factor = false; A.m_changed = false; } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MatrixPartitionedTyings.h b/include/GooseFEM/MatrixPartitionedTyings.h index 46a0b9f..730eec5 100644 --- a/include/GooseFEM/MatrixPartitionedTyings.h +++ b/include/GooseFEM/MatrixPartitionedTyings.h @@ -1,665 +1,665 @@ /** -Sparse matrix that is partitioned in: -- unknown DOFs -- prescribed DOFs -- tied DOFs - -\file MatrixPartitionedTyings.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Sparse matrix that is partitioned in: + * - unknown DOFs + * - prescribed DOFs + * - tied DOFs + * + * @file MatrixPartitionedTyings.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MATRIXPARTITIONEDTYINGS_H #define GOOSEFEM_MATRIXPARTITIONEDTYINGS_H #include "Matrix.h" #include "config.h" #include #include #include namespace GooseFEM { // forward declaration template class MatrixPartitionedTyingsSolver; /** -Sparse matrix from with dependent DOFs are eliminated, -and the remaining (small) independent system is partitioned in an unknown and a prescribed part. -In particular: - -\f$ A_{ii} = \begin{bmatrix} A_{uu} & A_{up} \\ A_{pu} & A_{pp} \end{bmatrix} \f$ - -See VectorPartitionedTyings() for bookkeeping definitions. -*/ + * Sparse matrix from with dependent DOFs are eliminated, + * and the remaining (small) independent system is partitioned in an unknown and a prescribed part. + * In particular: + * + * \f$ A_{ii} = \begin{bmatrix} A_{uu} & A_{up} \\ A_{pu} & A_{pp} \end{bmatrix} \f$ + * + * See VectorPartitionedTyings() for bookkeeping definitions. + */ class MatrixPartitionedTyings : public MatrixPartitionedTyingsBase { private: friend MatrixBase; friend MatrixPartitionedBase; friend MatrixPartitionedTyingsBase; protected: Eigen::SparseMatrix m_Auu; ///< The matrix. Eigen::SparseMatrix m_Aup; ///< The matrix. Eigen::SparseMatrix m_Apu; ///< The matrix. Eigen::SparseMatrix m_App; ///< The matrix. Eigen::SparseMatrix m_Aud; ///< The matrix. Eigen::SparseMatrix m_Apd; ///< The matrix. Eigen::SparseMatrix m_Adu; ///< The matrix. Eigen::SparseMatrix m_Adp; ///< The matrix. Eigen::SparseMatrix m_Add; ///< The matrix. Eigen::SparseMatrix m_ACuu; ///< // The matrix for which the tyings have been applied. Eigen::SparseMatrix m_ACup; ///< // The matrix for which the tyings have been applied. Eigen::SparseMatrix m_ACpu; ///< // The matrix for which the tyings have been applied. Eigen::SparseMatrix m_ACpp; ///< // The matrix for which the tyings have been applied. std::vector> m_Tuu; ///< Matrix entries. std::vector> m_Tup; ///< Matrix entries. std::vector> m_Tpu; ///< Matrix entries. std::vector> m_Tpp; ///< Matrix entries. std::vector> m_Tud; ///< Matrix entries. std::vector> m_Tpd; ///< Matrix entries. std::vector> m_Tdu; ///< Matrix entries. std::vector> m_Tdp; ///< Matrix entries. std::vector> m_Tdd; ///< Matrix entries. Eigen::SparseMatrix m_Cdu; ///< Tying matrix, see Tyings::Periodic::Cdu(). Eigen::SparseMatrix m_Cdp; ///< Tying matrix, see Tyings::Periodic::Cdp(). Eigen::SparseMatrix m_Cud; ///< Transpose of "m_Cdu". Eigen::SparseMatrix m_Cpd; ///< Transpose of "m_Cdp". // grant access to solver class template friend class MatrixPartitionedTyingsSolver; public: MatrixPartitionedTyings() = default; /** - Constructor. - - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - \param Cdu See Tyings::Periodic::Cdu(). - \param Cdp See Tyings::Periodic::Cdp(). - */ + * Constructor. + * + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + * @param Cdu See Tyings::Periodic::Cdu(). + * @param Cdp See Tyings::Periodic::Cdp(). + */ MatrixPartitionedTyings( const array_type::tensor& conn, const array_type::tensor& dofs, const Eigen::SparseMatrix& Cdu, const Eigen::SparseMatrix& Cdp) { GOOSEFEM_ASSERT(Cdu.rows() == Cdp.rows()); m_conn = conn; m_dofs = dofs; m_Cdu = Cdu; m_Cdp = Cdp; m_nnu = static_cast(m_Cdu.cols()); m_nnp = static_cast(m_Cdp.cols()); m_nnd = static_cast(m_Cdp.rows()); m_nni = m_nnu + m_nnp; m_ndof = m_nni + m_nnd; m_iiu = xt::arange(m_nnu); m_iip = xt::arange(m_nnu, m_nnu + m_nnp); m_iii = xt::arange(m_nni); m_iid = xt::arange(m_nni, m_nni + m_nnd); m_nelem = m_conn.shape(0); m_nne = m_conn.shape(1); m_nnode = m_dofs.shape(0); m_ndim = m_dofs.shape(1); m_Cud = m_Cdu.transpose(); m_Cpd = m_Cdp.transpose(); m_Tuu.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tup.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tpu.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tpp.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tud.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tpd.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tdu.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tdp.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Tdd.reserve(m_nelem * m_nne * m_ndim * m_nne * m_ndim); m_Auu.resize(m_nnu, m_nnu); m_Aup.resize(m_nnu, m_nnp); m_Apu.resize(m_nnp, m_nnu); m_App.resize(m_nnp, m_nnp); m_Aud.resize(m_nnu, m_nnd); m_Apd.resize(m_nnp, m_nnd); m_Adu.resize(m_nnd, m_nnu); m_Adp.resize(m_nnd, m_nnp); m_Add.resize(m_nnd, m_nnd); GOOSEFEM_ASSERT(m_ndof <= m_nnode * m_ndim); GOOSEFEM_ASSERT(m_ndof == xt::amax(m_dofs)() + 1); } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_uu() const { return m_Auu; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_up() const { return m_Aup; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_pu() const { return m_Apu; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_pp() const { return m_App; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_ud() const { return m_Aud; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_pd() const { return m_Apd; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_du() const { return m_Adu; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_dp() const { return m_Adp; } /** - Pointer to data. - */ + * Pointer to data. + */ const Eigen::SparseMatrix& data_dd() const { return m_Add; } private: template void assemble_impl(const T& elemmat) { GOOSEFEM_ASSERT(xt::has_shape(elemmat, {m_nelem, m_nne * m_ndim, m_nne * m_ndim})); m_Tuu.clear(); m_Tup.clear(); m_Tpu.clear(); m_Tpp.clear(); m_Tud.clear(); m_Tpd.clear(); m_Tdu.clear(); m_Tdp.clear(); m_Tdd.clear(); for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { size_t di = m_dofs(m_conn(e, m), i); for (size_t n = 0; n < m_nne; ++n) { for (size_t j = 0; j < m_ndim; ++j) { size_t dj = m_dofs(m_conn(e, n), j); if (di < m_nnu && dj < m_nnu) { m_Tuu.push_back(Eigen::Triplet( di, dj, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (di < m_nnu && dj < m_nni) { m_Tup.push_back(Eigen::Triplet( di, dj - m_nnu, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (di < m_nnu) { m_Tud.push_back(Eigen::Triplet( di, dj - m_nni, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (di < m_nni && dj < m_nnu) { m_Tpu.push_back(Eigen::Triplet( di - m_nnu, dj, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (di < m_nni && dj < m_nni) { m_Tpp.push_back(Eigen::Triplet( di - m_nnu, dj - m_nnu, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (di < m_nni) { m_Tpd.push_back(Eigen::Triplet( di - m_nnu, dj - m_nni, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (dj < m_nnu) { m_Tdu.push_back(Eigen::Triplet( di - m_nni, dj, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else if (dj < m_nni) { m_Tdp.push_back(Eigen::Triplet( di - m_nni, dj - m_nnu, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } else { m_Tdd.push_back(Eigen::Triplet( di - m_nni, dj - m_nni, elemmat(e, m * m_ndim + i, n * m_ndim + j))); } } } } } } m_Auu.setFromTriplets(m_Tuu.begin(), m_Tuu.end()); m_Aup.setFromTriplets(m_Tup.begin(), m_Tup.end()); m_Apu.setFromTriplets(m_Tpu.begin(), m_Tpu.end()); m_App.setFromTriplets(m_Tpp.begin(), m_Tpp.end()); m_Aud.setFromTriplets(m_Tud.begin(), m_Tud.end()); m_Apd.setFromTriplets(m_Tpd.begin(), m_Tpd.end()); m_Adu.setFromTriplets(m_Tdu.begin(), m_Tdu.end()); m_Adp.setFromTriplets(m_Tdp.begin(), m_Tdp.end()); m_Add.setFromTriplets(m_Tdd.begin(), m_Tdd.end()); m_changed = true; } // todo: test template void todense_impl(T& ret) const { ret.fill(0.0); for (int k = 0; k < m_Auu.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Auu, k); it; ++it) { ret(m_iiu(it.row()), m_iiu(it.col())) = it.value(); } } for (int k = 0; k < m_Aup.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Aup, k); it; ++it) { ret(m_iiu(it.row()), m_iip(it.col())) = it.value(); } } for (int k = 0; k < m_Apu.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Apu, k); it; ++it) { ret(m_iip(it.row()), m_iiu(it.col())) = it.value(); } } for (int k = 0; k < m_App.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_App, k); it; ++it) { ret(m_iip(it.row()), m_iip(it.col())) = it.value(); } } for (int k = 0; k < m_Adu.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Adu, k); it; ++it) { ret(m_iid(it.row()), m_iiu(it.col())) = it.value(); } } for (int k = 0; k < m_Adp.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Adp, k); it; ++it) { ret(m_iid(it.row()), m_iip(it.col())) = it.value(); } } for (int k = 0; k < m_Aud.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Aud, k); it; ++it) { ret(m_iiu(it.row()), m_iid(it.col())) = it.value(); } } for (int k = 0; k < m_Apd.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Apd, k); it; ++it) { ret(m_iip(it.row()), m_iid(it.col())) = it.value(); } } for (int k = 0; k < m_Add.outerSize(); ++k) { for (Eigen::SparseMatrix::InnerIterator it(m_Add, k); it; ++it) { ret(m_iid(it.row()), m_iid(it.col())) = it.value(); } } } template void dot_nodevec_impl(const T& x, T& b) const { UNUSED(x); UNUSED(b); throw std::runtime_error("Not yet implemented"); } template void dot_dofval_impl(const T& x, T& b) const { UNUSED(x); UNUSED(b); throw std::runtime_error("Not yet implemented"); } template void reaction_nodevec_impl(const T& x, T& b) const { UNUSED(x); UNUSED(b); throw std::runtime_error("Not yet implemented"); } template void reaction_dofval_impl(const T& x, T& b) const { UNUSED(x); UNUSED(b); throw std::runtime_error("Not yet implemented"); } void reaction_p_impl( const array_type::tensor& x_u, const array_type::tensor& x_p, array_type::tensor& b_p) const { UNUSED(x_u); UNUSED(x_p); UNUSED(b_p); throw std::runtime_error("Not yet implemented"); } private: // Convert arrays (Eigen version of VectorPartitioned, which contains public functions) Eigen::VectorXd AsDofs_u(const array_type::tensor& dofval) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); Eigen::VectorXd dofval_u(m_nnu, 1); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { dofval_u(d) = dofval(m_iiu(d)); } return dofval_u; } Eigen::VectorXd AsDofs_u(const array_type::tensor& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval_u = Eigen::VectorXd::Zero(m_nnu, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_dofs(m, i) < m_nnu) { dofval_u(m_dofs(m, i)) = nodevec(m, i); } } } return dofval_u; } Eigen::VectorXd AsDofs_p(const array_type::tensor& dofval) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); Eigen::VectorXd dofval_p(m_nnp, 1); #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { dofval_p(d) = dofval(m_iip(d)); } return dofval_p; } Eigen::VectorXd AsDofs_p(const array_type::tensor& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval_p = Eigen::VectorXd::Zero(m_nnp, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_dofs(m, i) >= m_nnu && m_dofs(m, i) < m_nni) { dofval_p(m_dofs(m, i) - m_nnu) = nodevec(m, i); } } } return dofval_p; } Eigen::VectorXd AsDofs_d(const array_type::tensor& dofval) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); Eigen::VectorXd dofval_d(m_nnd, 1); #pragma omp parallel for for (size_t d = 0; d < m_nnd; ++d) { dofval_d(d) = dofval(m_iip(d)); } return dofval_d; } Eigen::VectorXd AsDofs_d(const array_type::tensor& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval_d = Eigen::VectorXd::Zero(m_nnd, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_dofs(m, i) >= m_nni) { dofval_d(m_dofs(m, i) - m_nni) = nodevec(m, i); } } } return dofval_d; } }; /** -Solver for MatrixPartitionedTyings(). -This solver class can be used to solve for multiple right-hand-sides using one factorisation. - -Solving proceeds as follows: - -\f$ A' = A_{ii} + A_{id} * C_{di} + C_{di}^T * A_{di} + C_{di}^T * A_{dd} * C_{di} \f$ - -\f$ b' = b_i + C_{di}^T * b_d \f$ - -\f$ x_u = A'_{uu} \ ( b'_u - A'_{up} * x_p ) \f$ - -\f$ x_i = \begin{bmatrix} x_u \\ x_p \end{bmatrix} \f$ - -\f$ x_d = C_{di} * x_i \f$ -*/ + * Solver for MatrixPartitionedTyings(). + * This solver class can be used to solve for multiple right-hand-sides using one factorisation. + * + * Solving proceeds as follows: + * + * \f$ A' = A_{ii} + A_{id} * C_{di} + C_{di}^T * A_{di} + C_{di}^T * A_{dd} * C_{di} \f$ + * + * \f$ b' = b_i + C_{di}^T * b_d \f$ + * + * \f$ x_u = A'_{uu} \ ( b'_u - A'_{up} * x_p ) \f$ + * + * \f$ x_i = \begin{bmatrix} x_u \\ x_p \end{bmatrix} \f$ + * + * \f$ x_d = C_{di} * x_i \f$ + */ template >> class MatrixPartitionedTyingsSolver : public MatrixSolverBase>, public MatrixSolverPartitionedBase> { private: friend MatrixSolverBase>; friend MatrixSolverPartitionedBase>; public: MatrixPartitionedTyingsSolver() = default; private: template void solve_nodevec_impl(MatrixPartitionedTyings& A, const T& b, T& x) { this->factorize(A); Eigen::VectorXd B_u = A.AsDofs_u(b); Eigen::VectorXd B_d = A.AsDofs_d(b); Eigen::VectorXd X_p = A.AsDofs_p(x); B_u += A.m_Cud * B_d; Eigen::VectorXd X_u = m_solver.solve(Eigen::VectorXd(B_u - A.m_ACup * X_p)); Eigen::VectorXd X_d = A.m_Cdu * X_u + A.m_Cdp * X_p; #pragma omp parallel for for (size_t m = 0; m < A.m_nnode; ++m) { for (size_t i = 0; i < A.m_ndim; ++i) { if (A.m_dofs(m, i) < A.m_nnu) { x(m, i) = X_u(A.m_dofs(m, i)); } else if (A.m_dofs(m, i) >= A.m_nni) { x(m, i) = X_d(A.m_dofs(m, i) - A.m_nni); } } } } template void solve_dofval_impl(MatrixPartitionedTyings& A, const T& b, T& x) { this->factorize(A); Eigen::VectorXd B_u = A.AsDofs_u(b); Eigen::VectorXd B_d = A.AsDofs_d(b); Eigen::VectorXd X_p = A.AsDofs_p(x); Eigen::VectorXd X_u = m_solver.solve(Eigen::VectorXd(B_u - A.m_ACup * X_p)); Eigen::VectorXd X_d = A.m_Cdu * X_u + A.m_Cdp * X_p; #pragma omp parallel for for (size_t d = 0; d < A.m_nnu; ++d) { x(A.m_iiu(d)) = X_u(d); } #pragma omp parallel for for (size_t d = 0; d < A.m_nnd; ++d) { x(A.m_iid(d)) = X_d(d); } } public: /** - Same as - Solve(MatrixPartitionedTyings&, const array_type::tensor&, const - array_type::tensor&), but with partitioned input and output. - - \param A sparse matrix, see MatrixPartitionedTyings(). - \param b_u unknown dofval [nnu]. - \param b_d dependent dofval [nnd]. - \param x_p prescribed dofval [nnp] - \return x_u unknown dofval [nnu]. - */ + * Same as + * Solve(MatrixPartitionedTyings&, const array_type::tensor&, const + * array_type::tensor&), but with partitioned input and output. + * + * @param A sparse matrix, see MatrixPartitionedTyings(). + * @param b_u unknown dofval [nnu]. + * @param b_d dependent dofval [nnd]. + * @param x_p prescribed dofval [nnp] + * @return x_u unknown dofval [nnu]. + */ array_type::tensor Solve_u( MatrixPartitionedTyings& A, const array_type::tensor& b_u, const array_type::tensor& b_d, const array_type::tensor& x_p) { array_type::tensor x_u = xt::empty({A.m_nnu}); this->solve_u(A, b_u, b_d, x_p, x_u); return x_u; } /** - Same as - Solve_u(MatrixPartitionedTyings&, const array_type::tensor&, const - array_type::tensor&, const array_type::tensor&), but writing to - pre-allocated output. - - \param A sparse matrix, see MatrixPartitionedTyings(). - \param b_u unknown dofval [nnu]. - \param b_d dependent dofval [nnd]. - \param x_p prescribed dofval [nnp] - \param x_u (overwritten) unknown dofval [nnu]. - */ + * Same as + * Solve_u(MatrixPartitionedTyings&, const array_type::tensor&, const + * array_type::tensor&, const array_type::tensor&), but writing to + * pre-allocated output. + * + * @param A sparse matrix, see MatrixPartitionedTyings(). + * @param b_u unknown dofval [nnu]. + * @param b_d dependent dofval [nnd]. + * @param x_p prescribed dofval [nnp] + * @param x_u (overwritten) unknown dofval [nnu]. + */ void solve_u( MatrixPartitionedTyings& A, const array_type::tensor& b_u, const array_type::tensor& b_d, const array_type::tensor& x_p, array_type::tensor& x_u) { UNUSED(b_d); GOOSEFEM_ASSERT(b_u.size() == A.m_nnu); GOOSEFEM_ASSERT(b_d.size() == A.m_nnd); GOOSEFEM_ASSERT(x_p.size() == A.m_nnp); GOOSEFEM_ASSERT(x_u.size() == A.m_nnu); this->factorize(A); Eigen::Map(x_u.data(), x_u.size()).noalias() = m_solver.solve(Eigen::VectorXd( Eigen::Map(b_u.data(), b_u.size()) - A.m_ACup * Eigen::Map(x_p.data(), x_p.size()))); } private: Solver m_solver; ///< solver bool m_factor = true; ///< signal to force factorization /** - compute inverse (evaluated by "solve") - */ + * compute inverse (evaluated by "solve") + */ void factorize(MatrixPartitionedTyings& A) { if (!A.m_changed && !m_factor) { return; } A.m_ACuu = A.m_Auu + A.m_Aud * A.m_Cdu + A.m_Cud * A.m_Adu + A.m_Cud * A.m_Add * A.m_Cdu; A.m_ACup = A.m_Aup + A.m_Aud * A.m_Cdp + A.m_Cud * A.m_Adp + A.m_Cud * A.m_Add * A.m_Cdp; // A.m_ACpu = A.m_Apu + A.m_Apd * A.m_Cdu + A.m_Cpd * A.m_Adu // + A.m_Cpd * A.m_Add * A.m_Cdu; // A.m_ACpp = A.m_App + A.m_Apd * A.m_Cdp + A.m_Cpd * A.m_Adp // + A.m_Cpd * A.m_Add * A.m_Cdp; m_solver.compute(A.m_ACuu); m_factor = false; A.m_changed = false; } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/Mesh.h b/include/GooseFEM/Mesh.h index 0b759e8..806ae7f 100644 --- a/include/GooseFEM/Mesh.h +++ b/include/GooseFEM/Mesh.h @@ -1,2862 +1,2866 @@ /** -Generic mesh operations. - -\file Mesh.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Generic mesh operations. + * + * @file Mesh.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MESH_H #define GOOSEFEM_MESH_H #include "ElementQuad4.h" #include "MatrixDiagonal.h" #include "Vector.h" #include "assertions.h" #include "config.h" namespace GooseFEM { /** -Generic mesh operations, and simple mesh definitions. -*/ + * Generic mesh operations, and simple mesh definitions. + */ namespace Mesh { template inline std::vector> nodaltyings(const D& dofs); /** -Enumerator for element-types -*/ + * Enumerator for element-types + */ enum class ElementType { Unknown, ///< Unknown element-type Quad4, ///< Quadrilateral: 4-noded element in 2-d Hex8, ///< Hexahedron: 8-noded element in 3-d Tri3 ///< Triangle: 3-noded element in 2-d }; /** -Extract the element type based on the connectivity. - -\param coor Nodal coordinates [nnode, ndim]. -\param conn Connectivity [nelem, nne]. -\return ElementType(). -*/ + * Extract the element type based on the connectivity. + * + * @param coor Nodal coordinates [nnode, ndim]. + * @param conn Connectivity [nelem, nne]. + * @return ElementType(). + */ template inline ElementType defaultElementType(const S& coor, const T& conn) { GOOSEFEM_ASSERT(coor.dimension() == 2); GOOSEFEM_ASSERT(conn.dimension() == 2); if (coor.shape(1) == 2ul && conn.shape(1) == 3ul) { return ElementType::Tri3; } if (coor.shape(1) == 2ul && conn.shape(1) == 4ul) { return ElementType::Quad4; } if (coor.shape(1) == 3ul && conn.shape(1) == 8ul) { return ElementType::Hex8; } throw std::runtime_error("Element-type not implemented"); } namespace detail { template inline T renum(const T& arg, const R& mapping) { T ret = T::from_shape(arg.shape()); auto jt = ret.begin(); for (auto it = arg.begin(); it != arg.end(); ++it, ++jt) { *jt = mapping(*it); } return ret; } } // namespace detail /** -List with DOF-numbers in sequential order. -The output is a sequential list of DOF-numbers for each vector-component of each node. -For example for 3 nodes in 2 dimensions the output is - -\f$ \begin{bmatrix} 0 & 1 \\ 2 & 3 \\ 4 & 5 \end{bmatrix} \f$ - -\param nnode Number of nodes. -\param ndim Number of dimensions. -\return DOF-numbers. -*/ + * List with DOF-numbers in sequential order. + * The output is a sequential list of DOF-numbers for each vector-component of each node. + * For example for 3 nodes in 2 dimensions the output is + * + * \f$ \begin{bmatrix} 0 & 1 \\ 2 & 3 \\ 4 & 5 \end{bmatrix} \f$ + * + * @param nnode Number of nodes. + * @param ndim Number of dimensions. + * @return DOF-numbers. + */ inline array_type::tensor dofs(size_t nnode, size_t ndim) { return xt::reshape_view(xt::arange(nnode * ndim), {nnode, ndim}); } /** -Renumber indices to lowest possible index. For example: - -\f$ \begin{bmatrix} 0 & 1 \\ 5 & 4 \end{bmatrix} \f$ - -is renumbered to - -\f$ \begin{bmatrix} 0 & 1 \\ 3 & 2 \end{bmatrix} \f$ - -Or, in pseudo-code, the result of this function is that: - - dofs = renumber(dofs) - sort(unique(dofs[:])) == range(max(dofs+1)) - -\note One can use the wrapper function renumber(). This class gives more advanced features. -*/ + * Renumber indices to lowest possible index. For example: + * + * \f$ \begin{bmatrix} 0 & 1 \\ 5 & 4 \end{bmatrix} \f$ + * + * is renumbered to + * + * \f$ \begin{bmatrix} 0 & 1 \\ 3 & 2 \end{bmatrix} \f$ + * + * Or, in pseudo-code, the result of this function is that: + * + * dofs = renumber(dofs) + * sort(unique(dofs[:])) == range(max(dofs+1)) + * + * \note One can use the wrapper function renumber(). This class gives more advanced features. + */ class Renumber { public: Renumber() = default; /** - \param dofs DOF-numbers. - */ + * @param dofs DOF-numbers. + */ template Renumber(const T& dofs) { size_t n = xt::amax(dofs)() + 1; size_t i = 0; array_type::tensor unique = xt::unique(dofs); m_renum = xt::empty({n}); for (auto& j : unique) { m_renum(j) = i; ++i; } } /** - Apply renumbering to other set. - - \param list List of (DOF-)numbers. - \return Renumbered list of (DOF-)numbers. - */ + * Apply renumbering to other set. + * + * @param list List of (DOF-)numbers. + * @return Renumbered list of (DOF-)numbers. + */ template T apply(const T& list) const { return detail::renum(list, m_renum); } /** - Get the list needed to renumber, e.g.: - - dofs_renumbered(i, j) = index(dofs(i, j)) - - \return Renumber-index. - */ + * Get the list needed to renumber, e.g.: + * + * dofs_renumbered(i, j) = index(dofs(i, j)) + * + * @return Renumber-index. + */ const array_type::tensor& index() const { return m_renum; } private: array_type::tensor m_renum; }; /** -Renumber to lowest possible index (see GooseFEM::Mesh::Renumber). - -\param dofs DOF-numbers [nnode, ndim]. -\return Renumbered DOF-numbers. -*/ + * Renumber to lowest possible index (see GooseFEM::Mesh::Renumber). + * + * @param dofs DOF-numbers [nnode, ndim]. + * @return Renumbered DOF-numbers. + */ template inline T renumber(const T& dofs) { return Renumber(dofs).apply(dofs); } /** -CRTP base class for regular meshes. -*/ + * CRTP base class for regular meshes. + */ template class RegularBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; /** - Number of elements. - \return unsigned int - */ + * Number of elements. + * @return unsigned int + */ auto nelem() const { return derived_cast().m_nelem; } /** - Number of nodes. - \return unsigned int - */ + * Number of nodes. + * @return unsigned int + */ auto nnode() const { return derived_cast().m_nnode; } /** - Number of nodes-per-element == 4. - \return unsigned int - */ + * Number of nodes-per-element == 4. + * @return unsigned int + */ auto nne() const { return derived_cast().m_nne; } /** - Number of dimensions == 2. - \return unsigned int - */ + * Number of dimensions == 2. + * @return unsigned int + */ auto ndim() const { return derived_cast().m_ndim; } /** - Number of elements in x-direction == width of the mesh in units of #h. - \return unsigned int - */ + * Number of elements in x-direction == width of the mesh in units of #h. + * @return unsigned int + */ auto nelx() const { return derived_cast().nelx_impl(); } /** - Number of elements in y-direction == height of the mesh, in units of #h, - \return unsigned int - */ + * Number of elements in y-direction == height of the mesh, in units of #h, + * @return unsigned int + */ auto nely() const { return derived_cast().nely_impl(); } /** - Linear edge size of one 'block'. - \return double - */ + * Linear edge size of one 'block'. + * @return double + */ auto h() const { return derived_cast().m_h; } /** - The ElementType(). - \return element type - */ + * The ElementType(). + * @return element type + */ auto getElementType() const { return derived_cast().getElementType_impl(); } /** - Nodal coordinates [#nnode, #ndim]. - \return coordinates per node - */ + * Nodal coordinates [#nnode, #ndim]. + * @return coordinates per node + */ auto coor() const { return derived_cast().coor_impl(); } /** - Connectivity [#nelem, #nne]. - \return nodes per element - */ + * Connectivity [#nelem, #nne]. + * @return nodes per element + */ auto conn() const { return derived_cast().conn_impl(); } /** - DOF numbers for each node (numbered sequentially) [#nnode, #ndim]. - \return DOFs per node - */ + * DOF numbers for each node (numbered sequentially) [#nnode, #ndim]. + * @return DOFs per node + */ auto dofs() const { return GooseFEM::Mesh::dofs(this->nnode(), this->ndim()); } /** - DOF-numbers for the case that the periodicity if fully eliminated. - \return DOF numbers for each node [#nnode, #ndim]. - */ + * DOF-numbers for the case that the periodicity if fully eliminated. + * @return DOF numbers for each node [#nnode, #ndim]. + */ auto dofsPeriodic() const { array_type::tensor ret = this->dofs(); array_type::tensor nodePer = this->nodesPeriodic(); array_type::tensor independent = xt::view(nodePer, xt::all(), 0); array_type::tensor dependent = xt::view(nodePer, xt::all(), 1); for (size_t j = 0; j < this->ndim(); ++j) { xt::view(ret, xt::keep(dependent), j) = xt::view(ret, xt::keep(independent), j); } return GooseFEM::Mesh::renumber(ret); } /** - Periodic node pairs, in two columns: (independent, dependent). - \return [ntyings, #ndim]. - */ + * Periodic node pairs, in two columns: (independent, dependent). + * @return [ntyings, #ndim]. + */ auto nodesPeriodic() const { return derived_cast().nodesPeriodic_impl(); } /** - Reference node to use for periodicity, because all corners are tied to it. - \return Node number. - */ + * Reference node to use for periodicity, because all corners are tied to it. + * @return Node number. + */ auto nodesOrigin() const { return derived_cast().nodesOrigin_impl(); } private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } }; /** -CRTP base class for regular meshes in 2d. -*/ + * CRTP base class for regular meshes in 2d. + */ template class RegularBase2d : public RegularBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; /** - Nodes along the bottom edge (y = 0), in order of increasing x. - \return List of node numbers. - */ + * Nodes along the bottom edge (y = 0), in order of increasing x. + * @return List of node numbers. + */ auto nodesBottomEdge() const { return derived_cast().nodesBottomEdge_impl(); } /** - Nodes along the top edge (y = #nely * #h), in order of increasing x. - \return List of node numbers. - */ + * Nodes along the top edge (y = #nely * #h), in order of increasing x. + * @return List of node numbers. + */ auto nodesTopEdge() const { return derived_cast().nodesTopEdge_impl(); } /** - Nodes along the left edge (x = 0), in order of increasing y. - \return List of node numbers. - */ + * Nodes along the left edge (x = 0), in order of increasing y. + * @return List of node numbers. + */ auto nodesLeftEdge() const { return derived_cast().nodesLeftEdge_impl(); } /** - Nodes along the right edge (x = #nelx * #h), in order of increasing y. - \return List of node numbers. - */ + * Nodes along the right edge (x = #nelx * #h), in order of increasing y. + * @return List of node numbers. + */ auto nodesRightEdge() const { return derived_cast().nodesRightEdge_impl(); } /** - Nodes along the bottom edge (y = 0), without the corners (at x = 0 and x = #nelx * #h). - Same as: nodesBottomEdge()[1: -1]. - \return List of node numbers. - */ + * Nodes along the bottom edge (y = 0), without the corners (at x = 0 and x = #nelx * #h). + * Same as: nodesBottomEdge()[1: -1]. + * @return List of node numbers. + */ auto nodesBottomOpenEdge() const { return derived_cast().nodesBottomOpenEdge_impl(); } /** - Nodes along the top edge (y = #nely * #h), without the corners (at x = 0 and x = #nelx * #h). - Same as: nodesTopEdge()[1: -1]. - \return List of node numbers. - */ + * Nodes along the top edge (y = #nely * #h), without the corners (at x = 0 and x = #nelx * #h). + * Same as: nodesTopEdge()[1: -1]. + * @return List of node numbers. + */ auto nodesTopOpenEdge() const { return derived_cast().nodesTopOpenEdge_impl(); } /** - Nodes along the left edge (x = 0), without the corners (at y = 0 and y = #nely * #h). - Same as: nodesLeftEdge()[1: -1]. - \return List of node numbers. - */ + * Nodes along the left edge (x = 0), without the corners (at y = 0 and y = #nely * #h). + * Same as: nodesLeftEdge()[1: -1]. + * @return List of node numbers. + */ auto nodesLeftOpenEdge() const { return derived_cast().nodesLeftOpenEdge_impl(); } /** - Nodes along the right edge (x = #nelx * #h), without the corners (at y = 0 and y = #nely * #h). - Same as: nodesRightEdge()[1: -1]. - \return List of node numbers. - */ + * Nodes along the right edge (x = #nelx * #h), without the corners (at y = 0 and y = #nely * + * #h). Same as: nodesRightEdge()[1: -1]. + * @return List of node numbers. + */ auto nodesRightOpenEdge() const { return derived_cast().nodesRightOpenEdge_impl(); } /** - The bottom-left corner node (at x = 0, y = 0). - Same as nodesBottomEdge()[0] and nodesLeftEdge()[0]. - \return Node number. - */ + * The bottom-left corner node (at x = 0, y = 0). + * Same as nodesBottomEdge()[0] and nodesLeftEdge()[0]. + * @return Node number. + */ auto nodesBottomLeftCorner() const { return derived_cast().nodesBottomLeftCorner_impl(); } /** - The bottom-right corner node (at x = #nelx * #h, y = 0). - Same as nodesBottomEdge()[-1] and nodesRightEdge()[0]. - \return Node number. - */ + * The bottom-right corner node (at x = #nelx * #h, y = 0). + * Same as nodesBottomEdge()[-1] and nodesRightEdge()[0]. + * @return Node number. + */ auto nodesBottomRightCorner() const { return derived_cast().nodesBottomRightCorner_impl(); } /** - The top-left corner node (at x = 0, y = #nely * #h). - Same as nodesTopEdge()[0] and nodesRightEdge()[-1]. - \return Node number. - */ + * The top-left corner node (at x = 0, y = #nely * #h). + * Same as nodesTopEdge()[0] and nodesRightEdge()[-1]. + * @return Node number. + */ auto nodesTopLeftCorner() const { return derived_cast().nodesTopLeftCorner_impl(); } /** - The top-right corner node (at x = #nelx * #h, y = #nely * #h). - Same as nodesTopEdge()[-1] and nodesRightEdge()[-1]. - \return Node number. - */ + * The top-right corner node (at x = #nelx * #h, y = #nely * #h). + * Same as nodesTopEdge()[-1] and nodesRightEdge()[-1]. + * @return Node number. + */ auto nodesTopRightCorner() const { return derived_cast().nodesTopRightCorner_impl(); } /** - Alias of nodesBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesBottomLeftCorner(). + * @return Node number. + */ auto nodesLeftBottomCorner() const { return derived_cast().nodesBottomLeftCorner_impl(); } /** - Alias of nodesTopLeftCorner(). - \return Node number. - */ + * Alias of nodesTopLeftCorner(). + * @return Node number. + */ auto nodesLeftTopCorner() const { return derived_cast().nodesTopLeftCorner_impl(); } /** - Alias of nodesBottomRightCorner(). - \return Node number. - */ + * Alias of nodesBottomRightCorner(). + * @return Node number. + */ auto nodesRightBottomCorner() const { return derived_cast().nodesBottomRightCorner_impl(); } /** - Alias of nodesTopRightCorner(). - \return Node number. - */ + * Alias of nodesTopRightCorner(). + * @return Node number. + */ auto nodesRightTopCorner() const { return derived_cast().nodesTopRightCorner_impl(); } private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } friend class RegularBase; array_type::tensor nodesPeriodic_impl() const { array_type::tensor bot = derived_cast().nodesBottomOpenEdge_impl(); array_type::tensor top = derived_cast().nodesTopOpenEdge_impl(); array_type::tensor lft = derived_cast().nodesLeftOpenEdge_impl(); array_type::tensor rgt = derived_cast().nodesRightOpenEdge_impl(); std::array shape = {bot.size() + lft.size() + size_t(3), size_t(2)}; auto ret = array_type::tensor::from_shape(shape); ret(0, 0) = derived_cast().nodesBottomLeftCorner_impl(); ret(0, 1) = derived_cast().nodesBottomRightCorner_impl(); ret(1, 0) = derived_cast().nodesBottomLeftCorner_impl(); ret(1, 1) = derived_cast().nodesTopRightCorner_impl(); ret(2, 0) = derived_cast().nodesBottomLeftCorner_impl(); ret(2, 1) = derived_cast().nodesTopLeftCorner_impl(); size_t i = 3; xt::view(ret, xt::range(i, i + bot.size()), 0) = bot; xt::view(ret, xt::range(i, i + bot.size()), 1) = top; i += bot.size(); xt::view(ret, xt::range(i, i + lft.size()), 0) = lft; xt::view(ret, xt::range(i, i + lft.size()), 1) = rgt; return ret; } auto nodesOrigin_impl() const { return derived_cast().nodesBottomLeftCorner_impl(); } }; /** -CRTP base class for regular meshes in 3d. -*/ + * CRTP base class for regular meshes in 3d. + */ template class RegularBase3d : public RegularBase { public: /** - Underlying type. - */ + * Underlying type. + */ using derived_type = D; /** - Number of elements in y-direction == height of the mesh, in units of #h, - \return unsigned int - */ + * Number of elements in y-direction == height of the mesh, in units of #h, + * @return unsigned int + */ auto nelz() const { return derived_cast().nelz_impl(); } /** - Nodes along the bottom face (y = 0). - \return List of node numbers. - */ + * Nodes along the bottom face (y = 0). + * @return List of node numbers. + */ auto nodesBottom() const { return derived_cast().nodesBottom_impl(); } /** - Nodes along the top face (y = #nely * #h). - \return List of node numbers. - */ + * Nodes along the top face (y = #nely * #h). + * @return List of node numbers. + */ auto nodesTop() const { return derived_cast().nodesTop_impl(); } /** - Nodes along the left face (x = 0). - \return List of node numbers. - */ + * Nodes along the left face (x = 0). + * @return List of node numbers. + */ auto nodesLeft() const { return derived_cast().nodesLeft_impl(); } /** - Nodes along the right face (x = #nelx * #h). - \return List of node numbers. - */ + * Nodes along the right face (x = #nelx * #h). + * @return List of node numbers. + */ auto nodesRight() const { return derived_cast().nodesRight_impl(); } /** - Nodes along the front face (z = 0). - \return List of node numbers. - */ + * Nodes along the front face (z = 0). + * @return List of node numbers. + */ auto nodesFront() const { return derived_cast().nodesFront_impl(); } /** - Nodes along the back face (z = #nelz * #h). - \return List of node numbers. - */ + * Nodes along the back face (z = #nelz * #h). + * @return List of node numbers. + */ auto nodesBack() const { return derived_cast().nodesBack_impl(); } /** - Nodes along the edge at the intersection of the front and bottom faces - (z = 0 and y = 0). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the front and bottom faces + * (z = 0 and y = 0). + * @return List of node numbers. + */ auto nodesFrontBottomEdge() const { return derived_cast().nodesFrontBottomEdge_impl(); } /** - Nodes along the edge at the intersection of the front and top faces - (z = 0 and y = #nely * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the front and top faces + * (z = 0 and y = #nely * #h). + * @return List of node numbers. + */ auto nodesFrontTopEdge() const { return derived_cast().nodesFrontTopEdge_impl(); } /** - Nodes along the edge at the intersection of the front and left faces - (z = 0 and x = 0). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the front and left faces + * (z = 0 and x = 0). + * @return List of node numbers. + */ auto nodesFrontLeftEdge() const { return derived_cast().nodesFrontLeftEdge_impl(); } /** - Nodes along the edge at the intersection of the front and right faces - (z = 0 and x = #nelx * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the front and right faces + * (z = 0 and x = #nelx * #h). + * @return List of node numbers. + */ auto nodesFrontRightEdge() const { return derived_cast().nodesFrontRightEdge_impl(); } /** - Nodes along the edge at the intersection of the back and bottom faces - (z = #nelz * #h and y = #nely * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the back and bottom faces + * (z = #nelz * #h and y = #nely * #h). + * @return List of node numbers. + */ auto nodesBackBottomEdge() const { return derived_cast().nodesBackBottomEdge_impl(); } /** - Nodes along the edge at the intersection of the back and top faces - (z = #nelz * #h and x = 0). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the back and top faces + * (z = #nelz * #h and x = 0). + * @return List of node numbers. + */ auto nodesBackTopEdge() const { return derived_cast().nodesBackTopEdge_impl(); } /** - Nodes along the edge at the intersection of the back and left faces - (z = #nelz * #h and x = #nelx * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the back and left faces + * (z = #nelz * #h and x = #nelx * #h). + * @return List of node numbers. + */ auto nodesBackLeftEdge() const { return derived_cast().nodesBackLeftEdge_impl(); } /** - Nodes along the edge at the intersection of the back and right faces - (? = #nelz * #h and ? = ?). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the back and right faces + * (? = #nelz * #h and ? = ?). + * @return List of node numbers. + */ auto nodesBackRightEdge() const { return derived_cast().nodesBackRightEdge_impl(); } /** - Nodes along the edge at the intersection of the bottom and left faces - (y = 0 and x = 0). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the bottom and left faces + * (y = 0 and x = 0). + * @return List of node numbers. + */ auto nodesBottomLeftEdge() const { return derived_cast().nodesBottomLeftEdge_impl(); } /** - Nodes along the edge at the intersection of the bottom and right faces - (y = 0 and x = #nelx * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the bottom and right faces + * (y = 0 and x = #nelx * #h). + * @return List of node numbers. + */ auto nodesBottomRightEdge() const { return derived_cast().nodesBottomRightEdge_impl(); } /** - Nodes along the edge at the intersection of the top and left faces - (y = 0 and x = #nelx * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the top and left faces + * (y = 0 and x = #nelx * #h). + * @return List of node numbers. + */ auto nodesTopLeftEdge() const { return derived_cast().nodesTopLeftEdge_impl(); } /** - Nodes along the edge at the intersection of the top and right faces - (y = #nely * #h and x = #nelx * #h). - \return List of node numbers. - */ + * Nodes along the edge at the intersection of the top and right faces + * (y = #nely * #h and x = #nelx * #h). + * @return List of node numbers. + */ auto nodesTopRightEdge() const { return derived_cast().nodesTopRightEdge_impl(); } /** - Alias of nodesFrontBottomEdge() - \return List of node numbers. - */ + * Alias of nodesFrontBottomEdge() + * @return List of node numbers. + */ auto nodesBottomFrontEdge() const { return derived_cast().nodesFrontBottomEdge_impl(); } /** - Alias of nodesBackBottomEdge() - \return List of node numbers. - */ + * Alias of nodesBackBottomEdge() + * @return List of node numbers. + */ auto nodesBottomBackEdge() const { return derived_cast().nodesBackBottomEdge_impl(); } /** - Alias of nodesFrontTopEdge() - \return List of node numbers. - */ + * Alias of nodesFrontTopEdge() + * @return List of node numbers. + */ auto nodesTopFrontEdge() const { return derived_cast().nodesFrontTopEdge_impl(); } /** - Alias of nodesBackTopEdge() - \return List of node numbers. - */ + * Alias of nodesBackTopEdge() + * @return List of node numbers. + */ auto nodesTopBackEdge() const { return derived_cast().nodesBackTopEdge_impl(); } /** - Alias of nodesBottomLeftEdge() - \return List of node numbers. - */ + * Alias of nodesBottomLeftEdge() + * @return List of node numbers. + */ auto nodesLeftBottomEdge() const { return derived_cast().nodesBottomLeftEdge_impl(); } /** - Alias of nodesFrontLeftEdge() - \return List of node numbers. - */ + * Alias of nodesFrontLeftEdge() + * @return List of node numbers. + */ auto nodesLeftFrontEdge() const { return derived_cast().nodesFrontLeftEdge_impl(); } /** - Alias of nodesBackLeftEdge() - \return List of node numbers. - */ + * Alias of nodesBackLeftEdge() + * @return List of node numbers. + */ auto nodesLeftBackEdge() const { return derived_cast().nodesBackLeftEdge_impl(); } /** - Alias of nodesTopLeftEdge() - \return List of node numbers. - */ + * Alias of nodesTopLeftEdge() + * @return List of node numbers. + */ auto nodesLeftTopEdge() const { return derived_cast().nodesTopLeftEdge_impl(); } /** - Alias of nodesBottomRightEdge() - \return List of node numbers. - */ + * Alias of nodesBottomRightEdge() + * @return List of node numbers. + */ auto nodesRightBottomEdge() const { return derived_cast().nodesBottomRightEdge_impl(); } /** - Alias of nodesTopRightEdge() - \return List of node numbers. - */ + * Alias of nodesTopRightEdge() + * @return List of node numbers. + */ auto nodesRightTopEdge() const { return derived_cast().nodesTopRightEdge_impl(); } /** - Alias of nodesFrontRightEdge() - \return List of node numbers. - */ + * Alias of nodesFrontRightEdge() + * @return List of node numbers. + */ auto nodesRightFrontEdge() const { return derived_cast().nodesFrontRightEdge_impl(); } /** - Alias of nodesBackRightEdge() - \return List of node numbers. - */ + * Alias of nodesBackRightEdge() + * @return List of node numbers. + */ auto nodesRightBackEdge() const { return derived_cast().nodesBackRightEdge_impl(); } /** - Nodes along the front face excluding edges. - Same as different between nodesFront() and - [nodesFrontBottomEdge(), nodesFrontTopEdge(), nodesFrontLeftEdge(), nodesFrontRightEdge()] - \return list of node numbers. - */ + * Nodes along the front face excluding edges. + * Same as different between nodesFront() and + * [nodesFrontBottomEdge(), nodesFrontTopEdge(), nodesFrontLeftEdge(), nodesFrontRightEdge()] + * @return list of node numbers. + */ auto nodesFrontFace() const { return derived_cast().nodesFrontFace_impl(); } /** - Nodes along the back face excluding edges. - Same as different between nodesBack() and - [nodesBackBottomEdge(), nodesBackTopEdge(), nodesBackLeftEdge(), nodesBackRightEdge()] - \return list of node numbers. - */ + * Nodes along the back face excluding edges. + * Same as different between nodesBack() and + * [nodesBackBottomEdge(), nodesBackTopEdge(), nodesBackLeftEdge(), nodesBackRightEdge()] + * @return list of node numbers. + */ auto nodesBackFace() const { return derived_cast().nodesBackFace_impl(); } /** - Nodes along the left face excluding edges. - Same as different between nodesLeft() and - [nodesFrontLeftEdge(), nodesBackLeftEdge(), nodesBottomLeftEdge(), nodesTopLeftEdge()] - \return list of node numbers. - */ + * Nodes along the left face excluding edges. + * Same as different between nodesLeft() and + * [nodesFrontLeftEdge(), nodesBackLeftEdge(), nodesBottomLeftEdge(), nodesTopLeftEdge()] + * @return list of node numbers. + */ auto nodesLeftFace() const { return derived_cast().nodesLeftFace_impl(); } /** - Nodes along the right face excluding edges. - Same as different between nodesRight() and - [nodesFrontRightEdge(), nodesBackRightEdge(), nodesBottomRightEdge(), nodesTopRightEdge()] - \return list of node numbers. - */ + * Nodes along the right face excluding edges. + * Same as different between nodesRight() and + * [nodesFrontRightEdge(), nodesBackRightEdge(), nodesBottomRightEdge(), nodesTopRightEdge()] + * @return list of node numbers. + */ auto nodesRightFace() const { return derived_cast().nodesRightFace_impl(); } /** - Nodes along the bottom face excluding edges. - Same as different between nodesBottom() and - [nodesBackBottomEdge(), nodesBackTopEdge(), nodesBackLeftEdge(), nodesBackRightEdge()] - \return list of node numbers. - */ + * Nodes along the bottom face excluding edges. + * Same as different between nodesBottom() and + * [nodesBackBottomEdge(), nodesBackTopEdge(), nodesBackLeftEdge(), nodesBackRightEdge()] + * @return list of node numbers. + */ auto nodesBottomFace() const { return derived_cast().nodesBottomFace_impl(); } /** - Nodes along the top face excluding edges. - Same as different between nodesTop() and - [nodesFrontBottomEdge(), nodesFrontTopEdge(), nodesFrontLeftEdge(), nodesFrontRightEdge()] - \return list of node numbers. - */ + * Nodes along the top face excluding edges. + * Same as different between nodesTop() and + * [nodesFrontBottomEdge(), nodesFrontTopEdge(), nodesFrontLeftEdge(), nodesFrontRightEdge()] + * @return list of node numbers. + */ auto nodesTopFace() const { return derived_cast().nodesTopFace_impl(); } /** - Same as nodesFrontBottomEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesFrontBottomEdge() but without corners. + * @return List of node numbers. + */ auto nodesFrontBottomOpenEdge() const { return derived_cast().nodesFrontBottomOpenEdge_impl(); } /** - Same as nodesFrontTopEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesFrontTopEdge() but without corners. + * @return List of node numbers. + */ auto nodesFrontTopOpenEdge() const { return derived_cast().nodesFrontTopOpenEdge_impl(); } /** - Same as nodesFrontLeftEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesFrontLeftEdge() but without corners. + * @return List of node numbers. + */ auto nodesFrontLeftOpenEdge() const { return derived_cast().nodesFrontLeftOpenEdge_impl(); } /** - Same as nodesFrontRightEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesFrontRightEdge() but without corners. + * @return List of node numbers. + */ auto nodesFrontRightOpenEdge() const { return derived_cast().nodesFrontRightOpenEdge_impl(); } /** - Same as nodesBackBottomEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesBackBottomEdge() but without corners. + * @return List of node numbers. + */ auto nodesBackBottomOpenEdge() const { return derived_cast().nodesBackBottomOpenEdge_impl(); } /** - Same as nodesBackTopEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesBackTopEdge() but without corners. + * @return List of node numbers. + */ auto nodesBackTopOpenEdge() const { return derived_cast().nodesBackTopOpenEdge_impl(); } /** - Same as nodesBackLeftEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesBackLeftEdge() but without corners. + * @return List of node numbers. + */ auto nodesBackLeftOpenEdge() const { return derived_cast().nodesBackLeftOpenEdge_impl(); } /** - Same as nodesBackRightEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesBackRightEdge() but without corners. + * @return List of node numbers. + */ auto nodesBackRightOpenEdge() const { return derived_cast().nodesBackRightOpenEdge_impl(); } /** - Same as nodesBottomLeftEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesBottomLeftEdge() but without corners. + * @return List of node numbers. + */ auto nodesBottomLeftOpenEdge() const { return derived_cast().nodesBottomLeftOpenEdge_impl(); } /** - Same as nodesBottomRightEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesBottomRightEdge() but without corners. + * @return List of node numbers. + */ auto nodesBottomRightOpenEdge() const { return derived_cast().nodesBottomRightOpenEdge_impl(); } /** - Same as nodesTopLeftEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesTopLeftEdge() but without corners. + * @return List of node numbers. + */ auto nodesTopLeftOpenEdge() const { return derived_cast().nodesTopLeftOpenEdge_impl(); } /** - Same as nodesTopRightEdge() but without corners. - \return List of node numbers. - */ + * Same as nodesTopRightEdge() but without corners. + * @return List of node numbers. + */ auto nodesTopRightOpenEdge() const { return derived_cast().nodesTopRightOpenEdge_impl(); } /** - Alias of nodesFrontBottomOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesFrontBottomOpenEdge(). + * @return List of node numbers. + */ auto nodesBottomFrontOpenEdge() const { return derived_cast().nodesFrontBottomOpenEdge_impl(); } /** - Alias of nodesBackBottomOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesBackBottomOpenEdge(). + * @return List of node numbers. + */ auto nodesBottomBackOpenEdge() const { return derived_cast().nodesBackBottomOpenEdge_impl(); } /** - Alias of nodesFrontTopOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesFrontTopOpenEdge(). + * @return List of node numbers. + */ auto nodesTopFrontOpenEdge() const { return derived_cast().nodesFrontTopOpenEdge_impl(); } /** - Alias of nodesBackTopOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesBackTopOpenEdge(). + * @return List of node numbers. + */ auto nodesTopBackOpenEdge() const { return derived_cast().nodesBackTopOpenEdge_impl(); } /** - Alias of nodesBottomLeftOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesBottomLeftOpenEdge(). + * @return List of node numbers. + */ auto nodesLeftBottomOpenEdge() const { return derived_cast().nodesBottomLeftOpenEdge_impl(); } /** - Alias of nodesFrontLeftOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesFrontLeftOpenEdge(). + * @return List of node numbers. + */ auto nodesLeftFrontOpenEdge() const { return derived_cast().nodesFrontLeftOpenEdge_impl(); } /** - Alias of nodesBackLeftOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesBackLeftOpenEdge(). + * @return List of node numbers. + */ auto nodesLeftBackOpenEdge() const { return derived_cast().nodesBackLeftOpenEdge_impl(); } /** - Alias of nodesTopLeftOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesTopLeftOpenEdge(). + * @return List of node numbers. + */ auto nodesLeftTopOpenEdge() const { return derived_cast().nodesTopLeftOpenEdge_impl(); } /** - Alias of nodesBottomRightOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesBottomRightOpenEdge(). + * @return List of node numbers. + */ auto nodesRightBottomOpenEdge() const { return derived_cast().nodesBottomRightOpenEdge_impl(); } /** - Alias of nodesTopRightOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesTopRightOpenEdge(). + * @return List of node numbers. + */ auto nodesRightTopOpenEdge() const { return derived_cast().nodesTopRightOpenEdge_impl(); } /** - Alias of nodesFrontRightOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesFrontRightOpenEdge(). + * @return List of node numbers. + */ auto nodesRightFrontOpenEdge() const { return derived_cast().nodesFrontRightOpenEdge_impl(); } /** - Alias of nodesBackRightOpenEdge(). - \return List of node numbers. - */ + * Alias of nodesBackRightOpenEdge(). + * @return List of node numbers. + */ auto nodesRightBackOpenEdge() const { return derived_cast().nodesBackRightOpenEdge_impl(); } /** - Front-Bottom-Left corner node. - \return Node number. - */ + * Front-Bottom-Left corner node. + * @return Node number. + */ auto nodesFrontBottomLeftCorner() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } /** - Front-Bottom-Right corner node. - \return Node number. - */ + * Front-Bottom-Right corner node. + * @return Node number. + */ auto nodesFrontBottomRightCorner() const { return derived_cast().nodesFrontBottomRightCorner_impl(); } /** - Front-Top-Left corner node. - \return Node number. - */ + * Front-Top-Left corner node. + * @return Node number. + */ auto nodesFrontTopLeftCorner() const { return derived_cast().nodesFrontTopLeftCorner_impl(); } /** - Front-Top-Right corner node. - \return Node number. - */ + * Front-Top-Right corner node. + * @return Node number. + */ auto nodesFrontTopRightCorner() const { return derived_cast().nodesFrontTopRightCorner_impl(); } /** - Back-Bottom-Left corner node. - \return Node number. - */ + * Back-Bottom-Left corner node. + * @return Node number. + */ auto nodesBackBottomLeftCorner() const { return derived_cast().nodesBackBottomLeftCorner_impl(); } /** - Back-Bottom-Right corner node. - \return Node number. - */ + * Back-Bottom-Right corner node. + * @return Node number. + */ auto nodesBackBottomRightCorner() const { return derived_cast().nodesBackBottomRightCorner_impl(); } /** - Back-Top-Left corner node. - \return Node number. - */ + * Back-Top-Left corner node. + * @return Node number. + */ auto nodesBackTopLeftCorner() const { return derived_cast().nodesBackTopLeftCorner_impl(); } /** - Back-Top-Right corner node. - \return Node number. - */ + * Back-Top-Right corner node. + * @return Node number. + */ auto nodesBackTopRightCorner() const { return derived_cast().nodesBackTopRightCorner_impl(); } /** - Alias of nodesFrontBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomLeftCorner(). + * @return Node number. + */ auto nodesFrontLeftBottomCorner() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } /** - Alias of nodesFrontBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomLeftCorner(). + * @return Node number. + */ auto nodesBottomFrontLeftCorner() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } /** - Alias of nodesFrontBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomLeftCorner(). + * @return Node number. + */ auto nodesBottomLeftFrontCorner() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } /** - Alias of nodesFrontBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomLeftCorner(). + * @return Node number. + */ auto nodesLeftFrontBottomCorner() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } /** - Alias of nodesFrontBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomLeftCorner(). + * @return Node number. + */ auto nodesLeftBottomFrontCorner() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } /** - Alias of nodesFrontBottomRightCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomRightCorner(). + * @return Node number. + */ auto nodesFrontRightBottomCorner() const { return derived_cast().nodesFrontBottomRightCorner_impl(); } /** - Alias of nodesFrontBottomRightCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomRightCorner(). + * @return Node number. + */ auto nodesBottomFrontRightCorner() const { return derived_cast().nodesFrontBottomRightCorner_impl(); } /** - Alias of nodesFrontBottomRightCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomRightCorner(). + * @return Node number. + */ auto nodesBottomRightFrontCorner() const { return derived_cast().nodesFrontBottomRightCorner_impl(); } /** - Alias of nodesFrontBottomRightCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomRightCorner(). + * @return Node number. + */ auto nodesRightFrontBottomCorner() const { return derived_cast().nodesFrontBottomRightCorner_impl(); } /** - Alias of nodesFrontBottomRightCorner(). - \return Node number. - */ + * Alias of nodesFrontBottomRightCorner(). + * @return Node number. + */ auto nodesRightBottomFrontCorner() const { return derived_cast().nodesFrontBottomRightCorner_impl(); } /** - Alias of nodesFrontTopLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontTopLeftCorner(). + * @return Node number. + */ auto nodesFrontLeftTopCorner() const { return derived_cast().nodesFrontTopLeftCorner_impl(); } /** - Alias of nodesFrontTopLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontTopLeftCorner(). + * @return Node number. + */ auto nodesTopFrontLeftCorner() const { return derived_cast().nodesFrontTopLeftCorner_impl(); } /** - Alias of nodesFrontTopLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontTopLeftCorner(). + * @return Node number. + */ auto nodesTopLeftFrontCorner() const { return derived_cast().nodesFrontTopLeftCorner_impl(); } /** - Alias of nodesFrontTopLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontTopLeftCorner(). + * @return Node number. + */ auto nodesLeftFrontTopCorner() const { return derived_cast().nodesFrontTopLeftCorner_impl(); } /** - Alias of nodesFrontTopLeftCorner(). - \return Node number. - */ + * Alias of nodesFrontTopLeftCorner(). + * @return Node number. + */ auto nodesLeftTopFrontCorner() const { return derived_cast().nodesFrontTopLeftCorner_impl(); } /** - Alias of nodesFrontTopRightCorner(). - \return Node number. - */ + * Alias of nodesFrontTopRightCorner(). + * @return Node number. + */ auto nodesFrontRightTopCorner() const { return derived_cast().nodesFrontTopRightCorner_impl(); } /** - Alias of nodesFrontTopRightCorner(). - \return Node number. - */ + * Alias of nodesFrontTopRightCorner(). + * @return Node number. + */ auto nodesTopFrontRightCorner() const { return derived_cast().nodesFrontTopRightCorner_impl(); } /** - Alias of nodesFrontTopRightCorner(). - \return Node number. - */ + * Alias of nodesFrontTopRightCorner(). + * @return Node number. + */ auto nodesTopRightFrontCorner() const { return derived_cast().nodesFrontTopRightCorner_impl(); } /** - Alias of nodesFrontTopRightCorner(). - \return Node number. - */ + * Alias of nodesFrontTopRightCorner(). + * @return Node number. + */ auto nodesRightFrontTopCorner() const { return derived_cast().nodesFrontTopRightCorner_impl(); } /** - Alias of nodesFrontTopRightCorner(). - \return Node number. - */ + * Alias of nodesFrontTopRightCorner(). + * @return Node number. + */ auto nodesRightTopFrontCorner() const { return derived_cast().nodesFrontTopRightCorner_impl(); } /** - Alias of nodesBackBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesBackBottomLeftCorner(). + * @return Node number. + */ auto nodesBackLeftBottomCorner() const { return derived_cast().nodesBackBottomLeftCorner_impl(); } /** - Alias of nodesBackBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesBackBottomLeftCorner(). + * @return Node number. + */ auto nodesBottomBackLeftCorner() const { return derived_cast().nodesBackBottomLeftCorner_impl(); } /** - Alias of nodesBackBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesBackBottomLeftCorner(). + * @return Node number. + */ auto nodesBottomLeftBackCorner() const { return derived_cast().nodesBackBottomLeftCorner_impl(); } /** - Alias of nodesBackBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesBackBottomLeftCorner(). + * @return Node number. + */ auto nodesLeftBackBottomCorner() const { return derived_cast().nodesBackBottomLeftCorner_impl(); } /** - Alias of nodesBackBottomLeftCorner(). - \return Node number. - */ + * Alias of nodesBackBottomLeftCorner(). + * @return Node number. + */ auto nodesLeftBottomBackCorner() const { return derived_cast().nodesBackBottomLeftCorner_impl(); } /** - Alias of nodesBackBottomRightCorner(). - \return Node number. - */ + * Alias of nodesBackBottomRightCorner(). + * @return Node number. + */ auto nodesBackRightBottomCorner() const { return derived_cast().nodesBackBottomRightCorner_impl(); } /** - Alias of nodesBackBottomRightCorner(). - \return Node number. - */ + * Alias of nodesBackBottomRightCorner(). + * @return Node number. + */ auto nodesBottomBackRightCorner() const { return derived_cast().nodesBackBottomRightCorner_impl(); } /** - Alias of nodesBackBottomRightCorner(). - \return Node number. - */ + * Alias of nodesBackBottomRightCorner(). + * @return Node number. + */ auto nodesBottomRightBackCorner() const { return derived_cast().nodesBackBottomRightCorner_impl(); } /** - Alias of nodesBackBottomRightCorner(). - \return Node number. - */ + * Alias of nodesBackBottomRightCorner(). + * @return Node number. + */ auto nodesRightBackBottomCorner() const { return derived_cast().nodesBackBottomRightCorner_impl(); } /** - Alias of nodesBackBottomRightCorner(). - \return Node number. - */ + * Alias of nodesBackBottomRightCorner(). + * @return Node number. + */ auto nodesRightBottomBackCorner() const { return derived_cast().nodesBackBottomRightCorner_impl(); } /** - Alias of nodesBackTopLeftCorner(). - \return Node number. - */ + * Alias of nodesBackTopLeftCorner(). + * @return Node number. + */ auto nodesBackLeftTopCorner() const { return derived_cast().nodesBackTopLeftCorner_impl(); } /** - Alias of nodesBackTopLeftCorner(). - \return Node number. - */ + * Alias of nodesBackTopLeftCorner(). + * @return Node number. + */ auto nodesTopBackLeftCorner() const { return derived_cast().nodesBackTopLeftCorner_impl(); } /** - Alias of nodesBackTopLeftCorner(). - \return Node number. - */ + * Alias of nodesBackTopLeftCorner(). + * @return Node number. + */ auto nodesTopLeftBackCorner() const { return derived_cast().nodesBackTopLeftCorner_impl(); } /** - Alias of nodesBackTopLeftCorner(). - \return Node number. - */ + * Alias of nodesBackTopLeftCorner(). + * @return Node number. + */ auto nodesLeftBackTopCorner() const { return derived_cast().nodesBackTopLeftCorner_impl(); } /** - Alias of nodesBackTopLeftCorner(). - \return Node number. - */ + * Alias of nodesBackTopLeftCorner(). + * @return Node number. + */ auto nodesLeftTopBackCorner() const { return derived_cast().nodesBackTopLeftCorner_impl(); } /** - Alias of nodesBackTopRightCorner(). - \return Node number. - */ + * Alias of nodesBackTopRightCorner(). + * @return Node number. + */ auto nodesBackRightTopCorner() const { return derived_cast().nodesBackTopRightCorner_impl(); } /** - Alias of nodesBackTopRightCorner(). - \return Node number. - */ + * Alias of nodesBackTopRightCorner(). + * @return Node number. + */ auto nodesTopBackRightCorner() const { return derived_cast().nodesBackTopRightCorner_impl(); } /** - Alias of nodesBackTopRightCorner(). - \return Node number. - */ + * Alias of nodesBackTopRightCorner(). + * @return Node number. + */ auto nodesTopRightBackCorner() const { return derived_cast().nodesBackTopRightCorner_impl(); } /** - Alias of nodesBackTopRightCorner(). - \return Node number. - */ + * Alias of nodesBackTopRightCorner(). + * @return Node number. + */ auto nodesRightBackTopCorner() const { return derived_cast().nodesBackTopRightCorner_impl(); } /** - Alias of nodesBackTopRightCorner(). - \return Node number. - */ + * Alias of nodesBackTopRightCorner(). + * @return Node number. + */ auto nodesRightTopBackCorner() const { return derived_cast().nodesBackTopRightCorner_impl(); } private: auto derived_cast() -> derived_type& { return *static_cast(this); } auto derived_cast() const -> const derived_type& { return *static_cast(this); } friend class RegularBase; array_type::tensor nodesPeriodic_impl() const { array_type::tensor fro = derived_cast().nodesFrontFace_impl(); array_type::tensor bck = derived_cast().nodesBackFace_impl(); array_type::tensor lft = derived_cast().nodesLeftFace_impl(); array_type::tensor rgt = derived_cast().nodesRightFace_impl(); array_type::tensor bot = derived_cast().nodesBottomFace_impl(); array_type::tensor top = derived_cast().nodesTopFace_impl(); array_type::tensor froBot = derived_cast().nodesFrontBottomOpenEdge_impl(); array_type::tensor froTop = derived_cast().nodesFrontTopOpenEdge_impl(); array_type::tensor froLft = derived_cast().nodesFrontLeftOpenEdge_impl(); array_type::tensor froRgt = derived_cast().nodesFrontRightOpenEdge_impl(); array_type::tensor bckBot = derived_cast().nodesBackBottomOpenEdge_impl(); array_type::tensor bckTop = derived_cast().nodesBackTopOpenEdge_impl(); array_type::tensor bckLft = derived_cast().nodesBackLeftOpenEdge_impl(); array_type::tensor bckRgt = derived_cast().nodesBackRightOpenEdge_impl(); array_type::tensor botLft = derived_cast().nodesBottomLeftOpenEdge_impl(); array_type::tensor botRgt = derived_cast().nodesBottomRightOpenEdge_impl(); array_type::tensor topLft = derived_cast().nodesTopLeftOpenEdge_impl(); array_type::tensor topRgt = derived_cast().nodesTopRightOpenEdge_impl(); size_t tface = fro.size() + lft.size() + bot.size(); size_t tedge = 3 * froBot.size() + 3 * froLft.size() + 3 * botLft.size(); size_t tnode = 7; array_type::tensor ret = xt::empty({tface + tedge + tnode, std::size_t(2)}); size_t i = 0; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesFrontBottomRightCorner_impl(); ++i; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesBackBottomRightCorner_impl(); ++i; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesBackBottomLeftCorner_impl(); ++i; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesFrontTopLeftCorner_impl(); ++i; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesFrontTopRightCorner_impl(); ++i; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesBackTopRightCorner_impl(); ++i; ret(i, 0) = derived_cast().nodesFrontBottomLeftCorner_impl(); ret(i, 1) = derived_cast().nodesBackTopLeftCorner_impl(); ++i; for (size_t j = 0; j < froBot.size(); ++j) { ret(i, 0) = froBot(j); ret(i, 1) = bckBot(j); ++i; } for (size_t j = 0; j < froBot.size(); ++j) { ret(i, 0) = froBot(j); ret(i, 1) = bckTop(j); ++i; } for (size_t j = 0; j < froBot.size(); ++j) { ret(i, 0) = froBot(j); ret(i, 1) = froTop(j); ++i; } for (size_t j = 0; j < botLft.size(); ++j) { ret(i, 0) = botLft(j); ret(i, 1) = botRgt(j); ++i; } for (size_t j = 0; j < botLft.size(); ++j) { ret(i, 0) = botLft(j); ret(i, 1) = topRgt(j); ++i; } for (size_t j = 0; j < botLft.size(); ++j) { ret(i, 0) = botLft(j); ret(i, 1) = topLft(j); ++i; } for (size_t j = 0; j < froLft.size(); ++j) { ret(i, 0) = froLft(j); ret(i, 1) = froRgt(j); ++i; } for (size_t j = 0; j < froLft.size(); ++j) { ret(i, 0) = froLft(j); ret(i, 1) = bckRgt(j); ++i; } for (size_t j = 0; j < froLft.size(); ++j) { ret(i, 0) = froLft(j); ret(i, 1) = bckLft(j); ++i; } for (size_t j = 0; j < fro.size(); ++j) { ret(i, 0) = fro(j); ret(i, 1) = bck(j); ++i; } for (size_t j = 0; j < lft.size(); ++j) { ret(i, 0) = lft(j); ret(i, 1) = rgt(j); ++i; } for (size_t j = 0; j < bot.size(); ++j) { ret(i, 0) = bot(j); ret(i, 1) = top(j); ++i; } return ret; } auto nodesOrigin_impl() const { return derived_cast().nodesFrontBottomLeftCorner_impl(); } }; /** -Find overlapping nodes. The output has the following structure: - - [[nodes_from_mesh_a], - [nodes_from_mesh_b]] - -\param coor_a Nodal coordinates of mesh "a" [nnode, ndim]. -\param coor_b Nodal coordinates of mesh "b" [nnode, ndim]. -\param rtol Relative tolerance for position match. -\param atol Absolute tolerance for position match. -\return Overlapping nodes. -*/ + * Find overlapping nodes. The output has the following structure: + * + * [[nodes_from_mesh_a], + * [nodes_from_mesh_b]] + * + * @param coor_a Nodal coordinates of mesh "a" [nnode, ndim]. + * @param coor_b Nodal coordinates of mesh "b" [nnode, ndim]. + * @param rtol Relative tolerance for position match. + * @param atol Absolute tolerance for position match. + * @return Overlapping nodes. + */ template inline array_type::tensor overlapping(const S& coor_a, const T& coor_b, double rtol = 1e-5, double atol = 1e-8) { GOOSEFEM_ASSERT(coor_a.dimension() == 2); GOOSEFEM_ASSERT(coor_b.dimension() == 2); GOOSEFEM_ASSERT(coor_a.shape(1) == coor_b.shape(1)); std::vector ret_a; std::vector ret_b; for (size_t i = 0; i < coor_a.shape(0); ++i) { auto idx = xt::flatten_indices(xt::argwhere( xt::prod(xt::isclose(coor_b, xt::view(coor_a, i, xt::all()), rtol, atol), 1))); for (auto& j : idx) { ret_a.push_back(i); ret_b.push_back(j); } } array_type::tensor ret = xt::empty({size_t(2), ret_a.size()}); for (size_t i = 0; i < ret_a.size(); ++i) { ret(0, i) = ret_a[i]; ret(1, i) = ret_b[i]; } return ret; } /** -Stitch two mesh objects, specifying overlapping nodes by hand. -*/ + * Stitch two mesh objects, specifying overlapping nodes by hand. + */ class ManualStitch { public: ManualStitch() = default; /** - \param coor_a Nodal coordinates of mesh "a" [nnode, ndim]. - \param conn_a Connectivity of mesh "a" [nelem, nne]. - \param overlapping_nodes_a Node-numbers of mesh "a" that overlap with mesh "b" [n]. - \param coor_b Nodal coordinates of mesh "b" [nnode, ndim]. - \param conn_b Connectivity of mesh "b" [nelem, nne]. - \param overlapping_nodes_b Node-numbers of mesh "b" that overlap with mesh "a" [n]. - \param check_position If ``true`` the nodes are checked for position overlap. - \param rtol Relative tolerance for check on position overlap. - \param atol Absolute tolerance for check on position overlap. - */ + * @param coor_a Nodal coordinates of mesh "a" [nnode, ndim]. + * @param conn_a Connectivity of mesh "a" [nelem, nne]. + * @param overlapping_nodes_a Node-numbers of mesh "a" that overlap with mesh "b" [n]. + * @param coor_b Nodal coordinates of mesh "b" [nnode, ndim]. + * @param conn_b Connectivity of mesh "b" [nelem, nne]. + * @param overlapping_nodes_b Node-numbers of mesh "b" that overlap with mesh "a" [n]. + * @param check_position If ``true`` the nodes are checked for position overlap. + * @param rtol Relative tolerance for check on position overlap. + * @param atol Absolute tolerance for check on position overlap. + */ template ManualStitch( const CA& coor_a, const EA& conn_a, const NA& overlapping_nodes_a, const CB& coor_b, const EB& conn_b, const NB& overlapping_nodes_b, bool check_position = true, double rtol = 1e-5, double atol = 1e-8) { UNUSED(rtol); UNUSED(atol); GOOSEFEM_ASSERT(coor_a.dimension() == 2); GOOSEFEM_ASSERT(conn_a.dimension() == 2); GOOSEFEM_ASSERT(overlapping_nodes_a.dimension() == 1); GOOSEFEM_ASSERT(coor_b.dimension() == 2); GOOSEFEM_ASSERT(conn_b.dimension() == 2); GOOSEFEM_ASSERT(overlapping_nodes_b.dimension() == 1); GOOSEFEM_ASSERT(xt::has_shape(overlapping_nodes_a, overlapping_nodes_b.shape())); GOOSEFEM_ASSERT(coor_a.shape(1) == coor_b.shape(1)); GOOSEFEM_ASSERT(conn_a.shape(1) == conn_b.shape(1)); if (check_position) { GOOSEFEM_CHECK(xt::allclose( xt::view(coor_a, xt::keep(overlapping_nodes_a), xt::all()), xt::view(coor_b, xt::keep(overlapping_nodes_b), xt::all()), rtol, atol)); } size_t nnda = coor_a.shape(0); size_t nndb = coor_b.shape(0); size_t ndim = coor_a.shape(1); size_t nelim = overlapping_nodes_a.size(); size_t nela = conn_a.shape(0); size_t nelb = conn_b.shape(0); size_t nne = conn_a.shape(1); m_nel_a = nela; m_nel_b = nelb; m_nnd_a = nnda; array_type::tensor keep_b = xt::setdiff1d(xt::arange(nndb), overlapping_nodes_b); m_map_b = xt::empty({nndb}); xt::view(m_map_b, xt::keep(overlapping_nodes_b)) = overlapping_nodes_a; xt::view(m_map_b, xt::keep(keep_b)) = xt::arange(keep_b.size()) + nnda; m_conn = xt::empty({nela + nelb, nne}); xt::view(m_conn, xt::range(0, nela), xt::all()) = conn_a; xt::view(m_conn, xt::range(nela, nela + nelb), xt::all()) = detail::renum(conn_b, m_map_b); m_coor = xt::empty({nnda + nndb - nelim, ndim}); xt::view(m_coor, xt::range(0, nnda), xt::all()) = coor_a; xt::view(m_coor, xt::range(nnda, nnda + nndb - nelim), xt::all()) = xt::view(coor_b, xt::keep(keep_b), xt::all()); } /** - Number of sub meshes == 2. - \return unsigned int - */ + * Number of sub meshes == 2. + * @return unsigned int + */ size_t nmesh() const { return 2; } /** - Number of elements. - \return unsigned int - */ + * Number of elements. + * @return unsigned int + */ size_t nelem() const { return m_conn.shape(0); } /** - Number of nodes. - \return unsigned int - */ + * Number of nodes. + * @return unsigned int + */ size_t nnode() const { return m_coor.shape(0); } /** - Number of nodes-per-element. - \return unsigned int - */ + * Number of nodes-per-element. + * @return unsigned int + */ size_t nne() const { return m_conn.shape(1); } /** - Number of dimensions. - \return unsigned int - */ + * Number of dimensions. + * @return unsigned int + */ size_t ndim() const { return m_coor.shape(1); } /** - Nodal coordinates [#nnode, #ndim]. - \return coordinates per node - */ + * Nodal coordinates [#nnode, #ndim]. + * @return coordinates per node + */ const array_type::tensor& coor() const { return m_coor; } /** - Connectivity [#nelem, #nne]. - \return nodes per element - */ + * Connectivity [#nelem, #nne]. + * @return nodes per element + */ const array_type::tensor& conn() const { return m_conn; } /** - DOF numbers for each node (numbered sequentially) [#nnode, #ndim]. - \return DOFs per node - */ + * DOF numbers for each node (numbered sequentially) [#nnode, #ndim]. + * @return DOFs per node + */ array_type::tensor dofs() const { size_t nnode = this->nnode(); size_t ndim = this->ndim(); return xt::reshape_view(xt::arange(nnode * ndim), {nnode, ndim}); } /** - Node-map per sub-mesh. - \return nodes per mesh - */ + * Node-map per sub-mesh. + * @return nodes per mesh + */ std::vector> nodemap() const { std::vector> ret(this->nmesh()); for (size_t i = 0; i < this->nmesh(); ++i) { ret[i] = this->nodemap(i); } return ret; } /** - Element-map per sub-mesh. - \return elements per mesh - */ + * Element-map per sub-mesh. + * @return elements per mesh + */ std::vector> elemmap() const { std::vector> ret(this->nmesh()); for (size_t i = 0; i < this->nmesh(); ++i) { ret[i] = this->elemmap(i); } return ret; } /** - \param mesh_index Index of the mesh ("a" = 1, "b" = 1). - \return Node-map for a given mesh. - */ + * @param mesh_index Index of the mesh ("a" = 1, "b" = 1). + * @return Node-map for a given mesh. + */ array_type::tensor nodemap(size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index <= 1); if (mesh_index == 0) { return xt::arange(m_nnd_a); } return m_map_b; } /** - \param mesh_index Index of the mesh ("a" = 1, "b" = 1). - \return Element-map for a given mesh. - */ + * @param mesh_index Index of the mesh ("a" = 1, "b" = 1). + * @return Element-map for a given mesh. + */ array_type::tensor elemmap(size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index <= 1); if (mesh_index == 0) { return xt::arange(m_nel_a); } return xt::arange(m_nel_b) + m_nel_a; } /** - Convert set of node numbers for an original mesh to the stitched mesh. - - \param set List of node numbers. - \param mesh_index Index of the mesh ("a" = 1, "b" = 1). - \return List of node numbers for the stitched mesh. - */ + * Convert set of node numbers for an original mesh to the stitched mesh. + * + * @param set List of node numbers. + * @param mesh_index Index of the mesh ("a" = 1, "b" = 1). + * @return List of node numbers for the stitched mesh. + */ template T nodeset(const T& set, size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index <= 1); if (mesh_index == 0) { GOOSEFEM_ASSERT(xt::amax(set)() < m_nnd_a); return set; } GOOSEFEM_ASSERT(xt::amax(set)() < m_map_b.size()); return detail::renum(set, m_map_b); } /** - Convert set of element numbers for an original mesh to the stitched mesh. - - \param set List of element numbers. - \param mesh_index Index of the mesh ("a" = 1, "b" = 1). - \return List of element numbers for the stitched mesh. - */ + * Convert set of element numbers for an original mesh to the stitched mesh. + * + * @param set List of element numbers. + * @param mesh_index Index of the mesh ("a" = 1, "b" = 1). + * @return List of element numbers for the stitched mesh. + */ template T elemset(const T& set, size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index <= 1); if (mesh_index == 0) { GOOSEFEM_ASSERT(xt::amax(set)() < m_nel_a); return set; } GOOSEFEM_ASSERT(xt::amax(set)() < m_nel_b); return set + m_nel_a; } private: array_type::tensor m_coor; array_type::tensor m_conn; array_type::tensor m_map_b; size_t m_nnd_a; size_t m_nel_a; size_t m_nel_b; }; /** -Stitch mesh objects, automatically searching for overlapping nodes. -*/ + * Stitch mesh objects, automatically searching for overlapping nodes. + */ class Stitch { public: /** - \param rtol Relative tolerance for position match. - \param atol Absolute tolerance for position match. - */ + * @param rtol Relative tolerance for position match. + * @param atol Absolute tolerance for position match. + */ Stitch(double rtol = 1e-5, double atol = 1e-8) { m_rtol = rtol; m_atol = atol; } /** - Add mesh to be stitched. - - \param coor Nodal coordinates [nnode, ndim]. - \param conn Connectivity [nelem, nne]. - */ + * Add mesh to be stitched. + * + * @param coor Nodal coordinates [nnode, ndim]. + * @param conn Connectivity [nelem, nne]. + */ template void push_back(const C& coor, const E& conn) { GOOSEFEM_ASSERT(coor.dimension() == 2); GOOSEFEM_ASSERT(conn.dimension() == 2); if (m_map.size() == 0) { m_coor = coor; m_conn = conn; m_map.push_back(xt::eval(xt::arange(coor.shape(0)))); m_nel.push_back(conn.shape(0)); m_el_offset.push_back(0); return; } auto overlap = overlapping(m_coor, coor, m_rtol, m_atol); size_t index = m_map.size(); ManualStitch stitch( m_coor, m_conn, xt::eval(xt::view(overlap, 0, xt::all())), coor, conn, xt::eval(xt::view(overlap, 1, xt::all())), false); m_coor = stitch.coor(); m_conn = stitch.conn(); m_map.push_back(stitch.nodemap(1)); m_nel.push_back(conn.shape(0)); m_el_offset.push_back(m_el_offset[index - 1] + m_nel[index - 1]); } /** - Number of sub meshes. - \return unsigned int - */ + * Number of sub meshes. + * @return unsigned int + */ size_t nmesh() const { return m_map.size(); } /** - Number of elements. - \return unsigned int - */ + * Number of elements. + * @return unsigned int + */ size_t nelem() const { return m_conn.shape(0); } /** - Number of nodes. - \return unsigned int - */ + * Number of nodes. + * @return unsigned int + */ size_t nnode() const { return m_coor.shape(0); } /** - Number of nodes-per-element. - \return unsigned int - */ + * Number of nodes-per-element. + * @return unsigned int + */ size_t nne() const { return m_conn.shape(1); } /** - Number of dimensions. - \return unsigned int - */ + * Number of dimensions. + * @return unsigned int + */ size_t ndim() const { return m_coor.shape(1); } /** - Nodal coordinates [#nnode, #ndim]. - \return coordinates per node - */ + * Nodal coordinates [#nnode, #ndim]. + * @return coordinates per node + */ const array_type::tensor& coor() const { return m_coor; } /** - Connectivity [#nelem, #nne]. - \return nodes per element - */ + * Connectivity [#nelem, #nne]. + * @return nodes per element + */ const array_type::tensor& conn() const { return m_conn; } /** - DOF numbers for each node (numbered sequentially) [#nnode, #ndim]. - \return DOFs per node - */ + * DOF numbers for each node (numbered sequentially) [#nnode, #ndim]. + * @return DOFs per node + */ array_type::tensor dofs() const { size_t nnode = this->nnode(); size_t ndim = this->ndim(); return xt::reshape_view(xt::arange(nnode * ndim), {nnode, ndim}); } /** - Node-map per sub-mesh. - \return nodes per mesh - */ + * Node-map per sub-mesh. + * @return nodes per mesh + */ std::vector> nodemap() const { std::vector> ret(this->nmesh()); for (size_t i = 0; i < this->nmesh(); ++i) { ret[i] = this->nodemap(i); } return ret; } /** - Element-map per sub-mesh. - \return elements per mesh - */ + * Element-map per sub-mesh. + * @return elements per mesh + */ std::vector> elemmap() const { std::vector> ret(this->nmesh()); for (size_t i = 0; i < this->nmesh(); ++i) { ret[i] = this->elemmap(i); } return ret; } /** - The node numbers in the stitched mesh that are coming from a specific sub-mesh. - - \param mesh_index Index of the sub-mesh. - \return List of node numbers. - */ + * The node numbers in the stitched mesh that are coming from a specific sub-mesh. + * + * @param mesh_index Index of the sub-mesh. + * @return List of node numbers. + */ array_type::tensor nodemap(size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index < m_map.size()); return m_map[mesh_index]; } /** - The element numbers in the stitched mesh that are coming from a specific sub-mesh. - - \param mesh_index Index of the sub-mesh. - \return List of element numbers. - */ + * The element numbers in the stitched mesh that are coming from a specific sub-mesh. + * + * @param mesh_index Index of the sub-mesh. + * @return List of element numbers. + */ array_type::tensor elemmap(size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index < m_map.size()); return xt::arange(m_nel[mesh_index]) + m_el_offset[mesh_index]; } /** - Convert set of node-numbers for a sub-mesh to the stitched mesh. - - \param set List of node numbers. - \param mesh_index Index of the sub-mesh. - \return List of node numbers for the stitched mesh. - */ + * Convert set of node-numbers for a sub-mesh to the stitched mesh. + * + * @param set List of node numbers. + * @param mesh_index Index of the sub-mesh. + * @return List of node numbers for the stitched mesh. + */ template T nodeset(const T& set, size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index < m_map.size()); GOOSEFEM_ASSERT(xt::amax(set)() < m_map[mesh_index].size()); return detail::renum(set, m_map[mesh_index]); } /** - Convert set of element-numbers for a sub-mesh to the stitched mesh. - - \param set List of element numbers. - \param mesh_index Index of the sub-mesh. - \return List of element numbers for the stitched mesh. - */ + * Convert set of element-numbers for a sub-mesh to the stitched mesh. + * + * @param set List of element numbers. + * @param mesh_index Index of the sub-mesh. + * @return List of element numbers for the stitched mesh. + */ template T elemset(const T& set, size_t mesh_index) const { GOOSEFEM_ASSERT(mesh_index < m_map.size()); GOOSEFEM_ASSERT(xt::amax(set)() < m_nel[mesh_index]); return set + m_el_offset[mesh_index]; } /** - Combine set of node numbers for an original to the final mesh (removes duplicates). - - \param set List of node numbers per mesh. - \return List of node numbers for the stitched mesh. - */ + * Combine set of node numbers for an original to the final mesh (removes duplicates). + * + * @param set List of node numbers per mesh. + * @return List of node numbers for the stitched mesh. + */ template T nodeset(const std::vector& set) const { GOOSEFEM_ASSERT(set.size() == m_map.size()); size_t n = 0; for (size_t i = 0; i < set.size(); ++i) { n += set[i].size(); } array_type::tensor ret = xt::empty({n}); n = 0; for (size_t i = 0; i < set.size(); ++i) { xt::view(ret, xt::range(n, n + set[i].size())) = this->nodeset(set[i], i); n += set[i].size(); } return xt::unique(ret); } - /** \copydoc nodeset(const std::vector&) const */ + /** + * \copydoc nodeset(const std::vector&) const + */ template T nodeset(std::initializer_list set) const { return this->nodeset(std::vector(set)); } /** - Combine set of element numbers for an original to the final mesh. - - \param set List of element numbers per mesh. - \return List of element numbers for the stitched mesh. - */ + * Combine set of element numbers for an original to the final mesh. + * + * @param set List of element numbers per mesh. + * @return List of element numbers for the stitched mesh. + */ template T elemset(const std::vector& set) const { GOOSEFEM_ASSERT(set.size() == m_map.size()); size_t n = 0; for (size_t i = 0; i < set.size(); ++i) { n += set[i].size(); } array_type::tensor ret = xt::empty({n}); n = 0; for (size_t i = 0; i < set.size(); ++i) { xt::view(ret, xt::range(n, n + set[i].size())) = this->elemset(set[i], i); n += set[i].size(); } return ret; } - /** \copydoc elemset(const std::vector&) const */ + /** + * \copydoc elemset(const std::vector&) const + */ template T elemset(std::initializer_list set) const { return this->elemset(std::vector(set)); } protected: array_type::tensor m_coor; ///< Nodal coordinates [#nnode, #ndim] array_type::tensor m_conn; ///< Connectivity [#nelem, #nne] std::vector> m_map; ///< See nodemap(size_t) std::vector m_nel; ///< Number of elements per sub-mesh. std::vector m_el_offset; ///< First element of every sub-mesh. double m_rtol; ///< Relative tolerance to find overlapping nodes. double m_atol; ///< Absolute tolerance to find overlapping nodes. }; /** -Vertically stack meshes. -*/ + * Vertically stack meshes. + */ class Vstack : public Stitch { public: /** - \param check_overlap Check if nodes are overlapping when adding a mesh. - \param rtol Relative tolerance for position match. - \param atol Absolute tolerance for position match. - */ + * @param check_overlap Check if nodes are overlapping when adding a mesh. + * @param rtol Relative tolerance for position match. + * @param atol Absolute tolerance for position match. + */ Vstack(bool check_overlap = true, double rtol = 1e-5, double atol = 1e-8) { m_check_overlap = check_overlap; m_rtol = rtol; m_atol = atol; } /** - Add a mesh to the top of the current stack. - Each time the current `nodes_bot` are stitched with the then highest `nodes_top`. - - \param coor Nodal coordinates [nnode, ndim]. - \param conn Connectivity [nelem, nne]. - \param nodes_bot Nodes along the bottom edge [n]. - \param nodes_top Nodes along the top edge [n]. - */ + * Add a mesh to the top of the current stack. + * Each time the current `nodes_bot` are stitched with the then highest `nodes_top`. + * + * @param coor Nodal coordinates [nnode, ndim]. + * @param conn Connectivity [nelem, nne]. + * @param nodes_bot Nodes along the bottom edge [n]. + * @param nodes_top Nodes along the top edge [n]. + */ template void push_back(const C& coor, const E& conn, const N& nodes_bot, const N& nodes_top) { if (m_map.size() == 0) { m_coor = coor; m_conn = conn; m_map.push_back(xt::eval(xt::arange(coor.shape(0)))); m_nel.push_back(conn.shape(0)); m_el_offset.push_back(0); m_nodes_bot.push_back(nodes_bot); m_nodes_top.push_back(nodes_top); return; } GOOSEFEM_ASSERT(nodes_bot.size() == m_nodes_top.back().size()); size_t index = m_map.size(); double shift = xt::amax(xt::view(m_coor, xt::all(), 1))(); auto x = coor; xt::view(x, xt::all(), 1) += shift; ManualStitch stitch( m_coor, m_conn, m_nodes_top.back(), x, conn, nodes_bot, m_check_overlap, m_rtol, m_atol); m_nodes_bot.push_back(stitch.nodeset(nodes_bot, 1)); m_nodes_top.push_back(stitch.nodeset(nodes_top, 1)); m_coor = stitch.coor(); m_conn = stitch.conn(); m_map.push_back(stitch.nodemap(1)); m_nel.push_back(conn.shape(0)); m_el_offset.push_back(m_el_offset[index - 1] + m_nel[index - 1]); } private: std::vector> m_nodes_bot; ///< Bottom nodes of each mesh (renumbered). std::vector> m_nodes_top; ///< Top nodes of each mesh (renumbered). bool m_check_overlap; ///< Check if nodes are overlapping when adding a mesh. }; /** -Reorder to lowest possible index, in specific order. - -For example for ``Reorder({iiu, iip})`` after reordering: - - iiu = xt::range(nnu); - iip = xt::range(nnp) + nnu; -*/ + * Reorder to lowest possible index, in specific order. + * + * For example for ``Reorder({iiu, iip})`` after reordering: + * + * iiu = xt::range(nnu); + * iip = xt::range(nnp) + nnu; + */ class Reorder { public: Reorder() = default; /** - \param args List of (DOF-)numbers. - */ + * @param args List of (DOF-)numbers. + */ template Reorder(const std::initializer_list args) { size_t n = 0; size_t i = 0; for (auto& arg : args) { if (arg.size() == 0) { continue; } n = std::max(n, xt::amax(arg)() + 1); } #ifdef GOOSEFEM_ENABLE_ASSERT for (auto& arg : args) { GOOSEFEM_ASSERT(is_unique(arg)); } #endif m_renum = xt::empty({n}); for (auto& arg : args) { for (auto& j : arg) { m_renum(j) = i; ++i; } } } /** - Apply reordering to other set. - - \param list List of (DOF-)numbers. - \return Reordered list of (DOF-)numbers. - */ + * Apply reordering to other set. + * + * @param list List of (DOF-)numbers. + * @return Reordered list of (DOF-)numbers. + */ template T apply(const T& list) const { T ret = T::from_shape(list.shape()); auto jt = ret.begin(); for (auto it = list.begin(); it != list.end(); ++it, ++jt) { *jt = m_renum(*it); } return ret; } /** - Get the list needed to reorder, e.g.: - - dofs_reordered(i, j) = index(dofs(i, j)) - - \return Reorder-index. - */ + * Get the list needed to reorder, e.g.: + * + * dofs_reordered(i, j) = index(dofs(i, j)) + * + * @return Reorder-index. + */ const array_type::tensor& index() const { return m_renum; } private: array_type::tensor m_renum; }; /** -Number of elements connected to each node. - -\param conn Connectivity [nelem, nne]. -\return Coordination per node. -*/ + * Number of elements connected to each node. + * + * @param conn Connectivity [nelem, nne]. + * @return Coordination per node. + */ template inline array_type::tensor coordination(const E& conn) { GOOSEFEM_ASSERT(conn.dimension() == 2); size_t nnode = xt::amax(conn)() + 1; array_type::tensor N = xt::zeros({nnode}); for (auto it = conn.begin(); it != conn.end(); ++it) { N(*it) += 1; } return N; } /** -Elements connected to each node. - -\param conn Connectivity [nelem, nne]. -\param sorted If ``true`` the list of elements for each node is sorted. -\return Elements per node [nnode, ...]. -*/ + * Elements connected to each node. + * + * @param conn Connectivity [nelem, nne]. + * @param sorted If ``true`` the list of elements for each node is sorted. + * @return Elements per node [nnode, ...]. + */ template inline std::vector> elem2node(const E& conn, bool sorted = true) { auto N = coordination(conn); auto nnode = N.size(); std::vector> ret(nnode); for (size_t i = 0; i < nnode; ++i) { ret[i].reserve(N(i)); } for (size_t e = 0; e < conn.shape(0); ++e) { for (size_t m = 0; m < conn.shape(1); ++m) { ret[conn(e, m)].push_back(e); } } if (sorted) { for (auto& row : ret) { std::sort(row.begin(), row.end()); } } return ret; } /** -\copydoc elem2node(const E&, bool) - -\param dofs DOFs per node, allowing accounting for periodicity [nnode, ndim]. -*/ + * @copydoc elem2node(const E&, bool) + * + * @param dofs DOFs per node, allowing accounting for periodicity [nnode, ndim]. + */ template inline std::vector> elem2node(const E& conn, const D& dofs, bool sorted = true) { size_t nnode = dofs.shape(0); auto ret = elem2node(conn, sorted); auto nties = nodaltyings(dofs); for (size_t m = 0; m < nnode; ++m) { if (nties[m].size() <= 1) { continue; } if (m > nties[m][0]) { continue; } size_t n = ret[m].size(); for (size_t j = 1; j < nties[m].size(); ++j) { n += ret[nties[m][j]].size(); } ret[m].reserve(n); for (size_t j = 1; j < nties[m].size(); ++j) { ret[m].insert(ret[m].end(), ret[nties[m][j]].begin(), ret[nties[m][j]].end()); } if (sorted) { std::sort(ret[m].begin(), ret[m].end()); } for (size_t j = 1; j < nties[m].size(); ++j) { ret[nties[m][j]] = ret[m]; } } return ret; } /** -Nodes connected to each DOF. - -\param dofs DOFs per node [nnode, ndim]. -\param sorted If ``true`` the list of nodes for each DOF is sorted. -\return Nodes per DOF [ndof, ...]. -*/ + * Nodes connected to each DOF. + * + * @param dofs DOFs per node [nnode, ndim]. + * @param sorted If ``true`` the list of nodes for each DOF is sorted. + * @return Nodes per DOF [ndof, ...]. + */ template inline std::vector> node2dof(const D& dofs, bool sorted = true) { return elem2node(dofs, sorted); } /** -Return size of each element edge. - -\param coor Nodal coordinates. -\param conn Connectivity. -\param type ElementType. -\return Edge-sizes per element. -*/ + * Return size of each element edge. + * + * @param coor Nodal coordinates. + * @param conn Connectivity. + * @param type ElementType. + * @return Edge-sizes per element. + */ template inline array_type::tensor edgesize(const C& coor, const E& conn, ElementType type) { GOOSEFEM_ASSERT(coor.dimension() == 2); GOOSEFEM_ASSERT(conn.dimension() == 2); GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0)); if (type == ElementType::Quad4) { GOOSEFEM_ASSERT(coor.shape(1) == 2ul); GOOSEFEM_ASSERT(conn.shape(1) == 4ul); array_type::tensor n0 = xt::view(conn, xt::all(), 0); array_type::tensor n1 = xt::view(conn, xt::all(), 1); array_type::tensor n2 = xt::view(conn, xt::all(), 2); array_type::tensor n3 = xt::view(conn, xt::all(), 3); array_type::tensor x0 = xt::view(coor, xt::keep(n0), 0); array_type::tensor x1 = xt::view(coor, xt::keep(n1), 0); array_type::tensor x2 = xt::view(coor, xt::keep(n2), 0); array_type::tensor x3 = xt::view(coor, xt::keep(n3), 0); array_type::tensor y0 = xt::view(coor, xt::keep(n0), 1); array_type::tensor y1 = xt::view(coor, xt::keep(n1), 1); array_type::tensor y2 = xt::view(coor, xt::keep(n2), 1); array_type::tensor y3 = xt::view(coor, xt::keep(n3), 1); array_type::tensor ret = xt::empty(conn.shape()); xt::view(ret, xt::all(), 0) = xt::sqrt(xt::pow(x1 - x0, 2.0) + xt::pow(y1 - y0, 2.0)); xt::view(ret, xt::all(), 1) = xt::sqrt(xt::pow(x2 - x1, 2.0) + xt::pow(y2 - y1, 2.0)); xt::view(ret, xt::all(), 2) = xt::sqrt(xt::pow(x3 - x2, 2.0) + xt::pow(y3 - y2, 2.0)); xt::view(ret, xt::all(), 3) = xt::sqrt(xt::pow(x0 - x3, 2.0) + xt::pow(y0 - y3, 2.0)); return ret; } throw std::runtime_error("Element-type not implemented"); } /** -Return size of each element edge. -The element-type is automatically determined, see defaultElementType(). - -\param coor Nodal coordinates. -\param conn Connectivity. -\return Edge-sizes per element. -*/ + * Return size of each element edge. + * The element-type is automatically determined, see defaultElementType(). + * + * @param coor Nodal coordinates. + * @param conn Connectivity. + * @return Edge-sizes per element. + */ template inline array_type::tensor edgesize(const C& coor, const E& conn) { return edgesize(coor, conn, defaultElementType(coor, conn)); } /** -Coordinates of the center of each element. - -\param coor Nodal coordinates. -\param conn Connectivity. -\param type ElementType. -\return Center of each element. -*/ + * Coordinates of the center of each element. + * + * @param coor Nodal coordinates. + * @param conn Connectivity. + * @param type ElementType. + * @return Center of each element. + */ template inline array_type::tensor centers(const C& coor, const E& conn, ElementType type) { GOOSEFEM_ASSERT(coor.dimension() == 2); GOOSEFEM_ASSERT(conn.dimension() == 2); GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0)); array_type::tensor ret = xt::zeros({conn.shape(0), coor.shape(1)}); if (type == ElementType::Quad4) { GOOSEFEM_ASSERT(coor.shape(1) == 2); GOOSEFEM_ASSERT(conn.shape(1) == 4); for (size_t i = 0; i < 4; ++i) { auto n = xt::view(conn, xt::all(), i); ret += xt::view(coor, xt::keep(n), xt::all()); } ret /= 4.0; return ret; } throw std::runtime_error("Element-type not implemented"); } /** -Coordinates of the center of each element. -The element-type is automatically determined, see defaultElementType(). - -\param coor Nodal coordinates. -\param conn Connectivity. -\return Center of each element. -*/ + * Coordinates of the center of each element. + * The element-type is automatically determined, see defaultElementType(). + * + * @param coor Nodal coordinates. + * @param conn Connectivity. + * @return Center of each element. + */ template inline array_type::tensor centers(const C& coor, const E& conn) { return centers(coor, conn, defaultElementType(coor, conn)); } /** -Convert an element-map to a node-map. - -\param elem_map Element-map such that ``new_elvar = elvar[elem_map]``. -\param coor Nodal coordinates. -\param conn Connectivity. -\param type ElementType. -\return Node-map such that ``new_nodevar = nodevar[node_map]`` -*/ + * Convert an element-map to a node-map. + * + * @param elem_map Element-map such that ``new_elvar = elvar[elem_map]``. + * @param coor Nodal coordinates. + * @param conn Connectivity. + * @param type ElementType. + * @return Node-map such that ``new_nodevar = nodevar[node_map]`` + */ template inline array_type::tensor elemmap2nodemap(const T& elem_map, const C& coor, const E& conn, ElementType type) { GOOSEFEM_ASSERT(elem_map.dimension() == 1); GOOSEFEM_ASSERT(coor.dimension() == 2); GOOSEFEM_ASSERT(conn.dimension() == 2); GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0)); GOOSEFEM_ASSERT(elem_map.size() == conn.shape(0)); size_t N = coor.shape(0); array_type::tensor ret = N * xt::ones({N}); if (type == ElementType::Quad4) { GOOSEFEM_ASSERT(coor.shape(1) == 2); GOOSEFEM_ASSERT(conn.shape(1) == 4); for (size_t i = 0; i < 4; ++i) { array_type::tensor t = N * xt::ones({N}); auto old_nd = xt::view(conn, xt::all(), i); auto new_nd = xt::view(conn, xt::keep(elem_map), i); xt::view(t, xt::keep(old_nd)) = new_nd; ret = xt::where(xt::equal(ret, N), t, ret); } return ret; } throw std::runtime_error("Element-type not implemented"); } /** -Convert an element-map to a node-map. -The element-type is automatically determined, see defaultElementType(). - -\param elem_map Element-map such that ``new_elvar = elvar[elem_map]``. -\param coor Nodal coordinates. -\param conn Connectivity. -\return Node-map such that ``new_nodevar = nodevar[node_map]`` -*/ + * Convert an element-map to a node-map. + * The element-type is automatically determined, see defaultElementType(). + * + * @param elem_map Element-map such that ``new_elvar = elvar[elem_map]``. + * @param coor Nodal coordinates. + * @param conn Connectivity. + * @return Node-map such that ``new_nodevar = nodevar[node_map]`` + */ template inline array_type::tensor elemmap2nodemap(const T& elem_map, const C& coor, const E& conn) { return elemmap2nodemap(elem_map, coor, conn, defaultElementType(coor, conn)); } /** -Compute the mass of each node in the mesh. -If nodes are not part of the connectivity the mass is set to zero, -such that the center of gravity is simply:: - - average(coor, GooseFEM.Mesh.nodal_mass(coor, conn, type), axis=0); - -\tparam C e.g. `array_type::tensor` -\tparam E e.g. `array_type::tensor` -\param coor Nodal coordinates `[nnode, ndim]`. -\param conn Connectivity `[nelem, nne]`. -\param type ElementType. -\return Center of gravity `[ndim]`. -*/ + * Compute the mass of each node in the mesh. + * If nodes are not part of the connectivity the mass is set to zero, + * such that the center of gravity is simply:: + * + * average(coor, GooseFEM.Mesh.nodal_mass(coor, conn, type), axis=0); + * + * @tparam C e.g. `array_type::tensor` + * @tparam E e.g. `array_type::tensor` + * @param coor Nodal coordinates `[nnode, ndim]`. + * @param conn Connectivity `[nelem, nne]`. + * @param type ElementType. + * @return Center of gravity `[ndim]`. + */ template inline array_type::tensor nodal_mass(const C& coor, const E& conn, ElementType type) { auto dof = dofs(coor.shape(0), coor.shape(1)); GooseFEM::MatrixDiagonal M(conn, dof); GooseFEM::Vector vector(conn, dof); array_type::tensor rho = xt::ones(conn.shape()); if (type == ElementType::Quad4) { GooseFEM::Element::Quad4::Quadrature quad( vector.AsElement(coor), GooseFEM::Element::Quad4::Nodal::xi(), GooseFEM::Element::Quad4::Nodal::w()); M.assemble(quad.Int_N_scalar_NT_dV(rho)); } else { throw std::runtime_error("Element-type not implemented"); } return vector.AsNode(M.data()); } /** -Compute the mass of each node in the mesh. -If nodes are not part of the connectivity the mass is set to zero, -such that the center of gravity is simply:: - - average(coor, GooseFEM.Mesh.nodal_mass(coor, conn), axis=0); - -\tparam C e.g. `array_type::tensor` -\tparam E e.g. `array_type::tensor` -\param coor Nodal coordinates `[nnode, ndim]`. -\param conn Connectivity `[nelem, nne]`. -\return Center of gravity `[ndim]`. -*/ + * Compute the mass of each node in the mesh. + * If nodes are not part of the connectivity the mass is set to zero, + * such that the center of gravity is simply:: + * + * average(coor, GooseFEM.Mesh.nodal_mass(coor, conn), axis=0); + * + * @tparam C e.g. `array_type::tensor` + * @tparam E e.g. `array_type::tensor` + * @param coor Nodal coordinates `[nnode, ndim]`. + * @param conn Connectivity `[nelem, nne]`. + * @return Center of gravity `[ndim]`. + */ template inline array_type::tensor nodal_mass(const C& coor, const E& conn) { return nodal_mass(coor, conn, defaultElementType(coor, conn)); } namespace detail { // todo: remove after upstream fix template array_type::tensor average_axis_0(const T& data, const T& weights) { GOOSEFEM_ASSERT(data.dimension() == 2); GOOSEFEM_ASSERT(xt::has_shape(data, weights.shape())); array_type::tensor ret = xt::zeros({weights.shape(1)}); for (size_t j = 0; j < weights.shape(1); ++j) { double norm = 0.0; for (size_t i = 0; i < weights.shape(0); ++i) { ret(j) += data(i, j) * weights(i, j); norm += weights(i, j); } ret(j) /= norm; } return ret; } } // namespace detail /** -Compute the center of gravity of a mesh. - -\tparam C e.g. `array_type::tensor` -\tparam E e.g. `array_type::tensor` -\param coor Nodal coordinates `[nnode, ndim]`. -\param conn Connectivity `[nelem, nne]`. -\param type ElementType. -\return Center of gravity `[ndim]`. -*/ + * Compute the center of gravity of a mesh. + * + * @tparam C e.g. `array_type::tensor` + * @tparam E e.g. `array_type::tensor` + * @param coor Nodal coordinates `[nnode, ndim]`. + * @param conn Connectivity `[nelem, nne]`. + * @param type ElementType. + * @return Center of gravity `[ndim]`. + */ template inline array_type::tensor center_of_gravity(const C& coor, const E& conn, ElementType type) { // todo: remove after upstream fix return detail::average_axis_0(coor, nodal_mass(coor, conn, type)); // return xt::average(coor, nodal_mass(coor, conn, type), 0); } /** -Compute the center of gravity of a mesh. - -\tparam C e.g. `array_type::tensor` -\tparam E e.g. `array_type::tensor` -\param coor Nodal coordinates `[nnode, ndim]`. -\param conn Connectivity `[nelem, nne]`. -\return Center of gravity `[ndim]`. -*/ + * Compute the center of gravity of a mesh. + * + * @tparam C e.g. `array_type::tensor` + * @tparam E e.g. `array_type::tensor` + * @param coor Nodal coordinates `[nnode, ndim]`. + * @param conn Connectivity `[nelem, nne]`. + * @return Center of gravity `[ndim]`. + */ template inline array_type::tensor center_of_gravity(const C& coor, const E& conn) { // todo: remove after upstream fix return detail::average_axis_0(coor, nodal_mass(coor, conn, defaultElementType(coor, conn))); // return xt::average(coor, nodal_mass(coor, conn, defaultElementType(coor, conn)), 0); } /** -List nodal tyings based on DOF-numbers per node. - -\param dofs DOFs per node [nnode, ndim]. -\return Nodes to which the nodes is connected (sorted) [nnode, ...] -*/ + * List nodal tyings based on DOF-numbers per node. + * + * @param dofs DOFs per node [nnode, ndim]. + * @return Nodes to which the nodes is connected (sorted) [nnode, ...] + */ template inline std::vector> nodaltyings(const D& dofs) { size_t nnode = dofs.shape(0); size_t ndim = dofs.shape(1); auto nodemap = node2dof(dofs); std::vector> ret(nnode); for (size_t m = 0; m < nnode; ++m) { auto r = nodemap[dofs(m, 0)]; std::sort(r.begin(), r.end()); ret[m] = r; #ifdef GOOSEFEM_ENABLE_ASSERT for (size_t i = 1; i < ndim; ++i) { auto t = nodemap[dofs(m, i)]; std::sort(t.begin(), t.end()); GOOSEFEM_ASSERT(std::equal(r.begin(), r.end(), t.begin())); } #endif } return ret; } } // namespace Mesh } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MeshHex8.h b/include/GooseFEM/MeshHex8.h index 69be06a..fe0ee8d 100644 --- a/include/GooseFEM/MeshHex8.h +++ b/include/GooseFEM/MeshHex8.h @@ -1,2301 +1,2301 @@ /** -Generate simple meshes of 8-noded hexahedral elements in 3d (GooseFEM::Mesh::ElementType::Hex8). - -\file MeshHex8.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Generate simple meshes of 8-noded hexahedral elements in 3d (GooseFEM::Mesh::ElementType::Hex8). + * + * @file MeshHex8.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MESHHEX8_H #define GOOSEFEM_MESHHEX8_H #include "Mesh.h" #include "config.h" namespace GooseFEM { namespace Mesh { /** -Simple meshes of 8-noded hexahedral elements in 3d (ElementType::Hex8). -*/ + * Simple meshes of 8-noded hexahedral elements in 3d (ElementType::Hex8). + */ namespace Hex8 { /** -Regular mesh: equi-sized elements. -*/ + * Regular mesh: equi-sized elements. + */ class Regular : public RegularBase3d { public: /** - Constructor. - - \param nelx Number of elements in horizontal (x) direction. - \param nely Number of elements in vertical (y) direction. - \param nelz Number of elements in vertical (z) direction. - \param h Edge size (width == height == depth). - */ + * Constructor. + * + * @param nelx Number of elements in horizontal (x) direction. + * @param nely Number of elements in vertical (y) direction. + * @param nelz Number of elements in vertical (z) direction. + * @param h Edge size (width == height == depth). + */ Regular(size_t nelx, size_t nely, size_t nelz, double h = 1.0) { m_h = h; m_nelx = nelx; m_nely = nely; m_nelz = nelz; m_nne = 8; m_ndim = 3; GOOSEFEM_ASSERT(m_nelx >= 1ul); GOOSEFEM_ASSERT(m_nely >= 1ul); GOOSEFEM_ASSERT(m_nelz >= 1ul); m_nnode = (m_nelx + 1) * (m_nely + 1) * (m_nelz + 1); m_nelem = m_nelx * m_nely * m_nelz; } private: friend class RegularBase; friend class RegularBase3d; size_t nelx_impl() const { return m_nelx; } size_t nely_impl() const { return m_nely; } size_t nelz_impl() const { return m_nelz; } ElementType getElementType_impl() const { return ElementType::Hex8; } array_type::tensor coor_impl() const { array_type::tensor ret = xt::empty({m_nnode, m_ndim}); array_type::tensor x = xt::linspace(0.0, m_h * static_cast(m_nelx), m_nelx + 1); array_type::tensor y = xt::linspace(0.0, m_h * static_cast(m_nely), m_nely + 1); array_type::tensor z = xt::linspace(0.0, m_h * static_cast(m_nelz), m_nelz + 1); size_t inode = 0; for (size_t iz = 0; iz < m_nelz + 1; ++iz) { for (size_t iy = 0; iy < m_nely + 1; ++iy) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy); ret(inode, 2) = z(iz); ++inode; } } } return ret; } array_type::tensor conn_impl() const { array_type::tensor ret = xt::empty({m_nelem, m_nne}); size_t ielem = 0; for (size_t iz = 0; iz < m_nelz; ++iz) { for (size_t iy = 0; iy < m_nely; ++iy) { for (size_t ix = 0; ix < m_nelx; ++ix) { ret(ielem, 0) = iy * (m_nelx + 1) + ix + iz * (m_nely + 1) * (m_nelx + 1); ret(ielem, 1) = iy * (m_nelx + 1) + (ix + 1) + iz * (m_nely + 1) * (m_nelx + 1); ret(ielem, 3) = (iy + 1) * (m_nelx + 1) + ix + iz * (m_nely + 1) * (m_nelx + 1); ret(ielem, 2) = (iy + 1) * (m_nelx + 1) + (ix + 1) + iz * (m_nely + 1) * (m_nelx + 1); ret(ielem, 4) = iy * (m_nelx + 1) + ix + (iz + 1) * (m_nely + 1) * (m_nelx + 1); ret(ielem, 5) = (iy) * (m_nelx + 1) + (ix + 1) + (iz + 1) * (m_nely + 1) * (m_nelx + 1); ret(ielem, 7) = (iy + 1) * (m_nelx + 1) + ix + (iz + 1) * (m_nely + 1) * (m_nelx + 1); ret(ielem, 6) = (iy + 1) * (m_nelx + 1) + (ix + 1) + (iz + 1) * (m_nely + 1) * (m_nelx + 1); ++ielem; } } } return ret; } array_type::tensor nodesFront_impl() const { array_type::tensor ret = xt::empty({(m_nelx + 1) * (m_nely + 1)}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(iy * (m_nelx + 1) + ix) = iy * (m_nelx + 1) + ix; } } return ret; } array_type::tensor nodesBack_impl() const { array_type::tensor ret = xt::empty({(m_nelx + 1) * (m_nely + 1)}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(iy * (m_nelx + 1) + ix) = iy * (m_nelx + 1) + ix + m_nelz * (m_nely + 1) * (m_nelx + 1); } } return ret; } array_type::tensor nodesLeft_impl() const { array_type::tensor ret = xt::empty({(m_nely + 1) * (m_nelz + 1)}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iz * (m_nely + 1) + iy) = iy * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1); } } return ret; } array_type::tensor nodesRight_impl() const { array_type::tensor ret = xt::empty({(m_nely + 1) * (m_nelz + 1)}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iz * (m_nely + 1) + iy) = iy * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } } return ret; } array_type::tensor nodesBottom_impl() const { array_type::tensor ret = xt::empty({(m_nelx + 1) * (m_nelz + 1)}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(iz * (m_nelx + 1) + ix) = ix + iz * (m_nelx + 1) * (m_nely + 1); } } return ret; } array_type::tensor nodesTop_impl() const { array_type::tensor ret = xt::empty({(m_nelx + 1) * (m_nelz + 1)}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(iz * (m_nelx + 1) + ix) = ix + m_nely * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1); } } return ret; } array_type::tensor nodesFrontFace_impl() const { array_type::tensor ret = xt::empty({(m_nelx - 1) * (m_nely - 1)}); for (size_t iy = 1; iy < m_nely; ++iy) { for (size_t ix = 1; ix < m_nelx; ++ix) { ret((iy - 1) * (m_nelx - 1) + (ix - 1)) = iy * (m_nelx + 1) + ix; } } return ret; } array_type::tensor nodesBackFace_impl() const { array_type::tensor ret = xt::empty({(m_nelx - 1) * (m_nely - 1)}); for (size_t iy = 1; iy < m_nely; ++iy) { for (size_t ix = 1; ix < m_nelx; ++ix) { ret((iy - 1) * (m_nelx - 1) + (ix - 1)) = iy * (m_nelx + 1) + ix + m_nelz * (m_nely + 1) * (m_nelx + 1); } } return ret; } array_type::tensor nodesLeftFace_impl() const { array_type::tensor ret = xt::empty({(m_nely - 1) * (m_nelz - 1)}); for (size_t iz = 1; iz < m_nelz; ++iz) { for (size_t iy = 1; iy < m_nely; ++iy) { ret((iz - 1) * (m_nely - 1) + (iy - 1)) = iy * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1); } } return ret; } array_type::tensor nodesRightFace_impl() const { array_type::tensor ret = xt::empty({(m_nely - 1) * (m_nelz - 1)}); for (size_t iz = 1; iz < m_nelz; ++iz) { for (size_t iy = 1; iy < m_nely; ++iy) { ret((iz - 1) * (m_nely - 1) + (iy - 1)) = iy * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } } return ret; } array_type::tensor nodesBottomFace_impl() const { array_type::tensor ret = xt::empty({(m_nelx - 1) * (m_nelz - 1)}); for (size_t iz = 1; iz < m_nelz; ++iz) { for (size_t ix = 1; ix < m_nelx; ++ix) { ret((iz - 1) * (m_nelx - 1) + (ix - 1)) = ix + iz * (m_nelx + 1) * (m_nely + 1); } } return ret; } array_type::tensor nodesTopFace_impl() const { array_type::tensor ret = xt::empty({(m_nelx - 1) * (m_nelz - 1)}); for (size_t iz = 1; iz < m_nelz; ++iz) { for (size_t ix = 1; ix < m_nelx; ++ix) { ret((iz - 1) * (m_nelx - 1) + (ix - 1)) = ix + m_nely * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1); } } return ret; } array_type::tensor nodesFrontBottomEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx + 1}); for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(ix) = ix; } return ret; } array_type::tensor nodesFrontTopEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx + 1}); for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(ix) = ix + m_nely * (m_nelx + 1); } return ret; } array_type::tensor nodesFrontLeftEdge_impl() const { array_type::tensor ret = xt::empty({m_nely + 1}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iy) = iy * (m_nelx + 1); } return ret; } array_type::tensor nodesFrontRightEdge_impl() const { array_type::tensor ret = xt::empty({m_nely + 1}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iy) = iy * (m_nelx + 1) + m_nelx; } return ret; } array_type::tensor nodesBackBottomEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx + 1}); for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(ix) = ix + m_nelz * (m_nely + 1) * (m_nelx + 1); } return ret; } array_type::tensor nodesBackTopEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx + 1}); for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(ix) = m_nely * (m_nelx + 1) + ix + m_nelz * (m_nely + 1) * (m_nelx + 1); } return ret; } array_type::tensor nodesBackLeftEdge_impl() const { array_type::tensor ret = xt::empty({m_nely + 1}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iy) = iy * (m_nelx + 1) + m_nelz * (m_nelx + 1) * (m_nely + 1); } return ret; } array_type::tensor nodesBackRightEdge_impl() const { array_type::tensor ret = xt::empty({m_nely + 1}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iy) = iy * (m_nelx + 1) + m_nelz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } return ret; } array_type::tensor nodesBottomLeftEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz + 1}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { ret(iz) = iz * (m_nelx + 1) * (m_nely + 1); } return ret; } array_type::tensor nodesBottomRightEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz + 1}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { ret(iz) = iz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } return ret; } array_type::tensor nodesTopLeftEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz + 1}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { ret(iz) = m_nely * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1); } return ret; } array_type::tensor nodesTopRightEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz + 1}); for (size_t iz = 0; iz < m_nelz + 1; ++iz) { ret(iz) = m_nely * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } return ret; } array_type::tensor nodesFrontBottomOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx - 1}); for (size_t ix = 1; ix < m_nelx; ++ix) { ret(ix - 1) = ix; } return ret; } array_type::tensor nodesFrontTopOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx - 1}); for (size_t ix = 1; ix < m_nelx; ++ix) { ret(ix - 1) = ix + m_nely * (m_nelx + 1); } return ret; } array_type::tensor nodesFrontLeftOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nely - 1}); for (size_t iy = 1; iy < m_nely; ++iy) { ret(iy - 1) = iy * (m_nelx + 1); } return ret; } array_type::tensor nodesFrontRightOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nely - 1}); for (size_t iy = 1; iy < m_nely; ++iy) { ret(iy - 1) = iy * (m_nelx + 1) + m_nelx; } return ret; } array_type::tensor nodesBackBottomOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx - 1}); for (size_t ix = 1; ix < m_nelx; ++ix) { ret(ix - 1) = ix + m_nelz * (m_nely + 1) * (m_nelx + 1); } return ret; } array_type::tensor nodesBackTopOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx - 1}); for (size_t ix = 1; ix < m_nelx; ++ix) { ret(ix - 1) = m_nely * (m_nelx + 1) + ix + m_nelz * (m_nely + 1) * (m_nelx + 1); } return ret; } array_type::tensor nodesBackLeftOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nely - 1}); for (size_t iy = 1; iy < m_nely; ++iy) { ret(iy - 1) = iy * (m_nelx + 1) + m_nelz * (m_nelx + 1) * (m_nely + 1); } return ret; } array_type::tensor nodesBackRightOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nely - 1}); for (size_t iy = 1; iy < m_nely; ++iy) { ret(iy - 1) = iy * (m_nelx + 1) + m_nelz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } return ret; } array_type::tensor nodesBottomLeftOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz - 1}); for (size_t iz = 1; iz < m_nelz; ++iz) { ret(iz - 1) = iz * (m_nelx + 1) * (m_nely + 1); } return ret; } array_type::tensor nodesBottomRightOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz - 1}); for (size_t iz = 1; iz < m_nelz; ++iz) { ret(iz - 1) = iz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } return ret; } array_type::tensor nodesTopLeftOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz - 1}); for (size_t iz = 1; iz < m_nelz; ++iz) { ret(iz - 1) = m_nely * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1); } return ret; } array_type::tensor nodesTopRightOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelz - 1}); for (size_t iz = 1; iz < m_nelz; ++iz) { ret(iz - 1) = m_nely * (m_nelx + 1) + iz * (m_nelx + 1) * (m_nely + 1) + m_nelx; } return ret; } size_t nodesFrontBottomLeftCorner_impl() const { return 0; } size_t nodesFrontBottomRightCorner_impl() const { return m_nelx; } size_t nodesFrontTopLeftCorner_impl() const { return m_nely * (m_nelx + 1); } size_t nodesFrontTopRightCorner_impl() const { return m_nely * (m_nelx + 1) + m_nelx; } size_t nodesBackBottomLeftCorner_impl() const { return m_nelz * (m_nely + 1) * (m_nelx + 1); } size_t nodesBackBottomRightCorner_impl() const { return m_nelx + m_nelz * (m_nely + 1) * (m_nelx + 1); } size_t nodesBackTopLeftCorner_impl() const { return m_nely * (m_nelx + 1) + m_nelz * (m_nely + 1) * (m_nelx + 1); } size_t nodesBackTopRightCorner_impl() const { return m_nely * (m_nelx + 1) + m_nelx + m_nelz * (m_nely + 1) * (m_nelx + 1); } double m_h; ///< See h() size_t m_nelx; ///< See nelx() size_t m_nely; ///< See nely() size_t m_nelz; ///< See nely() size_t m_nelem; ///< See nelem() size_t m_nnode; ///< See nnode() size_t m_nne; ///< See nne() size_t m_ndim; ///< See ndim() }; /** -Mesh with fine middle layer, and coarser elements towards the top and bottom. -*/ + * Mesh with fine middle layer, and coarser elements towards the top and bottom. + */ class FineLayer : public RegularBase3d { public: /** - Constructor. - - \param nelx Number of elements (along the middle layer) in horizontal (x) direction. - \param nely Approximate equivalent number of elements in vertical (y) direction. - \param nelz Number of elements (along the middle layer) in depth (z) direction. - \param h Edge size (width == height == depth) of elements along the weak layer. - - \param nfine - Extra number of fine layers around the middle layer. - By default the element size is kept smaller than the distance to the middle layer. - */ + * Constructor. + * + * @param nelx Number of elements (along the middle layer) in horizontal (x) direction. + * @param nely Approximate equivalent number of elements in vertical (y) direction. + * @param nelz Number of elements (along the middle layer) in depth (z) direction. + * @param h Edge size (width == height == depth) of elements along the weak layer. + * + * @param nfine + * Extra number of fine layers around the middle layer. + * By default the element size is kept smaller than the distance to the middle layer. + */ FineLayer(size_t nelx, size_t nely, size_t nelz, double h = 1.0, size_t nfine = 1) { m_h = h; m_nne = 8; m_ndim = 3; // basic assumptions GOOSEFEM_ASSERT(nelx >= 1ul); GOOSEFEM_ASSERT(nely >= 1ul); GOOSEFEM_ASSERT(nelz >= 1ul); // store basic info m_Lx = m_h * static_cast(nelx); m_Lz = m_h * static_cast(nelz); // compute element size in y-direction (use symmetry, compute upper half) // temporary variables size_t nmin, ntot; array_type::tensor nhx = xt::ones({nely}); array_type::tensor nhy = xt::ones({nely}); array_type::tensor nhz = xt::ones({nely}); array_type::tensor refine = -1 * xt::ones({nely}); // minimum height in y-direction (half of the height because of symmetry) if (nely % 2 == 0) { nmin = nely / 2; } else { nmin = (nely + 1) / 2; } // minimum number of fine layers in y-direction (minimum 1, middle layer part of this half) if (nfine % 2 == 0) { nfine = nfine / 2 + 1; } else { nfine = (nfine + 1) / 2; } if (nfine < 1) { nfine = 1; } if (nfine > nmin) { nfine = nmin; } // loop over element layers in y-direction, try to coarsen using these rules: // (1) element size in y-direction <= distance to origin in y-direction // (2) element size in x-(z-)direction should fit the total number of elements in // x-(z-)direction (3) a certain number of layers have the minimum size "1" (are fine) for (size_t iy = nfine;;) { // initialize current size in y-direction if (iy == nfine) { ntot = nfine; } // check to stop if (iy >= nely || ntot >= nmin) { nely = iy; break; } // rules (1,2) satisfied: coarsen in x-direction (and z-direction) if (3 * nhy(iy) <= ntot && nelx % (3 * nhx(iy)) == 0 && ntot + nhy(iy) < nmin) { // - process refinement in x-direction refine(iy) = 0; nhy(iy) *= 2; auto vnhy = xt::view(nhy, xt::range(iy + 1, _)); auto vnhx = xt::view(nhx, xt::range(iy, _)); vnhy *= 3; vnhx *= 3; // - rule (2) satisfied: coarsen next element layer in z-direction if (iy + 1 < nely && ntot + 2 * nhy(iy) < nmin) { if (nelz % (3 * nhz(iy + 1)) == 0) { // - update the number of elements in y-direction ntot += nhy(iy); // - proceed to next element layer in y-direction ++iy; // - process refinement in z-direction refine(iy) = 2; nhy(iy) = nhy(iy - 1); auto vnhz = xt::view(nhz, xt::range(iy, _)); vnhz *= 3; } } } // rules (1,2) satisfied: coarse in z-direction else if (3 * nhy(iy) <= ntot && nelz % (3 * nhz(iy)) == 0 && ntot + nhy(iy) < nmin) { // - process refinement in z-direction refine(iy) = 2; nhy(iy) *= 2; auto vnhy = xt::view(nhy, xt::range(iy + 1, _)); auto vnhz = xt::view(nhz, xt::range(iy, _)); vnhy *= 3; vnhz *= 3; } // update the number of elements in y-direction ntot += nhy(iy); // proceed to next element layer in y-direction ++iy; // check to stop if (iy >= nely || ntot >= nmin) { nely = iy; break; } } // symmetrize, compute full information // allocate mesh constructor parameters m_nhx = xt::empty({nely * 2 - 1}); m_nhy = xt::empty({nely * 2 - 1}); m_nhz = xt::empty({nely * 2 - 1}); m_refine = xt::empty({nely * 2 - 1}); m_layer_nelx = xt::empty({nely * 2 - 1}); m_layer_nelz = xt::empty({nely * 2 - 1}); m_nnd = xt::empty({nely * 2}); m_startElem = xt::empty({nely * 2 - 1}); m_startNode = xt::empty({nely * 2}); // fill // - lower half for (size_t iy = 0; iy < nely; ++iy) { m_nhx(iy) = nhx(nely - iy - 1); m_nhy(iy) = nhy(nely - iy - 1); m_nhz(iy) = nhz(nely - iy - 1); m_refine(iy) = refine(nely - iy - 1); } // - upper half for (size_t iy = 0; iy < nely - 1; ++iy) { m_nhx(iy + nely) = nhx(iy + 1); m_nhy(iy + nely) = nhy(iy + 1); m_nhz(iy + nely) = nhz(iy + 1); m_refine(iy + nely) = refine(iy + 1); } // update size nely = m_nhx.size(); // compute the number of elements per element layer in y-direction for (size_t iy = 0; iy < nely; ++iy) { m_layer_nelx(iy) = nelx / m_nhx(iy); m_layer_nelz(iy) = nelz / m_nhz(iy); } // compute the number of nodes per node layer in y-direction // - bottom half for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) m_nnd(iy) = (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy) + 1); // - top half for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) m_nnd(iy + 1) = (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy) + 1); // compute mesh dimensions // initialize m_nnode = 0; m_nelem = 0; m_startNode(0) = 0; // loop over element layers (bottom -> middle, elements become finer) for (size_t i = 0; i < (nely - 1) / 2; ++i) { // - store the first element of the layer m_startElem(i) = m_nelem; // - add the nodes of this layer if (m_refine(i) == 0) { m_nnode += (3 * m_layer_nelx(i) + 1) * (m_layer_nelz(i) + 1); } else if (m_refine(i) == 2) { m_nnode += (m_layer_nelx(i) + 1) * (3 * m_layer_nelz(i) + 1); } else { m_nnode += (m_layer_nelx(i) + 1) * (m_layer_nelz(i) + 1); } // - add the elements of this layer if (m_refine(i) == 0) { m_nelem += (4 * m_layer_nelx(i)) * (m_layer_nelz(i)); } else if (m_refine(i) == 2) { m_nelem += (m_layer_nelx(i)) * (4 * m_layer_nelz(i)); } else { m_nelem += (m_layer_nelx(i)) * (m_layer_nelz(i)); } // - store the starting node of the next layer m_startNode(i + 1) = m_nnode; } // loop over element layers (middle -> top, elements become coarser) for (size_t i = (nely - 1) / 2; i < nely; ++i) { // - store the first element of the layer m_startElem(i) = m_nelem; // - add the nodes of this layer if (m_refine(i) == 0) { m_nnode += (5 * m_layer_nelx(i) + 1) * (m_layer_nelz(i) + 1); } else if (m_refine(i) == 2) { m_nnode += (m_layer_nelx(i) + 1) * (5 * m_layer_nelz(i) + 1); } else { m_nnode += (m_layer_nelx(i) + 1) * (m_layer_nelz(i) + 1); } // - add the elements of this layer if (m_refine(i) == 0) { m_nelem += (4 * m_layer_nelx(i)) * (m_layer_nelz(i)); } else if (m_refine(i) == 2) { m_nelem += (m_layer_nelx(i)) * (4 * m_layer_nelz(i)); } else { m_nelem += (m_layer_nelx(i)) * (m_layer_nelz(i)); } // - store the starting node of the next layer m_startNode(i + 1) = m_nnode; } // - add the top row of nodes m_nnode += (m_layer_nelx(nely - 1) + 1) * (m_layer_nelz(nely - 1) + 1); } /** - Elements in the middle (fine) layer. - \return List of element numbers (copy, involves computation). - */ + * Elements in the middle (fine) layer. + * @return List of element numbers (copy, involves computation). + */ array_type::tensor elementsMiddleLayer() const { size_t nely = static_cast(m_nhy.size()); size_t y = (nely - 1) / 2; array_type::tensor ret = xt::empty({m_layer_nelx(y) * m_layer_nelz(y)}); for (size_t x = 0; x < m_layer_nelx(y); ++x) { for (size_t z = 0; z < m_layer_nelz(y); ++z) { ret(x + z * m_layer_nelx(y)) = m_startElem(y) + x + z * m_layer_nelx(y); } } return ret; } private: friend class RegularBase; friend class RegularBase3d; size_t nelx_impl() const { return xt::amax(m_layer_nelx)(); } size_t nely_impl() const { return xt::sum(m_nhy)(); } size_t nelz_impl() const { return xt::amax(m_layer_nelz)(); } ElementType getElementType_impl() const { return ElementType::Hex8; } array_type::tensor coor_impl() const { // allocate output array_type::tensor ret = xt::empty({m_nnode, m_ndim}); // current node, number of element layers size_t inode = 0; size_t nely = static_cast(m_nhy.size()); // y-position of each main node layer (i.e. excluding node layers for refinement/coarsening) // - allocate array_type::tensor y = xt::empty({nely + 1}); // - initialize y(0) = 0.0; // - compute for (size_t iy = 1; iy < nely + 1; ++iy) { y(iy) = y(iy - 1) + m_nhy(iy - 1) * m_h; } // loop over element layers (bottom -> middle) : add bottom layer (+ refinement layer) of // nodes for (size_t iy = 0;; ++iy) { // get positions along the x- and z-axis array_type::tensor x = xt::linspace(0.0, m_Lx, m_layer_nelx(iy) + 1); array_type::tensor z = xt::linspace(0.0, m_Lz, m_layer_nelz(iy) + 1); // add nodes of the bottom layer of this element for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy); ret(inode, 2) = z(iz); ++inode; } } // stop at middle layer if (iy == (nely - 1) / 2) { break; } // add extra nodes of the intermediate layer, for refinement in x-direction if (m_refine(iy) == 0) { // - get position offset in x- and y-direction double dx = m_h * static_cast(m_nhx(iy) / 3); double dy = m_h * static_cast(m_nhy(iy) / 2); // - add nodes of the intermediate layer for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { for (size_t j = 0; j < 2; ++j) { ret(inode, 0) = x(ix) + dx * static_cast(j + 1); ret(inode, 1) = y(iy) + dy; ret(inode, 2) = z(iz); ++inode; } } } } // add extra nodes of the intermediate layer, for refinement in z-direction else if (m_refine(iy) == 2) { // - get position offset in y- and z-direction double dz = m_h * static_cast(m_nhz(iy) / 3); double dy = m_h * static_cast(m_nhy(iy) / 2); // - add nodes of the intermediate layer for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t j = 0; j < 2; ++j) { for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy) + dy; ret(inode, 2) = z(iz) + dz * static_cast(j + 1); ++inode; } } } } } // loop over element layers (middle -> top) : add (refinement layer +) top layer of nodes for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { // get positions along the x- and z-axis array_type::tensor x = xt::linspace(0.0, m_Lx, m_layer_nelx(iy) + 1); array_type::tensor z = xt::linspace(0.0, m_Lz, m_layer_nelz(iy) + 1); // add extra nodes of the intermediate layer, for refinement in x-direction if (m_refine(iy) == 0) { // - get position offset in x- and y-direction double dx = m_h * static_cast(m_nhx(iy) / 3); double dy = m_h * static_cast(m_nhy(iy) / 2); // - add nodes of the intermediate layer for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { for (size_t j = 0; j < 2; ++j) { ret(inode, 0) = x(ix) + dx * static_cast(j + 1); ret(inode, 1) = y(iy) + dy; ret(inode, 2) = z(iz); ++inode; } } } } // add extra nodes of the intermediate layer, for refinement in z-direction else if (m_refine(iy) == 2) { // - get position offset in y- and z-direction double dz = m_h * static_cast(m_nhz(iy) / 3); double dy = m_h * static_cast(m_nhy(iy) / 2); // - add nodes of the intermediate layer for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t j = 0; j < 2; ++j) { for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy) + dy; ret(inode, 2) = z(iz) + dz * static_cast(j + 1); ++inode; } } } } // add nodes of the top layer of this element for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy + 1); ret(inode, 2) = z(iz); ++inode; } } } return ret; } array_type::tensor conn_impl() const { // allocate output array_type::tensor ret = xt::empty({m_nelem, m_nne}); // current element, number of element layers, starting nodes of each node layer size_t ielem = 0; size_t nely = static_cast(m_nhy.size()); size_t bot, mid, top; // loop over all element layers for (size_t iy = 0; iy < nely; ++iy) { // - get: starting nodes of bottom(, middle) and top layer bot = m_startNode(iy); mid = m_startNode(iy) + m_nnd(iy); top = m_startNode(iy + 1); // - define connectivity: no coarsening/refinement if (m_refine(iy) == -1) { for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { ret(ielem, 0) = bot + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ielem++; } } } // - define connectivity: refinement along the x-direction (below the middle layer) else if (m_refine(iy) == 0 && iy <= (nely - 1) / 2) { for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { // -- bottom element ret(ielem, 0) = bot + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 2) = mid + (2 * ix + 1) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 3) = mid + (2 * ix) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 4) = bot + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = mid + (2 * ix + 1) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 7) = mid + (2 * ix) + (iz + 1) * (2 * m_layer_nelx(iy)); ielem++; // -- top-right element ret(ielem, 0) = bot + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 1) = top + (3 * ix + 3) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (3 * ix + 2) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 3) = mid + (2 * ix + 1) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 4) = bot + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = top + (3 * ix + 3) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (3 * ix + 2) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 7) = mid + (2 * ix + 1) + (iz + 1) * (2 * m_layer_nelx(iy)); ielem++; // -- top-center element ret(ielem, 0) = mid + (2 * ix) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 1) = mid + (2 * ix + 1) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 2) = top + (3 * ix + 2) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 3) = top + (3 * ix + 1) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 4) = mid + (2 * ix) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 5) = mid + (2 * ix + 1) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 6) = top + (3 * ix + 2) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 7) = top + (3 * ix + 1) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ielem++; // -- top-left element ret(ielem, 0) = bot + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 1) = mid + (2 * ix) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 2) = top + (3 * ix + 1) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 3) = top + (3 * ix) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = mid + (2 * ix) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 6) = top + (3 * ix + 1) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 7) = top + (3 * ix) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ielem++; } } } // - define connectivity: coarsening along the x-direction (above the middle layer) else if (m_refine(iy) == 0 && iy > (nely - 1) / 2) { for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { // -- lower-left element ret(ielem, 0) = bot + (3 * ix) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (3 * ix + 1) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 2) = mid + (2 * ix) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 3) = top + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + (3 * ix) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (3 * ix + 1) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 6) = mid + (2 * ix) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 7) = top + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ielem++; // -- lower-center element ret(ielem, 0) = bot + (3 * ix + 1) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (3 * ix + 2) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 2) = mid + (2 * ix + 1) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 3) = mid + (2 * ix) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 4) = bot + (3 * ix + 1) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (3 * ix + 2) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 6) = mid + (2 * ix + 1) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 7) = mid + (2 * ix) + (iz + 1) * (2 * m_layer_nelx(iy)); ielem++; // -- lower-right element ret(ielem, 0) = bot + (3 * ix + 2) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (3 * ix + 3) + iz * (3 * m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 3) = mid + (2 * ix + 1) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 4) = bot + (3 * ix + 2) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (3 * ix + 3) + (iz + 1) * (3 * m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = mid + (2 * ix + 1) + (iz + 1) * (2 * m_layer_nelx(iy)); ielem++; // -- upper element ret(ielem, 0) = mid + (2 * ix) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 1) = mid + (2 * ix + 1) + iz * (2 * m_layer_nelx(iy)); ret(ielem, 2) = top + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 4) = mid + (2 * ix) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 5) = mid + (2 * ix + 1) + (iz + 1) * (2 * m_layer_nelx(iy)); ret(ielem, 6) = top + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ielem++; } } } // - define connectivity: refinement along the z-direction (below the middle layer) else if (m_refine(iy) == 2 && iy <= (nely - 1) / 2) { for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { // -- bottom element ret(ielem, 0) = bot + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = bot + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 3) = bot + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 4) = mid + ix + 2 * iz * (m_layer_nelx(iy) + 1); ret(ielem, 5) = mid + ix + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = mid + (ix + 1) + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = mid + (ix + 1) + 2 * iz * (m_layer_nelx(iy) + 1); ielem++; // -- top-back element ret(ielem, 0) = mid + ix + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 1) = mid + (ix + 1) + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (3 * iz + 3) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (3 * iz + 3) * (m_layer_nelx(iy) + 1); ielem++; // -- top-center element ret(ielem, 0) = mid + ix + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 1) = mid + (ix + 1) + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 4) = mid + ix + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = mid + (ix + 1) + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ielem++; // -- top-front element ret(ielem, 0) = bot + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + (3 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + (3 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 4) = mid + ix + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = mid + (ix + 1) + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ielem++; } } } // - define connectivity: coarsening along the z-direction (above the middle layer) else if (m_refine(iy) == 2 && iy > (nely - 1) / 2) { for (size_t iz = 0; iz < m_layer_nelz(iy); ++iz) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { // -- bottom-front element ret(ielem, 0) = bot + ix + (3 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (ix + 1) + (3 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + ix + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (ix + 1) + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = mid + (ix + 1) + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = mid + ix + (2 * iz) * (m_layer_nelx(iy) + 1); ielem++; // -- bottom-center element ret(ielem, 0) = bot + ix + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (ix + 1) + (3 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = mid + (ix + 1) + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 3) = mid + ix + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + ix + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (ix + 1) + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = mid + (ix + 1) + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = mid + ix + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ielem++; // -- bottom-back element ret(ielem, 0) = bot + ix + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 1) = bot + (ix + 1) + (3 * iz + 2) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = mid + (ix + 1) + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 3) = mid + ix + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 4) = bot + ix + (3 * iz + 3) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = bot + (ix + 1) + (3 * iz + 3) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ielem++; // -- top element ret(ielem, 0) = mid + ix + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 1) = mid + (ix + 1) + (2 * iz) * (m_layer_nelx(iy) + 1); ret(ielem, 2) = top + (ix + 1) + iz * (m_layer_nelx(iy) + 1); ret(ielem, 3) = top + ix + iz * (m_layer_nelx(iy) + 1); ret(ielem, 4) = mid + ix + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 5) = mid + (ix + 1) + (2 * iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 6) = top + (ix + 1) + (iz + 1) * (m_layer_nelx(iy) + 1); ret(ielem, 7) = top + ix + (iz + 1) * (m_layer_nelx(iy) + 1); ielem++; } } } } return ret; } array_type::tensor nodesFront_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 + 1; } else { n += m_layer_nelx(iy) + 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 + 1; } else { n += m_layer_nelx(iy) + 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(j) = m_startNode(iy) + ix; ++j; } // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy); ++j; } } // -- top node layer for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(j) = m_startNode(iy + 1) + ix; ++j; } } return ret; } array_type::tensor nodesBack_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 + 1; } else { n += m_layer_nelx(iy) + 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 + 1; } else { n += m_layer_nelx(iy) + 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(j) = m_startNode(iy) + ix + (m_layer_nelx(iy) + 1) * m_layer_nelz(iy); ++j; } // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy) + 2 * m_layer_nelx(iy) * m_layer_nelz(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy) + 2 * m_layer_nelx(iy) * m_layer_nelz(iy); ++j; } } // -- top node layer for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(j) = m_startNode(iy + 1) + ix + (m_layer_nelx(iy) + 1) * m_layer_nelz(iy); ++j; } } return ret; } array_type::tensor nodesLeft_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 2) { n += m_layer_nelz(iy) * 3 + 1; } else { n += m_layer_nelz(iy) + 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { if (m_refine(iy) == 2) { n += m_layer_nelz(iy) * 3 + 1; } else { n += m_layer_nelz(iy) + 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1); ++j; } // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1) + m_nnd(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1) + m_nnd(iy); ++j; } } // -- top node layer for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { ret(j) = m_startNode(iy + 1) + iz * (m_layer_nelx(iy) + 1); ++j; } } return ret; } array_type::tensor nodesRight_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 2) n += m_layer_nelz(iy) * 3 + 1; else n += m_layer_nelz(iy) + 1; } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { if (m_refine(iy) == 2) n += m_layer_nelz(iy) * 3 + 1; else n += m_layer_nelz(iy) + 1; } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + m_nnd(iy) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + m_nnd(iy) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } } // -- top node layer for (size_t iz = 0; iz < m_layer_nelz(iy) + 1; ++iz) { ret(j) = m_startNode(iy + 1) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } } return ret; } array_type::tensor nodesBottom_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // allocate node list array_type::tensor ret = xt::empty({m_nnd(nely)}); // counter size_t j = 0; // fill node list for (size_t ix = 0; ix < m_layer_nelx(0) + 1; ++ix) { for (size_t iz = 0; iz < m_layer_nelz(0) + 1; ++iz) { ret(j) = m_startNode(0) + ix + iz * (m_layer_nelx(0) + 1); ++j; } } return ret; } array_type::tensor nodesTop_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // allocate node list array_type::tensor ret = xt::empty({m_nnd(nely)}); // counter size_t j = 0; // fill node list for (size_t ix = 0; ix < m_layer_nelx(nely - 1) + 1; ++ix) { for (size_t iz = 0; iz < m_layer_nelz(nely - 1) + 1; ++iz) { ret(j) = m_startNode(nely) + ix + iz * (m_layer_nelx(nely - 1) + 1); ++j; } } return ret; } array_type::tensor nodesFrontFace_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 - 1; } else { n += m_layer_nelx(iy) - 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 - 1; } else { n += m_layer_nelx(iy) - 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t ix = 1; ix < m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix; ++j; } // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy); ++j; } } // -- top node layer for (size_t ix = 1; ix < m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy + 1) + ix; ++j; } } return ret; } array_type::tensor nodesBackFace_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 - 1; } else { n += m_layer_nelx(iy) - 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { if (m_refine(iy) == 0) { n += m_layer_nelx(iy) * 3 - 1; } else { n += m_layer_nelx(iy) - 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t ix = 1; ix < m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + (m_layer_nelx(iy) + 1) * m_layer_nelz(iy); ++j; } // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy) + 2 * m_layer_nelx(iy) * m_layer_nelz(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { // -- refinement layer if (m_refine(iy) == 0) { for (size_t ix = 0; ix < 2 * m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy) + ix + m_nnd(iy) + 2 * m_layer_nelx(iy) * m_layer_nelz(iy); ++j; } } // -- top node layer for (size_t ix = 1; ix < m_layer_nelx(iy); ++ix) { ret(j) = m_startNode(iy + 1) + ix + (m_layer_nelx(iy) + 1) * m_layer_nelz(iy); ++j; } } return ret; } array_type::tensor nodesLeftFace_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 2) { n += m_layer_nelz(iy) * 3 - 1; } else { n += m_layer_nelz(iy) - 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { if (m_refine(iy) == 2) { n += m_layer_nelz(iy) * 3 - 1; } else { n += m_layer_nelz(iy) - 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t iz = 1; iz < m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1); ++j; } // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1) + m_nnd(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1) + m_nnd(iy); ++j; } } // -- top node layer for (size_t iz = 1; iz < m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy + 1) + iz * (m_layer_nelx(iy) + 1); ++j; } } return ret; } array_type::tensor nodesRightFace_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // number of boundary nodes // - initialize size_t n = 0; // - bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { if (m_refine(iy) == 2) { n += m_layer_nelz(iy) * 3 - 1; } else { n += m_layer_nelz(iy) - 1; } } // - top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { if (m_refine(iy) == 2) { n += m_layer_nelz(iy) * 3 - 1; } else { n += m_layer_nelz(iy) - 1; } } // allocate node-list array_type::tensor ret = xt::empty({n}); // initialize counter: current index in the node-list "ret" size_t j = 0; // bottom half: bottom node layer (+ middle node layer) for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { // -- bottom node layer for (size_t iz = 1; iz < m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + m_nnd(iy) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } } } // top half: (middle node layer +) top node layer for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { // -- refinement layer if (m_refine(iy) == 2) { for (size_t iz = 0; iz < 2 * m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy) + m_nnd(iy) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } } // -- top node layer for (size_t iz = 1; iz < m_layer_nelz(iy); ++iz) { ret(j) = m_startNode(iy + 1) + iz * (m_layer_nelx(iy) + 1) + m_layer_nelx(iy); ++j; } } return ret; } array_type::tensor nodesBottomFace_impl() const { // allocate node list array_type::tensor ret = xt::empty({(m_layer_nelx(0) - 1) * (m_layer_nelz(0) - 1)}); // counter size_t j = 0; // fill node list for (size_t ix = 1; ix < m_layer_nelx(0); ++ix) { for (size_t iz = 1; iz < m_layer_nelz(0); ++iz) { ret(j) = m_startNode(0) + ix + iz * (m_layer_nelx(0) + 1); ++j; } } return ret; } array_type::tensor nodesTopFace_impl() const { // number of element layers in y-direction size_t nely = static_cast(m_nhy.size()); // allocate node list array_type::tensor ret = xt::empty({(m_layer_nelx(nely - 1) - 1) * (m_layer_nelz(nely - 1) - 1)}); // counter size_t j = 0; // fill node list for (size_t ix = 1; ix < m_layer_nelx(nely - 1); ++ix) { for (size_t iz = 1; iz < m_layer_nelz(nely - 1); ++iz) { ret(j) = m_startNode(nely) + ix + iz * (m_layer_nelx(nely - 1) + 1); ++j; } } return ret; } array_type::tensor nodesFrontBottomEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelx(0) + 1}); for (size_t ix = 0; ix < m_layer_nelx(0) + 1; ++ix) { ret(ix) = m_startNode(0) + ix; } return ret; } array_type::tensor nodesFrontTopEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelx(nely - 1) + 1}); for (size_t ix = 0; ix < m_layer_nelx(nely - 1) + 1; ++ix) { ret(ix) = m_startNode(nely) + ix; } return ret; } array_type::tensor nodesFrontLeftEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely + 1}); for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { ret(iy) = m_startNode(iy); } for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { ret(iy + 1) = m_startNode(iy + 1); } return ret; } array_type::tensor nodesFrontRightEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely + 1}); for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { ret(iy) = m_startNode(iy) + m_layer_nelx(iy); } for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { ret(iy + 1) = m_startNode(iy + 1) + m_layer_nelx(iy); } return ret; } array_type::tensor nodesBackBottomEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelx(0) + 1}); for (size_t ix = 0; ix < m_layer_nelx(0) + 1; ++ix) { ret(ix) = m_startNode(0) + ix + (m_layer_nelx(0) + 1) * (m_layer_nelz(0)); } return ret; } array_type::tensor nodesBackTopEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelx(nely - 1) + 1}); for (size_t ix = 0; ix < m_layer_nelx(nely - 1) + 1; ++ix) { ret(ix) = m_startNode(nely) + ix + (m_layer_nelx(nely - 1) + 1) * (m_layer_nelz(nely - 1)); } return ret; } array_type::tensor nodesBackLeftEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely + 1}); for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { ret(iy) = m_startNode(iy) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { ret(iy + 1) = m_startNode(iy + 1) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } return ret; } array_type::tensor nodesBackRightEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely + 1}); for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { ret(iy) = m_startNode(iy) + m_layer_nelx(iy) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { ret(iy + 1) = m_startNode(iy + 1) + m_layer_nelx(iy) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } return ret; } array_type::tensor nodesBottomLeftEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelz(0) + 1}); for (size_t iz = 0; iz < m_layer_nelz(0) + 1; ++iz) { ret(iz) = m_startNode(0) + iz * (m_layer_nelx(0) + 1); } return ret; } array_type::tensor nodesBottomRightEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelz(0) + 1}); for (size_t iz = 0; iz < m_layer_nelz(0) + 1; ++iz) { ret(iz) = m_startNode(0) + m_layer_nelx(0) + iz * (m_layer_nelx(0) + 1); } return ret; } array_type::tensor nodesTopLeftEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelz(nely - 1) + 1}); for (size_t iz = 0; iz < m_layer_nelz(nely - 1) + 1; ++iz) { ret(iz) = m_startNode(nely) + iz * (m_layer_nelx(nely - 1) + 1); } return ret; } array_type::tensor nodesTopRightEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelz(nely - 1) + 1}); for (size_t iz = 0; iz < m_layer_nelz(nely - 1) + 1; ++iz) { ret(iz) = m_startNode(nely) + m_layer_nelx(nely - 1) + iz * (m_layer_nelx(nely - 1) + 1); } return ret; } array_type::tensor nodesFrontBottomOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelx(0) - 1}); for (size_t ix = 1; ix < m_layer_nelx(0); ++ix) { ret(ix - 1) = m_startNode(0) + ix; } return ret; } array_type::tensor nodesFrontTopOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelx(nely - 1) - 1}); for (size_t ix = 1; ix < m_layer_nelx(nely - 1); ++ix) { ret(ix - 1) = m_startNode(nely) + ix; } return ret; } array_type::tensor nodesFrontLeftOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely - 1}); for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { ret(iy - 1) = m_startNode(iy); } for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { ret(iy) = m_startNode(iy + 1); } return ret; } array_type::tensor nodesFrontRightOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely - 1}); for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { ret(iy - 1) = m_startNode(iy) + m_layer_nelx(iy); } for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { ret(iy) = m_startNode(iy + 1) + m_layer_nelx(iy); } return ret; } array_type::tensor nodesBackBottomOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelx(0) - 1}); for (size_t ix = 1; ix < m_layer_nelx(0); ++ix) { ret(ix - 1) = m_startNode(0) + ix + (m_layer_nelx(0) + 1) * (m_layer_nelz(0)); } return ret; } array_type::tensor nodesBackTopOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelx(nely - 1) - 1}); for (size_t ix = 1; ix < m_layer_nelx(nely - 1); ++ix) { ret(ix - 1) = m_startNode(nely) + ix + (m_layer_nelx(nely - 1) + 1) * (m_layer_nelz(nely - 1)); } return ret; } array_type::tensor nodesBackLeftOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely - 1}); for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { ret(iy - 1) = m_startNode(iy) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { ret(iy) = m_startNode(iy + 1) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } return ret; } array_type::tensor nodesBackRightOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({nely - 1}); for (size_t iy = 1; iy < (nely + 1) / 2; ++iy) { ret(iy - 1) = m_startNode(iy) + m_layer_nelx(iy) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } for (size_t iy = (nely - 1) / 2; iy < nely - 1; ++iy) { ret(iy) = m_startNode(iy + 1) + m_layer_nelx(iy) + (m_layer_nelx(iy) + 1) * (m_layer_nelz(iy)); } return ret; } array_type::tensor nodesBottomLeftOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelz(0) - 1}); for (size_t iz = 1; iz < m_layer_nelz(0); ++iz) { ret(iz - 1) = m_startNode(0) + iz * (m_layer_nelx(0) + 1); } return ret; } array_type::tensor nodesBottomRightOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_layer_nelz(0) - 1}); for (size_t iz = 1; iz < m_layer_nelz(0); ++iz) { ret(iz - 1) = m_startNode(0) + m_layer_nelx(0) + iz * (m_layer_nelx(0) + 1); } return ret; } array_type::tensor nodesTopLeftOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelz(nely - 1) - 1}); for (size_t iz = 1; iz < m_layer_nelz(nely - 1); ++iz) { ret(iz - 1) = m_startNode(nely) + iz * (m_layer_nelx(nely - 1) + 1); } return ret; } array_type::tensor nodesTopRightOpenEdge_impl() const { size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_layer_nelz(nely - 1) - 1}); for (size_t iz = 1; iz < m_layer_nelz(nely - 1); ++iz) { ret(iz - 1) = m_startNode(nely) + m_layer_nelx(nely - 1) + iz * (m_layer_nelx(nely - 1) + 1); } return ret; } size_t nodesFrontBottomLeftCorner_impl() const { return m_startNode(0); } size_t nodesFrontBottomRightCorner_impl() const { return m_startNode(0) + m_layer_nelx(0); } size_t nodesFrontTopLeftCorner_impl() const { size_t nely = static_cast(m_nhy.size()); return m_startNode(nely); } size_t nodesFrontTopRightCorner_impl() const { size_t nely = static_cast(m_nhy.size()); return m_startNode(nely) + m_layer_nelx(nely - 1); } size_t nodesBackBottomLeftCorner_impl() const { return m_startNode(0) + (m_layer_nelx(0) + 1) * (m_layer_nelz(0)); } size_t nodesBackBottomRightCorner_impl() const { return m_startNode(0) + m_layer_nelx(0) + (m_layer_nelx(0) + 1) * (m_layer_nelz(0)); } size_t nodesBackTopLeftCorner_impl() const { size_t nely = static_cast(m_nhy.size()); return m_startNode(nely) + (m_layer_nelx(nely - 1) + 1) * (m_layer_nelz(nely - 1)); } size_t nodesBackTopRightCorner_impl() const { size_t nely = static_cast(m_nhy.size()); return m_startNode(nely) + m_layer_nelx(nely - 1) + (m_layer_nelx(nely - 1) + 1) * (m_layer_nelz(nely - 1)); } double m_h; ///< See h() size_t m_nelx; ///< See nelx() size_t m_nely; ///< See nely() size_t m_nelz; ///< See nely() size_t m_nelem; ///< See nelem() size_t m_nnode; ///< See nnode() size_t m_nne; ///< See nne() size_t m_ndim; ///< See ndim() double m_Lx; ///< mesh size in "x" double m_Lz; ///< mesh size in "z" array_type::tensor m_layer_nelx; ///< number of elem in "x" per element layer in "y" array_type::tensor m_layer_nelz; ///< number of elem in "z" per element layer in "y" array_type::tensor m_nnd; ///< num nodes in the main node layer per node layer in "y" array_type::tensor m_nhx; ///< element size in x-direction per element layer in "y" array_type::tensor m_nhy; ///< element size in y-direction per element layer in "y" array_type::tensor m_nhz; ///< element size in z-direction per element layer in "y" array_type::tensor m_refine; ///< refine direction (-1:no refine, 0:"x", 2:"z") per element layer in "y" array_type::tensor m_startElem; ///< start element per element layer in "y" array_type::tensor m_startNode; ///< start node per node layer in "y" }; } // namespace Hex8 } // namespace Mesh } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MeshQuad4.h b/include/GooseFEM/MeshQuad4.h index 2efbbf5..5ff97ba 100644 --- a/include/GooseFEM/MeshQuad4.h +++ b/include/GooseFEM/MeshQuad4.h @@ -1,1773 +1,1774 @@ /** -Generate simple meshes of 4-noded quadrilateral elements in 2d (GooseFEM::Mesh::ElementType::Quad4). - -\file MeshQuad4.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Generate simple meshes of 4-noded quadrilateral elements in 2d + * (GooseFEM::Mesh::ElementType::Quad4). + * + * @file MeshQuad4.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MESHQUAD4_H #define GOOSEFEM_MESHQUAD4_H #include "Mesh.h" #include "config.h" namespace GooseFEM { namespace Mesh { /** -Simple meshes of 4-noded quadrilateral elements in 2d (ElementType::Quad4). -*/ + * Simple meshes of 4-noded quadrilateral elements in 2d (ElementType::Quad4). + */ namespace Quad4 { // pre-allocation namespace Map { class FineLayer2Regular; } /** -Regular mesh: equi-sized elements. -*/ + * Regular mesh: equi-sized elements. + */ class Regular : public RegularBase2d { public: Regular() = default; /** - Constructor. - - \param nelx Number of elements in horizontal (x) direction. - \param nely Number of elements in vertical (y) direction. - \param h Edge size (width == height). - */ + * Constructor. + * + * @param nelx Number of elements in horizontal (x) direction. + * @param nely Number of elements in vertical (y) direction. + * @param h Edge size (width == height). + */ Regular(size_t nelx, size_t nely, double h = 1.0) { m_h = h; m_nelx = nelx; m_nely = nely; m_ndim = 2; m_nne = 4; GOOSEFEM_ASSERT(m_nelx >= 1); GOOSEFEM_ASSERT(m_nely >= 1); m_nnode = (m_nelx + 1) * (m_nely + 1); m_nelem = m_nelx * m_nely; } /** - Element numbers as 'matrix'. - - \return [#nely, #nelx]. - */ + * Element numbers as 'matrix'. + * + * @return [#nely, #nelx]. + */ array_type::tensor elementgrid() const { return xt::arange(m_nelem).reshape({m_nely, m_nelx}); } private: friend class RegularBase; friend class RegularBase2d; size_t nelx_impl() const { return m_nelx; } size_t nely_impl() const { return m_nely; } ElementType getElementType_impl() const { return ElementType::Quad4; } array_type::tensor coor_impl() const { array_type::tensor ret = xt::empty({m_nnode, m_ndim}); array_type::tensor x = xt::linspace(0.0, m_h * static_cast(m_nelx), m_nelx + 1); array_type::tensor y = xt::linspace(0.0, m_h * static_cast(m_nely), m_nely + 1); size_t inode = 0; for (size_t iy = 0; iy < m_nely + 1; ++iy) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy); ++inode; } } return ret; } array_type::tensor conn_impl() const { array_type::tensor ret = xt::empty({m_nelem, m_nne}); size_t ielem = 0; for (size_t iy = 0; iy < m_nely; ++iy) { for (size_t ix = 0; ix < m_nelx; ++ix) { ret(ielem, 0) = (iy) * (m_nelx + 1) + (ix); ret(ielem, 1) = (iy) * (m_nelx + 1) + (ix + 1); ret(ielem, 3) = (iy + 1) * (m_nelx + 1) + (ix); ret(ielem, 2) = (iy + 1) * (m_nelx + 1) + (ix + 1); ++ielem; } } return ret; } array_type::tensor nodesBottomEdge_impl() const { return xt::arange(m_nelx + 1); } array_type::tensor nodesTopEdge_impl() const { return xt::arange(m_nelx + 1) + m_nely * (m_nelx + 1); } array_type::tensor nodesLeftEdge_impl() const { return xt::arange(m_nely + 1) * (m_nelx + 1); } array_type::tensor nodesRightEdge_impl() const { return xt::arange(m_nely + 1) * (m_nelx + 1) + m_nelx; } array_type::tensor nodesBottomOpenEdge_impl() const { return xt::arange(1, m_nelx); } array_type::tensor nodesTopOpenEdge_impl() const { return xt::arange(1, m_nelx) + m_nely * (m_nelx + 1); } array_type::tensor nodesLeftOpenEdge_impl() const { return xt::arange(1, m_nely) * (m_nelx + 1); } array_type::tensor nodesRightOpenEdge_impl() const { return xt::arange(1, m_nely) * (m_nelx + 1) + m_nelx; } size_t nodesBottomLeftCorner_impl() const { return 0; } size_t nodesBottomRightCorner_impl() const { return m_nelx; } size_t nodesTopLeftCorner_impl() const { return m_nely * (m_nelx + 1); } size_t nodesTopRightCorner_impl() const { return m_nely * (m_nelx + 1) + m_nelx; } double m_h; ///< See h() size_t m_nelx; ///< See nelx() size_t m_nely; ///< See nely() size_t m_nelem; ///< See nelem() size_t m_nnode; ///< See nnode() size_t m_nne; ///< See nne() size_t m_ndim; ///< See ndim() }; /** -Mesh with fine middle layer, and coarser elements towards the top and bottom. -*/ + * Mesh with fine middle layer, and coarser elements towards the top and bottom. + */ class FineLayer : public RegularBase2d { public: FineLayer() = default; /** - Constructor. - - \param nelx Number of elements (along the middle layer) in horizontal (x) direction. - \param nely Approximate equivalent number of elements in vertical (y) direction. - \param h Edge size (width == height) of elements along the weak layer. - - \param nfine - Extra number of fine layers around the middle layer. - By default the element size is kept smaller than the distance to the middle layer. - */ + * Constructor. + * + * @param nelx Number of elements (along the middle layer) in horizontal (x) direction. + * @param nely Approximate equivalent number of elements in vertical (y) direction. + * @param h Edge size (width == height) of elements along the weak layer. + * + * @param nfine + * Extra number of fine layers around the middle layer. + * By default the element size is kept smaller than the distance to the middle layer. + */ FineLayer(size_t nelx, size_t nely, double h = 1.0, size_t nfine = 1) { this->init(nelx, nely, h, nfine); } /** - Reconstruct class for given coordinates / connectivity. - - \tparam C e.g. `array_type::tensor` - \tparam E e.g. `array_type::tensor` - \param coor Nodal coordinates ``[nnode, ndim]`` with ``ndim == 2``. - \param conn Connectivity ``[nne, nne]`` with ``nne == 4``. - */ + * Reconstruct class for given coordinates / connectivity. + * + * @tparam C e.g. `array_type::tensor` + * @tparam E e.g. `array_type::tensor` + * @param coor Nodal coordinates ``[nnode, ndim]`` with ``ndim == 2``. + * @param conn Connectivity ``[nne, nne]`` with ``nne == 4``. + */ template ::value, bool> = true> FineLayer(const C& coor, const E& conn) { this->init_by_mapping(coor, conn); } /** - Edge size in x-direction of a block, in units of #h, per row of blocks. - Note that a block is equal to an element except in refinement layers - where it contains three elements. - - \return List of size equal to the number of rows of blocks. - */ + * Edge size in x-direction of a block, in units of #h, per row of blocks. + * Note that a block is equal to an element except in refinement layers + * where it contains three elements. + * + * @return List of size equal to the number of rows of blocks. + */ const array_type::tensor& elemrow_nhx() const { return m_nhx; } /** - Edge size in y-direction of a block, in units of #h, per row of blocks. - Note that a block is equal to an element except in refinement layers - where it contains three elements. - - \return List of size equal to the number of rows of blocks. - */ + * Edge size in y-direction of a block, in units of #h, per row of blocks. + * Note that a block is equal to an element except in refinement layers + * where it contains three elements. + * + * @return List of size equal to the number of rows of blocks. + */ const array_type::tensor& elemrow_nhy() const { return m_nhy; } /** - Per row of blocks: - * `-1`: normal layer - * `0`: transition layer to match coarse and finer element on the previous/next row. - - \return List of size equal to the number of rows of blocks. - */ + * Per row of blocks: + * `-1`: normal layer + * `0`: transition layer to match coarse and finer element on the previous/next row. + * + * @return List of size equal to the number of rows of blocks. + */ const array_type::tensor& elemrow_type() const { return m_refine; } /** - Number of elements per row of blocks. - Note that a block is equal to an element except in refinement layers - where it contains three elements. - - \return List of size equal to the number of rows of blocks. - */ + * Number of elements per row of blocks. + * Note that a block is equal to an element except in refinement layers + * where it contains three elements. + * + * @return List of size equal to the number of rows of blocks. + */ const array_type::tensor& elemrow_nelem() const { return m_layer_nelx; } /** - Elements in the middle (fine) layer. - - \return List of element numbers. - */ + * Elements in the middle (fine) layer. + * + * @return List of element numbers. + */ array_type::tensor elementsMiddleLayer() const { size_t nely = m_nhy.size(); size_t iy = (nely - 1) / 2; return m_startElem(iy) + xt::arange(m_layer_nelx(iy)); } /** - Elements along a layer. - - \return List of element numbers. - */ + * Elements along a layer. + * + * @return List of element numbers. + */ array_type::tensor elementsLayer(size_t layer) const { GOOSEFEM_ASSERT(layer < m_layer_nelx.size()); size_t n = m_layer_nelx(layer); if (m_refine(layer) != -1) { n *= 4; } return m_startElem(layer) + xt::arange(n); } /** - Select region of elements from 'matrix' of element numbers. - - \return List of element numbers. - */ + * Select region of elements from 'matrix' of element numbers. + * + * @return List of element numbers. + */ array_type::tensor elementgrid_ravel( std::vector start_stop_rows, std::vector start_stop_cols) const { GOOSEFEM_ASSERT(start_stop_rows.size() == 0 || start_stop_rows.size() == 2); GOOSEFEM_ASSERT(start_stop_cols.size() == 0 || start_stop_cols.size() == 2); std::array rows; std::array cols; if (start_stop_rows.size() == 2) { std::copy(start_stop_rows.begin(), start_stop_rows.end(), rows.begin()); GOOSEFEM_ASSERT(rows[0] <= this->nely()); GOOSEFEM_ASSERT(rows[1] <= this->nely()); } else { rows[0] = 0; rows[1] = this->nely(); } if (start_stop_cols.size() == 2) { std::copy(start_stop_cols.begin(), start_stop_cols.end(), cols.begin()); GOOSEFEM_ASSERT(cols[0] <= this->nelx()); GOOSEFEM_ASSERT(cols[1] <= this->nelx()); } else { cols[0] = 0; cols[1] = this->nelx(); } if (rows[0] == rows[1] || cols[0] == cols[1]) { array_type::tensor ret = xt::empty({0}); return ret; } // Compute dimensions auto H = xt::cumsum(m_nhy); size_t yl = 0; if (rows[0] > 0) { yl = xt::argmax(H > rows[0])(); } size_t yu = xt::argmax(H >= rows[1])(); size_t hx = std::max(m_nhx(yl), m_nhx(yu)); size_t xl = (cols[0] - cols[0] % hx) / hx; size_t xu = (cols[1] - cols[1] % hx) / hx; // Allocate output size_t N = 0; for (size_t i = yl; i <= yu; ++i) { // no refinement size_t n = (xu - xl) * hx / m_nhx(i); // refinement if (m_refine(i) != -1) { n *= 4; } N += n; } array_type::tensor ret = xt::empty({N}); // Write output N = 0; for (size_t i = yl; i <= yu; ++i) { // no refinement size_t n = (xu - xl) * hx / m_nhx(i); size_t h = hx; // refinement if (m_refine(i) != -1) { n *= 4; h *= 4; } array_type::tensor e = m_startElem(i) + xl * h / m_nhx(i) + xt::arange(n); xt::view(ret, xt::range(N, N + n)) = e; N += n; } return ret; } /** - Select region of elements from 'matrix' of element numbers around an element: - square box with edge-size ``(2 * size + 1) * h``, around ``element``. - - \param e The element around which to select elements. - \param size Edge size of the square box encapsulating the selected element. - \param periodic Assume the mesh periodic. - \return List of elements. - */ + * Select region of elements from 'matrix' of element numbers around an element: + * square box with edge-size ``(2 * size + 1) * h``, around ``element``. + * + * @param e The element around which to select elements. + * @param size Edge size of the square box encapsulating the selected element. + * @param periodic Assume the mesh periodic. + * @return List of elements. + */ array_type::tensor elementgrid_around_ravel(size_t e, size_t size, bool periodic = true) { GOOSEFEM_WIP_ASSERT(periodic == true); size_t iy = xt::argmin(m_startElem <= e)() - 1; size_t nel = m_layer_nelx(iy); GOOSEFEM_WIP_ASSERT(iy == (m_nhy.size() - 1) / 2); auto H = xt::cumsum(m_nhy); if (2 * size >= H(H.size() - 1)) { return xt::arange(this->nelem()); } size_t hy = H(iy); size_t l = xt::argmax(H > (hy - size - 1))(); size_t u = xt::argmax(H >= (hy + size))(); size_t lh = 0; if (l > 0) { lh = H(l - 1); } size_t uh = H(u); size_t step = xt::amax(m_nhx)(); size_t relx = (e - m_startElem(iy)) % step; size_t mid = (nel / step - (nel / step) % 2) / 2 * step + relx; size_t nroll = (nel - (nel - mid + e - m_startElem(iy)) % nel) / step; size_t dx = m_nhx(u); size_t xl = 0; size_t xu = nel; if (mid >= size) { xl = mid - size; } if (mid + size < nel) { xu = mid + size + 1; } xl = xl - xl % dx; xu = xu - xu % dx; if (mid - xl < size) { if (xl < dx) { xl = 0; } else { xl -= dx; } } if (xu - mid < size) { if (xu > nel - dx) { xu = nel; } else { xu += dx; } } auto ret = this->elementgrid_ravel({lh, uh}, {xl, xu}); auto map = this->roll(nroll); return xt::view(map, xt::keep(ret)); } /** - Select region of elements from 'matrix' of element numbers around an element: - left/right from ``element`` (on the same layer). - - \param e The element around which to select elements. - \param left Number of elements to select to the left. - \param right Number of elements to select to the right. - \param periodic Assume the mesh periodic. - \return List of elements. - */ + * Select region of elements from 'matrix' of element numbers around an element: + * left/right from ``element`` (on the same layer). + * + * @param e The element around which to select elements. + * @param left Number of elements to select to the left. + * @param right Number of elements to select to the right. + * @param periodic Assume the mesh periodic. + * @return List of elements. + */ // - array_type::tensor elementgrid_leftright(size_t e, size_t left, size_t right, bool periodic = true) { GOOSEFEM_WIP_ASSERT(periodic == true); size_t iy = xt::argmin(m_startElem <= e)() - 1; size_t nel = m_layer_nelx(iy); GOOSEFEM_WIP_ASSERT(iy == (m_nhy.size() - 1) / 2); size_t step = xt::amax(m_nhx)(); size_t relx = (e - m_startElem(iy)) % step; size_t mid = (nel / step - (nel / step) % 2) / 2 * step + relx; size_t nroll = (nel - (nel - mid + e - m_startElem(iy)) % nel) / step; size_t dx = m_nhx(iy); size_t xl = 0; size_t xu = nel; if (mid >= left) { xl = mid - left; } if (mid + right < nel) { xu = mid + right + 1; } xl = xl - xl % dx; xu = xu - xu % dx; if (mid - xl < left) { if (xl < dx) { xl = 0; } else { xl -= dx; } } if (xu - mid < right) { if (xu > nel - dx) { xu = nel; } else { xu += dx; } } auto H = xt::cumsum(m_nhy); size_t yl = 0; if (iy > 0) { yl = H(iy - 1); } auto ret = this->elementgrid_ravel({yl, H(iy)}, {xl, xu}); auto map = this->roll(nroll); return xt::view(map, xt::keep(ret)); } /** - Mapping to 'roll' periodically in the x-direction, - - \return element mapping, such that: new_elemvar = elemvar[elem_map] - */ + * Mapping to 'roll' periodically in the x-direction, + * + * @return element mapping, such that: new_elemvar = elemvar[elem_map] + */ array_type::tensor roll(size_t n) { auto conn = this->conn(); size_t nely = static_cast(m_nhy.size()); array_type::tensor ret = xt::empty({m_nelem}); // loop over all element layers for (size_t iy = 0; iy < nely; ++iy) { // no refinement size_t shift = n * (m_layer_nelx(iy) / m_layer_nelx(0)); size_t nel = m_layer_nelx(iy); // refinement if (m_refine(iy) != -1) { shift = n * (m_layer_nelx(iy) / m_layer_nelx(0)) * 4; nel = m_layer_nelx(iy) * 4; } // element numbers of the layer, and roll them auto e = m_startElem(iy) + xt::arange(nel); xt::view(ret, xt::range(m_startElem(iy), m_startElem(iy) + nel)) = xt::roll(e, shift); } return ret; } private: friend class RegularBase; friend class RegularBase2d; friend class GooseFEM::Mesh::Quad4::Map::FineLayer2Regular; size_t nelx_impl() const { return xt::amax(m_layer_nelx)(); } size_t nely_impl() const { return xt::sum(m_nhy)(); } ElementType getElementType_impl() const { return ElementType::Quad4; } array_type::tensor coor_impl() const { // allocate output array_type::tensor ret = xt::empty({m_nnode, m_ndim}); // current node, number of element layers size_t inode = 0; size_t nely = static_cast(m_nhy.size()); // y-position of each main node layer (i.e. excluding node layers for refinement/coarsening) // - allocate array_type::tensor y = xt::empty({nely + 1}); // - initialize y(0) = 0.0; // - compute for (size_t iy = 1; iy < nely + 1; ++iy) { y(iy) = y(iy - 1) + m_nhy(iy - 1) * m_h; } // loop over element layers (bottom -> middle) : add bottom layer (+ refinement layer) of // nodes for (size_t iy = 0;; ++iy) { // get positions along the x- and z-axis array_type::tensor x = xt::linspace(0.0, m_Lx, m_layer_nelx(iy) + 1); // add nodes of the bottom layer of this element for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy); ++inode; } // stop at middle layer if (iy == (nely - 1) / 2) { break; } // add extra nodes of the intermediate layer, for refinement in x-direction if (m_refine(iy) == 0) { // - get position offset in x- and y-direction double dx = m_h * static_cast(m_nhx(iy) / 3); double dy = m_h * static_cast(m_nhy(iy) / 2); // - add nodes of the intermediate layer for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { for (size_t j = 0; j < 2; ++j) { ret(inode, 0) = x(ix) + dx * static_cast(j + 1); ret(inode, 1) = y(iy) + dy; ++inode; } } } } // loop over element layers (middle -> top) : add (refinement layer +) top layer of nodes for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { // get positions along the x- and z-axis array_type::tensor x = xt::linspace(0.0, m_Lx, m_layer_nelx(iy) + 1); // add extra nodes of the intermediate layer, for refinement in x-direction if (m_refine(iy) == 0) { // - get position offset in x- and y-direction double dx = m_h * static_cast(m_nhx(iy) / 3); double dy = m_h * static_cast(m_nhy(iy) / 2); // - add nodes of the intermediate layer for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { for (size_t j = 0; j < 2; ++j) { ret(inode, 0) = x(ix) + dx * static_cast(j + 1); ret(inode, 1) = y(iy) + dy; ++inode; } } } // add nodes of the top layer of this element for (size_t ix = 0; ix < m_layer_nelx(iy) + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy + 1); ++inode; } } return ret; } array_type::tensor conn_impl() const { // allocate output array_type::tensor ret = xt::empty({m_nelem, m_nne}); // current element, number of element layers, starting nodes of each node layer size_t ielem = 0; size_t nely = static_cast(m_nhy.size()); size_t bot, mid, top; // loop over all element layers for (size_t iy = 0; iy < nely; ++iy) { // - get: starting nodes of bottom(, middle) and top layer bot = m_startNode(iy); mid = m_startNode(iy) + m_nnd(iy); top = m_startNode(iy + 1); // - define connectivity: no coarsening/refinement if (m_refine(iy) == -1) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { ret(ielem, 0) = bot + (ix); ret(ielem, 1) = bot + (ix + 1); ret(ielem, 2) = top + (ix + 1); ret(ielem, 3) = top + (ix); ielem++; } } // - define connectivity: refinement along the x-direction (below the middle layer) else if (m_refine(iy) == 0 && iy <= (nely - 1) / 2) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { // -- bottom element ret(ielem, 0) = bot + (ix); ret(ielem, 1) = bot + (ix + 1); ret(ielem, 2) = mid + (2 * ix + 1); ret(ielem, 3) = mid + (2 * ix); ielem++; // -- top-right element ret(ielem, 0) = bot + (ix + 1); ret(ielem, 1) = top + (3 * ix + 3); ret(ielem, 2) = top + (3 * ix + 2); ret(ielem, 3) = mid + (2 * ix + 1); ielem++; // -- top-center element ret(ielem, 0) = mid + (2 * ix); ret(ielem, 1) = mid + (2 * ix + 1); ret(ielem, 2) = top + (3 * ix + 2); ret(ielem, 3) = top + (3 * ix + 1); ielem++; // -- top-left element ret(ielem, 0) = bot + (ix); ret(ielem, 1) = mid + (2 * ix); ret(ielem, 2) = top + (3 * ix + 1); ret(ielem, 3) = top + (3 * ix); ielem++; } } // - define connectivity: coarsening along the x-direction (above the middle layer) else if (m_refine(iy) == 0 && iy > (nely - 1) / 2) { for (size_t ix = 0; ix < m_layer_nelx(iy); ++ix) { // -- lower-left element ret(ielem, 0) = bot + (3 * ix); ret(ielem, 1) = bot + (3 * ix + 1); ret(ielem, 2) = mid + (2 * ix); ret(ielem, 3) = top + (ix); ielem++; // -- lower-center element ret(ielem, 0) = bot + (3 * ix + 1); ret(ielem, 1) = bot + (3 * ix + 2); ret(ielem, 2) = mid + (2 * ix + 1); ret(ielem, 3) = mid + (2 * ix); ielem++; // -- lower-right element ret(ielem, 0) = bot + (3 * ix + 2); ret(ielem, 1) = bot + (3 * ix + 3); ret(ielem, 2) = top + (ix + 1); ret(ielem, 3) = mid + (2 * ix + 1); ielem++; // -- upper element ret(ielem, 0) = mid + (2 * ix); ret(ielem, 1) = mid + (2 * ix + 1); ret(ielem, 2) = top + (ix + 1); ret(ielem, 3) = top + (ix); ielem++; } } } return ret; } array_type::tensor nodesBottomEdge_impl() const { return m_startNode(0) + xt::arange(m_layer_nelx(0) + 1); } array_type::tensor nodesTopEdge_impl() const { size_t nely = m_nhy.size(); return m_startNode(nely) + xt::arange(m_layer_nelx(nely - 1) + 1); } array_type::tensor nodesLeftEdge_impl() const { size_t nely = m_nhy.size(); array_type::tensor ret = xt::empty({nely + 1}); size_t i = 0; size_t j = (nely + 1) / 2; size_t k = (nely - 1) / 2; size_t l = nely; xt::view(ret, xt::range(i, j)) = xt::view(m_startNode, xt::range(i, j)); xt::view(ret, xt::range(k + 1, l + 1)) = xt::view(m_startNode, xt::range(k + 1, l + 1)); return ret; } array_type::tensor nodesRightEdge_impl() const { size_t nely = m_nhy.size(); array_type::tensor ret = xt::empty({nely + 1}); size_t i = 0; size_t j = (nely + 1) / 2; size_t k = (nely - 1) / 2; size_t l = nely; xt::view(ret, xt::range(i, j)) = xt::view(m_startNode, xt::range(i, j)) + xt::view(m_layer_nelx, xt::range(i, j)); xt::view(ret, xt::range(k + 1, l + 1)) = xt::view(m_startNode, xt::range(k + 1, l + 1)) + xt::view(m_layer_nelx, xt::range(k, l)); return ret; } array_type::tensor nodesBottomOpenEdge_impl() const { return m_startNode(0) + xt::arange(1, m_layer_nelx(0)); } array_type::tensor nodesTopOpenEdge_impl() const { size_t nely = m_nhy.size(); return m_startNode(nely) + xt::arange(1, m_layer_nelx(nely - 1)); } array_type::tensor nodesLeftOpenEdge_impl() const { size_t nely = m_nhy.size(); array_type::tensor ret = xt::empty({nely - 1}); size_t i = 0; size_t j = (nely + 1) / 2; size_t k = (nely - 1) / 2; size_t l = nely; xt::view(ret, xt::range(i, j - 1)) = xt::view(m_startNode, xt::range(i + 1, j)); xt::view(ret, xt::range(k, l - 1)) = xt::view(m_startNode, xt::range(k + 1, l)); return ret; } array_type::tensor nodesRightOpenEdge_impl() const { size_t nely = m_nhy.size(); array_type::tensor ret = xt::empty({nely - 1}); size_t i = 0; size_t j = (nely + 1) / 2; size_t k = (nely - 1) / 2; size_t l = nely; xt::view(ret, xt::range(i, j - 1)) = xt::view(m_startNode, xt::range(i + 1, j)) + xt::view(m_layer_nelx, xt::range(i + 1, j)); xt::view(ret, xt::range(k, l - 1)) = xt::view(m_startNode, xt::range(k + 1, l)) + xt::view(m_layer_nelx, xt::range(k, l - 1)); return ret; } size_t nodesBottomLeftCorner_impl() const { return m_startNode(0); } size_t nodesBottomRightCorner_impl() const { return m_startNode(0) + m_layer_nelx(0); } size_t nodesTopLeftCorner_impl() const { size_t nely = m_nhy.size(); return m_startNode(nely); } size_t nodesTopRightCorner_impl() const { size_t nely = m_nhy.size(); return m_startNode(nely) + m_layer_nelx(nely - 1); } double m_h; ///< See h() size_t m_nelem; ///< See nelem() size_t m_nnode; ///< See nnode() size_t m_nne; ///< See nne() size_t m_ndim; ///< See ndim() double m_Lx; ///< Mesh size in x-direction. array_type::tensor m_layer_nelx; ///< See elemrow_nelem(). array_type::tensor m_nhx; ///< See elemrow_nhx(). array_type::tensor m_nhy; ///< See elemrow_nhy(). array_type::tensor m_nnd; ///< num. nodes in main node layer (per node layer in "y") array_type::tensor m_refine; ///< See elemrow_type(). array_type::tensor m_startElem; ///< start element (per element layer in "y") array_type::tensor m_startNode; ///< start node (per node layer in "y") /** - \copydoc FineLayer::FineLayer(size_t, size_t, double, size_t) - */ + * @copydoc FineLayer::FineLayer(size_t, size_t, double, size_t) + */ void init(size_t nelx, size_t nely, double h, size_t nfine = 1) { GOOSEFEM_ASSERT(nelx >= 1ul); GOOSEFEM_ASSERT(nely >= 1ul); m_h = h; m_ndim = 2; m_nne = 4; m_Lx = m_h * static_cast(nelx); // compute element size in y-direction (use symmetry, compute upper half) // temporary variables size_t nmin, ntot; array_type::tensor nhx = xt::ones({nely}); array_type::tensor nhy = xt::ones({nely}); array_type::tensor refine = -1 * xt::ones({nely}); // minimum height in y-direction (half of the height because of symmetry) if (nely % 2 == 0) { nmin = nely / 2; } else { nmin = (nely + 1) / 2; } // minimum number of fine layers in y-direction (minimum 1, middle layer part of this half) if (nfine % 2 == 0) { nfine = nfine / 2 + 1; } else { nfine = (nfine + 1) / 2; } if (nfine < 1) { nfine = 1; } if (nfine > nmin) { nfine = nmin; } // loop over element layers in y-direction, try to coarsen using these rules: // (1) element size in y-direction <= distance to origin in y-direction // (2) element size in x-direction should fit the total number of elements in x-direction // (3) a certain number of layers have the minimum size "1" (are fine) for (size_t iy = nfine;;) { // initialize current size in y-direction if (iy == nfine) { ntot = nfine; } // check to stop if (iy >= nely || ntot >= nmin) { nely = iy; break; } // rules (1,2) satisfied: coarsen in x-direction if (3 * nhy(iy) <= ntot && nelx % (3 * nhx(iy)) == 0 && ntot + nhy(iy) < nmin) { refine(iy) = 0; nhy(iy) *= 2; auto vnhy = xt::view(nhy, xt::range(iy + 1, _)); auto vnhx = xt::view(nhx, xt::range(iy, _)); vnhy *= 3; vnhx *= 3; } // update the number of elements in y-direction ntot += nhy(iy); // proceed to next element layer in y-direction ++iy; // check to stop if (iy >= nely || ntot >= nmin) { nely = iy; break; } } // symmetrize, compute full information // allocate mesh constructor parameters m_nhx = xt::empty({nely * 2 - 1}); m_nhy = xt::empty({nely * 2 - 1}); m_refine = xt::empty({nely * 2 - 1}); m_layer_nelx = xt::empty({nely * 2 - 1}); m_nnd = xt::empty({nely * 2}); m_startElem = xt::empty({nely * 2 - 1}); m_startNode = xt::empty({nely * 2}); // fill // - lower half for (size_t iy = 0; iy < nely; ++iy) { m_nhx(iy) = nhx(nely - iy - 1); m_nhy(iy) = nhy(nely - iy - 1); m_refine(iy) = refine(nely - iy - 1); } // - upper half for (size_t iy = 0; iy < nely - 1; ++iy) { m_nhx(iy + nely) = nhx(iy + 1); m_nhy(iy + nely) = nhy(iy + 1); m_refine(iy + nely) = refine(iy + 1); } // update size nely = m_nhx.size(); // compute the number of elements per element layer in y-direction for (size_t iy = 0; iy < nely; ++iy) { m_layer_nelx(iy) = nelx / m_nhx(iy); } // compute the number of nodes per node layer in y-direction for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) { m_nnd(iy) = m_layer_nelx(iy) + 1; } for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) { m_nnd(iy + 1) = m_layer_nelx(iy) + 1; } // compute mesh dimensions // initialize m_nnode = 0; m_nelem = 0; m_startNode(0) = 0; // loop over element layers (bottom -> middle, elements become finer) for (size_t i = 0; i < (nely - 1) / 2; ++i) { // - store the first element of the layer m_startElem(i) = m_nelem; // - add the nodes of this layer if (m_refine(i) == 0) { m_nnode += (3 * m_layer_nelx(i) + 1); } else { m_nnode += (m_layer_nelx(i) + 1); } // - add the elements of this layer if (m_refine(i) == 0) { m_nelem += (4 * m_layer_nelx(i)); } else { m_nelem += (m_layer_nelx(i)); } // - store the starting node of the next layer m_startNode(i + 1) = m_nnode; } // loop over element layers (middle -> top, elements become coarser) for (size_t i = (nely - 1) / 2; i < nely; ++i) { // - store the first element of the layer m_startElem(i) = m_nelem; // - add the nodes of this layer if (m_refine(i) == 0) { m_nnode += (5 * m_layer_nelx(i) + 1); } else { m_nnode += (m_layer_nelx(i) + 1); } // - add the elements of this layer if (m_refine(i) == 0) { m_nelem += (4 * m_layer_nelx(i)); } else { m_nelem += (m_layer_nelx(i)); } // - store the starting node of the next layer m_startNode(i + 1) = m_nnode; } // - add the top row of nodes m_nnode += m_layer_nelx(nely - 1) + 1; } /** - \copydoc FineLayer::FineLayer(const C&, const E&) - */ + * @copydoc FineLayer::FineLayer(const C&, const E&) + */ template void init_by_mapping(const C& coor, const E& conn) { GOOSEFEM_ASSERT(coor.dimension() == 2); GOOSEFEM_ASSERT(conn.dimension() == 2); GOOSEFEM_ASSERT(coor.shape(1) == 2); GOOSEFEM_ASSERT(conn.shape(1) == 4); GOOSEFEM_ASSERT(conn.shape(0) > 0); GOOSEFEM_ASSERT(coor.shape(0) >= 4); GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0)); if (conn.shape(0) == 1) { this->init(1, 1, coor(conn(0, 1), 0) - coor(conn(0, 0), 0)); GOOSEFEM_CHECK(xt::all(xt::equal(this->conn(), conn))); GOOSEFEM_CHECK(xt::allclose(this->coor(), coor)); return; } // Identify the middle layer size_t emid = (conn.shape(0) - conn.shape(0) % 2) / 2; size_t eleft = emid; size_t eright = emid; while (conn(eleft, 0) == conn(eleft - 1, 1) && eleft > 0) { eleft--; } while (conn(eright, 1) == conn(eright + 1, 0) && eright < conn.shape(0) - 1) { eright++; } GOOSEFEM_CHECK(xt::allclose(coor(conn(eleft, 0), 0), 0.0)); // Get element sizes along the middle layer auto n0 = xt::view(conn, xt::range(eleft, eright + 1), 0); auto n1 = xt::view(conn, xt::range(eleft, eright + 1), 1); auto n2 = xt::view(conn, xt::range(eleft, eright + 1), 2); auto dx = xt::view(coor, xt::keep(n1), 0) - xt::view(coor, xt::keep(n0), 0); auto dy = xt::view(coor, xt::keep(n2), 1) - xt::view(coor, xt::keep(n1), 1); auto hx = xt::amin(dx)(); auto hy = xt::amin(dy)(); GOOSEFEM_CHECK(xt::allclose(hx, hy)); GOOSEFEM_CHECK(xt::allclose(dx, hx)); GOOSEFEM_CHECK(xt::allclose(dy, hy)); // Extract shape and initialise size_t nelx = eright - eleft + 1; size_t nely = static_cast((coor(coor.shape(0) - 1, 1) - coor(0, 1)) / hx); this->init(nelx, nely, hx); GOOSEFEM_CHECK(xt::all(xt::equal(this->conn(), conn))); GOOSEFEM_CHECK(xt::allclose(this->coor(), coor)); GOOSEFEM_CHECK( xt::all(xt::equal(this->elementsMiddleLayer(), eleft + xt::arange(nelx)))); } }; /** -Mesh mappings. -*/ + * Mesh mappings. + */ namespace Map { /** -Refine a Regular mesh: subdivide elements in several smaller elements. -*/ + * Refine a Regular mesh: subdivide elements in several smaller elements. + */ class RefineRegular { public: RefineRegular() = default; /** - Constructor. - - \param mesh the coarse mesh. - \param nx for each coarse element: number of fine elements in x-direction. - \param ny for each coarse element: number of fine elements in y-direction. - */ + * Constructor. + * + * @param mesh the coarse mesh. + * @param nx for each coarse element: number of fine elements in x-direction. + * @param ny for each coarse element: number of fine elements in y-direction. + */ RefineRegular(const GooseFEM::Mesh::Quad4::Regular& mesh, size_t nx, size_t ny) : m_coarse(mesh), m_nx(nx), m_ny(ny) { m_fine = Regular(nx * m_coarse.nelx(), ny * m_coarse.nely(), m_coarse.h()); array_type::tensor elmat_coarse = m_coarse.elementgrid(); array_type::tensor elmat_fine = m_fine.elementgrid(); m_coarse2fine = xt::empty({m_coarse.nelem(), nx * ny}); for (size_t i = 0; i < elmat_coarse.shape(0); ++i) { for (size_t j = 0; j < elmat_coarse.shape(1); ++j) { xt::view(m_coarse2fine, elmat_coarse(i, j), xt::all()) = xt::flatten(xt::view( elmat_fine, xt::range(i * ny, (i + 1) * ny), xt::range(j * nx, (j + 1) * nx))); } } } /** - For each coarse element: number of fine elements in x-direction. - - \return unsigned int (same as used in constructor) - */ + * For each coarse element: number of fine elements in x-direction. + * + * @return unsigned int (same as used in constructor) + */ size_t nx() const { return m_nx; } /** - For each coarse element: number of fine elements in y-direction. - - \return unsigned int (same as used in constructor) - */ + * For each coarse element: number of fine elements in y-direction. + * + * @return unsigned int (same as used in constructor) + */ size_t ny() const { return m_ny; } /** - Obtain the coarse mesh (copy of the mesh passed to the constructor). - \return mesh - */ + * Obtain the coarse mesh (copy of the mesh passed to the constructor). + * @return mesh + */ GooseFEM::Mesh::Quad4::Regular coarseMesh() const { return m_coarse; } /** - Obtain the fine mesh. - \return mesh - */ + * Obtain the fine mesh. + * @return mesh + */ GooseFEM::Mesh::Quad4::Regular fineMesh() const { return m_fine; } /** - Get element-mapping: elements of the fine mesh per element of the coarse mesh. - \return [nelem_coarse, nx() * ny()] - */ + * Get element-mapping: elements of the fine mesh per element of the coarse mesh. + * @return [nelem_coarse, nx() * ny()] + */ const array_type::tensor& map() const { return m_coarse2fine; } /** - Obtain the coarse mesh (copy of the mesh passed to the constructor). - \return mesh - */ + * Obtain the coarse mesh (copy of the mesh passed to the constructor). + * @return mesh + */ [[deprecated]] GooseFEM::Mesh::Quad4::Regular getCoarseMesh() const { return m_coarse; } /** - Obtain the fine mesh. - \return mesh - */ + * Obtain the fine mesh. + * @return mesh + */ [[deprecated]] GooseFEM::Mesh::Quad4::Regular getFineMesh() const { return m_fine; } /** - Get element-mapping: elements of the fine mesh per element of the coarse mesh. - \return [nelem_coarse, nx() * ny()] - */ + * Get element-mapping: elements of the fine mesh per element of the coarse mesh. + * @return [nelem_coarse, nx() * ny()] + */ [[deprecated]] const array_type::tensor& getMap() const { return m_coarse2fine; } /** - Compute the mean of the quantity define on the fine mesh when mapped on the coarse mesh. - - \tparam T type of the data (e.g. ``double``). - \tparam rank rank of the data. - \param data the data [nelem_fine, ...] - \return the average data of the coarse mesh [nelem_coarse, ...] - */ + * Compute the mean of the quantity define on the fine mesh when mapped on the coarse mesh. + * + * @tparam T type of the data (e.g. ``double``). + * @tparam rank rank of the data. + * @param data the data [nelem_fine, ...] + * @return the average data of the coarse mesh [nelem_coarse, ...] + */ template array_type::tensor meanToCoarse(const array_type::tensor& data) const { GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.size()); std::array shape; std::copy(data.shape().cbegin(), data.shape().cend(), &shape[0]); shape[0] = m_coarse2fine.shape(0); array_type::tensor ret = xt::empty(shape); for (size_t i = 0; i < m_coarse2fine.shape(0); ++i) { auto e = xt::view(m_coarse2fine, i, xt::all()); auto d = xt::view(data, xt::keep(e)); xt::view(ret, i) = xt::mean(d, 0); } return ret; } /** - Compute the average of the quantity define on the fine mesh when mapped on the coarse mesh. - - \tparam T type of the data (e.g. ``double``). - \tparam rank rank of the data. - \tparam S type of the weights (e.g. ``double``). - \param data the data [nelem_fine, ...] - \param weights the weights [nelem_fine, ...] - \return the average data of the coarse mesh [nelem_coarse, ...] - */ + * Compute the average of the quantity define on the fine mesh when mapped on the coarse mesh. + * + * @tparam T type of the data (e.g. ``double``). + * @tparam rank rank of the data. + * @tparam S type of the weights (e.g. ``double``). + * @param data the data [nelem_fine, ...] + * @param weights the weights [nelem_fine, ...] + * @return the average data of the coarse mesh [nelem_coarse, ...] + */ template array_type::tensor averageToCoarse( const array_type::tensor& data, const array_type::tensor& weights) const { GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.size()); std::array shape; std::copy(data.shape().cbegin(), data.shape().cend(), &shape[0]); shape[0] = m_coarse2fine.shape(0); array_type::tensor ret = xt::empty(shape); for (size_t i = 0; i < m_coarse2fine.shape(0); ++i) { auto e = xt::view(m_coarse2fine, i, xt::all()); array_type::tensor d = xt::view(data, xt::keep(e)); array_type::tensor w = xt::view(weights, xt::keep(e)); xt::view(ret, i) = xt::average(d, w, {0}); } return ret; } /** - Map element quantities to the fine mesh. - The mapping is a bit simplistic: no interpolation is involved. - The mapping is such that:: - - ret[e_fine, ...] <- data[e_coarse, ...] - - \tparam T type of the data (e.g. ``double``). - \tparam rank rank of the data. - \param data the data. - \return mapped data. - */ + * Map element quantities to the fine mesh. + * The mapping is a bit simplistic: no interpolation is involved. + * The mapping is such that:: + * + * ret[e_fine, ...] <- data[e_coarse, ...] + * + * @tparam T type of the data (e.g. ``double``). + * @tparam rank rank of the data. + * @param data the data. + * @return mapped data. + */ template array_type::tensor mapToFine(const array_type::tensor& data) const { GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.shape(0)); std::array shape; std::copy(data.shape().cbegin(), data.shape().cend(), &shape[0]); shape[0] = m_coarse2fine.size(); array_type::tensor ret = xt::empty(shape); for (size_t e = 0; e < m_coarse2fine.shape(0); ++e) { for (size_t i = 0; i < m_coarse2fine.shape(1); ++i) { xt::view(ret, m_coarse2fine(e, i)) = xt::view(data, e); } } return ret; } private: GooseFEM::Mesh::Quad4::Regular m_coarse; ///< the coarse mesh GooseFEM::Mesh::Quad4::Regular m_fine; ///< the fine mesh size_t m_nx; ///< see nx() size_t m_ny; ///< see ny() array_type::tensor m_coarse2fine; ///< see getMap() }; /** -Map a FineLayer mesh to a Regular mesh. -The element size of the Regular corresponds to the smallest elements of the FineLayer mesh -(along the middle layer). -*/ + * Map a FineLayer mesh to a Regular mesh. + * The element size of the Regular corresponds to the smallest elements of the FineLayer mesh + * (along the middle layer). + */ class FineLayer2Regular { public: FineLayer2Regular() = default; /** - Constructors. - - \param mesh The FineLayer mesh. - */ + * Constructors. + * + * @param mesh The FineLayer mesh. + */ FineLayer2Regular(const GooseFEM::Mesh::Quad4::FineLayer& mesh) : m_finelayer(mesh) { // ------------ // Regular-mesh // ------------ m_regular = GooseFEM::Mesh::Quad4::Regular( xt::amax(m_finelayer.m_layer_nelx)(), xt::sum(m_finelayer.m_nhy)(), m_finelayer.m_h); // ------- // mapping // ------- // allocate mapping m_elem_regular.resize(m_finelayer.m_nelem); m_frac_regular.resize(m_finelayer.m_nelem); // alias array_type::tensor nhx = m_finelayer.m_nhx; array_type::tensor nhy = m_finelayer.m_nhy; array_type::tensor nelx = m_finelayer.m_layer_nelx; array_type::tensor start = m_finelayer.m_startElem; // 'matrix' of element numbers of the Regular-mesh array_type::tensor elementgrid = m_regular.elementgrid(); // cumulative number of element-rows of the Regular-mesh per layer of the FineLayer-mesh array_type::tensor cum_nhy = xt::concatenate(xt::xtuple(xt::zeros({1}), xt::cumsum(nhy))); // number of element layers in y-direction of the FineLayer-mesh size_t nely = nhy.size(); // loop over layers of the FineLayer-mesh for (size_t iy = 0; iy < nely; ++iy) { // element numbers of the Regular-mesh along this layer of the FineLayer-mesh auto el_new = xt::view(elementgrid, xt::range(cum_nhy(iy), cum_nhy(iy + 1)), xt::all()); // no coarsening/refinement // ------------------------ if (m_finelayer.m_refine(iy) == -1) { // element numbers of the FineLayer-mesh along this layer array_type::tensor el_old = start(iy) + xt::arange(nelx(iy)); // loop along this layer of the FineLayer-mesh for (size_t ix = 0; ix < nelx(iy); ++ix) { // get the element numbers of the Regular-mesh for this element of the // FineLayer-mesh auto block = xt::view(el_new, xt::all(), xt::range(ix * nhx(iy), (ix + 1) * nhx(iy))); // write to mapping for (auto& i : block) { m_elem_regular[el_old(ix)].push_back(i); m_frac_regular[el_old(ix)].push_back(1.0); } } } // refinement along the x-direction (below the middle layer) // --------------------------------------------------------- else if (m_finelayer.m_refine(iy) == 0 && iy <= (nely - 1) / 2) { // element numbers of the FineLayer-mesh along this layer // rows: coarse block, columns element numbers per block array_type::tensor el_old = start(iy) + xt::arange(nelx(iy) * 4ul).reshape({-1, 4}); // loop along this layer of the FineLayer-mesh for (size_t ix = 0; ix < nelx(iy); ++ix) { // get the element numbers of the Regular-mesh for this block of the // FineLayer-mesh auto block = xt::view(el_new, xt::all(), xt::range(ix * nhx(iy), (ix + 1) * nhx(iy))); // bottom: wide-to-narrow { for (size_t j = 0; j < nhy(iy) / 2; ++j) { auto e = xt::view(block, j, xt::range(j, nhx(iy) - j)); m_elem_regular[el_old(ix, 0)].push_back(e(0)); m_frac_regular[el_old(ix, 0)].push_back(0.5); for (size_t k = 1; k < e.size() - 1; ++k) { m_elem_regular[el_old(ix, 0)].push_back(e(k)); m_frac_regular[el_old(ix, 0)].push_back(1.0); } m_elem_regular[el_old(ix, 0)].push_back(e(e.size() - 1)); m_frac_regular[el_old(ix, 0)].push_back(0.5); } } // top: regular small element { auto e = xt::view( block, xt::range(nhy(iy) / 2, nhy(iy)), xt::range(1 * nhx(iy) / 3, 2 * nhx(iy) / 3)); for (auto& i : e) { m_elem_regular[el_old(ix, 2)].push_back(i); m_frac_regular[el_old(ix, 2)].push_back(1.0); } } // left { // left-bottom: narrow-to-wide for (size_t j = 0; j < nhy(iy) / 2; ++j) { auto e = xt::view(block, j, xt::range(0, j + 1)); for (size_t k = 0; k < e.size() - 1; ++k) { m_elem_regular[el_old(ix, 3)].push_back(e(k)); m_frac_regular[el_old(ix, 3)].push_back(1.0); } m_elem_regular[el_old(ix, 3)].push_back(e(e.size() - 1)); m_frac_regular[el_old(ix, 3)].push_back(0.5); } // left-top: regular { auto e = xt::view( block, xt::range(nhy(iy) / 2, nhy(iy)), xt::range(0 * nhx(iy) / 3, 1 * nhx(iy) / 3)); for (auto& i : e) { m_elem_regular[el_old(ix, 3)].push_back(i); m_frac_regular[el_old(ix, 3)].push_back(1.0); } } } // right { // right-bottom: narrow-to-wide for (size_t j = 0; j < nhy(iy) / 2; ++j) { auto e = xt::view(block, j, xt::range(nhx(iy) - j - 1, nhx(iy))); m_elem_regular[el_old(ix, 1)].push_back(e(0)); m_frac_regular[el_old(ix, 1)].push_back(0.5); for (size_t k = 1; k < e.size(); ++k) { m_elem_regular[el_old(ix, 1)].push_back(e(k)); m_frac_regular[el_old(ix, 1)].push_back(1.0); } } // right-top: regular { auto e = xt::view( block, xt::range(nhy(iy) / 2, nhy(iy)), xt::range(2 * nhx(iy) / 3, 3 * nhx(iy) / 3)); for (auto& i : e) { m_elem_regular[el_old(ix, 1)].push_back(i); m_frac_regular[el_old(ix, 1)].push_back(1.0); } } } } } // coarsening along the x-direction (above the middle layer) else if (m_finelayer.m_refine(iy) == 0 && iy > (nely - 1) / 2) { // element numbers of the FineLayer-mesh along this layer // rows: coarse block, columns element numbers per block array_type::tensor el_old = start(iy) + xt::arange(nelx(iy) * 4ul).reshape({-1, 4}); // loop along this layer of the FineLayer-mesh for (size_t ix = 0; ix < nelx(iy); ++ix) { // get the element numbers of the Regular-mesh for this block of the // FineLayer-mesh auto block = xt::view(el_new, xt::all(), xt::range(ix * nhx(iy), (ix + 1) * nhx(iy))); // top: narrow-to-wide { for (size_t j = 0; j < nhy(iy) / 2; ++j) { auto e = xt::view( block, nhy(iy) / 2 + j, xt::range(1 * nhx(iy) / 3 - j - 1, 2 * nhx(iy) / 3 + j + 1)); m_elem_regular[el_old(ix, 3)].push_back(e(0)); m_frac_regular[el_old(ix, 3)].push_back(0.5); for (size_t k = 1; k < e.size() - 1; ++k) { m_elem_regular[el_old(ix, 3)].push_back(e(k)); m_frac_regular[el_old(ix, 3)].push_back(1.0); } m_elem_regular[el_old(ix, 3)].push_back(e(e.size() - 1)); m_frac_regular[el_old(ix, 3)].push_back(0.5); } } // bottom: regular small element { auto e = xt::view( block, xt::range(0, nhy(iy) / 2), xt::range(1 * nhx(iy) / 3, 2 * nhx(iy) / 3)); for (auto& i : e) { m_elem_regular[el_old(ix, 1)].push_back(i); m_frac_regular[el_old(ix, 1)].push_back(1.0); } } // left { // left-bottom: regular { auto e = xt::view( block, xt::range(0, nhy(iy) / 2), xt::range(0 * nhx(iy) / 3, 1 * nhx(iy) / 3)); for (auto& i : e) { m_elem_regular[el_old(ix, 0)].push_back(i); m_frac_regular[el_old(ix, 0)].push_back(1.0); } } // left-top: narrow-to-wide for (size_t j = 0; j < nhy(iy) / 2; ++j) { auto e = xt::view(block, nhy(iy) / 2 + j, xt::range(0, 1 * nhx(iy) / 3 - j)); for (size_t k = 0; k < e.size() - 1; ++k) { m_elem_regular[el_old(ix, 0)].push_back(e(k)); m_frac_regular[el_old(ix, 0)].push_back(1.0); } m_elem_regular[el_old(ix, 0)].push_back(e(e.size() - 1)); m_frac_regular[el_old(ix, 0)].push_back(0.5); } } // right { // right-bottom: regular { auto e = xt::view( block, xt::range(0, nhy(iy) / 2), xt::range(2 * nhx(iy) / 3, 3 * nhx(iy) / 3)); for (auto& i : e) { m_elem_regular[el_old(ix, 2)].push_back(i); m_frac_regular[el_old(ix, 2)].push_back(1.0); } } // right-top: narrow-to-wide for (size_t j = 0; j < nhy(iy) / 2; ++j) { auto e = xt::view( block, nhy(iy) / 2 + j, xt::range(2 * nhx(iy) / 3 + j, nhx(iy))); m_elem_regular[el_old(ix, 2)].push_back(e(0)); m_frac_regular[el_old(ix, 2)].push_back(0.5); for (size_t k = 1; k < e.size(); ++k) { m_elem_regular[el_old(ix, 2)].push_back(e(k)); m_frac_regular[el_old(ix, 2)].push_back(1.0); } } } } } } } /** - Obtain the Regular mesh. - - \return mesh. - */ + * Obtain the Regular mesh. + * + * @return mesh. + */ GooseFEM::Mesh::Quad4::Regular regularMesh() const { return m_regular; } /** - Obtain the FineLayer mesh (copy of the mesh passed to the constructor). - - \return mesh. - */ + * Obtain the FineLayer mesh (copy of the mesh passed to the constructor). + * + * @return mesh. + */ GooseFEM::Mesh::Quad4::FineLayer fineLayerMesh() const { return m_finelayer; } // elements of the Regular mesh per element of the FineLayer mesh // and the fraction by which the overlap is /** - Get element-mapping: elements of the Regular mesh per element of the FineLayer mesh. - The number of Regular elements varies between elements of the FineLayer mesh. - - \return [nelem_finelayer, ?] - */ + * Get element-mapping: elements of the Regular mesh per element of the FineLayer mesh. + * The number of Regular elements varies between elements of the FineLayer mesh. + * + * @return [nelem_finelayer, ?] + */ std::vector> map() const { return m_elem_regular; } /** - To overlap fraction for each item in the mapping in map(). - - \return [nelem_finelayer, ?] - */ + * To overlap fraction for each item in the mapping in map(). + * + * @return [nelem_finelayer, ?] + */ std::vector> mapFraction() const { return m_frac_regular; } /** - Obtain the Regular mesh. - - \return mesh. - */ + * Obtain the Regular mesh. + * + * @return mesh. + */ [[deprecated]] GooseFEM::Mesh::Quad4::Regular getRegularMesh() const { return m_regular; } /** - Obtain the FineLayer mesh (copy of the mesh passed to the constructor). - - \return mesh. - */ + * Obtain the FineLayer mesh (copy of the mesh passed to the constructor). + * + * @return mesh. + */ [[deprecated]] GooseFEM::Mesh::Quad4::FineLayer getFineLayerMesh() const { return m_finelayer; } // elements of the Regular mesh per element of the FineLayer mesh // and the fraction by which the overlap is /** - Get element-mapping: elements of the Regular mesh per element of the FineLayer mesh. - The number of Regular elements varies between elements of the FineLayer mesh. - - \return [nelem_finelayer, ?] - */ + * Get element-mapping: elements of the Regular mesh per element of the FineLayer mesh. + * The number of Regular elements varies between elements of the FineLayer mesh. + * + * @return [nelem_finelayer, ?] + */ [[deprecated]] std::vector> getMap() const { return m_elem_regular; } /** - To overlap fraction for each item in the mapping in getMap(). - - \return [nelem_finelayer, ?] - */ + * To overlap fraction for each item in the mapping in getMap(). + * + * @return [nelem_finelayer, ?] + */ [[deprecated]] std::vector> getMapFraction() const { return m_frac_regular; } /** - Map element quantities to Regular. - The mapping is a bit simplistic: no interpolation is involved, the function just - accounts the fraction of overlap between the FineLayer element and the Regular element. - The mapping is such that:: - - ret[e_regular, ...] <- arg[e_finelayer, ...] - - \tparam T type of the data (e.g. ``double``). - \tparam rank rank of the data. - \param data data. - \return mapped data. - */ + * Map element quantities to Regular. + * The mapping is a bit simplistic: no interpolation is involved, the function just + * accounts the fraction of overlap between the FineLayer element and the Regular element. + * The mapping is such that:: + * + * ret[e_regular, ...] <- arg[e_finelayer, ...] + * + * @tparam T type of the data (e.g. ``double``). + * @tparam rank rank of the data. + * @param data data. + * @return mapped data. + */ template array_type::tensor mapToRegular(const array_type::tensor& data) const { GOOSEFEM_ASSERT(data.shape(0) == m_finelayer.nelem()); std::array shape; std::copy(data.shape().cbegin(), data.shape().cend(), &shape[0]); shape[0] = m_regular.nelem(); array_type::tensor ret = xt::zeros(shape); for (size_t e = 0; e < m_finelayer.nelem(); ++e) { for (size_t i = 0; i < m_elem_regular[e].size(); ++i) { xt::view(ret, m_elem_regular[e][i]) += m_frac_regular[e][i] * xt::view(data, e); } } return ret; } private: GooseFEM::Mesh::Quad4::FineLayer m_finelayer; ///< the FineLayer mesh to map GooseFEM::Mesh::Quad4::Regular m_regular; ///< the new Regular mesh to which to map std::vector> m_elem_regular; ///< see getMap() std::vector> m_frac_regular; ///< see getMapFraction() }; } // namespace Map } // namespace Quad4 } // namespace Mesh } // namespace GooseFEM #endif diff --git a/include/GooseFEM/MeshTri3.h b/include/GooseFEM/MeshTri3.h index ed3b673..2201a4e 100644 --- a/include/GooseFEM/MeshTri3.h +++ b/include/GooseFEM/MeshTri3.h @@ -1,330 +1,330 @@ /** -Generate simple meshes of 3-noded triangular elements in 2d (GooseFEM::Mesh::ElementType::Tri3). - -\file MeshTri3.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Generate simple meshes of 3-noded triangular elements in 2d (GooseFEM::Mesh::ElementType::Tri3). + * + * @file MeshTri3.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_MESHTRI3_H #define GOOSEFEM_MESHTRI3_H #include "Mesh.h" #include "config.h" namespace GooseFEM { namespace Mesh { /** -Simple meshes of and mesh operations for triangular elements of type ElementType::Tri3. -*/ + * Simple meshes of and mesh operations for triangular elements of type ElementType::Tri3. + */ namespace Tri3 { /** -Regular grid of squares, with each square cut into two triangular elements. -*/ + * Regular grid of squares, with each square cut into two triangular elements. + */ class Regular : public RegularBase2d { public: /** - Constructor. - - \param nelx Number of elements in x-direction. - \param nely Number of elements in y-direction. - \param h Edge-size (of the squares, and thus of two of three element-edges). - */ + * Constructor. + * + * @param nelx Number of elements in x-direction. + * @param nely Number of elements in y-direction. + * @param h Edge-size (of the squares, and thus of two of three element-edges). + */ Regular(size_t nelx, size_t nely, double h = 1.0) { m_h = h; m_nelx = nelx; m_nely = nely; m_ndim = 2; m_nne = 3; GOOSEFEM_ASSERT(m_nelx >= 1); GOOSEFEM_ASSERT(m_nely >= 1); m_nnode = (m_nelx + 1) * (m_nely + 1); m_nelem = m_nelx * m_nely * 2; } private: friend class RegularBase; friend class RegularBase2d; size_t nelx_impl() const { return m_nelx; } size_t nely_impl() const { return m_nely; } ElementType getElementType_impl() const { return ElementType::Tri3; } array_type::tensor coor_impl() const { array_type::tensor ret = xt::empty({m_nnode, m_ndim}); array_type::tensor x = xt::linspace(0.0, m_h * static_cast(m_nelx), m_nelx + 1); array_type::tensor y = xt::linspace(0.0, m_h * static_cast(m_nely), m_nely + 1); size_t inode = 0; for (size_t iy = 0; iy < m_nely + 1; ++iy) { for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(inode, 0) = x(ix); ret(inode, 1) = y(iy); ++inode; } } return ret; } array_type::tensor conn_impl() const { array_type::tensor ret = xt::empty({m_nelem, m_nne}); size_t ielem = 0; for (size_t iy = 0; iy < m_nely; ++iy) { for (size_t ix = 0; ix < m_nelx; ++ix) { ret(ielem, 0) = (iy) * (m_nelx + 1) + (ix); ret(ielem, 1) = (iy) * (m_nelx + 1) + (ix + 1); ret(ielem, 2) = (iy + 1) * (m_nelx + 1) + (ix); ++ielem; ret(ielem, 0) = (iy) * (m_nelx + 1) + (ix + 1); ret(ielem, 1) = (iy + 1) * (m_nelx + 1) + (ix + 1); ret(ielem, 2) = (iy + 1) * (m_nelx + 1) + (ix); ++ielem; } } return ret; } array_type::tensor nodesBottomEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx + 1}); for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(ix) = ix; } return ret; } array_type::tensor nodesTopEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx + 1}); for (size_t ix = 0; ix < m_nelx + 1; ++ix) { ret(ix) = ix + m_nely * (m_nelx + 1); } return ret; } array_type::tensor nodesLeftEdge_impl() const { array_type::tensor ret = xt::empty({m_nely + 1}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iy) = iy * (m_nelx + 1); } return ret; } array_type::tensor nodesRightEdge_impl() const { array_type::tensor ret = xt::empty({m_nely + 1}); for (size_t iy = 0; iy < m_nely + 1; ++iy) { ret(iy) = iy * (m_nelx + 1) + m_nelx; } return ret; } array_type::tensor nodesBottomOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx - 1}); for (size_t ix = 1; ix < m_nelx; ++ix) { ret(ix - 1) = ix; } return ret; } array_type::tensor nodesTopOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nelx - 1}); for (size_t ix = 1; ix < m_nelx; ++ix) { ret(ix - 1) = ix + m_nely * (m_nelx + 1); } return ret; } array_type::tensor nodesLeftOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nely - 1}); for (size_t iy = 1; iy < m_nely; ++iy) { ret(iy - 1) = iy * (m_nelx + 1); } return ret; } array_type::tensor nodesRightOpenEdge_impl() const { array_type::tensor ret = xt::empty({m_nely - 1}); for (size_t iy = 1; iy < m_nely; ++iy) { ret(iy - 1) = iy * (m_nelx + 1) + m_nelx; } return ret; } size_t nodesBottomLeftCorner_impl() const { return 0; } size_t nodesBottomRightCorner_impl() const { return m_nelx; } size_t nodesTopLeftCorner_impl() const { return m_nely * (m_nelx + 1); } size_t nodesTopRightCorner_impl() const { return m_nely * (m_nelx + 1) + m_nelx; } double m_h; ///< See h() size_t m_nelx; ///< See nelx() size_t m_nely; ///< See nely() size_t m_nelem; ///< See nelem() size_t m_nnode; ///< See nnode() size_t m_nne; ///< See nne() size_t m_ndim; ///< See ndim() }; // read / set the orientation (-1/+1) of all triangles /** -Read the orientation of a mesh of triangular elements of type ElementType::Tri3. - -\param coor Nodal coordinates [nnode, ndim]. -\param conn Connectivity [nelem, nne]. -\return Orientation (-1 or +1) per element [nelem]. -*/ + * Read the orientation of a mesh of triangular elements of type ElementType::Tri3. + * + * @param coor Nodal coordinates [nnode, ndim]. + * @param conn Connectivity [nelem, nne]. + * @return Orientation (-1 or +1) per element [nelem]. + */ inline array_type::tensor getOrientation(const array_type::tensor& coor, const array_type::tensor& conn) { GOOSEFEM_ASSERT(conn.shape(1) == 3ul); GOOSEFEM_ASSERT(coor.shape(1) == 2ul); double k; size_t nelem = conn.shape(0); array_type::tensor ret = xt::empty({nelem}); for (size_t ielem = 0; ielem < nelem; ++ielem) { auto v1 = xt::view(coor, conn(ielem, 0), xt::all()) - xt::view(coor, conn(ielem, 1), xt::all()); auto v2 = xt::view(coor, conn(ielem, 2), xt::all()) - xt::view(coor, conn(ielem, 1), xt::all()); k = v1(0) * v2(1) - v2(0) * v1(1); if (k < 0) { ret(ielem) = -1; } else { ret(ielem) = +1; } } return ret; } /** -Set the orientation of a mesh of triangular elements of type ElementType::Tri3. -For efficiency this function reuses the output of getOrientation(). - -\param coor Nodal coordinates [nnode, ndim]. -\param conn Connectivity [nelem, nne]. -\param val Current orientation per element (output of getOrientation()) [nelem]. -\param orientation Target orientation (applied to all elements). -\return Connectivity (order of nodes-per-element may have changed) [nelem, nne]. -*/ + * Set the orientation of a mesh of triangular elements of type ElementType::Tri3. + * For efficiency this function reuses the output of getOrientation(). + * + * @param coor Nodal coordinates [nnode, ndim]. + * @param conn Connectivity [nelem, nne]. + * @param val Current orientation per element (output of getOrientation()) [nelem]. + * @param orientation Target orientation (applied to all elements). + * @return Connectivity (order of nodes-per-element may have changed) [nelem, nne]. + */ inline array_type::tensor setOrientation( const array_type::tensor& coor, const array_type::tensor& conn, const array_type::tensor& val, int orientation = -1) { GOOSEFEM_ASSERT(conn.shape(1) == 3ul); GOOSEFEM_ASSERT(coor.shape(1) == 2ul); GOOSEFEM_ASSERT(conn.shape(0) == val.size()); GOOSEFEM_ASSERT(orientation == -1 || orientation == +1); UNUSED(coor); size_t nelem = conn.shape(0); array_type::tensor ret = conn; for (size_t ielem = 0; ielem < nelem; ++ielem) { if ((orientation == -1 && val(ielem) > 0) || (orientation == +1 && val(ielem) < 0)) { std::swap(ret(ielem, 2), ret(ielem, 1)); } } return ret; } /** -Set the orientation of a mesh of triangular elements of type ElementType::Tri3. - -\param coor Nodal coordinates [nnode, ndim]. -\param conn Connectivity [nelem, nne]. -\param orientation Target orientation (applied to all elements). -\return Connectivity (order of nodes-per-element may have changed) [nelem, nne]. -*/ + * Set the orientation of a mesh of triangular elements of type ElementType::Tri3. + * + * @param coor Nodal coordinates [nnode, ndim]. + * @param conn Connectivity [nelem, nne]. + * @param orientation Target orientation (applied to all elements). + * @return Connectivity (order of nodes-per-element may have changed) [nelem, nne]. + */ inline array_type::tensor setOrientation( const array_type::tensor& coor, const array_type::tensor& conn, int orientation = -1) { GOOSEFEM_ASSERT(conn.shape(1) == 3ul); GOOSEFEM_ASSERT(coor.shape(1) == 2ul); GOOSEFEM_ASSERT(orientation == -1 || orientation == +1); array_type::tensor val = getOrientation(coor, conn); return setOrientation(coor, conn, val, orientation); } } // namespace Tri3 } // namespace Mesh } // namespace GooseFEM #endif diff --git a/include/GooseFEM/TyingsPeriodic.h b/include/GooseFEM/TyingsPeriodic.h index 1e2e308..da60e8b 100644 --- a/include/GooseFEM/TyingsPeriodic.h +++ b/include/GooseFEM/TyingsPeriodic.h @@ -1,440 +1,441 @@ /** -Tools to store and apply nodal/DOF tyings. - -\file TyingsPeriodic.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Tools to store and apply nodal/DOF tyings. + * + * @file TyingsPeriodic.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_TYINGSPERIODIC_H #define GOOSEFEM_TYINGSPERIODIC_H #include "Mesh.h" #include "config.h" #include #include namespace GooseFEM { /** -Tools to store and apply nodal/DOF tyings. -*/ + * Tools to store and apply nodal/DOF tyings. + */ namespace Tyings { /** -Nodal tyings per periodic boundary conditions. -The idea is that the displacement of all DOFs of a node are tied to another node -and to the average displacement gradient. -The latter is applied/measured using 'virtual' control nodes. - -Consider the DOF list \f$ u \f$ renumbered such that it is split up in -independent and dependent DOFs as follows - -\f$ u = \begin{bmatrix} u_i \\ u_d \end{bmatrix}\f$ - -whereby the independent DOFs are furthermore split up in unknown and prescribed nodes as follows - -\f$ u_i = \begin{bmatrix} u_u \\ u_p \end{bmatrix}\f$ - -such that - -\f$ u = \begin{bmatrix} u_u \\ u_p \\ u_d \end{bmatrix}\f$ - -\todo Document how the DOFs are tied to the control nodes, and what the has to do with the mean. -*/ + * Nodal tyings per periodic boundary conditions. + * The idea is that the displacement of all DOFs of a node are tied to another node + * and to the average displacement gradient. + * The latter is applied/measured using 'virtual' control nodes. + * + * Consider the DOF list \f$ u \f$ renumbered such that it is split up in + * independent and dependent DOFs as follows + * + * \f$ u = \begin{bmatrix} u_i \\ u_d \end{bmatrix}\f$ + * + * whereby the independent DOFs are furthermore split up in unknown and prescribed nodes as follows + * + * \f$ u_i = \begin{bmatrix} u_u \\ u_p \end{bmatrix}\f$ + * + * such that + * + * \f$ u = \begin{bmatrix} u_u \\ u_p \\ u_d \end{bmatrix}\f$ + * + * \todo Document how the DOFs are tied to the control nodes, and what the has to do with the mean. + */ class Periodic { public: Periodic() = default; /** - Constructor. - - \tparam C array_type::tensor - \tparam D array_type::tensor - \tparam S array_type::tensor - \tparam T array_type::tensor - \param coor Nodal coordinates [nnode, ndim]. - \param dofs DOF-numbers per node [nnode, ndim]. - \param control_dofs DOF-numbers per control node [ndim, ndim]. - \param nodal_tyings List of nodal tyings, see nodal_tyings(). [ntyings, 2]. - */ + * Constructor. + * + * @tparam C array_type::tensor + * @tparam D array_type::tensor + * @tparam S array_type::tensor + * @tparam T array_type::tensor + * @param coor Nodal coordinates [nnode, ndim]. + * @param dofs DOF-numbers per node [nnode, ndim]. + * @param control_dofs DOF-numbers per control node [ndim, ndim]. + * @param nodal_tyings List of nodal tyings, see nodal_tyings(). [ntyings, 2]. + */ template Periodic(const C& coor, const D& dofs, const S& control_dofs, const T& nodal_tyings) : Periodic(coor, dofs, control_dofs, nodal_tyings, xt::eval(xt::empty({0}))) { } /** - Constructor. - - \tparam C array_type::tensor - \tparam D array_type::tensor - \tparam S array_type::tensor - \tparam T array_type::tensor - \tparam U array_type::tensor - \param coor Nodal coordinates [nnode, ndim]. - \param dofs DOF-numbers per node [nnode, ndim]. - \param control_dofs DOF-numbers per control node [ndim, ndim]. - \param nodal_tyings List of nodal tyings, see nodal_tyings(). [ntyings, 2]. - \param iip List of prescribed DOF-numbers. - */ + * Constructor. + * + * @tparam C array_type::tensor + * @tparam D array_type::tensor + * @tparam S array_type::tensor + * @tparam T array_type::tensor + * @tparam U array_type::tensor + * @param coor Nodal coordinates [nnode, ndim]. + * @param dofs DOF-numbers per node [nnode, ndim]. + * @param control_dofs DOF-numbers per control node [ndim, ndim]. + * @param nodal_tyings List of nodal tyings, see nodal_tyings(). [ntyings, 2]. + * @param iip List of prescribed DOF-numbers. + */ template Periodic( const C& coor, const D& dofs, const S& control_dofs, const T& nodal_tyings, const U& iip) { m_tyings = nodal_tyings; m_coor = coor; m_ndim = m_coor.shape(1); m_nties = m_tyings.shape(0); GOOSEFEM_ASSERT(xt::has_shape(m_tyings, {m_nties, size_t(2)})); GOOSEFEM_ASSERT(xt::has_shape(control_dofs, {m_ndim, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(dofs, m_coor.shape())); GOOSEFEM_ASSERT(xt::amax(control_dofs)() <= xt::amax(dofs)()); GOOSEFEM_ASSERT(xt::amin(control_dofs)() >= xt::amin(dofs)()); GOOSEFEM_ASSERT(xt::amax(iip)() <= xt::amax(dofs)()); GOOSEFEM_ASSERT(xt::amin(iip)() >= xt::amin(dofs)()); GOOSEFEM_ASSERT(xt::amax(nodal_tyings)() < m_coor.shape(0)); array_type::tensor dependent = xt::view(m_tyings, xt::all(), 1); array_type::tensor dependent_dofs = xt::view(dofs, xt::keep(dependent), xt::all()); U iid = xt::flatten(dependent_dofs); U iii = xt::setdiff1d(dofs, iid); U iiu = xt::setdiff1d(iii, iip); m_nnu = iiu.size(); m_nnp = iip.size(); m_nni = iii.size(); m_nnd = iid.size(); GooseFEM::Mesh::Reorder reorder({iiu, iip, iid}); m_dofs = reorder.apply(dofs); m_control = reorder.apply(control_dofs); } /** - \return Number of dependent DOFs. - */ + * @return Number of dependent DOFs. + */ size_t nnd() const { return m_nnd; } /** - \return Number of independent DOFs. - */ + * @return Number of independent DOFs. + */ size_t nni() const { return m_nni; } /** - \return Number of independent unknown DOFs. - */ + * @return Number of independent unknown DOFs. + */ size_t nnu() const { return m_nnu; } /** - \return Number of independent prescribed DOFs. - */ + * @return Number of independent prescribed DOFs. + */ size_t nnp() const { return m_nnp; } /** - \return DOF-numbers per node, as used internally (after renumbering), [nnode, ndim]. - */ + * @return DOF-numbers per node, as used internally (after renumbering), [nnode, ndim]. + */ const array_type::tensor& dofs() const { return m_dofs; } /** - \return DOF-numbers for each control node, as used internally (after renumbering), [ndim, ndim]. - */ + * @return DOF-numbers for each control node, as used internally (after renumbering), [ndim, + * ndim]. + */ const array_type::tensor& control() const { return m_control; } /** - Return the applied nodal tyings. - Per tying (row) two node numbers are specified, - according to the convention (independent, dependent). - - \return [ntyings, 2]. - */ + * Return the applied nodal tyings. + * Per tying (row) two node numbers are specified, + * according to the convention (independent, dependent). + * + * @return [ntyings, 2]. + */ const array_type::tensor& nodal_tyings() const { return m_tyings; } /** - Dependent DOFs. - - \return List of DOF numbers. - */ + * Dependent DOFs. + * + * @return List of DOF numbers. + */ array_type::tensor iid() const { return xt::arange(m_nni, m_nni + m_nnd); } /** - Independent DOFs. - - \return List of DOF numbers. - */ + * Independent DOFs. + * + * @return List of DOF numbers. + */ array_type::tensor iii() const { return xt::arange(m_nni); } /** - Independent unknown DOFs. - - \return List of DOF numbers. - */ + * Independent unknown DOFs. + * + * @return List of DOF numbers. + */ array_type::tensor iiu() const { return xt::arange(m_nnu); } /** - Independent prescribed DOFs. - - \return List of DOF numbers. - */ + * Independent prescribed DOFs. + * + * @return List of DOF numbers. + */ array_type::tensor iip() const { return xt::arange(m_nnp) + m_nnu; } /** - Return tying matrix such as to get the dependent DOFs \f$ u_d \f$ from - the independent DOFs \f$ u_i \f$ as follows - - \f$ u_d = C_{di} u_i \f$ - - Note that this can be further partitioned in - - \f$ u_d = C_{du} u_u + C_{dp} u_p \f$ - - See Cdu() and Cdp(). - - \return Sparse matrix. - */ + * Return tying matrix such as to get the dependent DOFs \f$ u_d \f$ from + * the independent DOFs \f$ u_i \f$ as follows + * + * \f$ u_d = C_{di} u_i \f$ + * + * Note that this can be further partitioned in + * + * \f$ u_d = C_{du} u_u + C_{dp} u_p \f$ + * + * See Cdu() and Cdp(). + * + * @return Sparse matrix. + */ Eigen::SparseMatrix Cdi() const { std::vector> data; data.reserve(m_nties * m_ndim * (m_ndim + 1)); for (size_t i = 0; i < m_nties; ++i) { for (size_t j = 0; j < m_ndim; ++j) { size_t ni = m_tyings(i, 0); size_t nd = m_tyings(i, 1); data.push_back(Eigen::Triplet(i * m_ndim + j, m_dofs(ni, j), +1.0)); for (size_t k = 0; k < m_ndim; ++k) { data.push_back(Eigen::Triplet( i * m_ndim + j, m_control(j, k), m_coor(nd, k) - m_coor(ni, k))); } } } Eigen::SparseMatrix Cdi; Cdi.resize(m_nnd, m_nni); Cdi.setFromTriplets(data.begin(), data.end()); return Cdi; } /** - Unknown part of the partitioned tying matrix, see Cdi(). - - \return Sparse matrix. - */ + * Unknown part of the partitioned tying matrix, see Cdi(). + * + * @return Sparse matrix. + */ Eigen::SparseMatrix Cdu() const { std::vector> data; data.reserve(m_nties * m_ndim * (m_ndim + 1)); for (size_t i = 0; i < m_nties; ++i) { for (size_t j = 0; j < m_ndim; ++j) { size_t ni = m_tyings(i, 0); size_t nd = m_tyings(i, 1); if (m_dofs(ni, j) < m_nnu) { data.push_back(Eigen::Triplet(i * m_ndim + j, m_dofs(ni, j), +1.0)); } for (size_t k = 0; k < m_ndim; ++k) { if (m_control(j, k) < m_nnu) { data.push_back(Eigen::Triplet( i * m_ndim + j, m_control(j, k), m_coor(nd, k) - m_coor(ni, k))); } } } } Eigen::SparseMatrix Cdu; Cdu.resize(m_nnd, m_nnu); Cdu.setFromTriplets(data.begin(), data.end()); return Cdu; } /** - Prescribed part of the partitioned tying matrix, see Cdi(). - - \return Sparse matrix. - */ + * Prescribed part of the partitioned tying matrix, see Cdi(). + * + * @return Sparse matrix. + */ Eigen::SparseMatrix Cdp() const { std::vector> data; data.reserve(m_nties * m_ndim * (m_ndim + 1)); for (size_t i = 0; i < m_nties; ++i) { for (size_t j = 0; j < m_ndim; ++j) { size_t ni = m_tyings(i, 0); size_t nd = m_tyings(i, 1); if (m_dofs(ni, j) >= m_nnu) { data.push_back( Eigen::Triplet(i * m_ndim + j, m_dofs(ni, j) - m_nnu, +1.0)); } for (size_t k = 0; k < m_ndim; ++k) { if (m_control(j, k) >= m_nnu) { data.push_back(Eigen::Triplet( i * m_ndim + j, m_control(j, k) - m_nnu, m_coor(nd, k) - m_coor(ni, k))); } } } } Eigen::SparseMatrix Cdp; Cdp.resize(m_nnd, m_nnp); Cdp.setFromTriplets(data.begin(), data.end()); return Cdp; } private: size_t m_nnu; ///< See nnu(). size_t m_nnp; ///< See nnp(). size_t m_nni; ///< See nni(). size_t m_nnd; ///< See nnd(). size_t m_ndim; ///< Number of dimensions. size_t m_nties; ///< Number of nodal ties. array_type::tensor m_dofs; ///< See dofs(). array_type::tensor m_control; ///< See control(). array_type::tensor m_tyings; ///< See nodal_tyings(). array_type::tensor m_coor; ///< Nodal coordinates [nnode, ndim]. }; /** -Add control nodes to an existing system. -*/ + * Add control nodes to an existing system. + */ class Control { public: Control() = default; /** - Constructor. - - \tparam C array_type::tensor - \tparam N array_type::tensor - \param coor Nodal coordinates [nnode, ndim]. - \param dofs DOF-numbers per node [nnode, ndim]. - */ + * Constructor. + * + * @tparam C array_type::tensor + * @tparam N array_type::tensor + * @param coor Nodal coordinates [nnode, ndim]. + * @param dofs DOF-numbers per node [nnode, ndim]. + */ template Control(const C& coor, const N& dofs) { GOOSEFEM_ASSERT(coor.shape().size() == 2); GOOSEFEM_ASSERT(coor.shape() == dofs.shape()); m_coor = coor; m_dofs = dofs; size_t nnode = coor.shape(0); size_t ndim = coor.shape(1); m_control_dofs = xt::arange(ndim * ndim).reshape({ndim, ndim}); m_control_dofs += xt::amax(dofs)() + 1; m_control_nodes = nnode + xt::arange(ndim); m_coor = xt::concatenate(xt::xtuple(coor, xt::zeros({ndim, ndim}))); m_dofs = xt::concatenate(xt::xtuple(dofs, m_control_dofs)); } /** - Nodal coordinates, for the system with control nodes added to it. - - \return [nnode + ndim, ndim], with nnode the number of nodes of the original system. - */ + * Nodal coordinates, for the system with control nodes added to it. + * + * @return [nnode + ndim, ndim], with nnode the number of nodes of the original system. + */ const array_type::tensor& coor() const { return m_coor; } /** - DOF-numbers per node, for the system with control nodes added to it. - - \return [nnode + ndim, ndim], with nnode the number of nodes of the original system. - */ + * DOF-numbers per node, for the system with control nodes added to it. + * + * @return [nnode + ndim, ndim], with nnode the number of nodes of the original system. + */ const array_type::tensor& dofs() const { return m_dofs; } /** - DOF-numbers of each control node. - - \return [ndim, ndim]. - */ + * DOF-numbers of each control node. + * + * @return [ndim, ndim]. + */ const array_type::tensor& controlDofs() const { return m_control_dofs; } /** - Node-numbers of the control nodes. - - \return [ndim]. - */ + * Node-numbers of the control nodes. + * + * @return [ndim]. + */ const array_type::tensor& controlNodes() const { return m_control_nodes; } private: array_type::tensor m_coor; ///< See coor(). array_type::tensor m_dofs; ///< See dofs(). array_type::tensor m_control_dofs; ///< See controlDofs(). array_type::tensor m_control_nodes; ///< See controlNodes(). }; } // namespace Tyings } // namespace GooseFEM #endif diff --git a/include/GooseFEM/Vector.h b/include/GooseFEM/Vector.h index d24a27c..5112fb0 100644 --- a/include/GooseFEM/Vector.h +++ b/include/GooseFEM/Vector.h @@ -1,716 +1,762 @@ /** -Methods to switch between storage types based on a mesh. - -\file Vector.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Methods to switch between storage types based on a mesh. + * + * @file Vector.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_VECTOR_H #define GOOSEFEM_VECTOR_H #include "config.h" namespace GooseFEM { /** -Class to switch between storage types. In particular: - -- "dofval": DOF values [#ndof]. -- "nodevec": nodal vectors [#nnode, #ndim]. -- "elemvec": nodal vectors stored per element [#nelem, #nne, #ndim]. -*/ + * Class to switch between storage types. In particular: + * + * - "dofval": DOF values [#ndof]. + * - "nodevec": nodal vectors [#nnode, #ndim]. + * - "elemvec": nodal vectors stored per element [#nelem, #nne, #ndim]. + */ class Vector { public: Vector() = default; /** - Constructor. - - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - */ + * Constructor. + * + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + */ template Vector(const S& conn, const T& dofs) : m_conn(conn), m_dofs(dofs) { GOOSEFEM_ASSERT(conn.dimension() == 2); GOOSEFEM_ASSERT(dofs.dimension() == 2); m_nelem = m_conn.shape(0); m_nne = m_conn.shape(1); m_nnode = m_dofs.shape(0); m_ndim = m_dofs.shape(1); m_ndof = xt::amax(m_dofs)() + 1; GOOSEFEM_ASSERT(xt::amax(m_conn)() + 1 <= m_nnode); GOOSEFEM_ASSERT(m_ndof <= m_nnode * m_ndim); } /** - \return Number of elements. - */ + * @return Number of elements. + */ size_t nelem() const { return m_nelem; } /** - \return Number of nodes per element. - */ + * @return Number of nodes per element. + */ size_t nne() const { return m_nne; } /** - \return Number of nodes. - */ + * @return Number of nodes. + */ size_t nnode() const { return m_nnode; } /** - \return Number of dimensions. - */ + * @return Number of dimensions. + */ size_t ndim() const { return m_ndim; } /** - \return Number of DOFs. - */ + * @return Number of DOFs. + */ size_t ndof() const { return m_ndof; } /** - \return Connectivity (nodes per element) [#nelem, #nne]. - */ + * @return Connectivity (nodes per element) [#nelem, #nne]. + */ const array_type::tensor& conn() const { return m_conn; } /** - \return DOFs per node [#nnode, #ndim] - */ + * @return DOFs per node [#nnode, #ndim] + */ const array_type::tensor& dofs() const { return m_dofs; } /** - Copy "nodevec" to another "nodevec". - - \param nodevec_src input [#nnode, #ndim] - \param nodevec_dest input [#nnode, #ndim] - \return nodevec output [#nnode, #ndim] - */ + * Copy "nodevec" to another "nodevec". + * + * @param nodevec_src input [#nnode, #ndim] + * @param nodevec_dest input [#nnode, #ndim] + * @return nodevec output [#nnode, #ndim] + */ template T Copy(const T& nodevec_src, const T& nodevec_dest) const { T ret = T::from_shape(nodevec_dest.shape()); this->copy(nodevec_src, ret); return ret; } /** - Copy "nodevec" to another "nodevec". - - \param nodevec_src input [#nnode, #ndim] - \param nodevec_dest output [#nnode, #ndim] - */ + * Copy "nodevec" to another "nodevec". + * + * @param nodevec_src input [#nnode, #ndim] + * @param nodevec_dest output [#nnode, #ndim] + */ template void copy(const T& nodevec_src, T& nodevec_dest) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec_src, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(nodevec_dest, this->shape_nodevec())); xt::noalias(nodevec_dest) = nodevec_src; } /** - Convert "nodevec" or "elemvec" to "dofval" (overwrite entries that occur more than once). - - \param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] - \return dofval [#ndof] - */ + * Convert "nodevec" or "elemvec" to "dofval" (overwrite entries that occur more than once). + * + * @param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] + * @return dofval [#ndof] + */ template array_type::tensor AsDofs(const T& arg) const { array_type::tensor ret = xt::empty(this->shape_dofval()); this->asDofs_impl(arg, ret); return ret; } /** - Convert "nodevec" or "elemvec" to "dofval" (overwrite entries that occur more than once). - - \param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] - \param ret dofval (output) [#ndof] - */ + * Convert "nodevec" or "elemvec" to "dofval" (overwrite entries that occur more than once). + * + * @param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] + * @param ret dofval (output) [#ndof] + */ template void asDofs(const T& arg, R& ret) const { this->asDofs_impl(arg, ret); } /** - Convert "dofval" or "elemvec" to "nodevec" (overwrite entries that occur more than once). - - \param arg dofval [#ndof] or elemvec [#nelem, #nne, #ndim] - \return nodevec output [#nnode, #ndim] - */ + * Convert "dofval" or "elemvec" to "nodevec" (overwrite entries that occur more than once). + * + * @param arg dofval [#ndof] or elemvec [#nelem, #nne, #ndim] + * @return nodevec output [#nnode, #ndim] + */ template array_type::tensor AsNode(const T& arg) const { array_type::tensor ret = xt::empty(this->shape_nodevec()); this->asNode_impl(arg, ret); return ret; } /** - Convert "dofval" or "elemvec" to "nodevec" (overwrite entries that occur more than once). - - \param arg dofval [#ndof] or elemvec [#nelem, #nne, #ndim] - \param ret nodevec, output [#nnode, #ndim] - */ + * Convert "dofval" or "elemvec" to "nodevec" (overwrite entries that occur more than once). + * + * @param arg dofval [#ndof] or elemvec [#nelem, #nne, #ndim] + * @param ret nodevec, output [#nnode, #ndim] + */ template void asNode(const T& arg, R& ret) const { this->asNode_impl(arg, ret); } /** - Convert "dofval" or "nodevec" to "elemvec" (overwrite entries that occur more than once). - - \param arg dofval [#ndof] or nodevec [#nnode, #ndim]. - \return elemvec output [#nelem, #nne, #ndim]. - */ + * Convert "dofval" or "nodevec" to "elemvec" (overwrite entries that occur more than once). + * + * @param arg dofval [#ndof] or nodevec [#nnode, #ndim]. + * @return elemvec output [#nelem, #nne, #ndim]. + */ template array_type::tensor AsElement(const T& arg) const { array_type::tensor ret = xt::empty(this->shape_elemvec()); this->asElement_impl(arg, ret); return ret; } /** - Convert "dofval" or "nodevec" to "elemvec" (overwrite entries that occur more than once). - - \param arg dofval [#ndof] or nodevec [#nnode, #ndim]. - \param ret elemvec, output [#nelem, #nne, #ndim]. - */ + * Convert "dofval" or "nodevec" to "elemvec" (overwrite entries that occur more than once). + * + * @param arg dofval [#ndof] or nodevec [#nnode, #ndim]. + * @param ret elemvec, output [#nelem, #nne, #ndim]. + */ template void asElement(const T& arg, R& ret) const { this->asElement_impl(arg, ret); } /** - Assemble "nodevec" or "elemvec" to "dofval" (adds entries that occur more that once). - - \param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] - \return dofval output [#ndof] - */ + * Assemble "nodevec" or "elemvec" to "dofval" (adds entries that occur more that once). + * + * @param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] + * @return dofval output [#ndof] + */ template array_type::tensor AssembleDofs(const T& arg) const { array_type::tensor ret = xt::empty(this->shape_dofval()); this->assembleDofs_impl(arg, ret); return ret; } /** - Assemble "nodevec" or "elemvec" to "dofval" (adds entries that occur more that once). - - \param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] - \param ret dofval, output [#ndof] - */ + * Assemble "nodevec" or "elemvec" to "dofval" (adds entries that occur more that once). + * + * @param arg nodevec [#nnode, #ndim] or elemvec [#nelem, #nne, #ndim] + * @param ret dofval, output [#ndof] + */ template void assembleDofs(const T& arg, R& ret) const { this->assembleDofs_impl(arg, ret); } /** - Assemble "elemvec" to "nodevec" (adds entries that occur more that once. - - \param arg elemvec [#nelem, #nne, #ndim] - \return nodevec output [#nnode, #ndim] - */ + * Assemble "elemvec" to "nodevec" (adds entries that occur more that once. + * + * @param arg elemvec [#nelem, #nne, #ndim] + * @return nodevec output [#nnode, #ndim] + */ template array_type::tensor AssembleNode(const T& arg) const { array_type::tensor ret = xt::empty(this->shape_nodevec()); this->assembleNode_impl(arg, ret); return ret; } /** - Assemble "elemvec" to "nodevec" (adds entries that occur more that once. - - \param arg elemvec [#nelem, #nne, #ndim] - \param ret nodevec, output [#nnode, #ndim] - */ + * Assemble "elemvec" to "nodevec" (adds entries that occur more that once. + * + * @param arg elemvec [#nelem, #nne, #ndim] + * @param ret nodevec, output [#nnode, #ndim] + */ template void assembleNode(const T& arg, R& ret) const { this->assembleNode_impl(arg, ret); } /** - Shape of "dofval". - - \return [#ndof] - */ + * Shape of "dofval". + * + * @return [#ndof] + */ std::array shape_dofval() const { return std::array{m_ndof}; } /** - Shape of "nodevec". - - \return [#nnode, #ndim] - */ + * Shape of "nodevec". + * + * @return [#nnode, #ndim] + */ std::array shape_nodevec() const { return std::array{m_nnode, m_ndim}; } /** - Shape of "elemvec". - - \return [#nelem, #nne, #ndim] - */ + * Shape of "elemvec". + * + * @return [#nelem, #nne, #ndim] + */ std::array shape_elemvec() const { return std::array{m_nelem, m_nne, m_ndim}; } /** - Shape of "elemmat". - - \return [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Shape of "elemmat". + * + * @return [#nelem, #nne * #ndim, #nne * #ndim] + */ std::array shape_elemmat() const { return std::array{m_nelem, m_nne * m_ndim, m_nne * m_ndim}; } /** - Allocated "dofval". - - \return [#ndof] - */ + * Allocated "dofval". + * + * @return [#ndof] + */ array_type::tensor allocate_dofval() const { array_type::tensor dofval = xt::empty(this->shape_dofval()); return dofval; } /** - Allocated and initialised "dofval". - - \param val value to which to initialise. - \return [#ndof] - */ + * Allocated and initialised "dofval". + * + * @param val value to which to initialise. + * @return [#ndof] + */ array_type::tensor allocate_dofval(double val) const { array_type::tensor dofval = xt::empty(this->shape_dofval()); dofval.fill(val); return dofval; } /** - Allocated "nodevec". - - \return [#nnode, #ndim] - */ + * Allocated "nodevec". + * + * @return [#nnode, #ndim] + */ array_type::tensor allocate_nodevec() const { array_type::tensor nodevec = xt::empty(this->shape_nodevec()); return nodevec; } /** - Allocated and initialised "nodevec". - - \param val value to which to initialise. - \return [#nnode, #ndim] - */ + * Allocated and initialised "nodevec". + * + * @param val value to which to initialise. + * @return [#nnode, #ndim] + */ array_type::tensor allocate_nodevec(double val) const { array_type::tensor nodevec = xt::empty(this->shape_nodevec()); nodevec.fill(val); return nodevec; } /** - Allocated "elemvec". - - \return [#nelem, #nne, #ndim] - */ + * Allocated "elemvec". + * + * @return [#nelem, #nne, #ndim] + */ array_type::tensor allocate_elemvec() const { array_type::tensor elemvec = xt::empty(this->shape_elemvec()); return elemvec; } /** - Allocated and initialised "elemvec". - - \param val value to which to initialise. - \return [#nelem, #nne, #ndim] - */ + * Allocated and initialised "elemvec". + * + * @param val value to which to initialise. + * @return [#nelem, #nne, #ndim] + */ array_type::tensor allocate_elemvec(double val) const { array_type::tensor elemvec = xt::empty(this->shape_elemvec()); elemvec.fill(val); return elemvec; } /** - Allocated "elemmat". - - \return [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Allocated "elemmat". + * + * @return [#nelem, #nne * #ndim, #nne * #ndim] + */ array_type::tensor allocate_elemmat() const { array_type::tensor elemmat = xt::empty(this->shape_elemmat()); return elemmat; } /** - Allocated and initialised "elemmat". - - \param val value to which to initialise. - \return [#nelem, #nne * #ndim, #nne * #ndim] - */ + * Allocated and initialised "elemmat". + * + * @param val value to which to initialise. + * @return [#nelem, #nne * #ndim, #nne * #ndim] + */ array_type::tensor allocate_elemmat(double val) const { array_type::tensor elemmat = xt::empty(this->shape_elemmat()); elemmat.fill(val); return elemmat; } private: - /** Distribution to relevant implementation of \copydoc asDofs(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asDofs(const T&, R&) const + */ template ::value, int> = 0> void asDofs_impl(const T& arg, R& ret) const { if (arg.dimension() == 2) { this->asDofs_impl_nodevec(arg, ret); } else if (arg.dimension() == 3) { this->asDofs_impl_elemvec(arg, ret); } else { throw std::runtime_error("Vector::asDofs unknown dimension for conversion"); } } - /** Distribution to relevant implementation of \copydoc asDofs(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asDofs(const T&, R&) const + */ template ::value == 2, int> = 0> void asDofs_impl(const T& arg, R& ret) const { this->asDofs_impl_nodevec(arg, ret); } - /** Distribution to relevant implementation of \copydoc asDofs(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asDofs(const T&, R&) const + */ template ::value == 3, int> = 0> void asDofs_impl(const T& arg, R& ret) const { this->asDofs_impl_elemvec(arg, ret); } - /** Distribution to relevant implementation of \copydoc asNode(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asNode(const T&, R&) const + */ template ::value, int> = 0> void asNode_impl(const T& arg, R& ret) const { if (arg.dimension() == 1) { this->asNode_impl_dofval(arg, ret); } else if (arg.dimension() == 3) { this->asNode_impl_elemvec(arg, ret); } else { throw std::runtime_error("Vector::asNode unknown dimension for conversion"); } } - /** Distribution to relevant implementation of \copydoc asNode(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asNode(const T&, R&) const + */ template ::value == 1, int> = 0> void asNode_impl(const T& arg, R& ret) const { this->asNode_impl_dofval(arg, ret); } - /** Distribution to relevant implementation of \copydoc asNode(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asNode(const T&, R&) const + */ template ::value == 3, int> = 0> void asNode_impl(const T& arg, R& ret) const { this->asNode_impl_elemvec(arg, ret); } - /** Distribution to relevant implementation of \copydoc asElement(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asElement(const T&, R&) const + */ template ::value, int> = 0> void asElement_impl(const T& arg, R& ret) const { if (arg.dimension() == 1) { this->asElement_impl_dofval(arg, ret); } else if (arg.dimension() == 2) { this->asElement_impl_nodevec(arg, ret); } else { throw std::runtime_error("Vector::asElement unknown dimension for conversion"); } } - /** Distribution to relevant implementation of \copydoc asElement(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asElement(const T&, R&) const + */ template ::value == 1, int> = 0> void asElement_impl(const T& arg, R& ret) const { this->asElement_impl_dofval(arg, ret); } - /** Distribution to relevant implementation of \copydoc asElement(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc asElement(const T&, R&) const + */ template ::value == 2, int> = 0> void asElement_impl(const T& arg, R& ret) const { this->asElement_impl_nodevec(arg, ret); } - /** Distribution to relevant implementation of \copydoc assembleDofs(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc assembleDofs(const T&, R&) const + */ template ::value, int> = 0> void assembleDofs_impl(const T& arg, R& ret) const { if (arg.dimension() == 2) { this->assembleDofs_impl_nodevec(arg, ret); } else if (arg.dimension() == 3) { this->assembleDofs_impl_elemvec(arg, ret); } else { throw std::runtime_error("Vector::assembleDofs unknown dimension for conversion"); } } - /** Distribution to relevant implementation of \copydoc assembleDofs(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc assembleDofs(const T&, R&) const + */ template ::value == 2, int> = 0> void assembleDofs_impl(const T& arg, R& ret) const { this->assembleDofs_impl_nodevec(arg, ret); } - /** Distribution to relevant implementation of \copydoc assembleDofs(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc assembleDofs(const T&, R&) const + */ template ::value == 3, int> = 0> void assembleDofs_impl(const T& arg, R& ret) const { this->assembleDofs_impl_elemvec(arg, ret); } - /** Distribution to relevant implementation of \copydoc assembleNode(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc assembleNode(const T&, R&) const + */ template ::value, int> = 0> void assembleNode_impl(const T& arg, R& ret) const { if (arg.dimension() == 3) { this->assembleNode_impl_elemvec(arg, ret); } else { throw std::runtime_error("Vector::assembleNode unknown dimension for conversion"); } } - /** Distribution to relevant implementation of \copydoc assembleNode(const T&, R&) const */ + /** + * Distribution to relevant implementation of \copydoc assembleNode(const T&, R&) const + */ template ::value == 3, int> = 0> void assembleNode_impl(const T& arg, R& ret) const { this->assembleNode_impl_elemvec(arg, ret); } - /** Implementation for 'nodevec' input of \copydoc asDofs(const T&, R&) const */ + /** + * Implementation for 'nodevec' input of \copydoc asDofs(const T&, R&) const + */ template void asDofs_impl_nodevec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 1 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_dofval())); ret.fill(0.0); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(m_dofs(m, i)) = arg(m, i); } } } - /** Implementation for 'elemvec' input of \copydoc asDofs(const T&, R&) const */ + /** + * Implementation for 'elemvec' input of \copydoc asDofs(const T&, R&) const + */ template void asDofs_impl_elemvec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 1 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_dofval())); ret.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(m_dofs(m_conn(e, m), i)) = arg(e, m, i); } } } } - /** Implementation for 'dofval' input of \copydoc asNode(const T&, R&) const */ + /** + * Implementation for 'dofval' input of \copydoc asNode(const T&, R&) const + */ template void asNode_impl_dofval(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 2 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_dofval())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_nodevec())); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(m, i) = arg(m_dofs(m, i)); } } } - /** Implementation for 'elemvec' input of \copydoc asNode(const T&, R&) const */ + /** + * Implementation for 'elemvec' input of \copydoc asNode(const T&, R&) const + */ template void asNode_impl_elemvec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 2 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_nodevec())); ret.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(m_conn(e, m), i) = arg(e, m, i); } } } } - /** Implementation for 'dofval' input of \copydoc asElement(const T&, R&) const */ + /** + * Implementation for 'dofval' input of \copydoc asElement(const T&, R&) const + */ template void asElement_impl_dofval(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 3 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(arg.size() == m_ndof); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_elemvec())); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(e, m, i) = arg(m_dofs(m_conn(e, m), i)); } } } } - /** Implementation for 'nodevec' input of \copydoc asElement(const T&, R&) const */ + /** + * Implementation for 'nodevec' input of \copydoc asElement(const T&, R&) const + */ template void asElement_impl_nodevec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 3 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_elemvec())); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(e, m, i) = arg(m_conn(e, m), i); } } } } - /** Implementation for 'nodevec' input of \copydoc assembleDofs(const T&, R&) const */ + /** + * Implementation for 'nodevec' input of \copydoc assembleDofs(const T&, R&) const + */ template void assembleDofs_impl_nodevec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 1 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_nodevec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_dofval())); ret.fill(0.0); for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(m_dofs(m, i)) += arg(m, i); } } } - /** Implementation for 'elemvec' input of \copydoc assembleDofs(const T&, R&) const */ + /** + * Implementation for 'elemvec' input of \copydoc assembleDofs(const T&, R&) const + */ template void assembleDofs_impl_elemvec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 1 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_dofval())); ret.fill(0.0); for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { ret(m_dofs(m_conn(e, m), i)) += arg(e, m, i); } } } } - /** Implementation for 'elemvec' input of \copydoc assembleNode(const T&, R&) const */ + /** + * Implementation for 'elemvec' input of \copydoc assembleNode(const T&, R&) const + */ template void assembleNode_impl_elemvec(const T& arg, R& ret) const { static_assert( xt::get_rank::value == 2 || !xt::has_fixed_rank_t::value, "Unknown rank 'ret'"); GOOSEFEM_ASSERT(xt::has_shape(arg, this->shape_elemvec())); GOOSEFEM_ASSERT(xt::has_shape(ret, this->shape_nodevec())); array_type::tensor dofval = this->AssembleDofs(arg); this->asNode(dofval, ret); } protected: array_type::tensor m_conn; ///< See conn() array_type::tensor m_dofs; ///< See dofs() size_t m_nelem; ///< See #nelem size_t m_nne; ///< See #nne size_t m_nnode; ///< See #nnode size_t m_ndim; ///< See #ndim size_t m_ndof; ///< See #ndof }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/VectorPartitioned.h b/include/GooseFEM/VectorPartitioned.h index 9fe43c2..94cd6a5 100644 --- a/include/GooseFEM/VectorPartitioned.h +++ b/include/GooseFEM/VectorPartitioned.h @@ -1,648 +1,648 @@ /** -Methods to switch between storage types based on a mesh and DOFs that are partitioned in: -- unknown DOFs -- prescribed DOFs - -\file VectorPartitioned.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Methods to switch between storage types based on a mesh and DOFs that are partitioned in: + * - unknown DOFs + * - prescribed DOFs + * + * @file VectorPartitioned.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_VECTORPARTITIONED_H #define GOOSEFEM_VECTORPARTITIONED_H #include "Mesh.h" #include "Vector.h" #include "assertions.h" #include "config.h" namespace GooseFEM { /** -Class to switch between storage types, -based on a mesh and DOFs that are partitioned in: - -- unknown DOFs (iiu()), indicated with "u". -- prescribed DOFs (iip()), indicated with "p". - -To this end some internal re-ordering of the DOFs has to be done, as follows: - - iiu() -> arange(nnu()) - iip() -> nnu() + arange(nnp()) - -which is relevant only if you interact using partitioned DOF-lists ("dofval_u" or "dofval_p"). - -The "dofval", "nodevec", and "elemvec" are all stored in the 'normal' order. - -For reference: - -- "dofval": DOF values [#ndof]. -- "dofval_u": unknown DOF values, `== dofval[iiu()]`, [#nnu]. -- "dofval_p": prescribed DOF values, `== dofval[iip()]`, [#nnp]. -- "nodevec": nodal vectors [#nnode, #ndim]. -- "elemvec": nodal vectors stored per element [#nelem, #nne, #ndim]. -*/ + * Class to switch between storage types, + * based on a mesh and DOFs that are partitioned in: + * + * - unknown DOFs (iiu()), indicated with "u". + * - prescribed DOFs (iip()), indicated with "p". + * + * To this end some internal re-ordering of the DOFs has to be done, as follows: + * + * iiu() -> arange(nnu()) + * iip() -> nnu() + arange(nnp()) + * + * which is relevant only if you interact using partitioned DOF-lists ("dofval_u" or "dofval_p"). + * + * The "dofval", "nodevec", and "elemvec" are all stored in the 'normal' order. + * + * For reference: + * + * - "dofval": DOF values [#ndof]. + * - "dofval_u": unknown DOF values, `== dofval[iiu()]`, [#nnu]. + * - "dofval_p": prescribed DOF values, `== dofval[iip()]`, [#nnp]. + * - "nodevec": nodal vectors [#nnode, #ndim]. + * - "elemvec": nodal vectors stored per element [#nelem, #nne, #ndim]. + */ class VectorPartitioned : public Vector { protected: array_type::tensor m_iiu; ///< See iiu() array_type::tensor m_iip; ///< See iip() size_t m_nnu; ///< See #nnu size_t m_nnp; ///< See #nnp /** - Renumbered DOFs per node, such that - - iiu = arange(nnu) - iip = nnu + arange(nnp) - - making is much simpler to slice. - */ + * Renumbered DOFs per node, such that + * + * iiu = arange(nnu) + * iip = nnu + arange(nnp) + * + * making is much simpler to slice. + */ array_type::tensor m_part; public: VectorPartitioned() = default; /** - Constructor. - - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - \param iip prescribed DOFs [#nnp]. - */ + * Constructor. + * + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + * @param iip prescribed DOFs [#nnp]. + */ VectorPartitioned( const array_type::tensor& conn, const array_type::tensor& dofs, const array_type::tensor& iip) : Vector(conn, dofs), m_iip(iip) { GOOSEFEM_ASSERT(is_unique(iip)); m_iiu = xt::setdiff1d(m_dofs, m_iip); m_nnp = m_iip.size(); m_nnu = m_iiu.size(); m_part = Mesh::Reorder({m_iiu, m_iip}).apply(m_dofs); GOOSEFEM_ASSERT(xt::amax(m_iip)() <= xt::amax(m_dofs)()); } /** - \return Number of unknown DOFs. - */ + * @return Number of unknown DOFs. + */ size_t nnu() const { return m_nnu; } /** - \return Number of prescribed DOFs. - */ + * @return Number of prescribed DOFs. + */ size_t nnp() const { return m_nnp; } /** - \return Unknown DOFs [#nnu]. - */ + * @return Unknown DOFs [#nnu]. + */ const array_type::tensor& iiu() const { return m_iiu; } /** - \return Prescribed DOFs [#nnp]. - */ + * @return Prescribed DOFs [#nnp]. + */ const array_type::tensor& iip() const { return m_iip; } /** - Per DOF (see Vector::dofs()) list if unknown ("u"). - - \return Boolean "nodevec". - */ + * Per DOF (see Vector::dofs()) list if unknown ("u"). + * + * @return Boolean "nodevec". + */ array_type::tensor dofs_is_u() const { array_type::tensor ret = xt::zeros(this->shape_nodevec()); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { ret(m, i) = true; } } } return ret; } /** - Per DOF (see Vector::dofs()) list if prescribed ("p"). - - \return Boolean "nodevec". - */ + * Per DOF (see Vector::dofs()) list if prescribed ("p"). + * + * @return Boolean "nodevec". + */ array_type::tensor dofs_is_p() const { array_type::tensor ret = xt::zeros(this->shape_nodevec()); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) >= m_nnu) { ret(m, i) = true; } } } return ret; } /** - Copy unknown DOFs from "nodevec" to another "nodvec": - - nodevec_dest[vector.dofs_is_u()] = nodevec_src - - the other DOFs are taken from ``nodevec_dest``: - - nodevec_dest[vector.dofs_is_p()] = nodevec_dest - - \param nodevec_src input [#nnode, #ndim] - \param nodevec_dest input [#nnode, #ndim] - \return nodevec output [#nnode, #ndim] - */ + * Copy unknown DOFs from "nodevec" to another "nodvec": + * + * nodevec_dest[vector.dofs_is_u()] = nodevec_src + * + * the other DOFs are taken from ``nodevec_dest``: + * + * nodevec_dest[vector.dofs_is_p()] = nodevec_dest + * + * @param nodevec_src input [#nnode, #ndim] + * @param nodevec_dest input [#nnode, #ndim] + * @return nodevec output [#nnode, #ndim] + */ array_type::tensor Copy_u( const array_type::tensor& nodevec_src, const array_type::tensor& nodevec_dest) const { array_type::tensor ret = nodevec_dest; this->copy_u(nodevec_src, ret); return ret; } /** - Copy unknown DOFs from "nodevec" to another "nodvec": - - nodevec_dest[vector.dofs_is_u()] = nodevec_src - - the other DOFs are taken from ``nodevec_dest``: - - nodevec_dest[vector.dofs_is_p()] = nodevec_dest - - \param nodevec_src input [#nnode, #ndim] - \param nodevec_dest input/output [#nnode, #ndim] - */ + * Copy unknown DOFs from "nodevec" to another "nodvec": + * + * nodevec_dest[vector.dofs_is_u()] = nodevec_src + * + * the other DOFs are taken from ``nodevec_dest``: + * + * nodevec_dest[vector.dofs_is_p()] = nodevec_dest + * + * @param nodevec_src input [#nnode, #ndim] + * @param nodevec_dest input/output [#nnode, #ndim] + */ void copy_u( const array_type::tensor& nodevec_src, array_type::tensor& nodevec_dest) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec_src, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(nodevec_dest, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { nodevec_dest(m, i) = nodevec_src(m, i); } } } } /** - Copy prescribed DOFs from "nodevec" to another "nodvec": - - nodevec_dest[vector.dofs_is_p()] = nodevec_src - - the other DOFs are taken from ``nodevec_dest``: - - nodevec_dest[vector.dofs_is_u()] = nodevec_dest - - \param nodevec_src input [#nnode, #ndim] - \param nodevec_dest input [#nnode, #ndim] - \return nodevec output [#nnode, #ndim] - */ + * Copy prescribed DOFs from "nodevec" to another "nodvec": + * + * nodevec_dest[vector.dofs_is_p()] = nodevec_src + * + * the other DOFs are taken from ``nodevec_dest``: + * + * nodevec_dest[vector.dofs_is_u()] = nodevec_dest + * + * @param nodevec_src input [#nnode, #ndim] + * @param nodevec_dest input [#nnode, #ndim] + * @return nodevec output [#nnode, #ndim] + */ array_type::tensor Copy_p( const array_type::tensor& nodevec_src, const array_type::tensor& nodevec_dest) const { array_type::tensor ret = nodevec_dest; this->copy_p(nodevec_src, ret); return ret; } /** - Copy prescribed DOFs from "nodevec" to another "nodvec": - - nodevec_dest[vector.dofs_is_p()] = nodevec_src - - the other DOFs are taken from ``nodevec_dest``: - - nodevec_dest[vector.dofs_is_u()] = nodevec_dest - - \param nodevec_src input [#nnode, #ndim] - \param nodevec_dest input/output [#nnode, #ndim] - */ + * Copy prescribed DOFs from "nodevec" to another "nodvec": + * + * nodevec_dest[vector.dofs_is_p()] = nodevec_src + * + * the other DOFs are taken from ``nodevec_dest``: + * + * nodevec_dest[vector.dofs_is_u()] = nodevec_dest + * + * @param nodevec_src input [#nnode, #ndim] + * @param nodevec_dest input/output [#nnode, #ndim] + */ void copy_p( const array_type::tensor& nodevec_src, array_type::tensor& nodevec_dest) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec_src, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(xt::has_shape(nodevec_dest, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) >= m_nnu) { nodevec_dest(m, i) = nodevec_src(m, i); } } } } /** - Combine unknown and prescribed "dofval" into a single "dofval" list. - - \param dofval_u input [#nnu] - \param dofval_p input [#nnp] - \return dofval output [#ndof] - */ + * Combine unknown and prescribed "dofval" into a single "dofval" list. + * + * @param dofval_u input [#nnu] + * @param dofval_p input [#nnp] + * @return dofval output [#ndof] + */ array_type::tensor DofsFromParitioned( const array_type::tensor& dofval_u, const array_type::tensor& dofval_p) const { array_type::tensor dofval = xt::empty({m_ndof}); this->dofsFromParitioned(dofval_u, dofval_p, dofval); return dofval; } /** - Combine unknown and prescribed "dofval" into a single "dofval" list. - - \param dofval_u input [#nnu] - \param dofval_p input [#nnp] - \param dofval output [#ndof] - */ + * Combine unknown and prescribed "dofval" into a single "dofval" list. + * + * @param dofval_u input [#nnu] + * @param dofval_p input [#nnp] + * @param dofval output [#ndof] + */ void dofsFromParitioned( const array_type::tensor& dofval_u, const array_type::tensor& dofval_p, array_type::tensor& dofval) const { GOOSEFEM_ASSERT(dofval_u.size() == m_nnu); GOOSEFEM_ASSERT(dofval_p.size() == m_nnp); GOOSEFEM_ASSERT(dofval.size() == m_ndof); dofval.fill(0.0); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { dofval(m_iiu(d)) = dofval_u(d); } #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { dofval(m_iip(d)) = dofval_p(d); } } /** - Combine unknown and prescribed "dofval" into a single "dofval" list - and directly convert to "nodeval" without a temporary - (overwrite entries that occur more than once). - - \param dofval_u input [#nnu] - \param dofval_p input [#nnp] - \return nodevec output [#nnode, #ndim] - */ + * Combine unknown and prescribed "dofval" into a single "dofval" list + * and directly convert to "nodeval" without a temporary + * (overwrite entries that occur more than once). + * + * @param dofval_u input [#nnu] + * @param dofval_p input [#nnp] + * @return nodevec output [#nnode, #ndim] + */ array_type::tensor NodeFromPartitioned( const array_type::tensor& dofval_u, const array_type::tensor& dofval_p) const { array_type::tensor nodevec = xt::empty({m_nnode, m_ndim}); this->nodeFromPartitioned(dofval_u, dofval_p, nodevec); return nodevec; } /** - Combine unknown and prescribed "dofval" into a single "dofval" list - and directly convert to "nodeval" without a temporary - (overwrite entries that occur more than once). - - \param dofval_u input [#nnu] - \param dofval_p input [#nnp] - \param nodevec output [#nnode, #ndim] - */ + * Combine unknown and prescribed "dofval" into a single "dofval" list + * and directly convert to "nodeval" without a temporary + * (overwrite entries that occur more than once). + * + * @param dofval_u input [#nnu] + * @param dofval_p input [#nnp] + * @param nodevec output [#nnode, #ndim] + */ void nodeFromPartitioned( const array_type::tensor& dofval_u, const array_type::tensor& dofval_p, array_type::tensor& nodevec) const { GOOSEFEM_ASSERT(dofval_u.size() == m_nnu); GOOSEFEM_ASSERT(dofval_p.size() == m_nnp); GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { nodevec(m, i) = dofval_u(m_part(m, i)); } else { nodevec(m, i) = dofval_p(m_part(m, i) - m_nnu); } } } } /** - Combine unknown and prescribed "dofval" into a single "dofval" list - and directly convert to "elemvec" without a temporary - (overwrite entries that occur more than once). - - \param dofval_u input [#nnu] - \param dofval_p input [#nnp] - \return elemvec output [#nelem, #nne, #ndim] - */ + * Combine unknown and prescribed "dofval" into a single "dofval" list + * and directly convert to "elemvec" without a temporary + * (overwrite entries that occur more than once). + * + * @param dofval_u input [#nnu] + * @param dofval_p input [#nnp] + * @return elemvec output [#nelem, #nne, #ndim] + */ array_type::tensor ElementFromPartitioned( const array_type::tensor& dofval_u, const array_type::tensor& dofval_p) const { array_type::tensor elemvec = xt::empty({m_nelem, m_nne, m_ndim}); this->elementFromPartitioned(dofval_u, dofval_p, elemvec); return elemvec; } /** - Combine unknown and prescribed "dofval" into a single "dofval" list - and directly convert to "elemvec" without a temporary - (overwrite entries that occur more than once). - - \param dofval_u input [#nnu] - \param dofval_p input [#nnp] - \param elemvec output [#nelem, #nne, #ndim] - */ + * Combine unknown and prescribed "dofval" into a single "dofval" list + * and directly convert to "elemvec" without a temporary + * (overwrite entries that occur more than once). + * + * @param dofval_u input [#nnu] + * @param dofval_p input [#nnp] + * @param elemvec output [#nelem, #nne, #ndim] + */ void elementFromPartitioned( const array_type::tensor& dofval_u, const array_type::tensor& dofval_p, array_type::tensor& elemvec) const { GOOSEFEM_ASSERT(dofval_u.size() == m_nnu); GOOSEFEM_ASSERT(dofval_p.size() == m_nnp); GOOSEFEM_ASSERT(xt::has_shape(elemvec, {m_nelem, m_nne, m_ndim})); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m_conn(e, m), i) < m_nnu) { elemvec(e, m, i) = dofval_u(m_part(m_conn(e, m), i)); } else { elemvec(e, m, i) = dofval_p(m_part(m_conn(e, m), i) - m_nnu); } } } } } /** - Extract the unknown "dofval": - - dofval[iiu()] - - \param dofval input [#ndof] - \return dofval_u input [#nnu] - */ + * Extract the unknown "dofval": + * + * dofval[iiu()] + * + * @param dofval input [#ndof] + * @return dofval_u input [#nnu] + */ array_type::tensor AsDofs_u(const array_type::tensor& dofval) const { array_type::tensor dofval_u = xt::empty({m_nnu}); this->asDofs_u(dofval, dofval_u); return dofval_u; } /** - Extract the unknown "dofval": - - dofval[iiu()] - - \param dofval input [#ndof] - \param dofval_u input [#nnu] - */ + * Extract the unknown "dofval": + * + * dofval[iiu()] + * + * @param dofval input [#ndof] + * @param dofval_u input [#nnu] + */ void asDofs_u( const array_type::tensor& dofval, array_type::tensor& dofval_u) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); GOOSEFEM_ASSERT(dofval_u.size() == m_nnu); #pragma omp parallel for for (size_t d = 0; d < m_nnu; ++d) { dofval_u(d) = dofval(m_iiu(d)); } } /** - Convert "nodevec" to "dofval" (overwrite entries that occur more than once) - and extract the unknown "dofval" without a temporary. - - \param nodevec input [#nnode, #ndim] - \return dofval_u input [#nnu] - */ + * Convert "nodevec" to "dofval" (overwrite entries that occur more than once) + * and extract the unknown "dofval" without a temporary. + * + * @param nodevec input [#nnode, #ndim] + * @return dofval_u input [#nnu] + */ array_type::tensor AsDofs_u(const array_type::tensor& nodevec) const { array_type::tensor dofval_u = xt::empty({m_nnu}); this->asDofs_u(nodevec, dofval_u); return dofval_u; } /** - Convert "nodevec" to "dofval" (overwrite entries that occur more than once) - and extract the unknown "dofval" without a temporary. - - \param nodevec input [#nnode, #ndim] - \param dofval_u input [#nnu] - */ + * Convert "nodevec" to "dofval" (overwrite entries that occur more than once) + * and extract the unknown "dofval" without a temporary. + * + * @param nodevec input [#nnode, #ndim] + * @param dofval_u input [#nnu] + */ void asDofs_u( const array_type::tensor& nodevec, array_type::tensor& dofval_u) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(dofval_u.size() == m_nnu); dofval_u.fill(0.0); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) < m_nnu) { dofval_u(m_part(m, i)) = nodevec(m, i); } } } } /** - Convert "elemvec" to "dofval" (overwrite entries that occur more than once) - and extract the unknown "dofval" without a temporary. - - \param elemvec input [#nelem, #nne, #ndim] - \return dofval_u input [#nnu] - */ + * Convert "elemvec" to "dofval" (overwrite entries that occur more than once) + * and extract the unknown "dofval" without a temporary. + * + * @param elemvec input [#nelem, #nne, #ndim] + * @return dofval_u input [#nnu] + */ array_type::tensor AsDofs_u(const array_type::tensor& elemvec) const { array_type::tensor dofval_u = xt::empty({m_nnu}); this->asDofs_u(elemvec, dofval_u); return dofval_u; } /** - Convert "elemvec" to "dofval" (overwrite entries that occur more than once) - and extract the unknown "dofval" without a temporary. - - \param elemvec input [#nelem, #nne, #ndim] - \param dofval_u input [#nnu] - */ + * Convert "elemvec" to "dofval" (overwrite entries that occur more than once) + * and extract the unknown "dofval" without a temporary. + * + * @param elemvec input [#nelem, #nne, #ndim] + * @param dofval_u input [#nnu] + */ void asDofs_u( const array_type::tensor& elemvec, array_type::tensor& dofval_u) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, {m_nelem, m_nne, m_ndim})); GOOSEFEM_ASSERT(dofval_u.size() == m_nnu); dofval_u.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m_conn(e, m), i) < m_nnu) { dofval_u(m_part(m_conn(e, m), i)) = elemvec(e, m, i); } } } } } /** - Extract the prescribed "dofval": - - dofval[iip()] - - \param dofval input [#ndof] - \return dofval_p input [#nnp] - */ + * Extract the prescribed "dofval": + * + * dofval[iip()] + * + * @param dofval input [#ndof] + * @return dofval_p input [#nnp] + */ array_type::tensor AsDofs_p(const array_type::tensor& dofval) const { array_type::tensor dofval_p = xt::empty({m_nnp}); this->asDofs_p(dofval, dofval_p); return dofval_p; } /** - Extract the prescribed "dofval": - - dofval[iip()] - - \param dofval input [#ndof] - \param dofval_p input [#nnp] - */ + * Extract the prescribed "dofval": + * + * dofval[iip()] + * + * @param dofval input [#ndof] + * @param dofval_p input [#nnp] + */ void asDofs_p( const array_type::tensor& dofval, array_type::tensor& dofval_p) const { GOOSEFEM_ASSERT(dofval.size() == m_ndof); GOOSEFEM_ASSERT(dofval_p.size() == m_nnp); #pragma omp parallel for for (size_t d = 0; d < m_nnp; ++d) { dofval_p(d) = dofval(m_iip(d)); } } /** - Convert "nodevec" to "dofval" (overwrite entries that occur more than once) - and extract the prescribed "dofval" without a temporary. - - \param nodevec input [#nnode, #ndim] - \return dofval_p input [#nnp] - */ + * Convert "nodevec" to "dofval" (overwrite entries that occur more than once) + * and extract the prescribed "dofval" without a temporary. + * + * @param nodevec input [#nnode, #ndim] + * @return dofval_p input [#nnp] + */ array_type::tensor AsDofs_p(const array_type::tensor& nodevec) const { array_type::tensor dofval_p = xt::empty({m_nnp}); this->asDofs_p(nodevec, dofval_p); return dofval_p; } /** - Convert "nodevec" to "dofval" (overwrite entries that occur more than once) - and extract the prescribed "dofval" without a temporary. - - \param nodevec input [#nnode, #ndim] - \param dofval_p input [#nnp] - */ + * Convert "nodevec" to "dofval" (overwrite entries that occur more than once) + * and extract the prescribed "dofval" without a temporary. + * + * @param nodevec input [#nnode, #ndim] + * @param dofval_p input [#nnp] + */ void asDofs_p( const array_type::tensor& nodevec, array_type::tensor& dofval_p) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(dofval_p.size() == m_nnp); dofval_p.fill(0.0); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m, i) >= m_nnu) { dofval_p(m_part(m, i) - m_nnu) = nodevec(m, i); } } } } /** - Convert "elemvec" to "dofval" (overwrite entries that occur more than once) - and extract the prescribed "dofval" without a temporary. - - \param elemvec input [#nelem, #nne, #ndim] - \return dofval_p input [#nnp] - */ + * Convert "elemvec" to "dofval" (overwrite entries that occur more than once) + * and extract the prescribed "dofval" without a temporary. + * + * @param elemvec input [#nelem, #nne, #ndim] + * @return dofval_p input [#nnp] + */ array_type::tensor AsDofs_p(const array_type::tensor& elemvec) const { array_type::tensor dofval_p = xt::empty({m_nnp}); this->asDofs_p(elemvec, dofval_p); return dofval_p; } /** - Convert "elemvec" to "dofval" (overwrite entries that occur more than once) - and extract the prescribed "dofval" without a temporary. - - \param elemvec input [#nelem, #nne, #ndim] - \param dofval_p input [#nnp] - */ + * Convert "elemvec" to "dofval" (overwrite entries that occur more than once) + * and extract the prescribed "dofval" without a temporary. + * + * @param elemvec input [#nelem, #nne, #ndim] + * @param dofval_p input [#nnp] + */ void asDofs_p( const array_type::tensor& elemvec, array_type::tensor& dofval_p) const { GOOSEFEM_ASSERT(xt::has_shape(elemvec, {m_nelem, m_nne, m_ndim})); GOOSEFEM_ASSERT(dofval_p.size() == m_nnp); dofval_p.fill(0.0); #pragma omp parallel for for (size_t e = 0; e < m_nelem; ++e) { for (size_t m = 0; m < m_nne; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_part(m_conn(e, m), i) >= m_nnu) { dofval_p(m_part(m_conn(e, m), i) - m_nnu) = elemvec(e, m, i); } } } } } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/VectorPartitionedTyings.h b/include/GooseFEM/VectorPartitionedTyings.h index 18b2267..2f6a73a 100644 --- a/include/GooseFEM/VectorPartitionedTyings.h +++ b/include/GooseFEM/VectorPartitionedTyings.h @@ -1,256 +1,256 @@ /** -Methods to switch between storage types based on a mesh and DOFs that are partitioned in: -- unknown DOFs -- prescribed DOFs -- dependent DOFs - -\file VectorPartitionedTyings.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Methods to switch between storage types based on a mesh and DOFs that are partitioned in: + * - unknown DOFs + * - prescribed DOFs + * - dependent DOFs + * + * @file VectorPartitionedTyings.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_VECTORPARTITIONEDTYINGS_H #define GOOSEFEM_VECTORPARTITIONEDTYINGS_H #include "Vector.h" #include "config.h" #include #include namespace GooseFEM { /** -Class to switch between storage types. In particular: - -- "nodevec": nodal vectors [#nnode, #ndim]. -- "elemvec": nodal vectors stored per element [nelem, #nne, #ndim]. -- "dofval": DOF values [#ndof]. -- "dofval_u": DOF values (Unknown), `== dofval[iiu]`, [#nnu]. -- "dofval_p": DOF values (Prescribed), `== dofval[iiu]`, [#nnp]. -*/ + * Class to switch between storage types. In particular: + * + * - "nodevec": nodal vectors [#nnode, #ndim]. + * - "elemvec": nodal vectors stored per element [nelem, #nne, #ndim]. + * - "dofval": DOF values [#ndof]. + * - "dofval_u": DOF values (Unknown), `== dofval[iiu]`, [#nnu]. + * - "dofval_p": DOF values (Prescribed), `== dofval[iiu]`, [#nnp]. + */ class VectorPartitionedTyings : public Vector { private: array_type::tensor m_iiu; ///< See iiu(). array_type::tensor m_iip; ///< See iip(). array_type::tensor m_iii; ///< See iii(). array_type::tensor m_iid; ///< See iid(). size_t m_nnu; ///< See nnu(). size_t m_nnp; ///< See nnp(). size_t m_nni; ///< See nni(). size_t m_nnd; ///< See nnd(). Eigen::SparseMatrix m_Cdu; ///< Tying matrix, see Tyings::Periodic::Cdu(). Eigen::SparseMatrix m_Cdp; ///< Tying matrix, see Tyings::Periodic::Cdp(). Eigen::SparseMatrix m_Cdi; ///< Tying matrix, see Tyings::Periodic::Cdi(). Eigen::SparseMatrix m_Cud; ///< Transpose of "m_Cdu". Eigen::SparseMatrix m_Cpd; ///< Transpose of "m_Cdp". Eigen::SparseMatrix m_Cid; ///< Transpose of "m_Cdi". private: /** - Convert to "dofval" (overwrite entries that occur more than once). - Only the dependent DOFs are retained. - - \param nodevec nodal vectors [#nnode, #ndim]. - \return dofval[iid()] [#nnd]. - */ + * Convert to "dofval" (overwrite entries that occur more than once). + * Only the dependent DOFs are retained. + * + * @param nodevec nodal vectors [#nnode, #ndim]. + * @return dofval[iid()] [#nnd]. + */ template Eigen::VectorXd Eigen_asDofs_d(const T& nodevec) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); Eigen::VectorXd dofval_d(m_nnd, 1); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_dofs(m, i) >= m_nni) { dofval_d(m_dofs(m, i) - m_nni) = nodevec(m, i); } } } return dofval_d; } public: VectorPartitionedTyings() = default; /** - Constructor. - - \tparam E e.g. `array_type::tensor` - \tparam M e.g. `Eigen::SparseMatrix` - \param conn connectivity [#nelem, #nne]. - \param dofs DOFs per node [#nnode, #ndim]. - \param Cdu See Tyings::Periodic::Cdu(). - \param Cdp See Tyings::Periodic::Cdp(). - \param Cdi See Tyings::Periodic::Cdi(). - */ + * Constructor. + * + * @tparam E e.g. `array_type::tensor` + * @tparam M e.g. `Eigen::SparseMatrix` + * @param conn connectivity [#nelem, #nne]. + * @param dofs DOFs per node [#nnode, #ndim]. + * @param Cdu See Tyings::Periodic::Cdu(). + * @param Cdp See Tyings::Periodic::Cdp(). + * @param Cdi See Tyings::Periodic::Cdi(). + */ template VectorPartitionedTyings(const E& conn, const E& dofs, const M& Cdu, const M& Cdp, const M& Cdi) : Vector(conn, dofs), m_Cdu(Cdu), m_Cdp(Cdp), m_Cdi(Cdi) { GOOSEFEM_ASSERT(Cdu.rows() == Cdp.rows()); GOOSEFEM_ASSERT(Cdi.rows() == Cdp.rows()); m_nnu = static_cast(m_Cdu.cols()); m_nnp = static_cast(m_Cdp.cols()); m_nnd = static_cast(m_Cdp.rows()); m_nni = m_nnu + m_nnp; m_iiu = xt::arange(m_nnu); m_iip = xt::arange(m_nnu, m_nnu + m_nnp); m_iii = xt::arange(m_nni); m_iid = xt::arange(m_nni, m_nni + m_nnd); m_Cud = m_Cdu.transpose(); m_Cpd = m_Cdp.transpose(); m_Cid = m_Cdi.transpose(); GOOSEFEM_ASSERT(static_cast(m_Cdi.cols()) == m_nni); GOOSEFEM_ASSERT(m_ndof == xt::amax(m_dofs)() + 1); } /** - \return Number of dependent DOFs. - */ + * @return Number of dependent DOFs. + */ size_t nnd() const { return m_nnd; } /** - \return Number of independent DOFs. - */ + * @return Number of independent DOFs. + */ size_t nni() const { return m_nni; } /** - \return Number of independent unknown DOFs. - */ + * @return Number of independent unknown DOFs. + */ size_t nnu() const { return m_nnu; } /** - \return Number of independent prescribed DOFs. - */ + * @return Number of independent prescribed DOFs. + */ size_t nnp() const { return m_nnp; } /** - Dependent DOFs (list of DOF numbers). - \return Pointer. - */ + * Dependent DOFs (list of DOF numbers). + * @return Pointer. + */ const array_type::tensor& iid() const { return m_iid; } /** - Independent DOFs (list of DOF numbers). - \return Copy. - */ + * Independent DOFs (list of DOF numbers). + * @return Copy. + */ const array_type::tensor& iii() const { return m_iii; } /** - Independent unknown DOFs (list of DOF numbers). - \return Pointer. - */ + * Independent unknown DOFs (list of DOF numbers). + * @return Pointer. + */ const array_type::tensor& iiu() const { return m_iiu; } /** - Independent prescribed DOFs (list of DOF numbers). - \return Pointer. - */ + * Independent prescribed DOFs (list of DOF numbers). + * @return Pointer. + */ const array_type::tensor& iip() const { return m_iip; } /** - Copy (part of) "dofval" to another "dofval": dofval_dest[iip()] = dofval_src[iip()]. - - \param dofval_src DOF values, iip() updated, [#ndof]. - \param dofval_dest DOF values, iip() updated, [#ndof]. - */ + * Copy (part of) "dofval" to another "dofval": dofval_dest[iip()] = dofval_src[iip()]. + * + * @param dofval_src DOF values, iip() updated, [#ndof]. + * @param dofval_dest DOF values, iip() updated, [#ndof]. + */ template void copy_p(const T& dofval_src, T& dofval_dest) const { GOOSEFEM_ASSERT(dofval_src.dimension() == 1); GOOSEFEM_ASSERT(dofval_dest.dimension() == 1); GOOSEFEM_ASSERT(dofval_src.size() == m_ndof || dofval_src.size() == m_nni); GOOSEFEM_ASSERT(dofval_dest.size() == m_ndof || dofval_dest.size() == m_nni); #pragma omp parallel for for (size_t i = m_nnu; i < m_nni; ++i) { dofval_dest(i) = dofval_src(i); } } /** - Convert to "dofval" (overwrite entries that occur more than once). - Only the independent DOFs are retained. - - \param nodevec nodal vectors [#nnode, #ndim]. - \return dofval[iii()] [#nni]. - */ + * Convert to "dofval" (overwrite entries that occur more than once). + * Only the independent DOFs are retained. + * + * @param nodevec nodal vectors [#nnode, #ndim]. + * @return dofval[iii()] [#nni]. + */ template array_type::tensor AsDofs_i(const T& nodevec) const { array_type::tensor dofval = xt::empty({m_nni}); this->asDofs_i(nodevec, dofval); return dofval; } /** - Same as InterpQuad_vector(), but writing to preallocated return. - - \param nodevec [#nnode, #ndim]. - \param dofval_i [#nni]. - \param apply_tyings If `true` the dependent DOFs are eliminated. - */ + * Same as InterpQuad_vector(), but writing to preallocated return. + * + * @param nodevec [#nnode, #ndim]. + * @param dofval_i [#nni]. + * @param apply_tyings If `true` the dependent DOFs are eliminated. + */ template void asDofs_i(const T& nodevec, R& dofval_i, bool apply_tyings = true) const { GOOSEFEM_ASSERT(xt::has_shape(nodevec, {m_nnode, m_ndim})); GOOSEFEM_ASSERT(dofval_i.size() == m_nni); dofval_i.fill(0.0); #pragma omp parallel for for (size_t m = 0; m < m_nnode; ++m) { for (size_t i = 0; i < m_ndim; ++i) { if (m_dofs(m, i) < m_nni) { dofval_i(m_dofs(m, i)) = nodevec(m, i); } } } if (!apply_tyings) { return; } Eigen::VectorXd Dofval_d = this->Eigen_asDofs_d(nodevec); Eigen::VectorXd Dofval_i = m_Cid * Dofval_d; #pragma omp parallel for for (size_t i = 0; i < m_nni; ++i) { dofval_i(i) += Dofval_i(i); } } }; } // namespace GooseFEM #endif diff --git a/include/GooseFEM/assertions.h b/include/GooseFEM/assertions.h index 02e51af..98cb5d1 100644 --- a/include/GooseFEM/assertions.h +++ b/include/GooseFEM/assertions.h @@ -1,35 +1,35 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_ASSERTIONS_H #define GOOSEFEM_ASSERTIONS_H #include "config.h" namespace GooseFEM { /** -Returns true is a list is unique (has not duplicate items). -\param arg Array-like. -\return `true` if `arg` is unique. -*/ + * Returns true is a list is unique (has not duplicate items). + * @param arg Array-like. + * @return `true` if `arg` is unique. + */ template inline bool is_unique(const T& arg) { array_type::tensor flat = xt::ravel(arg); array_type::tensor unique = xt::unique(flat); array_type::tensor sorted = xt::sort(flat); if (unique.size() != sorted.size()) { return false; } return xt::all(xt::equal(unique, sorted)); } } // namespace GooseFEM #endif diff --git a/include/GooseFEM/config.h b/include/GooseFEM/config.h index e5ec6ec..653d681 100644 --- a/include/GooseFEM/config.h +++ b/include/GooseFEM/config.h @@ -1,184 +1,184 @@ /** -Basic configuration: - -- Include general dependencies. -- Define assertions. - -\file config.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Basic configuration: + * + * - Include general dependencies. + * - Define assertions. + * + * @file config.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_CONFIG_H #define GOOSEFEM_CONFIG_H /** -\cond -*/ + * \cond + */ #define _USE_MATH_DEFINES // to use "M_PI" from "math.h" /** -\endcond -*/ + * \endcond + */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** -\cond -*/ + * \cond + */ using namespace xt::placeholders; #define Q(x) #x #define QUOTE(x) Q(x) #define UNUSED(p) ((void)(p)) #define GOOSEFEM_WARNING_IMPL(message, file, line, function) \ std::cout << std::string(file) + ":" + std::to_string(line) + " (" + std::string(function) + \ ")" + ": " message ") \n\t"; #define GOOSEFEM_ASSERT_IMPL(expr, file, line, function) \ if (!(expr)) { \ throw std::runtime_error( \ std::string(file) + ":" + std::to_string(line) + " (" + std::string(function) + ")" + \ ": assertion failed (" #expr ") \n\t"); \ } /** -\endcond -*/ + * \endcond + */ /** -All assertions are implementation as:: - - GOOSEFEM_ASSERT(...) - -They can be enabled by:: - - #define GOOSEFEM_ENABLE_ASSERT - -(before including GooseFEM). -The advantage is that: - -- File and line-number are displayed if the assertion fails. -- GooseFEM's assertions can be enabled/disabled independently from those of other libraries. - -\throw std::runtime_error -*/ + * All assertions are implementation as:: + * + * GOOSEFEM_ASSERT(...) + * + * They can be enabled by:: + * + * #define GOOSEFEM_ENABLE_ASSERT + * + * (before including GooseFEM). + * The advantage is that: + * + * - File and line-number are displayed if the assertion fails. + * - GooseFEM's assertions can be enabled/disabled independently from those of other libraries. + * + * \throw std::runtime_error + */ #ifdef GOOSEFEM_ENABLE_ASSERT #define GOOSEFEM_ASSERT(expr) GOOSEFEM_ASSERT_IMPL(expr, __FILE__, __LINE__, __FUNCTION__) #else #define GOOSEFEM_ASSERT(expr) #endif /** -Assertion that cannot be switched off. Implement assertion by:: - - GOOSEFEM_CHECK(...) - -\throw std::runtime_error -*/ + * Assertion that cannot be switched off. Implement assertion by:: + * + * GOOSEFEM_CHECK(...) + * + * \throw std::runtime_error + */ #define GOOSEFEM_CHECK(expr) GOOSEFEM_ASSERT_IMPL(expr, __FILE__, __LINE__, __FUNCTION__) /** -Assertion that concerns temporary implementation limitations. -Implement assertion by:: - - GOOSEFEM_WIP_ASSERT(...) - -\throw std::runtime_error -*/ + * Assertion that concerns temporary implementation limitations. + * Implement assertion by:: + * + * GOOSEFEM_WIP_ASSERT(...) + * + * \throw std::runtime_error + */ #define GOOSEFEM_WIP_ASSERT(expr) GOOSEFEM_ASSERT_IMPL(expr, __FILE__, __LINE__, __FUNCTION__) /** -All warnings are implemented as:: - - GOOSEFEM_WARNING(...) - -They can be disabled by:: - - #define GOOSEFEM_DISABLE_WARNING -*/ + * All warnings are implemented as:: + * + * GOOSEFEM_WARNING(...) + * + * They can be disabled by:: + * + * #define GOOSEFEM_DISABLE_WARNING + */ #ifdef GOOSEFEM_DISABLE_WARNING #define GOOSEFEM_WARNING(message) #else #define GOOSEFEM_WARNING(message) GOOSEFEM_WARNING_IMPL(message, __FILE__, __LINE__, __FUNCTION__) #endif /** -All warnings specific to the Python API are implemented as:: - - GOOSEFEM_WARNING_PYTHON(...) - -They can be enabled by:: - - #define GOOSEFEM_ENABLE_WARNING_PYTHON -*/ + * All warnings specific to the Python API are implemented as:: + * + * GOOSEFEM_WARNING_PYTHON(...) + * + * They can be enabled by:: + * + * #define GOOSEFEM_ENABLE_WARNING_PYTHON + */ #ifdef GOOSEFEM_ENABLE_WARNING_PYTHON #define GOOSEFEM_WARNING_PYTHON(message) \ GOOSEFEM_WARNING_IMPL(message, __FILE__, __LINE__, __FUNCTION__) #else #define GOOSEFEM_WARNING_PYTHON(message) #endif /** -Toolbox to perform finite element computations. -*/ + * Toolbox to perform finite element computations. + */ namespace GooseFEM { /** -Container type. -By default `array_type::tensor` is used. Otherwise: - -- `#define GOOSEFEM_USE_XTENSOR_PYTHON` to use `xt::pytensor` -*/ + * Container type. + * By default `array_type::tensor` is used. Otherwise: + * + * - `#define GOOSEFEM_USE_XTENSOR_PYTHON` to use `xt::pytensor` + */ namespace array_type { #ifdef GOOSEFEM_USE_XTENSOR_PYTHON /** -Fixed (static) rank array. -*/ + * Fixed (static) rank array. + */ template using tensor = xt::pytensor; #else /** -Fixed (static) rank array. -*/ + * Fixed (static) rank array. + */ template using tensor = xt::xtensor; #endif } // namespace array_type } // namespace GooseFEM #endif diff --git a/include/GooseFEM/detail.h b/include/GooseFEM/detail.h index 50155fd..de1522c 100644 --- a/include/GooseFEM/detail.h +++ b/include/GooseFEM/detail.h @@ -1,76 +1,76 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_DETAIL_H #define GOOSEFEM_DETAIL_H namespace GooseFEM { namespace detail { template struct tensor { }; template struct tensor> { /** - Inverse of a 2nd order tensor. - - \param The tensor. - \param The inverse (overwritten). - \return Determinant. - */ + * Inverse of a 2nd order tensor. + * + * @param The tensor. + * @param The inverse (overwritten). + * @return Determinant. + */ template static double inv(const T& A, T& Ainv) { double det = A(0, 0) * A(1, 1) - A(0, 1) * A(1, 0); Ainv(0, 0) = A(1, 1) / det; Ainv(0, 1) = -1.0 * A(0, 1) / det; Ainv(1, 0) = -1.0 * A(1, 0) / det; Ainv(1, 1) = A(0, 0) / det; return det; } }; template struct tensor> { /** - Inverse of a 2nd order tensor (shape: [3, 3]). - - \param The tensor. - \param The inverse (overwritten). - \return Determinant. - */ + * Inverse of a 2nd order tensor (shape: [3, 3]). + * + * @param The tensor. + * @param The inverse (overwritten). + * @return Determinant. + */ template static double inv(const T& A, T& Ainv) { double det = (A(0, 0) * A(1, 1) * A(2, 2) + A(0, 1) * A(1, 2) * A(2, 0) + A(0, 2) * A(1, 0) * A(2, 1)) - (A(0, 2) * A(1, 1) * A(2, 0) + A(0, 1) * A(1, 0) * A(2, 2) + A(0, 0) * A(1, 2) * A(2, 1)); Ainv(0, 0) = (A(1, 1) * A(2, 2) - A(1, 2) * A(2, 1)) / det; Ainv(0, 1) = (A(0, 2) * A(2, 1) - A(0, 1) * A(2, 2)) / det; Ainv(0, 2) = (A(0, 1) * A(1, 2) - A(0, 2) * A(1, 1)) / det; Ainv(1, 0) = (A(1, 2) * A(2, 0) - A(1, 0) * A(2, 2)) / det; Ainv(1, 1) = (A(0, 0) * A(2, 2) - A(0, 2) * A(2, 0)) / det; Ainv(1, 2) = (A(0, 2) * A(1, 0) - A(0, 0) * A(1, 2)) / det; Ainv(2, 0) = (A(1, 0) * A(2, 1) - A(1, 1) * A(2, 0)) / det; Ainv(2, 1) = (A(0, 1) * A(2, 0) - A(0, 0) * A(2, 1)) / det; Ainv(2, 2) = (A(0, 0) * A(1, 1) - A(0, 1) * A(1, 0)) / det; return det; } }; } // namespace detail } // namespace GooseFEM #endif diff --git a/include/GooseFEM/version.h b/include/GooseFEM/version.h index 168b03a..94902b0 100644 --- a/include/GooseFEM/version.h +++ b/include/GooseFEM/version.h @@ -1,95 +1,95 @@ /** -Version information. - -\file version.h -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * Version information. + * + * @file version.h + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef GOOSEFEM_VERSION_H #define GOOSEFEM_VERSION_H #include "config.h" /** -Current version. - -Either: - -- Configure using CMake at install time. Internally uses:: - - python -c "from setuptools_scm import get_version; print(get_version())" - -- Define externally using:: - - MYVERSION=`python -c "from setuptools_scm import get_version; print(get_version())"` - -DGOOSEFEM_VERSION="$MYVERSION" - - From the root of this project. This is what ``setup.py`` does. - -Note that both ``CMakeLists.txt`` and ``setup.py`` will construct the version using -``setuptools_scm``. Tip: use the environment variable ``SETUPTOOLS_SCM_PRETEND_VERSION`` to -overwrite the automatic version. -*/ + * Current version. + * + * Either: + * + * - Configure using CMake at install time. Internally uses:: + * + * python -c "from setuptools_scm import get_version; print(get_version())" + * + * - Define externally using:: + * + * MYVERSION=`python -c "from setuptools_scm import get_version; print(get_version())"` + * -DGOOSEFEM_VERSION="$MYVERSION" + * + * From the root of this project. This is what ``setup.py`` does. + * + * Note that both ``CMakeLists.txt`` and ``setup.py`` will construct the version using + * ``setuptools_scm``. Tip: use the environment variable ``SETUPTOOLS_SCM_PRETEND_VERSION`` to + * overwrite the automatic version. + */ #ifndef GOOSEFEM_VERSION #define GOOSEFEM_VERSION "@PROJECT_VERSION@" #endif namespace GooseFEM { namespace detail { inline std::string unquote(const std::string& arg) { std::string ret = arg; ret.erase(std::remove(ret.begin(), ret.end(), '\"'), ret.end()); return ret; } } // namespace detail /** -Return version string, e.g. `"0.8.0"` -\return String. -*/ + * Return version string, e.g. `"0.8.0"` + * @return String. + */ inline std::string version() { return detail::unquote(std::string(QUOTE(GOOSEFEM_VERSION))); } /** -Return versions of this library and of all of its dependencies. -The output is a list of strings, e.g.:: - - "goosefem=0.7.0", - "xtensor=0.20.1" - ... - -\return List of strings. -*/ + * Return versions of this library and of all of its dependencies. + * The output is a list of strings, e.g.:: + * + * "goosefem=0.7.0", + * "xtensor=0.20.1" + * ... + * + * @return List of strings. + */ inline std::vector version_dependencies() { std::vector ret; ret.push_back("goosefem=" + version()); ret.push_back( "xtensor=" + detail::unquote(std::string(QUOTE(XTENSOR_VERSION_MAJOR))) + "." + detail::unquote(std::string(QUOTE(XTENSOR_VERSION_MINOR))) + "." + detail::unquote(std::string(QUOTE(XTENSOR_VERSION_PATCH)))); #if defined(GOOSEFEM_EIGEN) || defined(EIGEN_WORLD_VERSION) ret.push_back( "eigen=" + detail::unquote(std::string(QUOTE(EIGEN_WORLD_VERSION))) + "." + detail::unquote(std::string(QUOTE(EIGEN_MAJOR_VERSION))) + "." + detail::unquote(std::string(QUOTE(EIGEN_MINOR_VERSION)))); #endif return ret; } } // namespace GooseFEM #endif diff --git a/python/Allocate.hpp b/python/Allocate.hpp index ee2d01e..f3861a9 100644 --- a/python/Allocate.hpp +++ b/python/Allocate.hpp @@ -1,37 +1,37 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include namespace py = pybind11; void init_Allocate(py::module& m) { m.def( "AsTensor", static_cast (*)(const xt::pyarray&, const std::vector&)>( &GooseFEM::AsTensor), "See :cpp:func:`GooseFEM::AsTensor`.", py::arg("arg"), py::arg("shape")); m.def( "AsTensor", static_cast (*)(size_t, const xt::pyarray&, size_t)>( &GooseFEM::AsTensor), "See :cpp:func:`GooseFEM::AsTensor`.", py::arg("rank"), py::arg("arg"), py::arg("n")); m.def( "as3d", &GooseFEM::as3d>, "See :cpp:func:`GooseFEM::as3d`.", py::arg("arg")); } diff --git a/python/Element.hpp b/python/Element.hpp index 13da247..5c6078a 100644 --- a/python/Element.hpp +++ b/python/Element.hpp @@ -1,182 +1,182 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_ELEMENT_H #define PYGOOSEFEM_ELEMENT_H #include #include #include #include #include namespace py = pybind11; template void register_Mesh_QuadratureBase(P& cls) { cls.def_property_readonly("nelem", &C::nelem, "Number of elements"); cls.def_property_readonly("nne", &C::nne, "Number of nodes per element"); cls.def_property_readonly("ndim", &C::ndim, "Number of spatial dimensions"); cls.def_property_readonly("tdim", &C::tdim, "Number of spatial dimensions for tensors"); cls.def_property_readonly("nip", &C::nip, "Number of integration points per element"); // todo: https://github.com/xtensor-stack/xtensor-python/issues/265 // cls.def("asTensor", // static_cast&, xt::pyarray&) // const>(&C::asTensor), "Convert 'qscalar' to 'qtensor' of certain rank", // py::arg("qscalar"), // py::arg("qtensor")); cls.def( "AsTensor", static_cast (C::*)(size_t, const xt::pyarray&) const>( &C::AsTensor), py::arg("rank"), py::arg("qscalar")); cls.def("shape_elemvec", static_cast (C::*)() const>(&C::shape_elemvec)); cls.def( "shape_elemvec", static_cast (C::*)(size_t) const>(&C::shape_elemvec), py::arg("tdim")); cls.def("shape_elemmat", &C::shape_elemmat); cls.def( "shape_qtensor", static_cast (C::*)(size_t) const>(&C::shape_qtensor), py::arg("rank")); cls.def( "shape_qtensor", static_cast (C::*)(size_t, size_t) const>(&C::shape_qtensor), py::arg("rank"), py::arg("tdim")); cls.def("shape_qscalar", &C::shape_qscalar); cls.def("shape_qvector", static_cast (C::*)() const>(&C::shape_qvector)); cls.def( "shape_qvector", static_cast (C::*)(size_t) const>(&C::shape_qvector), py::arg("tdim")); } template void register_Mesh_QuadratureBaseCartesian(P& cls) { cls.def("update_x", &C::template update_x>, py::arg("x")); cls.def_property_readonly("dV", &C::dV, "Integration point volume (qscalar)"); cls.def( "InterpQuad_vector", &C::template InterpQuad_vector>, py::arg("elemvec")); cls.def( "interpQuad_vector", &C::template interpQuad_vector, xt::pytensor>, py::arg("elemvec"), py::arg("qvector")); cls.def("GradN_vector", &C::template GradN_vector>, py::arg("elemvec")); cls.def( "gradN_vector", &C::template gradN_vector, xt::pytensor>, py::arg("elemvec"), py::arg("qtensor")); cls.def( "GradN_vector_T", &C::template GradN_vector_T>, py::arg("elemvec")); cls.def( "gradN_vector_T", &C::template gradN_vector_T, xt::pytensor>, py::arg("elemvec"), py::arg("qtensor")); cls.def( "SymGradN_vector", &C::template SymGradN_vector>, py::arg("elemvec")); cls.def( "symGradN_vector", &C::template symGradN_vector, xt::pytensor>, py::arg("elemvec"), py::arg("qtensor")); cls.def( "Int_N_vector_dV", &C::template Int_N_vector_dV>, py::arg("qvector")); cls.def( "int_N_vector_dV", &C::template int_N_vector_dV, xt::pytensor>, py::arg("qvector"), py::arg("elemvec")); cls.def( "Int_N_scalar_NT_dV", &C::template Int_N_scalar_NT_dV>, py::arg("qscalar")); cls.def( "int_N_scalar_NT_dV", &C::template int_N_scalar_NT_dV, xt::pytensor>, py::arg("qscalar"), py::arg("elemmat")); cls.def( "Int_gradN_dot_tensor2_dV", &C::template Int_gradN_dot_tensor2_dV>, py::arg("qtensor")); cls.def( "int_gradN_dot_tensor2_dV", &C::template int_gradN_dot_tensor2_dV, xt::pytensor>, py::arg("qtensor"), py::arg("elemvec")); cls.def( "Int_gradN_dot_tensor4_dot_gradNT_dV", &C::template Int_gradN_dot_tensor4_dot_gradNT_dV>, py::arg("qtensor")); cls.def( "int_gradN_dot_tensor4_dot_gradNT_dV", &C::template int_gradN_dot_tensor4_dot_gradNT_dV< xt::pytensor, xt::pytensor>, py::arg("qtensor"), py::arg("elemmat")); } void init_Element(py::module& m) { m.def( "asElementVector", &GooseFEM::Element::asElementVector, "See :cpp:func:`GooseFEM::Element::asElementVector`.", py::arg("conn"), py::arg("nodevec")); m.def( "assembleElementVector", &GooseFEM::Element::assembleNodeVector, "See :cpp:func:`GooseFEM::Element::assembleNodeVector`.", py::arg("conn"), py::arg("elemvec")); } #endif diff --git a/python/ElementHex8.hpp b/python/ElementHex8.hpp index e91f82a..8dbfb38 100644 --- a/python/ElementHex8.hpp +++ b/python/ElementHex8.hpp @@ -1,64 +1,64 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_ELEMENTHEX8_H #define PYGOOSEFEM_ELEMENTHEX8_H #include #include #include #include "Element.hpp" namespace py = pybind11; void init_ElementHex8(py::module& m) { py::class_ cls(m, "Quadrature"); cls.def( py::init&>(), "See :cpp:class:`GooseFEM::Element::Hex8::Quadrature`.", py::arg("x")); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Element::Hex8::Quadrature`.", py::arg("x"), py::arg("xi"), py::arg("w")); register_Mesh_QuadratureBase(cls); register_Mesh_QuadratureBaseCartesian(cls); cls.def_property_readonly( "GradN", &GooseFEM::Element::Hex8::Quadrature::GradN, "Shape function gradients [nelem, nip, nne, ndim]"); cls.def("__repr__", [](const GooseFEM::Element::Hex8::Quadrature&) { return ""; }); } void init_ElementHex8Gauss(py::module& m) { m.def("nip", &GooseFEM::Element::Hex8::Gauss::nip); m.def("xi", &GooseFEM::Element::Hex8::Gauss::xi); m.def("w", &GooseFEM::Element::Hex8::Gauss::w); } void init_ElementHex8Nodal(py::module& m) { m.def("nip", &GooseFEM::Element::Hex8::Nodal::nip); m.def("xi", &GooseFEM::Element::Hex8::Nodal::xi); m.def("w", &GooseFEM::Element::Hex8::Nodal::w); } #endif diff --git a/python/ElementQuad4.hpp b/python/ElementQuad4.hpp index 4daeb51..98830d7 100644 --- a/python/ElementQuad4.hpp +++ b/python/ElementQuad4.hpp @@ -1,71 +1,71 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_ELEMENTQUAD4_H #define PYGOOSEFEM_ELEMENTQUAD4_H #include #include #include #include "Element.hpp" namespace py = pybind11; void init_ElementQuad4(py::module& m) { py::class_ cls(m, "Quadrature"); cls.def( py::init&>(), "See :cpp:class:`GooseFEM::Element::Quad4::Quadrature`.", py::arg("x")); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Element::Quad4::Quadrature`.", py::arg("x"), py::arg("xi"), py::arg("w")); register_Mesh_QuadratureBase(cls); register_Mesh_QuadratureBaseCartesian(cls); cls.def_property_readonly( "GradN", &GooseFEM::Element::Quad4::Quadrature::GradN, "Shape function gradients [nelem, nip, nne, ndim]"); cls.def("__repr__", [](const GooseFEM::Element::Quad4::Quadrature&) { return ""; }); } void init_ElementQuad4Gauss(py::module& m) { m.def("nip", &GooseFEM::Element::Quad4::Gauss::nip); m.def("xi", &GooseFEM::Element::Quad4::Gauss::xi); m.def("w", &GooseFEM::Element::Quad4::Gauss::w); } void init_ElementQuad4Nodal(py::module& m) { m.def("nip", &GooseFEM::Element::Quad4::Nodal::nip); m.def("xi", &GooseFEM::Element::Quad4::Nodal::xi); m.def("w", &GooseFEM::Element::Quad4::Nodal::w); } void init_ElementQuad4MidPoint(py::module& m) { m.def("nip", &GooseFEM::Element::Quad4::MidPoint::nip); m.def("xi", &GooseFEM::Element::Quad4::MidPoint::xi); m.def("w", &GooseFEM::Element::Quad4::MidPoint::w); } #endif diff --git a/python/ElementQuad4Axisymmetric.hpp b/python/ElementQuad4Axisymmetric.hpp index 94f2a42..8a11166 100644 --- a/python/ElementQuad4Axisymmetric.hpp +++ b/python/ElementQuad4Axisymmetric.hpp @@ -1,50 +1,50 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_ELEMENTQUAD4AXISYMMETRIC_H #define PYGOOSEFEM_ELEMENTQUAD4AXISYMMETRIC_H #include #include #include #include "Element.hpp" namespace py = pybind11; void init_ElementQuad4Axisymmetric(py::module& m) { py::class_ cls(m, "QuadratureAxisymmetric"); cls.def( py::init&>(), "See :cpp:class:`GooseFEM::Element::Quad4::QuadratureAxisymmetric`.", py::arg("x")); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Element::Quad4::QuadratureAxisymmetric`.", py::arg("x"), py::arg("xi"), py::arg("w")); register_Mesh_QuadratureBase(cls); register_Mesh_QuadratureBaseCartesian(cls); cls.def_property_readonly( "B", &GooseFEM::Element::Quad4::QuadratureAxisymmetric::B, "B-matrix (shape function gradients) [nelem, nne, tdim, tdim, tdim]"); cls.def("__repr__", [](const GooseFEM::Element::Quad4::QuadratureAxisymmetric&) { return ""; }); } #endif diff --git a/python/ElementQuad4Planar.hpp b/python/ElementQuad4Planar.hpp index ffb692c..3c18780 100644 --- a/python/ElementQuad4Planar.hpp +++ b/python/ElementQuad4Planar.hpp @@ -1,53 +1,53 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_ELEMENTQUAD4PLANAR_H #define PYGOOSEFEM_ELEMENTQUAD4PLANAR_H #include #include #include #include "Element.hpp" namespace py = pybind11; void init_ElementQuad4Planar(py::module& m) { py::class_ cls(m, "QuadraturePlanar"); cls.def( py::init&, double>(), "See :cpp:class:`GooseFEM::Element::Quad4::QuadraturePlanar`.", py::arg("x"), py::arg("thick") = 1.0); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, double>(), "See :cpp:class:`GooseFEM::Element::Quad4::QuadraturePlanar`.", py::arg("x"), py::arg("xi"), py::arg("w"), py::arg("thick") = 1.0); register_Mesh_QuadratureBase(cls); register_Mesh_QuadratureBaseCartesian(cls); cls.def_property_readonly( "GradN", &GooseFEM::Element::Quad4::QuadraturePlanar::GradN, "Shape function gradients [nelem, nip, nne, ndim]"); cls.def("__repr__", [](const GooseFEM::Element::Quad4::QuadraturePlanar&) { return ""; }); } #endif diff --git a/python/Iterate.hpp b/python/Iterate.hpp index 1502e20..2e22efe 100644 --- a/python/Iterate.hpp +++ b/python/Iterate.hpp @@ -1,33 +1,33 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_ITERATE_H #define PYGOOSEFEM_ITERATE_H #include #include #include namespace py = pybind11; void init_Iterate(py::module& mod) { py::class_ cls(mod, "StopList"); cls.def(py::init(), "See :cpp:class:`GooseFEM::Iterate::StopList`.", py::arg("n") = 1); cls.def("reset", py::overload_cast<>(&GooseFEM::Iterate::StopList::reset)); cls.def("roll_insert", &GooseFEM::Iterate::StopList::roll_insert, py::arg("res")); cls.def("descending", &GooseFEM::Iterate::StopList::descending); cls.def("all_less", &GooseFEM::Iterate::StopList::all_less, py::arg("tol")); cls.def_property_readonly("data", &GooseFEM::Iterate::StopList::data); cls.def("__repr__", [](const GooseFEM::Iterate::StopList&) { return ""; }); } #endif diff --git a/python/Matrix.hpp b/python/Matrix.hpp index d484c9c..2f36cbe 100644 --- a/python/Matrix.hpp +++ b/python/Matrix.hpp @@ -1,224 +1,224 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_MATRIX_H #define PYGOOSEFEM_MATRIX_H #include #include #include #include #include #include namespace py = pybind11; template void register_Matrix_MatrixBase(P& cls) { cls.def_property_readonly("nelem", &C::nelem, "Number of elements"); cls.def_property_readonly("nne", &C::nne, "Number of nodes per element"); cls.def_property_readonly("nnode", &C::nnode, "Number of nodes"); cls.def_property_readonly("ndim", &C::ndim, "Number of dimensions"); cls.def_property_readonly("ndof", &C::ndof, "Number of DOFs"); cls.def_property_readonly("dofs", &C::dofs, "DOFs [nnode, ndim]"); cls.def_property_readonly("conn", &C::conn, "Connectivity [nelem, nne]"); cls.def( "assemble", &C::template assemble>, "Assemble from elemmat", py::arg("elemmat")); cls.def("Todense", &C::Todense, "Return a dense matrix (copy)"); cls.def( "todense", &C::template todense>, "Dense matrix (write to ret)", py::arg("ret")); cls.def( "Dot", py::overload_cast&>(&C::Dot, py::const_), "Dot product.", py::arg("x")); cls.def( "Dot", py::overload_cast&>(&C::Dot, py::const_), "Dot product.", py::arg("x")); cls.def( "dot", py::overload_cast&, xt::pytensor&>( &C::dot, py::const_), "Dot product (write to b).", py::arg("x"), py::arg("b")); cls.def( "dot", py::overload_cast&, xt::pytensor&>( &C::dot, py::const_), "Dot product (write to b).", py::arg("x"), py::arg("b")); } template void register_Matrix_MatrixPartitionedBase(P& cls) { cls.def_property_readonly("nnu", &C::nnu, "Number of unknown DOFs"); cls.def_property_readonly("nnp", &C::nnp, "Number of prescribed DOFs"); cls.def_property_readonly("iiu", &C::iiu, "Unknown DOFs"); cls.def_property_readonly("iip", &C::iip, "Prescribed DOFs"); cls.def( "Reaction", py::overload_cast&, const xt::pytensor&>( &C::Reaction, py::const_), "Return ``b`` with correct right-hand-side", py::arg("x"), py::arg("b")); cls.def( "Reaction", py::overload_cast&, const xt::pytensor&>( &C::Reaction, py::const_), "Return ``b`` with correct right-hand-side", py::arg("x"), py::arg("b")); cls.def( "reaction", py::overload_cast&, xt::pytensor&>( &C::reaction, py::const_), "Update ``b`` with correct right-hand-side", py::arg("x"), py::arg("b")); cls.def( "reaction", py::overload_cast&, xt::pytensor&>( &C::reaction, py::const_), "Update ``b`` with correct right-hand-side", py::arg("x"), py::arg("b")); cls.def( "Reaction_p", py::overload_cast&, const xt::pytensor&>( &C::Reaction_p, py::const_), "Return ``b_p``", py::arg("x_u"), py::arg("x_p")); } template void register_Matrix_MatrixPartitionedTyingsBase(P& cls) { cls.def_property_readonly("nni", &C::nnu, "Number of independent DOFs"); cls.def_property_readonly("nnd", &C::nnp, "Number of dependent DOFs"); cls.def_property_readonly("iii", &C::iiu, "Independent DOFs"); cls.def_property_readonly("iid", &C::iip, "Dependent DOFs"); } template void register_MatrixSolver_MatrixSolverBase(P& cls) { cls.def( "solve", py::overload_cast&, xt::pytensor&>( &C::template solve), "Solve system.", py::arg("A"), py::arg("b"), py::arg("x")); cls.def( "solve", py::overload_cast&, xt::pytensor&>( &C::template solve), "Solve system.", py::arg("A"), py::arg("b"), py::arg("x")); } template void register_MatrixSolver_MatrixSolverSingleBase(P& cls) { cls.def( "Solve", py::overload_cast&>(&C::template Solve), "Solve system.", py::arg("A"), py::arg("b")); cls.def( "Solve", py::overload_cast&>(&C::template Solve), "Solve system.", py::arg("A"), py::arg("b")); } template void register_MatrixSolver_MatrixSolverPartitionedBase(P& cls) { cls.def( "Solve", py::overload_cast&, const xt::pytensor&>( &C::template Solve), "Solve system.", py::arg("A"), py::arg("b"), py::arg("x")); cls.def( "Solve", py::overload_cast&, const xt::pytensor&>( &C::template Solve), "Solve system.", py::arg("A"), py::arg("b"), py::arg("x")); } void init_Matrix(py::module& m) { // --- py::class_ cls(m, "Matrix"); register_Matrix_MatrixBase(cls); cls.def( py::init&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Matrix`.", py::arg("conn"), py::arg("dofs")); cls.def_property_readonly("data", &GooseFEM::Matrix::data); cls.def("set", &GooseFEM::Matrix::set, py::arg("rows"), py::arg("cols"), py::arg("matrix")); cls.def("add", &GooseFEM::Matrix::add, py::arg("rows"), py::arg("cols"), py::arg("matrix")); cls.def("__repr__", [](const GooseFEM::Matrix&) { return ""; }); // --- py::class_> slv(m, "MatrixSolver"); register_MatrixSolver_MatrixSolverBase, GooseFEM::Matrix>(slv); register_MatrixSolver_MatrixSolverSingleBase, GooseFEM::Matrix>(slv); slv.def(py::init<>(), "See :cpp:class:`GooseFEM::MatrixSolver`."); slv.def("__repr__", [](const GooseFEM::MatrixSolver<>&) { return ""; }); } #endif diff --git a/python/MatrixDiagonal.hpp b/python/MatrixDiagonal.hpp index b4106c2..e19f4f7 100644 --- a/python/MatrixDiagonal.hpp +++ b/python/MatrixDiagonal.hpp @@ -1,68 +1,68 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_MATRIXDIAGONAL_H #define PYGOOSEFEM_MATRIXDIAGONAL_H #include #include #include #include #include "Matrix.hpp" namespace py = pybind11; template void register_Matrix_MatrixDiagonalBase(P& cls) { cls.def( "Solve", py::overload_cast&>(&C::Solve), "Solve", py::arg("b")); cls.def( "Solve", py::overload_cast&>(&C::Solve), "Solve", py::arg("b")); cls.def( "solve", py::overload_cast&, xt::pytensor&>(&C::solve), "Solve (write to x)", py::arg("b"), py::arg("x")); cls.def( "solve", py::overload_cast&, xt::pytensor&>(&C::solve), "Solve (write to x)", py::arg("b"), py::arg("x")); } void init_MatrixDiagonal(py::module& m) { py::class_ cls(m, "MatrixDiagonal"); register_Matrix_MatrixBase(cls); register_Matrix_MatrixDiagonalBase(cls); cls.def( py::init&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::MatrixDiagonal`.", py::arg("conn"), py::arg("dofs")); cls.def("set", &GooseFEM::MatrixDiagonal::set, py::arg("A")); cls.def_property_readonly("data", &GooseFEM::MatrixDiagonal::data); cls.def( "__repr__", [](const GooseFEM::MatrixDiagonal&) { return ""; }); } #endif diff --git a/python/MatrixDiagonalPartitioned.hpp b/python/MatrixDiagonalPartitioned.hpp index 0d0dd84..fb74570 100644 --- a/python/MatrixDiagonalPartitioned.hpp +++ b/python/MatrixDiagonalPartitioned.hpp @@ -1,58 +1,58 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include #include namespace py = pybind11; void init_MatrixDiagonalPartitioned(py::module& m) { py::class_ cls(m, "MatrixDiagonalPartitioned"); register_Matrix_MatrixBase(cls); register_Matrix_MatrixPartitionedBase(cls); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::MatrixDiagonalPartitioned`.", py::arg("conn"), py::arg("dofs"), py::arg("iip")); cls.def("data", &GooseFEM::MatrixDiagonalPartitioned::data, "Copy to assemble diagonal matrix"); cls.def_property_readonly("data_uu", &GooseFEM::MatrixDiagonalPartitioned::data_uu); cls.def_property_readonly("data_pp", &GooseFEM::MatrixDiagonalPartitioned::data_pp); cls.def( "Dot_u", py::overload_cast&, const xt::pytensor&>( &GooseFEM::MatrixDiagonalPartitioned::Dot_u, py::const_), py::arg("x_u"), py::arg("x_p")); cls.def( "Dot_p", py::overload_cast&, const xt::pytensor&>( &GooseFEM::MatrixDiagonalPartitioned::Dot_p, py::const_), py::arg("x_u"), py::arg("x_p")); cls.def( "Solve_u", py::overload_cast&, const xt::pytensor&>( &GooseFEM::MatrixDiagonalPartitioned::Solve_u), py::arg("b_u"), py::arg("x_p")); cls.def("__repr__", [](const GooseFEM::MatrixDiagonalPartitioned&) { return ""; }); } diff --git a/python/MatrixPartitioned.hpp b/python/MatrixPartitioned.hpp index c03da5a..c0bac2f 100644 --- a/python/MatrixPartitioned.hpp +++ b/python/MatrixPartitioned.hpp @@ -1,75 +1,75 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include #include #include #include #include "Matrix.hpp" namespace py = pybind11; void init_MatrixPartitioned(py::module& m) { py::class_ cls(m, "MatrixPartitioned"); register_Matrix_MatrixBase(cls); register_Matrix_MatrixPartitionedBase(cls); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::MatrixPartitioned`.", py::arg("conn"), py::arg("dofs"), py::arg("iip")); cls.def_property_readonly("data_uu", &GooseFEM::MatrixPartitioned::data_uu); cls.def_property_readonly("data_up", &GooseFEM::MatrixPartitioned::data_up); cls.def_property_readonly("data_pu", &GooseFEM::MatrixPartitioned::data_pu); cls.def_property_readonly("data_pp", &GooseFEM::MatrixPartitioned::data_pp); cls.def("__repr__", [](const GooseFEM::MatrixPartitioned&) { return ""; }); py::class_> slv(m, "MatrixPartitionedSolver"); register_MatrixSolver_MatrixSolverBase< GooseFEM::MatrixPartitionedSolver<>, GooseFEM::MatrixPartitioned>(slv); register_MatrixSolver_MatrixSolverPartitionedBase< GooseFEM::MatrixPartitionedSolver<>, GooseFEM::MatrixPartitioned>(slv); slv.def(py::init<>(), "See :cpp:class:`GooseFEM::MatrixPartitionedSolver`."); slv.def( "Solve_u", &GooseFEM::MatrixPartitionedSolver<>::template Solve_u, "Solve system.", py::arg("A"), py::arg("b_u"), py::arg("x_p")); slv.def( "solve_u", &GooseFEM::MatrixPartitionedSolver<>::template solve_u, "Solve system.", py::arg("A"), py::arg("b_u"), py::arg("x_p"), py::arg("x_u")); slv.def("__repr__", [](const GooseFEM::MatrixPartitionedSolver<>&) { return ""; }); } diff --git a/python/MatrixPartitionedTyings.hpp b/python/MatrixPartitionedTyings.hpp index 16b0cb6..b4d7d4c 100644 --- a/python/MatrixPartitionedTyings.hpp +++ b/python/MatrixPartitionedTyings.hpp @@ -1,93 +1,93 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include #include #include #include #include "Matrix.hpp" namespace py = pybind11; void init_MatrixPartitionedTyings(py::module& m) { py::class_ cls(m, "MatrixPartitionedTyings"); register_Matrix_MatrixBase(cls); register_Matrix_MatrixPartitionedBase(cls); register_Matrix_MatrixPartitionedTyingsBase(cls); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const Eigen::SparseMatrix&, const Eigen::SparseMatrix&>(), "See :cpp:class:`GooseFEM::MatrixPartitionedTyings`.", py::arg("conn"), py::arg("dofs"), py::arg("Cdu"), py::arg("Cdp")); cls.def_property_readonly("data_uu", &GooseFEM::MatrixPartitionedTyings::data_uu); cls.def_property_readonly("data_up", &GooseFEM::MatrixPartitionedTyings::data_up); cls.def_property_readonly("data_pu", &GooseFEM::MatrixPartitionedTyings::data_pu); cls.def_property_readonly("data_pp", &GooseFEM::MatrixPartitionedTyings::data_pp); cls.def_property_readonly("data_ud", &GooseFEM::MatrixPartitionedTyings::data_ud); cls.def_property_readonly("data_pd", &GooseFEM::MatrixPartitionedTyings::data_pd); cls.def_property_readonly("data_du", &GooseFEM::MatrixPartitionedTyings::data_du); cls.def_property_readonly("data_dp", &GooseFEM::MatrixPartitionedTyings::data_dp); cls.def_property_readonly("data_dd", &GooseFEM::MatrixPartitionedTyings::data_dd); cls.def("__repr__", [](const GooseFEM::MatrixPartitionedTyings&) { return ""; }); py::class_> slv(m, "MatrixPartitionedTyingsSolver"); register_MatrixSolver_MatrixSolverBase< GooseFEM::MatrixPartitionedTyingsSolver<>, GooseFEM::MatrixPartitionedTyings>(slv); register_MatrixSolver_MatrixSolverPartitionedBase< GooseFEM::MatrixPartitionedTyingsSolver<>, GooseFEM::MatrixPartitionedTyings>(slv); slv.def(py::init<>(), "See :cpp:class:`GooseFEM::MatrixPartitionedTyingsSolver`."); slv.def( "Solve_u", py::overload_cast< GooseFEM::MatrixPartitionedTyings&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(&GooseFEM::MatrixPartitionedTyingsSolver<>::Solve_u), py::arg("matrix"), py::arg("b_u"), py::arg("b_d"), py::arg("x_p")); slv.def( "solve_u", py::overload_cast< GooseFEM::MatrixPartitionedTyings&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, xt::pytensor&>(&GooseFEM::MatrixPartitionedTyingsSolver<>::solve_u), py::arg("matrix"), py::arg("b_u"), py::arg("b_d"), py::arg("x_p"), py::arg("x_u")); slv.def("__repr__", [](const GooseFEM::MatrixPartitionedTyingsSolver<>&) { return ""; }); } diff --git a/python/Mesh.hpp b/python/Mesh.hpp index 2d1422f..1761dd9 100644 --- a/python/Mesh.hpp +++ b/python/Mesh.hpp @@ -1,545 +1,545 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_MESH_H #define PYGOOSEFEM_MESH_H #include #include #include #include #include namespace py = pybind11; template void register_Mesh_RegularBase(P& cls) { cls.def_property_readonly("nelem", &C::nelem); cls.def_property_readonly("nnode", &C::nnode); cls.def_property_readonly("nne", &C::nne); cls.def_property_readonly("ndim", &C::ndim); cls.def_property_readonly("nelx", &C::nelx); cls.def_property_readonly("nely", &C::nely); cls.def_property_readonly("h", &C::h); cls.def_property_readonly("elementType", &C::getElementType); cls.def("coor", &C::coor); cls.def("conn", &C::conn); cls.def("dofs", &C::dofs); cls.def("dofsPeriodic", &C::dofsPeriodic); cls.def("nodesPeriodic", &C::nodesPeriodic); cls.def("nodesOrigin", &C::nodesOrigin); } template void register_Mesh_RegularBase2d(P& cls) { cls.def("nodesBottomEdge", &C::nodesBottomEdge); cls.def("nodesTopEdge", &C::nodesTopEdge); cls.def("nodesLeftEdge", &C::nodesLeftEdge); cls.def("nodesRightEdge", &C::nodesRightEdge); cls.def("nodesBottomOpenEdge", &C::nodesBottomOpenEdge); cls.def("nodesTopOpenEdge", &C::nodesTopOpenEdge); cls.def("nodesLeftOpenEdge", &C::nodesLeftOpenEdge); cls.def("nodesRightOpenEdge", &C::nodesRightOpenEdge); cls.def("nodesBottomLeftCorner", &C::nodesBottomLeftCorner); cls.def("nodesBottomRightCorner", &C::nodesBottomRightCorner); cls.def("nodesTopLeftCorner", &C::nodesTopLeftCorner); cls.def("nodesTopRightCorner", &C::nodesTopRightCorner); cls.def("nodesLeftBottomCorner", &C::nodesLeftBottomCorner); cls.def("nodesLeftTopCorner", &C::nodesLeftTopCorner); cls.def("nodesRightBottomCorner", &C::nodesRightBottomCorner); cls.def("nodesRightTopCorner", &C::nodesRightTopCorner); } template void register_Mesh_RegularBase3d(P& cls) { cls.def("nodesFront", &C::nodesFront); cls.def("nodesBack", &C::nodesBack); cls.def("nodesLeft", &C::nodesLeft); cls.def("nodesRight", &C::nodesRight); cls.def("nodesBottom", &C::nodesBottom); cls.def("nodesTop", &C::nodesTop); cls.def("nodesFrontFace", &C::nodesFrontFace); cls.def("nodesBackFace", &C::nodesBackFace); cls.def("nodesLeftFace", &C::nodesLeftFace); cls.def("nodesRightFace", &C::nodesRightFace); cls.def("nodesBottomFace", &C::nodesBottomFace); cls.def("nodesTopFace", &C::nodesTopFace); cls.def("nodesFrontBottomEdge", &C::nodesFrontBottomEdge); cls.def("nodesFrontTopEdge", &C::nodesFrontTopEdge); cls.def("nodesFrontLeftEdge", &C::nodesFrontLeftEdge); cls.def("nodesFrontRightEdge", &C::nodesFrontRightEdge); cls.def("nodesBackBottomEdge", &C::nodesBackBottomEdge); cls.def("nodesBackTopEdge", &C::nodesBackTopEdge); cls.def("nodesBackLeftEdge", &C::nodesBackLeftEdge); cls.def("nodesBackRightEdge", &C::nodesBackRightEdge); cls.def("nodesBottomLeftEdge", &C::nodesBottomLeftEdge); cls.def("nodesBottomRightEdge", &C::nodesBottomRightEdge); cls.def("nodesTopLeftEdge", &C::nodesTopLeftEdge); cls.def("nodesTopRightEdge", &C::nodesTopRightEdge); cls.def("nodesBottomFrontEdge", &C::nodesBottomFrontEdge); cls.def("nodesBottomBackEdge", &C::nodesBottomBackEdge); cls.def("nodesTopFrontEdge", &C::nodesTopFrontEdge); cls.def("nodesTopBackEdge", &C::nodesTopBackEdge); cls.def("nodesLeftBottomEdge", &C::nodesLeftBottomEdge); cls.def("nodesLeftFrontEdge", &C::nodesLeftFrontEdge); cls.def("nodesLeftBackEdge", &C::nodesLeftBackEdge); cls.def("nodesLeftTopEdge", &C::nodesLeftTopEdge); cls.def("nodesRightBottomEdge", &C::nodesRightBottomEdge); cls.def("nodesRightTopEdge", &C::nodesRightTopEdge); cls.def("nodesRightFrontEdge", &C::nodesRightFrontEdge); cls.def("nodesRightBackEdge", &C::nodesRightBackEdge); cls.def("nodesFrontBottomOpenEdge", &C::nodesFrontBottomOpenEdge); cls.def("nodesFrontTopOpenEdge", &C::nodesFrontTopOpenEdge); cls.def("nodesFrontLeftOpenEdge", &C::nodesFrontLeftOpenEdge); cls.def("nodesFrontRightOpenEdge", &C::nodesFrontRightOpenEdge); cls.def("nodesBackBottomOpenEdge", &C::nodesBackBottomOpenEdge); cls.def("nodesBackTopOpenEdge", &C::nodesBackTopOpenEdge); cls.def("nodesBackLeftOpenEdge", &C::nodesBackLeftOpenEdge); cls.def("nodesBackRightOpenEdge", &C::nodesBackRightOpenEdge); cls.def("nodesBottomLeftOpenEdge", &C::nodesBottomLeftOpenEdge); cls.def("nodesBottomRightOpenEdge", &C::nodesBottomRightOpenEdge); cls.def("nodesTopLeftOpenEdge", &C::nodesTopLeftOpenEdge); cls.def("nodesTopRightOpenEdge", &C::nodesTopRightOpenEdge); cls.def("nodesBottomFrontOpenEdge", &C::nodesBottomFrontOpenEdge); cls.def("nodesBottomBackOpenEdge", &C::nodesBottomBackOpenEdge); cls.def("nodesTopFrontOpenEdge", &C::nodesTopFrontOpenEdge); cls.def("nodesTopBackOpenEdge", &C::nodesTopBackOpenEdge); cls.def("nodesLeftBottomOpenEdge", &C::nodesLeftBottomOpenEdge); cls.def("nodesLeftFrontOpenEdge", &C::nodesLeftFrontOpenEdge); cls.def("nodesLeftBackOpenEdge", &C::nodesLeftBackOpenEdge); cls.def("nodesLeftTopOpenEdge", &C::nodesLeftTopOpenEdge); cls.def("nodesRightBottomOpenEdge", &C::nodesRightBottomOpenEdge); cls.def("nodesRightTopOpenEdge", &C::nodesRightTopOpenEdge); cls.def("nodesRightFrontOpenEdge", &C::nodesRightFrontOpenEdge); cls.def("nodesRightBackOpenEdge", &C::nodesRightBackOpenEdge); cls.def("nodesFrontBottomLeftCorner", &C::nodesFrontBottomLeftCorner); cls.def("nodesFrontBottomRightCorner", &C::nodesFrontBottomRightCorner); cls.def("nodesFrontTopLeftCorner", &C::nodesFrontTopLeftCorner); cls.def("nodesFrontTopRightCorner", &C::nodesFrontTopRightCorner); cls.def("nodesBackBottomLeftCorner", &C::nodesBackBottomLeftCorner); cls.def("nodesBackBottomRightCorner", &C::nodesBackBottomRightCorner); cls.def("nodesBackTopLeftCorner", &C::nodesBackTopLeftCorner); cls.def("nodesBackTopRightCorner", &C::nodesBackTopRightCorner); cls.def("nodesFrontLeftBottomCorner", &C::nodesFrontLeftBottomCorner); cls.def("nodesBottomFrontLeftCorner", &C::nodesBottomFrontLeftCorner); cls.def("nodesBottomLeftFrontCorner", &C::nodesBottomLeftFrontCorner); cls.def("nodesLeftFrontBottomCorner", &C::nodesLeftFrontBottomCorner); cls.def("nodesLeftBottomFrontCorner", &C::nodesLeftBottomFrontCorner); cls.def("nodesFrontRightBottomCorner", &C::nodesFrontRightBottomCorner); cls.def("nodesBottomFrontRightCorner", &C::nodesBottomFrontRightCorner); cls.def("nodesBottomRightFrontCorner", &C::nodesBottomRightFrontCorner); cls.def("nodesRightFrontBottomCorner", &C::nodesRightFrontBottomCorner); cls.def("nodesRightBottomFrontCorner", &C::nodesRightBottomFrontCorner); cls.def("nodesFrontLeftTopCorner", &C::nodesFrontLeftTopCorner); cls.def("nodesTopFrontLeftCorner", &C::nodesTopFrontLeftCorner); cls.def("nodesTopLeftFrontCorner", &C::nodesTopLeftFrontCorner); cls.def("nodesLeftFrontTopCorner", &C::nodesLeftFrontTopCorner); cls.def("nodesLeftTopFrontCorner", &C::nodesLeftTopFrontCorner); cls.def("nodesFrontRightTopCorner", &C::nodesFrontRightTopCorner); cls.def("nodesTopFrontRightCorner", &C::nodesTopFrontRightCorner); cls.def("nodesTopRightFrontCorner", &C::nodesTopRightFrontCorner); cls.def("nodesRightFrontTopCorner", &C::nodesRightFrontTopCorner); cls.def("nodesRightTopFrontCorner", &C::nodesRightTopFrontCorner); cls.def("nodesBackLeftBottomCorner", &C::nodesBackLeftBottomCorner); cls.def("nodesBottomBackLeftCorner", &C::nodesBottomBackLeftCorner); cls.def("nodesBottomLeftBackCorner", &C::nodesBottomLeftBackCorner); cls.def("nodesLeftBackBottomCorner", &C::nodesLeftBackBottomCorner); cls.def("nodesLeftBottomBackCorner", &C::nodesLeftBottomBackCorner); cls.def("nodesBackRightBottomCorner", &C::nodesBackRightBottomCorner); cls.def("nodesBottomBackRightCorner", &C::nodesBottomBackRightCorner); cls.def("nodesBottomRightBackCorner", &C::nodesBottomRightBackCorner); cls.def("nodesRightBackBottomCorner", &C::nodesRightBackBottomCorner); cls.def("nodesRightBottomBackCorner", &C::nodesRightBottomBackCorner); cls.def("nodesBackLeftTopCorner", &C::nodesBackLeftTopCorner); cls.def("nodesTopBackLeftCorner", &C::nodesTopBackLeftCorner); cls.def("nodesTopLeftBackCorner", &C::nodesTopLeftBackCorner); cls.def("nodesLeftBackTopCorner", &C::nodesLeftBackTopCorner); cls.def("nodesLeftTopBackCorner", &C::nodesLeftTopBackCorner); cls.def("nodesBackRightTopCorner", &C::nodesBackRightTopCorner); cls.def("nodesTopBackRightCorner", &C::nodesTopBackRightCorner); cls.def("nodesTopRightBackCorner", &C::nodesTopRightBackCorner); cls.def("nodesRightBackTopCorner", &C::nodesRightBackTopCorner); cls.def("nodesRightTopBackCorner", &C::nodesRightTopBackCorner); } void init_Mesh(py::module& mod) { py::enum_( mod, "ElementType", "See :cpp:enum:`GooseFEM::Mesh::ElementType`.") .value("Unknown", GooseFEM::Mesh::ElementType::Unknown) .value("Tri3", GooseFEM::Mesh::ElementType::Tri3) .value("Quad4", GooseFEM::Mesh::ElementType::Quad4) .value("Hex8", GooseFEM::Mesh::ElementType::Hex8) .export_values(); mod.def( "overlapping", &GooseFEM::Mesh::overlapping, xt::pytensor>, "See :cpp:func:`GooseFEM::Mesh::overlapping`.", py::arg("coor_a"), py::arg("coor_b"), py::arg("rtol") = 1e-5, py::arg("atol") = 1e-8); py::class_(mod, "ManualStitch") .def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, bool, double, double>(), "See :cpp:class:`GooseFEM::Mesh::ManualStitch`.", py::arg("coor_a"), py::arg("conn_a"), py::arg("overlapping_nodes_a"), py::arg("coor_b"), py::arg("conn_b"), py::arg("overlapping_nodes_b"), py::arg("check_position") = true, py::arg("rtol") = 1e-5, py::arg("atol") = 1e-8) .def_property_readonly("nmesh", &GooseFEM::Mesh::ManualStitch::nmesh) .def_property_readonly("nnode", &GooseFEM::Mesh::ManualStitch::nnode) .def_property_readonly("nelem", &GooseFEM::Mesh::ManualStitch::nelem) .def_property_readonly("ndim", &GooseFEM::Mesh::ManualStitch::ndim) .def_property_readonly("nne", &GooseFEM::Mesh::ManualStitch::nne) .def_property_readonly("coor", &GooseFEM::Mesh::ManualStitch::coor) .def_property_readonly("conn", &GooseFEM::Mesh::ManualStitch::conn) .def("dofs", &GooseFEM::Mesh::ManualStitch::dofs) .def("nodemap", py::overload_cast<>(&GooseFEM::Mesh::ManualStitch::nodemap, py::const_)) .def("elemmap", py::overload_cast<>(&GooseFEM::Mesh::ManualStitch::elemmap, py::const_)) .def( "nodemap", py::overload_cast(&GooseFEM::Mesh::ManualStitch::nodemap, py::const_), py::arg("mesh_index")) .def( "elemmap", py::overload_cast(&GooseFEM::Mesh::ManualStitch::elemmap, py::const_), py::arg("mesh_index")) .def( "nodeset", &GooseFEM::Mesh::ManualStitch::nodeset>, py::arg("set"), py::arg("mesh_index")) .def( "elemset", &GooseFEM::Mesh::ManualStitch::elemset>, py::arg("set"), py::arg("mesh_index")) .def("__repr__", [](const GooseFEM::Mesh::ManualStitch&) { return ""; }); py::class_(mod, "Stitch") .def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Stitch`.", py::arg("rtol") = 1e-5, py::arg("atol") = 1e-8) .def( "push_back", &GooseFEM::Mesh::Stitch::push_back, xt::pytensor>, py::arg("coor"), py::arg("conn")) .def_property_readonly("nmesh", &GooseFEM::Mesh::Stitch::nmesh) .def_property_readonly("nnode", &GooseFEM::Mesh::Stitch::nnode) .def_property_readonly("nelem", &GooseFEM::Mesh::Stitch::nelem) .def_property_readonly("ndim", &GooseFEM::Mesh::Stitch::ndim) .def_property_readonly("nne", &GooseFEM::Mesh::Stitch::nne) .def_property_readonly("coor", &GooseFEM::Mesh::Stitch::coor) .def_property_readonly("conn", &GooseFEM::Mesh::Stitch::conn) .def("dofs", &GooseFEM::Mesh::Stitch::dofs) .def("nodemap", py::overload_cast<>(&GooseFEM::Mesh::Stitch::nodemap, py::const_)) .def("elemmap", py::overload_cast<>(&GooseFEM::Mesh::Stitch::elemmap, py::const_)) .def( "nodemap", py::overload_cast(&GooseFEM::Mesh::Stitch::nodemap, py::const_), py::arg("mesh_index")) .def( "elemmap", py::overload_cast(&GooseFEM::Mesh::Stitch::elemmap, py::const_), py::arg("mesh_index")) .def( "nodeset", static_cast (GooseFEM::Mesh::Stitch::*)( const xt::pytensor&, size_t) const>(&GooseFEM::Mesh::Stitch::nodeset), py::arg("set"), py::arg("mesh_index")) .def( "elemset", static_cast (GooseFEM::Mesh::Stitch::*)( const xt::pytensor&, size_t) const>(&GooseFEM::Mesh::Stitch::elemset), py::arg("set"), py::arg("mesh_index")) .def( "nodeset", static_cast (GooseFEM::Mesh::Stitch::*)( const std::vector>&) const>( &GooseFEM::Mesh::Stitch::nodeset), py::arg("set")) .def( "elemset", static_cast (GooseFEM::Mesh::Stitch::*)( const std::vector>&) const>( &GooseFEM::Mesh::Stitch::elemset), py::arg("set")) .def("__repr__", [](const GooseFEM::Mesh::Stitch&) { return ""; }); py::class_(mod, "Vstack") .def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Vstack`.", py::arg("check_overlap") = true, py::arg("rtol") = 1e-5, py::arg("atol") = 1e-8) .def( "push_back", &GooseFEM::Mesh::Vstack::push_back< xt::pytensor, xt::pytensor, xt::pytensor>, py::arg("coor"), py::arg("conn"), py::arg("nodes_bottom"), py::arg("nodes_top")) .def("__repr__", [](const GooseFEM::Mesh::Vstack&) { return ""; }); py::class_(mod, "Renumber") .def( py::init&>(), "See :cpp:class:`GooseFEM::Mesh::Renumber`.", py::arg("dofs")) .def("apply", &GooseFEM::Mesh::Renumber::apply>) .def_property_readonly("index", &GooseFEM::Mesh::Renumber::index) .def( "__repr__", [](const GooseFEM::Mesh::Renumber&) { return ""; }); py::class_(mod, "Reorder") .def( py::init([](xt::pytensor& a) { return new GooseFEM::Mesh::Reorder({a}); }), "See :cpp:class:`GooseFEM::Mesh::Reorder`.") .def( py::init([](xt::pytensor& a, xt::pytensor& b) { return new GooseFEM::Mesh::Reorder({a, b}); }), "See :cpp:class:`GooseFEM::Mesh::Reorder`.") .def( py::init([](xt::pytensor& a, xt::pytensor& b, xt::pytensor& c) { return new GooseFEM::Mesh::Reorder({a, b, c}); }), "See :cpp:class:`GooseFEM::Mesh::Reorder`.") .def( py::init([](xt::pytensor& a, xt::pytensor& b, xt::pytensor& c, xt::pytensor& d) { return new GooseFEM::Mesh::Reorder({a, b, c, d}); }), "See :cpp:class:`GooseFEM::Mesh::Reorder`.") .def("apply", &GooseFEM::Mesh::Reorder::apply>) .def_property_readonly("index", &GooseFEM::Mesh::Reorder::index) .def("__repr__", [](const GooseFEM::Mesh::Reorder&) { return ""; }); mod.def( "dofs", &GooseFEM::Mesh::dofs, "See :cpp:func:`GooseFEM::Mesh::dofs`.", py::arg("nnode"), py::arg("ndim")); mod.def( "nodaltyings", &GooseFEM::Mesh::nodaltyings>, "See :cpp:func:`GooseFEM::Mesh::nodaltyings`.", py::arg("dofs")); mod.def( "renumber", &GooseFEM::Mesh::renumber>, "See :cpp:func:`GooseFEM::Mesh::renumber`.", py::arg("dofs")); mod.def( "coordination", &GooseFEM::Mesh::coordination>, "See :cpp:func:`GooseFEM::Mesh::coordination`.", py::arg("conn")); mod.def( "node2dof", &GooseFEM::Mesh::node2dof>, "See :cpp:func:`GooseFEM::Mesh::node2dof`.", py::arg("dofs"), py::arg("sorted") = true); mod.def( "elem2node", py::overload_cast&, bool>( &GooseFEM::Mesh::elem2node>), "See :cpp:func:`GooseFEM::Mesh::elem2node`.", py::arg("conn"), py::arg("sorted") = true); mod.def( "elem2node", py::overload_cast&, const xt::pytensor&, bool>( &GooseFEM::Mesh::elem2node, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::elem2node`.", py::arg("conn"), py::arg("dofs"), py::arg("sorted") = true); mod.def( "edgesize", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::edgesize, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::edgesize`.", py::arg("coor"), py::arg("conn")); mod.def( "edgesize", py::overload_cast< const xt::pytensor&, const xt::pytensor&, GooseFEM::Mesh::ElementType>( &GooseFEM::Mesh::edgesize, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::edgesize`.", py::arg("coor"), py::arg("conn"), py::arg("type")); mod.def( "centers", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::centers, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::centers`.", py::arg("coor"), py::arg("conn")); mod.def( "centers", py::overload_cast< const xt::pytensor&, const xt::pytensor&, GooseFEM::Mesh::ElementType>( &GooseFEM::Mesh::centers, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::centers`.", py::arg("coor"), py::arg("conn"), py::arg("type")); mod.def( "elemmap2nodemap", py::overload_cast< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(&GooseFEM::Mesh::elemmap2nodemap< xt::pytensor, xt::pytensor, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::elemmap2nodemap`.", py::arg("elem_map"), py::arg("coor"), py::arg("conn")); mod.def( "elemmap2nodemap", py::overload_cast< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, GooseFEM::Mesh::ElementType>(&GooseFEM::Mesh::elemmap2nodemap< xt::pytensor, xt::pytensor, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::elemmap2nodemap`.", py::arg("elem_map"), py::arg("coor"), py::arg("conn"), py::arg("type")); mod.def( "nodal_mass", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::nodal_mass, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::nodal_mass`.", py::arg("coor"), py::arg("conn")); mod.def( "nodal_mass", py::overload_cast< const xt::pytensor&, const xt::pytensor&, GooseFEM::Mesh::ElementType>( &GooseFEM::Mesh::nodal_mass, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::nodal_mass`.", py::arg("coor"), py::arg("conn"), py::arg("type")); mod.def( "center_of_gravity", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::center_of_gravity, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::center_of_gravity`.", py::arg("coor"), py::arg("conn")); mod.def( "center_of_gravity", py::overload_cast< const xt::pytensor&, const xt::pytensor&, GooseFEM::Mesh::ElementType>( &GooseFEM::Mesh::center_of_gravity, xt::pytensor>), "See :cpp:func:`GooseFEM::Mesh::center_of_gravity`.", py::arg("coor"), py::arg("conn"), py::arg("type")); } #endif diff --git a/python/MeshHex8.hpp b/python/MeshHex8.hpp index a64c273..2d7566e 100644 --- a/python/MeshHex8.hpp +++ b/python/MeshHex8.hpp @@ -1,61 +1,61 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_MESHHEX8_H #define PYGOOSEFEM_MESHHEX8_H #include #include #include #include #include #include "Mesh.hpp" namespace py = pybind11; void init_MeshHex8(py::module& mod) { { py::class_ cls(mod, "Regular"); cls.def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Hex8::Regular`.", py::arg("nx"), py::arg("ny"), py::arg("nz"), py::arg("h") = 1.); register_Mesh_RegularBase(cls); register_Mesh_RegularBase3d(cls); cls.def("__repr__", [](const GooseFEM::Mesh::Hex8::Regular&) { return ""; }); } { py::class_ cls(mod, "FineLayer"); cls.def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Hex8::FineLayer`.", py::arg("nx"), py::arg("ny"), py::arg("nz"), py::arg("h") = 1.0, py::arg("nfine") = 1); cls.def("elementsMiddleLayer", &GooseFEM::Mesh::Hex8::FineLayer::elementsMiddleLayer); cls.def("__repr__", [](const GooseFEM::Mesh::Hex8::FineLayer&) { return ""; }); } } #endif diff --git a/python/MeshQuad4.hpp b/python/MeshQuad4.hpp index f213195..c5831e4 100644 --- a/python/MeshQuad4.hpp +++ b/python/MeshQuad4.hpp @@ -1,234 +1,234 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_MESHQUAD4_H #define PYGOOSEFEM_MESHQUAD4_H #include #include #include #include #include #include "Mesh.hpp" namespace py = pybind11; void init_MeshQuad4(py::module& m) { { py::class_ cls(m, "Regular"); cls.def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Quad4::Regular`.", py::arg("nx"), py::arg("ny"), py::arg("h") = 1.0); register_Mesh_RegularBase(cls); register_Mesh_RegularBase2d(cls); cls.def("elementgrid", &GooseFEM::Mesh::Quad4::Regular::elementgrid); cls.def("__repr__", [](const GooseFEM::Mesh::Quad4::Regular&) { return ""; }); } { py::class_ cls(m, "FineLayer"); cls.def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Quad4::FineLayer`.", py::arg("nx"), py::arg("ny"), py::arg("h") = 1., py::arg("nfine") = 1); cls.def( py::init&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Mesh::Quad4::FineLayer`.", py::arg("coor"), py::arg("conn")); register_Mesh_RegularBase(cls); register_Mesh_RegularBase2d(cls); cls.def_property_readonly("elemrow_nhx", &GooseFEM::Mesh::Quad4::FineLayer::elemrow_nhx); cls.def_property_readonly("elemrow_nhy", &GooseFEM::Mesh::Quad4::FineLayer::elemrow_nhy); cls.def_property_readonly("elemrow_type", &GooseFEM::Mesh::Quad4::FineLayer::elemrow_type); cls.def_property_readonly( "elemrow_nelem", &GooseFEM::Mesh::Quad4::FineLayer::elemrow_nelem); cls.def("elementsMiddleLayer", &GooseFEM::Mesh::Quad4::FineLayer::elementsMiddleLayer); cls.def( "elementsLayer", &GooseFEM::Mesh::Quad4::FineLayer::elementsLayer, py::arg("layer")); cls.def( "elementgrid_ravel", &GooseFEM::Mesh::Quad4::FineLayer::elementgrid_ravel, py::arg("rows_range"), py::arg("cols_range")); cls.def( "elementgrid_around_ravel", &GooseFEM::Mesh::Quad4::FineLayer::elementgrid_around_ravel, py::arg("element"), py::arg("size"), py::arg("periodic") = true); cls.def( "elementgrid_leftright", &GooseFEM::Mesh::Quad4::FineLayer::elementgrid_leftright, py::arg("element"), py::arg("left"), py::arg("right"), py::arg("periodic") = true); cls.def("roll", &GooseFEM::Mesh::Quad4::FineLayer::roll); cls.def("__repr__", [](const GooseFEM::Mesh::Quad4::FineLayer&) { return ""; }); } } void init_MeshQuad4Map(py::module& m) { py::class_(m, "RefineRegular") .def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Quad4::Map::RefineRegular`.", py::arg("mesh"), py::arg("nx"), py::arg("ny")) .def_property_readonly("coarseMesh", &GooseFEM::Mesh::Quad4::Map::RefineRegular::coarseMesh) .def_property_readonly("fineMesh", &GooseFEM::Mesh::Quad4::Map::RefineRegular::fineMesh) .def_property_readonly("map", &GooseFEM::Mesh::Quad4::Map::RefineRegular::map) .def( "meanToCoarse", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::meanToCoarse, py::const_)) .def( "meanToCoarse", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::meanToCoarse, py::const_)) .def( "meanToCoarse", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::meanToCoarse, py::const_)) .def( "meanToCoarse", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::meanToCoarse, py::const_)) .def( "averageToCoarse", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::averageToCoarse, py::const_)) .def( "averageToCoarse", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::averageToCoarse, py::const_)) .def( "averageToCoarse", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::averageToCoarse, py::const_)) .def( "averageToCoarse", py::overload_cast&, const xt::pytensor&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::averageToCoarse, py::const_)) .def( "mapToFine", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_)) .def( "mapToFine", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_)) .def( "mapToFine", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_)) .def( "mapToFine", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_)) .def("__repr__", [](const GooseFEM::Mesh::Quad4::Map::RefineRegular&) { return ""; }); py::class_(m, "FineLayer2Regular") .def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Quad4::Map::FineLayer2Regular`.", py::arg("mesh")) .def_property_readonly( "regularMesh", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::regularMesh) .def_property_readonly( "fineLayerMesh", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::fineLayerMesh) .def_property_readonly("map", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::map) .def_property_readonly( "mapFraction", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapFraction) .def( "mapToRegular", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_)) .def( "mapToRegular", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_)) .def( "mapToRegular", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_)) .def( "mapToRegular", py::overload_cast&>( &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_)) .def("__repr__", [](const GooseFEM::Mesh::Quad4::Map::FineLayer2Regular&) { return ""; }); } #endif diff --git a/python/MeshTri3.hpp b/python/MeshTri3.hpp index 9dd9d49..13ce541 100644 --- a/python/MeshTri3.hpp +++ b/python/MeshTri3.hpp @@ -1,68 +1,68 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_MESHTRI3_H #define PYGOOSEFEM_MESHTRI3_H #include #include #include #include #include #include "Mesh.hpp" namespace py = pybind11; void init_MeshTri3(py::module& mod) { py::class_ cls(mod, "Regular"); cls.def( py::init(), "See :cpp:class:`GooseFEM::Mesh::Tri3::Regular`.", py::arg("nx"), py::arg("ny"), py::arg("h") = 1.); register_Mesh_RegularBase(cls); register_Mesh_RegularBase2d(cls); cls.def("__repr__", [](const GooseFEM::Mesh::Tri3::Regular&) { return ""; }); mod.def( "getOrientation", &GooseFEM::Mesh::Tri3::getOrientation, "See :cpp:func:`GooseFEM::Mesh::Tri3::getOrientation`.", py::arg("coor"), py::arg("conn")); mod.def( "setOrientation", py::overload_cast&, const xt::pytensor&, int>( &GooseFEM::Mesh::Tri3::setOrientation), "See :cpp:func:`GooseFEM::Mesh::Tri3::setOrientation`.", py::arg("coor"), py::arg("conn"), py::arg("orientation")); mod.def( "setOrientation", py::overload_cast< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, int>(&GooseFEM::Mesh::Tri3::setOrientation), "See :cpp:func:`GooseFEM::Mesh::Tri3::setOrientation`.", py::arg("coor"), py::arg("conn"), py::arg("val"), py::arg("orientation")); } #endif diff --git a/python/TyingsPeriodic.hpp b/python/TyingsPeriodic.hpp index 117b13d..5541bce 100644 --- a/python/TyingsPeriodic.hpp +++ b/python/TyingsPeriodic.hpp @@ -1,89 +1,89 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #ifndef PYGOOSEFEM_TYINGSPERIODIC_H #define PYGOOSEFEM_TYINGSPERIODIC_H #include #include #include #include #include namespace py = pybind11; void init_TyingsPeriodic(py::module& mod) { { py::class_ cls(mod, "Periodic"); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Tyings::Periodic`.", py::arg("coor"), py::arg("dofs"), py::arg("control_dofs"), py::arg("nodal_tyings")); cls.def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Tyings::Periodic`.", py::arg("coor"), py::arg("dofs"), py::arg("control_dofs"), py::arg("nodal_tyings"), py::arg("iip")); cls.def_property_readonly("nnd", &GooseFEM::Tyings::Periodic::nnd); cls.def_property_readonly("nni", &GooseFEM::Tyings::Periodic::nni); cls.def_property_readonly("nnu", &GooseFEM::Tyings::Periodic::nnu); cls.def_property_readonly("nnp", &GooseFEM::Tyings::Periodic::nnp); cls.def_property_readonly("dofs", &GooseFEM::Tyings::Periodic::dofs); cls.def_property_readonly("control", &GooseFEM::Tyings::Periodic::control); cls.def_property_readonly("nodal_tyings", &GooseFEM::Tyings::Periodic::nodal_tyings); cls.def("iid", &GooseFEM::Tyings::Periodic::iid); cls.def("iii", &GooseFEM::Tyings::Periodic::iii); cls.def("iiu", &GooseFEM::Tyings::Periodic::iiu); cls.def("iip", &GooseFEM::Tyings::Periodic::iip); cls.def("Cdi", &GooseFEM::Tyings::Periodic::Cdi); cls.def("Cdu", &GooseFEM::Tyings::Periodic::Cdu); cls.def("Cdp", &GooseFEM::Tyings::Periodic::Cdp); cls.def("__repr__", [](const GooseFEM::Tyings::Periodic&) { return ""; }); } { py::class_ cls(mod, "Control"); cls.def( py::init&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Tyings::Control`.", py::arg("coor"), py::arg("dofs")); cls.def_property_readonly("coor", &GooseFEM::Tyings::Control::coor); cls.def_property_readonly("dofs", &GooseFEM::Tyings::Control::dofs); cls.def_property_readonly("controlDofs", &GooseFEM::Tyings::Control::controlDofs); cls.def_property_readonly("controlNodes", &GooseFEM::Tyings::Control::controlNodes); cls.def("__repr__", [](const GooseFEM::Tyings::Control&) { return ""; }); } } #endif diff --git a/python/Vector.hpp b/python/Vector.hpp index 1df882e..02b0fe2 100644 --- a/python/Vector.hpp +++ b/python/Vector.hpp @@ -1,121 +1,121 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include #include namespace py = pybind11; void init_Vector(py::module& m) { py::class_(m, "Vector") .def( py::init&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::Vector`.", py::arg("conn"), py::arg("dofs")) .def_property_readonly("nelem", &GooseFEM::Vector::nelem) .def_property_readonly("nne", &GooseFEM::Vector::nne) .def_property_readonly("nnode", &GooseFEM::Vector::nnode) .def_property_readonly("ndim", &GooseFEM::Vector::ndim) .def_property_readonly("ndof", &GooseFEM::Vector::ndof) .def_property_readonly("conn", &GooseFEM::Vector::conn) .def_property_readonly("dofs", &GooseFEM::Vector::dofs) .def( "copy", &GooseFEM::Vector::copy>, py::arg("nodevec_src"), py::arg("nodevec_dest")) .def( "Copy", &GooseFEM::Vector::Copy>, py::arg("nodevec_src"), py::arg("nodevec_dest")) .def("AsDofs", &GooseFEM::Vector::AsDofs>, py::arg("arg")) .def( "asDofs", &GooseFEM::Vector::asDofs, xt::pytensor>, py::arg("arg"), py::arg("ret")) .def("AsNode", &GooseFEM::Vector::AsNode>, py::arg("arg")) .def( "asNode", &GooseFEM::Vector::asNode, xt::pytensor>, py::arg("arg"), py::arg("ret")) .def("AsElement", &GooseFEM::Vector::AsElement>, py::arg("arg")) .def( "asElement", &GooseFEM::Vector::asElement, xt::pytensor>, py::arg("arg"), py::arg("ret")) .def("AssembleDofs", &GooseFEM::Vector::AssembleDofs>, py::arg("arg")) .def( "assembleDofs", &GooseFEM::Vector::assembleDofs, xt::pytensor>, py::arg("arg"), py::arg("ret")) .def("AssembleNode", &GooseFEM::Vector::AssembleNode>, py::arg("arg")) .def( "assembleNode", &GooseFEM::Vector::assembleNode, xt::pytensor>, py::arg("arg"), py::arg("ret")) .def("shape_dofval", &GooseFEM::Vector::shape_dofval) .def("shape_nodevec", &GooseFEM::Vector::shape_nodevec) .def("shape_elemvec", &GooseFEM::Vector::shape_elemvec) .def("shape_elemmat", &GooseFEM::Vector::shape_elemmat) .def("allocate_dofval", py::overload_cast<>(&GooseFEM::Vector::allocate_dofval, py::const_)) .def( "allocate_dofval", py::overload_cast(&GooseFEM::Vector::allocate_dofval, py::const_)) .def( "allocate_nodevec", py::overload_cast<>(&GooseFEM::Vector::allocate_nodevec, py::const_)) .def( "allocate_nodevec", py::overload_cast(&GooseFEM::Vector::allocate_nodevec, py::const_)) .def( "allocate_elemvec", py::overload_cast<>(&GooseFEM::Vector::allocate_elemvec, py::const_)) .def( "allocate_elemvec", py::overload_cast(&GooseFEM::Vector::allocate_elemvec, py::const_)) .def( "allocate_elemmat", py::overload_cast<>(&GooseFEM::Vector::allocate_elemmat, py::const_)) .def( "allocate_elemmat", py::overload_cast(&GooseFEM::Vector::allocate_elemmat, py::const_)) .def("__repr__", [](const GooseFEM::Vector&) { return ""; }); } diff --git a/python/VectorPartitioned.hpp b/python/VectorPartitioned.hpp index 9c618f7..a0161b8 100644 --- a/python/VectorPartitioned.hpp +++ b/python/VectorPartitioned.hpp @@ -1,105 +1,105 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include #include namespace py = pybind11; void init_VectorPartitioned(py::module& m) { py::class_(m, "VectorPartitioned") .def( py::init< const xt::pytensor&, const xt::pytensor&, const xt::pytensor&>(), "See :cpp:class:`GooseFEM::VectorPartitioned`.", py::arg("conn"), py::arg("dofs"), py::arg("iip")) .def_property_readonly("nnu", &GooseFEM::VectorPartitioned::nnu) .def_property_readonly("nnp", &GooseFEM::VectorPartitioned::nnp) .def_property_readonly("iiu", &GooseFEM::VectorPartitioned::iiu) .def_property_readonly("iip", &GooseFEM::VectorPartitioned::iip) .def("dofs_is_u", &GooseFEM::VectorPartitioned::dofs_is_u) .def("dofs_is_p", &GooseFEM::VectorPartitioned::dofs_is_p) .def( "DofsFromParitioned", py::overload_cast&, const xt::pytensor&>( &GooseFEM::VectorPartitioned::DofsFromParitioned, py::const_), py::arg("dofval_u"), py::arg("dofval_p")) .def( "AsDofs_u", py::overload_cast&>( &GooseFEM::VectorPartitioned::AsDofs_u, py::const_), py::arg("nodevec")) .def( "AsDofs_u", py::overload_cast&>( &GooseFEM::VectorPartitioned::AsDofs_u, py::const_), py::arg("elemvec")) .def( "AsDofs_p", py::overload_cast&>( &GooseFEM::VectorPartitioned::AsDofs_p, py::const_), py::arg("nodevec")) .def( "AsDofs_p", py::overload_cast&>( &GooseFEM::VectorPartitioned::AsDofs_p, py::const_), py::arg("elemvec")) .def( "NodeFromPartitioned", py::overload_cast&, const xt::pytensor&>( &GooseFEM::VectorPartitioned::NodeFromPartitioned, py::const_), py::arg("dofval_u"), py::arg("dofval_p")) .def( "ElementFromPartitioned", py::overload_cast&, const xt::pytensor&>( &GooseFEM::VectorPartitioned::ElementFromPartitioned, py::const_), py::arg("dofval_u"), py::arg("dofval_p")) .def( "Copy_u", &GooseFEM::VectorPartitioned::Copy_u, py::arg("nodevec_src"), py::arg("nodevec_dest")) .def( "Copy_p", &GooseFEM::VectorPartitioned::Copy_p, py::arg("nodevec_src"), py::arg("nodevec_dest")) .def( "copy_u", &GooseFEM::VectorPartitioned::copy_u, py::arg("nodevec_src"), py::arg("nodevec_dest")) .def( "copy_p", &GooseFEM::VectorPartitioned::copy_p, py::arg("nodevec_src"), py::arg("nodevec_dest")) .def("__repr__", [](const GooseFEM::VectorPartitioned&) { return ""; }); } diff --git a/python/VectorPartitionedTyings.hpp b/python/VectorPartitionedTyings.hpp index d08ee60..06763e8 100644 --- a/python/VectorPartitionedTyings.hpp +++ b/python/VectorPartitionedTyings.hpp @@ -1,64 +1,64 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include #include namespace py = pybind11; void init_VectorPartitionedTyings(py::module& m) { py::class_(m, "VectorPartitionedTyings") .def( py::init< const xt::pytensor&, const xt::pytensor&, const Eigen::SparseMatrix&, const Eigen::SparseMatrix&, const Eigen::SparseMatrix&>(), "See :cpp:class:`GooseFEM::VectorPartitionedTyings`.", py::arg("conn"), py::arg("dofs"), py::arg("Cdu"), py::arg("Cdp"), py::arg("Cdi")) .def_property_readonly("nnu", &GooseFEM::VectorPartitionedTyings::nnu) .def_property_readonly("nnp", &GooseFEM::VectorPartitionedTyings::nnp) .def_property_readonly("nni", &GooseFEM::VectorPartitionedTyings::nni) .def_property_readonly("nnd", &GooseFEM::VectorPartitionedTyings::nnd) .def_property_readonly("iiu", &GooseFEM::VectorPartitionedTyings::iiu) .def_property_readonly("iip", &GooseFEM::VectorPartitionedTyings::iip) .def_property_readonly("iii", &GooseFEM::VectorPartitionedTyings::iii) .def_property_readonly("iid", &GooseFEM::VectorPartitionedTyings::iid) .def( "copy_p", &GooseFEM::VectorPartitionedTyings::copy_p>, py::arg("dofval_src"), py::arg("dofval_dest")) .def( "asDofs_i", &GooseFEM::VectorPartitionedTyings:: asDofs_i, xt::pytensor>, py::arg("nodevec"), py::arg("dofval_i"), py::arg("apply_tyings") = true) .def( "AsDofs_i", &GooseFEM::VectorPartitionedTyings::AsDofs_i>, py::arg("nodevec")) .def("__repr__", [](const GooseFEM::VectorPartitionedTyings&) { return ""; }); } diff --git a/python/assertions.hpp b/python/assertions.hpp index 7f980b0..7f65cee 100644 --- a/python/assertions.hpp +++ b/python/assertions.hpp @@ -1,20 +1,20 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include #include namespace py = pybind11; void init_assertions(py::module& mod) { mod.def( "is_unique", &GooseFEM::is_unique>, "See :cpp:func:`GooseFEM::is_unique`.", py::arg("a")); } diff --git a/python/main.cpp b/python/main.cpp index 0647a3b..377f61b 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -1,180 +1,180 @@ /* ================================================================================================= (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM ================================================================================================= */ #include #include #include #define FORCE_IMPORT_ARRAY #include #include #include #define GOOSEFEM_USE_XTENSOR_PYTHON #include "Allocate.hpp" #include "Element.hpp" #include "ElementHex8.hpp" #include "ElementQuad4.hpp" #include "ElementQuad4Axisymmetric.hpp" #include "ElementQuad4Planar.hpp" #include "Iterate.hpp" #include "Matrix.hpp" #include "MatrixDiagonal.hpp" #include "MatrixDiagonalPartitioned.hpp" #include "MatrixPartitioned.hpp" #include "MatrixPartitionedTyings.hpp" #include "Mesh.hpp" #include "MeshHex8.hpp" #include "MeshQuad4.hpp" #include "MeshTri3.hpp" #include "TyingsPeriodic.hpp" #include "Vector.hpp" #include "VectorPartitioned.hpp" #include "VectorPartitionedTyings.hpp" #include "assertions.hpp" #include "version.hpp" namespace py = pybind11; /** -Overrides the `__name__` of a module. -Classes defined by pybind11 use the `__name__` of the module as of the time they are defined, -which affects the `__repr__` of the class type objects. -*/ + * Overrides the `__name__` of a module. + * Classes defined by pybind11 use the `__name__` of the module as of the time they are defined, + * which affects the `__repr__` of the class type objects. + */ class ScopedModuleNameOverride { public: explicit ScopedModuleNameOverride(py::module m, std::string name) : module_(std::move(m)) { original_name_ = module_.attr("__name__"); module_.attr("__name__") = name; } ~ScopedModuleNameOverride() { module_.attr("__name__") = original_name_; } private: py::module module_; py::object original_name_; }; PYBIND11_MODULE(_GooseFEM, m) { // Ensure members to display as `GooseFEM.X` (not `GooseFEM._GooseFEM.X`) ScopedModuleNameOverride name_override(m, "GooseFEM"); xt::import_numpy(); // -------- // GooseFEM // -------- m.doc() = "Some simple finite element meshes and operations"; init_version(m); init_assertions(m); init_Allocate(m); init_Vector(m); init_VectorPartitioned(m); init_VectorPartitionedTyings(m); init_Matrix(m); init_MatrixPartitioned(m); init_MatrixPartitionedTyings(m); init_MatrixDiagonal(m); init_MatrixDiagonalPartitioned(m); // ---------------- // GooseFEM.Iterate // ---------------- py::module mIterate = m.def_submodule("Iterate", "Iteration support tools"); init_Iterate(mIterate); // ---------------- // GooseFEM.Element // ---------------- py::module mElement = m.def_submodule("Element", "Generic element routines"); init_Element(mElement); // ---------------------- // GooseFEM.Element.Quad4 // ---------------------- py::module mElementQuad4 = mElement.def_submodule("Quad4", "Linear quadrilateral elements (2D)"); py::module mElementQuad4Gauss = mElementQuad4.def_submodule("Gauss", "Gauss quadrature"); py::module mElementQuad4Nodal = mElementQuad4.def_submodule("Nodal", "Nodal quadrature"); py::module mElementQuad4MidPoint = mElementQuad4.def_submodule("MidPoint", "MidPoint quadrature"); init_ElementQuad4(mElementQuad4); init_ElementQuad4Planar(mElementQuad4); init_ElementQuad4Axisymmetric(mElementQuad4); init_ElementQuad4Gauss(mElementQuad4Gauss); init_ElementQuad4Nodal(mElementQuad4Nodal); init_ElementQuad4MidPoint(mElementQuad4MidPoint); // --------------------- // GooseFEM.Element.Hex8 // --------------------- py::module mElementHex8 = mElement.def_submodule("Hex8", "Linear hexahedron (brick) elements (3D)"); py::module mElementHex8Gauss = mElementHex8.def_submodule("Gauss", "Gauss quadrature"); py::module mElementHex8Nodal = mElementHex8.def_submodule("Nodal", "Nodal quadrature"); init_ElementHex8(mElementHex8); init_ElementHex8Gauss(mElementHex8Gauss); init_ElementHex8Nodal(mElementHex8Nodal); // ------------- // GooseFEM.Mesh // ------------- py::module mMesh = m.def_submodule("Mesh", "Generic mesh routines"); init_Mesh(mMesh); // ------------------ // GooseFEM.Mesh.Tri3 // ------------------ py::module mMeshTri3 = mMesh.def_submodule("Tri3", "Linear triangular elements (2D)"); init_MeshTri3(mMeshTri3); // ------------------- // GooseFEM.Mesh.Quad4 // ------------------- py::module mMeshQuad4 = mMesh.def_submodule("Quad4", "Linear quadrilateral elements (2D)"); init_MeshQuad4(mMeshQuad4); py::module mMeshQuad4Map = mMeshQuad4.def_submodule("Map", "Map mesh objects"); init_MeshQuad4Map(mMeshQuad4Map); // ------------------ // GooseFEM.Mesh.Hex8 // ------------------ py::module mMeshHex8 = mMesh.def_submodule("Hex8", "Linear hexahedron (brick) elements (3D)"); init_MeshHex8(mMeshHex8); // --------------- // GooseFEM.Tyings // --------------- py::module mTyings = m.def_submodule("Tyings", "Linear tying relations"); init_TyingsPeriodic(mTyings); } diff --git a/python/version.hpp b/python/version.hpp index bd4fe57..010d89c 100644 --- a/python/version.hpp +++ b/python/version.hpp @@ -1,21 +1,21 @@ /** -\file -\copyright Copyright 2017. Tom de Geus. All rights reserved. -\license This project is released under the GNU Public License (GPLv3). -*/ + * @file + * @copyright Copyright 2017. Tom de Geus. All rights reserved. + * @license This project is released under the GNU Public License (GPLv3). + */ #include #include namespace py = pybind11; void init_version(py::module& m) { m.def("version", &GooseFEM::version, "See :cpp:func:`GooseFEM::version`."); m.def( "version_dependencies", &GooseFEM::version_dependencies, "See :cpp:func:`GooseFEM::version_dependencies`."); }