Skip to content

Commit 85b3c4c

Browse files
committed
Reapply "Support dynamic quadrature in Laplacian"
This reverts commit 0da6085.
1 parent 0da6085 commit 85b3c4c

File tree

8 files changed

+136
-161
lines changed

8 files changed

+136
-161
lines changed

bindings/pypbat/fem/Laplacian.cpp

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ class Laplacian
1515
public:
1616
Laplacian(
1717
Mesh const& M,
18-
Eigen::Ref<MatrixX const> const& detJe,
19-
Eigen::Ref<MatrixX const> const& GNe,
20-
int dims,
21-
int qOrder);
18+
Eigen::Ref<IndexVectorX const> const& eg,
19+
Eigen::Ref<MatrixX const> const& wg,
20+
Eigen::Ref<MatrixX const> const& GNeg,
21+
int dims);
2222

2323
Laplacian(Laplacian const&) = delete;
2424
Laplacian& operator=(Laplacian const&) = delete;
@@ -40,9 +40,6 @@ class Laplacian
4040
int mMeshDims;
4141
int mMeshOrder;
4242
int mOrder;
43-
int mQuadratureOrder;
44-
45-
static auto constexpr kMaxQuadratureOrder = 4;
4643

4744
private:
4845
void* mLaplacian;
@@ -56,58 +53,52 @@ void BindLaplacian(pybind11::module& m)
5653
.def(
5754
pyb::init<
5855
Mesh const&,
56+
Eigen::Ref<IndexVectorX const> const&,
5957
Eigen::Ref<MatrixX const> const&,
6058
Eigen::Ref<MatrixX const> const&,
61-
int,
6259
int>(),
6360
pyb::arg("mesh"),
64-
pyb::arg("detJe"),
61+
pyb::arg("eg"),
62+
pyb::arg("wg"),
6563
pyb::arg("GNe"),
66-
pyb::arg("dims") = 1,
67-
pyb::arg("quadrature_order") = 1,
64+
pyb::arg("dims") = 1,
6865
"Construct the symmetric part of the Laplacian operator on mesh mesh, using "
69-
"precomputed jacobian determinants detJe and shape function gradients GNe evaluated at "
70-
"quadrature points given by the quadrature rule of order quadrature_order. The "
71-
"discretization is based on Galerkin projection. The dimensions dims can be set to "
72-
"accommodate vector-valued functions.")
66+
"precomputed shape function gradients GNeg evaluated at quadrature points g at "
67+
"elements eg with weights wg. The discretization is based on Galerkin projection. The "
68+
"dimensions dims can be set to accommodate vector-valued functions.")
7369
.def_property(
7470
"dims",
7571
[](Laplacian const& L) { return L.dims(); },
7672
[](Laplacian& L, int dims) { L.dims() = dims; })
7773
.def_readonly("order", &Laplacian::mOrder)
78-
.def_readonly("quadrature_order", &Laplacian::mQuadratureOrder)
7974
.def_property(
80-
"deltaE",
75+
"deltag",
8176
[](Laplacian const& L) { return L.ElementLaplacians(); },
82-
[](Laplacian& L, Eigen::Ref<MatrixX const> const& deltaE) {
83-
L.ElementLaplacians() = deltaE;
77+
[](Laplacian& L, Eigen::Ref<MatrixX const> const& deltag) {
78+
L.ElementLaplacians() = deltag;
8479
},
85-
"|#element nodes|x|#element nodes * #elements| matrix of element Laplacians")
80+
"|#element nodes|x|#element nodes * #quad.pts.| matrix of element Laplacians")
8681
.def_property_readonly("shape", &Laplacian::Shape)
8782
.def("to_matrix", &Laplacian::ToMatrix);
8883
}
8984

