Skip to main content

Uniform (Rational) B-Splines in PyTorch

Project description

torchnodo

torchnodo is an implementation of uniform (rational) B-splines in PyTorch. It provides a small, purely functional API for evaluating B-spline curves and surfaces and their parametric derivatives, with full autograd and GPU support.


Features

  • control points for curves and surfaces of arbitrary dimension (not limited to 2D/3D)
  • arbitrary B-spline polynomial degree P
  • analytical parametric differentiation or any order D ≤ P
  • periodic and non-periodic support, with clamped and unclamped knot vectors
  • rational variants (weighted control points) for both curves and surfaces
  • optimized surface evaluation on a regular U × V grid (bspline_surface_grid) and on scattered (u, v) samples (bspline_surface)
  • midpoint uniform knot refinement for curves and surfaces (rational and non-rational)
  • full autograd support — differentiable with respect to control points and rational weights
  • full GPU support with dtype and device correctness
  • zero runtime dependencies beyond PyTorch itself

Out of scope:

  • non-uniform knots (not a NURBS implementation in the general sense)
  • degree elevation
  • explicit surface-of-revolution, swept surface, or other higher-level constructors
  • B-splines volumes or higher order manifolds

Examples

A 2D B-Spline curve

import matplotlib.pyplot as plt
import torch

from torchnodo import bspline_curve

# Evaluate a 2D curve of degree 3 with 5 random control points
control_points = torch.rand(5, 2)
curve = bspline_curve(
    u=torch.linspace(0, 1, 200),
    points=control_points,
    degree=3,
    order=0,
    periodic=False,
    clamped=True,
)

# Plot the curve value (0-th order derivative) and its control polygon
plt.plot(curve[0, :, 0], curve[0, :, 1])
plt.plot(control_points[:, 0], control_points[:, 1], "o--", alpha=0.4)
plt.show()

basic_curve

A 1D B-Spline surface

import matplotlib.pyplot as plt
import torch

from torchnodo import bspline_surface_grid

# Evaluate a 1D surface of degree (3, 2) with 5x4 random control points
u = torch.linspace(0, 1, 60)
v = torch.linspace(0, 1, 60)
surface = bspline_surface_grid(
    u,
    v,
    points=torch.rand(5, 4, 1),
    degree=(3, 2),
    order=(0, 0),
    periodic=(False, False),
    clamped=(True, True),
)

z = surface[0, 0, :, :, 0]

U, V = torch.meshgrid(u, v, indexing="ij")

# Plot the surface over its U x V parametric grid
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(U, V, z, cmap="cividis")
ax.set(xlabel="U", ylabel="V", zlabel="Z")
plt.show()

basic_surface

Browse all examples

All runnable examples live in the examples/ directory:

Installation

Install with pip:

pip install torchnodo

Or, in a uv project:

uv add torchnodo

⚠️ PyTorch is not declared as a dependency. torchnodo requires PyTorch at runtime, but the pyproject.toml of torchnodo intentionally does not list torch so that you can install the variant of PyTorch you want (CPU-only, CUDA, ROCm, etc.) without interference.

Design choices

  • Purely functional API

There are no classes, no state, and no mutation. Every public entry point is a free function that takes control points and configuration and returns tensors. This keeps the API composable with torch.nn.Module, autograd, torch.compile, and functional transforms without wrapping.

  • Almost loop-free code

B-spline evaluation is expressed in terms of tensor operations and runs a batched de Boor-style recursion. The only Python-level loops are over B-spline degree P and parametric derivative order D, both of which are static — they are fixed at call time and typically small (≤ 5). There is no Python-level loop over parameter values or control points.

  • Arbitrary control-point dimension

The trailing C axis of points tensors is a pure "batch of coordinates" and is never inspected. Typical uses are 2D or 3D control points for curves and 3D control points for surfaces, but any C ≥ 1 is supported.

  • Joint evaluation of values and parametric derivatives

The "value" of a function is really its zero-th order derivative. So when evaluating a spline, request the number of parametric derivatives you need with the order= argument. The API returns a tensor of shape:

( order of parametric derivation, parametric samples, dimension of control points )

which in practice translates to:

