libMesh
Loading...
Searching...
No Matches
fem_system_ex6.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// <h1>FEMSystem Example 6 - Nonlinear Diffusion with Automatic Differentiation</h1>
19// \author Alexander Lindsay
20// \date 2026
21//
22// This example solves the steady nonlinear diffusion equation
23//
24// -div((1 + u^2) grad u) = f
25//
26// on the unit square with homogeneous Dirichlet boundary conditions,
27// with f chosen so that u = sin(pi x) sin(pi y).
28//
29// The element residual is evaluated with MetaPhysicL dual numbers in
30// place of libMesh's Number type, which is real or complex depending
31// on how libMesh was configured. Each element degree of freedom is
32// seeded with a unit derivative with respect to itself, and the
33// derivatives then propagate through the solution value, its
34// gradient, and the nonlinear diffusivity. The Jacobian is read off
35// the derivatives of the residual, so it is exact without any
36// hand-coded linearization. libMesh's numeric types, such as
37// VectorValue, accept dual-number components, so the residual is
38// written with the same expressions used for assembly with Number.
39//
40// Exactness of the Jacobian is visible in the quadratic convergence of
41// Newton's method. Setting verify_analytic_jacobians to a positive
42// tolerance additionally compares each element Jacobian against a
43// finite-difference approximation.
44
45// C++ includes
46#include <cmath>
47#include <memory>
48
49// libMesh includes
50#include "libmesh/libmesh.h"
51#include "libmesh/mesh.h"
52#include "libmesh/mesh_generation.h"
53#include "libmesh/equation_systems.h"
54#include "libmesh/fem_system.h"
55#include "libmesh/fem_context.h"
56#include "libmesh/steady_solver.h"
57#include "libmesh/diff_solver.h"
58#include "libmesh/dof_map.h"
59#include "libmesh/dirichlet_boundaries.h"
60#include "libmesh/zero_function.h"
61#include "libmesh/exact_solution.h"
62#include "libmesh/getpot.h"
63#include "libmesh/enum_solver_package.h"
64#include "libmesh/int_range.h"
65#include "libmesh/vector_value.h"
66
67// MetaPhysicL includes
68#ifdef LIBMESH_HAVE_METAPHYSICL
69#include "metaphysicl/dualnumber.h"
70#include "metaphysicl/dynamicsparsenumberarray.h"
71#include "metaphysicl/raw_type.h"
72#endif
73
74using namespace libMesh;
75
76#ifdef LIBMESH_HAVE_METAPHYSICL
77
78// A Number together with its derivatives with respect to the
79// element degrees of freedom, stored sparsely by local dof index.
80typedef MetaPhysicL::DualNumber<Number, MetaPhysicL::DynamicSparseNumberArray<Number, unsigned int>>
82
83// The manufactured solution
85exact_value(const Point & p, const Parameters &, const std::string &, const std::string &)
86{
87 return std::sin(libMesh::pi * p(0)) * std::sin(libMesh::pi * p(1));
88}
89
91exact_gradient(const Point & p, const Parameters &, const std::string &, const std::string &)
92{
93 const Real pi = libMesh::pi;
94 return RealGradient(pi * std::cos(pi * p(0)) * std::sin(pi * p(1)),
95 pi * std::sin(pi * p(0)) * std::cos(pi * p(1)));
96}
97
98// The source term f = -div((1 + u^2) grad u) for the manufactured solution,
99// expanded as -(1 + u^2) lap(u) - 2 u |grad u|^2 with lap(u) = -2 pi^2 u.
100Number
101forcing(const Point & p)
102{
103 const Number u = exact_value(p, Parameters(), "", "");
104 const Gradient grad_u = exact_gradient(p, Parameters(), "", "");
105 return 2 * libMesh::pi * libMesh::pi * (1. + u * u) * u - 2. * u * grad_u.norm_sq();
106}
107
114{
115public:
116 NonlinearDiffusionSystem(EquationSystems & es, const std::string & name, const unsigned int number)
117 : FEMSystem(es, name, number)
118 {
119 }
120
121protected:
122 virtual void init_data() override;
123
124 virtual void init_context(DiffContext & context) override;
125
126 virtual bool element_time_derivative(bool request_jacobian, DiffContext & context) override;
127
128private:
129 unsigned int _u_var;
130};
131
132void
134{
135 _u_var = this->add_variable("u", SECOND, LAGRANGE);
136
137 // u = 0 on all four sides of the square
141
142 FEMSystem::init_data();
143}
144
145void
147{
148 FEMContext & c = cast_ref<FEMContext &>(context);
149
150 FEBase * fe = nullptr;
151 c.get_element_fe(_u_var, fe);
152 fe->get_JxW();
153 fe->get_phi();
154 fe->get_dphi();
155 fe->get_xyz();
156
157 // There are no side integrals.
158 FEBase * side_fe = nullptr;
159 c.get_side_fe(_u_var, side_fe);
160 side_fe->get_nothing();
161
162 FEMSystem::init_context(context);
163}
164
165bool
167{
168 FEMContext & c = cast_ref<FEMContext &>(context);
169
170 FEBase * fe = nullptr;
171 c.get_element_fe(_u_var, fe);
172 const std::vector<Real> & JxW = fe->get_JxW();
173 const std::vector<std::vector<Real>> & phi = fe->get_phi();
174 const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
175 const std::vector<Point> & xyz = fe->get_xyz();
176
177 const unsigned int n_dofs = c.n_dof_indices(_u_var);
180
181 // Seed each element degree of freedom with a unit derivative with
182 // respect to itself.
184 std::vector<ADNumber> u_dofs(n_dofs);
185 for (const auto j : make_range(n_dofs))
186 {
187 u_dofs[j] = u_coefs(j);
188 u_dofs[j].derivatives().insert(j) = 1;
189 }
190
191 // FEMSystem's steady residual convention is F(u) = 0 with F the
192 // weak form of div((1 + u^2) grad u) + f.
193 std::vector<ADNumber> residual(n_dofs, 0);
194 for (const auto qp : index_range(JxW))
195 {
196 ADNumber u = 0;
198 for (const auto j : make_range(n_dofs))
199 {
200 u += u_dofs[j] * phi[j][qp];
201 grad_u += u_dofs[j] * dphi[j][qp];
202 }
203
204 // In complex builds the Jacobian is a complex derivative, which
205 // exists only for a holomorphic residual. The diffusivity
206 // therefore uses u^2: u * conj(u) is not holomorphic, and
207 // MetaPhysicL assigns NaN derivatives to conj() for that reason.
208 const ADNumber diffusivity = 1. + u * u;
209 const Number f = forcing(xyz[qp]);
210
211 for (const auto i : make_range(n_dofs))
212 residual[i] += JxW[qp] * (-diffusivity * (grad_u * dphi[i][qp]) + f * phi[i][qp]);
213 }
214
215 // The residual is the value part of each dual number, and its
216 // derivatives are the corresponding Jacobian row.
217 for (const auto i : make_range(n_dofs))
218 {
219 F(i) += MetaPhysicL::raw_value(residual[i]);
220
221 if (request_jacobian)
222 {
223 const auto & derivatives = residual[i].derivatives();
224 for (const auto k : index_range(derivatives.nude_indices()))
225 K(i, derivatives.nude_indices()[k]) +=
226 derivatives.nude_data()[k] * c.get_elem_solution_derivative();
227 }
228 }
229
230 return request_jacobian;
231}
232
233#endif // LIBMESH_HAVE_METAPHYSICL
234
235int
236main(int argc, char ** argv)
237{
238 // Initialize libMesh.
239 LibMeshInit init(argc, argv);
240
241 // This example requires a linear solver package.
242 libmesh_example_requires(libMesh::default_solver_package() != INVALID_SOLVER_PACKAGE,
243 "--enable-petsc, --enable-trilinos, or --enable-eigen");
244
245 // Dual numbers are provided by MetaPhysicL.
246#ifndef LIBMESH_HAVE_METAPHYSICL
247 libmesh_example_requires(false, "--enable-metaphysicl");
248#else
249
250 // The mesh is two-dimensional.
251 libmesh_example_requires(2 <= LIBMESH_DIM, "2D support");
252
253 // We use Dirichlet boundary conditions here.
254#ifndef LIBMESH_ENABLE_DIRICHLET
255 libmesh_example_requires(false, "--enable-dirichlet");
256#endif
257
258 // Parse the input file, allowing the command line to override it.
259 GetPot infile("fem_system_ex6.in");
260 infile.parse_command_line(argc, argv);
261
262 const unsigned int grid_size = infile("grid_size", 16);
263
264 Mesh mesh(init.comm());
265 MeshTools::Generation::build_square(mesh, grid_size, grid_size, 0., 1., 0., 1., QUAD9);
267
268 EquationSystems equation_systems(mesh);
269 auto & system = equation_systems.add_system<NonlinearDiffusionSystem>("NonlinearDiffusion");
270 system.time_solver = std::make_unique<SteadySolver>(system);
271
272 // A positive tolerance checks each element Jacobian against a
273 // finite-difference approximation.
274 system.verify_analytic_jacobians = infile("verify_analytic_jacobians", 0.);
275
276 equation_systems.init();
277
278 DiffSolver & solver = *(system.time_solver->diff_solver());
279 solver.quiet = false;
280 solver.verbose = true;
281 solver.max_nonlinear_iterations = infile("max_nonlinear_iterations", 10);
282 solver.relative_residual_tolerance = infile("relative_residual_tolerance", 1.e-12);
283 solver.relative_step_tolerance = infile("relative_step_tolerance", 0.);
284 solver.absolute_residual_tolerance = infile("absolute_residual_tolerance", 0.);
285
286 equation_systems.print_info();
287
288 system.solve();
289
290 ExactSolution exact_sol(equation_systems);
293 exact_sol.compute_error("NonlinearDiffusion", "u");
294 libMesh::out << "L2 error: " << exact_sol.l2_error("NonlinearDiffusion", "u") << std::endl
295 << "H1 error: " << exact_sol.h1_error("NonlinearDiffusion", "u") << std::endl;
296
297#endif // LIBMESH_HAVE_METAPHYSICL
298
299 return 0;
300}
An FEMSystem for -div((1 + u^2) grad u) = f whose element Jacobian is computed by forward-mode automa...
NonlinearDiffusionSystem(EquationSystems &es, const std::string &name, const unsigned int number)
virtual void init_context(DiffContext &context) override
virtual void init_data() override
Initializes the member data fields associated with the system, so that, e.g., assemble() may be used.
virtual bool element_time_derivative(bool request_jacobian, DiffContext &context) override
Adds the time derivative contribution on elem to elem_residual.
Defines a dense submatrix for use in Finite Element-type computations.
Defines a dense subvector for use in finite element computations.
This class provides all data required for a physics package (e.g.
Real get_elem_solution_derivative() const
The derivative of the current elem_solution w.r.t.
const DenseVector< Number > & get_elem_solution() const
Accessor for element solution.
unsigned int n_dof_indices() const
Total number of dof indices on the element.
const DenseVector< Number > & get_elem_residual() const
Const accessor for element residual.
const DenseMatrix< Number > & get_elem_jacobian() const
Const accessor for element Jacobian.
This is a generic class that defines a solver to handle ImplicitSystem classes, including NonlinearIm...
Definition diff_solver.h:70
Real absolute_residual_tolerance
The DiffSolver should exit after the residual is reduced to either less than absolute_residual_tolera...
bool verbose
The DiffSolver may print a lot more to libMesh::out if verbose is set to true; default is false.
unsigned int max_nonlinear_iterations
The DiffSolver should exit in failure if max_nonlinear_iterations is exceeded and continue_after_max_...
bool quiet
The DiffSolver should not print anything to libMesh::out unless quiet is set to false; default is tru...
std::unique_ptr< TimeSolver > time_solver
A pointer to the solver object we're going to use.
This class allows one to associate Dirichlet boundary values with a given set of mesh boundary ids an...
void add_dirichlet_boundary(const DirichletBoundary &dirichlet_boundary)
Adds a copy of the specified Dirichlet boundary to the system.
This is the EquationSystems class.
void print_info(std::ostream &os=libMesh::out) const
Prints information about the equation systems, by default to libMesh::out.
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.
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 h1_error(std::string_view sys_name, std::string_view unknown_name)
void attach_exact_deriv(unsigned int sys_num, FunctionBase< Gradient > *g)
Clone and attach an arbitrary functor which computes the exact gradient of the system sys_num solutio...
void attach_exact_value(unsigned int sys_num, FunctionBase< Number > *f)
Clone and attach an arbitrary functor which computes the exact value of the system sys_num solution a...
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 get_nothing() const
virtual_for_inffe const std::vector< Real > & get_JxW() const
virtual_for_inffe const std::vector< Point > & get_xyz() const
This class forms the foundation from which generic finite elements may be derived.
Definition fe_base.h:86
const std::vector< std::vector< OutputShape > > & get_phi() const
Definition fe_base.h:207
const std::vector< std::vector< OutputGradient > > & get_dphi() const
Definition fe_base.h:252
This class provides all data required for a physics package (e.g.
Definition fem_context.h:64
void get_side_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for edge/face (2D/3D) finite element object for variable var for the largest dimension in th...
void get_element_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for interior finite element object for variable var for the largest dimension in the mesh.
This class provides a specific system class.
Definition fem_system.h:55
The LibMeshInit class, when constructed, initializes the dependent libraries (e.g.
Definition libmesh.h:92
void print_info(std::ostream &os=libMesh::out, const unsigned int verbosity=0, const bool global=true) const
Prints relevant information about the mesh.
Definition mesh_base.C:1770
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
This class provides the ability to map between arbitrary, user-defined strings and several data types...
Definition parameters.h:75
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:41
const std::string & name() const
Definition system.h:2385
dof_id_type n_dofs() const
Definition system.C:118
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
const DofMap & get_dof_map() const
Definition system.h:2417
unsigned int number() const
Definition system.h:2393
auto norm_sq() const
This class defines a vector in LIBMESH_DIM dimensional Real or Complex space.
ConstFunction that simply returns 0.
Gradient exact_gradient(const Point &p, const Parameters &, const std::string &, const std::string &)
Number exact_value(const Point &p, const Parameters &, const std::string &, const std::string &)
MetaPhysicL::DualNumber< Number, MetaPhysicL::DynamicSparseNumberArray< Number, unsigned int > > ADNumber
Number forcing(const Point &p)
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.
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
OStreamProxy out
const Real pi
.
Definition libmesh.h:292
const Number zero
.
Definition libmesh.h:297
RealVectorValue RealGradient
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
int main()