|
| 1 | +#include <libmesh/dirichlet_boundaries.h> |
| 2 | +#include <libmesh/dof_map.h> |
| 3 | +#include <libmesh/elem.h> |
| 4 | +#include <libmesh/equation_systems.h> |
| 5 | +#include <libmesh/exodusII_io.h> |
| 6 | +#include <libmesh/function_base.h> |
| 7 | +#include <libmesh/linear_implicit_system.h> |
| 8 | +#include <libmesh/mesh.h> |
| 9 | +#include <libmesh/mesh_generation.h> |
| 10 | +#include <libmesh/numeric_vector.h> |
| 11 | +#include <libmesh/quadrature_gauss.h> |
| 12 | +#include <libmesh/sparse_matrix.h> |
| 13 | +#include <libmesh/wrapped_function.h> |
| 14 | + |
| 15 | +#include "test_comm.h" |
| 16 | +#include "libmesh_cppunit.h" |
| 17 | + |
| 18 | +using namespace libMesh; |
| 19 | + |
| 20 | +static const Real b = 1.0; |
| 21 | +static const Real Cgap = 1.0; |
| 22 | + |
| 23 | +// Boundary IDs (counterclockwise): bottom=0, right=1, top=2, left=3 |
| 24 | +static const boundary_id_type left_id = 3; |
| 25 | +static const boundary_id_type right_id = 1; |
| 26 | +static const boundary_id_type interface_left_id = 5; |
| 27 | +static const boundary_id_type interface_right_id = 6; |
| 28 | + |
| 29 | +Number |
| 30 | +heat_exact (const Point & p, |
| 31 | + const Parameters &, |
| 32 | + const std::string &, |
| 33 | + const std::string &) |
| 34 | +{ |
| 35 | + return (p(0) < 0.5) ? p(0) : p(0) + b; |
| 36 | +} |
| 37 | + |
| 38 | +// Assemble system with temperature jump interface conditions |
| 39 | +void assemble_temperature_jump(EquationSystems &es, |
| 40 | + const std::string & /*system_name*/) |
| 41 | + { |
| 42 | + const MeshBase &mesh = es.get_mesh(); |
| 43 | + LinearImplicitSystem &system = es.get_system<LinearImplicitSystem>("TempJump"); |
| 44 | + const DofMap &dof_map = system.get_dof_map(); |
| 45 | + FEType fe_type = dof_map.variable_type(0); |
| 46 | + |
| 47 | + // FE objects for volume and face integration |
| 48 | + auto fe = FEBase::build(mesh.mesh_dimension(), fe_type); |
| 49 | + auto fe_face_L = FEBase::build(mesh.mesh_dimension(), fe_type); |
| 50 | + auto fe_face_R = FEBase::build(mesh.mesh_dimension(), fe_type); |
| 51 | + |
| 52 | + // Quadrature rules |
| 53 | + QGauss qrule(mesh.mesh_dimension(), fe_type.default_quadrature_order()); |
| 54 | + QGauss qface(mesh.mesh_dimension() - 1, fe_type.default_quadrature_order()); |
| 55 | + fe->attach_quadrature_rule(&qrule); |
| 56 | + fe_face_L->attach_quadrature_rule(&qface); |
| 57 | + fe_face_R->attach_quadrature_rule(&qface); |
| 58 | + |
| 59 | + DenseMatrix<Number> Ke; |
| 60 | + DenseVector<Number> Fe; |
| 61 | + std::vector<dof_id_type> dof_indices; |
| 62 | + |
| 63 | + const BoundaryInfo &boundary = mesh.get_boundary_info(); |
| 64 | + const double conductance = Cgap * b; // interface conductance |
| 65 | + |
| 66 | + for (const Elem *elem : mesh.active_local_element_ptr_range()) |
| 67 | + { |
| 68 | + dof_map.dof_indices(elem, dof_indices); |
| 69 | + const unsigned int n_dofs = dof_indices.size(); |
| 70 | + |
| 71 | + Ke.resize(n_dofs, n_dofs); |
| 72 | + Fe.resize(n_dofs); |
| 73 | + Ke.zero(); |
| 74 | + Fe.zero(); |
| 75 | + |
| 76 | + // --- Volume contribution --- |
| 77 | + fe->reinit(elem); |
| 78 | + const auto &JxW_vol = fe->get_JxW(); |
| 79 | + const auto &dphi_vol = fe->get_dphi(); |
| 80 | + |
| 81 | + for (unsigned int qp = 0; qp < qrule.n_points(); qp++) |
| 82 | + for (unsigned int i = 0; i < n_dofs; i++) |
| 83 | + for (unsigned int j = 0; j < n_dofs; j++) |
| 84 | + Ke(i, j) += conductance * JxW_vol[qp] * (dphi_vol[i][qp] * dphi_vol[j][qp]); |
| 85 | + |
| 86 | + // --- Left-side interface --- |
| 87 | + unsigned int side = boundary.side_with_boundary_id(elem, interface_left_id); |
| 88 | + if (side != libMesh::invalid_uint) |
| 89 | + { |
| 90 | + fe_face_L->reinit(elem, side); |
| 91 | + const auto &phi_L = fe_face_L->get_phi(); |
| 92 | + const auto &JxW_face = fe_face_L->get_JxW(); |
| 93 | + |
| 94 | + const Elem *neighbor = elem->neighbor_ptr(side); |
| 95 | + libmesh_assert(neighbor); |
| 96 | + unsigned int n_side = neighbor->which_neighbor_am_i(elem); |
| 97 | + fe_face_R->reinit(neighbor, n_side); |
| 98 | + const auto &phi_R = fe_face_R->get_phi(); |
| 99 | + |
| 100 | + std::vector<dof_id_type> dofs_L, dofs_R; |
| 101 | + dof_map.dof_indices(elem, dofs_L); |
| 102 | + dof_map.dof_indices(neighbor, dofs_R); |
| 103 | + |
| 104 | + DenseMatrix<Number> Ke_ll(n_dofs, n_dofs); |
| 105 | + DenseMatrix<Number> Ke_lr(n_dofs, dofs_R.size()); |
| 106 | + |
| 107 | + for (unsigned int qp = 0; qp < qface.n_points(); qp++) |
| 108 | + { |
| 109 | + for (unsigned int i = 0; i < n_dofs; i++) |
| 110 | + for (unsigned int j = 0; j < n_dofs; j++) |
| 111 | + Ke_ll(i, j) += Cgap * phi_L[i][qp] * phi_L[j][qp] * JxW_face[qp]; |
| 112 | + |
| 113 | + for (unsigned int i = 0; i < n_dofs; i++) |
| 114 | + for (unsigned int j = 0; j < dofs_R.size(); j++) |
| 115 | + Ke_lr(i, j) += -Cgap * phi_L[i][qp] * phi_R[j][qp] * JxW_face[qp]; |
| 116 | + } |
| 117 | + |
| 118 | + Ke += Ke_ll; |
| 119 | + system.matrix->add_matrix(Ke_lr, dofs_L, dofs_R); |
| 120 | + } |
| 121 | + |
| 122 | + // --- Right-side interface --- |
| 123 | + side = boundary.side_with_boundary_id(elem, interface_right_id); |
| 124 | + if (side != libMesh::invalid_uint) |
| 125 | + { |
| 126 | + fe_face_R->reinit(elem, side); |
| 127 | + const auto &phi_R = fe_face_R->get_phi(); |
| 128 | + const auto &JxW_face = fe_face_R->get_JxW(); |
| 129 | + |
| 130 | + const Elem *neighbor = elem->neighbor_ptr(side); |
| 131 | + libmesh_assert(neighbor); |
| 132 | + unsigned int n_side = neighbor->which_neighbor_am_i(elem); |
| 133 | + fe_face_L->reinit(neighbor, n_side); |
| 134 | + const auto &phi_L = fe_face_L->get_phi(); |
| 135 | + |
| 136 | + std::vector<dof_id_type> dofs_R, dofs_L; |
| 137 | + dof_map.dof_indices(elem, dofs_R); |
| 138 | + dof_map.dof_indices(neighbor, dofs_L); |
| 139 | + |
| 140 | + DenseMatrix<Number> Ke_rr(n_dofs, n_dofs); |
| 141 | + DenseMatrix<Number> Ke_rl(n_dofs, dofs_L.size()); |
| 142 | + |
| 143 | + for (unsigned int qp = 0; qp < qface.n_points(); qp++) |
| 144 | + { |
| 145 | + for (unsigned int i = 0; i < n_dofs; i++) |
| 146 | + for (unsigned int j = 0; j < n_dofs; j++) |
| 147 | + Ke_rr(i, j) += Cgap * phi_R[i][qp] * phi_R[j][qp] * JxW_face[qp]; |
| 148 | + |
| 149 | + for (unsigned int i = 0; i < n_dofs; i++) |
| 150 | + for (unsigned int j = 0; j < dofs_L.size(); j++) |
| 151 | + Ke_rl(i, j) += -Cgap * phi_R[i][qp] * phi_L[j][qp] * JxW_face[qp]; |
| 152 | + } |
| 153 | + |
| 154 | + Ke += Ke_rr; |
| 155 | + system.matrix->add_matrix(Ke_rl, dofs_R, dofs_L); |
| 156 | + } |
| 157 | + |
| 158 | + // Apply constraints and add local contributions |
| 159 | + dof_map.heterogenously_constrain_element_matrix_and_vector(Ke, Fe, dof_indices); |
| 160 | + system.matrix->add_matrix(Ke, dof_indices); |
| 161 | + system.rhs->add_vector(Fe, dof_indices); |
| 162 | + } |
| 163 | + |
| 164 | + system.matrix->close(); |
| 165 | + system.rhs->close(); |
| 166 | + } |
| 167 | + |
| 168 | +class DisconnectedNeighborTest : public CppUnit::TestCase { |
| 169 | +public: |
| 170 | + LIBMESH_CPPUNIT_TEST_SUITE( DisconnectedNeighborTest ); |
| 171 | + CPPUNIT_TEST( testTempJump ); |
| 172 | + CPPUNIT_TEST_SUITE_END(); |
| 173 | + |
| 174 | +private: |
| 175 | + |
| 176 | + void testTempJump() |
| 177 | + { |
| 178 | + Mesh mesh(*TestCommWorld, 2); |
| 179 | + |
| 180 | + // Domain: x in (0, 1), y in (0, 1) |
| 181 | + // Split into two subdomains: |
| 182 | + // Left subdomain: 0 <= x <= 0.5 |
| 183 | + // Right subdomain: 0.5 <= x <= 1 |
| 184 | + // |
| 185 | + // Note: Points at x = 0.5 are duplicated (same coordinates but different node IDs) |
| 186 | + // to represent an interface or discontinuity between the two subdomains. |
| 187 | + // |
| 188 | + // Coordinates layout: |
| 189 | + // |
| 190 | + // (0,1) (0.5,1)_L (0.5,1)_R (1,1) |
| 191 | + // x--------x x--------x |
| 192 | + // | | | | |
| 193 | + // | Left | Interface | Right | |
| 194 | + // | | | | |
| 195 | + // x--------x x--------x |
| 196 | + // (0,0) (0.5,0)_L (0.5,0)_R (1,0) |
| 197 | + |
| 198 | + |
| 199 | + // ---- Define points ---- |
| 200 | + |
| 201 | + // Left subdomain nodes |
| 202 | + mesh.add_point(Point(0.0, 0.0), 0); // bottom-left corner |
| 203 | + mesh.add_point(Point(0.5, 0.0), 1); // bottom-right corner of left element (interface node) |
| 204 | + mesh.add_point(Point(0.0, 1.0), 2); // top-left corner |
| 205 | + mesh.add_point(Point(0.5, 1.0), 3); // top-right corner of left element (interface node) |
| 206 | + |
| 207 | + // Right subdomain nodes (duplicated interface points) |
| 208 | + mesh.add_point(Point(0.5, 0.0), 4); // bottom-left corner of right element (same coords as node 1) (interface node) |
| 209 | + mesh.add_point(Point(1.0, 0.0), 5); // bottom-right corner |
| 210 | + mesh.add_point(Point(0.5, 1.0), 6); // top-left corner of right element (same coords as node 3) (interface node) |
| 211 | + mesh.add_point(Point(1.0, 1.0), 7); // top-right corner |
| 212 | + |
| 213 | + |
| 214 | + // ---- Define elements ---- |
| 215 | + BoundaryInfo & boundary = mesh.get_boundary_info(); |
| 216 | + // Left element (element ID = 0) |
| 217 | + { |
| 218 | + Elem * elem = mesh.add_elem(Elem::build_with_id(QUAD4, 0)); |
| 219 | + elem->set_node(0, mesh.node_ptr(0)); // bottom-left (0,0) |
| 220 | + elem->set_node(1, mesh.node_ptr(1)); // bottom-right (0.5,0) |
| 221 | + elem->set_node(2, mesh.node_ptr(3)); // top-right (0.5,1) |
| 222 | + elem->set_node(3, mesh.node_ptr(2)); // top-left (0,1) |
| 223 | + boundary.add_side(elem, 3, left_id); // left boundary |
| 224 | + boundary.add_side(elem, 1, interface_left_id); |
| 225 | + boundary.sideset_name(left_id) = "left_boundary"; |
| 226 | + boundary.sideset_name(interface_left_id) = "interface_left"; |
| 227 | + } |
| 228 | + |
| 229 | + // Right element (element ID = 1) |
| 230 | + { |
| 231 | + Elem * elem = mesh.add_elem(Elem::build_with_id(QUAD4, 1)); |
| 232 | + elem->set_node(0, mesh.node_ptr(4)); // bottom-left (0.5,0)_R |
| 233 | + elem->set_node(1, mesh.node_ptr(5)); // bottom-right (1,0) |
| 234 | + elem->set_node(2, mesh.node_ptr(7)); // top-right (1,1) |
| 235 | + elem->set_node(3, mesh.node_ptr(6)); // top-left (0.5,1)_R |
| 236 | + boundary.add_side(elem, 1, right_id); // right boundary |
| 237 | + boundary.add_side(elem, 3, interface_right_id); |
| 238 | + boundary.sideset_name(right_id) = "right_boundary"; |
| 239 | + boundary.sideset_name(interface_right_id) = "interface_right"; |
| 240 | + } |
| 241 | + |
| 242 | + // This is the key testing step: inform libMesh about the disconnected boundaries |
| 243 | + // And, in `prepare_for_use()`, libMesh will set up the disconnected neighbor relationships. |
| 244 | + mesh.add_disconnected_boundaries(interface_left_id, interface_right_id); |
| 245 | + |
| 246 | + // libMesh shouldn't renumber, or our based-on-initial-id |
| 247 | + // assertions later may fail. |
| 248 | + mesh.allow_renumbering(false); |
| 249 | + |
| 250 | + mesh.prepare_for_use(); |
| 251 | + |
| 252 | + auto elem_left = mesh.elem_ptr(0); |
| 253 | + auto elem_right = mesh.elem_ptr(1); |
| 254 | + |
| 255 | + LIBMESH_ASSERT_NUMBERS_EQUAL(elem_left->neighbor_ptr(1)->id(), elem_right->id(), 1e-15); |
| 256 | + LIBMESH_ASSERT_NUMBERS_EQUAL(elem_right->neighbor_ptr(3)->id(), elem_left->id(), 1e-15); |
| 257 | + |
| 258 | + EquationSystems es(mesh); |
| 259 | + LinearImplicitSystem & sys = |
| 260 | + es.add_system<LinearImplicitSystem>("TempJump"); |
| 261 | + |
| 262 | + const unsigned int u_var = sys.add_variable("u", FEType(FIRST, LAGRANGE)); |
| 263 | + |
| 264 | + std::set<boundary_id_type> left_bdy { left_id }; |
| 265 | + std::set<boundary_id_type> right_bdy { right_id }; |
| 266 | + std::vector<unsigned int> vars (1, u_var); |
| 267 | + |
| 268 | + auto left_fn = [](const Point &, const Parameters &, const std::string &, const std::string &) { return 0.0; }; |
| 269 | + auto right_fn = [](const Point &, const Parameters &, const std::string &, const std::string &) { return 1.0 + b; }; |
| 270 | + |
| 271 | + WrappedFunction<Number> left_val(sys, left_fn); |
| 272 | + WrappedFunction<Number> right_val(sys, right_fn); |
| 273 | + |
| 274 | + DirichletBoundary bc_left (left_bdy, vars, left_val); |
| 275 | + DirichletBoundary bc_right(right_bdy, vars, right_val); |
| 276 | + |
| 277 | + sys.get_dof_map().add_dirichlet_boundary(bc_left); |
| 278 | + sys.get_dof_map().add_dirichlet_boundary(bc_right); |
| 279 | + |
| 280 | + sys.attach_assemble_function(assemble_temperature_jump); |
| 281 | + |
| 282 | + // Ensure the DofMap creates sparsity entries between neighboring elements. |
| 283 | + // Without this, PETSc may report "New nonzero at (a,b) caused a malloc." |
| 284 | + sys.get_dof_map().set_implicit_neighbor_dofs(true); |
| 285 | + |
| 286 | + es.init(); |
| 287 | + sys.solve(); |
| 288 | + |
| 289 | + ExodusII_IO(mesh).write_equation_systems("temperature_jump.e", es); |
| 290 | + |
| 291 | + Parameters params; |
| 292 | + |
| 293 | + for (Real x=0.; x<=1.; x+=0.05) |
| 294 | + for (Real y=0.; y<=1.; y+=0.05) |
| 295 | + { |
| 296 | + Point p(x,y); |
| 297 | + const Number exact = heat_exact(p,params,"",""); |
| 298 | + const Number approx = sys.point_value(0,p); |
| 299 | + LIBMESH_ASSERT_NUMBERS_EQUAL(exact, approx, 1e-2); |
| 300 | + } |
| 301 | + } |
| 302 | +}; |
| 303 | + |
| 304 | +CPPUNIT_TEST_SUITE_REGISTRATION( DisconnectedNeighborTest ); |
0 commit comments