1 // Copyright (c) 2010-2021, 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 //          Block Solvers Miniapp: Compare Saddle Point System Solvers
14 //          ----------------------------------------------------------
15 //
16 // This miniapp compares various linear solvers for the saddle point system
17 // obtained from mixed finite element discretization of the simple mixed Darcy
18 // problem in ex5p
19 //
20 //                            k*u + grad p = f
21 //                           - div u      = g
22 //
23 // with natural boundary condition -p = <given pressure>. We use a given exact
24 // solution (u,p) and compute the corresponding r.h.s. (f,g). We discretize
25 // with Raviart-Thomas finite elements (velocity u) and piecewise discontinuous
26 // polynomials (pressure p).
27 //
28 // The solvers being compared include:
29 //    1. The divergence free solver (couple and decoupled modes)
30 //    2. MINRES preconditioned by a block diagonal preconditioner
31 //
32 // We recommend viewing example 5 before viewing this miniapp.
33 //
34 // Sample runs:
35 //
36 //    mpirun -np 8 block-solvers-compare -r 2 -o 0
37 //    mpirun -np 8 block-solvers-compare -m anisotropic.mesh -c anisotropic.coeff -be anisotropic.bdr
38 //
39 //
40 // NOTE:  The coefficient file (provided through -c) defines a piecewise constant
41 //        scalar coefficient k. The number of entries in this file should equal
42 //        to the number of "element attributes" in the mesh file. The value of
43 //        the coefficient in elements with the i-th attribute is given by the
44 //        i-th entry of the coefficient file.
45 //
46 //
47 // NOTE:  The essential boundary attribute file (provided through -eb) defines
48 //        which attributes to impose essential boundary condition (on u). The
49 //        number of entries in this file should equal to the number of "boundary
50 //        attributes" in the mesh file. If the i-th entry of the file is nonzero
51 //        (respectively 0), essential (respectively natural) boundary condition
52 //        will be imposed on boundary with the i-th attribute.
53 
54 #include "mfem.hpp"
55 #include "div_free_solver.hpp"
56 #include <fstream>
57 #include <iostream>
58 #include <memory>
59 
60 using namespace std;
61 using namespace mfem;
62 using namespace blocksolvers;
63 
64 // Exact solution, u and p, and r.h.s., f and g.
65 void u_exact(const Vector & x, Vector & u);
66 double p_exact(const Vector & x);
67 void f_exact(const Vector & x, Vector & f);
68 double g_exact(const Vector & x);
69 double natural_bc(const Vector & x);
70 
71 /** Wrapper for assembling the discrete Darcy problem (ex5p)
72                      [ M  B^T ] [u] = [f]
73                      [ B   0  ] [p] = [g]
74     where:
75        M = \int_\Omega (k u_h) \cdot v_h dx,
76        B = -\int_\Omega (div_h u_h) q_h dx,
77        f = \int_\Omega f_exact v_h dx + \int_D natural_bc v_h dS,
78        g = \int_\Omega g_exact q_h dx,
79        u_h, v_h \in R_h (Raviart-Thomas finite element space),
80        q_h \in W_h (piecewise discontinuous polynomials),
81        D: subset of the boundary where natural boundary condition is imposed. */
82 class DarcyProblem
83 {
84    OperatorPtr M_;
85    OperatorPtr B_;
86    Vector rhs_;
87    Vector ess_data_;
88    ParGridFunction u_;
89    ParGridFunction p_;
90    ParMesh mesh_;
91    VectorFunctionCoefficient ucoeff_;
92    FunctionCoefficient pcoeff_;
93    DFSSpaces dfs_spaces_;
94    const IntegrationRule *irs_[Geometry::NumGeom];
95 public:
96    DarcyProblem(Mesh &mesh, int num_refines, int order, const char *coef_file,
97                 Array<int> &ess_bdr, DFSParameters param);
98 
GetM()99    HypreParMatrix& GetM() { return *M_.As<HypreParMatrix>(); }
GetB()100    HypreParMatrix& GetB() { return *B_.As<HypreParMatrix>(); }
GetRHS()101    const Vector& GetRHS() { return rhs_; }
GetEssentialBC()102    const Vector& GetEssentialBC() { return ess_data_; }
GetDFSData() const103    const DFSData& GetDFSData() const { return dfs_spaces_.GetDFSData(); }
104    void ShowError(const Vector &sol, bool verbose);
105    void VisualizeSolution(const Vector &sol, string tag);
106 };
107 
DarcyProblem(Mesh & mesh,int num_refs,int order,const char * coef_file,Array<int> & ess_bdr,DFSParameters dfs_param)108 DarcyProblem::DarcyProblem(Mesh &mesh, int num_refs, int order,
109                            const char *coef_file, Array<int> &ess_bdr,
110                            DFSParameters dfs_param)
111    : mesh_(MPI_COMM_WORLD, mesh), ucoeff_(mesh.Dimension(), u_exact),
112      pcoeff_(p_exact), dfs_spaces_(order, num_refs, &mesh_, ess_bdr, dfs_param)
113 {
114    for (int l = 0; l < num_refs; l++)
115    {
116       mesh_.UniformRefinement();
117       dfs_spaces_.CollectDFSData();
118    }
119 
120    Vector coef_vector(mesh.GetNE());
121    coef_vector = 1.0;
122    if (std::strcmp(coef_file, ""))
123    {
124       ifstream coef_str(coef_file);
125       coef_vector.Load(coef_str, mesh.GetNE());
126    }
127    PWConstCoefficient mass_coeff(coef_vector);
128    VectorFunctionCoefficient fcoeff(mesh_.Dimension(), f_exact);
129    FunctionCoefficient natcoeff(natural_bc);
130    FunctionCoefficient gcoeff(g_exact);
131 
132    u_.SetSpace(dfs_spaces_.GetHdivFES());
133    p_.SetSpace(dfs_spaces_.GetL2FES());
134    p_ = 0.0;
135    u_ = 0.0;
136    u_.ProjectBdrCoefficientNormal(ucoeff_, ess_bdr);
137 
138    ParLinearForm fform(dfs_spaces_.GetHdivFES());
139    fform.AddDomainIntegrator(new VectorFEDomainLFIntegrator(fcoeff));
140    fform.AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(natcoeff));
141    fform.Assemble();
142 
143    ParLinearForm gform(dfs_spaces_.GetL2FES());
144    gform.AddDomainIntegrator(new DomainLFIntegrator(gcoeff));
145    gform.Assemble();
146 
147    ParBilinearForm mVarf(dfs_spaces_.GetHdivFES());
148    ParMixedBilinearForm bVarf(dfs_spaces_.GetHdivFES(), dfs_spaces_.GetL2FES());
149 
150    mVarf.AddDomainIntegrator(new VectorFEMassIntegrator(mass_coeff));
151    mVarf.Assemble();
152    mVarf.EliminateEssentialBC(ess_bdr, u_, fform);
153    mVarf.Finalize();
154    M_.Reset(mVarf.ParallelAssemble());
155 
156    bVarf.AddDomainIntegrator(new VectorFEDivergenceIntegrator);
157    bVarf.Assemble();
158    bVarf.SpMat() *= -1.0;
159    bVarf.EliminateTrialDofs(ess_bdr, u_, gform);
160    bVarf.Finalize();
161    B_.Reset(bVarf.ParallelAssemble());
162 
163    rhs_.SetSize(M_->NumRows() + B_->NumRows());
164    Vector rhs_block0(rhs_.GetData(), M_->NumRows());
165    Vector rhs_block1(rhs_.GetData()+M_->NumRows(), B_->NumRows());
166    fform.ParallelAssemble(rhs_block0);
167    gform.ParallelAssemble(rhs_block1);
168 
169    ess_data_.SetSize(M_->NumRows() + B_->NumRows());
170    ess_data_ = 0.0;
171    Vector ess_data_block0(ess_data_.GetData(), M_->NumRows());
172    u_.ParallelProject(ess_data_block0);
173 
174    int order_quad = max(2, 2*order+1);
175    for (int i=0; i < Geometry::NumGeom; ++i)
176    {
177       irs_[i] = &(IntRules.Get(i, order_quad));
178    }
179 }
180 
ShowError(const Vector & sol,bool verbose)181 void DarcyProblem::ShowError(const Vector& sol, bool verbose)
182 {
183    u_.Distribute(Vector(sol.GetData(), M_->NumRows()));
184    p_.Distribute(Vector(sol.GetData()+M_->NumRows(), B_->NumRows()));
185 
186    double err_u  = u_.ComputeL2Error(ucoeff_, irs_);
187    double norm_u = ComputeGlobalLpNorm(2, ucoeff_, mesh_, irs_);
188    double err_p  = p_.ComputeL2Error(pcoeff_, irs_);
189    double norm_p = ComputeGlobalLpNorm(2, pcoeff_, mesh_, irs_);
190 
191    if (!verbose) { return; }
192    cout << "|| u_h - u_ex || / || u_ex || = " << err_u / norm_u << "\n";
193    cout << "|| p_h - p_ex || / || p_ex || = " << err_p / norm_p << "\n";
194 }
195 
VisualizeSolution(const Vector & sol,string tag)196 void DarcyProblem::VisualizeSolution(const Vector& sol, string tag)
197 {
198    int num_procs, myid;
199    MPI_Comm_size(mesh_.GetComm(), &num_procs);
200    MPI_Comm_rank(mesh_.GetComm(), &myid);
201 
202    u_.Distribute(Vector(sol.GetData(), M_->NumRows()));
203    p_.Distribute(Vector(sol.GetData()+M_->NumRows(), B_->NumRows()));
204 
205    const char vishost[] = "localhost";
206    const int  visport   = 19916;
207    socketstream u_sock(vishost, visport);
208    u_sock << "parallel " << num_procs << " " << myid << "\n";
209    u_sock.precision(8);
210    u_sock << "solution\n" << mesh_ << u_ << "window_title 'Velocity ("
211           << tag << " solver)'" << endl;
212    MPI_Barrier(mesh_.GetComm());
213    socketstream p_sock(vishost, visport);
214    p_sock << "parallel " << num_procs << " " << myid << "\n";
215    p_sock.precision(8);
216    p_sock << "solution\n" << mesh_ << p_ << "window_title 'Pressure ("
217           << tag << " solver)'" << endl;
218 }
219 
IsAllNeumannBoundary(const Array<int> & ess_bdr_attr)220 bool IsAllNeumannBoundary(const Array<int>& ess_bdr_attr)
221 {
222    for (int attr : ess_bdr_attr) { if (attr == 0) { return false; } }
223    return true;
224 }
225 
main(int argc,char * argv[])226 int main(int argc, char *argv[])
227 {
228    // Initialize MPI.
229    MPI_Session mpi(argc, argv);
230 
231    StopWatch chrono;
232    auto ResetTimer = [&chrono]() { chrono.Clear(); chrono.Start(); };
233 
234    // Parse command-line options.
235    const char *mesh_file = "../../data/beam-hex.mesh";
236    const char *coef_file = "";
237    const char *ess_bdr_attr_file = "";
238    int order = 0;
239    int par_ref_levels = 2;
240    bool show_error = false;
241    bool visualization = false;
242    DFSParameters param;
243    OptionsParser args(argc, argv);
244    args.AddOption(&mesh_file, "-m", "--mesh",
245                   "Mesh file to use.");
246    args.AddOption(&order, "-o", "--order",
247                   "Finite element order (polynomial degree).");
248    args.AddOption(&par_ref_levels, "-r", "--ref",
249                   "Number of parallel refinement steps.");
250    args.AddOption(&coef_file, "-c", "--coef",
251                   "Coefficient file to use.");
252    args.AddOption(&ess_bdr_attr_file, "-eb", "--ess-bdr",
253                   "Essential boundary attribute file to use.");
254    args.AddOption(&show_error, "-se", "--show-error", "-no-se",
255                   "--no-show-error",
256                   "Show or not show approximation error.");
257    args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
258                   "--no-visualization",
259                   "Enable or disable GLVis visualization.");
260    args.Parse();
261    if (!args.Good())
262    {
263       if (mpi.Root()) { args.PrintUsage(cout); }
264       return 1;
265    }
266    if (mpi.Root()) { args.PrintOptions(cout); }
267 
268    if (mpi.Root() && par_ref_levels == 0)
269    {
270       std::cout << "WARNING: DivFree solver is equivalent to BDPMinresSolver "
271                 << "when par_ref_levels == 0.\n";
272    }
273 
274    // Initialize the mesh, boundary attributes, and solver parameters
275    Mesh *mesh = new Mesh(mesh_file, 1, 1);
276    int dim = mesh->Dimension();
277    int ser_ref_lvls = (int)ceil(log(mpi.WorldSize()/mesh->GetNE())/log(2.)/dim);
278    for (int i = 0; i < ser_ref_lvls; ++i)
279    {
280       mesh->UniformRefinement();
281    }
282 
283    Array<int> ess_bdr(mesh->bdr_attributes.Max());
284    ess_bdr = 0;
285    if (std::strcmp(ess_bdr_attr_file, ""))
286    {
287       ifstream ess_bdr_attr_str(ess_bdr_attr_file);
288       ess_bdr.Load(mesh->bdr_attributes.Max(), ess_bdr_attr_str);
289    }
290    if (IsAllNeumannBoundary(ess_bdr))
291    {
292       if (mpi.Root())
293       {
294          cout << "\nSolution is not unique when Neumann boundary condition is "
295               << "imposed on the entire boundary. \nPlease provide a different "
296               << "boundary condition.\n";
297       }
298       delete mesh;
299       return 0;
300    }
301 
302    string line = "**********************************************************\n";
303 
304    ResetTimer();
305 
306    // Generate components of the saddle point problem
307    DarcyProblem darcy(*mesh, par_ref_levels, order, coef_file, ess_bdr, param);
308    HypreParMatrix& M = darcy.GetM();
309    HypreParMatrix& B = darcy.GetB();
310    const DFSData& DFS_data = darcy.GetDFSData();
311    delete mesh;
312 
313    if (mpi.Root())
314    {
315       cout << line << "System assembled in " << chrono.RealTime() << "s.\n";
316       cout << "Dimension of the physical space: " << dim << "\n";
317       cout << "Size of the discrete Darcy system: " << M.M() + B.M() << "\n";
318       if (par_ref_levels > 0)
319       {
320          cout << "Dimension of the divergence free subspace: "
321               << DFS_data.C.back().Ptr()->NumCols() << "\n\n";
322       }
323    }
324 
325    // Setup various solvers for the discrete problem
326    std::map<const DarcySolver*, double> setup_time;
327    ResetTimer();
328    BDPMinresSolver bdp(M, B, param);
329    setup_time[&bdp] = chrono.RealTime();
330 
331    ResetTimer();
332    DivFreeSolver dfs_dm(M, B, DFS_data);
333    setup_time[&dfs_dm] = chrono.RealTime();
334 
335    ResetTimer();
336    const_cast<bool&>(DFS_data.param.coupled_solve) = true;
337    DivFreeSolver dfs_cm(M, B, DFS_data);
338    setup_time[&dfs_cm] = chrono.RealTime();
339 
340    std::map<const DarcySolver*, std::string> solver_to_name;
341    solver_to_name[&bdp] = "Block-diagonal-preconditioned MINRES";
342    solver_to_name[&dfs_dm] = "Divergence free (decoupled mode)";
343    solver_to_name[&dfs_cm] = "Divergence free (coupled mode)";
344 
345    // Solve the problem using all solvers
346    for (const auto& solver_pair : solver_to_name)
347    {
348       auto& solver = solver_pair.first;
349       auto& name = solver_pair.second;
350 
351       Vector sol = darcy.GetEssentialBC();
352 
353       ResetTimer();
354       solver->Mult(darcy.GetRHS(), sol);
355       chrono.Stop();
356 
357       if (mpi.Root())
358       {
359          cout << line << name << " solver:\n   Setup time: "
360               << setup_time[solver] << "s.\n   Solve time: "
361               << chrono.RealTime() << "s.\n   Total time: "
362               << setup_time[solver] + chrono.RealTime() << "s.\n"
363               << "   Iteration count: " << solver->GetNumIterations() <<"\n\n";
364       }
365       if (show_error && std::strcmp(coef_file, "") == 0)
366       {
367          darcy.ShowError(sol, mpi.Root());
368       }
369       else if (show_error && mpi.Root())
370       {
371          cout << "Exact solution is unknown for coefficient '" << coef_file
372               << "'.\nApproximation error is computed in this case!\n\n";
373       }
374 
375       if (visualization) { darcy.VisualizeSolution(sol, name); }
376 
377    }
378 
379    return 0;
380 }
381 
u_exact(const Vector & x,Vector & u)382 void u_exact(const Vector & x, Vector & u)
383 {
384    double xi(x(0));
385    double yi(x(1));
386    double zi(x.Size() == 3 ? x(2) : 0.0);
387 
388    u(0) = - exp(xi)*sin(yi)*cos(zi);
389    u(1) = - exp(xi)*cos(yi)*cos(zi);
390    if (x.Size() == 3)
391    {
392       u(2) = exp(xi)*sin(yi)*sin(zi);
393    }
394 }
395 
p_exact(const Vector & x)396 double p_exact(const Vector & x)
397 {
398    double xi(x(0));
399    double yi(x(1));
400    double zi(x.Size() == 3 ? x(2) : 0.0);
401    return exp(xi)*sin(yi)*cos(zi);
402 }
403 
f_exact(const Vector & x,Vector & f)404 void f_exact(const Vector & x, Vector & f)
405 {
406    f = 0.0;
407 }
408 
g_exact(const Vector & x)409 double g_exact(const Vector & x)
410 {
411    if (x.Size() == 3) { return -p_exact(x); }
412    return 0;
413 }
414 
natural_bc(const Vector & x)415 double natural_bc(const Vector & x)
416 {
417    return (-p_exact(x));
418 }
419