Bases¶
kanforge.bases.base
¶
The Basis contract shared by every backend.
Every edge function is phi(x) = sum_b c_b * B_b(x; theta). A basis maps an
input of shape (..., in) to (..., in, size) and can return any
derivative order of the same shape.
theta are optional basis parameters: trainable ones (rational
coefficients, wavelet scales, knot positions, ...) and fixed state such as
the grid of a spline, which differs per input and is updated from data. A basis declares them with
param_specs(): one default value per parameter, shaped for a single
input. Layers store them per input, per group of inputs (GR-KAN) or once
per layer and pass them in shaped (in, *shape) or (*shape); both
broadcast against x[..., None].
local marks bases with compact support (at most support non-zero
values per input); fused kernels will use it to pick a sparse gather.
ParamSpec(value, trainable=True)
dataclass
¶
Default value of one basis parameter for a single input.
trainable=False marks state such as grids: layers keep it as a buffer,
always one copy per input, and grid tools update it from data.
Basis
¶
make_basis(spec='bspline', **kwargs)
¶
Build a basis from a name, a config() dict or an existing instance.
kanforge.bases.spline
¶
Piecewise bases: B-splines, NURBS, truncated powers, ReLU-KAN.
Their grid is a per-input buffer grid of increasing points; by default
it is uniform over grid_range and grid tools (kanforge.grid) adapt it
to the data. Splines extend it by order points on each side with the
mean grid step, as pykan does.
BSpline(grid_size=5, order=3, grid_range=(-1.0, 1.0))
¶
Bases: _Gridded
B-splines of order on grid_size intervals (pykan / efficient-kan).
There are grid_size + order functions; they sum to one inside the
grid. Outside the extended grid all values are zero and the layer falls
back to its base (SiLU) branch.
NURBS(grid_size=5, order=3, grid_range=(-1.0, 1.0))
¶
Bases: Basis
Rational B-splines R_j = w_j N_j / sum_k w_k N_k with trainable weights.
Weights are stored as logs, so they stay positive. With all weights equal
this is exactly BSpline. Used by Double-KANs for signals with jumps.
TruncatedPower(degree=3, num_knots=6, grid_range=(-1.0, 1.0), learn_knots=False)
¶
Bases: Basis
TruKAN basis: Legendre P_0..P_degree of the grid-normalised input plus (x - t_j)_+^degree / (hi - t_j)^degree.
The grid holds num_knots + 2 points: the ends of the range and the
interior knots. With learn_knots the interior knots are trainable per
input; they stay ordered because they are cumulative sums of softplus
increments (as in TruKAN).
ReLUKAN(grid_size=5, order=3, grid_range=(-1.0, 1.0))
¶
Bases: _Gridded
ReLU-KAN: R_i(x) = [relu(e_i - x) * relu(x - s_i)]^2 * 16 / (e_i - s_i)^4.
Same layout as BSpline: bump i spans knots t_i .. t_{i+order+1} of the
extended grid and peaks at 1 on a uniform grid. Only additions, products
and ReLU, so it is cheap on any accelerator.
extend_grid(grid, k)
¶
Add k points on each side of grid ((..., G+1)) with the mean step.
kanforge.bases.polynomial
¶
Polynomial bases: Chebyshev, Legendre, Jacobi, Gegenbauer, Hermite, Laguerre, Bernstein, Gram, Taylor and fractional Jacobi (fKAN).
Every family is stored as a matrix A of Chebyshev coefficients, so values
are always computed with the stable Chebyshev recurrence:
B(z) = [T_0(z) .. T_D(z)] @ A. Derivatives in z are chebder(A); the input
map z = g(x) (tanh, identity, fractional power) is applied with Faà di Bruno.
For fused kernels A can be folded into the layer coefficients.
PolynomialBasis(degree=5, input_map=None)
¶
Chebyshev(degree=5, input_map=None)
¶
Chebyshev2(degree=5, input_map=None)
¶
Jacobi(degree=5, alpha=1.0, beta=1.0, input_map=None)
¶
FractionalJacobi(degree=5, alpha=1.0, beta=1.0)
¶
Gegenbauer(degree=5, lam=1.0, input_map=None)
¶
Hermite(degree=5, input_map=None)
¶
Bernstein(degree=5, input_map=None)
¶
Gram(degree=5, points=None, input_map=None)
¶
Bases: PolynomialBasis
Gram polynomials: orthonormal on points equally spaced points of [-1, 1].
Normalised so that the mean of each squared polynomial over the points is 1.
Taylor(degree=5, input_map=None)
¶
kanforge.bases.radial
¶
Bump-shaped bases: Gaussian RBF (FastKAN), RSWAF (FasterKAN) and wavelets (Wav-KAN: Mexican hat, Morlet, derivative of Gaussian).
All Gaussian-envelope functions have the form psi(u) = (p(u) cos(w u) + q(u) sin(w u)) * exp(-u^2 / 2), which is closed under differentiation, so every derivative order is exact.
RBF(count=8, grid_range=(-1.0, 1.0), learnable=False)
¶
Bases: _Envelope
Gaussian RBF exp(-((x - c) / h)^2) on a uniform grid of centres (FastKAN).
MexicanHat(count=8, grid_range=(-1.0, 1.0), learnable=True)
¶
Bases: _Envelope
Mexican hat wavelet 2 / (sqrt(3) pi^(1/4)) (1 - u^2) exp(-u^2 / 2).
Morlet(count=8, grid_range=(-1.0, 1.0), learnable=True, omega=5.0)
¶
Bases: _Envelope
Real Morlet wavelet cos(omega u) exp(-u^2 / 2).
DoG(count=8, grid_range=(-1.0, 1.0), learnable=True)
¶
Bases: _Envelope
Derivative-of-Gaussian wavelet -u exp(-u^2 / 2).
kanforge.bases.fourier
¶
Periodic bases: Fourier series (FourierKAN) and random Fourier features (KAF).
kanforge.bases.rational
¶
Rational activation P(x) / (1 + |Q(x)|), the GR-KAN basis of KAT.
A single trainable function per input (size == 1); in a layer with
groups the coefficients are shared inside each group of inputs, and the
layer coefficients play the role of KAT's linear weight.
fit_rational(target, num_degree, den_degree, lim=3.0)
cached
¶
Coefficients (a, b) of P/(1+|Q|) that best fit target on [-lim, lim].
Linear least squares for P - f*Q = f gives the start, Levenberg–Marquardt
then minimises the true residual. Exact for identity.
kanforge.bases.neural
¶
Tiny-network edges (in the spirit of FCN-KAN): B_b(x) = act(a_b * (x - c_b)).
With the layer coefficients on top this is a one-hidden-layer MLP on every edge whose first layer is shared by the edges leaving the same input.
kanforge.bases.combine
¶
Concatenation of bases, e.g. BSRBF-KAN = B-splines + Gaussian RBF.