9085
Laplacian::Laplacian(
9186
Mesh const& M,
92-
Eigen::Ref<MatrixX const> const& detJe,
87+
Eigen::Ref<IndexVectorX const> const& eg,
88+
Eigen::Ref<MatrixX const> const& wg,
9389
Eigen::Ref<MatrixX const> const& GNe,
94-
int dims,
95-
int qOrder)
90+
int dims)
9691
: eMeshElement(M.eElement),
9792
mMeshDims(M.mDims),
9893
mMeshOrder(M.mOrder),
9994
mOrder(),
100-
mQuadratureOrder(),
10195
mLaplacian(nullptr)
10296
{
103-
M.ApplyWithQuadrature<kMaxQuadratureOrder>(
104-
[&]<pbat::fem::CMesh MeshType, auto QuadratureOrder>(MeshType* mesh) {
105-
using LaplacianType = pbat::fem::SymmetricLaplacianMatrix<MeshType, QuadratureOrder>;
106-
mLaplacian = new LaplacianType(*mesh, detJe, GNe, dims);
107-
mOrder = LaplacianType::kOrder;
108-
mQuadratureOrder = LaplacianType::kQuadratureOrder;
109-
},
110-
qOrder);
97+
M.Apply([&]<pbat::fem::CMesh MeshType>(MeshType* mesh) {
98+
using LaplacianType = pbat::fem::SymmetricLaplacianMatrix<MeshType>;
99+
mLaplacian = new LaplacianType(*mesh, eg, wg, GNe, dims);
100+
mOrder = LaplacianType::kOrder;
101+
});
111102
}
112103

113104
CSCMatrix Laplacian::ToMatrix() const
@@ -129,20 +120,20 @@ std::tuple<Index, Index> Laplacian::Shape() const
129120

130121
MatrixX const& Laplacian::ElementLaplacians() const
131122
{
132-
MatrixX* deltaEPtr;
123+
MatrixX* deltagPtr;
133124
Apply([&]<class LaplacianType>(LaplacianType* laplacian) {
134-
deltaEPtr = std::addressof(laplacian->deltaE);
125+
deltagPtr = std::addressof(laplacian->deltag);
135126
});
136-
return *deltaEPtr;
127+
return *deltagPtr;
137128
}
138129

139130
MatrixX& Laplacian::ElementLaplacians()
140131
{
141-
MatrixX* deltaEPtr;
132+
MatrixX* deltagPtr;
142133
Apply([&]<class LaplacianType>(LaplacianType* laplacian) {
143-
deltaEPtr = std::addressof(laplacian->deltaE);
134+
deltagPtr = std::addressof(laplacian->deltag);
144135
});
145-
return *deltaEPtr;
136+
return *deltagPtr;
146137
}
147138

148139
int const& Laplacian::dims() const
@@ -172,16 +163,11 @@ Laplacian::~Laplacian()
172163
template <class Func>
173164
void Laplacian::Apply(Func&& f) const
174165
{
175-
ApplyToMeshWithQuadrature<kMaxQuadratureOrder>(
176-
mMeshDims,
177-
mMeshOrder,
178-
eMeshElement,
179-
mQuadratureOrder,
180-
[&]<pbat::fem::CMesh MeshType, auto QuadratureOrder>() {
181-
using LaplacianType = pbat::fem::SymmetricLaplacianMatrix<MeshType, QuadratureOrder>;
182-
LaplacianType* laplacian = reinterpret_cast<LaplacianType*>(mLaplacian);
183-
f.template operator()<LaplacianType>(laplacian);
184-
});
166+
ApplyToMesh(mMeshDims, mMeshOrder, eMeshElement, [&]<pbat::fem::CMesh MeshType>() {
167+
using LaplacianType = pbat::fem::SymmetricLaplacianMatrix<MeshType>;
168+
LaplacianType* laplacian = reinterpret_cast<LaplacianType*>(mLaplacian);
169+
f.template operator()<LaplacianType>(laplacian);
170+
});
185171
}
186172

187173
} // namespace fem

python/examples/diffusion_smoothing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# Construct Galerkin laplacian, mass and gradient operators
2323
mesh = pbat.fem.Mesh(
2424
V.T, F.T, element=pbat.fem.Element.Triangle, order=1)
25-
L, detJeL, GNeL = pbat.fem.laplacian(mesh)
25+
L, egL, wgL, GNegL = pbat.fem.laplacian(mesh)
2626
M, detJeM = pbat.fem.mass_matrix(mesh, dims=1)
2727
# Setup heat diffusion
2828
dt = 0.016

