Core Abstractions (pypielm.core)¶
Core abstractions: BasePIELM, feature maps, and linear solvers.
Implemented across Steps 2 (core) and 5 (models).
BasePIELM¶
Abstract base class and shared configuration for all PIELM models.
All PIELM and PINN variants in PyPIELM inherit from BasePIELM, which
is itself a torch.nn.Module. The ELM paradigm mandates that hidden-layer
weights are randomly sampled and frozen; only the output layer weights
beta are determined during BasePIELM.fit() (analytically, not via
gradient descent).
- class pypielm.core.base.PIELMConfig(hidden_dim=200, activation='tanh', ridge_lambda=1e-08, seed=42, device='cpu', dtype=<factory>)[source]¶
Bases:
objectShared hyper-parameter configuration for PIELM models.
- Parameters:
hidden_dim (
int) – Number of hidden neurons (random feature dimension H).activation (
str) – Activation function name. Supported:'tanh','sin','relu','sigmoid','softplus'.ridge_lambda (
float) – Tikhonov regularisation coefficient λ used in the output-weight solve. Typically in [1e-12, 1e-4].seed (
int) – Integer seed for reproducible hidden-layer weight sampling.device (
str) – PyTorch device string (e.g.'cpu','cuda','cuda:0').dtype (
dtype) – Floating-point dtype.torch.float64(double precision) is the default because PDE residuals require high numerical accuracy.
Example:
cfg = PIELMConfig(hidden_dim=500, activation="tanh", ridge_lambda=1e-10)
- class pypielm.core.base.BasePIELM(*args, **kwargs)[source]¶
Bases:
ModuleAbstract base for all PIELM and PINN variants in PyPIELM.
Subclasses must implement
fit(),predict(),score(), andget_feature_matrix(). Hidden-layer weights are frozen (ELM paradigm): they are initialised in__init__and never updated by a gradient step. Output weightsbetaare solved analytically infit().The class inherits from
torch.nn.Moduleto enable:model.to(device)— move all parameters to a device.torch.jit.script/torch.jit.trace— TorchScript export.torch.onnx.export— ONNX export.torch.compile— optional kernel fusion.
Scikit-learn compatibility:
fit(dataset, ...)returnsself(fluent chaining).predict(X)returns atorch.Tensor.score(X, y)returns a scalarfloat.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
- abstractmethod fit(dataset, *, pde_operator=None, bcs=None, ics=None, collocation_sampler=None)[source]¶
Solve for output weights analytically.
- Parameters:
dataset (
PIELMDataset) – APIELMDatasetcontainingX_train,y_train, and (optionally) validation splits.pde_operator (
Any|None) – Optional differential operator applied at collocation points to build the physics residual block in the linear system. IfNone, the model falls back to pure data regression.bcs (
list[Any] |None) – List of boundary condition objects (DirichletBC, etc.).collocation_sampler (
Any|None) – Overrides the default collocation sampler used to generate interior PDE points.
- Returns:
model.fit(ds, pde_operator=op).predict(X_test).- Return type:
self— enables fluent chaining
- abstractmethod predict(X)[source]¶
Evaluate the surrogate solution at input coordinates X.
- Parameters:
X (
ndarray|Tensor) – Input coordinates of shape(N, d). Accepts bothtorch.Tensorandnumpy.ndarray; the result is always atorch.Tensor.- Return type:
- Returns:
Predicted values of shape
(N, 1)or(N, out_dim).
Feature Maps¶
Random and Fourier feature maps for ELM hidden layers (PyTorch).
All feature maps are torch.nn.Module subclasses with frozen
parameters. They provide both a forward pass and analytic first/second
partial derivatives (fast paths for tanh and sin/cos) as well as an autograd
fallback for arbitrary activations.
- class pypielm.core.feature_maps.RandomFeatureMap(input_dim, hidden_dim, activation='tanh', seed=42, w_scale=1.0, device='cpu', dtype=torch.float64)[source]¶
Bases:
ModuleRandom-weight ELM hidden layer with analytic partial derivatives.
The hidden representation is:
\[H_{ij} = \sigma\!\left(\sum_k x_{ik} W_{kj} + b_j\right)\]where \(\mathbf{W} \in \mathbb{R}^{d \times H}\) and \(\mathbf{b} \in \mathbb{R}^H\) are sampled once at construction and never updated (frozen buffers).
Analytic first- and second-order partial derivatives are provided for
'tanh','sin','relu','sigmoid', and'softplus'.- Parameters:
input_dim (
int) – Spatial dimensiondof the input coordinates.hidden_dim (
int) – Number of neuronsH.activation (
str) – Name of the activation function.seed (
int) – Random seed for weight initialisation.w_scale (
float) – Multiplicative scale applied to the random weightsW.dtype (
dtype) – Floating-point precision.torch.float64recommended.
Example:
fm = RandomFeatureMap(input_dim=2, hidden_dim=200, seed=42) H = fm(X) # shape (N, 200) dH = fm.d1(X, 0) # ∂H/∂x₀, shape (N, 200) d2H = fm.d2(X, 0) # ∂²H/∂x₀², shape (N, 200)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
- d1(X, axis)[source]¶
Analytic first partial derivative ∂H/∂x_{axis}.
\[\frac{\partial H_{ij}}{\partial x_{i,\text{axis}}} = \sigma'(Z_{ij}) \cdot W_{\text{axis},j}\]
- class pypielm.core.feature_maps.FourierFeatureMap(input_dim, hidden_dim, freq_init='log_uniform', freq_min=1.0, freq_max=100.0, seed=42, w_scale=1.0, normalize_w=True, device='cpu', dtype=torch.float64)[source]¶
Bases:
ModuleGeneralised Fourier Feature ELM hidden layer.
Each hidden neuron computes:
\[\phi_j(\mathbf{x}) = \sqrt{2} \cos\!\left( \omega_j \, \mathbf{w}_j^\top \mathbf{x} + b_j \right)\]where \(\mathbf{w}_j\) is a random direction, \(b_j \sim \mathcal{U}(0, 2\pi)\) is a random phase, and \(\omega_j\) is a per-neuron frequency coefficient.
Analytic derivatives:
\[ \begin{align}\begin{aligned}\frac{\partial \phi_j}{\partial x_i} = -\sqrt{2} \, \omega_j w_{ji} \sin(A_j)\\\frac{\partial^2 \phi_j}{\partial x_i^2} = -\sqrt{2} \, (\omega_j w_{ji})^2 \cos(A_j)\end{aligned}\end{align} \]where \(A_j = \omega_j \mathbf{w}_j^\top \mathbf{x} + b_j\).
- Parameters:
input_dim (
int) – Spatial dimensiond.hidden_dim (
int) – Number of neuronsH.freq_init (
Literal['uniform','log_uniform','auto']) – Frequency initialisation ('log_uniform','uniform').freq_min (
float) – Minimum frequency.freq_max (
float) – Maximum frequency.seed (
int) – Random seed.w_scale (
float) – Scale of Gaussian weight draw before normalisation.normalize_w (
bool) – IfTrue, unit-normalise each direction vector.dtype (
dtype) – Floating-point precision.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
- class pypielm.core.feature_maps.AutogradFeatureMap(input_dim, hidden_dim, activation_fn, seed=42, w_scale=1.0, device='cpu', dtype=torch.float64)[source]¶
Bases:
ModuleFeature map with arbitrary activation, derivatives via autograd.
Wraps any user-supplied callable activation. Derivatives are computed via
torch.autograd.grad()— slower than analytic but works for any smooth activation.- Parameters:
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Solvers¶
Linear solvers for ELM output-weight determination (PyTorch).
All solvers accept and return torch.Tensor objects and work on both
CPU and CUDA devices. float64 (double precision) is strongly recommended
for numerically stable PDE solves.
- class pypielm.core.solver.BayesianSolveResult(beta_mean, beta_cov)[source]¶
Bases:
NamedTupleResult of the Bayesian linear solve.
beta_mean: posterior mean of output weights, shape(H, out_dim).beta_cov: posterior precision matrix (inverse covariance), shape(H, H). Stored as precision (not covariance) for numerical stability; invert only when uncertainty quantification is needed.Create new instance of BayesianSolveResult(beta_mean, beta_cov)
- class pypielm.core.solver.WeightedLinearSystem(H, y, weight=1.0)[source]¶
Bases:
NamedTupleOne weighted observation block:
y ≈ H @ beta + eps.H: feature/design matrix, shape(N, H).y: target values, shape(N, out_dim)or(N,).weight: observation precision 1/σ²; rows are scaled bysqrt(weight)before the solve.Create new instance of WeightedLinearSystem(H, y, weight)
- pypielm.core.solver.ridge_solve(H, y, lam=1e-08)[source]¶
Closed-form ridge regression output weights.
Solves:
\[\boldsymbol{\beta} = (\mathbf{H}^\top \mathbf{H} + \lambda \mathbf{I})^{-1} \mathbf{H}^\top \mathbf{y}\]using
torch.linalg.solve()for numerical stability.
- pypielm.core.solver.rrqr_solve(H, y, tol=None)[source]¶
Rank-revealing QR (divide-and-conquer SVD) least-squares solve.
Delegates to
torch.linalg.lstsq()withdriver='gelsd'.
- pypielm.core.solver.bayesian_solve(blocks, prior_precision=0.0001)[source]¶
Sequential Bayesian update over weighted observation blocks.
Computes the posterior:
\[ \begin{align}\begin{aligned}\boldsymbol{\Lambda}_{\text{post}} = \alpha \mathbf{I} + \sum_k \lambda_k \mathbf{H}_k^\top \mathbf{H}_k\\\boldsymbol{\beta}_{\text{post}} = \boldsymbol{\Lambda}_{\text{post}}^{-1} \sum_k \lambda_k \mathbf{H}_k^\top \mathbf{y}_k\end{aligned}\end{align} \]- Parameters:
blocks (
list[WeightedLinearSystem]) – List ofWeightedLinearSystemobjects.prior_precision (
float) – Scalar α for the isotropic prior β ~ N(0, α⁻¹ I).
- Return type:
- Returns:
BayesianSolveResultwithbeta_meanandbeta_cov(posterior precision matrix).