Skip to content

Commit 53475e0

Browse files
committed
Support dynamic quadrature in Gradient operator
1 parent 83f2be3 commit 53475e0

File tree

8 files changed

+124
-125
lines changed

8 files changed

+124
-125
lines changed

bindings/pypbat/fem/Gradient.cpp

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ namespace fem {
1313
class Gradient
1414
{
1515
public:
16-
Gradient(Mesh const& M, Eigen::Ref<MatrixX const> const& GNe, int qOrder);
16+
Gradient(
17+
Mesh const& M,
18+
Eigen::Ref<IndexVectorX const> const& eg,
19+
Eigen::Ref<MatrixX const> const& GNeg);
1720

1821
Gradient(Gradient const&) = delete;
1922
Gradient& operator=(Gradient const&) = delete;
@@ -30,9 +33,6 @@ class Gradient
3033
int mMeshOrder;
3134
int mDims;
3235
int mOrder;
33-
int mQuadratureOrder;
34-
35-
static auto constexpr kMaxQuadratureOrder = 2;
3636

3737
private:
3838
void* mGradient;
@@ -44,37 +44,38 @@ void BindGradient(pybind11::module& m)
4444

4545
pyb::class_<Gradient>(m, "Gradient")
4646
.def(
47-
pyb::init<Mesh const&, Eigen::Ref<MatrixX const> const&, int>(),
47+
pyb::init<
48+
Mesh const&,
49+
Eigen::Ref<IndexVectorX const> const&,
50+
Eigen::Ref<MatrixX const> const&>(),
4851
pyb::arg("mesh"),
49-
pyb::arg("GNe"),
50-
pyb::arg("quadrature_order") = 1,
52+
pyb::arg("eg"),
53+
pyb::arg("GNeg"),
5154
"Construct Gradient operator from mesh mesh, using precomputed shape function "
52-
"gradients GNe at quadrature points given by quadrature rule of order quadrature_order")
55+
"gradients GNeg at quadrature points at elements eg.")
5356
.def_readonly("dims", &Gradient::mDims)
5457
.def_readonly("order", &Gradient::mOrder, "Polynomial order of the gradient")
55-
.def_readonly("quadrature_order", &Gradient::mQuadratureOrder)
5658
.def_property_readonly("shape", &Gradient::Shape)
5759
.def("to_matrix", &Gradient::ToMatrix);
5860
}
5961

60-
Gradient::Gradient(Mesh const& M, Eigen::Ref<MatrixX const> const& GNe, int qOrder)
62+
Gradient::Gradient(
63+
Mesh const& M,
64+
Eigen::Ref<IndexVectorX const> const& eg,
65+
Eigen::Ref<MatrixX const> const& GNeg)
6166
: eMeshElement(M.eElement),
6267
mMeshDims(M.mDims),
6368
mMeshOrder(M.mOrder),
6469
mDims(),
6570
mOrder(),
66-
mQuadratureOrder(),
6771
mGradient(nullptr)
6872
{
69-
M.ApplyWithQuadrature<kMaxQuadratureOrder>(
70-
[&]<pbat::fem::CMesh MeshType, auto QuadratureOrder>(MeshType* mesh) {
71-
using GradientType = pbat::fem::Gradient<MeshType, QuadratureOrder>;
72-
mGradient = new GradientType(*mesh, GNe);
73-
mDims = GradientType::kDims;
74-
mOrder = GradientType::kOrder;
75-
mQuadratureOrder = GradientType::kQuadratureOrder;
76-
},
77-
qOrder);
73+
M.Apply([&]<pbat::fem::CMesh MeshType>(MeshType* mesh) {
74+
using GradientType = pbat::fem::Gradient<MeshType>;
75+
mGradient = new GradientType(*mesh, eg, GNeg);
76+
mDims = GradientType::kDims;
77+
mOrder = GradientType::kOrder;
78+
});
7879
}
7980

8081
CSCMatrix Gradient::ToMatrix() const
@@ -103,16 +104,11 @@ Gradient::~Gradient()
103104
template <class Func>
104105
void Gradient::Apply(Func&& f) const
105106
{
106-
ApplyToMeshWithQuadrature<kMaxQuadratureOrder>(
107-
mMeshDims,
108-
mMeshOrder,
109-
eMeshElement,
110-
mQuadratureOrder,
111-
[&]<pbat::fem::CMesh MeshType, auto QuadratureOrder>() {
112-
using GradientType = pbat::fem::Gradient<MeshType, QuadratureOrder>;
113-
GradientType* gradient = reinterpret_cast<GradientType*>(mGradient);
114-
f.template operator()<GradientType>(gradient);
115-
});
107+
ApplyToMesh(mMeshDims, mMeshOrder, eMeshElement, [&]<pbat::fem::CMesh MeshType>() {
108+
using GradientType = pbat::fem::Gradient<MeshType>;
109+
GradientType* gradient = reinterpret_cast<GradientType*>(mGradient);
110+
f.template operator()<GradientType>(gradient);
111+
});
116112
}
117113

