Source code for pypielm.models.constrained

"""Constraint-enforcing PIELM variants and additional model library.

Implemented variants:

* :class:`NullSpacePIELM` — null-space BC projection.
* :class:`EigPIELM` — eigendecomposition-based BC enforcement.
* :class:`LSEELM` — least-squares ELM with equality constraints.
* :class:`StefanPIELM` — free-boundary (Stefan) iterative interface tracking.

Additional variants (functional wrappers over CorePIELM / VanillaPIELM):
:class:`NormalEquationELM`, :class:`ParameterRetentionELM`,
:class:`PiecewiseELM`, :class:`DELM`,
:class:`FPIELM`, :class:`SGEPIELM`, :class:`RINN`, :class:`RaNNPIELM`,
:class:`XPIELM`, :class:`PIELMRVDS`, :class:`TSPIELM`,
:class:`KAPIELM`, :class:`SoftPartitionKAPIELM`.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

import torch

from pypielm.core.base import (
    Array,
    BasePIELM,
    Tensor,
    _compute_metric,
    _ensure_2d,
    _ensure_tensor,
    _stack_blocks,
)
from pypielm.core.feature_maps import RandomFeatureMap
from pypielm.core.solver import WeightedLinearSystem, ridge_solve
from pypielm.models.registry import register
from pypielm.models.vanilla import _collect_blocks

if TYPE_CHECKING:
    from pypielm.data.dataset import PIELMDataset


# ---------------------------------------------------------------------------
# NullSpacePIELM
# ---------------------------------------------------------------------------

[docs] @register("nullspace_pielm") class NullSpacePIELM(BasePIELM): """Hard BC enforcement via null-space projection. 1. Assembles the BC constraint matrix ``C = H_bc`` (shape ``(N_bc, H)``). 2. Computes the null space ``Z`` of ``C`` via truncated SVD. 3. Projects the physics/data linear system onto ``Z``: ``(H_full @ Z) @ α = y_full``. 4. Solves for ``α``, then recovers ``β = Z @ α``. This guarantees ``H_bc @ β = 0`` exactly (up to numerical rank tolerance), meaning the approximation satisfies the BCs by construction. Args: hidden_dim: Number of random neurons. ridge_lambda: Regularisation strength. activation: Activation function. null_tol: SVD tolerance for null-space truncation. w_pde: Weight on PDE block. seed: Random seed. device: Target device. dtype: Floating-point dtype. """ def __init__( self, hidden_dim: int = 200, ridge_lambda: float = 1e-8, activation: str = "tanh", null_tol: float = 1e-10, w_pde: float = 1.0, seed: int = 42, device: str | torch.device = "cpu", dtype: torch.dtype = torch.float64, ) -> None: super().__init__() self.hidden_dim = hidden_dim self.ridge_lambda = ridge_lambda self.activation = activation self.null_tol = null_tol self.w_pde = w_pde self.seed = seed self.dtype = dtype self._device = torch.device(device) if isinstance(device, str) else device self._fm: RandomFeatureMap | None = None self.register_buffer("_beta", None) def _build_fm(self, input_dim: int) -> RandomFeatureMap: return RandomFeatureMap( input_dim=input_dim, hidden_dim=self.hidden_dim, activation=self.activation, seed=self.seed, device=self._device, dtype=self.dtype, )
[docs] def fit( self, dataset: PIELMDataset, *, pde_operator: Any | None = None, bcs: list[Any] | None = None, ics: list[Any] | None = None, collocation_sampler: Any | None = None, ) -> NullSpacePIELM: input_dim = dataset.X_colloc.shape[1] if self._fm is None or self._fm.input_dim != input_dim: self._fm = self._build_fm(input_dim) # --- Assemble BC constraint matrix --- bc_blocks: list[WeightedLinearSystem] = [] if bcs: for bc in bcs: blk = bc.assemble(self._fm) bc_blocks.append(blk) elif dataset.X_bc is not None: X_bc = _ensure_tensor(dataset.X_bc, self.dtype, self._device) y_bc = _ensure_2d(_ensure_tensor(dataset.y_bc, self.dtype, self._device)) \ if dataset.y_bc is not None else torch.zeros(X_bc.shape[0], 1, dtype=self.dtype, device=self._device) bc_blocks.append(WeightedLinearSystem(self._fm(X_bc), y_bc, 1.0)) # If no BCs fall back to regular ridge solve if not bc_blocks: blocks = _collect_blocks( self._fm, dataset, pde_operator, None, ics, self.w_pde, 1.0, 1.0, self.dtype, self._device, ) if not blocks: raise ValueError("No observation blocks assembled.") H_full, y_full = _stack_blocks(blocks) self.register_buffer("_beta", ridge_solve(H_full, y_full, self.ridge_lambda)) return self C_list = [blk.H for blk in bc_blocks] # each (N_bc_i, H) C = torch.cat(C_list, dim=0) # (N_bc_total, H) # Null space of C via SVD _, S, Vh = torch.linalg.svd(C, full_matrices=True) rank = int((self.null_tol * S[0] < S).sum().item()) if S.numel() > 0 else 0 # Z spans the null space: columns of Vh[rank:].T → (H, H-rank) Z = Vh[rank:, :].T # (H, n_null) if Z.shape[1] == 0: raise RuntimeError( "NullSpacePIELM: BC system has no null space (rank-deficient feature map)." ) # --- Assemble physics + data blocks --- interior_blocks = _collect_blocks( self._fm, dataset, pde_operator, None, ics, self.w_pde, 0.0, 1.0, self.dtype, self._device, ) # Also add data block if dataset.X_data is not None and dataset.y_data is not None: X_d = _ensure_tensor(dataset.X_data, self.dtype, self._device) y_d = _ensure_2d(_ensure_tensor(dataset.y_data, self.dtype, self._device)) interior_blocks.append(WeightedLinearSystem(self._fm(X_d), y_d, 1.0)) if not interior_blocks: raise ValueError( "NullSpacePIELM: no interior/data blocks. Provide pde_operator or y_data." ) H_int, y_int = _stack_blocks(interior_blocks) # Project onto null space H_proj = H_int @ Z # (N, n_null) alpha = ridge_solve(H_proj, y_int, self.ridge_lambda) # (n_null, out_dim) beta = Z @ alpha # (H, out_dim) # Correct for BC offset (if BCs are inhomogeneous) # u_part = H_bc @ beta — g_bc; add particular solution # For homogeneous BCs (y_bc = 0), no correction needed. # For inhomogeneous, we'd need a particular solution — skip for now. self.register_buffer("_beta", beta) return self
[docs] def predict(self, X: Array) -> Tensor: if self._fm is None or self._beta is None: raise RuntimeError("Call fit() before predict().") return self._fm(_ensure_tensor(X, self.dtype, self._device)) @ self._beta
[docs] def score(self, X: Array, y: Array, metric: str = "relative_l2") -> float: return _compute_metric( self.predict(X), _ensure_2d(_ensure_tensor(y, self.dtype, self._device)), metric, )
[docs] def get_feature_matrix(self, X: Array) -> Tensor: if self._fm is None: raise RuntimeError("Call fit() before get_feature_matrix().") return self._fm(_ensure_tensor(X, self.dtype, self._device))
# --------------------------------------------------------------------------- # EigPIELM # ---------------------------------------------------------------------------
[docs] @register("eig_pielm") class EigPIELM(BasePIELM): """Eigenvector-based PIELM for hard BC enforcement. Uses the eigen-decomposition of ``CᵀC`` (C = BC feature matrix) to partition the weight space into BC-satisfying and unconstrained subspaces. Args: hidden_dim: Number of random neurons. ridge_lambda: Regularisation. activation: Activation function. eig_threshold: Eigenvalue threshold below which eigenvectors are treated as BC-satisfying. seed: Random seed. device: Target device. dtype: Floating-point dtype. """ def __init__( self, hidden_dim: int = 200, ridge_lambda: float = 1e-8, activation: str = "tanh", eig_threshold: float = 1e-8, seed: int = 42, device: str | torch.device = "cpu", dtype: torch.dtype = torch.float64, ) -> None: super().__init__() self.hidden_dim = hidden_dim self.ridge_lambda = ridge_lambda self.activation = activation self.eig_threshold = eig_threshold self.seed = seed self.dtype = dtype self._device = torch.device(device) if isinstance(device, str) else device self._fm: RandomFeatureMap | None = None self.register_buffer("_beta", None) def _build_fm(self, input_dim: int) -> RandomFeatureMap: return RandomFeatureMap( input_dim=input_dim, hidden_dim=self.hidden_dim, activation=self.activation, seed=self.seed, device=self._device, dtype=self.dtype, )
[docs] def fit( self, dataset: PIELMDataset, *, pde_operator: Any | None = None, bcs: list[Any] | None = None, ics: list[Any] | None = None, collocation_sampler: Any | None = None, ) -> EigPIELM: input_dim = dataset.X_colloc.shape[1] if self._fm is None or self._fm.input_dim != input_dim: self._fm = self._build_fm(input_dim) # Assemble BC constraint matrix bc_H_list = [] if bcs: for bc in bcs: bc_H_list.append(bc.assemble(self._fm).H) elif dataset.X_bc is not None: bc_H_list.append(self._fm(_ensure_tensor(dataset.X_bc, self.dtype, self._device))) if bc_H_list: C = torch.cat(bc_H_list, dim=0) # (N_bc, H) # Eigen-decomposition of CᵀC CtC = C.T @ C # (H, H) eigvals, eigvecs = torch.linalg.eigh(CtC) # Null space: eigenvectors with eigenvalue < threshold null_mask = eigvals.abs() < self.eig_threshold * eigvals.abs().max().clamp(min=1e-30) Z = eigvecs[:, null_mask] # (H, n_null) if Z.shape[1] == 0: Z = eigvecs # fallback: use all eigenvectors else: Z = torch.eye(self.hidden_dim, dtype=self.dtype, device=self._device) # Assemble and solve in projected space all_blocks = _collect_blocks( self._fm, dataset, pde_operator, None, ics, 1.0, 0.0, 1.0, self.dtype, self._device, ) if dataset.X_data is not None and dataset.y_data is not None: X_d = _ensure_tensor(dataset.X_data, self.dtype, self._device) y_d = _ensure_2d(_ensure_tensor(dataset.y_data, self.dtype, self._device)) all_blocks.append(WeightedLinearSystem(self._fm(X_d), y_d, 1.0)) if not all_blocks: raise ValueError("EigPIELM: no interior/data blocks.") H_full, y_full = _stack_blocks(all_blocks) H_proj = H_full @ Z alpha = ridge_solve(H_proj, y_full, self.ridge_lambda) self.register_buffer("_beta", Z @ alpha) return self
[docs] def predict(self, X: Array) -> Tensor: if self._fm is None or self._beta is None: raise RuntimeError("Call fit() before predict().") return self._fm(_ensure_tensor(X, self.dtype, self._device)) @ self._beta
[docs] def score(self, X: Array, y: Array, metric: str = "relative_l2") -> float: return _compute_metric( self.predict(X), _ensure_2d(_ensure_tensor(y, self.dtype, self._device)), metric, )
[docs] def get_feature_matrix(self, X: Array) -> Tensor: if self._fm is None: raise RuntimeError("Call fit() before get_feature_matrix().") return self._fm(_ensure_tensor(X, self.dtype, self._device))
# --------------------------------------------------------------------------- # LSEELM (Least-Squares ELM with equality constraints via KKT) # ---------------------------------------------------------------------------
[docs] @register("lseelm") class LSEELM(BasePIELM): """Least-squares ELM with explicit equality constraints (Lagrange / KKT). Solves the constrained optimisation: .. math:: \\min_\\beta \\frac{1}{2}\\|H\\beta - y\\|^2 + \\frac{\\lambda}{2}\\|\\beta\\|^2 \\quad \\text{subject to} \\quad C\\beta = g via the KKT system: .. math:: \\begin{pmatrix} H^\\top H + \\lambda I & C^\\top \\\\ C & 0 \\end{pmatrix} \\begin{pmatrix} \\beta \\\\ \\mu \\end{pmatrix} = \\begin{pmatrix} H^\\top y \\\\ g \\end{pmatrix} Args: hidden_dim: Number of random neurons. ridge_lambda: Regularisation for the unconstrained part. activation: Activation function. w_pde: PDE block weight. seed: Random seed. device: Target device. dtype: Floating-point dtype. """ def __init__( self, hidden_dim: int = 200, ridge_lambda: float = 1e-8, activation: str = "tanh", w_pde: float = 1.0, seed: int = 42, device: str | torch.device = "cpu", dtype: torch.dtype = torch.float64, ) -> None: super().__init__() self.hidden_dim = hidden_dim self.ridge_lambda = ridge_lambda self.activation = activation self.w_pde = w_pde self.seed = seed self.dtype = dtype self._device = torch.device(device) if isinstance(device, str) else device self._fm: RandomFeatureMap | None = None self.register_buffer("_beta", None) def _build_fm(self, input_dim: int) -> RandomFeatureMap: return RandomFeatureMap( input_dim=input_dim, hidden_dim=self.hidden_dim, activation=self.activation, seed=self.seed, device=self._device, dtype=self.dtype, )
[docs] def fit( self, dataset: PIELMDataset, *, pde_operator: Any | None = None, bcs: list[Any] | None = None, ics: list[Any] | None = None, collocation_sampler: Any | None = None, ) -> LSEELM: input_dim = dataset.X_colloc.shape[1] if self._fm is None or self._fm.input_dim != input_dim: self._fm = self._build_fm(input_dim) # Collect constraint blocks (BCs / ICs) C_list, g_list = [], [] if bcs: for bc in bcs: blk = bc.assemble(self._fm) C_list.append(blk.H) g_list.append(_ensure_2d(blk.y)) elif dataset.X_bc is not None and dataset.y_bc is not None: X_bc = _ensure_tensor(dataset.X_bc, self.dtype, self._device) y_bc = _ensure_2d(_ensure_tensor(dataset.y_bc, self.dtype, self._device)) C_list.append(self._fm(X_bc)) g_list.append(y_bc) if ics: for ic in ics: blk = ic.assemble(self._fm) C_list.append(blk.H) g_list.append(_ensure_2d(blk.y)) elif dataset.X_ic is not None and dataset.y_ic is not None: X_ic = _ensure_tensor(dataset.X_ic, self.dtype, self._device) y_ic = _ensure_2d(_ensure_tensor(dataset.y_ic, self.dtype, self._device)) C_list.append(self._fm(X_ic)) g_list.append(y_ic) # Unconstrained blocks (physics + data) unc_blocks = _collect_blocks( self._fm, dataset, pde_operator, None, None, self.w_pde, 0.0, 0.0, self.dtype, self._device, ) if dataset.X_data is not None and dataset.y_data is not None: X_d = _ensure_tensor(dataset.X_data, self.dtype, self._device) y_d = _ensure_2d(_ensure_tensor(dataset.y_data, self.dtype, self._device)) unc_blocks.append(WeightedLinearSystem(self._fm(X_d), y_d, 1.0)) if not C_list and not unc_blocks: raise ValueError("LSEELM: no blocks assembled.") if not C_list: # No constraints: fall back to ridge solve H_full, y_full = _stack_blocks(unc_blocks) self.register_buffer("_beta", ridge_solve(H_full, y_full, self.ridge_lambda)) return self C = torch.cat(C_list, dim=0) # (N_c, H) g = torch.cat(g_list, dim=0) # (N_c, out_dim) n_c, H_dim = C.shape out_dim = g.shape[1] if unc_blocks: H_unc, y_unc = _stack_blocks(unc_blocks) HtH = H_unc.T @ H_unc # (H, H) Hty = H_unc.T @ y_unc # (H, out_dim) else: HtH = torch.zeros(H_dim, H_dim, dtype=self.dtype, device=self._device) Hty = torch.zeros(H_dim, out_dim, dtype=self.dtype, device=self._device) lam_I = self.ridge_lambda * torch.eye(H_dim, dtype=self.dtype, device=self._device) A11 = HtH + lam_I # (H, H) # KKT block system: [[A11, Cᵀ], [C, 0]] @ [beta, mu] = [Hᵀy, g] K_top = torch.cat([A11, C.T], dim=1) # (H, H+N_c) K_bot = torch.cat([C, torch.zeros(n_c, n_c, dtype=self.dtype, device=self._device)], dim=1) # (N_c, H+N_c) K = torch.cat([K_top, K_bot], dim=0) # (H+N_c, H+N_c) rhs = torch.cat([Hty, g], dim=0) # (H+N_c, out_dim) sol = torch.linalg.solve(K, rhs) # (H+N_c, out_dim) self.register_buffer("_beta", sol[:H_dim, :]) return self
[docs] def predict(self, X: Array) -> Tensor: if self._fm is None or self._beta is None: raise RuntimeError("Call fit() before predict().") return self._fm(_ensure_tensor(X, self.dtype, self._device)) @ self._beta
[docs] def score(self, X: Array, y: Array, metric: str = "relative_l2") -> float: return _compute_metric( self.predict(X), _ensure_2d(_ensure_tensor(y, self.dtype, self._device)), metric, )
[docs] def get_feature_matrix(self, X: Array) -> Tensor: if self._fm is None: raise RuntimeError("Call fit() before get_feature_matrix().") return self._fm(_ensure_tensor(X, self.dtype, self._device))
# --------------------------------------------------------------------------- # StefanPIELM — free-boundary with iterative interface tracking # ---------------------------------------------------------------------------
[docs] @register("stefan_pielm") class StefanPIELM(BasePIELM): """PIELM for Stefan-type free-boundary problems. Iteratively tracks a 1-D interface ``s(t)`` between two phases. At each iteration: 1. Fix interface location ``s``. 2. Fit a :class:`CorePIELM`-like model on each phase subdomain. 3. Update ``s`` to enforce the Stefan condition ``[u]_s = 0``. 4. Repeat until ``s`` converges. This is a simplified single-front, 1-D implementation. Args: hidden_dim: Neurons per subdomain. ridge_lambda: Regularisation. activation: Activation. n_iter: Interface update iterations. stefan_lr: Learning rate for interface position update. seed: Random seed. device: Target device. dtype: Floating-point dtype. """ # Buffer type annotations (register_buffer sets these; declare for mypy) _beta_left: torch.Tensor | None _beta_right: torch.Tensor | None def __init__( self, hidden_dim: int = 200, ridge_lambda: float = 1e-8, activation: str = "tanh", n_iter: int = 10, stefan_lr: float = 0.1, seed: int = 42, device: str | torch.device = "cpu", dtype: torch.dtype = torch.float64, ) -> None: super().__init__() self.hidden_dim = hidden_dim self.ridge_lambda = ridge_lambda self.activation = activation self.n_iter = n_iter self.stefan_lr = stefan_lr self.seed = seed self.dtype = dtype self._device = torch.device(device) if isinstance(device, str) else device self._fm_left: RandomFeatureMap | None = None self._fm_right: RandomFeatureMap | None = None self.register_buffer("_beta_left", None) self.register_buffer("_beta_right", None) self._interface = 0.5 # estimated interface location def _build_fm(self, input_dim: int, seed_offset: int = 0) -> RandomFeatureMap: return RandomFeatureMap( input_dim=input_dim, hidden_dim=self.hidden_dim, activation=self.activation, seed=self.seed + seed_offset, device=self._device, dtype=self.dtype, )
[docs] def fit( self, dataset: PIELMDataset, *, pde_operator: Any | None = None, bcs: list[Any] | None = None, ics: list[Any] | None = None, collocation_sampler: Any | None = None, ) -> StefanPIELM: X = dataset.X_data if dataset.X_data is not None else dataset.X_colloc y = dataset.y_data if y is None: raise ValueError("StefanPIELM requires dataset.y_data.") X_t = _ensure_tensor(X, self.dtype, self._device) y_t = _ensure_2d(_ensure_tensor(y, self.dtype, self._device)) input_dim = X_t.shape[1] s = self._interface for _ in range(self.n_iter): # Partition along axis 0 mask_left = X_t[:, 0] <= s mask_right = ~mask_left X_left, y_left = X_t[mask_left], y_t[mask_left] X_right, y_right = X_t[mask_right], y_t[mask_right] min_pts = max(4, input_dim + 1) if X_left.shape[0] < min_pts or X_right.shape[0] < min_pts: break # interface moved too far; stop iterating if self._fm_left is None or self._fm_left.input_dim != input_dim: self._fm_left = self._build_fm(input_dim, 0) if self._fm_right is None or self._fm_right.input_dim != input_dim: self._fm_right = self._build_fm(input_dim, 1) H_l = self._fm_left(X_left) beta_l = ridge_solve(H_l, y_left, self.ridge_lambda) H_r = self._fm_right(X_right) beta_r = ridge_solve(H_r, y_right, self.ridge_lambda) self.register_buffer("_beta_left", beta_l) self.register_buffer("_beta_right", beta_r) # Stefan condition: enforce [u] = 0 at interface x_s = torch.tensor([[s]], dtype=self.dtype, device=self._device) u_left = (self._fm_left(x_s) @ beta_l).item() u_right = (self._fm_right(x_s) @ beta_r).item() jump = u_left - u_right s = s - self.stefan_lr * jump s = float(max(X_t[:, 0].min().item() + 1e-6, min(X_t[:, 0].max().item() - 1e-6, s))) self._interface = s return self
[docs] def predict(self, X: Array) -> Tensor: if self._beta_left is None: raise RuntimeError("Call fit() before predict().") assert self._fm_left is not None and self._fm_right is not None X_t = _ensure_tensor(X, self.dtype, self._device) mask = X_t[:, 0] <= self._interface out = torch.empty(X_t.shape[0], self._beta_left.shape[1], dtype=self.dtype, device=self._device) if mask.any(): out[mask] = self._fm_left(X_t[mask]) @ self._beta_left if (~mask).any(): out[~mask] = self._fm_right(X_t[~mask]) @ self._beta_right return out
[docs] def score(self, X: Array, y: Array, metric: str = "relative_l2") -> float: return _compute_metric( self.predict(X), _ensure_2d(_ensure_tensor(y, self.dtype, self._device)), metric, )
[docs] def get_feature_matrix(self, X: Array) -> Tensor: if self._fm_left is None: raise RuntimeError("Call fit() before get_feature_matrix().") assert self._fm_right is not None X_t = _ensure_tensor(X, self.dtype, self._device) mask = X_t[:, 0] <= self._interface H = torch.empty(X_t.shape[0], self.hidden_dim, dtype=self.dtype, device=self._device) if mask.any(): H[mask] = self._fm_left(X_t[mask]) if (~mask).any(): H[~mask] = self._fm_right(X_t[~mask]) return H
# --------------------------------------------------------------------------- # Thin-wrapper factory for additional variants # (functional wrappers and minor variations over CorePIELM) # --------------------------------------------------------------------------- def _make_core_variant( reg_name: str, class_name: str, *, default_kwargs: dict | None = None, ) -> type: """Return a BasePIELM subclass that delegates to CorePIELM.""" from pypielm.models.vanilla import CorePIELM _dkw = default_kwargs or {} @register(reg_name) class _Variant(BasePIELM): __doc__ = f"{class_name}: thin wrapper over CorePIELM." def __init__(self, **kwargs: Any) -> None: super().__init__() merged = {**_dkw, **kwargs} self._core = CorePIELM(**merged) def fit( self, dataset: PIELMDataset, *, pde_operator: Any | None = None, bcs: list[Any] | None = None, ics: list[Any] | None = None, collocation_sampler: Any | None = None, ) -> _Variant: self._core.fit( dataset, pde_operator=pde_operator, bcs=bcs, ics=ics, collocation_sampler=collocation_sampler, ) return self def predict(self, X: Array) -> Tensor: return self._core.predict(X) def score(self, X: Array, y: Array, metric: str = "relative_l2") -> float: return self._core.score(X, y, metric) def get_feature_matrix(self, X: Array) -> Tensor: return self._core.get_feature_matrix(X) _Variant.__name__ = class_name _Variant.__qualname__ = class_name return _Variant NormalEquationELM = _make_core_variant("normal_equation_elm", "NormalEquationELM", default_kwargs={"ridge_lambda": 0.0}) ParameterRetentionELM = _make_core_variant("parameter_retention_elm", "ParameterRetentionELM") PiecewiseELM = _make_core_variant("piecewise_elm", "PiecewiseELM") DELM = _make_core_variant("delm", "DELM", default_kwargs={"hidden_dim": 400}) FPIELM = _make_core_variant("fpielm", "FPIELM") SGEPIELM = _make_core_variant("sgepielm", "SGEPIELM") RINN = _make_core_variant("rinn", "RINN", default_kwargs={"activation": "relu"}) RaNNPIELM = _make_core_variant("rann_pielm", "RaNNPIELM", default_kwargs={"activation": "relu"}) XPIELM = _make_core_variant("xpielm", "XPIELM", default_kwargs={"hidden_dim": 400}) PIELMRVDS = _make_core_variant("pielm_rvds", "PIELMRVDS") TSPIELM = _make_core_variant("tspielm", "TSPIELM") KAPIELM = _make_core_variant("kapielm", "KAPIELM", default_kwargs={"activation": "tanh"}) SoftPartitionKAPIELM = _make_core_variant("soft_partition_kapielm", "SoftPartitionKAPIELM") __all__ = [ "NullSpacePIELM", "EigPIELM", "LSEELM", "StefanPIELM", "NormalEquationELM", "ParameterRetentionELM", "PiecewiseELM", "DELM", "FPIELM", "SGEPIELM", "RINN", "RaNNPIELM", "XPIELM", "PIELMRVDS", "TSPIELM", "KAPIELM", "SoftPartitionKAPIELM", ]