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)
  • full autograd support — differentiable with respect to control points and rational weights
  • full GPU support with dtype and device correctness
  • midpoint uniform knot refinement for curves and surfaces (rational and non-rational)
  • gaussian and mean curvature for 3D B-spline surfaces
  • 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

Surface curvatures

K, H = bspline_surface_curvatures(uv, points, *, degree, periodic, clamped)
K, H = bspline_rational_surface_curvatures(uv, points, weights, *, degree, periodic, clamped)

K, H = bspline_surface_grid_curvatures(u, v, points, *, degree, periodic, clamped)
K, H = bspline_rational_surface_grid_curvatures(u, v, points, weights, *, degree, periodic, clamped)

Gaussian (K) and mean (H) curvatures of a B-spline surface in R³ at the sampled (u, v) points, computed from the first and second fundamental forms. Restricted to 3D control points (points.shape[-1] == 3). Each degree component must be >= 2 so that second-order parametric derivatives exist.

surface_curvature

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.2.0.tar.gz (14.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.2.0-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: torchnodo-1.2.0.tar.gz
  • Upload date:
  • Size: 14.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.2.0.tar.gz
Algorithm Hash digest
SHA256 6a734b4fdd497aff449f5a6ac24a872b02659681e2123886565c197f8ef411f6
MD5 9688464ca8e5dbdb23edb0337e9937c4
BLAKE2b-256 c2133d91af94e55816d3c0fda79dc61c7801de9c01e222fd2c813cab321d8419

See more details on using hashes here.

File details

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

File metadata

  • Download URL: torchnodo-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 22.3 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4a6b63ed6ca7417d5f8b94bc2ddcd4da446cae0b66af069647bef0d9a87f115b
MD5 636833df173c1b0e6b48825f55412c7c
BLAKE2b-256 114508ec7cb21e5bc0a5e64ed5f45bfd8d97be7f7e8b65e6d2d65698c6dbaf3e

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