Function Output tensor shape
bspline_curve (order+1, U, C)
bspline_surface_grid (order[0] + 1, order[1] + 1, U, V, C)
bspline_surface (order[0] + 1, order[1] + 1, UV, C)
  • Uniform knots only

Knots vectors are either uniform clamped or uniform unclamped. They are never stored as tensors and remain implicit in the code.

  • Normal vs grid surface

Two surface evaluators are provided:

  1. bspline_surface_grid(u, v, points, ...) evaluates on the full Cartesian product u × v. This is the fast path for rendering, plotting, or any dense grid use case: basis functions in u and v are computed independently and combined with a single einsum.
  2. bspline_surface(uv, points, ...) evaluates on arbitrary scattered (u, v) pairs. Use it when surface samples are not on a grid.

Nomenclature

  • degree (P, Q): the polynomial degree of the B-spline.
  • order (D, E): the parametric derivative order. Unrelated to spline order in some textbooks (which use "order" to mean degree + 1).

API

Evaluation

curve = bspline_curve(u, points, *, degree, order, periodic, clamped)
curve = bspline_rational_curve(u, points, weights, *, degree, order, periodic, clamped)

surface = bspline_surface(uv, points, *, degree, order, periodic, clamped)
surface = bspline_rational_surface(uv, points, weights, *, degree, order, periodic, clamped)

surface = bspline_surface_grid(u, v, points, *, degree, order, periodic, clamped)
surface = bspline_rational_surface_grid(u, v, points, weights, *, degree, order, periodic, clamped)

Common arguments:

  • u / v / uv: parameter values in [0, 1]. For curves, u is shape (U,). For scattered surface evaluation, uv is shape (UV, 2). For grid surface evaluation, u and v are independent 1D tensors.
  • points: control points.
    • curves: shape (K, C)
    • surfaces: shape (K, L, C)
  • weights (rational variants only): positive weights with shape (K,) for curves and (K, L) for surfaces.
  • degree: polynomial degree. For curves, an int. For surfaces, a tuple[int, int] of (P, Q).
  • order: highest parametric derivative order to compute. For curves, an int in [0, P]. For surfaces, a tuple[int, int] with each component in [0, P] / [0, Q].
  • periodic: whether the curve/surface is periodic. For surfaces, a tuple[bool, bool] — the two parametric axes are independent, so surfaces can be periodic in u only, v only, both (torus-like), or neither.
  • clamped: whether the knot vector is clamped (boundary knots repeated P times so the curve passes through the first and last control point) or unclamped (uniformly extended on both sides). For surfaces, a tuple[bool, bool].

Typical periodic / clamped combinations

curve type periodic clamped
open, interpolating False True
open, "floating" False False
closed loop True False

periodic=True, clamped=True works but is not a very natural configuration.

Control points refinement

points = refine_curve_points(points, degree, *, periodic, clamped)
points, weights = refine_rational_curve_points(points, weights, degree, *, periodic, clamped)

points = refine_surface_points(points, degree, *, periodic, clamped)
points, weights = refine_rational_surface_points(points, weights, degree, *, periodic, clamped)

Each refinement call inserts one knot at the midpoint of every inner knot span, along every parametric axis. The returned control points define a curve/surface that is geometrically identical to the original; only the control polygon / control grid densifies.

Midpoint refinement requires an unclamped knot vector.

curve_refinement


surface_refinement

License

MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

torchnodo-1.0.0.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

torchnodo-1.0.0-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file torchnodo-1.0.0.tar.gz.

File metadata

  • Download URL: torchnodo-1.0.0.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for torchnodo-1.0.0.tar.gz
Algorithm Hash digest
SHA256 f819c7f8f3253523f7c8d55d32589f755273bc6c398a26bddc5f74a09a3aec17
MD5 6e521b16dc23baf02ecf9da381414348
BLAKE2b-256 ad522ca97cf44af127dda484b4d64a62e51704185d9762cd0f230ff1b637fa62

See more details on using hashes here.

File details

Details for the file torchnodo-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: torchnodo-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 20.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for torchnodo-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d27d69bd7ca1d379523de08ee9a254d7a47c59e28c1ba57ca38156fad4077b6
MD5 aee1da09870c50a5582bfa028c176e92
BLAKE2b-256 5487f11ac5803517d76a8cea682fab7e74c334efc26c9a83e27a777432af5586

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page