181 lines
6.1 KiB
C++
181 lines
6.1 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
//
|
|
// ---------------------------------
|
|
// Poisson/Darcy Mixed Method Solver
|
|
// ---------------------------------
|
|
//
|
|
// Solves a Poisson problem -Delta p = f using a mixed finite element
|
|
// formulation). The right-hand side of the Poisson problem is the same as that
|
|
// used in the LOR Solvers miniapp (see miniapps/solvers). Dirichlet boundary
|
|
// conditions are enforced on all domain boundaries.
|
|
//
|
|
// Optionally, the equation alpha*p - Delta p = f can be solved by setting the
|
|
// alpha parameter to a nonzero value.
|
|
|
|
// This can be written in the form of a Darcy problem
|
|
//
|
|
// -u - grad(p) = 0
|
|
// alpha*p + div(u) = f
|
|
//
|
|
// where natural boundary conditions are enforced on the flux u, and the
|
|
// Dirichlet condition on p is enforced by modifying the right-hand side.
|
|
//
|
|
// The resulting saddle-point system is solved using MINRES with a matrix-free
|
|
// block-diagonal preconditioner.
|
|
//
|
|
// See also example 5 and its parallel version.
|
|
//
|
|
// Sample runs:
|
|
//
|
|
// darcy
|
|
// mpirun -np 4 darcy -m ../../data/fichera-q2.mesh
|
|
|
|
#include "mfem.hpp"
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include "discrete_divergence.hpp"
|
|
#include "hdiv_linear_solver.hpp"
|
|
|
|
#include "../solvers/lor_mms.hpp"
|
|
|
|
using namespace std;
|
|
using namespace mfem;
|
|
|
|
ParMesh LoadParMesh(const char *mesh_file, int ser_ref = 0, int par_ref = 0);
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
Mpi::Init(argc, argv);
|
|
Hypre::Init();
|
|
|
|
const char *mesh_file = "../../data/star.mesh";
|
|
const char *device_config = "cpu";
|
|
int ser_ref = 1;
|
|
int par_ref = 1;
|
|
int order = 3;
|
|
real_t alpha = 0.0;
|
|
|
|
OptionsParser args(argc, argv);
|
|
args.AddOption(&device_config, "-d", "--device",
|
|
"Device configuration string, see Device::Configure().");
|
|
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
|
|
args.AddOption(&ser_ref, "-rs", "--serial-refine",
|
|
"Number of times to refine the mesh in serial.");
|
|
args.AddOption(&par_ref, "-rp", "--parallel-refine",
|
|
"Number of times to refine the mesh in parallel.");
|
|
args.AddOption(&order, "-o", "--order", "Polynomial degree.");
|
|
args.AddOption(&alpha, "-a", "--alpha", "Value of alpha coefficient.");
|
|
args.ParseCheck();
|
|
|
|
Device device(device_config);
|
|
if (Mpi::Root()) { device.Print(); }
|
|
|
|
ParMesh mesh = LoadParMesh(mesh_file, ser_ref, par_ref);
|
|
const int dim = mesh.Dimension();
|
|
MFEM_VERIFY(dim == 2 || dim == 3, "Spatial dimension must be 2 or 3.");
|
|
|
|
const int b1 = BasisType::GaussLobatto, b2 = BasisType::GaussLegendre;
|
|
const int mt = FiniteElement::VALUE;
|
|
RT_FECollection fec_rt(order-1, dim, b1, b2);
|
|
L2_FECollection fec_l2(order-1, dim, b2, mt);
|
|
ParFiniteElementSpace fes_rt(&mesh, &fec_rt);
|
|
ParFiniteElementSpace fes_l2(&mesh, &fec_l2);
|
|
|
|
HYPRE_BigInt ndofs_rt = fes_rt.GlobalTrueVSize();
|
|
HYPRE_BigInt ndofs_l2 = fes_l2.GlobalTrueVSize();
|
|
|
|
if (Mpi::Root())
|
|
{
|
|
cout << "\nRT DOFs: " << ndofs_rt << "\nL2 DOFs: " << ndofs_l2 << endl;
|
|
}
|
|
|
|
Array<int> ess_rt_dofs; // empty
|
|
|
|
// f is the RHS, u is the exact solution
|
|
FunctionCoefficient f_coeff(f(alpha)), u_coeff(u);
|
|
|
|
// Assemble the right-hand side for the scalar (L2) unknown.
|
|
ParLinearForm b_l2(&fes_l2);
|
|
b_l2.AddDomainIntegrator(new DomainLFIntegrator(f_coeff));
|
|
b_l2.UseFastAssembly(true);
|
|
b_l2.Assemble();
|
|
|
|
// Enforce Dirichlet boundary conditions on the scalar unknown by adding
|
|
// the boundary term to the flux equation.
|
|
ParLinearForm b_rt(&fes_rt);
|
|
b_rt.AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(u_coeff));
|
|
b_rt.UseFastAssembly(true);
|
|
b_rt.Assemble();
|
|
|
|
if (Mpi::Root()) { cout << "\nSaddle point solver... " << flush; }
|
|
tic_toc.Clear(); tic_toc.Start();
|
|
|
|
// Set up the block system of the form
|
|
//
|
|
// [ W D ][ u ] = [ f ]
|
|
// [ D^T -M ][ q ] = [ g_D ]
|
|
//
|
|
// where W is the L2 mass matrix, D is the discrete divergence, and M is
|
|
// the RT mass matrix.
|
|
//
|
|
// If the coefficient alpha is set to zero, the system takes the form
|
|
//
|
|
// [ 0 D ][ u ] = [ f ]
|
|
// [ D^T -M ][ q ] = [ g_D ]
|
|
//
|
|
// u is the scalar unknown, and q is the flux. f is the right-hand side from
|
|
// the Poisson problem, and g_D is the contribution to the right-hand side
|
|
// from the Dirichlet boundary condition.
|
|
|
|
ConstantCoefficient one(1.0);
|
|
ConstantCoefficient alpha_coeff(alpha);
|
|
const auto solver_mode = HdivSaddlePointSolver::Mode::DARCY;
|
|
HdivSaddlePointSolver saddle_point_solver(
|
|
mesh, fes_rt, fes_l2, alpha_coeff, one, ess_rt_dofs, solver_mode);
|
|
|
|
const Array<int> &offsets = saddle_point_solver.GetOffsets();
|
|
BlockVector X_block(offsets), B_block(offsets);
|
|
|
|
b_l2.ParallelAssemble(B_block.GetBlock(0));
|
|
b_rt.ParallelAssemble(B_block.GetBlock(1));
|
|
B_block.SyncFromBlocks();
|
|
|
|
X_block = 0.0;
|
|
saddle_point_solver.Mult(B_block, X_block);
|
|
X_block.SyncToBlocks();
|
|
|
|
if (Mpi::Root())
|
|
{
|
|
cout << "Done.\nIterations: "
|
|
<< saddle_point_solver.GetNumIterations()
|
|
<< "\nElapsed: " << tic_toc.RealTime() << endl;
|
|
}
|
|
|
|
ParGridFunction x(&fes_l2);
|
|
x.SetFromTrueDofs(X_block.GetBlock(0));
|
|
const real_t error = x.ComputeL2Error(u_coeff);
|
|
if (Mpi::Root()) { cout << "L2 error: " << error << endl; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
ParMesh LoadParMesh(const char *mesh_file, int ser_ref, int par_ref)
|
|
{
|
|
Mesh serial_mesh = Mesh::LoadFromFile(mesh_file);
|
|
for (int i = 0; i < ser_ref; ++i) { serial_mesh.UniformRefinement(); }
|
|
ParMesh mesh(MPI_COMM_WORLD, serial_mesh);
|
|
serial_mesh.Clear();
|
|
for (int i = 0; i < par_ref; ++i) { mesh.UniformRefinement(); }
|
|
return mesh;
|
|
}
|