omnibias.torch.architectures.pinn
PyTorch activation derivatives, trainable operator layers, network jets, and direct differential-operator contractions.
import omnibias.torch.architectures.pinnModule contract & mathematical conventions
Physics-informed neural networks built from OMBU primitives.
These architectures sidestep the standard PINN pain point -- repeated
torch.autograd.grad calls to compute spatial / temporal derivatives
of the network output -- by structuring the network as a single hidden
layer of width H and computing each derivative analytically via the
activation's closed-form derivative tower:
u(x_1, ..., x_d) = c_bias + sum_{i=1..H} c_i * sigma(W_i . x + b_i)
du/dx_a = sum_i c_i * W_{i,a} * sigma'(z_i) d^2u/dx_a^2 = sum_i c_i * W_{i,a}^2 * sigma''(z_i) d^2u/dx_a dx_b = sum_i c_i * W_{i,a} * W_{i,b} * sigma''(z_i)
with z_i = W_i . x + b_i and sigma^(n) evaluated through the
fast-path kernel of the chosen ActivationSpec. There is no
torch.autograd.grad in the inner loop and no quotient-of-differences
in the bias-collapse regime.
The PINNOMBU base class collects the chain-rule helpers; the
concrete PINNHeat implements the 1D heat equation
u_t = alpha * u_xx.
For *deep* networks and *arbitrary* derivative order, JetMLP
replaces the hand-coded order-2 chain rule with the exact multivariate jet
kernel omnibias.torch.jet_mv.mlp_jet_mv: one forward pass yields the
value, gradient, Hessian and every mixed partial up to total order N with
no torch.autograd.grad stacking. DeepPINNHeat is the deep
counterpart of PINNHeat built on it.
*Spectral bias* -- the tendency of plain MLPs to learn low frequencies first --
is mitigated by two omnibias-native, closed-form-tower constructs that share the
same exact jet readout (_JetMLPCore):
FourierFeatureMLPlifts the input through a random Fourier-feature encodinggamma(x) = [cos(B x), sin(B x)](Tancik et al. 2020). Becausecos(z) = sin(z + pi/2), the whole encoding is a *single* omnibiassinlayer, soD^alpha u(x)stays exactly closed form to arbitrary order -- no autodiff for the PDE residual. A tuple offrequency_scalevalues gives a multi-scale encoding.make_sirenbuilds a SIREN (Sitzmann et al. 2020): aJetMLPwithsinactivations and the SIREN initialisation. Its derivative tower is exact for every order (sin^{(n)}(z) = sin(z + n pi/2)).
Shared formulas do not imply universal floating-point identity. Verify dtype, shape, derivative-order support, and the stated numerical assumptions for the path you use.
DeepPINNHeat
omnibias.torch.architectures.DeepPINNHeatSource ↗class DeepPINNHeat(nn.Module)Deep 1-D heat-equation PINN u_t = alpha * u_xx on JetMLP.
The deep, arbitrary-depth counterpart of PINNHeat: the spatial and
temporal derivatives come from a single closed-form order-2 multivariate jet per
collocation point (no autograd-through-derivative), so the PDE residual is exact.
Parameters
hidden : int, default 32 Hidden width.
depth : int, default 3 Number of hidden layers.
base : str or ActivationSpec, default "tanh" Base activation with a fast path supporting orders 1 and 2.
alpha : float, default 0.1 Diffusion coefficient.
Parameters
| Name | Type & default | Contract |
|---|---|---|
hiddenpositional-or-keyword | intDefault: 32 | int, default 32 Hidden width. |
depthpositional-or-keyword | intDefault: 3 | int, default 3 Number of hidden layers. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'tanh' | str or ActivationSpec, default "tanh" Base activation with a fast path supporting orders 1 and 2. |
alphapositional-or-keyword | floatDefault: 0.1 | float, default 0.1 Diffusion coefficient. |
Executed regression · deeppinnheat shape mismatch raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_deeppinnheat_shape_mismatch_raises() -> None:
model = DeepPINNHeat(hidden=8, depth=2)
with pytest.raises(ValueError, match="matching shape"):
model(torch.rand(5), torch.rand(6))python -m pytest "packages/omnibias-torch/tests/test_jet_pinn.py::test_deeppinnheat_shape_mismatch_raises" -qRegression example · deeppinnheat training decreases
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_deeppinnheat_training_decreases() -> None:
torch.manual_seed(0)
model = DeepPINNHeat(hidden=16, depth=2, base="tanh", alpha=0.1)
optim = torch.optim.Adam(model.parameters(), lr=1e-2)
losses: list[float] = []
for _ in range(12):
optim.zero_grad()
x = torch.rand(64)
t = torch.rand(64)
u, res = model(x, t)
loss = (res**2).mean() + (u[0] ** 2)
loss.backward()
optim.step()
losses.append(loss.item())
assert losses[-1] < losses[0]python -m pytest packages/omnibias-torch/tests/test_jet_pinn.py -k test_deeppinnheat_training_decreases -qFields & methods
__init__method
__init__(self, hidden: int=32, depth: int=3, base: str | ActivationSpec[Tensor]='tanh', alpha: float=0.1) -> NoneSee the class contract above and the exact method implementation below.
Parameters
| Name | Type & default | Contract |
|---|---|---|
hiddenpositional-or-keyword | intDefault: 32 | See the operation contract and implementation below. |
depthpositional-or-keyword | intDefault: 3 | See the operation contract and implementation below. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'tanh' | See the operation contract and implementation below. |
alphapositional-or-keyword | floatDefault: 0.1 | See the operation contract and implementation below. |
Returns: None
Executed regression · deeppinnheat shape mismatch raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_deeppinnheat_shape_mismatch_raises() -> None:
model = DeepPINNHeat(hidden=8, depth=2)
with pytest.raises(ValueError, match="matching shape"):
model(torch.rand(5), torch.rand(6))python -m pytest "packages/omnibias-torch/tests/test_jet_pinn.py::test_deeppinnheat_shape_mismatch_raises" -qMethod implementation
def __init__(
self,
hidden: int = 32,
depth: int = 3,
base: str | ActivationSpec[Tensor] = "tanh",
alpha: float = 0.1,
) -> None:
super().__init__()
self.alpha = alpha
self.net = JetMLP(in_dim=2, hidden=hidden, out_dim=1, depth=depth, base=base)
self.net._check_fastpath(max_order=2)forwardmethod
forward(self, x: Tensor, t: Tensor) -> tuple[Tensor, Tensor]Return (u, residual) with residual = u_t - alpha * u_xx (target 0).
Parameters
| Name | Type & default | Contract |
|---|---|---|
xpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
tpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
Returns: tuple[Tensor, Tensor]
Validation & failure cases
- When
x.shape != t.shape, raisesValueError(f'x and t must have matching shape, got {x.shape} vs {t.shape}.').
Executed regression · deeppinnheat shape mismatch raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_deeppinnheat_shape_mismatch_raises() -> None:
model = DeepPINNHeat(hidden=8, depth=2)
with pytest.raises(ValueError, match="matching shape"):
model(torch.rand(5), torch.rand(6))python -m pytest "packages/omnibias-torch/tests/test_jet_pinn.py::test_deeppinnheat_shape_mismatch_raises" -qMethod implementation
def forward(self, x: Tensor, t: Tensor) -> tuple[Tensor, Tensor]:
"""Return ``(u, residual)`` with ``residual = u_t - alpha * u_xx`` (target 0)."""
if x.shape != t.shape:
raise ValueError(f"x and t must have matching shape, got {x.shape} vs {t.shape}.")
inp = torch.stack([x, t], dim=-1) # (B, 2): axis 0 is x, axis 1 is t
value, grad, hess = self.net.value_grad_hessian(inp)
u = value.squeeze(-1) # (B,)
u_t = grad[:, 1, 0] # d/dt
u_xx = hess[:, 0, 0, 0] # d^2/dx^2
residual = u_t - self.alpha * u_xx
return u, residualnetattribute
net = JetMLP(in_dim=2, hidden=hidden, out_dim=1, depth=depth, base=base)Public instance attribute assigned by __init__. The source assignment is net = JetMLP(in_dim=2, hidden=hidden, out_dim=1, depth=depth, base=base); it describes instance state, not a literal constructor default.
Read the implementation
class DeepPINNHeat(nn.Module):
"""Deep 1-D heat-equation PINN ``u_t = alpha * u_xx`` on :class:`JetMLP`.
The deep, arbitrary-depth counterpart of :class:`PINNHeat`: the spatial and
temporal derivatives come from a single closed-form order-2 multivariate jet per
collocation point (no autograd-through-derivative), so the PDE residual is exact.
Parameters
----------
hidden : int, default 32
Hidden width.
depth : int, default 3
Number of hidden layers.
base : str or :class:`ActivationSpec`, default ``"tanh"``
Base activation with a fast path supporting orders 1 and 2.
alpha : float, default 0.1
Diffusion coefficient.
"""
def __init__(
self,
hidden: int = 32,
depth: int = 3,
base: str | ActivationSpec[Tensor] = "tanh",
alpha: float = 0.1,
) -> None:
super().__init__()
self.alpha = alpha
self.net = JetMLP(in_dim=2, hidden=hidden, out_dim=1, depth=depth, base=base)
self.net._check_fastpath(max_order=2)
def forward(self, x: Tensor, t: Tensor) -> tuple[Tensor, Tensor]:
"""Return ``(u, residual)`` with ``residual = u_t - alpha * u_xx`` (target 0)."""
if x.shape != t.shape:
raise ValueError(f"x and t must have matching shape, got {x.shape} vs {t.shape}.")
inp = torch.stack([x, t], dim=-1) # (B, 2): axis 0 is x, axis 1 is t
value, grad, hess = self.net.value_grad_hessian(inp)
u = value.squeeze(-1) # (B,)
u_t = grad[:, 1, 0] # d/dt
u_xx = hess[:, 0, 0, 0] # d^2/dx^2
residual = u_t - self.alpha * u_xx
return u, residualOther public import paths
omnibias.torch.architectures.DeepPINNHeatomnibias.torch.architectures.pinn.DeepPINNHeat
FourierFeatureMLP
omnibias.torch.architectures.FourierFeatureMLPSource ↗class FourierFeatureMLP(_JetMLPCore)Spectral-bias-mitigating MLP: a sin-encoded random Fourier-feature front end.
The input is lifted by the random Fourier-feature map (Tancik et al. 2020)
\gamma(x) = \big[\cos(B x),\ \sin(B x)\big], \qquad B \in \mathbb{R}^{F \times D},
and a standard JetMLP body acts on gamma(x). Lifting the input into a
high-frequency basis lets the downstream net represent high-frequency targets with
*low-frequency* weights, which is the standard cure for the low-frequency
(spectral) bias of plain MLPs.
The omnibias twist is that the encoding costs nothing in the differential operator:
because cos(z) = sin(z + pi/2),
\gamma(x) = \sigma_{\sin}\!\big([B;\,B]\,x + [\tfrac{\pi}{2}\mathbf 1;\,\mathbf 0]\big),
i.e. the whole encoding is a *single* omnibias sin layer with the exact
derivative tower sin^{(n)}(z) = sin(z + n pi/2). Stacked in front of the body,
the composite u(x) therefore still yields *every* mixed partial D^alpha u(x)
in closed form through omnibias.torch.jet_mv.mlp_jet_mv -- exact, arbitrary
order, no torch.autograd.grad in the PDE residual.
Parameters
in_dim: Number of input coordinates D.
num_features: Number of Fourier features F *per frequency band*; the encoding width is 2 * F * len(scales).
hidden: Hidden width of the body MLP.
out_dim: Number of output components (default 1).
depth: Number of hidden (activated) body layers >= 0; depth=0 is a pure random-Fourier-feature model with a linear readout.
base: Body activation (name or ActivationSpec) with a closed-form derivative fast path (default "tanh").
frequency_scale: Bandwidth(s) of the Gaussian frequency matrix. A scalar selects one band with B ~ N(0, (2 pi * scale)^2); a sequence concatenates several bands for a *multi-scale* encoding (helps when the target mixes low and high frequencies).
trainable_features: If True the frequency matrix / phases are learnable parameters; otherwise they are fixed buffers (the classic random-features regime).
seed: Seed for the frequency-matrix draw.
Parameters
| Name | Type & default | Contract |
|---|---|---|
in_dimpositional-or-keyword | intRequired | Number of input coordinates D. |
num_featurespositional-or-keyword | intDefault: 64 | Number of Fourier features F *per frequency band*; the encoding width is 2 * F * len(scales). |
hiddenpositional-or-keyword | intDefault: 64 | Hidden width of the body MLP. |
out_dimpositional-or-keyword | intDefault: 1 | Number of output components (default 1). |
depthpositional-or-keyword | intDefault: 3 | Number of hidden (activated) body layers >= 0; depth=0 is a pure random-Fourier-feature model with a linear readout. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'tanh' | Body activation (name or ActivationSpec) with a closed-form derivative fast path (default "tanh"). |
frequency_scalekeyword-only | float | Sequence[float]Default: 1.0 | Bandwidth(s) of the Gaussian frequency matrix. A scalar selects one band with B ~ N(0, (2 pi * scale)^2); a sequence concatenates several bands for a *multi-scale* encoding (helps when the target mixes low and high frequencies). |
trainable_featureskeyword-only | boolDefault: False | If True the frequency matrix / phases are learnable parameters; otherwise they are fixed buffers (the classic random-features regime). |
seedkeyword-only | intDefault: 0 | Seed for the frequency-matrix draw. |
Executed regression · fourier features buffer by default
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_fourier_features_buffer_by_default() -> None:
net = FourierFeatureMLP(2, num_features=4, hidden=8, depth=1, seed=8)
names = dict(net.named_buffers())
assert "W_ff" in names and "b_ff" in names
assert "W_ff" not in dict(net.named_parameters())python -m pytest "packages/omnibias-torch/tests/test_spectral_pinn.py::test_fourier_features_buffer_by_default" -qRegression example · fourier value equals jet row0
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_fourier_value_equals_jet_row0() -> None:
net = FourierFeatureMLP(2, num_features=8, hidden=16, depth=2, seed=1).double()
x = torch.randn(6, 2, dtype=torch.float64)
jet = net.jet(x, 2)
assert torch.allclose(jet[:, 0, :], net.value(x), atol=1e-13)python -m pytest packages/omnibias-torch/tests/test_spectral_pinn.py -k test_fourier_value_equals_jet_row0 -qFields & methods
specfield
spec: ActivationSpec[Tensor]Declared ActivationSpec[Tensor] field on FourierFeatureMLP. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗sin_specfield
sin_spec: ActivationSpec[Tensor]Declared ActivationSpec[Tensor] field on FourierFeatureMLP. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗W_fffield
W_ff: TensorDeclared Tensor field on FourierFeatureMLP. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗b_fffield
b_ff: TensorDeclared Tensor field on FourierFeatureMLP. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗linearsfield
linears: nn.ModuleListDeclared nn.ModuleList field on FourierFeatureMLP. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗__init__method
__init__(self, in_dim: int, num_features: int=64, hidden: int=64, out_dim: int=1, depth: int=3, base: str | ActivationSpec[Tensor]='tanh', *, frequency_scale: float | Sequence[float]=1.0, trainable_features: bool=False, seed: int=0) -> NoneSee the class contract above and the exact method implementation below.
Parameters
| Name | Type & default | Contract |
|---|---|---|
in_dimpositional-or-keyword | intRequired | See the operation contract and implementation below. |
num_featurespositional-or-keyword | intDefault: 64 | See the operation contract and implementation below. |
hiddenpositional-or-keyword | intDefault: 64 | See the operation contract and implementation below. |
out_dimpositional-or-keyword | intDefault: 1 | See the operation contract and implementation below. |
depthpositional-or-keyword | intDefault: 3 | See the operation contract and implementation below. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'tanh' | See the operation contract and implementation below. |
frequency_scalekeyword-only | float | Sequence[float]Default: 1.0 | See the operation contract and implementation below. |
trainable_featureskeyword-only | boolDefault: False | See the operation contract and implementation below. |
seedkeyword-only | intDefault: 0 | See the operation contract and implementation below. |
Returns: None
Validation & failure cases
- When
in_dim < 1, raisesValueError(f'in_dim must be >= 1, got {in_dim}'). - When
num_features < 1, raisesValueError(f'num_features must be >= 1, got {num_features}'). - When
hidden < 1, raisesValueError(f'hidden must be >= 1, got {hidden}'). - When
out_dim < 1, raisesValueError(f'out_dim must be >= 1, got {out_dim}'). - When
depth < 0, raisesValueError(f'depth (hidden layers after encoding) must be >= 0, got {depth}').
Executed regression · fourier features buffer by default
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_fourier_features_buffer_by_default() -> None:
net = FourierFeatureMLP(2, num_features=4, hidden=8, depth=1, seed=8)
names = dict(net.named_buffers())
assert "W_ff" in names and "b_ff" in names
assert "W_ff" not in dict(net.named_parameters())python -m pytest "packages/omnibias-torch/tests/test_spectral_pinn.py::test_fourier_features_buffer_by_default" -qMethod implementation
def __init__(
self,
in_dim: int,
num_features: int = 64,
hidden: int = 64,
out_dim: int = 1,
depth: int = 3,
base: str | ActivationSpec[Tensor] = "tanh",
*,
frequency_scale: float | Sequence[float] = 1.0,
trainable_features: bool = False,
seed: int = 0,
) -> None:
super().__init__()
if in_dim < 1:
raise ValueError(f"in_dim must be >= 1, got {in_dim}")
if num_features < 1:
raise ValueError(f"num_features must be >= 1, got {num_features}")
if hidden < 1:
raise ValueError(f"hidden must be >= 1, got {hidden}")
if out_dim < 1:
raise ValueError(f"out_dim must be >= 1, got {out_dim}")
if depth < 0:
raise ValueError(f"depth (hidden layers after encoding) must be >= 0, got {depth}")
scales = _as_scales(frequency_scale)
self.in_dim = in_dim
self.out_dim = out_dim
self.depth = depth
self.num_features = num_features
self.scales = scales
self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
self.sin_spec = get_activation("sin")
gen = torch.Generator().manual_seed(seed)
bands = [
torch.randn(num_features, in_dim, generator=gen) * (2.0 * math.pi * s)
for s in scales
]
b_mat = torch.cat(bands, dim=0) # (F_total, in_dim)
f_total = b_mat.shape[0]
self.feature_dim = 2 * f_total
# sin([B x + pi/2 ; B x]) = [cos(B x) ; sin(B x)]: the encoding is one sin layer.
w_ff = torch.cat([b_mat, b_mat], dim=0) # (2 F_total, in_dim)
b_ff = torch.cat(
[torch.full((f_total,), 0.5 * math.pi), torch.zeros(f_total)]
)
if trainable_features:
self.W_ff = nn.Parameter(w_ff)
self.b_ff = nn.Parameter(b_ff)
else:
self.register_buffer("W_ff", w_ff)
self.register_buffer("b_ff", b_ff)
linears: list[nn.Linear] = []
prev = self.feature_dim
for _ in range(depth):
linears.append(nn.Linear(prev, hidden))
prev = hidden
linears.append(nn.Linear(prev, out_dim)) # affine readout (no activation)
self.linears = nn.ModuleList(linears)depthattribute
depth: int = depthNumber of hidden (activated) body layers >= 0; depth=0 is a pure random-Fourier-feature model with a linear readout.
num_featuresattribute
num_features: int = num_featuresNumber of Fourier features F *per frequency band*; the encoding width is 2 * F * len(scales).
scalesattribute
scales = scalesPublic instance attribute assigned by __init__. The source assignment is scales = scales; it describes instance state, not a literal constructor default.
feature_dimattribute
feature_dim = 2 * f_totalPublic instance attribute assigned by __init__. The source assignment is feature_dim = 2 * f_total; it describes instance state, not a literal constructor default.
Read the implementation
class FourierFeatureMLP(_JetMLPCore):
r"""Spectral-bias-mitigating MLP: a sin-encoded random Fourier-feature front end.
The input is lifted by the random Fourier-feature map (Tancik et al. 2020)
.. math::
\gamma(x) = \big[\cos(B x),\ \sin(B x)\big], \qquad B \in \mathbb{R}^{F \times D},
and a standard :class:`JetMLP` body acts on ``gamma(x)``. Lifting the input into a
high-frequency basis lets the downstream net represent high-frequency targets with
*low-frequency* weights, which is the standard cure for the low-frequency
(spectral) bias of plain MLPs.
The omnibias twist is that the encoding costs nothing in the differential operator:
because ``cos(z) = sin(z + pi/2)``,
.. math::
\gamma(x) = \sigma_{\sin}\!\big([B;\,B]\,x + [\tfrac{\pi}{2}\mathbf 1;\,\mathbf 0]\big),
i.e. the whole encoding is a *single* omnibias ``sin`` layer with the exact
derivative tower ``sin^{(n)}(z) = sin(z + n pi/2)``. Stacked in front of the body,
the composite ``u(x)`` therefore still yields *every* mixed partial ``D^alpha u(x)``
in closed form through :func:`omnibias.torch.jet_mv.mlp_jet_mv` -- exact, arbitrary
order, no ``torch.autograd.grad`` in the PDE residual.
Parameters
----------
in_dim:
Number of input coordinates ``D``.
num_features:
Number of Fourier features ``F`` *per frequency band*; the encoding width is
``2 * F * len(scales)``.
hidden:
Hidden width of the body MLP.
out_dim:
Number of output components (default 1).
depth:
Number of hidden (activated) body layers ``>= 0``; ``depth=0`` is a pure
random-Fourier-feature model with a linear readout.
base:
Body activation (name or :class:`ActivationSpec`) with a closed-form
derivative fast path (default ``"tanh"``).
frequency_scale:
Bandwidth(s) of the Gaussian frequency matrix. A scalar selects one band with
``B ~ N(0, (2 pi * scale)^2)``; a sequence concatenates several bands for a
*multi-scale* encoding (helps when the target mixes low and high frequencies).
trainable_features:
If ``True`` the frequency matrix / phases are learnable parameters; otherwise
they are fixed buffers (the classic random-features regime).
seed:
Seed for the frequency-matrix draw.
"""
spec: ActivationSpec[Tensor]
sin_spec: ActivationSpec[Tensor]
W_ff: Tensor
b_ff: Tensor
linears: nn.ModuleList
def __init__(
self,
in_dim: int,
num_features: int = 64,
hidden: int = 64,
out_dim: int = 1,
depth: int = 3,
base: str | ActivationSpec[Tensor] = "tanh",
*,
frequency_scale: float | Sequence[float] = 1.0,
trainable_features: bool = False,
seed: int = 0,
) -> None:
super().__init__()
if in_dim < 1:
raise ValueError(f"in_dim must be >= 1, got {in_dim}")
if num_features < 1:
raise ValueError(f"num_features must be >= 1, got {num_features}")
if hidden < 1:
raise ValueError(f"hidden must be >= 1, got {hidden}")
if out_dim < 1:
raise ValueError(f"out_dim must be >= 1, got {out_dim}")
if depth < 0:
raise ValueError(f"depth (hidden layers after encoding) must be >= 0, got {depth}")
scales = _as_scales(frequency_scale)
self.in_dim = in_dim
self.out_dim = out_dim
self.depth = depth
self.num_features = num_features
self.scales = scales
self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
self.sin_spec = get_activation("sin")
gen = torch.Generator().manual_seed(seed)
bands = [
torch.randn(num_features, in_dim, generator=gen) * (2.0 * math.pi * s)
for s in scales
]
b_mat = torch.cat(bands, dim=0) # (F_total, in_dim)
f_total = b_mat.shape[0]
self.feature_dim = 2 * f_total
# sin([B x + pi/2 ; B x]) = [cos(B x) ; sin(B x)]: the encoding is one sin layer.
w_ff = torch.cat([b_mat, b_mat], dim=0) # (2 F_total, in_dim)
b_ff = torch.cat(
[torch.full((f_total,), 0.5 * math.pi), torch.zeros(f_total)]
)
if trainable_features:
self.W_ff = nn.Parameter(w_ff)
self.b_ff = nn.Parameter(b_ff)
else:
self.register_buffer("W_ff", w_ff)
self.register_buffer("b_ff", b_ff)
linears: list[nn.Linear] = []
prev = self.feature_dim
for _ in range(depth):
linears.append(nn.Linear(prev, hidden))
prev = hidden
linears.append(nn.Linear(prev, out_dim)) # affine readout (no activation)
self.linears = nn.ModuleList(linears)
def _layer_specs(
self,
) -> list[tuple[Tensor, Tensor | None, ActivationSpec[Tensor] | None]]:
specs: list[tuple[Tensor, Tensor | None, ActivationSpec[Tensor] | None]] = [
(self.W_ff, self.b_ff, self.sin_spec)
]
n = len(self.linears)
for i, lin in enumerate(self.linears):
assert isinstance(lin, nn.Linear)
act = None if i == n - 1 else self.spec
specs.append((lin.weight, lin.bias, act))
return specsOther public import paths
omnibias.torch.architectures.FourierFeatureMLPomnibias.torch.architectures.pinn.FourierFeatureMLP
JetMLP
omnibias.torch.architectures.JetMLPSource ↗class JetMLP(_JetMLPCore)Deep MLP whose exact input derivatives come from omnibias multivariate jets.
A standard fully-connected network
u(x) = (A_L \circ \sigma \circ A_{L-1} \circ \cdots \circ \sigma \circ A_1)(x),
but *every* mixed input partial D^alpha u(x) up to total order N is
obtained by the supported analytic recurrence (subject to floating-point error) in a single forward pass through
omnibias.torch.jet_mv.mlp_jet_mv -- no torch.autograd.grad stacking,
no finite differences. The number of retained mixed coefficients and the cost of
jet composition grow with dimension and derivative order. This generalises PINNOMBU (single hidden layer, order 2)
to arbitrary depth and order while keeping the closed-form-tower contract: the
base activation must expose a fast-path derivative kernel.
The parameters are ordinary torch.nn.Linear leaves, so training gradients
still flow through the backend autograd / optimiser; omnibias supplies the
*spatial* differential operator, not the optimiser. Derivatives are read out per
collocation point and batched with torch.func.vmap.
Parameters
in_dim: Number of input coordinates D (e.g. 2 for (x, t)).
hidden: Hidden width.
out_dim: Number of output components (default 1).
depth: Number of hidden (activated) layers >= 1; the readout is a pure affine map. depth=1 recovers a single-hidden-layer network like PINNOMBU.
base: Base activation (name or ActivationSpec) with a closed-form derivative fast path (e.g. "tanh", "sigmoid", "softplus").
Parameters
| Name | Type & default | Contract |
|---|---|---|
in_dimpositional-or-keyword | intRequired | Number of input coordinates D (e.g. 2 for (x, t)). |
hiddenpositional-or-keyword | intRequired | Hidden width. |
out_dimpositional-or-keyword | intDefault: 1 | Number of output components (default 1). |
depthpositional-or-keyword | intDefault: 3 | Number of hidden (activated) layers >= 1; the readout is a pure affine map. depth=1 recovers a single-hidden-layer network like PINNOMBU. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'tanh' | Base activation (name or ActivationSpec) with a closed-form derivative fast path (e.g. "tanh", "sigmoid", "softplus"). |
Executed regression · dirichlet interval rejects bad bounds
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_dirichlet_interval_rejects_bad_bounds() -> None:
net = JetMLP(1, 8, 1, depth=2).double()
with pytest.raises(ValueError, match="must exceed"):
dirichlet_interval(net, 1.0, 0.0)python -m pytest "packages/omnibias-torch/tests/test_hardbc.py::test_dirichlet_interval_rejects_bad_bounds" -qRegression example · homogeneous box rejects wrong length
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_homogeneous_box_rejects_wrong_length() -> None:
net = JetMLP(2, 8, 1, depth=2).double()
with pytest.raises(ValueError, match="length in_dim"):
homogeneous_box(net, [0.0], [1.0])python -m pytest packages/omnibias-torch/tests/test_hardbc.py -k test_homogeneous_box_rejects_wrong_length -qFields & methods
__init__method
__init__(self, in_dim: int, hidden: int, out_dim: int=1, depth: int=3, base: str | ActivationSpec[Tensor]='tanh') -> NoneSee the class contract above and the exact method implementation below.
Parameters
| Name | Type & default | Contract |
|---|---|---|
in_dimpositional-or-keyword | intRequired | See the operation contract and implementation below. |
hiddenpositional-or-keyword | intRequired | See the operation contract and implementation below. |
out_dimpositional-or-keyword | intDefault: 1 | See the operation contract and implementation below. |
depthpositional-or-keyword | intDefault: 3 | See the operation contract and implementation below. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'tanh' | See the operation contract and implementation below. |
Returns: None
Validation & failure cases
- When
in_dim < 1, raisesValueError(f'in_dim must be >= 1, got {in_dim}'). - When
hidden < 1, raisesValueError(f'hidden must be >= 1, got {hidden}'). - When
out_dim < 1, raisesValueError(f'out_dim must be >= 1, got {out_dim}'). - When
depth < 1, raisesValueError(f'depth (number of hidden layers) must be >= 1, got {depth}').
Executed regression · dirichlet interval rejects bad bounds
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_dirichlet_interval_rejects_bad_bounds() -> None:
net = JetMLP(1, 8, 1, depth=2).double()
with pytest.raises(ValueError, match="must exceed"):
dirichlet_interval(net, 1.0, 0.0)python -m pytest "packages/omnibias-torch/tests/test_hardbc.py::test_dirichlet_interval_rejects_bad_bounds" -qMethod implementation
def __init__(
self,
in_dim: int,
hidden: int,
out_dim: int = 1,
depth: int = 3,
base: str | ActivationSpec[Tensor] = "tanh",
) -> None:
super().__init__()
if in_dim < 1:
raise ValueError(f"in_dim must be >= 1, got {in_dim}")
if hidden < 1:
raise ValueError(f"hidden must be >= 1, got {hidden}")
if out_dim < 1:
raise ValueError(f"out_dim must be >= 1, got {out_dim}")
if depth < 1:
raise ValueError(f"depth (number of hidden layers) must be >= 1, got {depth}")
self.in_dim = in_dim
self.out_dim = out_dim
self.depth = depth
linears: list[nn.Linear] = []
prev = in_dim
for _ in range(depth):
linears.append(nn.Linear(prev, hidden))
prev = hidden
linears.append(nn.Linear(prev, out_dim)) # affine readout (no activation)
self.linears = nn.ModuleList(linears)
self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)depthattribute
depth: int = depthNumber of hidden (activated) layers >= 1; the readout is a pure affine map. depth=1 recovers a single-hidden-layer network like PINNOMBU.
linearsattribute
linears = nn.ModuleList(linears)Public instance attribute assigned by __init__. The source assignment is linears = nn.ModuleList(linears); it describes instance state, not a literal constructor default.
specattribute
spec = base if isinstance(base, ActivationSpec) else get_activation(base)Public instance attribute assigned by __init__. The source assignment is spec = base if isinstance(base, ActivationSpec) else get_activation(base); it describes instance state, not a literal constructor default.
Read the implementation
class JetMLP(_JetMLPCore):
r"""Deep MLP whose exact input derivatives come from omnibias multivariate jets.
A standard fully-connected network
.. math::
u(x) = (A_L \circ \sigma \circ A_{L-1} \circ \cdots \circ \sigma \circ A_1)(x),
but *every* mixed input partial ``D^alpha u(x)`` up to total order ``N`` is
obtained **exactly** in a single forward pass through
:func:`omnibias.torch.jet_mv.mlp_jet_mv` -- no ``torch.autograd.grad`` stacking,
no finite differences, and a cost independent of the derivative order beyond the
jet truncation. This generalises :class:`PINNOMBU` (single hidden layer, order 2)
to arbitrary depth and order while keeping the closed-form-tower contract: the
base activation must expose a fast-path derivative kernel.
The parameters are ordinary :class:`torch.nn.Linear` leaves, so training gradients
still flow through the backend autograd / optimiser; omnibias supplies the
*spatial* differential operator, not the optimiser. Derivatives are read out per
collocation point and batched with :func:`torch.func.vmap`.
Parameters
----------
in_dim:
Number of input coordinates ``D`` (e.g. 2 for ``(x, t)``).
hidden:
Hidden width.
out_dim:
Number of output components (default 1).
depth:
Number of hidden (activated) layers ``>= 1``; the readout is a pure affine
map. ``depth=1`` recovers a single-hidden-layer network like
:class:`PINNOMBU`.
base:
Base activation (name or :class:`ActivationSpec`) with a closed-form
derivative fast path (e.g. ``"tanh"``, ``"sigmoid"``, ``"softplus"``).
"""
def __init__(
self,
in_dim: int,
hidden: int,
out_dim: int = 1,
depth: int = 3,
base: str | ActivationSpec[Tensor] = "tanh",
) -> None:
super().__init__()
if in_dim < 1:
raise ValueError(f"in_dim must be >= 1, got {in_dim}")
if hidden < 1:
raise ValueError(f"hidden must be >= 1, got {hidden}")
if out_dim < 1:
raise ValueError(f"out_dim must be >= 1, got {out_dim}")
if depth < 1:
raise ValueError(f"depth (number of hidden layers) must be >= 1, got {depth}")
self.in_dim = in_dim
self.out_dim = out_dim
self.depth = depth
linears: list[nn.Linear] = []
prev = in_dim
for _ in range(depth):
linears.append(nn.Linear(prev, hidden))
prev = hidden
linears.append(nn.Linear(prev, out_dim)) # affine readout (no activation)
self.linears = nn.ModuleList(linears)
self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
def _layer_specs(
self,
) -> list[tuple[Tensor, Tensor | None, ActivationSpec[Tensor] | None]]:
"""Assemble the ``(W, b, spec)`` list ``mlp_jet_mv`` consumes (readout = affine)."""
n = len(self.linears)
specs: list[tuple[Tensor, Tensor | None, ActivationSpec[Tensor] | None]] = []
for i, lin in enumerate(self.linears):
assert isinstance(lin, nn.Linear)
act = None if i == n - 1 else self.spec
specs.append((lin.weight, lin.bias, act))
return specsOther public import paths
omnibias.torch.architectures.JetMLPomnibias.torch.architectures.pinn.JetMLP
PINNHeat
omnibias.torch.architectures.PINNHeatSource ↗class PINNHeat(PINNOMBU)1D heat equation u_t = alpha * u_xx solver.
Single hidden layer of width hidden; spatial and temporal
derivatives are computed via PINNOMBU chain-rule helpers,
no autograd-through-derivative.
Parameters
hidden : int, default 64 Number of hidden units (H).
base : str or ActivationSpec, default "softplus" Base activation. Must have a fast-path supporting orders 1 and 2.
alpha : float, default 0.1 Diffusion coefficient.
Parameters
| Name | Type & default | Contract |
|---|---|---|
hiddenpositional-or-keyword | intDefault: 64 | int, default 64 Number of hidden units (H). |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'softplus' | str or ActivationSpec, default "softplus" Base activation. Must have a fast-path supporting orders 1 and 2. |
alphapositional-or-keyword | floatDefault: 0.1 | float, default 0.1 Diffusion coefficient. |
Fields & methods
__init__method
__init__(self, hidden: int=64, base: str | ActivationSpec[Tensor]='softplus', alpha: float=0.1) -> NoneSee the class contract above and the exact method implementation below.
Parameters
| Name | Type & default | Contract |
|---|---|---|
hiddenpositional-or-keyword | intDefault: 64 | See the operation contract and implementation below. |
basepositional-or-keyword | str | ActivationSpec[Tensor]Default: 'softplus' | See the operation contract and implementation below. |
alphapositional-or-keyword | floatDefault: 0.1 | See the operation contract and implementation below. |
Returns: None
Method implementation
def __init__(
self,
hidden: int = 64,
base: str | ActivationSpec[Tensor] = "softplus",
alpha: float = 0.1,
) -> None:
super().__init__()
self.hidden = hidden
self.alpha = alpha
self.W = nn.Linear(2, hidden, bias=True) # input (x, t) -> hidden
self.c = nn.Linear(hidden, 1, bias=True)
self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
self._check_fastpath(max_order=2)forwardmethod
forward(self, x: Tensor, t: Tensor) -> tuple[Tensor, Tensor]Compute (u, residual) at the given collocation points.
Parameters
x, t : Tensor of shape (B,)
Returns
u : Tensor of shape (B,) Network output u(x, t).
residual : Tensor of shape (B,) PDE residual u_t - alpha * u_xx (target zero).
Parameters
| Name | Type & default | Contract |
|---|---|---|
xpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
tpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
Returns: tuple[Tensor, Tensor]
Validation & failure cases
- When
x.shape != t.shape, raisesValueError(f'x and t must have matching shape, got {x.shape} vs {t.shape}.').
Method implementation
def forward(self, x: Tensor, t: Tensor) -> tuple[Tensor, Tensor]:
"""Compute ``(u, residual)`` at the given collocation points.
Parameters
----------
x, t : Tensor of shape ``(B,)``
Returns
-------
u : Tensor of shape ``(B,)``
Network output ``u(x, t)``.
residual : Tensor of shape ``(B,)``
PDE residual ``u_t - alpha * u_xx`` (target zero).
"""
if x.shape != t.shape:
raise ValueError(f"x and t must have matching shape, got {x.shape} vs {t.shape}.")
inp = torch.stack([x, t], dim=-1) # (B, 2); axis 0 is x, axis 1 is t
u, z = self.base_forward(inp) # u: (B, 1), z: (B, H)
u_t = self.first_derivative(z, axis=1) # (B, 1)
u_xx = self.second_derivative(z, axis=0) # (B, 1)
residual = u_t - self.alpha * u_xx
return u.squeeze(-1), residual.squeeze(-1)cattribute
c = nn.Linear(hidden, 1, bias=True)Public instance attribute assigned by __init__. The source assignment is c = nn.Linear(hidden, 1, bias=True); it describes instance state, not a literal constructor default.
specattribute
spec = base if isinstance(base, ActivationSpec) else get_activation(base)Public instance attribute assigned by __init__. The source assignment is spec = base if isinstance(base, ActivationSpec) else get_activation(base); it describes instance state, not a literal constructor default.
Read the implementation
class PINNHeat(PINNOMBU):
"""1D heat equation ``u_t = alpha * u_xx`` solver.
Single hidden layer of width ``hidden``; spatial and temporal
derivatives are computed via :class:`PINNOMBU` chain-rule helpers,
no autograd-through-derivative.
Parameters
----------
hidden : int, default 64
Number of hidden units (``H``).
base : str or :class:`ActivationSpec`, default ``"softplus"``
Base activation. Must have a fast-path supporting orders 1 and 2.
alpha : float, default 0.1
Diffusion coefficient.
"""
def __init__(
self,
hidden: int = 64,
base: str | ActivationSpec[Tensor] = "softplus",
alpha: float = 0.1,
) -> None:
super().__init__()
self.hidden = hidden
self.alpha = alpha
self.W = nn.Linear(2, hidden, bias=True) # input (x, t) -> hidden
self.c = nn.Linear(hidden, 1, bias=True)
self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
self._check_fastpath(max_order=2)
def forward(self, x: Tensor, t: Tensor) -> tuple[Tensor, Tensor]:
"""Compute ``(u, residual)`` at the given collocation points.
Parameters
----------
x, t : Tensor of shape ``(B,)``
Returns
-------
u : Tensor of shape ``(B,)``
Network output ``u(x, t)``.
residual : Tensor of shape ``(B,)``
PDE residual ``u_t - alpha * u_xx`` (target zero).
"""
if x.shape != t.shape:
raise ValueError(f"x and t must have matching shape, got {x.shape} vs {t.shape}.")
inp = torch.stack([x, t], dim=-1) # (B, 2); axis 0 is x, axis 1 is t
u, z = self.base_forward(inp) # u: (B, 1), z: (B, H)
u_t = self.first_derivative(z, axis=1) # (B, 1)
u_xx = self.second_derivative(z, axis=0) # (B, 1)
residual = u_t - self.alpha * u_xx
return u.squeeze(-1), residual.squeeze(-1)Other public import paths
omnibias.torch.architectures.PINNHeatomnibias.torch.architectures.pinn.PINNHeat
PINNOMBU
omnibias.torch.architectures.pinn.PINNOMBUSource ↗class PINNOMBU(nn.Module)Template for closed-form-derivative PINNs.
Subclasses must define:
self.W:nn.Linear(in_dim, hidden)producing the pre-activationz = W . input + b.self.c:nn.Linear(hidden, out_dim)producing the readout.self.spec:ActivationSpecwhosefastpathkernel is not None (i.e. derivative orders 1 and 2 must be available).
The helper methods evaluate the network's value and its partial
derivatives at any given pre-activation z via the chain rule.
Fields & methods
Wfield
W: nn.LinearDeclared nn.Linear field on PINNOMBU. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗cfield
c: nn.LinearDeclared nn.Linear field on PINNOMBU. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗specfield
spec: ActivationSpec[Tensor]Declared ActivationSpec[Tensor] field on PINNOMBU. A value must be provided when constructing the configuration, unless an inherited constructor supplies it.
View source ↗base_forwardmethod
base_forward(self, inp: Tensor) -> tuple[Tensor, Tensor]Compute the value u and the pre-activation z.
Returns
u : Tensor of shape (..., out_dim)
z : Tensor of shape (..., hidden)
Parameters
| Name | Type & default | Contract |
|---|---|---|
inppositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
Returns: tuple[Tensor, Tensor]
Method implementation
def base_forward(self, inp: Tensor) -> tuple[Tensor, Tensor]:
"""Compute the value ``u`` and the pre-activation ``z``.
Returns
-------
u : Tensor of shape ``(..., out_dim)``
z : Tensor of shape ``(..., hidden)``
"""
z = self.W(inp)
u_h = self.spec.forward(z)
u = self.c(u_h)
return u, zfirst_derivativemethod
first_derivative(self, z: Tensor, axis: int) -> Tensordu / dx_{axis} evaluated via the chain rule.
Returns a tensor of shape (..., out_dim).
Parameters
| Name | Type & default | Contract |
|---|---|---|
zpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
axispositional-or-keyword | intRequired | See the operation contract and implementation below. |
Returns: Tensor
Method implementation
def first_derivative(self, z: Tensor, axis: int) -> Tensor:
"""``du / dx_{axis}`` evaluated via the chain rule.
Returns a tensor of shape ``(..., out_dim)``.
"""
sigma_p = self._fp(z, 1) # (..., H)
W_axis = self.W.weight[:, axis] # (H,)
c_w = self.c.weight # (out_dim, H)
weighted = sigma_p * W_axis # (..., H)
out: Tensor = weighted @ c_w.T # (..., out_dim)
return outsecond_derivativemethod
second_derivative(self, z: Tensor, axis: int) -> Tensord^2 u / dx_{axis}^2 evaluated via the chain rule.
Parameters
| Name | Type & default | Contract |
|---|---|---|
zpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
axispositional-or-keyword | intRequired | See the operation contract and implementation below. |
Returns: Tensor
Method implementation
def second_derivative(self, z: Tensor, axis: int) -> Tensor:
"""``d^2 u / dx_{axis}^2`` evaluated via the chain rule."""
sigma_pp = self._fp(z, 2)
W_axis = self.W.weight[:, axis]
c_w = self.c.weight
weighted = sigma_pp * (W_axis * W_axis)
out: Tensor = weighted @ c_w.T
return outmixed_second_derivativemethod
mixed_second_derivative(self, z: Tensor, axis_a: int, axis_b: int) -> Tensord^2 u / dx_a dx_b.
Parameters
| Name | Type & default | Contract |
|---|---|---|
zpositional-or-keyword | TensorRequired | See the operation contract and implementation below. |
axis_apositional-or-keyword | intRequired | See the operation contract and implementation below. |
axis_bpositional-or-keyword | intRequired | See the operation contract and implementation below. |
Returns: Tensor
Method implementation
def mixed_second_derivative(self, z: Tensor, axis_a: int, axis_b: int) -> Tensor:
"""``d^2 u / dx_a dx_b``."""
sigma_pp = self._fp(z, 2)
Wa = self.W.weight[:, axis_a]
Wb = self.W.weight[:, axis_b]
c_w = self.c.weight
weighted = sigma_pp * (Wa * Wb)
out: Tensor = weighted @ c_w.T
return outRead the implementation
class PINNOMBU(nn.Module):
"""Template for closed-form-derivative PINNs.
Subclasses must define:
- ``self.W``: ``nn.Linear(in_dim, hidden)`` producing the pre-activation
``z = W . input + b``.
- ``self.c``: ``nn.Linear(hidden, out_dim)`` producing the readout.
- ``self.spec``: :class:`ActivationSpec` whose ``fastpath`` kernel is
not None (i.e. derivative orders 1 and 2 must be available).
The helper methods evaluate the network's value and its partial
derivatives at any given pre-activation ``z`` via the chain rule.
"""
W: nn.Linear
c: nn.Linear
spec: ActivationSpec[Tensor]
def _check_fastpath(self, max_order: int) -> None:
if self.spec.fastpath is None:
raise ValueError(
f"PINNOMBU requires a base activation with a closed-form "
f"derivative kernel; activation {self.spec.name!r} has none."
)
try:
self.spec.fastpath(torch.zeros(1), max_order)
except NotImplementedError as e:
raise ValueError(
f"Activation {self.spec.name!r} fast-path does not support order {max_order}: {e}"
) from None
def _fp(self, z: Tensor, order: int) -> Tensor:
"""Internal fast-path call with ``None`` guarded out by ``_check_fastpath``."""
fp = self.spec.fastpath
assert fp is not None, "fastpath checked at __init__"
out: Tensor = fp(z, order)
return out
def base_forward(self, inp: Tensor) -> tuple[Tensor, Tensor]:
"""Compute the value ``u`` and the pre-activation ``z``.
Returns
-------
u : Tensor of shape ``(..., out_dim)``
z : Tensor of shape ``(..., hidden)``
"""
z = self.W(inp)
u_h = self.spec.forward(z)
u = self.c(u_h)
return u, z
def first_derivative(self, z: Tensor, axis: int) -> Tensor:
"""``du / dx_{axis}`` evaluated via the chain rule.
Returns a tensor of shape ``(..., out_dim)``.
"""
sigma_p = self._fp(z, 1) # (..., H)
W_axis = self.W.weight[:, axis] # (H,)
c_w = self.c.weight # (out_dim, H)
weighted = sigma_p * W_axis # (..., H)
out: Tensor = weighted @ c_w.T # (..., out_dim)
return out
def second_derivative(self, z: Tensor, axis: int) -> Tensor:
"""``d^2 u / dx_{axis}^2`` evaluated via the chain rule."""
sigma_pp = self._fp(z, 2)
W_axis = self.W.weight[:, axis]
c_w = self.c.weight
weighted = sigma_pp * (W_axis * W_axis)
out: Tensor = weighted @ c_w.T
return out
def mixed_second_derivative(self, z: Tensor, axis_a: int, axis_b: int) -> Tensor:
"""``d^2 u / dx_a dx_b``."""
sigma_pp = self._fp(z, 2)
Wa = self.W.weight[:, axis_a]
Wb = self.W.weight[:, axis_b]
c_w = self.c.weight
weighted = sigma_pp * (Wa * Wb)
out: Tensor = weighted @ c_w.T
return outmake_siren
omnibias.torch.architectures.make_sirenSource ↗make_siren(in_dim: int, hidden: int, out_dim: int=1, depth: int=3, *, omega_0: float=30.0, seed: int=0) -> JetMLPBuild a SIREN (Sitzmann et al. 2020) as an omnibias JetMLP.
A SIREN is an MLP with sin activations and a specific initialisation that keeps
the pre-activation distribution stable across depth. Because the base activation is
sin, every input derivative is exact for *all* orders via the closed-form tower
sin^{(n)}(z) = sin(z + n pi/2) -- so a SIREN trained here has bit-stable,
arbitrary-order derivatives with no autodiff in the differential operator.
The first layer's frequency is scaled by omega_0 (folded into its weights),
weights are drawn U(-1/fan_in, 1/fan_in) for the first layer and
U(-sqrt(6/fan_in)/omega_0, +sqrt(6/fan_in)/omega_0) thereafter, and all biases
start at zero -- the standard SIREN scheme.
Parameters
in_dim, hidden, out_dim, depth: As for JetMLP (depth >= 1).
omega_0: First-layer frequency scale (default 30, the SIREN default).
seed: Seed for the weight draw.
Parameters
| Name | Type & default | Contract |
|---|---|---|
in_dimpositional-or-keyword | intRequired | As for JetMLP (depth >= 1). |
hiddenpositional-or-keyword | intRequired | See the operation contract and implementation below. |
out_dimpositional-or-keyword | intDefault: 1 | See the operation contract and implementation below. |
depthpositional-or-keyword | intDefault: 3 | See the operation contract and implementation below. |
omega_0keyword-only | floatDefault: 30.0 | First-layer frequency scale (default 30, the SIREN default). |
seedkeyword-only | intDefault: 0 | Seed for the weight draw. |
JetMLPExplicit validation & failure cases
- When
omega_0 <= 0.0, raisesValueError(f'omega_0 must be > 0, got {omega_0}').
These are explicit source guards. Backend operations may also raise for invalid shapes, dtypes, or unsupported devices.
Executed regression · make siren invalid omega raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_make_siren_invalid_omega_raises() -> None:
with pytest.raises(ValueError):
make_siren(1, hidden=8, depth=2, omega_0=0.0)python -m pytest "packages/omnibias-torch/tests/test_spectral_pinn.py::test_make_siren_invalid_omega_raises" -qRegression example · fourier features buffer by default
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_fourier_features_buffer_by_default() -> None:
net = FourierFeatureMLP(2, num_features=4, hidden=8, depth=1, seed=8)
names = dict(net.named_buffers())
assert "W_ff" in names and "b_ff" in names
assert "W_ff" not in dict(net.named_parameters())python -m pytest packages/omnibias-torch/tests/test_spectral_pinn.py -k test_fourier_features_buffer_by_default -qRead the implementation
def make_siren(
in_dim: int,
hidden: int,
out_dim: int = 1,
depth: int = 3,
*,
omega_0: float = 30.0,
seed: int = 0,
) -> JetMLP:
r"""Build a SIREN (Sitzmann et al. 2020) as an omnibias :class:`JetMLP`.
A SIREN is an MLP with ``sin`` activations and a specific initialisation that keeps
the pre-activation distribution stable across depth. Because the base activation is
``sin``, every input derivative is exact for *all* orders via the closed-form tower
``sin^{(n)}(z) = sin(z + n pi/2)`` -- so a SIREN trained here has bit-stable,
arbitrary-order derivatives with no autodiff in the differential operator.
The first layer's frequency is scaled by ``omega_0`` (folded into its weights),
weights are drawn ``U(-1/fan_in, 1/fan_in)`` for the first layer and
``U(-sqrt(6/fan_in)/omega_0, +sqrt(6/fan_in)/omega_0)`` thereafter, and all biases
start at zero -- the standard SIREN scheme.
Parameters
----------
in_dim, hidden, out_dim, depth:
As for :class:`JetMLP` (``depth >= 1``).
omega_0:
First-layer frequency scale (default ``30``, the SIREN default).
seed:
Seed for the weight draw.
"""
if omega_0 <= 0.0:
raise ValueError(f"omega_0 must be > 0, got {omega_0}")
net = JetMLP(in_dim, hidden, out_dim=out_dim, depth=depth, base="sin")
gen = torch.Generator().manual_seed(seed)
with torch.no_grad():
for i, lin in enumerate(net.linears):
assert isinstance(lin, nn.Linear)
fan_in = int(lin.weight.shape[1])
if i == 0:
bound = 1.0 / fan_in
lin.weight.uniform_(-bound, bound, generator=gen)
lin.weight.mul_(omega_0) # fold omega_0 into the first-layer frequency
else:
bound = math.sqrt(6.0 / fan_in) / omega_0
lin.weight.uniform_(-bound, bound, generator=gen)
if lin.bias is not None:
lin.bias.zero_()
return netOther public import paths
omnibias.torch.architectures.make_sirenomnibias.torch.architectures.pinn.make_siren