python/examples/laplace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def harmonic_field(V: np.ndarray, C: np.ndarray, order: int, eps: float = 0.1):
1111
mesh = pbat.fem.Mesh(
1212
V.T, C.T, element=pbat.fem.Element.Tetrahedron, order=order)
1313
quadrature_order = max(int(2*(order-1)), 1)
14-
L, detJeL, GNeL = pbat.fem.laplacian(
14+
L, egL, wgL, GNegL = pbat.fem.laplacian(
1515
mesh, quadrature_order=quadrature_order)
1616
# Set Dirichlet boundary conditions at bottom and top of the model
1717
Xmin = mesh.X.min(axis=1)

python/pbatoolkit/fem.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ def divergence(mesh, quadrature_order: int = 1, eg: np.ndarray = None, wg: np.nd
3131
Args:
3232
mesh (pbat.fem.Mesh):
3333
quadrature_order (int, optional): Polynomial order to use for operator construction. Defaults to 1.
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.
34+
eg (np.ndarray, optional): |#quad.pts.| array of elements corresponding to quadrature points. Defaults to None.
35+
wg (np.ndarray, optional): |#quad.pts.| array of quadrature weights. Defaults to None.
3636
GNeg (np.ndarray, optional): |#elem. nodes|x|#dims * #quad.pts.| array of shape function gradients at quadrature points. Defaults to None.
3737
3838
Returns:
39-
(scipy.sparse.csc_matrix, np.ndarray):
39+
(scipy.sparse.csc_matrix, np.ndarray, np.ndarray):
4040
"""
4141
should_compute_quadrature = eg is None or wg is None or GNeg is None
4242
G, eg, GNeg = gradient(mesh, quadrature_order=quadrature_order, as_matrix=True) if should_compute_quadrature else gradient(
@@ -60,7 +60,7 @@ def gradient(mesh, quadrature_order: int = 1, eg: np.ndarray = None, GNeg: np.nd
6060
as_matrix (bool, optional): Return the operator as a sparse matrix. Defaults to True.
6161
6262
Returns:
63-
(scipy.sparse.csc_matrix | pbat.fem.Gradient, np.ndarray):
63+
(scipy.sparse.csc_matrix | pbat.fem.Gradient, np.ndarray, np.ndarray):
6464
"""
6565

6666
if GNeg is None:
@@ -100,7 +100,7 @@ def hyper_elastic_potential(
100100
GNeg (np.ndarray, optional): Shape function gradients at quadrature points. Defaults to None.
101101
102102
Returns:
103-
(pbat.fem.HyperElasticPotential, np.ndarray, np.ndarray):
103+
(pbat.fem.HyperElasticPotential, np.ndarray, np.ndarray, np.ndarray):
104104
"""
105105
if eg is None or wg is None or GNeg is None:
106106
wg = _fem.inner_product_weights(
@@ -122,32 +122,37 @@ def laplacian(
122122
mesh,
123123
dims: int = 1,
124124
quadrature_order: int = 1,
125-
detJe: np.ndarray = None,
126-
GNe: np.ndarray = None,
125+
eg: np.ndarray = None,
126+
wg: np.ndarray = None,
127+
GNeg: np.ndarray = None,
127128
as_matrix: bool = True):
128129
"""Construct an FEM Laplacian operator
129130
130131
Args:
131132
mesh (pbat.fem.Mesh):
132133
dims (int, optional): Solution space dimensions. Defaults to 1.
133134
quadrature_order (int, optional): Polynomial order to use for operator construction. Defaults to 1.
134-
detJe (np.ndarray, optional): Jacobian determinants at quadrature points. Defaults to None.
135-
GNe (np.ndarray, optional): Shape function gradients at quadrature points. Defaults to None.
135+
eg (np.ndarray, optional): |#quad.pts.| array of elements associated with quadrature points. Defaults to None.
136+
wg (np.ndarray, optional): |#quad.pts.| array of quadrature weights. Defaults to None.
137+
GNeg (np.ndarray, optional): Shape function gradients at quadrature points. Defaults to None.
136138
as_matrix (bool, optional): Return the operator as a sparse matrix. Defaults to True.
137139
138140
Returns:
139-
(pbat.fem.Laplacian | scipy.sparse.csc_matrix, np.ndarray, np.ndarray):
141+
(pbat.fem.Laplacian | scipy.sparse.csc_matrix, np.ndarray, np.ndarray, np.ndarray):
140142
"""
141-
if detJe is None:
142-
detJe = _fem.jacobian_determinants(
143+
if eg is None or wg is None or GNeg is None:
144+
wg = _fem.inner_product_weights(
143145
mesh, quadrature_order=quadrature_order)
144-
if GNe is None:
145-
GNe = _fem.shape_function_gradients(
146+
eg = np.linspace(0, mesh.E.shape[1]-1,
147+
num=mesh.E.shape[1], dtype=np.int64)
148+
eg = np.repeat(eg, wg.shape[0])
149+
wg = wg.flatten(order="F")
150+
GNeg = _fem.shape_function_gradients(
146151
mesh, quadrature_order=quadrature_order)
147-
L = _fem.Laplacian(mesh, detJe, GNe, dims=dims,
152+
L = _fem.Laplacian(mesh, eg, wg, GNeg, dims=dims,
148153
quadrature_order=quadrature_order)
149154
L = L.to_matrix() if as_matrix else L
150-
return L, detJe, GNe
155+
return L, eg, wg, GNeg
151156

152157

153158
def mass_matrix(

source/pbat/fem/Gradient.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ TEST_CASE("[fem] Gradient")
8686
CSCMatrix Lhat = -GMT * Ihat.asDiagonal() * GM;
8787
VectorX const LhatValues = Lhat.coeffs();
8888
Lhat.coeffs().setOnes();
89-
using LaplacianType = fem::SymmetricLaplacianMatrix<Mesh, kQuadratureOrder>;
90-
MatrixX const detJe = fem::DeterminantOfJacobian<kQuadratureOrder>(mesh);
91-
CSCMatrix L = LaplacianType(mesh, detJe, GNe).ToMatrix();
89+
using LaplacianType = fem::SymmetricLaplacianMatrix<Mesh>;
90+
VectorX const wg = fem::InnerProductWeights<kQuadratureOrder>(mesh).reshaped();
91+
CSCMatrix L = LaplacianType(mesh, eg, wg, GNe).ToMatrix();
9292
VectorX const Lvalues = L.coeffs();
9393
L.coeffs().setOnes();
9494
Scalar const LsparsityError = (L - Lhat).squaredNorm();

source/pbat/fem/HyperElasticPotential.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,10 @@ TEST_CASE("[fem] HyperElasticPotential")
4747

4848
MeshType const M(V, C);
4949
VectorX const x = M.X.reshaped();
50-
MatrixX const WG = fem::InnerProductWeights<kQuadratureOrder>(M);
51-
VectorX const wg = WG.reshaped();
50+
MatrixX const wg = fem::InnerProductWeights<kQuadratureOrder>(M).reshaped();
5251
MatrixX const GNeg = fem::ShapeFunctionGradients<kQuadratureOrder>(M);
5352
IndexVectorX const eg = IndexVectorX::LinSpaced(M.E.cols(), Index(0), M.E.cols() - 1)
54-
.replicate(1, WG.rows())
53+
.replicate(1, wg.size() / M.E.cols())
5554
.transpose()
5655
.reshaped();
5756
ElasticPotentialType U(M, eg, wg, GNeg, x, Y, nu);

source/pbat/fem/LaplacianMatrix.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,16 @@ TEST_CASE("[fem] LaplacianMatrix")
4444
return (kOrder - 1) + (kOrder - 1);
4545
}();
4646

47-
using LaplacianMatrix = fem::SymmetricLaplacianMatrix<Mesh, kQuadratureOrder>;
48-
MatrixX const detJe = fem::DeterminantOfJacobian<kQuadratureOrder>(mesh);
49-
MatrixX const GNe = fem::ShapeFunctionGradients<kQuadratureOrder>(mesh);
47+
using LaplacianMatrix = fem::SymmetricLaplacianMatrix<Mesh>;
48+
VectorX const wg = fem::InnerProductWeights<kQuadratureOrder>(mesh).reshaped();
49+
IndexVectorX const eg =
50+
IndexVectorX::LinSpaced(mesh.E.cols(), Index(0), mesh.E.cols() - 1)
51+
.replicate(1, wg.size() / mesh.E.cols())
52+
.transpose()
53+
.reshaped();
54+
MatrixX const GNeg = fem::ShapeFunctionGradients<kQuadratureOrder>(mesh);
5055
CHECK(math::CLinearOperator<LaplacianMatrix>);
51-
LaplacianMatrix matrixFreeLaplacian(mesh, detJe, GNe, outDims);
56+
LaplacianMatrix matrixFreeLaplacian(mesh, eg, wg, GNeg, outDims);
5257

5358
CSCMatrix const L = matrixFreeLaplacian.ToMatrix();
5459
CHECK_EQ(L.rows(), n);

0 commit comments

Comments
 (0)