# maths pack 33 nodes, in `noodlelab[maths]` and above. ## Math/Arrays (node-maths.add_noise)= ### Add Noise `maths.add_noise` Add Gaussian noise. A fixed seed keeps runs reproducible (and cacheable). **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[floating]` | | | `sigma` | `float` | Default `0.1`. | | `seed` | `int` | Default `0`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | (node-maths.apply_function)= ### Apply Function `maths.apply_function` Apply a common function element-wise. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[floating]` | | | `function` | `Literal['sin', 'cos', 'exp', 'log', 'log10', 'sqrt', 'abs', 'gaussian']` | Default `'sin'`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | (node-maths.array_math)= ### Array Math `maths.array_math` Element-wise math between an array and a scalar or another array. **Inputs** | Name | Type | Description | |---|---|---| | `a` | `NDArray[floating]` | | | `b` | `float \| NDArray[floating]` | Default `1.0`. | | `operation` | `Literal['add', 'subtract', 'multiply', 'divide', 'power']` | Default `'multiply'`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | (node-maths.clip_values)= ### Clip Values `maths.clip_values` Limit values to [minimum, maximum]. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `minimum` | `float` | Default `0.0`. | | `maximum` | `float` | Default `1.0`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | (node-maths.cumulative_sum)= ### Cumulative Sum `maths.cumulative_sum` Running total of an array (NaNs count as zero). **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | (node-maths.linspace)= ### Linspace `maths.linspace` Evenly spaced numbers over an interval. **Inputs** | Name | Type | Description | |---|---|---| | `start` | `float` | Default `0.0`. | | `stop` | `float` | Default `10.0`. | | `num` | `int` | Default `200`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[float64]` | | ## Math/Calculus (node-maths.derivative)= ### Derivative `maths.derivative` dy/dx by central differences (numpy.gradient), for uneven spacing too. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `NDArray[number]` | | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | (node-maths.integrate)= ### Integrate `maths.integrate` The area under y(x) by the trapezoidal rule, and its running total (e.g. rainfall rate to accumulated rain, velocity to distance). **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `NDArray[number]` | | **Outputs** | Name | Type | Description | |---|---|---| | `total` | `float` | | | `cumulative` | `NDArray[float64]` | | (node-maths.interpolate)= ### Interpolate `maths.interpolate` Values of y at new positions: resample onto another grid, or fill gaps. Positions outside the data give NaN. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `NDArray[number]` | | | `new_x` | `NDArray[number]` | | | `method` | `Literal['linear', 'cubic', 'nearest']` | Default `'linear'`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `NDArray[floating]` | | ## Math/Complex (node-maths.complex_parts)= ### Complex Parts `maths.complex_parts` The parts of a complex number or array: ``real`` and ``imag``, ``magnitude`` |z| and ``phase`` arg z (in radians, from −π to π), and the ``conjugate``. Each keeps the value's unit, except the phase. A real value gives itself, an imaginary part of 0 and a phase of 0 or π. **Inputs** | Name | Type | Description | |---|---|---| | `value` | `Quantity \| NDArray[number] \| complex` | A complex number or array, with a unit or without: 3+4j ohm Default `'(3+4j) Ω'`. | **Outputs** | Name | Type | Description | |---|---|---| | `real` | `Any` | | | `imag` | `Any` | | | `magnitude` | `Any` | | | `phase` | `Quantity[rad]` | | | `conjugate` | `Any` | | ## Math/Fitting (node-maths.curve_fit)= ### Curve Fit `maths.curve_fit` Non-linear least squares fit of a standard model (scipy.optimize.curve_fit). **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `NDArray[number]` | | | `model` | `Literal['linear', 'quadratic', 'exponential decay', 'exponential growth', 'gaussian', 'logistic', 'power law', 'michaelis-menten', 'damped sine']` | Default `'exponential decay'`. | | `initial` | `str` | Starting values, comma-separated; empty: estimated from the data Default `''`. | | `sigma` | `NDArray[number] \| None` | Optional. | | `confidence` | `float` | Default `0.95`. | | `curve_points` | `int` | Default `300`. | **Outputs** | Name | Type | Description | |---|---|---| | `parameters` | `DataFrame` | | | `values` | `dict[str, float]` | | | `fitted` | `NDArray[float64]` | | | `residuals` | `NDArray[float64]` | | | `r_squared` | `float` | | | `rmse` | `float` | | | `curve_x` | `NDArray[float64]` | | | `curve_y` | `NDArray[float64]` | | | `equation` | `str` | | | `summary` | `dict[str, Any]` | | | `estimates` | `dict[str, Uncertain]` | | (node-maths.inverse_prediction)= ### Inverse Prediction `maths.inverse_prediction` Calibration: fit the standards (x = known amount, y = signal) with a straight line, then estimate the amount in each sample from its signal. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `NDArray[number]` | | | `samples` | `DataFrame` | | | `response` | `str` | The measured signal column Default `''`. | | `replicates` | `int` | Readings per sample Default `1`. | | `confidence` | `float` | Default `0.95`. | **Outputs** | Name | Type | Description | |---|---|---| | `results` | `DataFrame` | | | `lod` | `float` | | | `loq` | `float` | | | `summary` | `dict[str, Any]` | | (node-maths.linear_regression)= ### Linear Regression `maths.linear_regression` Ordinary least squares y = slope·x + intercept, with standard errors, R², the p-value of the slope and the residual standard deviation. Pairs with a NaN are left out. ``through_origin`` fixes the intercept at zero. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `Quantity \| NDArray[number]` | Numbers, or a quantity holding them (a column with a unit) | | `y` | `Quantity \| NDArray[number]` | Numbers, or a quantity holding them (a column with a unit) | | `through_origin` | `bool` | Default `False`. | | `confidence` | `float` | Default `0.95`. | **Outputs** | Name | Type | Description | |---|---|---| | `slope` | `float` | | | `intercept` | `float` | | | `slope_se` | `float` | | | `intercept_se` | `float` | | | `r_squared` | `float` | | | `p_value` | `float` | | | `residual_std` | `float` | | | `n` | `int` | | | `fitted` | `NDArray[float64]` | | | `residuals` | `NDArray[float64]` | | | `summary` | `dict[str, Any]` | | | `slope_q` | `Uncertain` | | | `intercept_q` | `Uncertain` | | (node-maths.polynomial_fit)= ### Polynomial Fit `maths.polynomial_fit` Least-squares polynomial fit, highest power first. Pairs with a NaN are left out; ``fitted`` has a value for every x. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `NDArray[number]` | | | `degree` | `int` | Default `1`. | **Outputs** | Name | Type | Description | |---|---|---| | `coefficients` | `NDArray[float64]` | | | `fitted` | `NDArray[float64]` | | | `r_squared` | `float` | | (node-maths.statistics)= ### Statistics `maths.statistics` Summary statistics of an array, ignoring NaNs: mean, standard deviation, minimum, maximum, sum (``total``) and the number of values. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `ddof` | `int` | Default `1`. | **Outputs** | Name | Type | Description | |---|---|---| | `mean` | `float` | | | `std` | `float` | | | `minimum` | `float` | | | `maximum` | `float` | | | `total` | `float` | | | `count` | `int` | | | `summary` | `dict[str, float]` | | ## Math/Fourier (node-maths.fourier_transform)= ### Fourier Transform `maths.fourier_transform` The frequencies in a signal, with the discrete Fourier transform (NumPy's FFT). ``amplitude`` is in the signal's unit, scaled so that a sine of amplitude a reads a at its frequency (with a window, exactly so only at a bin; flattop reads amplitudes best, hann separates peaks best). ``spectrum`` is the complex DFT itself, for Inverse Fourier Transform. **Inputs** | Name | Type | Description | |---|---|---| | `signal` | `Quantity \| NDArray[number]` | Evenly sampled values: an array, or a quantity holding one | | `time` | `Quantity \| NDArray[floating] \| None` | When each sample was taken, evenly spaced; else use sample_spacing Optional. | | `sample_spacing` | `Quantity` | Time (or distance) between samples, when time is not linked Default `'1.0 s'`. | | `window` | `Literal['hann', 'hamming', 'blackman', 'flattop', 'none']` | Tapers the ends against leakage; none for an exact inverse Default `'hann'`. | | `detrend` | `Literal['mean', 'linear', 'none']` | Taken off before transforming Default `'mean'`. | | `padding` | `int` | Pad with zeros to this many times the length Default `1`. | | `sides` | `Literal['one-sided', 'two-sided']` | One-sided: frequencies from 0 (real signals); two-sided: ± Default `'one-sided'`. | | `scaling` | `Literal['amplitude', 'rms', 'raw']` | amplitude: a sine of amplitude a reads a; rms: a/√2; raw: \|DFT\| Default `'amplitude'`. | | `peaks` | `int` | How many peaks to find Default `3`. | **Outputs** | Name | Type | Description | |---|---|---| | `frequency` | `Quantity` | | | `amplitude` | `Quantity` | | | `spectrum` | `NDArray[complex128]` | | | `phase` | `NDArray[float64]` | | | `plot` | `Figure` | | | `peak_frequencies` | `Quantity` | | | `dominant_frequency` | `Quantity` | | | `dominant_amplitude` | `Quantity` | | | `resolution` | `Quantity` | | | `summary` | `dict[str, float]` | | (node-maths.inverse_fourier_transform)= ### Inverse Fourier Transform `maths.inverse_fourier_transform` The signal back from its complex spectrum (the ``spectrum`` of Fourier Transform, perhaps filtered on the way). One-sided or two-sided is told from the frequencies. The round trip is exact with window none and detrend none; otherwise the result is the windowed, detrended signal. With a one-sided spectrum of an odd-length signal, give ``samples``. **Inputs** | Name | Type | Description | |---|---|---| | `spectrum` | `NDArray[complexfloating]` | | | `frequency` | `Quantity` | | | `samples` | `int` | Length of the signal; 0: from the spectrum Default `0`. | **Outputs** | Name | Type | Description | |---|---|---| | `time` | `Quantity` | | | `signal` | `NDArray[float64]` | | | `imaginary` | `NDArray[float64]` | | (node-maths.power_spectrum)= ### Power Spectrum `maths.power_spectrum` How the power of a signal is spread over frequency: its power spectral density (unit² per Hz), or with ``spectrum`` scaling the power of each tone (unit²). ``rms`` is the signal's root mean square about its mean, from the whole spectrum (Parseval). **Inputs** | Name | Type | Description | |---|---|---| | `signal` | `Quantity \| NDArray[number]` | Evenly sampled values: an array, or a quantity holding one | | `time` | `Quantity \| NDArray[floating] \| None` | When each sample was taken, evenly spaced; else use sample_spacing Optional. | | `sample_spacing` | `Quantity` | Time (or distance) between samples, when time is not linked Default `'1.0 s'`. | | `method` | `Literal['welch', 'periodogram']` | welch: averaged over overlapping segments, less noisy Default `'welch'`. | | `window` | `Literal['hann', 'hamming', 'blackman', 'flattop', 'none']` | Default `'hann'`. | | `segment` | `int` | Welch: samples per segment (finer frequency: longer) Default `1024`. | | `scaling` | `Literal['density', 'spectrum']` | density: power per Hz (noise); spectrum: power per peak (tones) Default `'density'`. | **Outputs** | Name | Type | Description | |---|---|---| | `frequency` | `Quantity` | | | `psd` | `Quantity` | | | `plot` | `Figure` | | | `rms` | `Quantity` | | | `dominant_frequency` | `Quantity` | | | `summary` | `dict[str, float]` | | ## Math/Matrices (node-maths.determinant)= ### Determinant `maths.determinant` The determinant, in the matrix's unit to the power of its size. Zero means the matrix is singular: its equations are not independent. **Inputs** | Name | Type | Description | |---|---|---| | `matrix` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.diagonal_matrix)= ### Diagonal Matrix `maths.diagonal_matrix` A matrix with ``diagonal`` on its diagonal and zeros elsewhere, such as the mass matrix of masses on springs. A linked vector replaces the text. **Inputs** | Name | Type | Description | |---|---|---| | `diagonal` | `str` | The diagonal, comma separated: 1, 1 Default `'1, 1'`. | | `unit` | `str` | One unit for every entry; empty: plain numbers Default `'kg'`. | | `values` | `Quantity \| NDArray[floating] \| None` | A vector for the diagonal, instead of the text (and its unit) Optional. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.eigenvalues)= ### Eigenvalues `maths.eigenvalues` The eigenvalues λ and eigenvectors v of A (A v = λ v), or of A against B (A v = λ B v). λ is in A's unit over B's. Column j of ``vectors`` belongs to value j, scaled to length 1. **Inputs** | Name | Type | Description | |---|---|---| | `a` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `b` | `Quantity \| NDArray[floating] \| None` | For the generalized problem A v = λ B v, such as K v = ω² M v Optional. | | `sort` | `Literal['ascending', 'descending', 'as computed']` | Default `'ascending'`. | **Outputs** | Name | Type | Description | |---|---|---| | `values` | `Quantity` | | | `vectors` | `NDArray[number]` | | | `real` | `bool` | | | `symmetric` | `bool` | | | `summary` | `dict[str, float]` | | (node-maths.element)= ### Element `maths.element` One entry of a matrix or vector, with its unit. Rows and columns are numbered from 1, so row 2 of a solution vector is x2. An entry of a complex matrix is complex. **Inputs** | Name | Type | Description | |---|---|---| | `matrix` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `row` | `int` | Numbered from 1 Default `1`. | | `column` | `int` | Numbered from 1; 1 for a vector Default `1`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.identity_matrix)= ### Identity Matrix `maths.identity_matrix` The identity matrix: ones on the diagonal, zeros elsewhere. **Inputs** | Name | Type | Description | |---|---|---| | `size` | `int` | Default `2`. | | `unit` | `str` | One unit for every entry; empty: plain numbers Default `''`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.inverse)= ### Inverse `maths.inverse` The inverse A⁻¹, in the reciprocal unit: the inverse of a stiffness matrix (N/m) is a flexibility matrix (m/N). To solve A x = b, Solve Linear System is more accurate than multiplying by the inverse. **Inputs** | Name | Type | Description | |---|---|---| | `matrix` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.matrix)= ### Matrix `maths.matrix` A matrix typed as text, MATLAB style: ``2, -1; -1, 2`` is a 2×2 matrix, ``10; 0`` a column. Every entry has the one ``unit``. **Inputs** | Name | Type | Description | |---|---|---| | `text` | `str` | Rows end at ; or a new line, entries are separated by commas or spaces: 2, -1; -1, 2 Default `'2, -1; -1, 2'`. | | `unit` | `str` | One unit for every entry; empty: plain numbers Default `''`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.matrix_multiply)= ### Matrix Multiply `maths.matrix_multiply` The matrix product A B, with the units multiplied too: a stiffness matrix times a displacement vector is a force vector. A vector counts as a column. For element-by-element products use Quantity Math. **Inputs** | Name | Type | Description | |---|---|---| | `a` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `b` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | (node-maths.natural_frequencies)= ### Natural Frequencies `maths.natural_frequencies` The natural frequencies and mode shapes of an undamped system of masses and springs, from its stiffness matrix K and mass matrix M: the solutions of K φ = ω² M φ. Frequencies are in ascending order, and column j of ``shapes`` is mode j, the way the masses move at that frequency. **Inputs** | Name | Type | Description | |---|---|---| | `stiffness` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `mass` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `normalise` | `Literal['largest = 1', 'mass']` | Scale each mode shape so its largest entry is 1, or so φᵀ M φ = 1 Default `'largest = 1'`. | **Outputs** | Name | Type | Description | |---|---|---| | `frequency` | `Quantity[Hz]` | | | `angular_frequency` | `Quantity[rad/s]` | | | `shapes` | `NDArray[float64]` | | | `first` | `Quantity[Hz]` | | | `plot` | `Figure` | | | `summary` | `dict[str, float]` | | (node-maths.solve_linear)= ### Solve Linear System `maths.solve_linear` Solve A x = b for x, such as K x = F for the displacements of a structure. x is in b's unit over A's (N over N/m is m), and has b's shape. **Inputs** | Name | Type | Description | |---|---|---| | `a` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `b` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | | `unit` | `str` | Unit of x; empty: b's unit over A's Default `''`. | **Outputs** | Name | Type | Description | |---|---|---| | `x` | `Quantity` | | | `residual` | `Quantity` | | | `condition` | `float` | | | `rank` | `int` | | | `method` | `str` | | | `summary` | `dict[str, float]` | | (node-maths.transpose)= ### Transpose `maths.transpose` Rows become columns. A vector (a column) becomes a row, 1×n. **Inputs** | Name | Type | Description | |---|---|---| | `matrix` | `Quantity \| NDArray[floating]` | A matrix: a quantity holding a 2-D array (one unit for every entry), or plain numbers | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Quantity` | | ## Math/Plot (node-maths.heatmap)= ### Heatmap `maths.heatmap` A matrix as coloured cells, such as a correlation matrix. A first text column names the rows; the other columns must be numeric. **Inputs** | Name | Type | Description | |---|---|---| | `table` | `DataFrame` | | | `colormap` | `Literal['viridis', 'plasma', 'cividis', 'magma', 'coolwarm', 'RdBu_r', 'YlOrRd', 'Blues']` | Default `'RdBu_r'`. | | `annotate` | `bool` | Default `True`. | | `symmetric` | `bool` | Centre the colours on zero (for correlations, anomalies) Default `True`. | | `title` | `str` | Default `''`. | | `colorbar_label` | `str` | Default `''`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Figure` | | (node-maths.histogram_plot)= ### Histogram Plot `maths.histogram_plot` Distribution of values, optionally with the normal curve of the same mean and standard deviation for comparison. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `bins` | `int` | Default `30`. | | `normal_curve` | `bool` | Overlay a normal distribution Default `False`. | | `title` | `str` | Default `''`. | | `x_label` | `str` | Default `'value'`. | | `log_y` | `bool` | Default `False`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Figure` | | (node-maths.xy_plot)= ### XY Plot `maths.xy_plot` Plot y (and optionally y2, e.g. a fitted curve or marked peaks) against x. A y with uncertainties gets error bars of ±u unless ``error`` is linked. **Inputs** | Name | Type | Description | |---|---|---| | `x` | `NDArray[number]` | | | `y` | `Uncertain[NDArray[number]]` | | | `y2` | `NDArray[number] \| None` | Optional. | | `x2` | `NDArray[number] \| None` | x for y2, if it differs from x Optional. | | `error` | `NDArray[number] \| None` | Error bars for y Optional. | | `style` | `Literal['line', 'scatter', 'scatter + line']` | Default `'line'`. | | `title` | `str` | Default `''`. | | `x_label` | `str` | Default `'x'`. | | `y_label` | `str` | Default `'y'`. | | `label` | `str` | Default `'data'`. | | `label2` | `str` | Default `'fit'`. | | `log_x` | `bool` | Default `False`. | | `log_y` | `bool` | Default `False`. | | `style2` | `Literal['line', 'markers']` | markers: e.g. to mark peaks on y Default `'line'`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Figure` | | ## Output (node-maths.save_figure)= ### Save Figure `maths.save_figure` Write a figure into this run's output folder. The extension picks the format: .png, .svg or .pdf. **Inputs** | Name | Type | Description | |---|---|---| | `figure` | `Figure` | | | `filename` | `str` | Default `'figure.png'`. | | `dpi` | `int` | Default `200`. | **Outputs** | Name | Type | Description | |---|---|---| | `result` | `Path` | |