libMesh
Loading...
Searching...
No Matches
vector_fe_ex7.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19// <h1>Vector Finite Elements Example 7 - Hybridized Raviart-Thomas elements</h1>
20// \author Alexander Lindsay
21// \date 2023
22//
23// This example hybridizes Raviart-Thomas elements to solve a model div-grad
24// problem in both 2d and 3d. Before hybridization, the mixed problem is simply
25// a div-grad formulation, \vec{u} = -\nabla p, and \nabla \cdot \vec{u} = f, of
26// the Poisson problem in Introduction Example 3, \nabla^2 p = -f. A standard
27// (non-hybridized) Raviart-Thomas discretization of the same problem can be
28// found in Vector Example 6.
29//
30// One of the first references for the hybridized Raviart-Thomas (RT)
31// formulation is Arnold's and Brezzi's "Mixed and nonconforming finite element
32// methods: implementation, postprocessing and error estimates". The key piece
33// to the hybridized formulation is breaking the continuity of the RT space,
34// e.g. we no longer require that the normal component of the RT space be
35// continuous across interelement boundaries. In the notation of Arnold and
36// Brezzi this means a change from RT_0^k -> RT_{-1}^k where k is the polynomial
37// order of the basis (note that here k denotes the polynomial order of the
38// *divergence* of the vector shape functions, so for k = 0, the RT basis in
39// libMesh is FIRST). This breakage in continuity is accomplished by changing
40// from an FEFamily of RAVIART_THOMAS to L2_RAVIART_THOMAS. Instead of being
41// enforced by the finite element space itself, continuity of the normal
42// component of the vector field is enforced through Lagrange multipliers that
43// live on the sides of the elements. Let's introduce a subspace of L2, M_{-1}^k,
44// where the functions in M_{-1}^k are polynomials of degree of k or less. These
45// polynomials can live on our elements (I_h) or on our internal element faces
46// (E0_h); we will denote these polynomial subspaces respectively as M_{-1}^k (I_h)
47// and M_{-1}^k (E0_h). We will denote boundary faces by E. The hybridized
48// problem can be summarized as follows:
49//
50// find the approximate solutions (u_h, p_h, lambda_h) in
51// RT_{-1}^k x M_{-1}^k (I_h) x M^{-1}^k (E0_h)
52// such that
53//
54// (u, tau) - (p, div(tau)) + <lambda, tau*n>_E0 = -<g, tau*n>_E for all tau in RT_{-1}^k (I_h)
55// -(v, div(u)) = -(f, v) for all v in M_{-1}^k (I_h)
56// <mu, u*n> = 0 for all mu in M_{-1}^k (E0_h)
57//
58// with p = g on the boundary (Dirichlet problem). In the above, n denotes the
59// normal vector facing outward from the element for which we are doing local
60// assembly
61//
62// We can write this in a matrix form:
63// (A B C)(u) (G)
64// (Bt 0 0)(p) = (F)
65// (Ct 0 0)(lambda) (0)
66//
67// The matrix A is block diagonal, e.g. it is entirely localized within an
68// element due to the breakage of the continuity requirements of the RT
69// space. This allows us to write:
70//
71// u = A^{-1} (G - Bp - C lambda)
72//
73// We can further eliminate p to end up with a global system that depends only on lambda:
74//
75// E lambda = H
76//
77// where
78//
79// E = Ct * A^{-1} * (A - B * S^{-1} * Bt) * A^{-1} * C
80// S = Bt * A^{-1} * B
81// H = Hg + Hf
82// Hg = Ct * A^{-1} * (A - B * S^{-1} * Bt) * A^{-1} * G
83// Hf = Ct * A^{-1} * B * S^{-1} * F
84//
85// Here in our example we compose local element matrices A, B, C, Bt, Ct, G, and
86// F using finite element assembly. Then due to the small size of the local
87// element matrices, we actually compute the required inverses and build E and H
88// which is then fed into the global matrix and vector respectively. The
89// resulting global matrix is symmetric positive definite which is a nice change
90// over the saddle point standard RT discretization. Moreover, the global system
91// size of the hybridized problem is less than standard RT.
92//
93// Once the global system is solved. We go through finite element assembly a
94// second time to locally construct the u and p solutions from lambda. Finally,
95// lambda (and p for non-simplices) are used to reconstruct a higher-order
96// approximation of p, e.g. a solution using a basis of polynomials of degree k + 1.
97// Lambda is used in this reconstruction because it represents a
98// projection of the true solution for p onto M_{-1}^k (E0_h) ; e.g. as is so
99// often the case, the Lagrange multipliers have a physical meaning
100
101// Basic utilities.
102#include "libmesh/string_to_enum.h"
103
104// The solver packages supported by libMesh.
105#include "libmesh/enum_solver_package.h"
106
107// The mesh object and mesh generation and modification utilities.
108#include "libmesh/mesh.h"
109#include "libmesh/mesh_generation.h"
110#include "libmesh/mesh_modification.h"
111
112// Matrix and vector types.
113#include "libmesh/dense_matrix.h"
114#include "libmesh/sparse_matrix.h"
115#include "libmesh/dense_vector.h"
116#include "libmesh/numeric_vector.h"
117
118// The finite element object and the geometric element type.
119#include "libmesh/fe.h"
120#include "libmesh/elem.h"
121
122// Gauss quadrature rules.
123#include "libmesh/quadrature_gauss.h"
124
125// The dof map, which handles degree of freedom indexing.
126#include "libmesh/dof_map.h"
127
128// The system of equations.
129#include "libmesh/equation_systems.h"
130#include "libmesh/linear_implicit_system.h"
131
132// The exact solution and error computation.
133#include "libmesh/exact_solution.h"
134#include "libmesh/enum_norm_type.h"
135#include "solution_function.h"
136
137// I/O utilities.
138#include "libmesh/getpot.h"
139#include "libmesh/exodusII_io.h"
140
141#ifdef LIBMESH_HAVE_EIGEN_DENSE
142
143#include <Eigen/Dense>
144
145using namespace libMesh;
146using namespace Eigen;
147
148#ifdef LIBMESH_USE_COMPLEX_NUMBERS
149typedef MatrixXcd EigenMatrix;
150typedef VectorXcd EigenVector;
151#else
152typedef MatrixXd EigenMatrix;
153typedef VectorXd EigenVector;
154#endif
155
156void fe_assembly(EquationSystems & es, bool global_solve);
157void assemble_divgrad(EquationSystems & es, const std::string & system_name);
158
159int
160main(int argc, char ** argv)
161{
162 // Initialize libMesh.
163 LibMeshInit init(argc, argv);
164
165 // This example requires a linear solver package.
166 libmesh_example_requires(libMesh::default_solver_package() != INVALID_SOLVER_PACKAGE,
167 "--enable-petsc, --enable-trilinos, or --enable-eigen");
168
169 // Parse the input file.
170 GetPot infile("vector_fe_ex7.in");
171
172 // But allow the command line to override it.
173 infile.parse_command_line(argc, argv);
174
175 // Read in parameters from the command line and the input file.
176 const unsigned int dimension = infile("dim", 2);
177 const unsigned int grid_size = infile("grid_size", 15);
178
179 // Skip higher-dimensional examples on a lower-dimensional libMesh build.
180 libmesh_example_requires(dimension <= LIBMESH_DIM, dimension << "D support");
181
182 // Create a mesh, with dimension to be overridden later, distributed
183 // across the default MPI communicator.
184 Mesh mesh(init.comm());
185
186 // Use the MeshTools::Generation mesh generator to create a uniform
187 // grid on the cube [-1,1]^D. To accomodate Raviart-Thomas elements, we must
188 // use TRI6/7 or QUAD8/9 elements in 2d, or TET14 or HEX27 in 3d.
189 const std::string elem_str = infile("element_type", std::string("TRI6"));
190
191 libmesh_error_msg_if((dimension == 2 && elem_str != "TRI6" && elem_str != "TRI7" &&
192 elem_str != "QUAD8" && elem_str != "QUAD9") ||
193 (dimension == 3 && elem_str != "TET14" && elem_str != "HEX27"),
194 "You selected "
195 << elem_str
196 << " but this example must be run with TRI6, TRI7, QUAD8, or QUAD9 in 2d"
197 << " or with TET14, or HEX27 in 3d.");
198
199 if (dimension == 2)
201 mesh, grid_size, grid_size, -1., 1., -1., 1., Utility::string_to_enum<ElemType>(elem_str));
202 else if (dimension == 3)
204 grid_size,
205 grid_size,
206 grid_size,
207 -1.,
208 1.,
209 -1.,
210 1.,
211 -1.,
212 1.,
213 Utility::string_to_enum<ElemType>(elem_str));
214
215 // Make sure the code is robust against nodal reorderings.
217
218 // Create an equation systems object.
219 EquationSystems equation_systems(mesh);
220 const bool simplicial = (elem_str == "TRI6") || (elem_str == "TRI7") || (elem_str == "TET14");
221 equation_systems.parameters.set<bool>("simplicial") = simplicial;
222
223
224 // Declare the system "DivGrad" and its variables.
225 auto & system = equation_systems.add_system<System>("DivGrad");
226
227 // Add the LM system
228 auto & lm_system = equation_systems.add_system<LinearImplicitSystem>("Lambda");
229
230 // Adds the variable "u" and "p" to "DivGrad". "u" will be our vector field
231 // whereas "p" will be the scalar field.
233 system.add_variable("p", CONSTANT, MONOMIAL);
234 // We also add a higher order version of our 'p' variable whose solution we
235 // will compute using the Lagrange multiplier field and, for non-simplexes,
236 // the low order 'p' solution
237 system.add_variable("p_enriched", FIRST, MONOMIAL);
238
239 // Add our Lagrange multiplier to the implicit system
240 lm_system.add_variable("lambda", CONSTANT, SIDE_HIERARCHIC);
241
242 // Give the system a pointer to the matrix assembly
243 // function. This will be called when needed by the library.
245
246 // Initialize the data structures for the equation system.
247 equation_systems.init();
248
249 // Solve the implicit system for the Lagrange multiplier
250 lm_system.solve();
251
252 // Armed with our Lagrange multiplier solution, we can now compute the vector and scalar solutions
253 fe_assembly(equation_systems, /*global_solve=*/false);
254
255 //
256 // Now we will compute our solution approximation errors
257 //
258
259 ExactSolution exact_sol(equation_systems);
260
261 if (dimension == 2)
262 {
263 SolutionFunction<2> soln_func;
264 SolutionGradient<2> soln_grad;
265
266 // Build FunctionBase* containers to attach to the ExactSolution object.
267 std::vector<FunctionBase<Number> *> sols(1, &soln_func);
268 std::vector<FunctionBase<Gradient> *> grads(1, &soln_grad);
269
270 exact_sol.attach_exact_values(sols);
271 exact_sol.attach_exact_derivs(grads);
272 }
273 else if (dimension == 3)
274 {
275 SolutionFunction<3> soln_func;
276 SolutionGradient<3> soln_grad;
277
278 // Build FunctionBase* containers to attach to the ExactSolution object.
279 std::vector<FunctionBase<Number> *> sols(1, &soln_func);
280 std::vector<FunctionBase<Gradient> *> grads(1, &soln_grad);
281
282 exact_sol.attach_exact_values(sols);
283 exact_sol.attach_exact_derivs(grads);
284 }
285
286 // Use higher quadrature order for more accurate error results.
287 int extra_error_quadrature = infile("extra_error_quadrature", 2);
288 if (extra_error_quadrature)
289 exact_sol.extra_quadrature_order(extra_error_quadrature);
290
291 // Compute the error.
292 exact_sol.compute_error("DivGrad", "u");
293 exact_sol.compute_error("DivGrad", "p");
294#if !defined(LIBMESH_HAVE_PETSC) || !defined(LIBMESH_USE_REAL_NUMBERS)
295 if (simplicial)
296#endif
297 exact_sol.compute_error("DivGrad", "p_enriched");
298
299 // Print out the error values.
300 libMesh::out << "L2 error is: " << exact_sol.l2_error("DivGrad", "u") << std::endl;
301 libMesh::out << "HDiv semi-norm error is: " << exact_sol.error_norm("DivGrad", "u", HDIV_SEMINORM)
302 << std::endl;
303 libMesh::out << "HDiv error is: " << exact_sol.hdiv_error("DivGrad", "u") << std::endl;
304 libMesh::out << "L2 error for p is: " << exact_sol.l2_error("DivGrad", "p") << std::endl;
305#if !defined(LIBMESH_HAVE_PETSC) || !defined(LIBMESH_USE_REAL_NUMBERS)
306 if (simplicial)
307#endif
308 libMesh::out << "L2 error p_enriched is: " << exact_sol.l2_error("DivGrad", "p_enriched")
309 << std::endl;
310
311#ifdef LIBMESH_HAVE_EXODUS_API
312
313 // We write the file in the ExodusII format.
314 ExodusII_IO(mesh).write_equation_systems("out.e", equation_systems);
315
316#endif // #ifdef LIBMESH_HAVE_EXODUS_API
317
318 // All done.
319 return 0;
320}
321
322// We will perform finite element assembly twice. The first time to setup the global implicit system
323// for the Lagrange multiplier degrees of freedom. And then the second time to compute the vector
324// and scalar field solutions using the already-compute Lagrange multiplier solution
325void
326fe_assembly(EquationSystems & es, const bool global_solve)
327{
328 const MeshBase & mesh = es.get_mesh();
329 const unsigned int dim = mesh.mesh_dimension();
330 // Are our elements simplicial?
331 const bool simplicial = es.parameters.get<bool>("simplicial");
332
333 // The div-grad, e.g. vector-scalar system
334 auto & system = es.get_system<System>("DivGrad");
335 // Our implicit Lagrange multiplier system
336 auto & lambda_system = es.get_system<LinearImplicitSystem>("Lambda");
337
338 const auto & dof_map = system.get_dof_map();
339 const auto & lambda_dof_map = lambda_system.get_dof_map();
340
341 const FEType vector_fe_type = dof_map.variable_type(system.variable_number("u"));
342 const FEType scalar_fe_type = dof_map.variable_type(system.variable_number("p"));
343 const FEType enriched_scalar_fe_type =
344 dof_map.variable_type(system.variable_number("p_enriched"));
345 const FEType lambda_fe_type =
346 lambda_dof_map.variable_type(lambda_system.variable_number("lambda"));
347
348 // Volumetric FE objects
349 std::unique_ptr<FEVectorBase> vector_fe(FEVectorBase::build(dim, vector_fe_type));
350 std::unique_ptr<FEBase> scalar_fe(FEBase::build(dim, scalar_fe_type));
351 std::unique_ptr<FEBase> enriched_scalar_fe(FEBase::build(dim, enriched_scalar_fe_type));
352
353 // Volumetric quadrature rule
354 QGauss qrule(dim, FIFTH);
355
356 // Attach quadrature rules for the FE objects that we will reinit within the element "volume"
357 vector_fe->attach_quadrature_rule(&qrule);
358 scalar_fe->attach_quadrature_rule(&qrule);
359 enriched_scalar_fe->attach_quadrature_rule(&qrule);
360
361 // Declare finite element objects for boundary integration
362 std::unique_ptr<FEVectorBase> vector_fe_face(FEVectorBase::build(dim, vector_fe_type));
363 std::unique_ptr<FEBase> enriched_scalar_fe_face(FEBase::build(dim, enriched_scalar_fe_type));
364 std::unique_ptr<FEBase> lambda_fe_face(FEBase::build(dim, lambda_fe_type));
365
366 // Boundary integration requires one quadrature rule with dimensionality one
367 // less than the dimensionality of the element.
368 QGauss qface(dim - 1, FIFTH);
369
370 // Attach quadrature rules for the FE objects that we will reinit on the element faces
371 vector_fe_face->attach_quadrature_rule(&qface);
372 enriched_scalar_fe_face->attach_quadrature_rule(&qface);
373 lambda_fe_face->attach_quadrature_rule(&qface);
374
375 // pre-request our required volumetric data
376 const auto & JxW = vector_fe->get_JxW();
377 const auto & q_point = vector_fe->get_xyz();
378 const auto & vector_phi = vector_fe->get_phi();
379 const auto & scalar_phi = scalar_fe->get_phi();
380 const auto & enriched_scalar_phi = enriched_scalar_fe->get_phi();
381 const auto & div_vector_phi = vector_fe->get_div_phi();
382
383 // pre-request our required element face data
384 const auto & vector_phi_face = vector_fe_face->get_phi();
385 const auto & enriched_scalar_phi_face = enriched_scalar_fe_face->get_phi();
386 const auto & lambda_phi_face = lambda_fe_face->get_phi();
387 const auto & JxW_face = vector_fe_face->get_JxW();
388 const auto & qface_point = vector_fe_face->get_xyz();
389 const auto & normals = vector_fe_face->get_normals();
390
391 //
392 // We follow the notation of Cockburn in "A Charactrization of Hybridized
393 // Mixed methods for Second Order Elliptic problems"for the element
394 // matrices/vectors. We will need "Eigen" versions of many of the
395 // matrices/vectors because the libMesh DenseMatrix doesn't have an inverse
396 // API
397 //
398
399 // LM matrix and RHS after eliminating vector and scalar dofs
400 DenseMatrix<Number> E_libmesh;
401 DenseVector<Number> H_libmesh;
402 EigenMatrix E;
403 EigenMatrix H;
404 // Auxiliary matrices and RHS. A is vector-vector. B is vector-scalar. C is
405 // vector-LM. Sinv is the inverse of the Schur complement S which is given by
406 // S = Bt * A^{-1} * B. G is the RHS of the vector equation (e.g. the
407 // Dirichlet condition). F is the RHS of the scalar equation (resulting from
408 // the body force or MMS force in this case). Hg is the post-elimination
409 // version of G, and Hf is the post-elimination version of F
410 EigenMatrix A, Ainv, B, Bt, C, Ct, Sinv;
411 EigenVector G, F, Hg, Hf;
412 // element matrix for boundary LM dofs. We have to have this because our
413 // SIDE_HIERARCHIC variables live on *all* element faces, not just interior
414 // ones.
415 EigenMatrix L;
416
417 // Lambda eigen vector for constructing vector and scalar solutions
418 EigenVector Lambda;
419 // The lambda solution at the quadrature points
420 std::vector<Number> lambda_qps;
422 std::vector<Number> scalar_qps;
423
425 DenseMatrix<Number> K_enriched_scalar;
426 DenseVector<Number> F_enriched_scalar, U_enriched_scalar;
427
428 // Containers for dof indices
429 std::vector<dof_id_type> vector_dof_indices;
430 std::vector<dof_id_type> scalar_dof_indices;
431 std::vector<dof_id_type> enriched_scalar_dof_indices;
432 std::vector<dof_id_type> lambda_dof_indices;
433 std::vector<Number> lambda_solution_std_vec;
434
435 // The global system matrix
436 SparseMatrix<Number> & matrix = lambda_system.get_system_matrix();
437
438 for (const auto & elem : mesh.active_local_element_ptr_range())
439 {
440 // Retrive our dof indices for all fields
441 dof_map.dof_indices(elem, vector_dof_indices, system.variable_number("u"));
442 dof_map.dof_indices(elem, scalar_dof_indices, system.variable_number("p"));
443 lambda_dof_map.dof_indices(elem, lambda_dof_indices, lambda_system.variable_number("lambda"));
444
445 const auto vector_n_dofs = vector_dof_indices.size();
446 const auto scalar_n_dofs = scalar_dof_indices.size();
447 const auto lambda_n_dofs = lambda_dof_indices.size();
448
449 // Reinit our volume FE objects
450 vector_fe->reinit(elem);
451 scalar_fe->reinit(elem);
452
453 libmesh_assert_equal_to(vector_n_dofs, vector_phi.size());
454 libmesh_assert_equal_to(scalar_n_dofs, scalar_phi.size());
455
456 // prepare our matrix/vector data structures for the vector equation
457 A.setZero(vector_n_dofs, vector_n_dofs);
458 B.setZero(vector_n_dofs, scalar_n_dofs);
459 C.setZero(vector_n_dofs, lambda_n_dofs);
460 G.setZero(vector_n_dofs);
461 // and for the scalar equation
462 Bt.setZero(scalar_n_dofs, vector_n_dofs);
463 F.setZero(scalar_n_dofs);
464 // and for the LM equation
465 Ct.setZero(lambda_n_dofs, vector_n_dofs);
466 L.setZero(lambda_n_dofs, lambda_n_dofs);
467
468 for (const auto qp : make_range(qrule.n_points()))
469 {
470 // Vector equation dependence on vector dofs
471 for (const auto i : make_range(vector_n_dofs))
472 for (const auto j : make_range(vector_n_dofs))
473 A(i, j) += JxW[qp] * (vector_phi[i][qp] * vector_phi[j][qp]);
474
475 // Vector equation dependence on scalar dofs
476 for (const auto i : make_range(vector_n_dofs))
477 for (const auto j : make_range(scalar_n_dofs))
478 B(i, j) -= JxW[qp] * (div_vector_phi[i][qp] * scalar_phi[j][qp]);
479
480 // Scalar equation dependence on vector dofs
481 for (const auto i : make_range(scalar_n_dofs))
482 for (const auto j : make_range(vector_n_dofs))
483 Bt(i, j) -= JxW[qp] * (scalar_phi[i][qp] * div_vector_phi[j][qp]);
484
485 // This is the end of the matrix summation loop
486 // Now we build the element right-hand-side contribution.
487 // This involves a single loop in which we integrate the "forcing
488 // function" in the PDE against the scalar test functions (k).
489 {
490 const Real x = q_point[qp](0);
491 const Real y = q_point[qp](1);
492 const Real z = q_point[qp](2);
493
494 // "f" is the forcing function for the Poisson equation, which is
495 // just the divergence of the exact solution for the vector field.
496 // This is the well-known "method of manufactured solutions".
497 Real f = 0;
498 if (dim == 2)
499 f = DivGradExactSolution().forcing(x, y);
500 else if (dim == 3)
501 f = DivGradExactSolution().forcing(x, y, z);
502
503 // Scalar equation RHS
504 for (const auto i : make_range(scalar_n_dofs))
505 F(i) -= JxW[qp] * scalar_phi[i][qp] * f;
506 }
507 }
508
509 {
510 for (auto side : elem->side_index_range())
511 {
512 // Reinit our face FE objects
513 vector_fe_face->reinit(elem, side);
514 libmesh_assert_equal_to(vector_n_dofs, vector_phi_face.size());
515 lambda_fe_face->reinit(elem, side);
516
517 if (elem->neighbor_ptr(side) == nullptr)
518 for (const auto qp : make_range(qface.n_points()))
519 {
520 const Real xf = qface_point[qp](0);
521 const Real yf = qface_point[qp](1);
522 const Real zf = qface_point[qp](2);
523
524 // The boundary value for scalar field.
525 Real scalar_value = 0;
526 if (dim == 2)
527 scalar_value = DivGradExactSolution().scalar(xf, yf);
528 else if (dim == 3)
529 scalar_value = DivGradExactSolution().scalar(xf, yf, zf);
530
531 // External boundary -> Dirichlet faces -> Vector equaton RHS
532 for (const auto i : make_range(vector_n_dofs))
533 G(i) -= JxW_face[qp] * (vector_phi_face[i][qp] * normals[qp]) * scalar_value;
534
535 // Need to do something with the external boundary LM dofs to prevent the matrix from
536 // being singular
537 for (const auto i : make_range(lambda_n_dofs))
538 for (const auto j : make_range(lambda_n_dofs))
539 L(i, j) += JxW_face[qp] * lambda_phi_face[i][qp] * lambda_phi_face[j][qp];
540 }
541 else
542 for (const auto qp : make_range(qface.n_points()))
543 {
544 // Vector equation dependence on LM dofs
545 for (const auto i : make_range(vector_n_dofs))
546 for (const auto j : make_range(lambda_n_dofs))
547 C(i, j) +=
548 JxW_face[qp] * (vector_phi_face[i][qp] * normals[qp]) * lambda_phi_face[j][qp];
549
550 // LM equation dependence on vector dofs
551 for (const auto i : make_range(lambda_n_dofs))
552 for (const auto j : make_range(vector_n_dofs))
553 Ct(i, j) +=
554 JxW_face[qp] * lambda_phi_face[i][qp] * (vector_phi_face[j][qp] * normals[qp]);
555 }
556 }
557 }
558
559 Ainv = A.inverse();
560 // Compute the Schur complement inverse
561 Sinv = (Bt * Ainv * B).inverse();
562 // These equations can be derived at by eliminating the u and p dofs from the system
563 // (A B C)(u) (G)
564 // (Bt 0 0)(p) = (F)
565 // (Ct 0 0)(lambda) (0)
566 E = Ct * Ainv * (A - B * Sinv * Bt) * Ainv * C;
567 E = E + L;
568 Hg = Ct * Ainv * (A - B * Sinv * Bt) * Ainv * G;
569 Hf = Ct * Ainv * B * Sinv * F;
570 H = Hg + Hf;
571
572 // Build our libMesh data structures from the Eigen ones
573 E_libmesh.resize(E.rows(), E.cols());
574 H_libmesh.resize(H.size());
575 libmesh_assert((E.rows() == E.cols()) && (E.rows() == H.size()));
576 for (const auto i : make_range(E.rows()))
577 {
578 H_libmesh(i) = H(i);
579 for (const auto j : make_range(E.cols()))
580 E_libmesh(i, j) = E(i, j);
581 }
582
583 if (global_solve)
584 {
585 // We were performing our finite element assembly for the implicit solve step of our
586 // example. Add our local element vectors/matrices into the global system
587 dof_map.constrain_element_matrix_and_vector(E_libmesh, H_libmesh, lambda_dof_indices);
588 matrix.add_matrix(E_libmesh, lambda_dof_indices);
589 lambda_system.rhs->add_vector(H_libmesh, lambda_dof_indices);
590 }
591 else
592 {
593 //
594 // We are doing our finite element assembly for the second time. We now know the Lagrange
595 // multiplier solution. With that and the local element matrices and vectors we can compute
596 // the vector and scalar solutions
597 //
598
599 Lambda.resize(lambda_n_dofs);
600 lambda_system.current_local_solution->get(lambda_dof_indices, lambda_solution_std_vec);
601 for (const auto i : make_range(lambda_n_dofs))
602 Lambda(i) = lambda_solution_std_vec[i];
603 const auto scalar_soln = Sinv * Bt * Ainv * G - Sinv * F - Sinv * Bt * Ainv * C * Lambda;
604 const auto vector_soln = Ainv * (G - B * scalar_soln - C * Lambda);
605 for (const auto i : make_range(vector_n_dofs))
606 system.solution->set(vector_dof_indices[i], vector_soln(i));
607 for (const auto i : make_range(scalar_n_dofs))
608 system.solution->set(scalar_dof_indices[i], scalar_soln(i));
609
610#if !defined(LIBMESH_HAVE_PETSC) || !defined(LIBMESH_USE_REAL_NUMBERS)
611 if (!simplicial)
612 // We don't support SVD solves in this configuration
613 continue;
614#endif
615
616 //
617 // Now solve for the enriched scalar solution using our Lagrange multiplier solution and, for
618 // non-simplexes, the lower-order scalar solution. Note that the Lagrange multiplier
619 // represents the trace of p so it is a logical choice to leverage in this postprocessing
620 // stage!
621 //
622
623 dof_map.dof_indices(elem, enriched_scalar_dof_indices, system.variable_number("p_enriched"));
624 const auto enriched_scalar_n_dofs = enriched_scalar_dof_indices.size();
625 const auto m = simplicial ? lambda_n_dofs : lambda_n_dofs + 1;
626 const auto n = enriched_scalar_n_dofs;
627
628 K_enriched_scalar.resize(m, n);
629 F_enriched_scalar.resize(m);
630 U_enriched_scalar.resize(n);
631
632 // L2 projection of the enriched scalar into the LM space
633 for (const auto side : elem->side_index_range())
634 {
635 vector_fe_face->reinit(elem, side); // for JxW_face and qface_point
636 enriched_scalar_fe_face->reinit(elem, side);
637 lambda_fe_face->reinit(elem, side);
638 if (elem->neighbor_ptr(side) == nullptr)
639 for (const auto qp : make_range(qface.n_points()))
640 {
641 const Real xf = qface_point[qp](0);
642 const Real yf = qface_point[qp](1);
643 const Real zf = qface_point[qp](2);
644
645 // The boundary value for scalar field.
646 Real scalar_value = 0;
647 if (dim == 2)
648 scalar_value = DivGradExactSolution().scalar(xf, yf);
649 else if (dim == 3)
650 scalar_value = DivGradExactSolution().scalar(xf, yf, zf);
651
652 for (const auto i : make_range(lambda_n_dofs))
653 {
654 F_enriched_scalar(i) += JxW_face[qp] * lambda_phi_face[i][qp] * scalar_value;
655 for (const auto j : make_range(enriched_scalar_n_dofs))
656 K_enriched_scalar(i, j) +=
657 JxW_face[qp] * lambda_phi_face[i][qp] * enriched_scalar_phi_face[j][qp];
658 }
659 }
660 else
661 {
662 // compute local face lambda solution
663 lambda_qps.resize(qface.n_points());
664 for (auto & lambda_qp : lambda_qps)
665 lambda_qp = 0;
666 for (const auto qp : make_range(qface.n_points()))
667 for (const auto i : index_range(lambda_solution_std_vec))
668 lambda_qps[qp] += lambda_solution_std_vec[i] * lambda_phi_face[i][qp];
669
670 for (const auto qp : make_range(qface.n_points()))
671 for (const auto i : make_range(lambda_n_dofs))
672 {
673 F_enriched_scalar(i) += JxW_face[qp] * lambda_phi_face[i][qp] * lambda_qps[qp];
674 for (const auto j : make_range(enriched_scalar_n_dofs))
675 K_enriched_scalar(i, j) +=
676 JxW_face[qp] * lambda_phi_face[i][qp] * enriched_scalar_phi_face[j][qp];
677 }
678 }
679 }
680
681 if (simplicial)
682 K_enriched_scalar.lu_solve(F_enriched_scalar, U_enriched_scalar);
683 else
684 {
685 //
686 // For tensor product elements, the system of equations coming from the L2 projection into
687 // the Lagrange multiplier space is singular. To make the system nonsingular, we add an L2
688 // projection into the low-order scalar solution space.
689 //
690
691 enriched_scalar_fe->reinit(elem);
692 libmesh_assert(scalar_n_dofs == 1);
693 libmesh_assert(scalar_soln.size() == 1);
694 libmesh_assert(scalar_phi.size() == 1);
695
696 scalar_qps.resize(qrule.n_points());
697 for (auto & scalar_qp : scalar_qps)
698 scalar_qp = 0;
699 for (const auto qp : make_range(qrule.n_points()))
700 scalar_qps[qp] += scalar_soln(0) * scalar_phi[0][qp];
701 for (const auto qp : make_range(qrule.n_points()))
702 {
703 F_enriched_scalar(n) += JxW[qp] * scalar_phi[0][qp] * scalar_qps[qp];
704 for (const auto j : make_range(n))
705 K_enriched_scalar(n, j) += JxW[qp] * scalar_phi[0][qp] * enriched_scalar_phi[j][qp];
706 }
707 // We have more rows than columns so an LU solve isn't an option
708 K_enriched_scalar.svd_solve(F_enriched_scalar, U_enriched_scalar);
709 }
710
711 // Our solution for the local enriched scalar dofs is complete. Insert into the global vector
712 system.solution->insert(U_enriched_scalar, enriched_scalar_dof_indices);
713 }
714 }
715
716 if (!global_solve)
717 {
718 system.solution->close();
719 // Scatter solution into the current_solution which is used in error computation
720 system.update();
721 }
722}
723
724// Call this assembly function when assembling the global implicit system
725void
726assemble_divgrad(EquationSystems & es, const std::string & libmesh_dbg_var(system_name))
727{
728 libmesh_assert_equal_to(system_name, "Lambda");
729 fe_assembly(es, /*global_solve=*/true);
730}
731
732#else
733
734int
736{
737 return 0;
738}
739
740#endif
unsigned int dim
Real forcing(Real x, Real y)
Real scalar(Real x, Real y)
Defines a dense matrix for use in Finite Element-type computations.
void svd_solve(const DenseVector< T > &rhs, DenseVector< T > &x, Real rcond=std::numeric_limits< Real >::epsilon()) const
Solve the system of equations for in the least-squares sense.
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
void lu_solve(const DenseVector< T > &b, DenseVector< T > &x)
Solve the system Ax=b given the input vector b.
Defines a dense vector for use in Finite Element-type computations.
void resize(const unsigned int n)
Resize the vector.
This is the EquationSystems class.
const MeshBase & get_mesh() const
Parameters parameters
Data structure holding arbitrary parameters.
virtual void init()
Initialize all the systems.
virtual System & add_system(std::string_view system_type, std::string_view name)
Add the system of type system_type named name to the systems array.
const T_sys & get_system(std::string_view name) const
This class handles the computation of the L2 and/or H1 error for the Systems in the EquationSystems o...
Real l2_error(std::string_view sys_name, std::string_view unknown_name)
Real error_norm(std::string_view sys_name, std::string_view unknown_name, const FEMNormType &norm)
void attach_exact_values(const std::vector< FunctionBase< Number > * > &f)
Clone and attach arbitrary functors which compute the exact values of the EquationSystems' solutions ...
Real hdiv_error(std::string_view sys_name, std::string_view unknown_name)
void compute_error(std::string_view sys_name, std::string_view unknown_name)
Computes and stores the error in the solution value e = u-u_h, the gradient grad(e) = grad(u) - grad(...
void extra_quadrature_order(const int extraorder)
Increases or decreases the order of the quadrature rule used for numerical integration.
void attach_exact_derivs(const std::vector< FunctionBase< Gradient > * > &g)
Clone and attach arbitrary functors which compute the exact gradients of the EquationSystems' solutio...
The ExodusII_IO class implements reading meshes in the ExodusII file format from Sandia National Labs...
Definition exodusII_io.h:53
virtual void write_equation_systems(const std::string &fname, const EquationSystems &es, const std::set< std::string > *system_names=nullptr) override
Writes out the solution for no specific time or timestep.
static std::unique_ptr< FEGenericBase > build(const unsigned int dim, const FEType &type)
Builds a specific finite element type.
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition fe_type.h:197
The LibMeshInit class, when constructed, initializes the dependent libraries (e.g.
Definition libmesh.h:92
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
virtual void reinit() override
Reinitializes the member data fields associated with the system, so that, e.g., assemble() may be use...
This is the MeshBase class.
Definition mesh_base.h:81
unsigned int mesh_dimension() const
Definition mesh_base.C:430
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
T & set(const std::string &)
Definition parameters.h:494
const T & get(std::string_view) const
Definition parameters.h:451
unsigned int n_points() const
Definition quadrature.h:131
This class implements specific orders of Gauss quadrature.
Generic sparse matrix.
virtual void add_matrix(const DenseMatrix< T > &dm, const std::vector< numeric_index_type > &rows, const std::vector< numeric_index_type > &cols)=0
Add the full matrix dm to the SparseMatrix.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
std::unique_ptr< NumericVector< Number > > current_local_solution
All the values I need to compute my contribution to the simulation at hand.
Definition system.h:1667
const FEType & variable_type(const unsigned int i) const
Definition system.C:2721
void attach_assemble_function(void fptr(EquationSystems &es, const std::string &name))
Register a user function to use in assembling the system matrix and RHS.
Definition system.C:1959
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
Adds the variable var to the list of variables for this system.
Definition system.C:1344
NumericVector< Number > & add_vector(std::string_view vec_name, const bool projections=true, const ParallelType type=PARALLEL)
Adds the additional vector vec_name to this system.
Definition system.C:756
virtual void solve()
Solves the system.
Definition system.h:349
unsigned int variable_number(std::string_view var) const
Definition system.C:1398
const DofMap & get_dof_map() const
Definition system.h:2417
MeshBase & mesh
void build_square(UnstructuredMesh &mesh, const unsigned int nx, const unsigned int ny, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 2D meshes.
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.
void permute_elements(MeshBase &mesh)
Randomly permute the nodal ordering of each element (without twisting the element mapping).
The libMesh namespace provides an interface to certain functionality in the library.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
SolverPackage default_solver_package()
Definition libmesh.C:1064
libmesh_assert(ctx)
OStreamProxy out
@ L2_RAVIART_THOMAS
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176
Definition assembly.h:39
VectorXcd EigenVector
void fe_assembly(EquationSystems &es, bool global_solve)
MatrixXcd EigenMatrix
void assemble_divgrad(EquationSystems &es, const std::string &system_name)
int main()