250 lines
8.2 KiB
C++
250 lines
8.2 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 "lor_ads.hpp"
|
|
#include "../../general/forall.hpp"
|
|
#include "../../fem/pbilinearform.hpp"
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
BatchedLOR_ADS::BatchedLOR_ADS(ParFiniteElementSpace &pfes_ho_,
|
|
const Vector &X_vert)
|
|
: face_fes(pfes_ho_),
|
|
order(face_fes.GetMaxElementOrder()),
|
|
edge_fec(order, dim),
|
|
edge_fes(face_fes.GetParMesh(), &edge_fec),
|
|
ams(edge_fes, X_vert)
|
|
{
|
|
MFEM_VERIFY(face_fes.GetParMesh()->Dimension() == dim, "Bad dimension.")
|
|
FormCurlMatrix();
|
|
}
|
|
|
|
void BatchedLOR_ADS::Form3DFaceToEdge(Array<int> &face2edge)
|
|
{
|
|
const int o = order;
|
|
const int op1 = o + 1;
|
|
const int nface = dim*o*o*op1;
|
|
|
|
face2edge.SetSize(4*nface);
|
|
auto f2e = Reshape(face2edge.HostWrite(), 4, nface);
|
|
|
|
for (int c=0; c<dim; ++c)
|
|
{
|
|
const int nx = (c == 0) ? op1 : o;
|
|
const int ny = (c == 1) ? op1 : o;
|
|
|
|
const int c1 = (c+1)%dim;
|
|
const int c2 = (c+2)%dim;
|
|
|
|
const int nx_e1 = (c1 == 0) ? o : op1;
|
|
const int ny_e1 = (c1 == 1) ? o : op1;
|
|
const int nx_e2 = (c2 == 0) ? o : op1;
|
|
const int ny_e2 = (c2 == 1) ? o : op1;
|
|
|
|
for (int i=0; i<o*o*op1; ++i)
|
|
{
|
|
int i1[dim], i2[dim];
|
|
const int ix = i1[0] = i2[0] = i%nx;
|
|
const int iy = i1[1] = i2[1] = (i/nx)%ny;
|
|
const int iz = i1[2] = i2[2] = i/nx/ny;
|
|
|
|
const int iface = ix + iy*nx + iz*nx*ny + c*o*o*op1;
|
|
|
|
++i1[c2];
|
|
++i2[c1];
|
|
|
|
const int ie0 = c1*o*op1*op1 + ix + iy*nx_e1 + iz*nx_e1*ny_e1;
|
|
const int ie1 = c1*o*op1*op1 + i1[0] + i1[1]*nx_e1 + i1[2]*nx_e1*ny_e1;
|
|
|
|
const int ie2 = c2*o*op1*op1 + ix + iy*nx_e2 + iz*nx_e2*ny_e2;
|
|
const int ie3 = c2*o*op1*op1 + i2[0] + i2[1]*nx_e2 + i2[2]*nx_e2*ny_e2;
|
|
|
|
f2e(0, iface) = ie0;
|
|
f2e(1, iface) = ie1;
|
|
f2e(2, iface) = ie2;
|
|
f2e(3, iface) = ie3;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BatchedLOR_ADS::FormCurlMatrix()
|
|
{
|
|
// The curl matrix maps from LOR edges to LOR faces. Given a quadrilateral
|
|
// face (defined by its four edges) f_i = (e_j1, e_j2, e_j3, e_j4), the
|
|
// matrix has nonzeros A(i, jk), so there are always exactly four nonzeros
|
|
// per row.
|
|
const int nface_dof = face_fes.GetNDofs();
|
|
const int nedge_dof = edge_fes.GetNDofs();
|
|
|
|
SparseMatrix C_local;
|
|
C_local.OverrideSize(nface_dof, nedge_dof);
|
|
|
|
C_local.GetMemoryI().New(nedge_dof+1, Device::GetDeviceMemoryType());
|
|
// Each row always has four nonzeros
|
|
const int nnz = 4*nedge_dof;
|
|
auto I = C_local.WriteI();
|
|
mfem::forall(nedge_dof+1, [=] MFEM_HOST_DEVICE (int i) { I[i] = 4*i; });
|
|
|
|
// face2edge is a mapping of size (4, nface_per_el), such that with a macro
|
|
// element, face i (in lexicographic ordering) has four edges given by the
|
|
// entries (k, i), for k=1,2,3,4.
|
|
Array<int> face2edge;
|
|
Form3DFaceToEdge(face2edge);
|
|
|
|
ElementDofOrdering ordering = ElementDofOrdering::LEXICOGRAPHIC;
|
|
const auto *R_f = dynamic_cast<const ElementRestriction*>(
|
|
face_fes.GetElementRestriction(ordering));
|
|
const auto *R_e = dynamic_cast<const ElementRestriction*>(
|
|
edge_fes.GetElementRestriction(ordering));
|
|
MFEM_VERIFY(R_f != NULL && R_e != NULL, "");
|
|
|
|
const int nel_ho = edge_fes.GetNE();
|
|
const int nedge_per_el = dim*order*(order+1)*(order+1);
|
|
const int nface_per_el = dim*order*order*(order+1);
|
|
|
|
const auto offsets_f = R_f->Offsets().Read();
|
|
const auto indices_f = R_f->Indices().Read();
|
|
const auto gather_e = Reshape(R_e->GatherMap().Read(), nedge_per_el, nel_ho);
|
|
|
|
const auto f2e = Reshape(face2edge.Read(), 4, nface_per_el);
|
|
|
|
// Fill J and data
|
|
C_local.GetMemoryJ().New(nnz, Device::GetDeviceMemoryType());
|
|
C_local.GetMemoryData().New(nnz, Device::GetDeviceMemoryType());
|
|
|
|
auto J = C_local.WriteJ();
|
|
auto V = C_local.WriteData();
|
|
|
|
// Loop over Raviart-Thomas L-DOFs
|
|
mfem::forall(nface_dof, [=] MFEM_HOST_DEVICE (int i)
|
|
{
|
|
const int sj = indices_f[offsets_f[i]]; // signed
|
|
const int j = (sj >= 0) ? sj : -1 - sj;
|
|
const int sgn_f = (sj >= 0) ? 1 : -1;
|
|
const int j_loc = j%nface_per_el;
|
|
const int j_el = j/nface_per_el;
|
|
|
|
for (int k=0; k<4; ++k)
|
|
{
|
|
const int je_loc = f2e(k, j_loc);
|
|
const int sje = gather_e(je_loc, j_el); // signed
|
|
const int je = (sje >= 0) ? sje : -1 - sje;
|
|
const int sgn_e = (sje >= 0) ? 1 : -1;
|
|
const int sgn = (k == 1 || k == 2) ? -1 : 1;
|
|
J[i*4 + k] = je;
|
|
V[i*4 + k] = sgn*sgn_f*sgn_e;
|
|
}
|
|
});
|
|
|
|
// Create a block diagonal parallel matrix
|
|
OperatorHandle C_diag(Operator::Hypre_ParCSR);
|
|
C_diag.MakeRectangularBlockDiag(edge_fes.GetComm(),
|
|
face_fes.GlobalVSize(),
|
|
edge_fes.GlobalVSize(),
|
|
face_fes.GetDofOffsets(),
|
|
edge_fes.GetDofOffsets(),
|
|
&C_local);
|
|
|
|
// Assemble the parallel gradient matrix, must be deleted by the caller
|
|
if (IsIdentityProlongation(edge_fes.GetProlongationMatrix()))
|
|
{
|
|
C = C_diag.As<HypreParMatrix>();
|
|
C_diag.SetOperatorOwner(false);
|
|
HypreStealOwnership(*C, C_local);
|
|
}
|
|
else
|
|
{
|
|
OperatorHandle Rt(Transpose(*face_fes.GetRestrictionMatrix()));
|
|
OperatorHandle Rt_diag(Operator::Hypre_ParCSR);
|
|
Rt_diag.MakeRectangularBlockDiag(face_fes.GetComm(),
|
|
face_fes.GlobalVSize(),
|
|
face_fes.GlobalTrueVSize(),
|
|
face_fes.GetDofOffsets(),
|
|
face_fes.GetTrueDofOffsets(),
|
|
Rt.As<SparseMatrix>());
|
|
C = RAP(Rt_diag.As<HypreParMatrix>(),
|
|
C_diag.As<HypreParMatrix>(),
|
|
edge_fes.Dof_TrueDof_Matrix());
|
|
}
|
|
C->CopyRowStarts();
|
|
C->CopyColStarts();
|
|
}
|
|
|
|
HypreParMatrix *BatchedLOR_ADS::StealCurlMatrix()
|
|
{
|
|
return StealPointer(C);
|
|
}
|
|
|
|
BatchedLOR_ADS::~BatchedLOR_ADS()
|
|
{
|
|
delete C;
|
|
}
|
|
|
|
LORSolver<HypreADS>::LORSolver(
|
|
ParBilinearForm &a_ho, const Array<int> &ess_tdof_list, int ref_type)
|
|
{
|
|
MFEM_VERIFY(a_ho.FESpace()->GetMesh()->Dimension() == 3,
|
|
"The ADS solver is only valid in 3D.");
|
|
if (BatchedLORAssembly::FormIsSupported(a_ho))
|
|
{
|
|
ParFiniteElementSpace &pfes = *a_ho.ParFESpace();
|
|
BatchedLORAssembly batched_lor(pfes);
|
|
BatchedLOR_ADS lor_ads(pfes, batched_lor.GetLORVertexCoordinates());
|
|
BatchedLOR_AMS &lor_ams = lor_ads.GetAMS();
|
|
batched_lor.Assemble(a_ho, ess_tdof_list, A);
|
|
xyz = lor_ams.StealCoordinateVector();
|
|
solver = new HypreADS(*A.As<HypreParMatrix>(),
|
|
lor_ads.StealCurlMatrix(),
|
|
lor_ams.StealGradientMatrix(),
|
|
lor_ams.StealXCoordinate(),
|
|
lor_ams.StealYCoordinate(),
|
|
lor_ams.StealZCoordinate());
|
|
}
|
|
else
|
|
{
|
|
ParLORDiscretization lor(a_ho, ess_tdof_list, ref_type);
|
|
// Assume ownership of the system matrix so that `lor` can be safely
|
|
// deleted
|
|
A.Reset(lor.GetAssembledSystem().Ptr());
|
|
lor.GetAssembledSystem().SetOperatorOwner(false);
|
|
solver = new HypreADS(lor.GetAssembledMatrix(), &lor.GetParFESpace());
|
|
}
|
|
width = solver->Width();
|
|
height = solver->Height();
|
|
}
|
|
|
|
void LORSolver<HypreADS>::SetOperator(const Operator &op)
|
|
{
|
|
solver->SetOperator(op);
|
|
}
|
|
|
|
void LORSolver<HypreADS>::Mult(const Vector &x, Vector &y) const
|
|
{
|
|
solver->Mult(x, y);
|
|
}
|
|
|
|
HypreADS &LORSolver<HypreADS>::GetSolver() { return *solver; }
|
|
|
|
const HypreADS &LORSolver<HypreADS>::GetSolver() const { return *solver; }
|
|
|
|
LORSolver<HypreADS>::~LORSolver()
|
|
{
|
|
delete solver;
|
|
delete xyz;
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace mfem
|