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

365 lines
10 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 "change_basis.hpp"
namespace mfem
{
/// @brief Compute the inverse of the matrix A and store the result in Ainv.
///
/// The input A is an array of size n*n, interpreted as a matrix with column
/// major ordering.
void ComputeInverse(const Array<real_t> &A, Array<real_t> &Ainv)
{
Array<real_t> A2 = A;
const int n2 = A.Size();
const int n = static_cast<const int>(sqrt(n2));
Array<int> ipiv(n);
LUFactors lu(A2.GetData(), ipiv.GetData());
lu.Factor(n);
Ainv.SetSize(n2);
lu.GetInverseMatrix(n, Ainv.GetData());
}
void SubcellIntegrals(int n, const Poly_1D::Basis &basis, Array<real_t> &B)
{
const IntegrationRule &ir = IntRules.Get(Geometry::SEGMENT, n);
const real_t *gll_pts = poly1d.GetPoints(n, BasisType::GaussLobatto);
Vector u(n);
B.SetSize(n*n);
B = 0.0;
for (int i = 0; i < n; ++i)
{
const real_t h = gll_pts[i+1] - gll_pts[i];
// Loop over subcell quadrature points
for (int iq = 0; iq < ir.Size(); ++iq)
{
const IntegrationPoint &ip = ir[iq];
const real_t x = gll_pts[i] + h*ip.x;
const real_t w = h*ip.weight;
basis.Eval(x, u);
for (int j = 0; j < n; ++j)
{
B[i + j*n] += w*u[j];
}
}
}
}
void Transpose(const Array<real_t> &B, Array<real_t> &Bt)
{
const int n = static_cast<const int>(sqrt(B.Size()));
Bt.SetSize(n*n);
for (int i=0; i<n; ++i) for (int j=0; j<n; ++j) { Bt[i+j*n] = B[j+i*n]; }
}
ChangeOfBasis_L2::ChangeOfBasis_L2(FiniteElementSpace &fes)
: Operator(fes.GetTrueVSize()),
ne(fes.GetNE())
{
auto *fec1 = dynamic_cast<const L2_FECollection*>(fes.FEColl());
MFEM_VERIFY(fec1, "Must be L2 finite element space");
const int btype = fec1->GetBasisType();
// If the basis types are the same, don't need to perform change of basis.
no_op = (btype == BasisType::IntegratedGLL);
if (no_op) { return; }
// Convert from the given basis to the "integrated GLL basis".
// The degrees of freedom are integrals over subcells.
const FiniteElement *fe = fes.GetTypicalFE();
auto *tbe = dynamic_cast<const TensorBasisElement*>(fe);
MFEM_VERIFY(tbe != nullptr, "Must be a tensor element.");
const Poly_1D::Basis &basis = tbe->GetBasis1D();
const int p = fes.GetMaxElementOrder();
const int pp1 = p + 1;
Array<real_t> B_inv;
SubcellIntegrals(pp1, basis, B_inv);
ComputeInverse(B_inv, B_1d);
Transpose(B_1d, Bt_1d);
// Set up the DofToQuad object, used in TensorValues
dof2quad.FE = fe;
dof2quad.mode = DofToQuad::TENSOR;
dof2quad.ndof = pp1;
dof2quad.nqpt = pp1;
}
void ChangeOfBasis_L2::Mult(const Vector &x, Vector &y) const
{
if (no_op) { y = x; return; }
dof2quad.B.MakeRef(B_1d);
const int dim = dof2quad.FE->GetDim();
const int nd = dof2quad.ndof;
const int nq = dof2quad.nqpt;
QuadratureInterpolator::TensorEvalKernels::Run(
dim, QVectorLayout::byVDIM, 1, nd, nq, ne, dof2quad.B.Read(), x.Read(),
y.Write(), 1, nd, nq);
}
void ChangeOfBasis_L2::MultTranspose(const Vector &x, Vector &y) const
{
if (no_op) { y = x; return; }
dof2quad.B.MakeRef(Bt_1d);
const int dim = dof2quad.FE->GetDim();
const int nd = dof2quad.ndof;
const int nq = dof2quad.nqpt;
QuadratureInterpolator::TensorEvalKernels::Run(
dim, QVectorLayout::byVDIM, 1, nd, nq, ne, dof2quad.B.Read(), x.Read(),
y.Write(), 1, nd, nq);
}
ChangeOfBasis_RT::ChangeOfBasis_RT(FiniteElementSpace &fes)
: Operator(fes.GetTrueVSize()),
fes(fes),
dim(fes.GetMesh()->Dimension()),
ne(fes.GetNE()),
p(fes.GetMaxElementOrder())
{
auto op = fes.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
elem_restr = dynamic_cast<const ElementRestriction*>(op);
MFEM_VERIFY(elem_restr != NULL, "Missing element restriction.");
const auto *rt_fec = dynamic_cast<const RT_FECollection*>(fes.FEColl());
MFEM_VERIFY(rt_fec, "Must be RT finite element space.");
const int cb_type = rt_fec->GetClosedBasisType();
const int ob_type = rt_fec->GetOpenBasisType();
no_op = (cb_type == BasisType::GaussLobatto &&
ob_type == BasisType::IntegratedGLL);
if (no_op) { return; }
const int pp1 = p + 1;
Poly_1D::Basis &cbasis = poly1d.GetBasis(p, cb_type);
Poly_1D::Basis &obasis = poly1d.GetBasis(p-1, ob_type);
const real_t *cpts2 = poly1d.GetPoints(p, BasisType::GaussLobatto);
Bci_1d.SetSize(pp1*pp1);
Vector b(pp1);
for (int i = 0; i < pp1; ++i)
{
cbasis.Eval(cpts2[i], b);
for (int j = 0; j < pp1; ++j)
{
Bci_1d[i + j*pp1] = b[j];
}
}
SubcellIntegrals(p, obasis, Boi_1d);
ComputeInverse(Boi_1d, Bo_1d);
Transpose(Bo_1d, Bot_1d);
ComputeInverse(Bci_1d, Bc_1d);
Transpose(Bc_1d, Bct_1d);
}
const real_t *ChangeOfBasis_RT::GetOpenMap(Mode mode) const
{
switch (mode)
{
case NORMAL: return Bo_1d.Read();
case TRANSPOSE: return Bot_1d.Read();
case INVERSE: return Boi_1d.Read();
}
return nullptr;
}
const real_t *ChangeOfBasis_RT::GetClosedMap(Mode mode) const
{
switch (mode)
{
case NORMAL: return Bc_1d.Read();
case TRANSPOSE: return Bct_1d.Read();
case INVERSE: return Bci_1d.Read();
}
return nullptr;
}
void ChangeOfBasis_RT::MultRT_2D(const Vector &x, Vector &y, Mode mode) const
{
const int DIM = dim;
const int NE = ne;
const int D1D = p + 1;
const int ND = (p+1)*p;
const real_t *BC = GetClosedMap(mode);
const real_t *BO = GetOpenMap(mode);
const auto X = Reshape(x.Read(), DIM*ND, ne);
auto Y = Reshape(y.Write(), DIM*ND, ne);
MFEM_FORALL(e, NE,
{
for (int c = 0; c < DIM; ++c)
{
const int nx = (c == 0) ? D1D : D1D-1;
const int ny = (c == 1) ? D1D : D1D-1;
const real_t *Bx = (c == 0) ? BC : BO;
const real_t *By = (c == 1) ? BC : BO;
for (int i = 0; i < ND; ++i)
{
Y(i + c*ND, e) = 0.0;
}
for (int iy = 0; iy < ny; ++ iy)
{
real_t xx[DofQuadLimits::MAX_D1D];
for (int ix = 0; ix < nx; ++ix) { xx[ix] = 0.0; }
for (int jx = 0; jx < nx; ++jx)
{
const real_t val = X(jx + iy*nx + c*nx*ny, e);
for (int ix = 0; ix < nx; ++ix)
{
xx[ix] += val*Bx[ix + jx*nx];
}
}
for (int jy = 0; jy < ny; ++jy)
{
const real_t b = By[jy + iy*ny];
for (int ix = 0; ix < nx; ++ix)
{
Y(ix + jy*nx + c*nx*ny, e) += xx[ix]*b;
}
}
}
}
});
}
void ChangeOfBasis_RT::MultRT_3D(const Vector &x, Vector &y, Mode mode) const
{
const int DIM = dim;
const int NE = ne;
const int D1D = p + 1;
const int ND = (p+1)*p*p;
const real_t *BC = GetClosedMap(mode);
const real_t *BO = GetOpenMap(mode);
const auto X = Reshape(x.Read(), DIM*ND, ne);
auto Y = Reshape(y.Write(), DIM*ND, ne);
MFEM_FORALL(e, NE,
{
for (int c = 0; c < DIM; ++c)
{
const int nx = (c == 0) ? D1D : D1D-1;
const int ny = (c == 1) ? D1D : D1D-1;
const int nz = (c == 2) ? D1D : D1D-1;
const real_t *Bx = (c == 0) ? BC : BO;
const real_t *By = (c == 1) ? BC : BO;
const real_t *Bz = (c == 2) ? BC : BO;
for (int i = 0; i < ND; ++i)
{
Y(i + c*ND, e) = 0.0;
}
for (int iz = 0; iz < nz; ++ iz)
{
real_t xy[DofQuadLimits::MAX_D1D][DofQuadLimits::MAX_D1D];
for (int iy = 0; iy < ny; ++iy)
{
for (int ix = 0; ix < nx; ++ix)
{
xy[iy][ix] = 0.0;
}
}
for (int iy = 0; iy < ny; ++iy)
{
real_t xx[DofQuadLimits::MAX_D1D];
for (int ix = 0; ix < nx; ++ix) { xx[ix] = 0.0; }
for (int ix = 0; ix < nx; ++ix)
{
const real_t val = X(ix + iy*nx + iz*nx*ny + c*ND, e);
for (int jx = 0; jx < nx; ++jx)
{
xx[jx] += val*Bx[jx + ix*nx];
}
}
for (int jy = 0; jy < ny; ++jy)
{
const real_t b = By[jy + iy*ny];
for (int jx = 0; jx < nx; ++jx)
{
xy[jy][jx] += xx[jx] * b;
}
}
}
for (int jz = 0; jz < nz; ++jz)
{
const real_t b = Bz[jz + iz*nz];
for (int jy = 0; jy < ny; ++jy)
{
for (int jx = 0; jx < nx; ++jx)
{
Y(jx + jy*nx + jz*nx*ny + c*ND, e) += xy[jy][jx] * b;
}
}
}
}
}
});
}
void ChangeOfBasis_RT::Mult(const Vector &x, Vector &y, Mode mode) const
{
if (no_op) { y = x; return; }
const Operator *P = fes.GetProlongationMatrix();
if (IsIdentityProlongation(P))
{
x_l.MakeRef(const_cast<Vector&>(x), 0, fes.GetVSize());
y_l.MakeRef(y, 0, fes.GetVSize());
}
else
{
x_l.SetSize(fes.GetVSize());
y_l.SetSize(fes.GetVSize());
P->Mult(x, x_l);
}
x_e.SetSize(elem_restr->Height());
y_e.SetSize(elem_restr->Height());
elem_restr->Mult(x_l, x_e);
if (dim == 2) { MultRT_2D(x_e, y_e, mode); }
else { MultRT_3D(x_e, y_e, mode); }
elem_restr->MultLeftInverse(y_e, y_l);
const Operator *R = fes.GetRestrictionOperator();
if (R) { R->Mult(y_l, y); }
else { MFEM_VERIFY(P == NULL, "Invalid state."); }
}
void ChangeOfBasis_RT::Mult(const Vector &x, Vector &y) const
{
Mult(x, y, NORMAL);
}
void ChangeOfBasis_RT::MultTranspose(const Vector &x, Vector &y) const
{
Mult(x, y, TRANSPOSE);
}
void ChangeOfBasis_RT::MultInverse(const Vector &x, Vector &y) const
{
Mult(x, y, INVERSE);
}
} // namespace mfem