Files
mfem/miniapps/hdiv-linear-solver/discrete_divergence.cpp
T
2025-12-04 16:49:09 -08:00

277 lines
9.4 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 "mfem.hpp"
#include "discrete_divergence.hpp"
namespace mfem
{
/// @brief Eliminates columns in the given HypreParMatrix.
///
/// This is similar to HypreParMatrix::EliminateBC, except that only the columns
/// are eliminated.
void EliminateColumns(HypreParMatrix &D, const Array<int> &ess_dofs)
{
hypre_ParCSRMatrix *A_hypre = D;
D.HypreReadWrite();
hypre_CSRMatrix *diag = hypre_ParCSRMatrixDiag(A_hypre);
hypre_CSRMatrix *offd = hypre_ParCSRMatrixOffd(A_hypre);
HYPRE_Int diag_ncols = hypre_CSRMatrixNumCols(diag);
HYPRE_Int offd_ncols = hypre_CSRMatrixNumCols(offd);
const int n_ess_dofs = ess_dofs.Size();
// Start communication to figure out which columns need to be eliminated in
// the off-diagonal block
hypre_ParCSRCommHandle *comm_handle;
HYPRE_Int *int_buf_data, *eliminate_col_diag, *eliminate_col_offd;
{
eliminate_col_diag = mfem_hypre_CTAlloc_host(HYPRE_Int, diag_ncols);
eliminate_col_offd = mfem_hypre_CTAlloc_host(HYPRE_Int, offd_ncols);
// Make sure A has a communication package
hypre_ParCSRCommPkg *comm_pkg = hypre_ParCSRMatrixCommPkg(A_hypre);
if (!comm_pkg)
{
hypre_MatvecCommPkgCreate(A_hypre);
comm_pkg = hypre_ParCSRMatrixCommPkg(A_hypre);
}
// Which of the local columns are to be eliminated?
for (int i = 0; i < diag_ncols; i++)
{
eliminate_col_diag[i] = 0;
}
ess_dofs.HostRead();
for (int i = 0; i < n_ess_dofs; i++)
{
eliminate_col_diag[ess_dofs[i]] = 1;
}
// Use a matvec communication pattern to find (in eliminate_col_offd)
// which of the local offd columns are to be eliminated
HYPRE_Int num_sends = hypre_ParCSRCommPkgNumSends(comm_pkg);
HYPRE_Int int_buf_sz = hypre_ParCSRCommPkgSendMapStart(comm_pkg, num_sends);
int_buf_data = mfem_hypre_CTAlloc_host(HYPRE_Int, int_buf_sz);
HYPRE_Int *send_map_elmts = hypre_ParCSRCommPkgSendMapElmts(comm_pkg);
for (int i = 0; i < int_buf_sz; ++i)
{
const int k = send_map_elmts[i];
int_buf_data[i] = eliminate_col_diag[k];
}
comm_handle = hypre_ParCSRCommHandleCreate(
11, comm_pkg, int_buf_data, eliminate_col_offd);
}
// Eliminate columns in the diagonal block
{
Memory<HYPRE_Int> col_mem(eliminate_col_diag, diag_ncols, false);
const auto cols = col_mem.Read(GetHypreMemoryClass(), diag_ncols);
const int nrows_diag = hypre_CSRMatrixNumRows(diag);
const auto I = diag->i;
const auto J = diag->j;
auto data = diag->data;
mfem::hypre_forall(nrows_diag, [=] MFEM_HOST_DEVICE (int i)
{
for (int jj=I[i]; jj<I[i+1]; ++jj)
{
const int j = J[jj];
data[jj] *= 1 - cols[j];
}
});
col_mem.Delete();
}
// Wait for MPI communication to finish
hypre_ParCSRCommHandleDestroy(comm_handle);
mfem_hypre_TFree_host(int_buf_data);
mfem_hypre_TFree_host(eliminate_col_diag);
// Eliminate columns in the off-diagonal block
{
Memory<HYPRE_Int> col_mem(eliminate_col_offd, offd_ncols, false);
const auto cols = col_mem.Read(GetHypreMemoryClass(), offd_ncols);
const int nrows_offd = hypre_CSRMatrixNumRows(offd);
const auto I = offd->i;
const auto J = offd->j;
auto data = offd->data;
mfem::hypre_forall(nrows_offd, [=] MFEM_HOST_DEVICE (int i)
{
for (int jj=I[i]; jj<I[i+1]; ++jj)
{
const int j = J[jj];
data[jj] *= 1 - cols[j];
}
});
col_mem.Delete();
}
mfem_hypre_TFree_host(eliminate_col_offd);
}
void FormElementToFace2D(int order, Array<int> &element2face)
{
const int o = order;
const int op1 = order + 1;
for (int iy = 0; iy < o; ++iy)
{
for (int ix = 0; ix < o; ++ix)
{
const int ivol = ix + iy*o;
element2face[0 + 4*ivol] = -1 - (ix + iy*op1); // left, x = 0
element2face[1 + 4*ivol] = ix+1 + iy*op1; // right, x = 1
element2face[2 + 4*ivol] = -1 - (ix + iy*o + o*op1); // bottom, y = 0
element2face[3 + 4*ivol] = ix + (iy+1)*o + o*op1; // top, y = 1
}
}
}
void FormElementToFace3D(int order, Array<int> &element2face)
{
const int o = order;
const int op1 = order + 1;
const int n = o*o*op1; // number of faces per dimension
for (int iz = 0; iz < o; ++iz)
{
for (int iy = 0; iy < o; ++iy)
{
for (int ix = 0; ix < o; ++ix)
{
const int ivol = ix + iy*o + iz*o*o;
element2face[0 + 6*ivol] = -1 - (ix + iy*op1 + iz*o*op1); // x = 0
element2face[1 + 6*ivol] = ix+1 + iy*op1 + iz*o*op1; // x = 1
element2face[2 + 6*ivol] = -1 - (ix + iy*o + iz*o*op1 + n); // y = 0
element2face[3 + 6*ivol] = ix + (iy+1)*o + iz*o*op1 + n; // y = 1
element2face[4 + 6*ivol] = -1 - (ix + iy*o + iz*o*o + 2*n); // z = 0
element2face[5 + 6*ivol] = ix + iy*o + (iz+1)*o*o + 2*n; // z = 1
}
}
}
}
HypreParMatrix *FormDiscreteDivergenceMatrix(ParFiniteElementSpace &fes_rt,
ParFiniteElementSpace &fes_l2,
const Array<int> &ess_dofs)
{
const Mesh &mesh = *fes_rt.GetMesh();
const int dim = mesh.Dimension();
const int order = fes_rt.GetMaxElementOrder();
const int n_rt = fes_rt.GetNDofs();
const int n_l2 = fes_l2.GetNDofs();
SparseMatrix D_local;
D_local.OverrideSize(n_l2, n_rt);
D_local.GetMemoryI().New(n_l2 + 1);
// Each row always has 2*dim nonzeros (one for each face of the element)
const int nnz = n_l2*2*dim;
auto I = D_local.WriteI();
MFEM_FORALL(i, n_l2+1, I[i] = 2*dim*i; );
const int nel_ho = mesh.GetNE();
const int nface_per_el = dim*pow(order, dim-1)*(order+1);
const int nvol_per_el = pow(order, dim);
// element2face is a mapping of size (2*dim, nvol_per_el) such that with a
// macro element, subelement i (in lexicographic ordering) has faces (also
// in lexicographic order) given by the entries (j, i).
Array<int> element2face;
element2face.SetSize(2*dim*nvol_per_el);
if (dim == 2) { FormElementToFace2D(order, element2face); }
else if (dim == 3) { FormElementToFace3D(order, element2face); }
else { MFEM_ABORT("Unsupported dimension.") }
const ElementDofOrdering ordering = ElementDofOrdering::LEXICOGRAPHIC;
const auto *R_rt = dynamic_cast<const ElementRestriction*>(
fes_rt.GetElementRestriction(ordering));
const auto gather_rt = Reshape(R_rt->GatherMap().Read(), nface_per_el, nel_ho);
const auto e2f = Reshape(element2face.Read(), 2*dim, nvol_per_el);
// Fill J and data
D_local.GetMemoryJ().New(nnz);
D_local.GetMemoryData().New(nnz);
auto J = D_local.WriteJ();
auto V = D_local.WriteData();
const int two_dim = 2*dim;
// Loop over L2 DOFs
MFEM_FORALL(ii, n_l2*2*dim,
{
const int k = ii % (two_dim);
const int i = ii / (two_dim);
const int i_loc = i%nvol_per_el;
const int i_el = i/nvol_per_el;
const int sjv_loc = e2f(k, i_loc);
const int jv_loc = (sjv_loc >= 0) ? sjv_loc : -1 - sjv_loc;
const int sgn1 = (sjv_loc >= 0) ? 1 : -1;
const int sj = gather_rt(jv_loc, i_el);
const int j = (sj >= 0) ? sj : -1 - sj;
const int sgn2 = (sj >= 0) ? 1 : -1;
J[k + 2*dim*i] = j;
V[k + 2*dim*i] = sgn1*sgn2;
});
// Create a block diagonal parallel matrix
OperatorHandle D_diag(Operator::Hypre_ParCSR);
D_diag.MakeRectangularBlockDiag(fes_rt.GetComm(),
fes_l2.GlobalVSize(),
fes_rt.GlobalVSize(),
fes_l2.GetDofOffsets(),
fes_rt.GetDofOffsets(),
&D_local);
HypreParMatrix *D;
// Assemble the parallel gradient matrix, must be deleted by the caller
if (IsIdentityProlongation(fes_rt.GetProlongationMatrix()))
{
D = D_diag.As<HypreParMatrix>();
D_diag.SetOperatorOwner(false);
HypreStealOwnership(*D, D_local);
}
else
{
OperatorHandle Rt(Transpose(*fes_l2.GetRestrictionMatrix()));
OperatorHandle Rt_diag(Operator::Hypre_ParCSR);
Rt_diag.MakeRectangularBlockDiag(fes_l2.GetComm(),
fes_l2.GlobalVSize(),
fes_l2.GlobalTrueVSize(),
fes_l2.GetDofOffsets(),
fes_l2.GetTrueDofOffsets(),
Rt.As<SparseMatrix>());
D = RAP(Rt_diag.As<HypreParMatrix>(),
D_diag.As<HypreParMatrix>(),
fes_rt.Dof_TrueDof_Matrix());
}
D->CopyRowStarts();
D->CopyColStarts();
// Eliminate the boundary conditions
EliminateColumns(*D, ess_dofs);
return D;
}
} // namespace mfem