Compare commits

...
Author SHA1 Message Date
Will Pazner ad545a44ab Support integrated Legendre polynomials in ChangeOfBasis 2024-08-28 10:56:21 -07:00
Will Pazner feecdfe646 Prototype implementation of change of basis operator 2024-08-28 10:34:11 -07:00
Will Pazner 8aa4124324 Add MultLeftInverse to ElementRestrictionOperator abstract base class
Implement for L2ElementRestriction (in this case, same as transpose)
2024-08-28 10:33:47 -07:00
10 changed files with 315 additions and 3 deletions
+2
View File
@@ -56,6 +56,7 @@ set(SRCS
integ/lininteg_domain_vectorfe.cpp
integ/nonlininteg_vecconvection_pa.cpp
integ/nonlininteg_vecconvection_mf.cpp
change_basis.cpp
coefficient.cpp
complex_fem.cpp
convergence.cpp
@@ -164,6 +165,7 @@ set(HDRS
integ/bilininteg_hdiv_kernels.hpp
integ/bilininteg_hcurlhdiv_kernels.hpp
integ/bilininteg_mass_kernels.hpp
change_basis.hpp
coefficient.hpp
complex_fem.hpp
convergence.hpp
+132
View File
@@ -0,0 +1,132 @@
// Copyright (c) 2010-2024, 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"
#include "qinterp/dispatch.hpp"
namespace mfem
{
ChangeOfBasis::ChangeOfBasis(FiniteElementSpace &fes_, int dest_btype)
: Operator(fes_.GetVSize()),
fes(fes_)
{
const FiniteElement *fe = fes.GetFE(0);
auto *tbe = dynamic_cast<const TensorBasisElement*>(fe);
MFEM_VERIFY(tbe != nullptr, "Must be a tensor element.");
const int source_btype = tbe->GetBasisType();
const int order = fes.GetElementOrder(0);
IntegrationRules irs(0, Quadrature1D::GaussLobatto);
const IntegrationRule &ir = irs.Get(Geometry::SEGMENT, 2*order - 1);
auto compute_vandermonde = [&](int btype, DenseMatrix &V)
{
if (btype < BasisType::NumBasisTypes)
{
Poly_1D::Basis &basis = poly1d.GetBasis(order, btype);
for (int i = 0; i < ir.Size(); ++i)
{
Vector col;
V.GetColumnReference(i, col);
basis.Eval(ir[i].x, col);
}
}
else if (btype == LEGENDRE)
{
for (int i = 0; i < ir.Size(); ++i)
{
Vector col;
V.GetColumnReference(i, col);
Poly_1D::CalcLegendre(order, ir[i].x, col.HostWrite());
}
}
else if (btype == INTEGRATED_LEGENDRE)
{
for (int i = 0; i < ir.Size(); ++i)
{
Vector col;
V.GetColumnReference(i, col);
Poly_1D::CalcIntegratedLegendre(order, ir[i].x, col.HostWrite());
}
}
else
{
MFEM_ABORT("");
}
V.Transpose();
};
DenseMatrix V1(order + 1, order + 1);
compute_vandermonde(source_btype, V1);
DenseMatrix V2(order + 1, order + 1);
compute_vandermonde(dest_btype, V2);
DenseMatrixInverse V2_inv(V2);
T1D.SetSize(order + 1, order + 1);
V2_inv.Mult(V1, T1D);
{
T1D_inv.SetSize(order + 1, order + 1);
DenseMatrix A = T1D;
DenseMatrixInverse A_inv(A);
A_inv.GetInverseMatrix(T1D_inv);
}
dof2quad.FE = fe;
dof2quad.mode = DofToQuad::TENSOR;
dof2quad.ndof = order + 1;
dof2quad.nqpt = order + 1;
dof2quad.B.SetSize((order + 1)*(order + 1));
}
void ChangeOfBasis::Mult_(const DenseMatrix &B1D, const Vector &x,
Vector &y) const
{
using namespace internal::quadrature_interpolator;
const auto ordering = ElementDofOrdering::LEXICOGRAPHIC;
const Operator *restr_op = fes.GetElementRestriction(ordering);
auto restr = dynamic_cast<const ElementRestrictionOperator*>(restr_op);
MFEM_VERIFY(restr != nullptr, "Unsupported element restriction type.");
x_e.SetSize(restr->Height());
y_e.SetSize(restr->Height());
restr->Mult(x, x_e);
dof2quad.B.GetMemory().CopyFrom(B1D.GetMemory(), dof2quad.B.Size());
if (fes.GetOrdering() == Ordering::byNODES)
{
TensorValues<QVectorLayout::byNODES>(fes.GetNE(), 1, dof2quad, x_e, y_e);
}
else
{
TensorValues<QVectorLayout::byVDIM>(fes.GetNE(), 1, dof2quad, x_e, y_e);
}
restr->MultLeftInverse(y_e, y);
}
void ChangeOfBasis::Mult(const Vector &x, Vector &y) const
{
Mult_(T1D, x, y);
}
void ChangeOfBasis::MultInverse(const Vector &x, Vector &y) const
{
Mult_(T1D_inv, x, y);
}
} // namespace mfem
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2010-2024, 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.
#ifndef MFEM_CHANGE_BASIS_HPP
#define MFEM_CHANGE_BASIS_HPP
#include "../linalg/operator.hpp"
#include "fespace.hpp"
namespace mfem
{
class ChangeOfBasis : public Operator
{
public:
enum
{
LEGENDRE = BasisType::NumBasisTypes + 1,
INTEGRATED_LEGENDRE = BasisType::NumBasisTypes + 2
};
protected:
FiniteElementSpace &fes;
mutable Vector x_e, y_e;
mutable DofToQuad dof2quad;
DenseMatrix T1D;
DenseMatrix T1D_inv;
void Mult_(const DenseMatrix &B1D, const Vector &x, Vector &y) const;
public:
ChangeOfBasis(FiniteElementSpace &fes_, int dest_btype);
void Mult(const Vector &x, Vector &y) const override;
void MultInverse(const Vector &x, Vector &y) const;
};
} // namespace mfem
#endif
+15
View File
@@ -2201,6 +2201,21 @@ void Poly_1D::CalcLegendre(const int p, const real_t x, real_t *u, real_t *d)
}
}
void Poly_1D::CalcIntegratedLegendre(const int p, const real_t x, real_t *u)
{
Poly_1D::CalcLegendre(p, x, u);
for (int n = p; n > 1; n--)
{
u[n] = (u[n] - u[n - 2]) / 2;
}
for (int n = 1; n < p; ++n)
{
u[n] = u[n+1];
}
u[p] = x;
u[0] = 1 - x;
}
void Poly_1D::CalcChebyshev(const int p, const real_t x, real_t *u)
{
// recursive definition, z in [-1,1]
+2
View File
@@ -1186,6 +1186,8 @@ public:
static void CalcLegendre(const int p, const real_t x, real_t *u);
static void CalcLegendre(const int p, const real_t x, real_t *u, real_t *d);
static void CalcIntegratedLegendre(const int p, const real_t x, real_t *u);
~Poly_1D();
};
+1
View File
@@ -49,6 +49,7 @@
#include "lor/lor.hpp"
#include "dgmassinv.hpp"
#include "hyperbolic.hpp"
#include "change_basis.hpp"
#ifdef MFEM_USE_MPI
#include "pfespace.hpp"
+7
View File
@@ -560,6 +560,13 @@ void L2ElementRestriction::AddMultTranspose(const Vector &x, Vector &y,
TAddMultTranspose<ADD>(x, y);
}
void L2ElementRestriction::MultLeftInverse(const Vector &x, Vector &y) const
{
// For L2 spaces, the restriction is a permutation, and so its inverse is
// equal to its transpose.
MultTranspose(x, y);
}
void L2ElementRestriction::FillI(SparseMatrix &mat) const
{
const int elem_dofs = ndof;
+6 -3
View File
@@ -32,6 +32,10 @@ public:
/// of freedom @a y.
void AddMultTranspose(const Vector &x, Vector &y,
const real_t a = 1.0) const override = 0;
/// @brief Compute MultTranspose by setting (rather than adding) element
/// contributions; this is a left inverse of the Mult() operation
virtual void MultLeftInverse(const Vector &x, Vector &y) const = 0;
};
/// Operator that converts FiniteElementSpace L-vectors to E-vectors.
@@ -62,15 +66,13 @@ public:
void MultTranspose(const Vector &x, Vector &y) const override;
void AddMultTranspose(const Vector &x, Vector &y,
const real_t a = 1.0) const override;
void MultLeftInverse(const Vector &x, Vector &y) const override;
/// Compute Mult without applying signs based on DOF orientations.
void MultUnsigned(const Vector &x, Vector &y) const;
/// Compute MultTranspose without applying signs based on DOF orientations.
void MultTransposeUnsigned(const Vector &x, Vector &y) const;
/// Compute MultTranspose by setting (rather than adding) element
/// contributions; this is a left inverse of the Mult() operation
void MultLeftInverse(const Vector &x, Vector &y) const;
/// @brief Fills the E-vector y with `boolean` values 0.0 and 1.0 such that each
/// each entry of the L-vector is uniquely represented in `y`.
@@ -121,6 +123,7 @@ public:
void MultTranspose(const Vector &x, Vector &y) const override;
void AddMultTranspose(const Vector &x, Vector &y,
const real_t a = 1.0) const override;
void MultLeftInverse(const Vector &x, Vector &y) const override;
/** Fill the I array of SparseMatrix corresponding to the sparsity pattern
given by this ElementRestriction. */
void FillI(SparseMatrix &mat) const;
+1
View File
@@ -71,6 +71,7 @@ set(UNIT_TESTS_SRCS
fem/test_calcdshape.cpp
fem/test_calcshape.cpp
fem/test_calcvshape.cpp
fem/test_change_basis.cpp
fem/test_coefficient.cpp
fem/test_datacollection.cpp
fem/test_derefine.cpp
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) 2010-2024, 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 "unit_tests.hpp"
using namespace mfem;
TEST_CASE("Change of Basis", "[ChangeOfBasis][CUDA]")
{
const auto mesh_fname = GENERATE(
"../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/fichera.mesh",
"../../data/fichera-q3.mesh"
);
const int order = GENERATE(1, 2, 3);
const int btype1 = GENERATE(BasisType::GaussLobatto,
BasisType::ClosedUniform,
BasisType::Positive);
const int btype2 = GENERATE(BasisType::GaussLobatto,
BasisType::ClosedUniform,
BasisType::Positive);
if (btype1 == btype2) { return; }
CAPTURE(mesh_fname, order, btype1, btype2);
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
H1_FECollection fec1(order, mesh.Dimension(), btype1);
H1_FECollection fec2(order, mesh.Dimension(), btype2);
FiniteElementSpace fes1(&mesh, &fec1);
FiniteElementSpace fes2(&mesh, &fec2);
DiscreteLinearOperator op1(&fes1, &fes2);
op1.AddDomainInterpolator(new IdentityInterpolator);
op1.Assemble();
ChangeOfBasis op2(fes1, btype2);
GridFunction x1(&fes1), x2(&fes1), y1(&fes2), y2(&fes2);
x1.Randomize(1);
op1.Mult(x1, y1);
op2.Mult(x1, y2);
op2.MultInverse(y2, x2);
y2 -= y1;
x2 -= x1;
REQUIRE(y2.Normlinf() == MFEM_Approx(0.0));
REQUIRE(x2.Normlinf() == MFEM_Approx(0.0));
}
TEST_CASE("Change of Basis Legendre", "[ChangeOfBasis][CUDA]")
{
const auto mesh_fname = GENERATE(
"../../data/star.mesh",
"../../data/star-q3.mesh",
"../../data/fichera.mesh",
"../../data/fichera-q3.mesh"
);
const int order = GENERATE(1, 2, 3);
const int btype = GENERATE(BasisType::GaussLobatto,
BasisType::ClosedUniform,
BasisType::Positive);
const int dest_btype = GENERATE(ChangeOfBasis::LEGENDRE,
ChangeOfBasis::INTEGRATED_LEGENDRE);
CAPTURE(mesh_fname, order, btype);
Mesh mesh = Mesh::LoadFromFile(mesh_fname);
std::unique_ptr<FiniteElementCollection> fec;
if (dest_btype == ChangeOfBasis::LEGENDRE)
{
fec.reset(new L2_FECollection(order, mesh.Dimension(), btype));
}
else
{
fec.reset(new H1_FECollection(order, mesh.Dimension(), btype));
}
FiniteElementSpace fes(&mesh, fec.get());
ChangeOfBasis op(fes, dest_btype);
GridFunction x1(&fes), x2(&fes), y(&fes);
x1.Randomize(1);
op.Mult(x1, y);
op.MultInverse(y, x2);
x2 -= x1;
REQUIRE(x2.Normlinf() == MFEM_Approx(0.0));
}