9.16. Numerical extras¶
The pages so far covered the everyday surface – construction, indexing, math, broadcasting, reductions, linear algebra, FFT, filtering. This page catalogues the remaining numerical tools that did not fit one of those headings: interpolation, polynomial fitting, integration, convolution, root finding, optimisation, special functions, random numbers.
Almost every function on this page accepts – and most
require – ndarray inputs and
returns either a float scalar or a float
ndarray. The imports:
from ulab import numpy as np
from ulab import scipy as sp
9.16.1. Interpolation¶
interp(x, xp, fp)() does
one-dimensional linear interpolation. xp is a
monotonically increasing 1-D array of independent
values; fp is the matching dependent values; x
is where the interpolant should be evaluated:
xp = np.array([0.0, 1.0, 2.0, 3.0])
fp = np.array([10.0, 20.0, 25.0, 30.0])
x = np.array([0.5, 1.5, 2.5])
np.interp(x, xp, fp)
# array([15.0, 22.5, 27.5])
Outside the [xp[0], xp[-1]] range the result is
clipped to fp[0] and fp[-1] respectively; the
left= and right= keywords override those
endpoints.
Used on the camera to remap a calibration table to arbitrary sample positions – a temperature-to-voltage table from a thermistor, a non-linear pixel response curve, a per-channel gamma lookup. One library call, one pass over the inputs, no Python loop.
9.16.2. Polynomial fitting and evaluation¶
polyfit(x, y, deg)() fits a polynomial
of degree deg to the data points (x, y) by least
squares and returns the coefficients (highest degree
first):
x = np.array([0.0, 1.0, 2.0, 3.0, 4.0])
y = np.array([0.0, 1.1, 3.9, 9.1, 15.8])
coeffs = np.polyfit(x, y, 2)
# array([1.0, 0.0, 0.0]) (approximately)
When x is omitted, range(len(y)) is used –
useful for fitting a quick polynomial to a regularly
sampled buffer with no associated x-axis:
np.polyfit(y, 2)
polyval(p, x)() evaluates the
polynomial whose coefficients are p at x. The
input x can be a scalar (returns a float) or an
ndarray (returns an ndarray):
p = np.polyfit(x, y, 2)
fitted = np.polyval(p, x)
9.16.3. Convolution¶
convolve(a, v)() returns the
full-length discrete linear convolution of two 1-D
arrays. Only 'full' mode is implemented; the output
length is len(a) + len(v) - 1. Slice the result for
the same effect as the 'same' and 'valid' modes
that the desktop numpy offers:
a = np.array([1.0, 2.0, 3.0])
v = np.array([0.5, 0.5])
np.convolve(a, v)
# array([0.5, 1.5, 2.5, 1.5])
Useful for short FIR filters and smoothing kernels (box, triangle, gaussian) where setting up an SOS chain is overkill.
9.16.4. Trapezoidal integration¶
0)() integrates a
sampled function by the composite trapezoidal rule:
x = np.linspace(0, np.pi, num=128)
y = np.sin(x)
np.trapz(y, x) # ~ 2.0
Pass dx= when the sample spacing is uniform and only
the step matters; pass x= when the samples are not
evenly spaced. The right call for integrating already-
captured sensor data, where the analytic form is not
available.
9.16.5. Numerical integration of a callable¶
When the integrand is a Python function rather than a
buffer of samples, scipy.integrate exposes four
quadrature algorithms:
)()– adaptive Gauss-Kronrod. The right default for smooth integrands. Returns(value, error).)()– classical Romberg / Newton-Cotes. Returns a single float. Deprecated upstream; included for compatibility.)()– adaptive Simpson’s rule. Returns a single float.)()– double-exponential quadrature. Use when the integrand has endpoint singularities or an infinite limit. Returns(value, error).
The Gaussian integral as a worked example:
from math import exp, pi, sqrt
from ulab import numpy as np
from ulab import scipy as sp
f = lambda x: exp(-x * x)
value, err = sp.integrate.tanhsinh(f, -np.inf, np.inf)
print("approx:", value, " exact:", sqrt(pi))
Output:
approx: 1.7724538... exact: 1.7724538...
Numerical integration is most accurate on cams whose firmware uses double-precision floats. With single precision the routines still work, but the achievable tolerance is lower.
9.16.6. Root finding and minimisation¶
scipy.optimize covers three classic
single-variable solvers. Each iteration calls back into
the user-supplied Python function, so the speedup over a
pure-Python solver is modest (roughly 2x); the
convenience is in not having to write the solver.
)()– find a root offon[a, b]by halving the interval.f(a)andf(b)must have opposite signs:def f(x): return x * x - 1 sp.optimize.bisect(f, 0, 4) # ~1.0
)()– find a root using secant / Newton-Raphson iteration:def f(x): return x * x * x - 2.0 sp.optimize.newton(f, 3., tol=0.001, rtol=0.01) # ~1.260
)()– find a local minimum using the downhill-simplex (Nelder-Mead) method:def f(x): return (x - 1) ** 2 - 1 sp.optimize.fmin(f, 3.0) # ~1.0
9.16.7. Special functions¶
scipy.special exposes a handful of statistical
and probability functions that behave like universal
functions – they accept a scalar, an iterable, or an
ndarray and return a float
ndarray:
x = np.linspace(0, 4, num=8)
sp.special.erf(x) # error function
sp.special.erfc(x) # complementary error function
sp.special.gamma(x + 1) # gamma function
sp.special.gammaln(x + 1) # log-gamma function
The error function and its complement appear in the CDF of a Gaussian; the gamma functions show up in beta / chi-squared / student-t calculations.
9.16.8. Random numbers¶
numpy.random provides a
Generator class that draws
samples from common distributions. The generator is
stateful: each call advances its internal state, so
consecutive calls return independent samples:
from ulab import numpy as np
rng = np.random.Generator(seed=42)
rng.random(size=5) # 5 uniform [0.0, 1.0) samples
rng.uniform(low=-1.0, high=1.0, size=10)
rng.normal(loc=0.0, scale=1.0, size=(2, 4))
The output dtype is always float. size= accepts
an integer (1-D output) or a tuple (n-D output); when
omitted, a single Python float is returned.
The generator is suitable for simulation, dithering, synthetic test data, and any other application where cryptographic strength is not required. It is not suitable for keys or tokens.
9.16.9. What is not covered here¶
A handful of routines exposed by the reference – the
.npy I/O helpers (load(),
save(),
loadtxt(),
savetxt()),
set_printoptions(), the experimental
curve_fit() stub – are
straightforward extensions of patterns already shown on
this page. See numpy — numpy-compatible array operations and
scipy — subset of scipy via ulab for the complete
argument-level reference.
9.16.10. Firmware availability¶
Whether each submodule is actually present depends on
how the firmware on the cam was built. Calling a
function the firmware does not include raises
AttributeError. dir(sp),
dir(sp.optimize), dir(np.random) and friends
report what is available on the cam being targeted.