$$
\newcommand{\uex}{{u_{\small\mbox{e}}}}
\newcommand{\uexd}[1]{{u_{\small\mbox{e}, #1}}}
\newcommand{\vex}{{v_{\small\mbox{e}}}}
\newcommand{\vexd}[1]{{v_{\small\mbox{e}, #1}}}
\newcommand{\Aex}{{A_{\small\mbox{e}}}}
\newcommand{\half}{\frac{1}{2}}
\newcommand{\halfi}{{1/2}}
\newcommand{\tp}{\thinspace .}
\newcommand{\Ddt}[1]{\frac{D #1}{dt}}
\newcommand{\E}[1]{\hbox{E}\lbrack #1 \rbrack}
\newcommand{\Var}[1]{\hbox{Var}\lbrack #1 \rbrack}
\newcommand{\Std}[1]{\hbox{Std}\lbrack #1 \rbrack}
\newcommand{\xpoint}{\boldsymbol{x}}
\newcommand{\normalvec}{\boldsymbol{n}}
\newcommand{\Oof}[1]{\mathcal{O}(#1)}
\newcommand{\x}{\boldsymbol{x}}
\newcommand{\X}{\boldsymbol{X}}
\renewcommand{\u}{\boldsymbol{u}}
\renewcommand{\v}{\boldsymbol{v}}
\newcommand{\w}{\boldsymbol{w}}
\newcommand{\V}{\boldsymbol{V}}
\newcommand{\e}{\boldsymbol{e}}
\newcommand{\f}{\boldsymbol{f}}
\newcommand{\F}{\boldsymbol{F}}
\newcommand{\stress}{\boldsymbol{\sigma}}
\newcommand{\strain}{\boldsymbol{\varepsilon}}
\newcommand{\stressc}{{\sigma}}
\newcommand{\strainc}{{\varepsilon}}
\newcommand{\I}{\boldsymbol{I}}
\newcommand{\T}{\boldsymbol{T}}
\newcommand{\dfc}{\alpha} % diffusion coefficient
\newcommand{\ii}{\boldsymbol{i}}
\newcommand{\jj}{\boldsymbol{j}}
\newcommand{\kk}{\boldsymbol{k}}
\newcommand{\ir}{\boldsymbol{i}_r}
\newcommand{\ith}{\boldsymbol{i}_{\theta}}
\newcommand{\iz}{\boldsymbol{i}_z}
\newcommand{\Ix}{\mathcal{I}_x}
\newcommand{\Iy}{\mathcal{I}_y}
\newcommand{\Iz}{\mathcal{I}_z}
\newcommand{\It}{\mathcal{I}_t}
\newcommand{\If}{\mathcal{I}_s} % for FEM
\newcommand{\Ifd}{{I_d}} % for FEM
\newcommand{\Ifb}{{I_b}} % for FEM
\newcommand{\setb}[1]{#1^0} % set begin
\newcommand{\sete}[1]{#1^{-1}} % set end
\newcommand{\setl}[1]{#1^-}
\newcommand{\setr}[1]{#1^+}
\newcommand{\seti}[1]{#1^i}
\newcommand{\sequencei}[1]{\left\{ {#1}_i \right\}_{i\in\If}}
\newcommand{\basphi}{\varphi}
\newcommand{\baspsi}{\psi}
\newcommand{\refphi}{\tilde\basphi}
\newcommand{\psib}{\boldsymbol{\psi}}
\newcommand{\sinL}[1]{\sin\left((#1+1)\pi\frac{x}{L}\right)}
\newcommand{\xno}[1]{x_{#1}}
\newcommand{\Xno}[1]{X_{(#1)}}
\newcommand{\yno}[1]{y_{#1}}
\newcommand{\Yno}[1]{Y_{(#1)}}
\newcommand{\xdno}[1]{\boldsymbol{x}_{#1}}
\newcommand{\dX}{\, \mathrm{d}X}
\newcommand{\dx}{\, \mathrm{d}x}
\newcommand{\ds}{\, \mathrm{d}s}
\newcommand{\Real}{\mathbb{R}}
\newcommand{\Integerp}{\mathbb{N}}
\newcommand{\Integer}{\mathbb{Z}}
$$
Numerical integration
Finite element codes usually apply numerical approximations to
integrals. Since the integrands in the coefficient matrix often
are (lower-order) polynomials, integration rules that can
integrate polynomials exactly are popular.
The numerical integration rules can be expressed in a common form,
$$
\begin{equation}
\int_{-1}^{1} g(X)dX \approx \sum_{j=0}^M w_j g(\bar X_j),
\end{equation}
$$
where \( \bar X_j \) are integration points and \( w_j \) are
integration weights, \( j=0,\ldots,M \).
Different rules correspond to different choices of points and weights.
The very simplest method is the Midpoint rule,
$$
\begin{equation}
\int_{-1}^{1} g(X)dX \approx 2g(0),\quad \bar X_0=0,\ w_0=2,
\end{equation}
$$
which integrates linear functions exactly.
Newton-Cotes rules
The Newton-Cotes
rules are based on a fixed uniform distribution of the integration points.
The first two formulas in this family are the well-known
Trapezoidal rule,
$$
\begin{equation}
\int_{-1}^{1} g(X)dX \approx g(-1) + g(1),\quad \bar X_0=-1,\ \bar X_1=1,\ w_0=w_1=1,
\tag{41}
\end{equation}
$$
and Simpson's rule,
$$
\begin{equation}
\int_{-1}^{1} g(X)dX \approx \frac{1}{3}\left(g(-1) + 4g(0)
+ g(1)\right),
\end{equation}
$$
where
$$
\begin{equation}
\bar X_0=-1,\ \bar X_1=0,\ \bar X_2=1,\ w_0=w_2=\frac{1}{3},\ w_1=\frac{4}{3}\tp \end{equation}
$$
Newton-Cotes rules up to five points is supported in the
module file numint.py.
For higher accuracy one can divide the reference cell into a set of
subintervals and use the rules above on each subinterval. This approach
results in composite rules, well-known from basic introductions
to numerical integration of \( \int_{a}^{b}f(x)dx \).
Gauss-Legendre rules with optimized points
More accurate rules, for a given \( M \), arise if the location of the
integration points are optimized for polynomial integrands. The
Gauss-Legendre rules (also known as
Gauss-Legendre quadrature or Gaussian quadrature) constitute one such
class of integration methods. Two widely applied Gauss-Legendre rules
in this family have the choice
$$
\begin{align}
M=1&:\quad \bar X_0=-\frac{1}{\sqrt{3}},\
\bar X_1=\frac{1}{\sqrt{3}},\ w_0=w_1=1\\
M=2&:\quad \bar X_0=-\sqrt{\frac{3}{{5}}},\ \bar X_0=0,\
\bar X_2= \sqrt{\frac{3}{{5}}},\ w_0=w_2=\frac{5}{9},\ w_1=\frac{8}{9}\tp \end{align}
$$
These rules integrate 3rd and 5th degree polynomials exactly.
In general, an \( M \)-point Gauss-Legendre rule integrates a polynomial
of degree \( 2M+1 \) exactly.
The code numint.py
contains a large collection of Gauss-Legendre rules.