118114
} // namespace fem

python/examples/elasticity.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@
3737
M, detJeM = pbat.fem.mass_matrix(mesh, rho=rho)
3838
Minv = pbat.math.linalg.ldlt(M)
3939
Minv.compute(M)
40-
# Could also lump the mass matrix like this
41-
# lumpedm = M.sum(axis=0)
42-
# M = sp.sparse.spdiags(lumpedm, np.array([0]), m=M.shape[0], n=M.shape[0])
43-
# Minv = sp.sparse.spdiags(
44-
# 1./lumpedm, np.array([0]), m=M.shape[0], n=M.shape[0])
4540

4641
# Construct load vector from gravity field
4742
g = np.zeros(mesh.dims)

python/examples/heat.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@
3535
# Construct Galerkin laplacian, mass and gradient operators
3636
n = V.shape[0]
3737
M, detJeM = pbat.fem.mass_matrix(mesh, dims=1)
38-
G, GNeG = pbat.fem.gradient(mesh)
39-
D, GNeD = pbat.fem.divergence(mesh, GNe=GNeG)
38+
G, egG, GNegG = pbat.fem.gradient(mesh)
39+
wgD = pbat.fem.inner_product_weights(mesh)
40+
D, wgD, egD, GNegD = pbat.fem.divergence(mesh, eg=egG, wg=wgD, GNeg=GNegG)
4041
L = D @ G
4142
# Setup 1-step heat diffusion
4243
h = igl.avg_edge_length(V, C)

python/examples/ipc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,7 @@ def __call__(self, xk: np.ndarray):
367367
# Lumped mass matrix
368368
rho = args.rho
369369
M, detJeM = pbat.fem.mass_matrix(mesh, rho=rho, lump=True)
370-
Minv = sp.sparse.spdiags(
371-
1./M.diagonal(), np.array([0]), m=M.shape[0], n=M.shape[0])
370+
Minv = sp.sparse.diags(1./M.diagonal())
372371

373372
# Construct load vector from gravity field
374373
g = np.zeros(mesh.dims)

python/pbatoolkit/fem.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,47 +23,57 @@
2323
)
2424

2525

26-
def divergence(mesh, quadrature_order: int = 1, GNe: np.ndarray = None):
26+
def divergence(mesh, quadrature_order: int = 1, eg: np.ndarray = None, wg: np.ndarray = None, GNeg: np.ndarray = None):
2727
"""Construct an FEM divergence operator such that composing div(grad(u))
2828
computes the Laplacian of u, i.e. D @ G = L, where L is the FEM Laplacian
2929
operator.
3030
3131
Args:
3232
mesh (pbat.fem.Mesh):
3333
quadrature_order (int, optional): Polynomial order to use for operator construction. Defaults to 1.
34-
GNe (np.ndarray, optional): Shape function gradients at quadrature points. Defaults to None.
34+
eg (np.ndarray, optional): |#quad.pts.|x1 array of elements corresponding to quadrature points. Defaults to None.
35+
wg (np.ndarray, optional): |#quad.pts.|x1 array of quadrature weights. Defaults to None.
36+
GNeg (np.ndarray, optional): |#elem. nodes|x|#dims * #quad.pts.| array of shape function gradients at quadrature points. Defaults to None.
3537
3638
Returns:
3739
(scipy.sparse.csc_matrix, np.ndarray):
3840
"""
39-
G, GNe = gradient(mesh, quadrature_order=quadrature_order,
40-
GNe=GNe, as_matrix=True)
41-
qgL = _fem.inner_product_weights(
42-
mesh, quadrature_order=quadrature_order).flatten(order="F")
43-
QL = sp.sparse.diags_array([qgL], offsets=[0])
41+
should_compute_quadrature = eg is None or wg is None or GNeg is None
42+
G, eg, GNeg = gradient(mesh, quadrature_order=quadrature_order, as_matrix=True) if should_compute_quadrature else gradient(
43+
mesh, eg=eg, GNeg=GNeg, as_matrix=True)
44+
wg = _fem.inner_product_weights(
45+
mesh, quadrature_order=quadrature_order).flatten(order="F") if should_compute_quadrature else wg
46+
QL = sp.sparse.diags_array(np.asarray(wg).squeeze())
4447
QL = sp.sparse.kron(sp.sparse.eye_array(mesh.dims), QL)
4548
D = -G.T @ QL
46-
return D, GNe
49+
return D, eg, wg, GNeg
4750

