User manual

Fortran module cubatures: the derived type cubature, which holds the abscissae and weights of a Gaussian integration rule on one of the six reference elements of the finite element method (line, triangle, quadrilateral, tetrahedron, hexahedron, wedge). Rules are chosen by degree of exactness, with no upper limit, and with separate degrees per direction on tensor-product elements. Fortran 2018, fpm or make. This file is the complete API reference for tools and coding agents; it is checked against the source in CI.

Use: use cubatures (everything) or use cubatures, only: cubature, CUB_TRI, .... fpm dependency: fortran_cubatures = { git = "https://github.com/willklausler/fortran_cubatures", tag = "v0.2.0" } Kinds are not exported: abscissae and weights are real64 from iso_fortran_env.

Conventions (read first)

  • A rule approximates the integral over the reference element by sum(q%weights * f(q%abscissae)), where column k of q%abscissae is point k. The integral over a physical element needs the Jacobian determinant of the map at each point.
  • Reference elements and their measures (the sum of the weights):
Constant Element Reference domain Measure
CUB_LIN line [-1, 1] 2
CUB_TRI triangle x, y >= 0, x + y <= 1 1/2
CUB_QUA quadrilateral [-1, 1]^2 4
CUB_TET tetrahedron x, y, z >= 0, x + y + z <= 1 1/6
CUB_HEX hexahedron [-1, 1]^3 8
CUB_WED wedge (prism) triangle x [-1, 1] 1
  • Degree of exactness p: the rule integrates exactly
  • triangle, tetrahedron: every polynomial of total degree <= p;
  • line, quadrilateral, hexahedron: every polynomial of degree <= p_i in each coordinate i (this includes total degree <= min p_i);
  • wedge: total degree <= p_1 in (x, y) times degree <= p_2 in z.
  • degree is one integer (the same in every direction) or an array of ndeg integers: 1 for line, triangle and tetrahedron, 2 for quadrilateral ([px, py]) and wedge ([ptri, pz]), 3 for hexahedron ([px, py, pz]).
  • Every rule has positive weights and abscissae strictly inside the element.
  • Triangle and tetrahedron abscissae are Cartesian coordinates on the unit simplex, which equal the leading barycentric coordinates; the last barycentric coordinate is 1 - sum(xi).
  • Point order: tensor-product rules vary the first coordinate fastest, and the Gauss points of each direction run from +1 down to -1. Wedge rules vary the triangle point fastest and the axial point slowest.
  • Error policy: an invalid element type, a negative degree or a degree array of the wrong size stops with error stop and a message that starts with cubature%set:. Errors cannot be caught. The only other stop, "Gauss-Jacobi iteration did not converge", guards against an internal fault; it has not occurred up to 1000 points per direction.
  • No module state; every procedure except summary and show is pure.

Types and constructors

Call Result
type(cubature) an unset rule: elm = 0, arrays not allocated, is_valid() false
cubature(elm, p) rule of degree p (integer) in every direction on element elm
cubature(elm, [p1, p2, ...]) rule with one degree per direction; size 1 or ndeg (see Conventions)
CUB_LIN, CUB_TRI, CUB_QUA, CUB_TET, CUB_HEX, CUB_WED integer element constants 1 to 6, usable as array indices

Assignment between cubatures is intrinsic (deep copy).

Components

All public, read them directly; set them only through the constructor or set.

Component Meaning
q%elm element type, a CUB_* constant; 0 if unset
q%dim spatial dimension, 1 to 3
q%degree(3) degrees of exactness, padded with 0 ([4, 2, 0] for a wedge)
q%npoints number of points
q%abscissae(dim, npoints) real64 point coordinates in the reference element
q%weights(npoints) real64 weights, all > 0

Type-bound procedures

Call Result
call q%set(elm, p) builds the rule in place, like the constructor; reuses q for any element
q%is_valid() logical: the rule is set and its array shapes match dim and npoints
call q%summary([unit]) writes element, dimension, degrees and number of points (default output_unit)
call q%show([unit]) writes the summary, one line per point with its abscissae and weight, and the weight sum
call q%destroy() deallocates and resets to the unset state

