Skip to main content

Uniform (Rational) B-Splines in PyTorch

Project description

torchnodo

PyPI - Version PyPI - License

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.1.0.tar.gz (13.6 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.1.0-py3-none-any.whl (20.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: torchnodo-1.1.0.tar.gz
  • Upload date:
  • Size: 13.6 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.1.0.tar.gz
Algorithm Hash digest
SHA256 f8c05c6458c66708c427dd59a99894775696e5868edc4e38417ed6f033f0ab10
MD5 bbb98d92730afed013290b602c398cb8
BLAKE2b-256 681e807a6dc344ea92586cfab906d2511c62d196bd61658c4173759b78be4d4d

See more details on using hashes here.

File details

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

File metadata

  • Download URL: torchnodo-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.8 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1f1d2c9bd9f3d34aa83e460f7f369653bb5054f9453c9cd33de065447485f4f2
MD5 98d3d7f93d9a82e45f01aa24c13f20df
BLAKE2b-256 496c9b8835b718e4c45a3002ffc51ce8af0474f822efbe4b2c4438fe9b7850b4

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