libMesh
quadrature_nodal_3D.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 
20 // Local includes
21 #include "libmesh/quadrature_nodal.h"
22 #include "libmesh/quadrature_trap.h"
23 #include "libmesh/quadrature_simpson.h"
24 #include "libmesh/enum_to_string.h"
25 #include "libmesh/cell_c0polyhedron.h"
26 
27 namespace libMesh
28 {
29 
31 {
32 #if LIBMESH_DIM == 3
33 
34  switch (_type)
35  {
36  case TET4:
37  case PRISM6:
38  case HEX8:
39  case PYRAMID5:
40  {
41  // Construct QTrap rule that matches our own nodal pyramid quadrature permissions
42  QTrap rule(/*dim=*/3, /*ignored*/_order);
44  rule.init(*this);
45  _points.swap (rule.get_points());
46  _weights.swap(rule.get_weights());
47  return;
48  }
49 
50  case PRISM15:
51  {
52  // A rule with 15 points which is exact for linears, and
53  // naturally produces a lumped approximation to the mass
54  // matrix. The quadrature points are numbered the same way as
55  // the reference element nodes.
56  _points =
57  {
58  Point(0.,0.,-1), Point(+1,0.,-1), Point(0.,+1,-1),
59  Point(0.,0.,+1), Point(+1,0.,+1), Point(0.,+1,+1),
60  Point(.5,0.,-1), Point(.5,.5,-1), Point(0.,.5,-1),
61  Point(0.,0.,0.), Point(+1,0.,0.), Point(0.,+1,0.),
62  Point(.5,0.,+1), Point(.5,.5,+1), Point(0.,.5,+1),
63  };
64 
65  // vertex (wv), tri edge (wt), and quad edge (wq) weights are
66  // obtained using the same approach that was used for the Quad8,
67  // see quadrature_nodal_2D.C for details.
68  Real wv = Real(1) / 34;
69  Real wt = Real(4) / 51;
70  Real wq = Real(2) / 17;
71 
72  _weights = {wv, wv, wv, wv, wv, wv,
73  wt, wt, wt,
74  wq, wq, wq,
75  wt, wt, wt};
76 
77  return;
78  }
79 
80  case HEX20:
81  {
82  // A rule with 20 points which is exact for linears, and
83  // naturally produces a lumped approximation to the mass
84  // matrix. The quadrature points are numbered the same way as
85  // the reference element nodes.
86  _points =
87  {
88  Point(-1,-1,-1), Point(+1,-1,-1), Point(+1,+1,-1), Point(-1,+1,-1),
89  Point(-1,-1,+1), Point(+1,-1,+1), Point(+1,+1,+1), Point(-1,+1,+1),
90  Point(0.,-1,-1), Point(+1,0.,-1), Point(0.,+1,-1), Point(-1,0.,-1),
91  Point(-1,-1,0.), Point(+1,-1,0.), Point(+1,+1,0.), Point(-1,+1,0.),
92  Point(0.,-1,+1), Point(+1,0.,+1), Point(0.,+1,+1), Point(-1,0.,+1)
93  };
94 
95  // vertex (wv), and edge (we) weights are obtained using the
96  // same approach that was used for the Quad8, see
97  // quadrature_nodal_2D.C for details.
98  Real wv = Real(7) / 31;
99  Real we = Real(16) / 31;
100 
101  _weights = {wv, wv, wv, wv, wv, wv, wv, wv,
102  we, we, we, we, we, we, we, we, we, we, we, we};
103 
104  return;
105  }
106 
107  case TET10:
108  case PRISM18:
109  case HEX27:
110  case PYRAMID13:
111  case PYRAMID14:
112  {
113  // Construct QSimpson rule that matches our own nodal pyramid quadrature permissions
114  QSimpson rule(/*dim=*/3, /*ignored*/_order);
116  rule.init(*this);
117  _points.swap (rule.get_points());
118  _weights.swap(rule.get_weights());
119 
120  // We can't do a proper Simpson rule for pyramids regardless
121  if (_type == PYRAMID13)
122  {
123  _points.resize(13);
124  _weights.resize(13);
125  }
126 
127  return;
128  }
129 
130  case PRISM20:
131  {
132  _points =
133  {
134  Point(0.,0.,-1), Point(+1,0.,-1), Point(0.,+1,-1),
135  Point(0.,0.,+1), Point(+1,0.,+1), Point(0.,+1,+1),
136  Point(.5,0.,-1), Point(.5,.5,-1), Point(0.,.5,-1),
137  Point(0.,0.,0.), Point(+1,0.,0.), Point(0.,+1,0.),
138  Point(.5,0.,+1), Point(.5,.5,+1), Point(0.,.5,+1),
139  Point(.5,0.,0.), Point(.5,.5,0.), Point(0.,.5,0.),
140  Point(1/Real(3),1/Real(3),-1), Point(1/Real(3),1/Real(3),+1)
141  };
142 
143  // Symmetry gives us identical weights on vertices, triangle
144  // edges, square edges, triangle faces, square faces; then we
145  // have the midnode. Solving for weights which exactly
146  // integrate cubics and xi^2*zeta^2 would give a unique answer
147  // ... with wse=0 and wte<0. See Quad8 in
148  // quadrature_nodal_2D.C for discussion of this problem.
149  //
150  // Dropping the xi^2 zeta^2 constraint gives us a rank-1 null
151  // space to play with ... but adding anything from that null
152  // space to push wte closer to positive just pushes wse
153  // negative.
154  //
155  // Dropping exact integration of xi^3 type terms gives us a
156  // rank-2 null space. I just found the max(min(weight)) from
157  // that solution space using Octave. Someone who cares more
158  // than me might want to repeat this exercise with better than
159  // double precision...
160 
161  constexpr Real wv = 8.546754839782711e-03;
162  constexpr Real wte = 8.548403936750599e-03;
163  constexpr Real wse = wv; // here's our min(weight) constraint...
164  constexpr Real wtf = 1.153811903370667e-01;
165  constexpr Real wsf = 2.136754673824396e-01;
166 
167  _weights = {wv, wv, wv, wv, wv, wv,
168  wte, wte, wte, wse, wse, wse, wte, wte, wte,
169  wsf, wsf, wsf, wtf, wtf};
170 
171  return;
172  }
173 
174  case PRISM21:
175  {
176  _points =
177  {
178  Point(0.,0.,-1), Point(+1,0.,-1), Point(0.,+1,-1),
179  Point(0.,0.,+1), Point(+1,0.,+1), Point(0.,+1,+1),
180  Point(.5,0.,-1), Point(.5,.5,-1), Point(0.,.5,-1),
181  Point(0.,0.,0.), Point(+1,0.,0.), Point(0.,+1,0.),
182  Point(.5,0.,+1), Point(.5,.5,+1), Point(0.,.5,+1),
183  Point(.5,0.,0.), Point(.5,.5,0.), Point(0.,.5,0.),
184  Point(1/Real(3),1/Real(3),-1), Point(1/Real(3),1/Real(3),+1),
185  Point(1/Real(3),1/Real(3),0.)
186  };
187 
188  // Symmetry gives us identical weights on vertices, triangle
189  // edges, square edges, triangle faces, square faces; then we
190  // have the midnode. Solving for weights which exactly
191  // integrate cubics, xi^2*zeta^2, and xi^3*zeta^2, gives a
192  // unique answer:
193  constexpr Real wv = Real(1)/120;
194  constexpr Real wte = Real(1)/45;
195  constexpr Real wse = Real(1)/30;
196  constexpr Real wtf = Real(3)/40;
197  constexpr Real wsf = Real(4)/45;
198 
199  _weights = {wv, wv, wv, wv, wv, wv,
200  wte, wte, wte, wse, wse, wse, wte, wte, wte,
201  wsf, wsf, wsf, wtf, wtf, Real(3)/10};
202 
203  return;
204  }
205 
206  case PYRAMID18:
207  {
208  libmesh_error_msg_if(!allow_nodal_pyramid_quadrature,
209  "Nodal quadrature on Pyramid elements is not allowed by default since\n"
210  "the Jacobian of the inverse element map is not well-defined at the Pyramid apex.\n"
211  "Set the QBase::allow_nodal_pyramid_quadrature flag to true to ignore skip this check.");
212 
213  _points =
214  {
215  Point(-1,-1,0.), Point(+1,-1,0.), Point(+1,+1,0.),
216  Point(-1,+1,0.), Point(0.,0.,+1), Point(0.,-1,0.),
217  Point(+1,0.,0.), Point(0.,+1,0.), Point(-1,0.,0.),
218  Point(-.5,-.5,.5), Point(-.5,.5,.5), Point(.5,.5,.5),
219  Point(-.5,.5,.5), Point(0.,0.,0.), Point(0.,-2/Real(3),1/Real(3)),
220  Point(2/Real(3),0.,1/Real(3)), Point(0.,2/Real(3),1/Real(3)),
221  Point(-2/Real(3),0.,1/Real(3))
222  };
223 
224  // Even with triangle faces to play with, I can't seem to get
225  // exact integrals of any higher order functions without
226  // negative weights. So I punt and just use QTrap weights.
227  _weights = {1/Real(4), 1/Real(4), 1/Real(4), 1/Real(4), 1/Real(3),
228  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
229 
230  return;
231  }
232 
233  case TET14:
234  {
235  _points.resize(14);
236  _weights.resize(14);
237 
238  _points[0](0) = 0.; _points[5](0) = .5;
239  _points[0](1) = 0.; _points[5](1) = .5;
240  _points[0](2) = 0.; _points[5](2) = 0.;
241 
242  _points[1](0) = 1.; _points[6](0) = 0.;
243  _points[1](1) = 0.; _points[6](1) = .5;
244  _points[1](2) = 0.; _points[6](2) = 0.;
245 
246  _points[2](0) = 0.; _points[7](0) = 0.;
247  _points[2](1) = 1.; _points[7](1) = 0.;
248  _points[2](2) = 0.; _points[7](2) = .5;
249 
250  _points[3](0) = 0.; _points[8](0) = .5;
251  _points[3](1) = 0.; _points[8](1) = 0.;
252  _points[3](2) = 1.; _points[8](2) = .5;
253 
254  _points[4](0) = .5; _points[9](0) = 0.;
255  _points[4](1) = 0.; _points[9](1) = .5;
256  _points[4](2) = 0.; _points[9](2) = .5;
257 
258 
259  _points[10](0) = 1/Real(3); _points[11](0) = 1/Real(3);
260  _points[10](1) = 1/Real(3); _points[11](1) = 0.;
261  _points[10](2) = 0.; _points[11](2) = 1/Real(3);
262 
263  _points[12](0) = 1/Real(3); _points[13](0) = 0.;
264  _points[12](1) = 1/Real(3); _points[13](1) = 1/Real(3);
265  _points[12](2) = 1/Real(3); _points[13](2) = 1/Real(3);
266 
267  // RHS:
268  //
269  // The ``optimal'' nodal quadrature here, the one that
270  // integrates every Lagrange polynomial on these nodes
271  // exactly, produces an indefinite mass matrix ...
272  //
273  // const Real wv = 1/Real(240);
274  // const Real we = 0;
275  // const Real wf = 3/Real(80);
276 
277  // We could average that with our Tet10 rule and get:
278  //
279  // const Real wv = (1/Real(240)+1/Real(192))/2;
280  // const Real we = Real(14)/576/2;
281  // const Real wf = 3/Real(80)/2;
282  //
283  // Except our Tet10 rule won't actually exactly integrate
284  // quadratics! (exactly integrating quadratics wouldn't even
285  // have given a positive semidefinite mass matrix there...)
286  //
287  // John derived equations for wv and we based on symmetry and
288  // the requirement to exactly integrate quadratics; within
289  // those constraints we might pick the wf that maximizes the
290  // minimum nodal weight:
291  // const Real wf = Real(15)/440;
292  // const Real wv = -1/Real(120) + wf/3;
293  // const Real we = 1/Real(30) - wf*(Real(8)/9);
294  //
295  // But John also did the same clever optimization trick that
296  // quadrature_nodal_2D.C discusses in the context of Quad8 and
297  // Hex20 outputs, and for Tet14 that gives us:
298  const Real wv = Real(87)/47120;
299  const Real we = Real(164)/26505;
300  const Real wf = Real(1439)/47120;
301 
302  _weights = {wv, wv, wv, wv, we, we, we, we, we, we, wf, wf, wf, wf};
303  return;
304  }
305 
306  //---------------------------------------------
307  // Arbitrary polygon quadrature rules
308  case C0POLYHEDRON:
309  {
310  // Polyhedra require the newer Quadrature API
311  if (!_elem)
312  libmesh_error();
313 
315 
316  const C0Polyhedron & poly = *cast_ptr<const C0Polyhedron *>(_elem);
317 
318  const Real master_poly_volume = _elem->volume();
319 
320  // For polyhedra the master element is the physical element.
321  //
322  // Using even weights is not the most effective nodal rule for
323  // that case, but users who want an effective rule probably
324  // want a non-nodal rule anyway.
325  const unsigned int nn = poly.n_nodes();
326  const Real weight = master_poly_volume / nn;
327  _weights.resize(nn, weight);
328 
329  _points.resize(poly.n_nodes());
330  for (auto n : make_range(nn))
331  _points[n] = poly.master_point(n);
332 
333  return;
334  }
335 
336 
337  default:
338  libmesh_error_msg("ERROR: Unsupported type: " << Utility::enum_to_string(_type));
339  }
340 #endif
341 }
342 
343 } // namespace libMesh
This class implements Simpson quadrature.
ElemType _type
The type of element for which the current values have been computed.
Definition: quadrature.h:391
const std::vector< Real > & get_weights() const
Definition: quadrature.h:168
const Elem * _elem
The element for which the current values were computed, or nullptr if values were computed without a ...
Definition: quadrature.h:397
The libMesh namespace provides an interface to certain functionality in the library.
std::vector< Point > _points
The locations of the quadrature points in reference element space.
Definition: quadrature.h:409
std::vector< Real > _weights
The quadrature weights.
Definition: quadrature.h:415
virtual void init_3D() override
Initializes the 3D quadrature rule by filling the points and weights vectors with the appropriate val...
dof_id_type weight(const MeshBase &mesh, const processor_id_type pid)
Definition: mesh_tools.C:437
libmesh_assert(ctx)
std::string enum_to_string(const T e)
Order _order
The polynomial order which the quadrature rule is capable of integrating exactly. ...
Definition: quadrature.h:385
bool allow_nodal_pyramid_quadrature
The flag&#39;s value defaults to false so that one does not accidentally use a nodal quadrature rule on P...
Definition: quadrature.h:314
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const std::vector< Point > & get_points() const
Definition: quadrature.h:156
virtual Real volume() const
Definition: elem.C:3429
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
virtual void init(const Elem &e, unsigned int p_level=invalid_uint)
Initializes the data structures for a quadrature rule for the element e.
Definition: quadrature.C:65
The C0Polyhedron is an element in 3D with an arbitrary (but fixed) number of polygonal first-order (C...
virtual ElemType type() const =0
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
This class implements trapezoidal quadrature.