Rules and point counts

n(p) = p/2 + 1 (integer division) is the number of Gauss points that integrate degree p in one direction.

Element Rule Points
line, quadrilateral, hexahedron tensor-product Gauss-Legendre n(px)·n(py)·n(pz)
wedge triangle rule x Gauss-Legendre T(ptri)·n(pz)
triangle see below T(p)
tetrahedron see below see below
p Triangle points T(p) Triangle rule Tetrahedron points Tetrahedron rule
0, 1 1 centroid 1 centroid
2 3 symmetric, interior (Zienkiewicz et al.) 4 symmetric (Zienkiewicz et al.)
3 4 collapsed Gauss-Jacobi 8 collapsed Gauss-Jacobi
4 6 symmetric (Dunavant) 27 collapsed Gauss-Jacobi
5 7 symmetric (Radon) 27 collapsed Gauss-Jacobi
>= 6 n(p)^2 collapsed Gauss-Jacobi n(p)^3 collapsed Gauss-Jacobi

Collapsed (conical product) rules map the unit square or cube onto the simplex and absorb the Jacobian of that map in Gauss-Jacobi weights. Gauss points are computed at run time by Newton iteration on Legendre and Jacobi polynomials, so any degree works; cost grows like the number of points.

Choosing a degree: for shape functions of order k on an element with an affine map, the mass matrix needs 2k and the stiffness matrix 2k - 2. A bilinear quadrilateral or trilinear hexahedron mass matrix needs degree 3 per direction because det(J) is not constant. Curved elements and non-polynomial integrands need more.

Compiler notes

Tested in CI with gfortran 14 (Linux, macOS, Windows), gfortran 13 (Linux), ifx 2026 (Linux, Windows), LLVM flang 21 and nvfortran 26 (Linux). No compiler-specific code and no known compiler limitations.

Differs from

  • stdlib_quadrature (fortran-lang/stdlib), QUADPACK and GaussJacobiQuad: one-dimensional rules only; here a rule is a single object for any FE element.
  • quadrature-fortran (jacobwilliams): adaptive integration of a user function over boxes; it returns an integral, not points and weights, and has no simplex elements.
  • triasymq (Gimbutas and Xiao): tabulated, more efficient symmetric rules of high degree on triangles, squares, cubes and tetrahedra, in FORTRAN 77; no lines or wedges. For high-degree simplex rules it uses fewer points than the collapsed rules here.
  • FE codes such as deal.II or FEniCS use the same reference elements but other coordinates for some of them (for example [0, 1] instead of [-1, 1]); map the abscissae if you mix them.
  • Tabulated symmetric simplex rules (Dunavant for triangles, Keast for tetrahedra) need fewer points than the n(p)^2 and n(p)^3 of the collapsed rules here, except at the degrees this package tabulates. The collapsed rules exist for every degree and always have positive weights and interior points, which not every tabulated rule has. If the point count of a simplex rule matters, compare with the table in "Rules and point counts".

Module layout

One module, cubatures, in src/cubatures.f90. The specific procedures behind the constructor and set (new_iso, new_aniso, set_iso, set_aniso) are private; call them through the generic names.

Examples

These are the programs in example/, compiled in CI.

