Numerical integration
- •
Quadrature and sampled data
- •
Quadrature -> approximate using weighted function/sample values.
- •
Function available -> evaluate it at nodes chosen by the rule.
- •
Samples only -> respect available sample locations; new evaluations require a justified interpolant/model.
- •
integration accumulates quantities: power -> energy, velocity -> displacement, depth over area -> volume.
- •
check units, sample order, smoothness, and known discontinuities before choosing a rule.
- •
- •
Composite Newton-Cotes rules
- •
Romberg integration
- •
Gauss-Legendre quadrature
- •
Multiple integrals and engineering use
- •
- •
- •
Gridded volumes -> repeated 1-D integration can integrate depth over a rectangular grid.
- •
- •
Unit-sensitive examples
- •
: volts times amperes times seconds gives joules.
- •
integrating kW over seconds gives kJ; divide by 3600 to obtain kWh.
- •
power samples in seconds/kW give trapezoidal energy kJ, or kWh.
- •
heat flux requires spatial distances in units compatible with conductivity.
- •
integrating acceleration requires an initial velocity; integrating velocity requires an initial position.
- •
integration of sensor bias can drift; smoothing does not by itself remove bias or supply initial conditions.
- •
- •
- •
Python: quadrature checks
- •
standalone NumPy/SciPy example;
trapezoidhandles nonuniform samples with supplied coordinates. - •
the composite Simpson implementation below deliberately requires equal spacing and even .
- •
SciPy's sampled-data
simpson(y, x=x)supports other cases, but its usual uniform-grid accuracy claims need qualification. - •
import math import numpy as np from scipy.integrate import trapezoid from numpy.polynomial.legendre import leggauss def simpson_uniform(f, a, b, n): if not isinstance(n, int) or n < 2 or n % 2: raise ValueError("even number of subintervals required") if not np.isfinite([a, b]).all() or a >= b: raise ValueError("finite increasing bounds required") x = np.linspace(a, b, n+1) y = np.asarray(f(x), dtype=float) if y.shape != x.shape or not np.isfinite(y).all(): raise ValueError("one finite value per node required") return (b-a)/(3*n)*(y[0]+y[-1]+4*y[1:-1:2].sum()+2*y[2:-1:2].sum()) def romberg_table(f, a, b, levels=3): if not isinstance(levels, int) or not 1 <= levels <= 15: raise ValueError("levels must be an integer in 1..15") if not math.isfinite(a) or not math.isfinite(b) or a >= b: raise ValueError("finite increasing bounds required") def evaluate(x): y = f(x) if not math.isfinite(y): raise ValueError("nonfinite integrand") return y R = np.full((levels, levels), np.nan) R[0, 0] = (b-a)*(evaluate(a)+evaluate(b))/2 for k in range(1, levels): h = (b-a)/(2**k) new_sum = math.fsum(evaluate(a+(2*i-1)*h) for i in range(1, 2**(k-1)+1)) R[k, 0] = R[k-1, 0]/2 + h*new_sum for j in range(1, k+1): R[k, j] = R[k, j-1]+(R[k, j-1]-R[k-1, j-1])/(4**j-1) return R x = np.linspace(0., 1., 5) assert math.isclose(trapezoid(4/(1+x*x), x=x), 3.131176470588235) assert math.isclose(simpson_uniform(lambda x: 4/(1+x*x), 0., 1., 4), 3.1415686274509804) R = romberg_table(lambda x: math.exp(-x), 0., 2.) assert math.isclose(R[2, 2], 0.8646899224948368) nodes, weights = leggauss(2) mapped = 1 + nodes assert math.isclose(weights @ (mapped**3 - 2*mapped + 1), 2.) t = np.array([0., 10., 30., 60.]) power_kw = np.array([0., 40., 50., 10.]) energy_kj = trapezoid(power_kw, x=t) assert math.isclose(energy_kj, 2000.) print("Romberg table:", R, "energy (kWh):", energy_kj/3600)
- •
