378 lines
12 KiB
C++
378 lines
12 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.
|
|
|
|
#include "hdiv_linear_solver.hpp"
|
|
#include "discrete_divergence.hpp"
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
/// Replace x[i] with 1.0/x[i] for all i.
|
|
void Reciprocal(Vector &x)
|
|
{
|
|
const int n = x.Size();
|
|
real_t *d_x = x.ReadWrite();
|
|
MFEM_FORALL(i, n, d_x[i] = 1.0/d_x[i]; );
|
|
}
|
|
|
|
/// Return a new HypreParMatrix with given diagonal entries
|
|
HypreParMatrix *MakeDiagonalMatrix(Vector &diag,
|
|
const ParFiniteElementSpace &fes)
|
|
{
|
|
const int n = diag.Size();
|
|
|
|
SparseMatrix diag_spmat;
|
|
diag_spmat.OverrideSize(n, n);
|
|
diag_spmat.GetMemoryI().New(n+1, Device::GetDeviceMemoryType());
|
|
diag_spmat.GetMemoryJ().New(n, Device::GetDeviceMemoryType());
|
|
diag_spmat.GetMemoryData().New(n, Device::GetDeviceMemoryType());
|
|
|
|
{
|
|
int *I = diag_spmat.WriteI();
|
|
int *J = diag_spmat.WriteJ();
|
|
real_t *A = diag_spmat.WriteData();
|
|
const real_t *d_diag = diag.Read();
|
|
MFEM_FORALL(i, n+1, I[i] = i;);
|
|
MFEM_FORALL(i, n,
|
|
{
|
|
J[i] = i;
|
|
A[i] = d_diag[i];
|
|
});
|
|
}
|
|
|
|
HYPRE_BigInt global_size = fes.GlobalTrueVSize();
|
|
HYPRE_BigInt *row_starts = fes.GetTrueDofOffsets();
|
|
HypreParMatrix D(fes.GetComm(), global_size, row_starts, &diag_spmat);
|
|
return new HypreParMatrix(D); // make a deep copy
|
|
}
|
|
|
|
const IntegrationRule &GetMassIntRule(FiniteElementSpace &fes_l2)
|
|
{
|
|
Mesh *mesh = fes_l2.GetMesh();
|
|
const FiniteElement *fe = fes_l2.GetTypicalFE();
|
|
return MassIntegrator::GetRule(
|
|
*fe, *fe, *mesh->GetTypicalElementTransformation());
|
|
}
|
|
|
|
HdivSaddlePointSolver::HdivSaddlePointSolver(
|
|
ParMesh &mesh, ParFiniteElementSpace &fes_rt_, ParFiniteElementSpace &fes_l2_,
|
|
Coefficient &L_coeff_, Coefficient &R_coeff_, const Array<int> &ess_rt_dofs_,
|
|
Mode mode_)
|
|
: minres(mesh.GetComm()),
|
|
order(fes_rt_.GetMaxElementOrder()),
|
|
fec_l2(order - 1, mesh.Dimension(), b2, mt),
|
|
fes_l2(&mesh, &fec_l2),
|
|
fec_rt(order - 1, mesh.Dimension(), b1, b2),
|
|
fes_rt(&mesh, &fec_rt),
|
|
ess_rt_dofs(ess_rt_dofs_),
|
|
basis_l2(fes_l2_),
|
|
basis_rt(fes_rt_),
|
|
convert_map_type(fes_l2_.GetTypicalFE()->GetMapType() == FiniteElement::VALUE),
|
|
mass_l2(&fes_l2),
|
|
mass_rt(&fes_rt),
|
|
L_coeff(L_coeff_),
|
|
R_coeff(R_coeff_),
|
|
mode(mode_),
|
|
qs(mesh, GetMassIntRule(fes_l2)),
|
|
W_coeff_qf(qs),
|
|
W_mix_coeff_qf(qs),
|
|
W_coeff(W_coeff_qf),
|
|
W_mix_coeff(W_mix_coeff_qf)
|
|
{
|
|
// If the user gives zero L coefficient, switch mode to DARCY_ZERO
|
|
auto *L_const_coeff = dynamic_cast<ConstantCoefficient*>(&L_coeff);
|
|
zero_l2_block = (L_const_coeff && L_const_coeff->constant == 0.0);
|
|
|
|
if (mode == Mode::GRAD_DIV)
|
|
{
|
|
MFEM_VERIFY(!zero_l2_block,
|
|
"Mode::GRAD_DIV incompatible with zero coefficient.");
|
|
}
|
|
|
|
mass_l2.AddDomainIntegrator(new MassIntegrator(W_coeff));
|
|
mass_l2.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
|
|
mass_rt.AddDomainIntegrator(new VectorFEMassIntegrator(&R_coeff));
|
|
mass_rt.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
|
|
D.reset(FormDiscreteDivergenceMatrix(fes_rt, fes_l2, ess_rt_dofs));
|
|
Dt.reset(D->Transpose());
|
|
|
|
// Versions without BCs needed for elimination
|
|
D_e.reset(FormDiscreteDivergenceMatrix(fes_rt, fes_l2, empty));
|
|
mass_rt.FormSystemMatrix(empty, R_e);
|
|
|
|
offsets.SetSize(3);
|
|
offsets[0] = 0;
|
|
offsets[1] = fes_l2.GetTrueVSize();
|
|
offsets[2] = offsets[1] + fes_rt.GetTrueVSize();
|
|
|
|
minres.SetAbsTol(0.0);
|
|
minres.SetRelTol(1e-12);
|
|
minres.SetMaxIter(500);
|
|
minres.SetPrintLevel(IterativeSolver::PrintLevel().None());
|
|
minres.iterative_mode = false;
|
|
|
|
R_diag.SetSize(fes_rt.GetTrueVSize());
|
|
L_diag.SetSize(fes_l2.GetTrueVSize());
|
|
|
|
S_inv.SetPrintLevel(0);
|
|
|
|
if (mode == Mode::DARCY && !zero_l2_block)
|
|
{
|
|
ParBilinearForm mass_l2_unweighted(&fes_l2);
|
|
QuadratureFunction det_J_qf(qs);
|
|
QuadratureFunctionCoefficient det_J_coeff(det_J_qf);
|
|
if (convert_map_type)
|
|
{
|
|
const auto flags = GeometricFactors::DETERMINANTS;
|
|
auto *geom = fes_l2.GetMesh()->GetGeometricFactors(qs.GetIntRule(0), flags);
|
|
det_J_qf = geom->detJ;
|
|
mass_l2_unweighted.AddDomainIntegrator(new MassIntegrator(det_J_coeff));
|
|
}
|
|
else
|
|
{
|
|
mass_l2_unweighted.AddDomainIntegrator(new MassIntegrator);
|
|
}
|
|
mass_l2_unweighted.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
mass_l2_unweighted.Assemble();
|
|
const int n_l2 = fes_l2.GetTrueVSize();
|
|
L_diag_unweighted.SetSize(n_l2);
|
|
mass_l2_unweighted.AssembleDiagonal(L_diag_unweighted);
|
|
}
|
|
|
|
Setup();
|
|
}
|
|
|
|
HdivSaddlePointSolver::HdivSaddlePointSolver(
|
|
ParMesh &mesh, ParFiniteElementSpace &fes_rt_, ParFiniteElementSpace &fes_l2_,
|
|
Coefficient &R_coeff_, const Array<int> &ess_rt_dofs_)
|
|
: HdivSaddlePointSolver(mesh, fes_rt_, fes_l2_, zero, R_coeff_,
|
|
ess_rt_dofs_, Mode::DARCY)
|
|
{ }
|
|
|
|
void HdivSaddlePointSolver::Setup()
|
|
{
|
|
const auto flags = GeometricFactors::DETERMINANTS;
|
|
auto *geom = fes_l2.GetMesh()->GetGeometricFactors(qs.GetIntRule(0), flags);
|
|
|
|
if (!zero_l2_block) { L_coeff.Project(W_coeff_qf); }
|
|
// In "grad-div mode", the transformation matrix is scaled by the coefficient
|
|
// of the mass and divergence matrices.
|
|
// In "Darcy mode", the transformation matrix is unweighted.
|
|
if (mode == Mode::GRAD_DIV) { W_mix_coeff_qf = W_coeff_qf; }
|
|
else { W_mix_coeff_qf = 1.0; }
|
|
|
|
// The transformation matrix has to be "mixed" value and integral map type,
|
|
// which means that the coefficient has to be scaled like the Jacobian
|
|
// determinant.
|
|
if (convert_map_type)
|
|
{
|
|
const int n = W_mix_coeff_qf.Size();
|
|
const real_t *d_detJ = geom->detJ.Read();
|
|
real_t *d_w_mix = W_mix_coeff_qf.ReadWrite();
|
|
real_t *d_w = W_coeff_qf.ReadWrite();
|
|
const bool zero_l2 = zero_l2_block;
|
|
MFEM_FORALL(i, n,
|
|
{
|
|
const real_t detJ = d_detJ[i];
|
|
if (!zero_l2) { d_w[i] *= detJ*detJ; }
|
|
d_w_mix[i] *= detJ;
|
|
});
|
|
}
|
|
|
|
L_inv.reset(new DGMassInverse(fes_l2, W_mix_coeff));
|
|
|
|
if (zero_l2_block)
|
|
{
|
|
A_11.reset();
|
|
}
|
|
else
|
|
{
|
|
mass_l2.Assemble();
|
|
mass_l2.AssembleDiagonal(L_diag);
|
|
mass_l2.FormSystemMatrix(empty, L);
|
|
|
|
A_11.reset(new RAPOperator(*L_inv, *L, *L_inv));
|
|
|
|
if (mode == GRAD_DIV)
|
|
{
|
|
L_diag_unweighted.SetSize(L_diag.Size());
|
|
|
|
BilinearForm mass_l2_mix(&fes_l2);
|
|
mass_l2_mix.AddDomainIntegrator(new MassIntegrator(W_mix_coeff));
|
|
mass_l2_mix.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
mass_l2_mix.Assemble();
|
|
mass_l2_mix.AssembleDiagonal(L_diag_unweighted);
|
|
}
|
|
|
|
const real_t *d_L_diag_unweighted = L_diag_unweighted.Read();
|
|
real_t *d_L_diag = L_diag.ReadWrite();
|
|
MFEM_FORALL(i, L_diag.Size(),
|
|
{
|
|
const real_t d = d_L_diag_unweighted[i];
|
|
d_L_diag[i] /= d*d;
|
|
});
|
|
}
|
|
|
|
// Reassemble the RT mass operator with the new coefficient
|
|
mass_rt.Update();
|
|
mass_rt.Assemble();
|
|
mass_rt.FormSystemMatrix(ess_rt_dofs, R);
|
|
|
|
// Form the updated approximate Schur complement
|
|
mass_rt.AssembleDiagonal(R_diag);
|
|
|
|
// Update the mass RT diagonal for essential DOFs
|
|
{
|
|
const int *d_I = ess_rt_dofs.Read();
|
|
real_t *d_R_diag = R_diag.ReadWrite();
|
|
MFEM_FORALL(i, ess_rt_dofs.Size(), d_R_diag[d_I[i]] = 1.0;);
|
|
}
|
|
|
|
// Form the approximate Schur complement
|
|
{
|
|
Reciprocal(R_diag);
|
|
std::unique_ptr<HypreParMatrix> R_diag_inv(MakeDiagonalMatrix(R_diag, fes_rt));
|
|
if (zero_l2_block)
|
|
{
|
|
S.reset(RAP(R_diag_inv.get(), Dt.get()));
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<HypreParMatrix> D_Minv_Dt(RAP(R_diag_inv.get(), Dt.get()));
|
|
std::unique_ptr<HypreParMatrix> L_diag_inv(MakeDiagonalMatrix(L_diag, fes_l2));
|
|
S.reset(ParAdd(D_Minv_Dt.get(), L_diag_inv.get()));
|
|
}
|
|
}
|
|
|
|
// Reassemble the preconditioners
|
|
R_inv.reset(new OperatorJacobiSmoother(mass_rt, ess_rt_dofs));
|
|
S_inv.SetOperator(*S);
|
|
|
|
// Set up the block operators
|
|
A_block.reset(new BlockOperator(offsets));
|
|
// Omit the (1,1)-block when the L coefficient is identically zero.
|
|
if (A_11) { A_block->SetBlock(0, 0, A_11.get()); }
|
|
A_block->SetBlock(0, 1, D.get());
|
|
A_block->SetBlock(1, 0, Dt.get());
|
|
A_block->SetBlock(1, 1, R.Ptr(), -1.0);
|
|
|
|
D_prec.reset(new BlockDiagonalPreconditioner(offsets));
|
|
D_prec->SetDiagonalBlock(0, &S_inv);
|
|
D_prec->SetDiagonalBlock(1, R_inv.get());
|
|
|
|
minres.SetPreconditioner(*D_prec);
|
|
minres.SetOperator(*A_block);
|
|
}
|
|
|
|
void HdivSaddlePointSolver::EliminateBC(Vector &b) const
|
|
{
|
|
const int n_ess_dofs = ess_rt_dofs.Size();
|
|
if (fes_l2.GetParMesh()->ReduceInt(n_ess_dofs) == 0) { return; }
|
|
|
|
const int n_l2 = offsets[1];
|
|
const int n_rt = offsets[2]-offsets[1];
|
|
Vector bE(b, 0, n_l2);
|
|
Vector bF(b, n_l2, n_rt);
|
|
|
|
// SetBC must be called first
|
|
MFEM_VERIFY(x_bc.Size() == n_rt || n_ess_dofs == 0, "BCs not set");
|
|
|
|
// Create a vector z that has the BC values at essential DOFs, zero elsewhere
|
|
z.SetSize(n_rt);
|
|
z.UseDevice(true);
|
|
z = 0.0;
|
|
const int *d_I = ess_rt_dofs.Read();
|
|
const real_t *d_x_bc = x_bc.Read();
|
|
real_t *d_z = z.ReadWrite();
|
|
MFEM_FORALL(i, n_ess_dofs,
|
|
{
|
|
const int j = d_I[i];
|
|
d_z[j] = d_x_bc[j];
|
|
});
|
|
|
|
// Convert to the IntegratedGLL basis used internally
|
|
w.SetSize(n_rt);
|
|
basis_rt.MultInverse(z, w);
|
|
|
|
// Eliminate the BCs in the L2 RHS
|
|
D_e->Mult(-1.0, w, 1.0, bE);
|
|
|
|
// Eliminate the BCs in the RT RHS
|
|
// Flip the sign because the R block appears with multiplier -1
|
|
z.SetSize(n_rt);
|
|
R_e->Mult(w, z);
|
|
bF += z;
|
|
|
|
// Insert the RT BCs into the RHS at the essential DOFs.
|
|
const real_t *d_w = w.Read();
|
|
real_t *d_bF = bF.ReadWrite(); // Need read-write access to set subvector
|
|
MFEM_FORALL(i, n_ess_dofs,
|
|
{
|
|
const int j = d_I[i];
|
|
d_bF[j] = -d_w[j];
|
|
});
|
|
|
|
// Make sure the monolithic RHS is updated
|
|
bE.SyncAliasMemory(b);
|
|
bF.SyncAliasMemory(b);
|
|
}
|
|
|
|
void HdivSaddlePointSolver::Mult(const Vector &b, Vector &x) const
|
|
{
|
|
w.SetSize(fes_l2.GetTrueVSize());
|
|
b_prime.SetSize(b.Size());
|
|
x_prime.SetSize(x.Size());
|
|
|
|
// Transform RHS to the IntegratedGLL basis
|
|
Vector bE_prime(b_prime, offsets[0], offsets[1]-offsets[0]);
|
|
Vector bF_prime(b_prime, offsets[1], offsets[2]-offsets[1]);
|
|
|
|
const Vector bE(const_cast<Vector&>(b), offsets[0], offsets[1]-offsets[0]);
|
|
const Vector bF(const_cast<Vector&>(b), offsets[1], offsets[2]-offsets[1]);
|
|
|
|
z.SetSize(bE.Size());
|
|
basis_l2.MultTranspose(bE, z);
|
|
basis_rt.MultTranspose(bF, bF_prime);
|
|
// Transform by the inverse of the L2 mass matrix
|
|
L_inv->Mult(z, bE_prime);
|
|
|
|
// Update the monolithic transformed RHS
|
|
bE_prime.SyncAliasMemory(b_prime);
|
|
bF_prime.SyncAliasMemory(b_prime);
|
|
|
|
// Eliminate the RT essential BCs
|
|
EliminateBC(b_prime);
|
|
|
|
// Solve the transformed system
|
|
minres.Mult(b_prime, x_prime);
|
|
|
|
// Transform the solution back to the user's basis
|
|
Vector xE_prime(x_prime, offsets[0], offsets[1]-offsets[0]);
|
|
Vector xF_prime(x_prime, offsets[1], offsets[2]-offsets[1]);
|
|
|
|
Vector xE(x, offsets[0], offsets[1]-offsets[0]);
|
|
Vector xF(x, offsets[1], offsets[2]-offsets[1]);
|
|
|
|
z.SetSize(bE.Size()); // Size of z may have changed in EliminateBC
|
|
L_inv->Mult(xE_prime, z);
|
|
|
|
basis_l2.Mult(z, xE);
|
|
basis_rt.Mult(xF_prime, xF);
|
|
|
|
// Update the monolithic solution vector
|
|
xE.SyncAliasMemory(x);
|
|
xF.SyncAliasMemory(x);
|
|
}
|
|
|
|
} // namespace mfem
|