MFEM v4.8.0
Finite element discretization library
Loading...
Searching...
No Matches
nurbs_solenoidal.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11//
12// -----------------------------------------------------
13// NURBS Solenoidal Miniapp: Project solenoidal velocity
14// -----------------------------------------------------
15//
16// Compile with: make nurbs_solenoidal
17//
18// Sample runs: nurbs_solenoidal -m ../../data/square-nurbs.mesh -o 2
19// nurbs_solenoidal -m ../../data/cube-nurbs.mesh -o 2
20//
21// Description: This code projects a velocity field, and forces this field
22// to be solenoidal, viz. the divergence is zero. If the correct
23// discrete spaces are chosen the divergence is pointwise zero.
24//
25// This is achieved by solving a simple 2D/3D mixed Darcy problem
26// corresponding to the saddle point system (similar to ex5)
27//
28// u + grad p = u_ex
29// - div u = 0
30//
31// NURBS-based H(div) spaces only implemented for meshes
32// consisting of a single patch.
33//
34// Here, u_ex is the specified velocity field. If u_ex is
35// divergence free, we expect the pressure to converge to zero.
36// We discretize with H(div) and L2/H1 conforming elements.
37
38#include "mfem.hpp"
39#include <fstream>
40#include <iostream>
41#include <algorithm>
42
43using namespace std;
44using namespace mfem;
45
46void u_2d(const Vector & x, Vector & u)
47{
48 real_t xi(x(0));
49 real_t yi(x(1));
50
51 int p = 4;
52
53 u(0) = pow(xi,p + 1)*pow(yi,p );
54 u(1) = -pow(xi,p )*pow(yi,p + 1);
55}
56
57void u_3d(const Vector & x, Vector & u)
58{
59 real_t xi(x(0));
60 real_t yi(x(1));
61 real_t zi(x(2));
62
63 int p = 4;
64
65 real_t cx = 3.0/4.0;
66 real_t cy = 2.0/3.0;
67 real_t cz = -cx - cy;
68
69 u(0) = cx*pow(xi,p + 1)*pow(yi,p )*pow(zi,p );
70 u(1) = cy*pow(xi,p )*pow(yi,p + 1)*pow(zi,p );
71 u(2) = cz*pow(xi,p )*pow(yi,p )*pow(zi,p + 1);
72}
73
74// Define the analytical solution and forcing terms / boundary conditions
75void u_ex(const Vector & x, Vector & u)
76{
77 if (x.Size() == 2)
78 {
79 u_2d(x, u);
80 }
81 else if (x.Size() == 3)
82 {
83 u_3d(x, u);
84 }
85}
86
87int main(int argc, char *argv[])
88{
89 StopWatch chrono;
90
91 // 1. Parse command-line options.
92 const char *mesh_file = "../../data/square-nurbs.mesh";
93 int ref_levels = -1;
94 int order = 1;
95 const char *device_config = "cpu";
96 bool visualization = 1;
97 bool NURBS = true;
98 int visport = 19916;
99 bool div_free = true;
100
101 OptionsParser args(argc, argv);
102 args.AddOption(&mesh_file, "-m", "--mesh",
103 "Mesh file to use.");
104 args.AddOption(&ref_levels, "-r", "--refine",
105 "Number of times to refine the mesh uniformly, -1 for auto.");
106 args.AddOption(&order, "-o", "--order",
107 "Finite element order (polynomial degree).");
108 args.AddOption(&div_free, "-df", "--div-free", "-p","--proj",
109 "Div-free or standard projection.");
110 args.AddOption(&NURBS, "-n", "--nurbs", "-nn","--no-nurbs",
111 "NURBS.");
112 args.AddOption(&device_config, "-d", "--device",
113 "Device configuration string, see Device::Configure().");
114 args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
115 "--no-visualization",
116 "Enable or disable GLVis visualization.");
117 args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
118 args.Parse();
119 if (!args.Good())
120 {
121 args.PrintUsage(mfem::out);
122 return 1;
123 }
125
126 // 2. Enable hardware devices such as GPUs, and programming models such as
127 // CUDA, OCCA, RAJA and OpenMP based on command line options.
128 Device device(device_config);
129 device.Print();
130
131 // 3. Read the mesh from the given mesh file. We can handle triangular,
132 // quadrilateral, tetrahedral, hexahedral, surface and volume meshes with
133 // the same code.
134 Mesh *mesh = new Mesh(mesh_file, 1, 1);
135 int dim = mesh->Dimension();
136
137 // 4. Refine the mesh to increase the resolution. In this example we do
138 // 'ref_levels' of uniform refinement. We choose 'ref_levels' to be the
139 // largest number that gives a final mesh with no more than 10,000
140 // elements.
141 {
142 if (ref_levels < 0)
143 {
144 ref_levels =
145 (int)floor(log(5000./mesh->GetNE())/log(2.)/dim);
146 }
147 for (int l = 0; l < ref_levels; l++)
148 {
149 mesh->UniformRefinement();
150 }
151 }
152
153 // 5. Define a finite element space on the mesh. Here we use the
154 // Raviart-Thomas finite elements of the specified order.
155 FiniteElementCollection *hdiv_coll = nullptr;
156 FiniteElementCollection *l2_coll = nullptr;
157 NURBSExtension *NURBSext = nullptr;
158
159 if (mesh->NURBSext&& NURBS)
160 {
161 hdiv_coll = new NURBS_HDivFECollection(order, dim);
162 l2_coll = new NURBSFECollection(order);
163 NURBSext = new NURBSExtension(mesh->NURBSext, order);
164 mfem::out<<"Create NURBS fec and ext"<<std::endl;
165 }
166 else
167 {
168 NURBS = false;
169 hdiv_coll = new RT_FECollection(order, dim);
170 l2_coll = new L2_FECollection(order, dim);
171 mfem::out<<"Create Normal fec"<<std::endl;
172 }
173
174 FiniteElementSpace *W_space = new FiniteElementSpace(mesh, NURBSext, l2_coll);
175 FiniteElementSpace *R_space = new FiniteElementSpace(mesh,
176 W_space->StealNURBSext(),
177 hdiv_coll);
178
179 // 6. Define the BlockStructure of the problem, i.e. define the array of
180 // offsets for each variable. The last component of the Array is the sum
181 // of the dimensions of each block.
182 Array<int> block_offsets(3); // number of variables + 1
183 block_offsets[0] = 0;
184 block_offsets[1] = R_space->GetVSize();
185 block_offsets[2] = W_space->GetVSize();
186 block_offsets.PartialSum();
187
188 mfem::out << "***********************************************************\n";
189 mfem::out << "dim(R) = " << block_offsets[1] - block_offsets[0] << "\n";
190 mfem::out << "dim(W) = " << block_offsets[2] - block_offsets[1] << "\n";
191 mfem::out << "dim(R+W) = " << block_offsets.Last() << "\n";
192 mfem::out << "***********************************************************\n";
193
194 // 7. Define the coefficients, analytical solution, and rhs of the PDE.
195 ConstantCoefficient one(1.0);
196 ConstantCoefficient zero(0.0);
198
199 // 8. Allocate memory (x, rhs) for the analytical solution and the right hand
200 // side. Define the GridFunction u,p for the finite element solution and
201 // linear forms fform and gform for the right hand side. The data
202 // allocated by x and rhs are passed as a reference to the grid functions
203 // (u,p) and the linear forms (fform, gform).
204 MemoryType mt = device.GetMemoryType();
205 BlockVector x(block_offsets, mt), rhs(block_offsets, mt);
206 rhs = 0.0;
207
208 LinearForm *fform(new LinearForm);
209 fform->Update(R_space, rhs.GetBlock(0), 0);
211 fform->Assemble();
212 fform->SyncAliasMemory(rhs);
213
214 // 9. Assemble the finite element matrices for the Darcy operator
215 //
216 // D = [ M B^T ]
217 // [ B 0 ]
218 // where:
219 //
220 // M = \int_\Omega k u_h \cdot v_h d\Omega u_h, v_h \in R_h
221 // B = -\int_\Omega \div u_h q_h d\Omega u_h \in R_h, q_h \in W_h
222 BilinearForm *mVarf(new BilinearForm(R_space));
223 MixedBilinearForm *bVarf(new MixedBilinearForm(R_space, W_space));
224
226 mVarf->Assemble();
227 mVarf->Finalize();
228
230 bVarf->Assemble();
231 bVarf->Finalize();
232
233 SparseMatrix &M(mVarf->SpMat());
234 SparseMatrix &B(bVarf->SpMat());
235 B *= -1.;
237
238 BlockOperator darcyOp(block_offsets);
239 darcyOp.SetBlock(0,0, &M);
240 if (div_free) { darcyOp.SetBlock(0,1, Bt); }
241 if (div_free) { darcyOp.SetBlock(1,0, &B); }
242
243 // 10. Construct the operators for preconditioner
244 //
245 // P = [ diag(M) 0 ]
246 // [ 0 B diag(M)^-1 B^T ]
247 //
248 // Here we use Symmetric Gauss-Seidel to approximate the inverse of the
249 // pressure Schur Complement
250 Vector Md(mVarf->Height());
251 M.GetDiag(Md);
252 Md.HostReadWrite();
253 SparseMatrix *MinvBt = Transpose(B);
254 for (int i = 0; i < Md.Size(); i++)
255 {
256 MinvBt->ScaleRow(i, 1./Md(i));
257 }
258 SparseMatrix *S = Mult(B, *MinvBt);
259 Solver *invS;
260#ifndef MFEM_USE_SUITESPARSE
261 invS = new GSSmoother(*S);
262#else
263 invS = new UMFPackSolver(*S);
264#endif
265 invS->iterative_mode = false;
266
267 Solver *invM = new GSSmoother(M);
268 invM->iterative_mode = false;
269
270 BlockDiagonalPreconditioner darcyPrec(block_offsets);
271 darcyPrec.SetDiagonalBlock(0, invM);
272 darcyPrec.SetDiagonalBlock(1, invS);
273
274 // 11. Solve the linear system with MINRES.
275 // Check the norm of the unpreconditioned residual.
276 int maxIter(10000);
277 real_t rtol(10*std::numeric_limits<real_t>::epsilon());
278 real_t atol(10*std::numeric_limits<real_t>::epsilon());
279
280 chrono.Clear();
281 chrono.Start();
282 MINRESSolver solver;
283 solver.SetAbsTol(atol);
284 solver.SetRelTol(rtol);
285 solver.SetMaxIter(maxIter);
286 solver.SetOperator(darcyOp);
287 solver.SetPreconditioner(darcyPrec);
288 solver.SetPrintLevel(2);
289 x = 0.0;
290 solver.Mult(rhs, x);
291 if (device.IsEnabled()) { x.HostRead(); }
292 chrono.Stop();
293
294 if (solver.GetConverged())
295 {
296 mfem::out << "MINRES converged in " << solver.GetNumIterations()
297 << " iterations with a residual norm of "
298 << solver.GetFinalNorm() << ".\n";
299 }
300 else
301 {
302 mfem::out << "MINRES did not converge in " << solver.GetNumIterations()
303 << " iterations. Residual norm is " << solver.GetFinalNorm()
304 << ".\n";
305 }
306 mfem::out << "MINRES solver took " << chrono.RealTime() << "s.\n";
307
308 // 12. Create the grid functions u and p
309 GridFunction u, p, uu, vv, ww;
310 u.MakeRef(R_space, x.GetBlock(0), 0);
311 p.MakeRef(W_space, x.GetBlock(1), 0);
312
313 // 13. Save the mesh and the solution. This output can be viewed later using
314 // GLVis: "glvis -m exsol.mesh -g sol_u.gf" or "glvis -m exsol.mesh -g
315 // sol_p.gf".
316 {
317 ofstream mesh_ofs("exsol.mesh");
318 mesh_ofs.precision(8);
319 mesh->Print(mesh_ofs);
320
321 ofstream u_ofs("sol_u.gf");
322 u_ofs.precision(8);
323 u.Save(u_ofs);
324
325 ofstream p_ofs("sol_p.gf");
326 p_ofs.precision(8);
327 p.Save(p_ofs);
328 }
329
330 // 14. Save data in the VisIt format
331 VisItDataCollection visit_dc("Solenoidal", mesh);
332 visit_dc.RegisterField("velocity", &u);
333 visit_dc.RegisterField("pressure", &p);
334 visit_dc.Save();
335
336 // 15. Save data in the ParaView format
337 ParaViewDataCollection paraview_dc("Solenoidal", mesh);
338 paraview_dc.SetPrefixPath("ParaView");
339 paraview_dc.SetLevelsOfDetail(order);
340 paraview_dc.SetCycle(0);
341 paraview_dc.SetDataFormat(VTKFormat::BINARY);
342 paraview_dc.SetHighOrderOutput(true);
343 paraview_dc.SetTime(0.0); // set the time
344 paraview_dc.RegisterField("velocity",&u);
345 paraview_dc.RegisterField("pressure",&p);
346 paraview_dc.Save();
347
348 // 16. Send the solution by socket to a GLVis server.
349 if (visualization)
350 {
351 char vishost[] = "localhost";
352 socketstream u_sock(vishost, visport);
353 u_sock.precision(8);
354 u_sock << "solution\n" << *mesh << u << "window_title 'Velocity'" << endl;
355 socketstream p_sock(vishost, visport);
356 p_sock.precision(8);
357 p_sock << "solution\n" << *mesh << p << "window_title 'Pressure'" << endl;
358 }
359
360 // 17. Compute errors
361 int order_quad = 2*order+2;
363 for (int i=0; i < Geometry::NumGeom; ++i)
364 {
365 irs[i] = &(IntRules.Get(i, order_quad));
366 }
367
368 real_t err_u = u.ComputeL2Error(ucoeff, irs);
369 real_t err_p = p.ComputeL2Error(zero, irs);
370 real_t err_div = u.ComputeDivError(&zero, irs);
371
372 mfem::out << "|| u_h - u_ex || = " << err_u << "\n";
373 mfem::out << "|| div u_h - div u_ex || = " << err_div << "\n";
374 mfem::out << "|| p_h - p_ex || = " << err_p << "\n";
375
376 // 18. Free the used memory.
377 delete fform;
378 delete invM;
379 delete invS;
380 delete S;
381 delete Bt;
382 delete MinvBt;
383 delete mVarf;
384 delete bVarf;
385 delete W_space;
386 delete R_space;
387 delete l2_coll;
388 delete hdiv_coll;
389 delete mesh;
390
391 if (err_div > 1e4*std::numeric_limits<real_t>::epsilon() )
392 {
393 mfem::out << "std::numeric_limits<real_t>::epsilon() = "
394 << std::numeric_limits<real_t>::epsilon() << "\n";
395 mfem_error("Divergence error larger than expected");
396 }
397
398 return 0;
399}
void PartialSum()
Fill the entries of the array with the cumulative sum of the entries.
Definition array.cpp:103
T & Last()
Return the last element in the array.
Definition array.hpp:863
A "square matrix" operator for the associated FE space and BLFIntegrators The sum of all the BLFInteg...
void AddDomainIntegrator(BilinearFormIntegrator *bfi)
Adds new Domain Integrator. Assumes ownership of bfi.
void Finalize(int skip_zeros=1) override
Finalizes the matrix initialization if the AssemblyLevel is AssemblyLevel::LEGACY....
void Assemble(int skip_zeros=1)
Assembles the form i.e. sums over all domain/bdr integrators.
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.
Vector & GetBlock(int i)
Get the i-th vector in the block.
A coefficient that is constant across space and time.
virtual void RegisterField(const std::string &field_name, GridFunction *gf)
Add a grid function to the collection.
void SetCycle(int c)
Set time cycle (for time-dependent simulations)
void SetTime(real_t t)
Set physical time (for time-dependent simulations)
void SetPrefixPath(const std::string &prefix)
Set the path where the DataCollection will be saved.
The MFEM Device class abstracts hardware devices such as GPUs, as well as programming models such as ...
Definition device.hpp:123
static MemoryType GetMemoryType()
(DEPRECATED) Equivalent to GetDeviceMemoryType().
Definition device.hpp:278
void Print(std::ostream &out=mfem::out)
Print the configuration of the MFEM virtual device object.
Definition device.cpp:297
static bool IsEnabled()
Return true if any backend other than Backend::CPU is enabled.
Definition device.hpp:247
Collection of finite elements from the same family in multiple dimensions. This class is used to matc...
Definition fe_coll.hpp:27
Class FiniteElementSpace - responsible for providing FEM view of the mesh, mainly managing the set of...
Definition fespace.hpp:244
NURBSExtension * StealNURBSext()
Definition fespace.cpp:2611
int GetVSize() const
Return the number of vector dofs, i.e. GetNDofs() x GetVDim().
Definition fespace.hpp:848
Data type for Gauss-Seidel smoother of sparse matrix.
static const int NumGeom
Definition geom.hpp:46
Class for grid function - Vector with associated FE space.
Definition gridfunc.hpp:31
virtual void MakeRef(FiniteElementSpace *f, real_t *v)
Make the GridFunction reference external data on a new FiniteElementSpace.
Definition gridfunc.cpp:233
Class for an integration rule - an Array of IntegrationPoint.
Definition intrules.hpp:100
const IntegrationRule & Get(int GeomType, int Order)
Returns an integration rule for given GeomType and Order.
real_t GetFinalNorm() const
Returns the final residual norm after termination of the solver during the last call to Mult().
Definition solvers.hpp:295
void SetRelTol(real_t rtol)
Definition solvers.hpp:229
int GetNumIterations() const
Returns the number of iterations taken during the last call to Mult()
Definition solvers.hpp:280
virtual void SetPrintLevel(int print_lvl)
Legacy method to set the level of verbosity of the solver output.
Definition solvers.cpp:72
void SetMaxIter(int max_it)
Definition solvers.hpp:231
bool GetConverged() const
Returns true if the last call to Mult() converged successfully.
Definition solvers.hpp:282
void SetAbsTol(real_t atol)
Definition solvers.hpp:230
Arbitrary order "L2-conforming" discontinuous finite elements.
Definition fe_coll.hpp:346
Vector with associated FE space and LinearFormIntegrators.
void AddDomainIntegrator(LinearFormIntegrator *lfi)
Adds new Domain Integrator. Assumes ownership of lfi.
void Update()
Update the object according to the associated FE space fes.
void Assemble()
Assembles the linear form i.e. sums over all domain/bdr integrators.
MINRES method.
Definition solvers.hpp:653
void Mult(const Vector &b, Vector &x) const override
Iterative solution of the linear system using the MINRES method.
Definition solvers.cpp:1705
void SetOperator(const Operator &op) override
Also calls SetOperator for the preconditioner if there is one.
Definition solvers.cpp:1680
void SetPreconditioner(Solver &pr) override
This should be called before SetOperator.
Definition solvers.hpp:665
Mesh data type.
Definition mesh.hpp:64
NURBSExtension * NURBSext
Optional NURBS mesh extension.
Definition mesh.hpp:298
virtual void Print(std::ostream &os=mfem::out, const std::string &comments="") const
Definition mesh.hpp:2433
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1282
int Dimension() const
Dimension of the reference space used within the elements.
Definition mesh.hpp:1216
void UniformRefinement(int i, const DSTable &, int *, int *, int *)
Definition mesh.cpp:11295
void Assemble(int skip_zeros=1)
void Finalize(int skip_zeros=1) override
Finalizes the matrix initialization if the AssemblyLevel is AssemblyLevel::LEGACY.
const SparseMatrix & SpMat() const
Returns a const reference to the sparse matrix: .
void AddDomainIntegrator(BilinearFormIntegrator *bfi)
Adds a domain integrator. Assumes ownership of bfi.
NURBSExtension generally contains multiple NURBSPatch objects spanning an entire Mesh....
Definition nurbs.hpp:449
Arbitrary order non-uniform rational B-splines (NURBS) finite elements.
Definition fe_coll.hpp:699
Arbitrary order H(div) NURBS finite elements.
Definition fe_coll.hpp:758
int Height() const
Get the height (size of output) of the Operator. Synonym with NumRows().
Definition operator.hpp:66
void Parse()
Parse the command-line options. Note that this function expects all the options provided through the ...
void PrintUsage(std::ostream &out) const
Print the usage message.
void PrintOptions(std::ostream &out) const
Print the options.
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.
Helper class for ParaView visualization data.
void SetHighOrderOutput(bool high_order_output_)
void SetLevelsOfDetail(int levels_of_detail_)
void SetDataFormat(VTKFormat fmt)
Arbitrary order H(div)-conforming Raviart-Thomas finite elements.
Definition fe_coll.hpp:403
Base class for solvers.
Definition operator.hpp:780
bool iterative_mode
If true, use the second argument of Mult() as an initial guess.
Definition operator.hpp:783
Data type sparse matrix.
Definition sparsemat.hpp:51
void GetDiag(Vector &d) const
Returns the Diagonal of A.
void ScaleRow(const int row, const real_t scale)
Timing object.
Definition tic_toc.hpp:36
double RealTime()
Return the number of real seconds elapsed since the stopwatch was started.
Definition tic_toc.cpp:432
void Start()
Start the stopwatch. The elapsed time is not cleared.
Definition tic_toc.cpp:411
void Stop()
Stop the stopwatch.
Definition tic_toc.cpp:422
void Clear()
Clear the elapsed time on the stopwatch and restart it if it's running.
Definition tic_toc.cpp:406
The transpose of a given operator. Switches the roles of the methods Mult() and MultTranspose().
Definition operator.hpp:847
Direct sparse solver using UMFPACK.
Definition solvers.hpp:1121
for VectorFiniteElements (Nedelec, Raviart-Thomas)
Definition lininteg.hpp:344
A general vector function coefficient.
Vector data type.
Definition vector.hpp:82
void SyncAliasMemory(const Vector &v) const
Update the alias memory location of the vector to match v.
Definition vector.hpp:267
int Size() const
Returns the size of the vector.
Definition vector.hpp:226
virtual real_t * HostReadWrite()
Shortcut for mfem::ReadWrite(vec.GetMemory(), vec.Size(), false).
Definition vector.hpp:514
Data collection with VisIt I/O routines.
void Save() override
Save the collection and a VisIt root file.
void RegisterField(const std::string &field_name, GridFunction *gf) override
Add a grid function to the collection and update the root file.
int dim
Definition ex24.cpp:53
int main()
real_t u(const Vector &xvec)
Definition lor_mms.hpp:22
void mfem_error(const char *msg)
Definition error.cpp:154
void Mult(const Table &A, const Table &B, Table &C)
C = A * B (as boolean matrices)
Definition table.cpp:548
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:486
float real_t
Definition config.hpp:43
MemoryType
Memory types supported by MFEM.
IntegrationRules IntRules(0, Quadrature1D::GaussLegendre)
A global object with all integration rules (defined in intrules.cpp)
Definition intrules.hpp:492
const char vishost[]
real_t p(const Vector &x, real_t t)
void u_2d(const Vector &x, Vector &u)
void u_3d(const Vector &x, Vector &u)
void u_ex(const Vector &x, Vector &u)