program demo_rule
  !! Build rules, read their points and weights, and keep one rule per element type
  use, intrinsic :: iso_fortran_env, only: dp => real64
  use cubatures
  implicit none
  integer, parameter :: mesh(*) = [CUB_TET, CUB_HEX, CUB_WED, CUB_TET]
  type(cubature) :: q, rules(6)
  real(dp) :: measure
  integer :: e

  q = cubature(CUB_TRI, 2)                   ! exact for total degree <= 2
  call q%show()                              ! 3 points and weights, sum 0.5
  print *, q%dim, q%npoints, q%is_valid()    ! 2 3 T

  call q%set(CUB_HEX, [1, 3, 5])             ! reuse q; one degree per direction
  print *, q%degree, q%npoints               ! 1 3 5 6: 1 x 2 x 3 points
  print *, q%abscissae(:, 2)                 ! 0 -0.577 0.775: first coordinate fastest

  ! One rule per element type, indexed by the CUB_* constants
  rules(CUB_TET) = cubature(CUB_TET, 4)      ! 27 points
  rules(CUB_HEX) = cubature(CUB_HEX, 4)      ! 27 points
  rules(CUB_WED) = cubature(CUB_WED, [4, 2]) ! 6 x 2 = 12 points
  do e = 1, size(mesh)
    associate (r => rules(mesh(e)))
      measure = sum(r%weights)               ! 1/6, 8, 1, 1/6
      print '(a, i0, a, i3, a, f9.6)', 'element ', e, ':', r%npoints, ' points, measure', measure
    end associate
  end do
end program demo_rule
program demo_triangle
  !! Integrate over a physical triangle through the affine map from the reference triangle
  use, intrinsic :: iso_fortran_env, only: dp => real64
  use cubatures
  implicit none
  real(dp), parameter :: xv(2,3) = reshape([1._dp, 0._dp, &
                                            4._dp, 1._dp, &
                                            2._dp, 3._dp], [2, 3])  ! vertices
  type(cubature) :: q
  real(dp) :: jac(2,2), detj, x(2), total, exact
  integer :: k

  q = cubature(CUB_TRI, 2)                   ! x*y has degree 2 after an affine map
  jac(:,1) = xv(:,2) - xv(:,1)               ! x = xv(:,1) + jac*xi
  jac(:,2) = xv(:,3) - xv(:,1)               ! det(jac) = 8 = twice the area
  detj = jac(1,1)*jac(2,2) - jac(1,2)*jac(2,1)

  total = 0
  do k = 1, q%npoints
    x = xv(:,1) + matmul(jac, q%abscissae(:,k))
    total = total + x(1)*x(2)*detj*q%weights(k)
  end do

  ! Closed form: area/12*(sum(x_k*y_k) + sum(x_k)*sum(y_k))
  exact = detj/24*(sum(xv(1,:)*xv(2,:)) + sum(xv(1,:))*sum(xv(2,:)))
  print *, total, exact                      ! 12.666666666666664 12.666666666666666
end program demo_triangle
program demo_mass_matrix
  !! Consistent mass matrix of a distorted bilinear quadrilateral (isoparametric map)
  use, intrinsic :: iso_fortran_env, only: dp => real64
  use cubatures
  implicit none
  real(dp), parameter :: xn(2,4) = reshape([0._dp, 0._dp, &
                                            2._dp, 0._dp, &
                                            2.5_dp, 1.5_dp, &
                                            0._dp, 1._dp], [2, 4])  ! nodes
  real(dp), parameter :: sn(2,4) = reshape([-1, -1, 1, -1, 1, 1, -1, 1], [2, 4])  ! reference nodes
  type(cubature) :: q
  real(dp) :: n(4), dn(4,2), jac(2,2), detj, m(4,4)
  integer :: k

  q = cubature(CUB_QUA, 3)                   ! N_i*N_j*det(J) has degree 3 per direction
  m = 0
  do k = 1, q%npoints                        ! 2 x 2 Gauss points
    associate (xi => q%abscissae(:,k))
      n = (1 + sn(1,:)*xi(1))*(1 + sn(2,:)*xi(2))/4
      dn(:,1) = sn(1,:)*(1 + sn(2,:)*xi(2))/4
      dn(:,2) = sn(2,:)*(1 + sn(1,:)*xi(1))/4
    end associate
    jac = matmul(xn, dn)
    detj = jac(1,1)*jac(2,2) - jac(1,2)*jac(2,1)
    m = m + spread(n, 2, 4)*spread(n, 1, 4)*detj*q%weights(k)
  end do

  print '(4f10.6)', m                        ! one column per line; M is symmetric
  print *, sum(m)                            ! 2.75: the area of the element
end program demo_mass_matrix