4851

49-
def gradient(mesh, quadrature_order: int = 1, GNe: np.ndarray = None, as_matrix: bool = True):
52+
def gradient(mesh, quadrature_order: int = 1, eg: np.ndarray = None, GNeg: np.ndarray = None, as_matrix: bool = True):
5053
"""Construct an FEM gradient operator
5154
5255
Args:
5356
mesh (pbat.fem.Mesh):
5457
quadrature_order (int, optional): Polynomial order to use for operator construction. Defaults to 1.
55-
GNe (np.ndarray, optional): Shape function gradients at quadrature points. Defaults to None.
58+
eg (np.ndarray, optional): Elements corresponding to quadrature points. Defaults to None.
59+
GNeg (np.ndarray, optional): Shape function gradients at quadrature points. Defaults to None.
5660
as_matrix (bool, optional): Return the operator as a sparse matrix. Defaults to True.
5761
5862
Returns:
5963
(scipy.sparse.csc_matrix | pbat.fem.Gradient, np.ndarray):
6064
"""
61-
if GNe is None:
62-
GNe = _fem.shape_function_gradients(
65+
66+
if GNeg is None:
67+
GNeg = _fem.shape_function_gradients(
6368
mesh, quadrature_order=quadrature_order)
64-
G = _fem.Gradient(mesh, GNe, quadrature_order=quadrature_order)
69+
n_quadpts_per_element = GNeg.shape[1] / (mesh.dims * mesh.E.shape[1])
70+
eg = np.linspace(0, mesh.E.shape[1]-1,
71+
num=mesh.E.shape[1], dtype=np.int64)
72+
eg = np.repeat(eg, n_quadpts_per_element)
73+
74+
G = _fem.Gradient(mesh, eg, GNeg)
6575
G = G.to_matrix() if as_matrix else G
66-
return G, GNe
76+
return G, eg, GNeg
6777

6878

6979
def hyper_elastic_potential(
@@ -170,9 +180,8 @@ def mass_matrix(
170180
if as_matrix:
171181
M = M.to_matrix()
172182
if lump:
173-
lumpedm = M.sum(axis=0)
174-
M = sp.sparse.spdiags(lumpedm, np.array(
175-
[0]), m=M.shape[0], n=M.shape[0])
183+
lumpedm = np.asarray(M.sum(axis=0)).squeeze()
184+
M = sp.sparse.diags_array(lumpedm)
176185
return M, detJe
177186

178187

@@ -199,7 +208,7 @@ def load_vector(
199208
nelems = mesh.E.shape[1]
200209
wg = np.tile(mesh.quadrature_weights(quadrature_order), nelems)
201210
qgf = detJe.flatten(order="F") * wg
202-
Qf = sp.sparse.diags_array([qgf], offsets=[0])
211+
Qf = sp.sparse.diags_array(qgf)
203212
Nf = _fem.shape_function_matrix(mesh, quadrature_order=quadrature_order)
204213
if len(fe.shape) == 1:
205214
fe = np.tile(fe[:, np.newaxis], Qf.shape[0])

source/pbat/fem/Gradient.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,20 @@ TEST_CASE("[fem] Gradient")
3636
using Mesh = fem::Mesh<Element, kDims>;
3737
Mesh mesh(V, C);
3838

39-
MatrixX const GNe = fem::ShapeFunctionGradients<kQuadratureOrder>(mesh);
40-
using GradientType = fem::Gradient<Mesh, kQuadratureOrder>;
39+
MatrixX const GNe = fem::ShapeFunctionGradients<kQuadratureOrder>(mesh);
40+
auto const nQuadPointsPerElement = GNe.cols() / (kDims * mesh.E.cols());
41+
IndexVectorX const eg = IndexVectorX::LinSpaced(mesh.E.cols(), Index(0), mesh.E.cols() - 1)
42+
.replicate(1, nQuadPointsPerElement)
43+
.transpose()
44+
.reshaped();
45+
using GradientType = fem::Gradient<Mesh>;
4146
CHECK(math::CLinearOperator<GradientType>);
42-
GradientType G{mesh, GNe};
47+
GradientType G{mesh, eg, GNe};
4348

4449
auto const n = G.InputDimensions();
4550
auto const m = G.OutputDimensions();
4651
auto const numberOfElements = mesh.E.cols();
47-
auto constexpr kQuadPts = GradientType::QuadratureRuleType::kPoints;
48-
CHECK_EQ(m, kDims * kQuadPts * numberOfElements);
52+
CHECK_EQ(m, kDims * nQuadPointsPerElement * numberOfElements);
4953

5054
VectorX const ones = VectorX::Ones(n);
5155
VectorX gradOnes = VectorX::Zero(m);

0 commit comments

Comments
 (0)