MFEM v4.7.0
Finite element discretization library
Loading...
Searching...
No Matches
ex36.cpp
Go to the documentation of this file.
1// MFEM Example 36
2//
3// Compile with: make ex36
4//
5// Sample runs: ex36 -o 2
6// ex36 -o 2 -r 4
7//
8// Description: This example code demonstrates the use of MFEM to solve the
9// bound-constrained energy minimization problem
10//
11// minimize ||∇u||² subject to u ≥ ϕ in H¹₀.
12//
13// This is known as the obstacle problem, and it is a simple
14// mathematical model for contact mechanics.
15//
16// In this example, the obstacle ϕ is a half-sphere centered
17// at the origin of a circular domain Ω. After solving to a
18// specified tolerance, the numerical solution is compared to
19// a closed-form exact solution to assess accuracy.
20//
21// The problem is discretized and solved using the proximal
22// Galerkin finite element method, introduced by Keith and
23// Surowiec [1].
24//
25// This example highlights the ability of MFEM to deliver high-
26// order solutions to variation inequality problems and
27// showcases how to set up and solve nonlinear mixed methods.
28//
29// [1] Keith, B. and Surowiec, T. (2023) Proximal Galerkin: A structure-
30// preserving finite element method for pointwise bound constraints.
31// arXiv:2307.12444 [math.NA]
32
33#include "mfem.hpp"
34#include <fstream>
35#include <iostream>
36
37using namespace std;
38using namespace mfem;
39
43
44class LogarithmGridFunctionCoefficient : public Coefficient
45{
46protected:
47 GridFunction *u; // grid function
48 Coefficient *obstacle;
49 real_t min_val;
50
51public:
52 LogarithmGridFunctionCoefficient(GridFunction &u_, Coefficient &obst_,
53 real_t min_val_=-36)
54 : u(&u_), obstacle(&obst_), min_val(min_val_) { }
55
57};
58
59class ExponentialGridFunctionCoefficient : public Coefficient
60{
61protected:
63 Coefficient *obstacle;
64 real_t min_val;
65 real_t max_val;
66
67public:
68 ExponentialGridFunctionCoefficient(GridFunction &u_, Coefficient &obst_,
69 real_t min_val_=0.0, real_t max_val_=1e6)
70 : u(&u_), obstacle(&obst_), min_val(min_val_), max_val(max_val_) { }
71
73};
74
75int main(int argc, char *argv[])
76{
77 // 1. Parse command-line options.
78 int order = 1;
79 int max_it = 10;
80 int ref_levels = 3;
81 real_t alpha = 1.0;
82 real_t tol = 1e-5;
83 bool visualization = true;
84
85 OptionsParser args(argc, argv);
86 args.AddOption(&order, "-o", "--order",
87 "Finite element order (polynomial degree).");
88 args.AddOption(&ref_levels, "-r", "--refs",
89 "Number of h-refinements.");
90 args.AddOption(&max_it, "-mi", "--max-it",
91 "Maximum number of iterations");
92 args.AddOption(&tol, "-tol", "--tol",
93 "Stopping criteria based on the difference between"
94 "successive solution updates");
95 args.AddOption(&alpha, "-step", "--step",
96 "Step size alpha");
97 args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
98 "--no-visualization",
99 "Enable or disable GLVis visualization.");
100 args.Parse();
101 if (!args.Good())
102 {
103 args.PrintUsage(cout);
104 return 1;
105 }
106 args.PrintOptions(cout);
107
108 // 2. Read the mesh from the mesh file.
109 const char *mesh_file = "../data/disc-nurbs.mesh";
110 Mesh mesh(mesh_file, 1, 1);
111 int dim = mesh.Dimension();
112
113 // 3. Postprocess the mesh.
114 // 3A. Refine the mesh to increase the resolution.
115 for (int l = 0; l < ref_levels; l++)
116 {
117 mesh.UniformRefinement();
118 }
119
120 // 3B. Interpolate the geometry after refinement to control geometry error.
121 // NOTE: Minimum second-order interpolation is used to improve the accuracy.
122 int curvature_order = max(order,2);
123 mesh.SetCurvature(curvature_order);
124
125 // 3C. Rescale the domain to a unit circle (radius = 1).
126 GridFunction *nodes = mesh.GetNodes();
127 real_t scale = 2*sqrt(2);
128 *nodes /= scale;
129
130 // 4. Define the necessary finite element spaces on the mesh.
131 H1_FECollection H1fec(order+1, dim);
132 FiniteElementSpace H1fes(&mesh, &H1fec);
133
134 L2_FECollection L2fec(order-1, dim);
135 FiniteElementSpace L2fes(&mesh, &L2fec);
136
137 cout << "Number of H1 finite element unknowns: "
138 << H1fes.GetTrueVSize() << endl;
139 cout << "Number of L2 finite element unknowns: "
140 << L2fes.GetTrueVSize() << endl;
141
142 Array<int> offsets(3);
143 offsets[0] = 0;
144 offsets[1] = H1fes.GetVSize();
145 offsets[2] = L2fes.GetVSize();
146 offsets.PartialSum();
147
148 BlockVector x(offsets), rhs(offsets);
149 x = 0.0; rhs = 0.0;
150
151 // 5. Determine the list of true (i.e. conforming) essential boundary dofs.
152 Array<int> ess_bdr;
153 if (mesh.bdr_attributes.Size())
154 {
155 ess_bdr.SetSize(mesh.bdr_attributes.Max());
156 ess_bdr = 1;
157 }
158
159 // 6. Define an initial guess for the solution.
160 auto IC_func = [](const Vector &x)
161 {
162 real_t r0 = 1.0;
163 real_t rr = 0.0;
164 for (int i=0; i<x.Size(); i++)
165 {
166 rr += x(i)*x(i);
167 }
168 return r0*r0 - rr;
169 };
170 ConstantCoefficient one(1.0);
171 ConstantCoefficient zero(0.0);
172
173 // 7. Define the solution vectors as a finite element grid functions
174 // corresponding to the fespaces.
175 GridFunction u_gf, delta_psi_gf;
176
177 u_gf.MakeRef(&H1fes,x,offsets[0]);
178 delta_psi_gf.MakeRef(&L2fes,x,offsets[1]);
179 delta_psi_gf = 0.0;
180
181 GridFunction u_old_gf(&H1fes);
182 GridFunction psi_old_gf(&L2fes);
183 GridFunction psi_gf(&L2fes);
184 u_old_gf = 0.0;
185 psi_old_gf = 0.0;
186
187 // 8. Define the function coefficients for the solution and use them to
188 // initialize the initial guess
191 FunctionCoefficient IC_coef(IC_func);
194 u_gf.ProjectCoefficient(IC_coef);
195 u_old_gf = u_gf;
196
197 // 9. Initialize the slack variable ψₕ = ln(uₕ)
198 LogarithmGridFunctionCoefficient ln_u(u_gf, obstacle);
199 psi_gf.ProjectCoefficient(ln_u);
200 psi_old_gf = psi_gf;
201
202 char vishost[] = "localhost";
203 int visport = 19916;
204 socketstream sol_sock;
205 if (visualization)
206 {
207 sol_sock.open(vishost,visport);
208 sol_sock.precision(8);
209 }
210
211 // 10. Iterate
212 int k;
213 int total_iterations = 0;
214 real_t increment_u = 0.1;
215 for (k = 0; k < max_it; k++)
216 {
217 GridFunction u_tmp(&H1fes);
218 u_tmp = u_old_gf;
219
220 mfem::out << "\nOUTER ITERATION " << k+1 << endl;
221
222 int j;
223 for ( j = 0; j < 10; j++)
224 {
225 total_iterations++;
226
227 ConstantCoefficient alpha_cf(alpha);
228
229 LinearForm b0,b1;
230 b0.Update(&H1fes,rhs.GetBlock(0),0);
231 b1.Update(&L2fes,rhs.GetBlock(1),0);
232
233 ExponentialGridFunctionCoefficient exp_psi(psi_gf, zero);
234 ProductCoefficient neg_exp_psi(-1.0,exp_psi);
235 GradientGridFunctionCoefficient grad_u_old(&u_old_gf);
236 ProductCoefficient alpha_f(alpha, f);
237 GridFunctionCoefficient psi_cf(&psi_gf);
238 GridFunctionCoefficient psi_old_cf(&psi_old_gf);
239 SumCoefficient psi_old_minus_psi(psi_old_cf, psi_cf, 1.0, -1.0);
240
242 b0.AddDomainIntegrator(new DomainLFIntegrator(psi_old_minus_psi));
243 b0.Assemble();
244
246 b1.AddDomainIntegrator(new DomainLFIntegrator(obstacle));
247 b1.Assemble();
248
249 BilinearForm a00(&H1fes);
251 a00.AddDomainIntegrator(new DiffusionIntegrator(alpha_cf));
252 a00.Assemble();
253 a00.EliminateEssentialBC(ess_bdr,x.GetBlock(0),rhs.GetBlock(0),
255 a00.Finalize();
256 SparseMatrix &A00 = a00.SpMat();
257
258 MixedBilinearForm a10(&H1fes,&L2fes);
260 a10.Assemble();
261 a10.EliminateTrialDofs(ess_bdr, x.GetBlock(0), rhs.GetBlock(1));
262 a10.Finalize();
263 SparseMatrix &A10 = a10.SpMat();
264
265 SparseMatrix *A01 = Transpose(A10);
266
267 BilinearForm a11(&L2fes);
268 a11.AddDomainIntegrator(new MassIntegrator(neg_exp_psi));
269 // NOTE: Shift the spectrum of the Hessian matrix for additional
270 // stability (Quasi-Newton).
271 ConstantCoefficient eps_cf(-1e-6);
272 if (order == 1)
273 {
274 // NOTE: ∇ₕuₕ = 0 for constant functions.
275 // Therefore, we use the mass matrix to shift the spectrum
276 a11.AddDomainIntegrator(new MassIntegrator(eps_cf));
277 }
278 else
279 {
281 }
282 a11.Assemble();
283 a11.Finalize();
284 SparseMatrix &A11 = a11.SpMat();
285
286 BlockOperator A(offsets);
287 A.SetBlock(0,0,&A00);
288 A.SetBlock(1,0,&A10);
289 A.SetBlock(0,1,A01);
290 A.SetBlock(1,1,&A11);
291
292 BlockDiagonalPreconditioner prec(offsets);
293 prec.SetDiagonalBlock(0,new GSSmoother(A00));
294 prec.SetDiagonalBlock(1,new GSSmoother(A11));
295 prec.owns_blocks = 1;
296
297 GMRES(A,prec,rhs,x,0,10000,500,1e-12,0.0);
298
299 u_gf.MakeRef(&H1fes, x.GetBlock(0), 0);
300 delta_psi_gf.MakeRef(&L2fes, x.GetBlock(1), 0);
301
302 u_tmp -= u_gf;
303 real_t Newton_update_size = u_tmp.ComputeL2Error(zero);
304 u_tmp = u_gf;
305
306 real_t gamma = 1.0;
307 delta_psi_gf *= gamma;
308 psi_gf += delta_psi_gf;
309
310 if (visualization)
311 {
312 sol_sock << "solution\n" << mesh << u_gf << "window_title 'Discrete solution'"
313 << flush;
314 mfem::out << "Newton_update_size = " << Newton_update_size << endl;
315 }
316
317 delete A01;
318
319 if (Newton_update_size < increment_u)
320 {
321 break;
322 }
323 }
324
325 u_tmp = u_gf;
326 u_tmp -= u_old_gf;
327 increment_u = u_tmp.ComputeL2Error(zero);
328
329 mfem::out << "Number of Newton iterations = " << j+1 << endl;
330 mfem::out << "Increment (|| uₕ - uₕ_prvs||) = " << increment_u << endl;
331
332 u_old_gf = u_gf;
333 psi_old_gf = psi_gf;
334
335 if (increment_u < tol || k == max_it-1)
336 {
337 break;
338 }
339
340 real_t H1_error = u_gf.ComputeH1Error(&exact_coef,&exact_grad_coef);
341 mfem::out << "H1-error (|| u - uₕᵏ||) = " << H1_error << endl;
342
343 }
344
345 mfem::out << "\n Outer iterations: " << k+1
346 << "\n Total iterations: " << total_iterations
347 << "\n Total dofs: " << H1fes.GetTrueVSize() + L2fes.GetTrueVSize()
348 << endl;
349
350 // 11. Exact solution.
351 if (visualization)
352 {
353 socketstream err_sock(vishost, visport);
354 err_sock.precision(8);
355
356 GridFunction error_gf(&H1fes);
357 error_gf.ProjectCoefficient(exact_coef);
358 error_gf -= u_gf;
359
360 err_sock << "solution\n" << mesh << error_gf << "window_title 'Error'" <<
361 flush;
362 }
363
364 {
365 real_t L2_error = u_gf.ComputeL2Error(exact_coef);
366 real_t H1_error = u_gf.ComputeH1Error(&exact_coef,&exact_grad_coef);
367
368 ExponentialGridFunctionCoefficient u_alt_cf(psi_gf,obstacle);
369 GridFunction u_alt_gf(&L2fes);
370 u_alt_gf.ProjectCoefficient(u_alt_cf);
371 real_t L2_error_alt = u_alt_gf.ComputeL2Error(exact_coef);
372
373 mfem::out << "\n Final L2-error (|| u - uₕ||) = " << L2_error <<
374 endl;
375 mfem::out << " Final H1-error (|| u - uₕ||) = " << H1_error << endl;
376 mfem::out << " Final L2-error (|| u - ϕ - exp(ψₕ)||) = " << L2_error_alt <<
377 endl;
378 }
379
380 return 0;
381}
382
384 const IntegrationPoint &ip)
385{
386 MFEM_ASSERT(u != NULL, "grid function is not set");
387
388 real_t val = u->GetValue(T, ip) - obstacle->Eval(T, ip);
389 return max(min_val, log(val));
390}
391
393 const IntegrationPoint &ip)
394{
395 MFEM_ASSERT(u != NULL, "grid function is not set");
396
397 real_t val = u->GetValue(T, ip);
398 return min(max_val, max(min_val, exp(val) + obstacle->Eval(T, ip)));
399}
400
402{
403 real_t x = pt(0), y = pt(1);
404 real_t r = sqrt(x*x + y*y);
405 real_t r0 = 0.5;
406 real_t beta = 0.9;
407
408 real_t b = r0*beta;
409 real_t tmp = sqrt(r0*r0 - b*b);
410 real_t B = tmp + b*b/tmp;
411 real_t C = -b/tmp;
412
413 if (r > b)
414 {
415 return B + r * C;
416 }
417 else
418 {
419 return sqrt(r0*r0 - r*r);
420 }
421}
422
424{
425 real_t x = pt(0), y = pt(1);
426 real_t r = sqrt(x*x + y*y);
427 real_t r0 = 0.5;
428 real_t a = 0.348982574111686;
429 real_t A = -0.340129705945858;
430
431 if (r > a)
432 {
433 return A * log(r);
434 }
435 else
436 {
437 return sqrt(r0*r0-r*r);
438 }
439}
440
442{
443 real_t x = pt(0), y = pt(1);
444 real_t r = sqrt(x*x + y*y);
445 real_t r0 = 0.5;
446 real_t a = 0.348982574111686;
447 real_t A = -0.340129705945858;
448
449 if (r > a)
450 {
451 grad(0) = A * x / (r*r);
452 grad(1) = A * y / (r*r);
453 }
454 else
455 {
456 grad(0) = - x / sqrt( r0*r0 - r*r );
457 grad(1) = - y / sqrt( r0*r0 - r*r );
458 }
459}
T Max() const
Find the maximal element in the array, using the comparison operator < for class T.
Definition: array.cpp:68
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition: array.hpp:697
int Size() const
Return the logical size of the array.
Definition: array.hpp:144
void PartialSum()
Fill the entries of the array with the cumulative sum of the entries.
Definition: array.cpp:103
A "square matrix" operator for the associated FE space and BLFIntegrators The sum of all the BLFInteg...
virtual void Finalize(int skip_zeros=1)
Finalizes the matrix initialization if the AssemblyLevel is AssemblyLevel::LEGACY....
void SetDiagonalPolicy(DiagonalPolicy policy)
Sets Operator::DiagonalPolicy used upon construction of the linear system. Policies include:
void AddDomainIntegrator(BilinearFormIntegrator *bfi)
Adds new Domain Integrator. Assumes ownership of bfi.
void Assemble(int skip_zeros=1)
Assembles the form i.e. sums over all domain/bdr integrators.
void EliminateEssentialBC(const Array< int > &bdr_attr_is_ess, const Vector &sol, Vector &rhs, DiagonalPolicy dpolicy=DIAG_ONE)
Eliminate essential boundary DOFs from the system.
const SparseMatrix & SpMat() const
Returns a const reference to the sparse matrix: .
A class to handle Block diagonal preconditioners in a matrix-free implementation.
void SetDiagonalBlock(int iblock, Operator *op)
Add a square block op in the block-entry (iblock, iblock).
A class to handle Block systems in a matrix-free implementation.
void SetBlock(int iRow, int iCol, Operator *op, real_t c=1.0)
Add a block op in the block-entry (iblock, jblock).
A class to handle Vectors in a block fashion.
Definition: blockvector.hpp:31
Vector & GetBlock(int i)
Get the i-th vector in the block.
Definition: blockvector.hpp:93
Base class Coefficients that optionally depend on space and time. These are used by the BilinearFormI...
Definition: coefficient.hpp:42
real_t Eval(ElementTransformation &T, const IntegrationPoint &ip, real_t t)
Evaluate the coefficient in the element described by T at the point ip at time t.
Definition: coefficient.hpp:68
virtual real_t Eval(ElementTransformation &T, const IntegrationPoint &ip)=0
Evaluate the coefficient in the element described by T at the point ip.
A coefficient that is constant across space and time.
Definition: coefficient.hpp:85
Class for domain integration .
Definition: lininteg.hpp:109
Class FiniteElementSpace - responsible for providing FEM view of the mesh, mainly managing the set of...
Definition: fespace.hpp:220
virtual int GetTrueVSize() const
Return the number of vector true (conforming) dofs.
Definition: fespace.hpp:716
int GetVSize() const
Return the number of vector dofs, i.e. GetNDofs() x GetVDim().
Definition: fespace.hpp:713
A general function coefficient.
Data type for Gauss-Seidel smoother of sparse matrix.
Vector coefficient defined as the Gradient of a scalar GridFunction.
Coefficient defined by a GridFunction. This coefficient is mesh dependent.
Class for grid function - Vector with associated FE space.
Definition: gridfunc.hpp:31
virtual real_t ComputeH1Error(Coefficient *exsol, VectorCoefficient *exgrad, Coefficient *ell_coef, real_t Nu, int norm_type) const
Definition: gridfunc.cpp:3092
virtual void MakeRef(FiniteElementSpace *f, real_t *v)
Make the GridFunction reference external data on a new FiniteElementSpace.
Definition: gridfunc.cpp:203
virtual real_t ComputeL2Error(Coefficient *exsol[], const IntegrationRule *irs[]=NULL, const Array< int > *elems=NULL) const
Definition: gridfunc.cpp:2718
virtual void ProjectCoefficient(Coefficient &coeff)
Project coeff Coefficient to this GridFunction. The projection computation depends on the choice of t...
Definition: gridfunc.cpp:2346
Arbitrary order H1-conforming (continuous) finite elements.
Definition: fe_coll.hpp:260
Class for integration point with weight.
Definition: intrules.hpp:35
Arbitrary order "L2-conforming" discontinuous finite elements.
Definition: fe_coll.hpp:330
Vector with associated FE space and LinearFormIntegrators.
Definition: linearform.hpp:25
void AddDomainIntegrator(LinearFormIntegrator *lfi)
Adds new Domain Integrator. Assumes ownership of lfi.
Definition: linearform.cpp:45
void Update()
Update the object according to the associated FE space fes.
Definition: linearform.cpp:348
void Assemble()
Assembles the linear form i.e. sums over all domain/bdr integrators.
Definition: linearform.cpp:172
Mesh data type.
Definition: mesh.hpp:56
Array< int > bdr_attributes
A list of all unique boundary attributes used by the Mesh.
Definition: mesh.hpp:282
int Dimension() const
Dimension of the reference space used within the elements.
Definition: mesh.hpp:1160
void GetNodes(Vector &node_coord) const
Definition: mesh.cpp:8973
virtual void SetCurvature(int order, bool discont=false, int space_dim=-1, int ordering=1)
Set the curvature of the mesh nodes using the given polynomial degree.
Definition: mesh.cpp:6211
void UniformRefinement(int i, const DSTable &, int *, int *, int *)
Definition: mesh.cpp:10970
virtual void Finalize(int skip_zeros=1)
Finalizes the matrix initialization if the AssemblyLevel is AssemblyLevel::LEGACY.
void Assemble(int skip_zeros=1)
void EliminateTrialDofs(const Array< int > &bdr_attr_is_ess, const Vector &sol, Vector &rhs)
Eliminate essential boundary DOFs from the columns of the system.
const SparseMatrix & SpMat() const
Returns a const reference to the sparse matrix: .
void AddDomainIntegrator(BilinearFormIntegrator *bfi)
Adds a domain integrator. Assumes ownership of bfi.
@ DIAG_ONE
Set the diagonal value to one.
Definition: operator.hpp:50
void Parse()
Parse the command-line options. Note that this function expects all the options provided through the ...
Definition: optparser.cpp:151
void PrintUsage(std::ostream &out) const
Print the usage message.
Definition: optparser.cpp:462
void PrintOptions(std::ostream &out) const
Print the options.
Definition: optparser.cpp:331
void AddOption(bool *var, const char *enable_short_name, const char *enable_long_name, const char *disable_short_name, const char *disable_long_name, const char *description, bool required=false)
Add a boolean option and set 'var' to receive the value. Enable/disable tags are used to set the bool...
Definition: optparser.hpp:82
bool Good() const
Return true if the command line options were parsed successfully.
Definition: optparser.hpp:159
Scalar coefficient defined as the product of two scalar coefficients or a scalar and a scalar coeffic...
Data type sparse matrix.
Definition: sparsemat.hpp:51
Scalar coefficient defined as the linear combination of two scalar coefficients or a scalar and a sca...
A general vector function coefficient.
Vector data type.
Definition: vector.hpp:80
int open(const char hostname[], int port)
Open the socket stream on 'port' at 'hostname'.
Vector beta
const real_t alpha
Definition: ex15.cpp:369
int dim
Definition: ex24.cpp:53
void exact_solution_gradient_obstacle(const Vector &pt, Vector &grad)
Definition: ex36.cpp:441
real_t spherical_obstacle(const Vector &pt)
Definition: ex36.cpp:401
real_t exact_solution_obstacle(const Vector &pt)
Definition: ex36.cpp:423
int visport
char vishost[]
int main()
real_t b
Definition: lissajous.cpp:42
real_t a
Definition: lissajous.cpp:41
real_t f(const Vector &p)
real_t u(const Vector &xvec)
Definition: lor_mms.hpp:22
OutStream out(std::cout)
Global stream used by the library for standard output. Initially it uses the same std::streambuf as s...
Definition: globals.hpp:66
void Transpose(const Table &A, Table &At, int ncols_A_)
Transpose a Table.
Definition: table.cpp:414
int GMRES(const Operator &A, Vector &x, const Vector &b, Solver &M, int &max_iter, int m, real_t &tol, real_t atol, int printit)
GMRES method. (tolerances are squared)
Definition: solvers.cpp:1342
float real_t
Definition: config.hpp:43