From 4ac14827272101eaa3beccfaae78575395c5dd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Dokken?= Date: Thu, 21 Apr 2022 16:03:41 +0000 Subject: [PATCH 1/8] More clean-up --- cpp/Contact.h | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/cpp/Contact.h b/cpp/Contact.h index 5ae77dc0..54218d97 100644 --- a/cpp/Contact.h +++ b/cpp/Contact.h @@ -92,12 +92,16 @@ class Contact return _marker; } + /// @brief Create a PETSc matrix with contact sparsity pattern + /// /// Create a PETSc matrix with the sparsity pattern of the input form and the /// coupling contact interfaces + /// /// @param[in] The bilinear form /// @param[in] The matrix type, see: /// https://petsc.org/main/docs/manualpages/Mat/MatType.html#MatType for /// available types + /// @returns Mat The PETSc matrix Mat create_petsc_matrix(const dolfinx::fem::Form& a, const std::string& type); @@ -131,14 +135,17 @@ class Contact const xtl::span& coeffs, int cstride, const xtl::span& constants); + /// Generate contact kernel contact_kernel_fn generate_kernel(dolfinx_contact::Kernel type) { - // mesh data + // Extract mesh data auto mesh = _marker->mesh(); + assert(mesh); const std::size_t gdim = mesh->geometry().dim(); // geometrical dimension const int tdim = mesh->topology().dim(); // topological dimension const dolfinx::fem::CoordinateElement& cmap = mesh->geometry().cmap(); bool affine = cmap.is_affine(); + const int num_coordinate_dofs = cmap.dim(); // Extract function space data (assuming same test and trial space) std::shared_ptr dofmap = _V->dofmap(); @@ -150,19 +157,19 @@ class Contact const std::size_t max_links = *std::max_element(_max_links.begin(), _max_links.end()); - // Create coordinate elements (for facet and cell) _marker->mesh() - const basix::FiniteElement basix_element - = dolfinx_cuas::mesh_to_basix_element(mesh, tdim); - const int num_coordinate_dofs = basix_element.dim(); // Structures needed for basis function tabulation // phi and grad(phi) at quadrature points + const std::size_t num_facets = dolfinx::mesh::cell_num_entities( + mesh->topology().cell_type(), tdim - 1); + assert(num_facets == _qp_ref_facet.size()); + std::shared_ptr element = _V->element(); std::vector> phi; - phi.reserve(_qp_ref_facet.size()); + phi.reserve(num_facets); std::vector> dphi; - phi.reserve(_qp_ref_facet.size()); + phi.reserve(num_facets); std::vector> dphi_c; - dphi_c.reserve(_qp_ref_facet.size()); + dphi_c.reserve(num_facets); // Temporary structures used in loop xt::xtensor cell_tab( @@ -171,7 +178,7 @@ class Contact xt::xtensor dphi_i( {(std::size_t)tdim, num_q_points, ndofs_cell}); std::array tabulate_shape - = basix_element.tabulate_shape(1, num_q_points); + = cmap.tabulate_shape(1, num_q_points); xt::xtensor c_tab(tabulate_shape); xt::xtensor dphi_ci( {(std::size_t)tdim, tabulate_shape[1], tabulate_shape[2]}); @@ -194,7 +201,7 @@ class Contact dphi.push_back(dphi_i); // Tabulate coordinate element of reference cell - basix_element.tabulate(1, q_facet, c_tab); + cmap.tabulate(1, q_facet, c_tab); dphi_ci = xt::view(c_tab, xt::range(1, (std::size_t)tdim + 1), xt::all(), xt::all(), 0); dphi_c.push_back(dphi_ci); @@ -220,12 +227,14 @@ class Contact num_q_points * bs}; // As reference facet and reference cell are affine, we do not need to // compute this per quadrature point + basix::cell::type basix_cell + = dolfinx::mesh::cell_type_to_basix_type(mesh->topology().cell_type()); xt::xtensor ref_jacobians - = basix::cell::facet_jacobians(basix_element.cell_type()); + = basix::cell::facet_jacobians(basix_cell); // Get facet normals on reference cell xt::xtensor facet_normals - = basix::cell::facet_outward_normals(basix_element.cell_type()); + = basix::cell::facet_outward_normals(basix_cell); auto update_jacobian = dolfinx_contact::get_update_jacobian_dependencies(cmap); @@ -764,16 +773,20 @@ class Contact std::pair, int> pack_gap(int origin_meshtag) { // Mesh info - auto puppet_mesh = _submeshes[origin_meshtag].mesh(); // mesh - auto candidate_mesh = _submeshes[_opposites[origin_meshtag]].mesh(); - const int gdim = candidate_mesh->geometry().dim(); // geometrical dimension + const std::shared_ptr& candidate_mesh + = _submeshes[_opposites[origin_meshtag]].mesh(); + assert(candidate_mesh); + const dolfinx::mesh::Geometry& geometry = candidate_mesh->geometry(); + const int gdim = geometry.dim(); // geometrical dimension + xtl::span mesh_geometry = geometry.x(); + const int tdim = candidate_mesh->topology().dim(); const int fdim = tdim - 1; - xtl::span mesh_geometry = candidate_mesh->geometry().x(); // Select which side of the contact interface to loop from and get the // correct map auto map = _facet_maps[origin_meshtag]; + assert(map); auto qp_phys = _qp_phys[origin_meshtag]; const std::size_t num_facets = _cell_facet_pairs[origin_meshtag].size(); const std::size_t num_q_point = _qp_ref_facet[0].shape(0); From a06ee3f976fb635ca9bfe37663887151b691f257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Dokken?= Date: Fri, 22 Apr 2022 10:39:35 +0000 Subject: [PATCH 2/8] Various improvements to docs and coeff access --- cpp/Contact.cpp | 32 ++-- cpp/Contact.h | 221 +++++++++++++++---------- python/dolfinx_contact/kernelwrapper.h | 2 +- 3 files changed, 149 insertions(+), 106 deletions(-) diff --git a/cpp/Contact.cpp b/cpp/Contact.cpp index e8930db0..bbd2cdfd 100644 --- a/cpp/Contact.cpp +++ b/cpp/Contact.cpp @@ -164,13 +164,19 @@ void dolfinx_contact::Contact::assemble_matrix( const dolfinx::fem::CoordinateElement& cmap = geometry.cmap(); const std::size_t num_dofs_g = cmap.dim(); + std::shared_ptr element = _V->element(); + const bool needs_dof_transformations = element->needs_dof_transformations(); + if (needs_dof_transformations) + { + throw std::invalid_argument( + "Function-space requiring dof-transformations is not supported."); + } + // Extract function space data (assuming same test and trial space) std::shared_ptr dofmap = _V->dofmap(); const std::size_t ndofs_cell = dofmap->cell_dofs(0).size(); const int bs = dofmap->bs(); - // FIXME: Need to reconsider facet permutations for jump integrals - std::uint8_t perm = 0; std::size_t max_links = std::max(_max_links[0], _max_links[1]); auto active_facets = _cell_facet_pairs[origin_meshtag]; auto map = _facet_maps[origin_meshtag]; @@ -207,14 +213,10 @@ void dolfinx_contact::Contact::assemble_matrix( } kernel(Aes, coeffs.data() + i * cstride, constants.data(), - coordinate_dofs.data(), &local_index, &perm, num_linked_cells); + coordinate_dofs.data(), &local_index, num_linked_cells); // FIXME: We would have to handle possible Dirichlet conditions here, if we // think that we can have a case with contact and Dirichlet - - // NOTE Normally - // dof transform needs to be applied to the elements in Aes at this - // stage This is not need for the function spaces we currently consider auto dmap_cell = dofmap->cell_dofs(cell); mat_set(dmap_cell, dmap_cell, Aes[0]); @@ -234,6 +236,15 @@ void dolfinx_contact::Contact::assemble_vector( const contact_kernel_fn& kernel, const xtl::span& coeffs, int cstride, const xtl::span& constants) { + /// Check that we support the function space + std::shared_ptr element = _V->element(); + const bool needs_dof_transformations = element->needs_dof_transformations(); + if (needs_dof_transformations) + { + throw std::invalid_argument( + "Function-space requiring dof-transformations is not supported."); + } + // Extract mesh auto mesh = _marker->mesh(); assert(mesh); @@ -253,8 +264,6 @@ void dolfinx_contact::Contact::assemble_vector( const std::size_t ndofs_cell = dofmap->cell_dofs(0).size(); const int bs = dofmap->bs(); - // FIXME: Need to reconsider facet permutations for jump integrals - std::uint8_t perm = 0; // Select which side of the contact interface to loop from and get the // correct map auto active_facets = _cell_facet_pairs[origin_meshtag]; @@ -291,10 +300,7 @@ void dolfinx_contact::Contact::assemble_vector( for (std::size_t j = 0; j < num_linked_cells; j++) std::fill(bes[j + 1].begin(), bes[j + 1].end(), 0); kernel(bes, coeffs.data() + i * cstride, constants.data(), - coordinate_dofs.data(), &local_index, &perm, num_linked_cells); - // NOTE: Normally dof transform needs to be applied to the elements in - // bes at this stage This is not need for the function spaces - // we currently consider + coordinate_dofs.data(), &local_index, num_linked_cells); // Add element vector to global vector auto dofs_cell = dofmap->cell_dofs(cell); diff --git a/cpp/Contact.h b/cpp/Contact.h index 54218d97..dbbaaf39 100644 --- a/cpp/Contact.h +++ b/cpp/Contact.h @@ -23,10 +23,9 @@ #include #include #include - using contact_kernel_fn = std::function>&, const double*, const double*, - const double*, const int*, const std::uint8_t*, const std::size_t)>; + const double*, const int*, const std::size_t)>; using mat_set_fn = const std::function&, const xtl::span&, @@ -135,7 +134,22 @@ class Contact const xtl::span& coeffs, int cstride, const xtl::span& constants); - /// Generate contact kernel + /// @brief Generate contact kernel + /// + /// The kernel will expect input on the form + /// @param[in] type The kernel type (Either `Jac` or `Rhs`). + /// @returns Kernel function that takes in a vector (b) to assemble into, the + /// coefficients (`c`), the constants (`w`), the local facet entity (`entity + /// _local_index`), the quadrature permutation and the number of cells on the + /// other contact boundary coefficients are extracted from. + /// @note The ordering of coefficients are expected to be `mu`, `lmbda`, `h`, + /// `gap`, `normals` `test_fn`, `u`, `u_opposite`. + /// @note The scalar valued coefficients `mu`,`lmbda` and `h` are expected to + /// be DG-0 functions, with a single value per facet. + /// @note The coefficients `gap`, `normals`,`test_fn` and `u_opposite` is + /// packed at quadrature points. The coefficient `u` is packed at dofs. + /// @note The vector valued coefficents `gap`, `test_fn`, `u`, `u_opposite` + /// has dimension `bs == gdim`. contact_kernel_fn generate_kernel(dolfinx_contact::Kernel type) { // Extract mesh data @@ -147,10 +161,24 @@ class Contact bool affine = cmap.is_affine(); const int num_coordinate_dofs = cmap.dim(); + std::shared_ptr element = _V->element(); + const bool needs_dof_transformations = element->needs_dof_transformations(); + if (needs_dof_transformations) + { + throw std::invalid_argument("Contact-kernels are not supporting finite " + "elements requiring dof transformations."); + } + // Extract function space data (assuming same test and trial space) std::shared_ptr dofmap = _V->dofmap(); const std::size_t ndofs_cell = dofmap->element_dof_layout().num_dofs(); const std::size_t bs = dofmap->bs(); + if (bs != gdim) + { + throw std::invalid_argument( + "The geometric dimension of the mesh is not equal to the block size " + "of the function space."); + } // NOTE: Assuming same number of quadrature points on each cell const std::size_t num_q_points = _qp_ref_facet[0].shape(0); @@ -163,7 +191,6 @@ class Contact mesh->topology().cell_type(), tdim - 1); assert(num_facets == _qp_ref_facet.size()); - std::shared_ptr element = _V->element(); std::vector> phi; phi.reserve(num_facets); std::vector> dphi; @@ -180,43 +207,37 @@ class Contact std::array tabulate_shape = cmap.tabulate_shape(1, num_q_points); xt::xtensor c_tab(tabulate_shape); + assert(tabulate_shape[0] - 1 == (std::size_t)tdim); xt::xtensor dphi_ci( - {(std::size_t)tdim, tabulate_shape[1], tabulate_shape[2]}); + {tabulate_shape[0] - 1, tabulate_shape[1], tabulate_shape[2]}); // Tabulate basis functions and first order derivatives for each facet in // the reference cell. This tabulation is done both for the finite element // of the unknown and the coordinate element (which might differ) - std::for_each( - _qp_ref_facet.cbegin(), _qp_ref_facet.cend(), - [&](const auto& q_facet) - { - assert(q_facet.shape(0) == num_q_points); - element->tabulate(cell_tab, q_facet, 1); - - phi_i = xt::view(cell_tab, 0, xt::all(), xt::all(), 0); - phi.push_back(phi_i); - - dphi_i = xt::view(cell_tab, xt::range(1, (std::size_t)tdim + 1), - xt::all(), xt::all(), 0); - dphi.push_back(dphi_i); - - // Tabulate coordinate element of reference cell - cmap.tabulate(1, q_facet, c_tab); - dphi_ci = xt::view(c_tab, xt::range(1, (std::size_t)tdim + 1), - xt::all(), xt::all(), 0); - dphi_c.push_back(dphi_ci); - }); - // coefficient offsets - // expecting coefficients in following order: + std::for_each(_qp_ref_facet.cbegin(), _qp_ref_facet.cend(), + [&](const auto& q_facet) + { + assert(q_facet.shape(0) == num_q_points); + element->tabulate(cell_tab, q_facet, 1); + + phi_i = xt::view(cell_tab, 0, xt::all(), xt::all(), 0); + phi.push_back(phi_i); + + dphi_i = xt::view(cell_tab, xt::range(1, tabulate_shape[0]), + xt::all(), xt::all(), 0); + dphi.push_back(dphi_i); + + // Tabulate coordinate element of reference cell + cmap.tabulate(1, q_facet, c_tab); + dphi_ci = xt::view(c_tab, xt::range(1, tabulate_shape[0]), + xt::all(), xt::all(), 0); + dphi_c.push_back(dphi_ci); + }); + + // Coefficient offsets + // Expecting coefficients in following order: // mu, lmbda, h, gap, normals, test_fn, u, u_opposite - // mu, lmbda, h - DG0 one value per facet - // gap, normals, test_fn, u_opposite - // packed at quadrature points - // u packed at dofs - // mu, lmbda, h scalar - // gap vector valued gdim - // test_fn, u, u_opposite vector valued bs (should be bs = gdim) - std::vector cstrides + std::array cstrides = {1, 1, 1, @@ -225,6 +246,11 @@ class Contact num_q_points * ndofs_cell * bs * max_links, ndofs_cell * bs, num_q_points * bs}; + std::array offsets; + offsets[0] = 0; + std::partial_sum(cstrides.cbegin(), cstrides.cend(), + std::next(offsets.begin())); + // As reference facet and reference cell are affine, we do not need to // compute this per quadrature point basix::cell::type basix_cell @@ -236,23 +262,31 @@ class Contact xt::xtensor facet_normals = basix::cell::facet_outward_normals(basix_cell); + // Get update Jacobian function (for each quadrature point) auto update_jacobian = dolfinx_contact::get_update_jacobian_dependencies(cmap); + + // Get update FacetNormal function (for each quadrature point) auto update_normal = dolfinx_contact::get_update_normal(cmap); - // right hand side kernel + /// @brief Assemble kernel for RHS of unbiased contact problem + /// + /// Assemble of the residual of the unbiased contact problem into vector + /// `b`. + /// @param[in,out] b The vector to assemble the residual into + /// @param[in] c The coefficients used in kernel. Assumed to be + /// ordered as mu, lmbda, h, gap, normals, test_fn, u, u_opposite. + /// @param[in] w The constants used in kernel. Assumed to be ordered as + /// `gamma`, `theta`. + /// @param[in] coordinate_dofs The physical coordinates of cell. Assumed to + /// be padded to 3D, (shape (num_nodes, 3)). contact_kernel_fn unbiased_rhs = [=](std::vector>& b, const double* c, const double* w, const double* coordinate_dofs, - const int* entity_local_index, - [[maybe_unused]] const std::uint8_t* quadrature_permutation, - const std::size_t num_links) + const int* entity_local_index, const std::size_t num_links) { - // assumption that the vector function space has block size tdim - assert(bs == gdim); const auto facet_index = size_t(*entity_local_index); - // Reshape coordinate dofs to two dimensional array // NOTE: DOLFINx has 3D input coordinate dofs // FIXME: These array should be views (when compute_jacobian doesn't use // xtensor) @@ -287,40 +321,42 @@ class Contact n_phys, K, xt::row(facet_normals, facet_index)); } - // h/gamma + // Extract constants used inside quadrature loop double gamma = c[2] / w[0]; - // gamma/h double gamma_inv = w[0] / c[2]; double theta = w[1]; double mu = c[0]; double lmbda = c[1]; - const xt::xtensor& dphi_f = dphi[facet_index]; + // Extract reference to the tabulated basis function at the local + // facet const xt::xtensor& phi_f = phi[facet_index]; + const xt::xtensor& dphi_f = dphi[facet_index]; + + // Extract reference to quadrature weights for the local facet const std::vector& weights = _qw_ref_facet[facet_index]; - xt::xtensor n_surf = xt::zeros({gdim}); - xt::xtensor tr = xt::zeros({ndofs_cell, gdim}); - xt::xtensor epsn = xt::zeros({ndofs_cell, gdim}); + + // Temporary data structures used inside quadrature loop + std::array n_surf = {0, 0, 0}; + xt::xtensor tr({ndofs_cell, gdim}); + xt::xtensor epsn({ndofs_cell, gdim}); for (std::size_t q = 0; q < weights.size(); q++) { // Update Jacobian and physical normal detJ = update_jacobian(q, detJ, J, K, J_tot, J_f, dphi_fc, coord); - update_normal(n_phys, K, facet_normals, facet_index); double n_dot = 0; double gap = 0; - const std::size_t gap_offset = 3; - const std::size_t normal_offset = gap_offset + cstrides[3]; for (std::size_t i = 0; i < gdim; i++) { // For closest point projection the gap function is given by // (-n_y)* (Pi(x) - x), where n_y is the outward unit normal // in y = Pi(x) - n_surf(i) = -c[normal_offset + q * gdim + i]; - n_dot += n_phys(i) * n_surf(i); - gap += c[gap_offset + q * gdim + i] * n_surf(i); + n_surf[i] = -c[offsets[4] + q * gdim + i]; + n_dot += n_phys(i) * n_surf[i]; + gap += c[offsets[3] + q * gdim + i] * n_surf[i]; } // precompute tr(eps(phi_j e_l)), eps(phi^j e_l)n*n2 @@ -336,7 +372,7 @@ class Contact for (std::size_t s = 0; s < gdim; s++) { epsn(j, l) += K(k, s) * dphi_f(k, q, j) - * (n_phys(s) * n_surf(l) + n_phys(l) * n_surf(s)); + * (n_phys(s) * n_surf[l] + n_phys(l) * n_surf[s]); } } } @@ -345,23 +381,20 @@ class Contact double tr_u = 0; double epsn_u = 0; double jump_un = 0; - std::size_t offset_u = cstrides[0] + cstrides[1] + cstrides[2] - + cstrides[3] + cstrides[4] + cstrides[5]; - for (std::size_t i = 0; i < ndofs_cell; i++) { - std::size_t block_index = offset_u + i * bs; + std::size_t block_index = offsets[6] + i * bs; for (std::size_t j = 0; j < bs; j++) { auto coeff = c[block_index + j]; tr_u += coeff * tr(i, j); epsn_u += coeff * epsn(i, j); - jump_un += coeff * phi_f(q, i) * n_surf(j); + jump_un += coeff * phi_f(q, i) * n_surf[j]; } } - std::size_t offset_u_opp = offset_u + cstrides[6] + q * bs; + std::size_t offset_u_opp = offsets[7] + q * bs; for (std::size_t j = 0; j < bs; ++j) - jump_un += -c[offset_u_opp + j] * n_surf(j); + jump_un += -c[offset_u_opp + j] * n_surf[j]; double sign_u = lmbda * tr_u * n_dot + mu * epsn_u; const double w0 = weights[q] * detJ; double Pn_u @@ -373,7 +406,7 @@ class Contact { for (std::size_t n = 0; n < bs; n++) { - double v_dot_nsurf = n_surf(n) * phi_f(q, i); + double v_dot_nsurf = n_surf[n] * phi_f(q, i); double sign_v = (lmbda * tr(i, n) * n_dot + mu * epsn(i, n)); // This is (1./gamma)*Pn_v to avoid the product gamma*(1./gamma) double Pn_v = gamma_inv * v_dot_nsurf - theta * sign_v; @@ -383,10 +416,9 @@ class Contact // entries corresponding to v on the other surface for (std::size_t k = 0; k < num_links; k++) { - int index = 3 + cstrides[3] + cstrides[4] - + k * num_q_points * ndofs_cell * bs + int index = offsets[5] + k * num_q_points * ndofs_cell * bs + i * num_q_points * bs + q * bs + n; - double v_n_opp = c[index] * n_surf(n); + double v_n_opp = c[index] * n_surf[n]; b[k + 1][n + i * bs] -= 0.5 * gamma_inv * v_n_opp * Pn_u; } @@ -395,24 +427,31 @@ class Contact } }; - // jacobian kernel + /// @brief Assemble kernel for Jacobian (LHS) of unbiased contact + /// problem + /// + /// Assemble of the residual of the unbiased contact problem into matrix + /// `A`. + /// @param[in,out] A The matrix to assemble the Jacobian into + /// @param[in] c The coefficients used in kernel. Assumed to be + /// ordered as mu, lmbda, h, gap, normals, test_fn, u, u_opposite. + /// @param[in] w The constants used in kernel. Assumed to be ordered as + /// `gamma`, `theta`. + /// @param[in] coordinate_dofs The physical coordinates of cell. Assumed + /// to be padded to 3D, (shape (num_nodes, 3)). contact_kernel_fn unbiased_jac = [=](std::vector>& A, const double* c, const double* w, const double* coordinate_dofs, - const int* entity_local_index, - [[maybe_unused]] const std::uint8_t* quadrature_permutation, - const std::size_t num_links) + const int* entity_local_index, const std::size_t num_links) { - // assumption that the vector function space has block size tdim - assert(bs == gdim); const auto facet_index = std::size_t(*entity_local_index); // Reshape coordinate dofs to two dimensional array // NOTE: DOLFINx has 3D input coordinate dofs std::array shape = {(std::size_t)num_coordinate_dofs, 3}; - // FIXME: These array should be views (when compute_jacobian doesn't use - // xtensor) + // FIXME: These array should be views (when compute_jacobian doesn't + // use xtensor) xt::xtensor coord = xt::adapt( coordinate_dofs, num_coordinate_dofs * 3, xt::no_ownership(), shape); @@ -465,16 +504,14 @@ class Contact double n_dot = 0; double gap = 0; - const std::size_t gap_offset = 3; - const std::size_t normal_offset = gap_offset + cstrides[3]; for (std::size_t i = 0; i < gdim; i++) { // For closest point projection the gap function is given by // (-n_y)* (Pi(x) - x), where n_y is the outward unit normal // in y = Pi(x) - n_surf(i) = -c[normal_offset + q * gdim + i]; + n_surf(i) = -c[offsets[4] + q * gdim + i]; n_dot += n_phys(i) * n_surf(i); - gap += c[gap_offset + q * gdim + i] * n_surf(i); + gap += c[offsets[3] + q * gdim + i] * n_surf(i); } // precompute tr(eps(phi_j e_l)), eps(phi^j e_l)n*n2 @@ -500,11 +537,10 @@ class Contact double tr_u = 0; double epsn_u = 0; double jump_un = 0; - std::size_t offset_u = cstrides[0] + cstrides[1] + cstrides[2] - + cstrides[3] + cstrides[4] + cstrides[5]; + for (std::size_t i = 0; i < ndofs_cell; i++) { - std::size_t block_index = offset_u + i * bs; + std::size_t block_index = offsets[6] + i * bs; for (std::size_t j = 0; j < bs; j++) { tr_u += c[block_index + j] * tr(i, j); @@ -512,7 +548,7 @@ class Contact jump_un += c[block_index + j] * phi_f(q, i) * n_surf(j); } } - std::size_t offset_u_opp = offset_u + cstrides[6] + q * bs; + std::size_t offset_u_opp = offsets[7] + q * bs; for (std::size_t j = 0; j < bs; ++j) jump_un += -c[offset_u_opp + j] * n_surf(j); double sign_u = lmbda * tr_u * n_dot + mu * epsn_u; @@ -543,14 +579,13 @@ class Contact // entries corresponding to u and v on the other surface for (std::size_t k = 0; k < num_links; k++) { - std::size_t index = 3 + cstrides[3] + cstrides[4] + std::size_t index = offsets[5] + k * num_q_points * ndofs_cell * bs + j * num_q_points * bs + q * bs + l; double du_n_opp = c[index] * n_surf(l); du_n_opp *= w0 * Pn_u; - index = 3 + cstrides[3] + cstrides[4] - + k * num_q_points * ndofs_cell * bs + index = offsets[5] + k * num_q_points * ndofs_cell * bs + i * num_q_points * bs + q * bs + b; double v_n_opp = c[index] * n_surf(b); A[3 * k + 1][(b + i * bs) * ndofs_cell * bs + l + j * bs] @@ -599,9 +634,9 @@ class Contact return phi; } - /// Compute push forward of quadrature points _qp_ref_facet to the physical - /// facet for each facet in _facet_"origin_meshtag" Creates and fills - /// _qp_phys_"origin_meshtag" + /// Compute push forward of quadrature points _qp_ref_facet to the + /// physical facet for each facet in _facet_"origin_meshtag" Creates and + /// fills _qp_phys_"origin_meshtag" /// @param[in] origin_meshtag flag to choose the surface void create_q_phys(int origin_meshtag) { @@ -677,7 +712,8 @@ class Contact /// Compute closest candidate_facet for each quadrature point in /// _qp_phys[origin_meshtag] /// This is saved as an adjacency list in _facet_maps[origin_meshtag] - /// and an xtensor containing cell_facet_pairs in _cell_maps[origin_mesthtag] + /// and an xtensor containing cell_facet_pairs in + /// _cell_maps[origin_mesthtag] void create_distance_map(int puppet_mt, int candidate_mt) { // save opposite surface @@ -768,8 +804,8 @@ class Contact /// distance vector /// @param[in] orgin_meshtag - surface on which to integrate /// @param[out] c - gap packed on facets. c[i*cstride + gdim * k+ j] - /// contains the jth component of the Gap on the ith facet at kth quadrature - /// point + /// contains the jth component of the Gap on the ith facet at kth + /// quadrature point std::pair, int> pack_gap(int origin_meshtag) { // Mesh info @@ -911,7 +947,8 @@ class Contact { std::int32_t linked_cell = unique_cells[j]; - // Extract indices of all occurances of cell in the unsorted cell array + // Extract indices of all occurances of cell in the unsorted cell + // array auto indices = xtl::span(perm.data() + offsets[j], offsets[j + 1] - offsets[j]); // Extract local dofs diff --git a/python/dolfinx_contact/kernelwrapper.h b/python/dolfinx_contact/kernelwrapper.h index 0b2649d4..3f313dc7 100644 --- a/python/dolfinx_contact/kernelwrapper.h +++ b/python/dolfinx_contact/kernelwrapper.h @@ -8,7 +8,7 @@ using contact_kernel_fn = std::function>&, const double*, const double*, - const double*, const int*, const std::uint8_t*, const std::size_t)>; + const double*, const int*, const std::size_t)>; namespace contact_wrappers { From 8ac4eb396e6fbc0adf0c6e7bebc0db03a3340b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Dokken?= Date: Fri, 22 Apr 2022 10:52:36 +0000 Subject: [PATCH 3/8] Code smells --- cpp/Contact.cpp | 10 ++++++---- cpp/Contact.h | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cpp/Contact.cpp b/cpp/Contact.cpp index bbd2cdfd..501e83af 100644 --- a/cpp/Contact.cpp +++ b/cpp/Contact.cpp @@ -165,8 +165,9 @@ void dolfinx_contact::Contact::assemble_matrix( const std::size_t num_dofs_g = cmap.dim(); std::shared_ptr element = _V->element(); - const bool needs_dof_transformations = element->needs_dof_transformations(); - if (needs_dof_transformations) + if (const bool needs_dof_transformations + = element->needs_dof_transformations(); + needs_dof_transformations) { throw std::invalid_argument( "Function-space requiring dof-transformations is not supported."); @@ -238,8 +239,9 @@ void dolfinx_contact::Contact::assemble_vector( { /// Check that we support the function space std::shared_ptr element = _V->element(); - const bool needs_dof_transformations = element->needs_dof_transformations(); - if (needs_dof_transformations) + if (const bool needs_dof_transformations + = element->needs_dof_transformations(); + needs_dof_transformations) { throw std::invalid_argument( "Function-space requiring dof-transformations is not supported."); diff --git a/cpp/Contact.h b/cpp/Contact.h index dbbaaf39..9060eaad 100644 --- a/cpp/Contact.h +++ b/cpp/Contact.h @@ -162,8 +162,9 @@ class Contact const int num_coordinate_dofs = cmap.dim(); std::shared_ptr element = _V->element(); - const bool needs_dof_transformations = element->needs_dof_transformations(); - if (needs_dof_transformations) + if (const bool needs_dof_transformations + = element->needs_dof_transformations(); + needs_dof_transformations) { throw std::invalid_argument("Contact-kernels are not supporting finite " "elements requiring dof transformations."); @@ -416,8 +417,9 @@ class Contact // entries corresponding to v on the other surface for (std::size_t k = 0; k < num_links; k++) { - int index = offsets[5] + k * num_q_points * ndofs_cell * bs - + i * num_q_points * bs + q * bs + n; + std::size_t index = offsets[5] + + k * num_q_points * ndofs_cell * bs + + i * num_q_points * bs + q * bs + n; double v_n_opp = c[index] * n_surf[n]; b[k + 1][n + i * bs] -= 0.5 * gamma_inv * v_n_opp * Pn_u; From 21d246b3244b4cc81d760e7c0d28a1d6bb89a026 Mon Sep 17 00:00:00 2001 From: Sarah Roggendorf <33656497+SarahRo@users.noreply.github.com> Date: Fri, 22 Apr 2022 14:22:53 +0100 Subject: [PATCH 4/8] Apply suggestions from code review --- cpp/Contact.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/Contact.h b/cpp/Contact.h index 9060eaad..c33a9361 100644 --- a/cpp/Contact.h +++ b/cpp/Contact.h @@ -323,8 +323,8 @@ class Contact } // Extract constants used inside quadrature loop - double gamma = c[2] / w[0]; - double gamma_inv = w[0] / c[2]; + double gamma = c[2] / w[0]; // h/gamma + double gamma_inv = w[0] / c[2]; // gamma/h double theta = w[1]; double mu = c[0]; double lmbda = c[1]; From 479de7985c02c6ce4516b7b41e8f5ff19f6bd87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Dokken?= Date: Fri, 22 Apr 2022 18:12:58 +0100 Subject: [PATCH 5/8] Fix push --- cpp/Contact.h | 52 +++++++++++----------- cpp/geometric_quantities.cpp | 84 ++++++++++++++--------------------- cpp/geometric_quantities.h | 37 +++++++-------- cpp/utils.cpp | 2 +- cpp/utils.h | 2 +- python/tests/test_unbiased.py | 2 +- 6 files changed, 81 insertions(+), 98 deletions(-) diff --git a/cpp/Contact.h b/cpp/Contact.h index c33a9361..1b3b710f 100644 --- a/cpp/Contact.h +++ b/cpp/Contact.h @@ -23,6 +23,7 @@ #include #include #include + using contact_kernel_fn = std::function>&, const double*, const double*, const double*, const int*, const std::size_t)>; @@ -323,7 +324,7 @@ class Contact } // Extract constants used inside quadrature loop - double gamma = c[2] / w[0]; // h/gamma + double gamma = c[2] / w[0]; // h/gamma double gamma_inv = w[0] / c[2]; // gamma/h double theta = w[1]; double mu = c[0]; @@ -347,14 +348,14 @@ class Contact // Update Jacobian and physical normal detJ = update_jacobian(q, detJ, J, K, J_tot, J_f, dphi_fc, coord); update_normal(n_phys, K, facet_normals, facet_index); - double n_dot = 0; double gap = 0; + // For closest point projection the gap function is given by + // (-n_y)* (Pi(x) - x), where n_y is the outward unit normal + // in y = Pi(x) for (std::size_t i = 0; i < gdim; i++) { - // For closest point projection the gap function is given by - // (-n_y)* (Pi(x) - x), where n_y is the outward unit normal - // in y = Pi(x) + n_surf[i] = -c[offsets[4] + q * gdim + i]; n_dot += n_phys(i) * n_surf[i]; gap += c[offsets[3] + q * gdim + i] * n_surf[i]; @@ -401,6 +402,7 @@ class Contact double Pn_u = dolfinx_contact::R_plus((jump_un - gap) - gamma * sign_u) * w0; sign_u *= w0; + // Fill contributions of facet with itself for (std::size_t i = 0; i < ndofs_cell; i++) @@ -1118,17 +1120,17 @@ class Contact const std::size_t num_q_points = _qp_ref_facet[0].shape(0); std::vector c(num_facets * num_q_points * gdim, 0.0); const auto cstride = (int)num_q_points * gdim; - xt::xtensor point = xt::zeros( - {std::size_t(1), std::size_t(gdim)}); // To store Pi(x) - // Needed for pull_back in get_facet_normals - xt::xtensor J = xt::zeros( - {std::size_t(num_q_points), std::size_t(gdim), std::size_t(tdim)}); - xt::xtensor K = xt::zeros( - {std::size_t(1), std::size_t(tdim), std::size_t(gdim)}); - xt::xtensor detJ = xt::zeros({std::size_t(1)}); - xt::xtensor facet_indices - = xt::zeros({std::size_t(1)}); + xt::xtensor J + = xt::zeros({std::size_t(gdim), std::size_t(tdim)}); + xt::xtensor K + = xt::zeros({std::size_t(tdim), std::size_t(gdim)}); + + const std::size_t num_dofs_g = cmap.dim(); + xt::xtensor coordinate_dofs + = xt::zeros({num_dofs_g, std::size_t(gdim)}); + std::array normals = {0, 0, 0}; + std::array point = {0, 0, 0}; // To store Pi(x) // Loop over quadrature points for (std::size_t i = 0; i < num_facets; i++) @@ -1140,18 +1142,16 @@ class Contact // Extract linked cell and facet at quadrature point q auto linked_pair = facet_map->links(links[q]); std::int32_t linked_cell = linked_pair[0]; - facet_indices(0) = linked_pair[1]; // Compute Pi(x) from x, and gap = Pi(x) - x for (int k = 0; k < gdim; ++k) - point(0, k) + point[k] = qp_phys[i](q, k) + gap[i * gdim * num_q_points + q * gdim + k]; // extract local dofs auto x_dofs = x_dofmap.links(linked_cell); - const std::size_t num_dofs_g = x_dofmap.num_links(linked_cell); - xt::xtensor coordinate_dofs - = xt::zeros({num_dofs_g, std::size_t(gdim)}); + assert(num_dofs_g == (std::size_t)x_dofmap.num_links(linked_cell)); + for (std::size_t j = 0; j < x_dofs.size(); ++j) { std::copy_n(std::next(mesh_geometry.begin(), 3 * x_dofs[j]), gdim, @@ -1162,16 +1162,14 @@ class Contact // Note: in the affine case potential gains can be made // if the cells are sorted like in pack_test_functions assert(linked_cell >= 0); - xt::xtensor normals - = dolfinx_contact::push_forward_facet_normal( - point, J, K, coordinate_dofs, facet_indices, cmap, - facet_normals); + + normals = dolfinx_contact::push_forward_facet_normal( + J, K, point, coordinate_dofs, linked_pair[1], cmap, facet_normals); // Copy normal into c + const std::size_t c_offset = i * cstride + q * gdim; for (int l = 0; l < gdim; l++) - { - c[i * cstride + q * gdim + l] = normals(0, l); - } + c[c_offset + l] = normals[l]; } } diff --git a/cpp/geometric_quantities.cpp b/cpp/geometric_quantities.cpp index e39cd407..cc2051fe 100644 --- a/cpp/geometric_quantities.cpp +++ b/cpp/geometric_quantities.cpp @@ -1,86 +1,70 @@ -// Copyright (C) 2021 Sarah Roggendorf and Jørgen S. Dokken +// Copyright (C) 2021-2022 Sarah Roggendorf and Jørgen S. Dokken // // This file is part of DOLFINx_CONTACT // // SPDX-License-Identifier: MIT #include "geometric_quantities.h" -#include #include #include #include - using namespace dolfinx_contact; - -xt::xtensor dolfinx_contact::push_forward_facet_normal( - const xt::xtensor& x, xt::xtensor& J, - xt::xtensor& K, const xt::xtensor& coordinate_dofs, - const xt::xtensor& facet_indices, - const dolfinx::fem::CoordinateElement& cmap, +std::array dolfinx_contact::push_forward_facet_normal( + xt::xtensor& J, xt::xtensor& K, + const std::array& x, + const xt::xtensor& coordinate_dofs, + const std::size_t facet_index, const dolfinx::fem::CoordinateElement& cmap, const xt::xtensor& reference_normals) { - assert(J.shape(0) >= x.shape(0)); - assert(K.shape(0) >= x.shape(0)); + assert(J.shape(0) == K.shape(1)); + assert(K.shape(0) == J.shape(1)); // Shapes needed for computing the Jacobian inverse - const std::size_t num_points = x.shape(0); - const size_t gdim = coordinate_dofs.shape(1); - const size_t tdim = K.shape(1); + const size_t tdim = K.shape(0); + const size_t gdim = K.shape(1); // Data structures for computing J inverse xt::xtensor phi(cmap.tabulate_shape(1, 1)); xt::xtensor dphi({tdim, cmap.tabulate_shape(1, 1)[2]}); - - xt::xtensor X({num_points, tdim}); + xt::xtensor X({1, tdim}); // Compute Jacobian inverse if (cmap.is_affine()) { - J.fill(0); - // Affine Jacobian can be computed at any point in the cell (0,0,0) in the - // reference cell - X.fill(0); - auto _J = xt::view(J, 0, xt::all(), xt::all()); - auto _K = xt::view(K, 0, xt::all(), xt::all()); + // Affine Jacobian can be computed at any point in the cell (0,0,0) in + // the reference cell + std::fill(X.begin(), X.end(), 0); cmap.tabulate(1, X, phi); dphi = xt::view(phi, xt::range(1, tdim + 1), 0, xt::all(), 0); - dolfinx::fem::CoordinateElement::compute_jacobian(dphi, coordinate_dofs, - _J); - dolfinx::fem::CoordinateElement::compute_jacobian_inverse(_J, _K); - for (std::size_t p = 1; p < num_points; ++p) - { - xt::view(J, p, xt::all(), xt::all()) = _J; - xt::view(K, p, xt::all(), xt::all()) = _K; - } + std::fill(J.begin(), J.end(), 0); + dolfinx::fem::CoordinateElement::compute_jacobian(dphi, coordinate_dofs, J); + std::fill(K.begin(), K.end(), 0); + dolfinx::fem::CoordinateElement::compute_jacobian_inverse(J, K); } else { + // Copy x into gdim restriction + xt::xtensor _x({1, gdim}); + std::transform(x.cbegin(), std::next(x.cbegin() + gdim), _x.begin(), + [](auto xi) { return xi; }); + // For non-affine geometries we have to compute the point in the reference // cell, which is a nonlinear operation. Internally cmap uses a // Newton-solver to get the reference coordinates X - cmap.pull_back_nonaffine(X, x, coordinate_dofs); + cmap.pull_back_nonaffine(X, _x, coordinate_dofs); cmap.tabulate(1, X, phi); - J.fill(0); - for (std::size_t p = 0; p < num_points; ++p) - { - dphi = xt::view(phi, xt::range(1, tdim + 1), p, xt::all(), 0); - auto _J = xt::view(J, p, xt::all(), xt::all()); - dolfinx::fem::CoordinateElement::compute_jacobian(dphi, coordinate_dofs, - _J); - dolfinx::fem::CoordinateElement::compute_jacobian_inverse( - _J, xt::view(K, p, xt::all(), xt::all())); - } - } - xt::xtensor normals = xt::zeros({num_points, gdim}); - for (std::size_t q = 0; q < num_points; ++q) - { - // Push forward reference facet normal - physical_facet_normal(xt::row(normals, q), - xt::view(K, q, xt::all(), xt::all()), - xt::row(reference_normals, facet_indices[q])); + dphi = xt::view(phi, xt::range(1, tdim + 1), 0, xt::all(), 0); + std::fill(J.begin(), J.end(), 0); + dolfinx::fem::CoordinateElement::compute_jacobian(dphi, coordinate_dofs, J); + std::fill(K.begin(), K.end(), 0); + dolfinx::fem::CoordinateElement::compute_jacobian_inverse(J, K); } - return normals; + // Push forward reference facet normal + std::array normal = {0, 0, 0}; + physical_facet_normal(xtl::span(normal.data(), gdim), K, + xt::row(reference_normals, facet_index)); + return normal; } //----------------------------------------------------------------------------- diff --git a/cpp/geometric_quantities.h b/cpp/geometric_quantities.h index 0939cf5a..5647bb4e 100644 --- a/cpp/geometric_quantities.h +++ b/cpp/geometric_quantities.h @@ -13,26 +13,27 @@ namespace dolfinx_contact { //----------------------------------------------------------------------------- -/// Computes the outward unit normal for a set of points x located on -/// the surface of the cell. Each point x has a corresponding facet index, -/// relating to which local facet the point belongs to. +/// @brief Compute facet normal on physical cell +/// +/// Computes the outward unit normal fora a point x located on +/// the surface of the physical cell. Each point x has a corresponding facet +/// index, relating to which local facet the point belongs to. /// @note: The normal is computed using a covariant Piola transform /// @note: The Jacobian J and its inverse K are computed internally, and /// is only passed in to avoid dynamic memory allocation. -/// @param[in] x: points on facets physical element -/// @param[in, out] J: Jacobians of transformation from reference element to -/// physical element. Shape = (num_points, tdim, gdim). Computed at each point -/// in x. -/// @param[in, out] K: inverse of J at each point. -/// @param[in] coordinate_dofs: geometry coordinates of cell -/// @param[in] facet_indices: local facet index corresponding to each point -/// @param[in] cmap: the coordinate element +/// @param[in, out] J: Jacobian of transformation from reference element to +/// physical element. Shape = (gdim, tdim). +/// @param[in, out] K: Inverse of J. Shape = (tdim, gdim) +/// @param[in] x: The point on the facet of the physical cell(padded to 3D) +/// @param[in] coordinate_dofs: Geometry coordinates of cell +/// @param[in] facet_index: Local facet index +/// @param[in] cmap: The coordinate element /// @param[in] reference_normals: The facet normals on the reference cell -xt::xtensor -push_forward_facet_normal(const xt::xtensor& x, - xt::xtensor& J, xt::xtensor& K, +std::array +push_forward_facet_normal(xt::xtensor& J, xt::xtensor& K, + const std::array& x, const xt::xtensor& coordinate_dofs, - const xt::xtensor& facet_indices, + const std::size_t facet_index, const dolfinx::fem::CoordinateElement& cmap, const xt::xtensor& reference_normals); @@ -57,9 +58,9 @@ double compute_circumradius(const dolfinx::mesh::Mesh& mesh, double detJ, template void physical_facet_normal(E&& physical_normal, F&& K, G&& reference_normal) { - assert(physical_normal.shape(0) == K.shape(1)); - const std::size_t gdim = K.shape(0); - const std::size_t tdim = K.shape(1); + assert(physical_normal.size() == K.shape(1)); + const std::size_t tdim = K.shape(0); + const std::size_t gdim = K.shape(1); for (std::size_t i = 0; i < gdim; i++) { // FIXME: Replace with math-dot diff --git a/cpp/utils.cpp b/cpp/utils.cpp index f39ff5e3..da09df24 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -473,7 +473,7 @@ double dolfinx_contact::compute_facet_jacobians( } //------------------------------------------------------------------------------------- std::function&, xt::xtensor&, + std::size_t, double, xt::xtensor&, xt::xtensor&, xt::xtensor&, const xt::xtensor&, const xt::xtensor&, const xt::xtensor&)> dolfinx_contact::get_update_jacobian_dependencies( diff --git a/cpp/utils.h b/cpp/utils.h index 5ef49d39..eda5515f 100644 --- a/cpp/utils.h +++ b/cpp/utils.h @@ -157,7 +157,7 @@ double compute_facet_jacobians(std::size_t q, xt::xtensor& J, /// (J*J_f) is computed. /// @param[in] cmap The coordinate element std::function&, xt::xtensor&, + std::size_t, double, xt::xtensor&, xt::xtensor&, xt::xtensor&, const xt::xtensor&, const xt::xtensor&, const xt::xtensor&)> get_update_jacobian_dependencies(const dolfinx::fem::CoordinateElement& cmap); diff --git a/python/tests/test_unbiased.py b/python/tests/test_unbiased.py index 5f83b49d..f2355ea7 100644 --- a/python/tests/test_unbiased.py +++ b/python/tests/test_unbiased.py @@ -250,7 +250,7 @@ def create_contact_data(V, u, quadrature_degree, lmbda, mu, facets_cg): gap_1 = contact.pack_gap(1) n_1 = contact.pack_ny(1, gap_1) test_fn_1 = contact.pack_test_functions(1, gap_1) - + # Concatenate all coeffs coeff_0 = np.hstack([material_0, h_0, gap_0, n_0, test_fn_0]) coeff_1 = np.hstack([material_1, h_1, gap_1, n_1, test_fn_1]) From d04309446dc01eac1ef559cf2fea5be16828125b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Sat, 23 Apr 2022 17:33:15 +0100 Subject: [PATCH 6/8] Apply suggestions from code review --- python/tests/test_unbiased.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/tests/test_unbiased.py b/python/tests/test_unbiased.py index f2355ea7..45964b45 100644 --- a/python/tests/test_unbiased.py +++ b/python/tests/test_unbiased.py @@ -250,7 +250,6 @@ def create_contact_data(V, u, quadrature_degree, lmbda, mu, facets_cg): gap_1 = contact.pack_gap(1) n_1 = contact.pack_ny(1, gap_1) test_fn_1 = contact.pack_test_functions(1, gap_1) - # Concatenate all coeffs coeff_0 = np.hstack([material_0, h_0, gap_0, n_0, test_fn_0]) coeff_1 = np.hstack([material_1, h_1, gap_1, n_1, test_fn_1]) From 8be9589fdd67510dd9f20de413abe0f7abbba7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Dokken?= Date: Mon, 25 Apr 2022 12:28:05 +0000 Subject: [PATCH 7/8] Move `pack_ny` to cpp file. Add function for non-affine pullback as the cmap one has dynamic memory allocation --- cpp/Contact.cpp | 95 ++++++++++++++++++++++++++++++++++++ cpp/Contact.h | 80 +----------------------------- cpp/geometric_quantities.cpp | 86 ++++++++++++++++++++++++++------ cpp/geometric_quantities.h | 29 ++++++++++- 4 files changed, 194 insertions(+), 96 deletions(-) diff --git a/cpp/Contact.cpp b/cpp/Contact.cpp index 501e83af..8043c3ed 100644 --- a/cpp/Contact.cpp +++ b/cpp/Contact.cpp @@ -144,6 +144,101 @@ Mat dolfinx_contact::Contact::create_petsc_matrix( return dolfinx::la::petsc::create_matrix(a.mesh()->comm(), pattern, type); } //------------------------------------------------------------------------------------------------ +std::pair, int> +dolfinx_contact::Contact::pack_ny(int origin_meshtag, + const xtl::span gap) +{ + + // Get information from candidate mesh + + // Get mesh and submesh + const dolfinx_contact::SubMesh& submesh + = _submeshes[_opposites[origin_meshtag]]; + auto candidate_mesh = submesh.mesh(); // mesh + + // Geometrical info + const dolfinx::mesh::Geometry& geometry = candidate_mesh->geometry(); + const int gdim = geometry.dim(); // geometrical dimension + auto cmap = geometry.cmap(); + auto x_dofmap = geometry.dofmap(); + xtl::span mesh_geometry = geometry.x(); + + // Topological info + const int tdim = candidate_mesh->topology().dim(); + auto cell_type = dolfinx::mesh::cell_type_to_basix_type( + candidate_mesh->topology().cell_type()); + + // Get facet normals on reference cell + const xt::xtensor reference_normals + = basix::cell::facet_outward_normals(cell_type); + + // Select which side of the contact interface to loop from and get the + // correct map + auto facet_map = submesh.facet_map(); + auto map = _facet_maps[origin_meshtag]; + auto qp_phys = _qp_phys[origin_meshtag]; + + const std::size_t num_facets = _cell_facet_pairs[origin_meshtag].size(); + const std::size_t num_q_points = _qp_ref_facet[0].shape(0); + + // Needed for pull_back in get_facet_normals + xt::xtensor J + = xt::zeros({std::size_t(gdim), std::size_t(tdim)}); + xt::xtensor K + = xt::zeros({std::size_t(tdim), std::size_t(gdim)}); + + const std::size_t num_dofs_g = cmap.dim(); + xt::xtensor coordinate_dofs + = xt::zeros({num_dofs_g, std::size_t(gdim)}); + std::array normal = {0, 0, 0}; + std::array point = {0, 0, 0}; // To store Pi(x) + + // Loop over quadrature points + const auto cstride = (int)num_q_points * gdim; + std::vector normals(num_facets * num_q_points * gdim, 0.0); + + for (std::size_t i = 0; i < num_facets; ++i) + { + const xt::xtensor& qp_i = qp_phys[i]; + auto links = map->links((int)i); + assert(links.size() == num_q_points); + for (std::size_t q = 0; q < num_q_points; ++q) + { + + // Extract linked cell and facet at quadrature point q + auto linked_pair = facet_map->links(links[q]); + std::int32_t linked_cell = linked_pair[0]; + // Compute Pi(x) from x, and gap = Pi(x) - x + auto qp_iq = xt::row(qp_i, q); + for (int k = 0; k < gdim; ++k) + point[k] = qp_iq[k] + gap[i * gdim * num_q_points + q * gdim + k]; + + // Extract local dofs + auto x_dofs = x_dofmap.links(linked_cell); + assert(num_dofs_g == (std::size_t)x_dofmap.num_links(linked_cell)); + + for (std::size_t j = 0; j < x_dofs.size(); ++j) + { + std::copy_n(std::next(mesh_geometry.begin(), 3 * x_dofs[j]), gdim, + std::next(coordinate_dofs.begin(), j * gdim)); + } + + // Compute outward unit normal in point = Pi(x) + // Note: in the affine case potential gains can be made + // if the cells are sorted like in pack_test_functions + assert(linked_cell >= 0); + normal = dolfinx_contact::push_forward_facet_normal( + J, K, point, coordinate_dofs, linked_pair[1], cmap, + reference_normals); + // Copy normal into c + const std::size_t offset = i * cstride + q * gdim; + for (int l = 0; l < gdim; ++l) + normals[offset + l] = normal[l]; + } + } + return {std::move(normals), cstride}; +} +//------------------------------------------------------------------------------------------------ void dolfinx_contact::Contact::assemble_matrix( mat_set_fn& mat_set, [[maybe_unused]] const std::vector< diff --git a/cpp/Contact.h b/cpp/Contact.h index 1b3b710f..13430c66 100644 --- a/cpp/Contact.h +++ b/cpp/Contact.h @@ -1096,85 +1096,7 @@ class Contact /// the body coming into contact /// @param[out] c - normals ny packed on facets. std::pair, int> - pack_ny(int origin_meshtag, const xtl::span gap) - { - - // Mesh info - auto candidate_mesh = _submeshes[_opposites[origin_meshtag]].mesh(); // mesh - const int gdim = candidate_mesh->geometry().dim(); // geometrical dimension - const int tdim = candidate_mesh->topology().dim(); - auto facet_map = _submeshes[_opposites[origin_meshtag]].facet_map(); - auto cmap = candidate_mesh->geometry().cmap(); - auto x_dofmap = candidate_mesh->geometry().dofmap(); - xtl::span mesh_geometry = candidate_mesh->geometry().x(); - auto cell_type = dolfinx::mesh::cell_type_to_basix_type( - candidate_mesh->topology().cell_type()); - // Get facet normals on reference cell - auto facet_normals = basix::cell::facet_outward_normals(cell_type); - - // Select which side of the contact interface to loop from and get the - // correct map - auto map = _facet_maps[origin_meshtag]; - auto qp_phys = _qp_phys[origin_meshtag]; - const std::size_t num_facets = _cell_facet_pairs[origin_meshtag].size(); - const std::size_t num_q_points = _qp_ref_facet[0].shape(0); - std::vector c(num_facets * num_q_points * gdim, 0.0); - const auto cstride = (int)num_q_points * gdim; - // Needed for pull_back in get_facet_normals - xt::xtensor J - = xt::zeros({std::size_t(gdim), std::size_t(tdim)}); - xt::xtensor K - = xt::zeros({std::size_t(tdim), std::size_t(gdim)}); - - const std::size_t num_dofs_g = cmap.dim(); - xt::xtensor coordinate_dofs - = xt::zeros({num_dofs_g, std::size_t(gdim)}); - std::array normals = {0, 0, 0}; - std::array point = {0, 0, 0}; // To store Pi(x) - - // Loop over quadrature points - for (std::size_t i = 0; i < num_facets; i++) - { - auto links = map->links((int)i); - assert(links.size() == num_q_points); - for (std::size_t q = 0; q < num_q_points; ++q) - { - // Extract linked cell and facet at quadrature point q - auto linked_pair = facet_map->links(links[q]); - std::int32_t linked_cell = linked_pair[0]; - - // Compute Pi(x) from x, and gap = Pi(x) - x - for (int k = 0; k < gdim; ++k) - point[k] - = qp_phys[i](q, k) + gap[i * gdim * num_q_points + q * gdim + k]; - - // extract local dofs - auto x_dofs = x_dofmap.links(linked_cell); - assert(num_dofs_g == (std::size_t)x_dofmap.num_links(linked_cell)); - - for (std::size_t j = 0; j < x_dofs.size(); ++j) - { - std::copy_n(std::next(mesh_geometry.begin(), 3 * x_dofs[j]), gdim, - std::next(coordinate_dofs.begin(), j * gdim)); - } - - // Compute outward unit normal in point = Pi(x) - // Note: in the affine case potential gains can be made - // if the cells are sorted like in pack_test_functions - assert(linked_cell >= 0); - - normals = dolfinx_contact::push_forward_facet_normal( - J, K, point, coordinate_dofs, linked_pair[1], cmap, facet_normals); - - // Copy normal into c - const std::size_t c_offset = i * cstride + q * gdim; - for (int l = 0; l < gdim; l++) - c[c_offset + l] = normals[l]; - } - } - - return {std::move(c), cstride}; - } + pack_ny(int origin_meshtag, const xtl::span gap); /// Pack gap with rigid surface defined by x[gdim-1] = -g. /// g_vec = zeros(gdim), g_vec[gdim-1] = -g diff --git a/cpp/geometric_quantities.cpp b/cpp/geometric_quantities.cpp index cc2051fe..42173b50 100644 --- a/cpp/geometric_quantities.cpp +++ b/cpp/geometric_quantities.cpp @@ -5,10 +5,71 @@ // SPDX-License-Identifier: MIT #include "geometric_quantities.h" -#include -#include +#include #include + using namespace dolfinx_contact; + +void dolfinx_contact::pull_back_nonaffine( + xt::xtensor& X, xt::xtensor& J, + xt::xtensor& K, xt::xtensor& basis, + const std::array& x, const dolfinx::fem::CoordinateElement& cmap, + const xt::xtensor& cell_geometry, double tol, int max_it) +{ + assert(cmap.dim() == cell_geometry.shape(0)); + assert(X.shape(0) == 1); + assert(X.shape(1) == J.shape(1)); + // Temporary data structures for Newton iteration + const std::size_t tdim = J.shape(1); + xt::xtensor dphi({tdim, (std::size_t)cmap.dim()}); + xt::xtensor Xk = xt::zeros({(std::size_t)1, tdim}); + std::array xk; + std::array dX = {0, 0, 0}; + int k; + for (k = 0; k < max_it; ++k) + { + // Tabulate coordinate basis at Xk + cmap.tabulate(1, Xk, basis); + + // x = cell_geometry * phi + auto phi = xt::view(basis, 0, 0, xt::all(), 0); + std::fill(xk.begin(), xk.end(), 0.0); + for (std::size_t i = 0; i < cell_geometry.shape(1); ++i) + for (std::size_t j = 0; j < cell_geometry.shape(0); ++j) + xk[i] += cell_geometry(j, i) * phi[j]; + + // Compute Jacobian, its inverse and determinant + std::fill(J.begin(), J.end(), 0.0); + dphi = xt::view(basis, xt::range(1, tdim + 1), 0, xt::all(), 0); + dolfinx::fem::CoordinateElement::compute_jacobian(dphi, cell_geometry, J); + dolfinx::fem::CoordinateElement::compute_jacobian_inverse(J, K); + + // Compute dXk = K (x-xk) + std::fill(dX.begin(), dX.end(), 0.0); + for (std::size_t i = 0; i < K.shape(0); ++i) + for (std::size_t j = 0; j < K.shape(1); ++j) + dX[i] += K(i, j) * (x[j] - xk[j]); + + // Compute Xk += dX + std::transform(dX.cbegin(), std::next(dX.cbegin(), tdim), Xk.cbegin(), + Xk.begin(), [](double a, double b) { return a + b; }); + + // Compute dot(dX, dX) + auto dX_squared = std::transform_reduce(dX.cbegin(), dX.cend(), 0.0, + std::plus(), + [](const auto v) { return v * v; }); + if (std::sqrt(dX_squared) < tol) + break; + } + + std::copy(Xk.cbegin(), std::next(Xk.cbegin(), tdim), X.begin()); + if (k == max_it) + { + throw std::runtime_error( + "Newton method failed to converge for non-affine geometry"); + } +} + std::array dolfinx_contact::push_forward_facet_normal( xt::xtensor& J, xt::xtensor& K, const std::array& x, @@ -24,11 +85,13 @@ std::array dolfinx_contact::push_forward_facet_normal( const size_t gdim = K.shape(1); // Data structures for computing J inverse - xt::xtensor phi(cmap.tabulate_shape(1, 1)); - xt::xtensor dphi({tdim, cmap.tabulate_shape(1, 1)[2]}); + std::array shape = cmap.tabulate_shape(1, 1); + xt::xtensor phi(shape); + xt::xtensor dphi({tdim, shape[2]}); xt::xtensor X({1, tdim}); - // Compute Jacobian inverse + std::fill(J.begin(), J.end(), 0); + std::fill(K.begin(), K.end(), 0); if (cmap.is_affine()) { // Affine Jacobian can be computed at any point in the cell (0,0,0) in @@ -36,24 +99,17 @@ std::array dolfinx_contact::push_forward_facet_normal( std::fill(X.begin(), X.end(), 0); cmap.tabulate(1, X, phi); dphi = xt::view(phi, xt::range(1, tdim + 1), 0, xt::all(), 0); - std::fill(J.begin(), J.end(), 0); dolfinx::fem::CoordinateElement::compute_jacobian(dphi, coordinate_dofs, J); std::fill(K.begin(), K.end(), 0); dolfinx::fem::CoordinateElement::compute_jacobian_inverse(J, K); } else { - // Copy x into gdim restriction - xt::xtensor _x({1, gdim}); - std::transform(x.cbegin(), std::next(x.cbegin() + gdim), _x.begin(), - [](auto xi) { return xi; }); - // For non-affine geometries we have to compute the point in the reference - // cell, which is a nonlinear operation. Internally cmap uses a - // Newton-solver to get the reference coordinates X - cmap.pull_back_nonaffine(X, _x, coordinate_dofs); + // cell, which is a nonlinear operation. + dolfinx_contact::pull_back_nonaffine(X, J, K, phi, x, cmap, + coordinate_dofs); cmap.tabulate(1, X, phi); - dphi = xt::view(phi, xt::range(1, tdim + 1), 0, xt::all(), 0); std::fill(J.begin(), J.end(), 0); dolfinx::fem::CoordinateElement::compute_jacobian(dphi, coordinate_dofs, J); diff --git a/cpp/geometric_quantities.h b/cpp/geometric_quantities.h index 5647bb4e..2d7719a3 100644 --- a/cpp/geometric_quantities.h +++ b/cpp/geometric_quantities.h @@ -37,8 +37,33 @@ push_forward_facet_normal(xt::xtensor& J, xt::xtensor& K, const dolfinx::fem::CoordinateElement& cmap, const xt::xtensor& reference_normals); -/// Compute circumradius for a cell with given coordinates and determinant of -/// Jacobian +/// @brief Pull back a single point of a non-affine cell to the reference cell. +/// +/// Given a single cell and a point in the fell, pull it back to the reference +/// element. To compute this pull back Newton's method is employed. +/// @note The data structures `J`, `K` and `basis_values` does not correspond to +/// the Jacobian, its inverse and phi(X). They are sent in to avoid dynamic +/// memory allocation. +/// @param[in, out] X The point on the reference cell +/// @param[in, out] J The Jacobian +/// @param[in, out] K The inverse of the Jacobian +/// @param[in,out] basis Tabulated basis functions (and first order derivatives) +/// at a single point +/// @param[in] x The physical point +/// @param[in] cmap The coordinate element +/// @param[in] cell_geometry The cell geometry +/// @param[in] tol The tolerance for the Newton solver +/// @param[in] max_it The maximum number of Newton iterations +void pull_back_nonaffine(xt::xtensor& X, xt::xtensor& J, + xt::xtensor& K, + xt::xtensor& basis, + const std::array& x, + const dolfinx::fem::CoordinateElement& cmap, + const xt::xtensor& cell_geometry, + double tol = 1e-8, const int max_it = 10); + +/// Compute circumradius for a cell with given coordinates and determinant +/// of Jacobian /// @param[in] mesh The mesh /// @param[in] detJ The determinant of the Jacobian of the mapping to the /// reference cell From bb23bd0fd78563ae911cf694660c5dae0c2a5490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Dokken?= Date: Mon, 25 Apr 2022 12:40:53 +0000 Subject: [PATCH 8/8] Fix signedness of cmap dim --- cpp/geometric_quantities.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/geometric_quantities.cpp b/cpp/geometric_quantities.cpp index 42173b50..d3d018c1 100644 --- a/cpp/geometric_quantities.cpp +++ b/cpp/geometric_quantities.cpp @@ -16,7 +16,7 @@ void dolfinx_contact::pull_back_nonaffine( const std::array& x, const dolfinx::fem::CoordinateElement& cmap, const xt::xtensor& cell_geometry, double tol, int max_it) { - assert(cmap.dim() == cell_geometry.shape(0)); + assert((std::size_t)cmap.dim() == cell_geometry.shape(0)); assert(X.shape(0) == 1); assert(X.shape(1) == J.shape(1)); // Temporary data structures for Newton iteration