Python API reference
The Python reference describes the public objects and their arguments. Begin with Quickstart for a complete fit, or Advanced Topics for practical choices and interpretation. The sections below supply exact signatures once you know which operation you need.
Most workflows start with BezierSpace, Samples and fit. Use
Points when cell identity must travel with coordinates, and use the
returned FitResult.model for prediction. The common types live in
torch_bsf.core and are also available from the package root.
For numerical methods, consult fitting and solvers for regression,
or optimization for differentiable costs. Sampling, validation and
active learning build on the same space and model. Plotting, scikit-learn,
Lightning and MLflow integrations are optional; install the relevant extra
before executing their examples.
geometry describes mutable topology construction, while a compiled
BezierSpace records immutable geometry and coefficient layout. The
archive interface in io preserves that layout with model coefficients.
Private modules beginning with an underscore are implementation details.
core
BezierSpace defines the domain and global control layout. Points
carries coordinates and cell IDs, Samples adds observed values and
weights, and BezierModel adds output coefficients to a space. Geometry
does not depend on output count. Use these types to make data alignment
and model state explicit.
Points, Samples and BezierModel preserve floating tensor and
NumPy array dtypes. Python float lists use torch.get_default_dtype()
(normally float32). To retain double-precision coordinates, observations
and coefficients, construct float64 tensors or NumPy arrays before passing
them in; converting an already rounded float32 tensor cannot recover precision.
Integer coordinates and coefficients become float64; on MPS, these integer
inputs move to CPU for that promotion because MPS does not support float64.
Integer observations remain integers. Samples weights are stored as CPU float64 tensors.
Device inputs converted to CPU float64 move to CPU before changing dtype,
preserving coordinate values and their autograd connection.
The constructor contracts and executable examples below use n_samples
for the number of aligned rows, coordinate_width for the number of
coordinate columns, and n_outputs for the number of response columns.
See Points and Samples for
shapes, defaults and validation. Their from_cells methods pack local
rows of different widths; from_cells() shows
the shared order of coordinates, observations and weights. These helpers
convert nonempty coordinate batches and observations to CPU float64.
forward() describes single-cell tensor
inputs, explicit multi-cell Points and the prediction shape.
FixedControls stores CPU float64 values and reads non-tensor floating
inputs directly as float64, so FixedControls({0: [0.2]}) preserves the
Python float value independently of the default dtype. Existing tensor
values are converted with their original precision. Complex fixed values
are rejected.
Spaces, observations and differentiable models shared by every fitting backend.
- class torch_bsf.core.BasisOperator(space: BezierSpace, points: Points, *, _groups: list[tuple[int, Tensor]] | None = None)[source]
Bases:
objectCell-local basis supporting products, transpose products and explicit export.
- Parameters:
space (BezierSpace) – Compiled geometry and coefficient layout.
points (Points) – Coordinate rows with valid cell IDs and local barycentric coordinates.
_groups (list, optional) – Precomputed
(cell_id, row_indices)groups for internal reuse; otherwise grouped from points.
- matmat(coefficients: Tensor) Tensor[source]
Evaluate a coefficient matrix, accumulating only local contributions.
- Parameters:
coefficients (torch.Tensor) – Coefficient matrix of shape
(space.n_controls, n_outputs).- Returns:
Predicted values of shape
(n_points, n_outputs).- Return type:
- rmatmat(values: Tensor) Tensor[source]
Apply the transpose, summing repeated shared coefficient columns.
- Parameters:
values (torch.Tensor) – Matrix of shape
(n_points, n_outputs)to multiply by the transposed basis.- Returns:
Accumulated coefficient contributions of shape
(space.n_controls, n_outputs).- Return type:
- class torch_bsf.core.BezierModel(space: BezierSpace, coefficients: Any)[source]
Bases:
ModuleA vector-valued differentiable map on a compiled
BezierSpace.- Parameters:
space – Geometry, degrees and coefficient ordering.
coefficients – Matrix of shape
(space.n_controls, n_outputs). Copied on construction. Floating tensors and NumPy arrays retain their dtype. Python float lists usetorch.get_default_dtype(); integer inputs become float64 (on CPU for integer MPS tensors). Pass float64 tensors or arrays for double precision.
Examples
>>> import torch >>> from torch_bsf import BezierSpace, BezierModel >>> model = BezierModel(BezierSpace.simplex(1, degree=1), torch.tensor([[1.], [3.]])) >>> model(torch.tensor([[0.5, 0.5]])).item() 2.0
- copy() BezierModel[source]
Copy coefficients without mutating or duplicating immutable geometry.
- Returns:
Independent coefficient copy sharing this model’s immutable space.
- Return type:
- forward(points: Points | Tensor) Tensor[source]
Evaluate points in their original row order.
- Parameters:
points (Points or torch.Tensor) – Barycentric coordinates. A raw tensor of shape
(n_samples, space.width)is accepted only when the space has one addressable cell; all rows then belong to cell zero. Multi-cell spaces requirePointswith explicit IDs, including when every requested row belongs to the same cell. Active coordinates must be finite barycentric blocks; extra padding in mixed-cell Points is ignored.- Returns:
Predictions in input row order, on the coefficient dtype and device. Input coordinates are copied to that dtype and device. Evaluation is differentiable in coordinates and coefficients.
- Return type:
torch.Tensor, shape (n_samples, n_outputs)
- Raises:
ValueError – If raw coordinates are used with a multi-cell space, Points construction fails, IDs are outside the space, coordinate width is invalid, or active coordinates fail barycentric validation.
Examples
>>> import torch >>> from torch_bsf import BezierModel, BezierSpace, Points, SimploidalSet >>> model = BezierModel(BezierSpace.simplex(1, degree=1), [[1.0], [3.0]]) >>> tuple(model(torch.tensor([[0.5, 0.5], [0.25, 0.75]])).shape) (2, 1) >>> domain = SimploidalSet() >>> edge = domain.add_simplex(1) >>> space = BezierSpace.on_set(domain, degree=1) >>> model = BezierModel(space, torch.ones(space.n_controls, 2)) >>> points = Points.from_cells(space, [(edge, [[0.5, 0.5]])]) >>> model(points).tolist() [[1.0, 1.0]] >>> model(torch.tensor([[0.5, 0.5]])) Traceback (most recent call last): ... ValueError: multi-cell models require explicit Points with cell IDs.
- class torch_bsf.core.BezierSpace(cells: tuple[~torch_bsf.core.Cell, ...], n_controls: int, main_cells: tuple[int, ...], _edges: tuple[tuple[int, int], ...], _source_ids: ~collections.abc.Mapping[~torch_bsf.geometry.Simploid, int] = <factory>, _topology: tuple[~typing.Any, ...] = ())[source]
Bases:
objectImmutable polynomial space, independent of learned values and output dimension.
Construct a simplex with
simplex(), a product withsimploid(), or compile a mutable topology builder withon_set(). Dimensions, degrees and control multi-indices accept Python and NumPy integers; booleans, strings and floats (including integer-valued floats) raiseValueError. Factor dimensions are positive;simplex()also accepts dimension zero. Single-cell degrees are non-negative; shared-space degrees are positive.Examples
>>> from torch_bsf import BezierSpace >>> space = BezierSpace.simplex(dimension=2, degree=3) >>> space.n_controls 10
- adjacency() Tensor[source]
Return a copy of the undirected control-net edges.
- Returns:
Integer edge endpoints of shape
(n_edges, 2)on CPU.- Return type:
- basis(points: Points | Tensor) BasisOperator[source]
Build a cell-local linear operator without a global dense allocation.
- Parameters:
points (Points or torch.Tensor) – Coordinate rows to evaluate. Raw tensors are accepted only for a single-cell space.
- Returns:
Validated operator with one local Bernstein block per active cell.
- Return type:
- cell_id(cell: int | Simploid) int[source]
Resolve an integer or an original builder handle to its frozen cell ID.
- compatible(other: BezierSpace) bool[source]
Whether coefficient ordering and all addressable cells agree.
- Parameters:
other (BezierSpace) – Space to compare with this snapshot.
- Returns:
Whether geometry, coefficient ordering and addressable cells agree.
- Return type:
- control_index(index: Sequence[Any], *, cell: int | Simploid = 0) int[source]
Resolve a cell-local multi-index to a globally shared coefficient row.
- property n_cells: int[source]
Number of addressable non-degenerate cells, including faces.
- Returns:
Number of cells in the compiled snapshot, including addressable faces.
- Return type:
- classmethod on_set(domain: SimploidalSet, *, degree: int | Mapping[Simploid, int | Sequence[int]], default_degree: int | None = None) BezierSpace[source]
Snapshot a simploidal set, propagating compatible degrees across shared faces.
The builder’s cell handles remain usable for addressing this snapshot; subsequent changes to the builder cannot change its basis or layout.
- Parameters:
domain (SimploidalSet) – Nonempty topology builder to snapshot, including its shared faces.
degree (int or mapping) – Positive scalar degree or per-cell degree assignment, propagated across shared faces.
default_degree (int, optional) – Fallback degree for cells without an explicit assignment.
- Returns:
Immutable space whose cell IDs also resolve handles from the original builder.
- Return type:
- classmethod simplex(dimension: int, *, degree: int) BezierSpace[source]
Create a single simplex, including a zero-dimensional constant.
- Parameters:
- Returns:
Compiled single-cell simplex space.
- Return type:
- classmethod simploid(dimensions: Sequence[int], *, degree: int | Sequence[int]) BezierSpace[source]
Create a tensor product of simplices with one degree per factor.
- Parameters:
- Returns:
Compiled single-cell product space.
- Return type:
- validate(points: Points) None[source]
Check IDs, finite coordinates and simplex membership of each factor.
- Parameters:
points (Points) – Coordinate rows and cell IDs to validate against this space.
- property width: int[source]
Width used when packing mixed-cell coordinate batches.
- Returns:
Largest barycentric coordinate width among all cells.
- Return type:
- with_degree(degree: int | Sequence[int]) BezierSpace[source]
Recompile the same frozen domain at another degree for model selection.
- class torch_bsf.core.Cell(dimensions: tuple[int, ...], degree: tuple[int, ...], controls: tuple[int, ...])[source]
Bases:
objectA compiled cell’s factor dimensions, degrees and global coefficient rows.
- class torch_bsf.core.FixedControls(values: Mapping[int, Any])[source]
Bases:
objectExplicit fixed values keyed by globally shared coefficient row.
Values are copied to CPU float64 tensors. Non-tensor floating inputs are read directly as float64, independently of PyTorch’s default dtype; tensor inputs retain their existing precision before conversion. Complex inputs are rejected.
- Parameters:
values (mapping of int to array-like) – Global coefficient rows and their finite one-dimensional output vectors.
Examples
>>> from torch_bsf import FixedControls >>> FixedControls({0: [0.2]}).values[0].item() 0.2
- classmethod from_cells(space: BezierSpace, values: Sequence[tuple[int | Simploid, Sequence[Any], Any]]) FixedControls[source]
Resolve local references and reject contradictory shared values.
- Parameters:
space (BezierSpace) – Space used to resolve cell-local controls to globally shared rows.
values (sequence of triples) – Each triple contains a cell reference, local multi-index and fixed output vector.
- Returns:
Fixed global controls with duplicate shared references checked for agreement.
- Return type:
- class torch_bsf.core.Points(parameters: Any, cell_ids: Any = None)[source]
Bases:
objectBarycentric coordinate rows and their cell IDs, in prediction order.
- Parameters:
parameters (array-like, shape (n_samples, coordinate_width)) – Real coordinate rows. For a simplex of dimension
d, the width isd + 1; for a product, concatenate one barycentric block per factor. Mixed-cell rows may be padded on the right tospace.width. Coordinates are copied on their input device, except integer MPS coordinates, which move to CPU for float64 promotion.cell_ids (array-like of int, shape (n_samples,), optional) – One integer cell ID per row. Defaults to all zeros on the coordinate device. IDs are copied as
torch.longon that device.
- Raises:
ValueError – If coordinates are complex or not two-dimensional, or IDs are not a matching one-dimensional integer vector (booleans are rejected).
Notes
Floating tensors and NumPy arrays retain their dtype. Python float lists use
torch.get_default_dtype(); integer coordinates become float64. Pass float64 tensors or arrays to preserve double-precision coordinates. Construction checks shapes and types only. Cell membership, finite active coordinates and barycentric blocks are checked byspace.validate, basis construction or model evaluation. Inactive padding is ignored.Examples
>>> from torch_bsf import BezierSpace, Points >>> points = Points([[1.0, 0.0], [0.25, 0.75]]) >>> tuple(points.parameters.shape), points.cell_ids.tolist() ((2, 2), [0, 0]) >>> BezierSpace.simplex(1, degree=1).validate(points)
- classmethod from_cells(space: BezierSpace, data: Sequence[tuple[int | Simploid, Any]]) Points[source]
Pack ordered cell-local coordinates without manual padding.
- Parameters:
space (BezierSpace) – Compiled space used to resolve cell references and validate rows.
data (sequence of (cell, parameters) pairs) – Each
cellis an integer ID or an original builder handle. Its coordinates have shape(n_cell_samples, cell.width). Entries and rows within each entry retain their supplied order; repeated cell references are allowed.
- Returns:
Coordinates of shape
(n_samples, space.width), wheren_samplesis the sum of entry row counts. Nonempty input is converted to CPU float64 and zero-padded on the right. IDs are CPUtorch.long. An empty sequence returns zero rows with coordinates in PyTorch’s default floating dtype on its default device.- Return type:
- Raises:
ValueError – If a cell reference is invalid, a coordinate batch has the wrong shape or local width, or its coordinates are not finite valid barycentric blocks for that cell.
Notes
Inputs first follow
Pointsdtype inference, then convert to float64; this cannot recover precision already lost to float32. SeeSamples.from_cells()for a mixed-width packing example with aligned observations and weights.Examples
>>> from torch_bsf import BezierSpace, Points >>> space = BezierSpace.simplex(1, degree=1) >>> points = Points.from_cells(space, [(0, [[0.25, 0.75], [1.0, 0.0]])]) >>> points.parameters.tolist(), points.cell_ids.tolist() ([[0.25, 0.75], [1.0, 0.0]], [0, 0]) >>> str(points.parameters.dtype), str(points.parameters.device) ('torch.float64', 'cpu')
- take(indices: Tensor) Points[source]
Select rows while preserving their cell identities.
- Parameters:
indices (torch.Tensor) – Integer row indices in the requested output order.
- Returns:
Selected coordinate rows and their corresponding cell IDs.
- Return type:
- to(*, device: device | str = 'cpu', dtype: dtype = torch.float64) Points[source]
Copy coordinates to an explicit compute dtype and device.
- Parameters:
device (torch.device or str, optional) – Destination device; defaults to CPU.
dtype (torch.dtype, optional) – Coordinate dtype; defaults to
torch.float64.
- Returns:
Coordinates in the requested dtype and cell IDs on the requested device.
- Return type:
- class torch_bsf.core.QuadraticPenalty(ridge: float = 0.0, smoothness: float = 0.0)[source]
Bases:
objectWeights of squared coefficients and adjacent coefficient differences.
- class torch_bsf.core.Samples(parameters: Any, values: Any, *, cell_ids: Any = None, weights: Any = None)[source]
Bases:
objectPoints with finite observed outputs and optional nonnegative weights.
- Parameters:
parameters (Points or array-like, shape (n_samples, coordinate_width)) – Existing points, retained as supplied, or coordinates used to build
Points. Raw coordinates and cell IDs are copied by Points.values (array-like, shape (n_samples, n_outputs) or (n_samples,)) – Real, finite observations in point row order. One-dimensional values become one output column. Both
n_samplesandn_outputsmust be positive. Values are copied and retain their input device.cell_ids (array-like of int, shape (n_samples,), optional) – IDs for raw coordinates; defaults to all zeros. Must be omitted when
parametersis already aPointsinstance.weights (array-like, shape (n_samples,), optional) – Observation weights in the same row order, copied to CPU float64. Defaults to all ones. Entries must be finite and nonnegative, and their sum must be finite and strictly positive. Individual zeros are allowed, but all observations must still be finite, including observations with zero weight.
- Raises:
ValueError – If both Points and separate IDs are supplied, values are complex, nonfinite, empty or have a mismatched shape, or weights violate the shape, finiteness or positivity requirements. Raw coordinates also follow the construction checks of
Points.
Notes
Coordinates follow
Points. Observed values usetorch.as_tensordtype inference: tensors and NumPy arrays retain their dtype, Python float lists usetorch.get_default_dtype(), and integer values remain integers. For double precision, pass float64 tensors or arrays. Weights are stored as CPU float64 tensors. As with direct Points construction, space membership is checked when the points are validated or used with a space, rather than here.Examples
>>> from torch_bsf import Points, Samples >>> points = Points([[1.0, 0.0], [0.0, 1.0]]) >>> samples = Samples(points, [2.0, 4.0], weights=[0.0, 1.0]) >>> tuple(samples.values.shape), samples.weights.tolist() ((2, 1), [0.0, 1.0]) >>> Samples(points, [2.0, 4.0], cell_ids=[0, 0]) Traceback (most recent call last): ... ValueError: cell_ids are already specified by Points. >>> Samples(points, [float("nan"), 4.0], weights=[0.0, 1.0]) Traceback (most recent call last): ... ValueError: values must be finite with shape (n_samples, n_outputs), both positive.
- classmethod from_cells(space: BezierSpace, data: Sequence[tuple[int | Simploid, Any, Any]], *, weights: Any = None) Samples[source]
Pack ordered cell-local observations with different coordinate widths.
- Parameters:
space (BezierSpace) – Compiled space used to resolve cell references and validate rows.
data (sequence of (cell, parameters, values) triples) – Nonempty sequence of cell IDs or original builder handles, coordinates of shape
(n_cell_samples, cell.width), and corresponding finite observations of shape(n_cell_samples, n_outputs)or(n_cell_samples,). Each entry must contain at least one row and all entries must have the samen_outputs.weights (array-like, shape (n_samples,), optional) – One flat vector for all concatenated rows, in entry order then row order within each entry. Defaults to ones; follows the finite, nonnegative, positive-sum contract of
Samples.
- Returns:
Coordinates of shape
(n_samples, space.width)and observations of shape(n_samples, n_outputs), both on CPU in float64. Narrower coordinates have zero padding on the right. Cell IDs, values and weights follow the same concatenation order.- Return type:
- Raises:
ValueError – If data is empty, cell references or local coordinates are invalid, an entry has invalid observations, or weights violate the
Samplescontract.RuntimeError – If entries have inconsistent output widths, so their observed values cannot be concatenated.
Notes
Unlike direct construction, this helper converts coordinates and observations to CPU float64 after their usual dtype inference. Pass float64 inputs to avoid first rounding Python float lists to the default dtype. Outputs and weights are copied.
Examples
Pack a triangle row before two edge rows. The edge needs two active coordinates; its third column is padding. Weights follow this order, regardless of the numeric cell IDs.
>>> from torch_bsf import BezierSpace, Samples, SimploidalSet >>> domain = SimploidalSet() >>> edge, triangle = domain.add_simplex(1), domain.add_simplex(2) >>> space = BezierSpace.on_set(domain, degree=1) >>> samples = Samples.from_cells(space, [ ... (triangle, [[0.25, 0.25, 0.5]], [10.0]), ... (edge, [[1.0, 0.0], [0.0, 1.0]], [20.0, 30.0]), ... ], weights=[2.0, 0.0, 3.0]) >>> samples.points.parameters.tolist() [[0.25, 0.25, 0.5], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] >>> samples.points.cell_ids.tolist() == [space.cell_id(triangle), space.cell_id(edge), space.cell_id(edge)] True >>> tuple(samples.values.shape), samples.values[:, 0].tolist(), samples.weights.tolist() ((3, 1), [10.0, 20.0, 30.0], [2.0, 0.0, 3.0]) >>> str(samples.points.parameters.dtype), str(samples.values.dtype), str(samples.values.device) ('torch.float64', 'torch.float64', 'cpu')
- take(indices: Tensor) Samples[source]
Select observations, keeping coordinates, outputs and weights aligned.
- Parameters:
indices (torch.Tensor) – Integer observation indices in the requested output order.
- Returns:
Selected points, observed values and weights with matching row order.
- Return type:
geometry
Build an assembled domain with SimploidalSet and identify the faces
that must share coefficients. Compile it into BezierSpace before
fitting. Builder mutations do not alter an existing compiled space.
See Single and assembled domains for chart orientation and continuity.
Topology builders for simplex products and their glued parameter domains.
Compile a builder with BezierSpace.on_set before fitting. A compiled space
snapshots the topology; subsequent builder mutations cannot change its basis.
- class torch_bsf.geometry.Simploid(simploid_type: Sequence[int])[source]
Bases:
objectA simploid of a
SimploidalSet.Simploids are created by the construction methods of
SimploidalSet, not directly. A non-degenerate simploid stores its faces explicitly. A degenerate simploid is stored in canonical form, as a non-degenerate base simploid together with aDegeneracyMap, so that two equal degenerate simploids are the same object.Examples
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> prism = S.add_simploid((2, 1)) >>> prism.simploid_type, prism.dim ((2, 1), 3) >>> prism.face(1, 0).simploid_type # a triangle (2,) >>> prism.face(0, 0).simploid_type # a square (1, 1) >>> e = prism.face(1, 0).face(0, 0) # an edge >>> e.degeneracy(0, 0).is_degenerate True >>> e.degeneracy(0, 0).face(0, 0) is e True
- property base: Simploid[source]
The non-degenerate simploid this one is degenerate from, or itself if it is not degenerate.
- degeneracy(i: int, j: int) Simploid[source]
The degenerate simploid \(s^{i+1}_j\) (
j >= 0) or \(s^i_{-1}\) (j == -1).- Parameters:
i – The factor index:
0 <= i < lengthifj >= 0,0 <= i <= lengthifj == -1.j – The vertex of factor
ito repeat, or-1to insert a degenerate edge as the new factori.
- Return type:
The degenerate simploid.
- property degeneracy_map: tuple[tuple[int, tuple[int, ...]], ...][source]
The degeneracy map from
baseto this simploid (the identity map if not degenerate).
- face(i: int, j: int) Simploid[source]
The face \(d^{i+1}_j\): removes vertex
jof factori.- Parameters:
i – The factor index,
0 <= i < length.j – The vertex index,
0 <= j <= a_i.
- Return type:
The face.
- class torch_bsf.geometry.SimploidalSet[source]
Bases:
objectA simploidal set [PL18].
A simploidal set is a set of simploids equipped with face and degeneracy operators satisfying the commutation relations of Definition 1 of the paper. Only non-degenerate simploids are stored; degenerate simploids are created on demand by
Simploid.degeneracy()and by the faces of degenerate simploids.Simploidal sets are built with the basic operations of the paper:
add_vertex(),add_simplex(),add_simploid(): complete cells with their boundary;product(): cartesian product of two simploidal sets;identify(): identification of two simploids (and of their boundaries);degenerate(): degeneracy of a simploid onto a degenerate simploid of one of its faces;cone(): cone with a new vertex.
Examples
A tetrahedron and a cube connected by a prism (Figure 1(c) of the paper):
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> tetra = S.add_simplex(3) >>> prism = S.add_simploid((2, 1)) >>> cube = S.add_simploid((1, 1, 1)) >>> _ = S.identify(tetra.face(0, 0), prism.face(1, 0)) >>> _ = S.identify(prism.face(0, 0), cube.face(0, 0)) >>> S.check() >>> [s.simploid_type for s in S.main_simploids()] [(3,), (2, 1), (1, 1, 1)] >>> len(S.simploids(0)) # 4 + 6 + 8 vertices, minus 3 and 4 identified ones 11
- add_simplex(dim: int) Simploid[source]
Adds a complete simplex (a simploid of type \((\mathrm{dim})\)) with its boundary.
Face
face(0, j)of a simplex \((v_0, \ldots, v_k)\) is \((v_0, \ldots, \hat v_j, \ldots, v_k)\).- Parameters:
dim – The dimension of the simplex.
- Return type:
The simplex.
Examples
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> tri = S.add_simplex(2) >>> len(S), len(tri.vertices()) (7, 3)
- add_simploid(simploid_type: Sequence[int]) Simploid[source]
Adds a complete simploid of the given type with its boundary.
It is built as the cartesian product of complete simplices.
- Parameters:
simploid_type – The type \((a_1, \ldots, a_n)\).
- Return type:
The simploid.
Examples
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> square = S.add_simploid((1, 1)) >>> len(S.simploids(0)), len(S.simploids(1)), len(S.simploids(2)) (4, 4, 1)
- betti_numbers(field: Literal['Q', 'Z2'] = 'Q') list[int][source]
The Betti numbers \(\beta_0, \ldots, \beta_{\mathrm{dim}}\) of the simploidal set.
They are the dimensions of the homology groups of the normalized chain complex (see
boundary_matrix()), \(\beta_k = \dim C_k - \operatorname{rank} \partial_k - \operatorname{rank} \partial_{k+1}\). The ranks are computed exactly by integer Gaussian elimination, so the result does not depend on a floating-point tolerance.- Parameters:
field – The coefficient field:
"Q"(rational numbers) or"Z2"(integers modulo 2). They differ only when the integral homology has torsion, e.g., for non-orientable spaces.- Return type:
The Betti numbers, one per dimension from
0todim(empty if the set is empty).- Raises:
ValueError – If
fieldis neither"Q"nor"Z2".
Examples
A torus, a square whose opposite edges are identified:
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> square = S.add_simploid((1, 1)) >>> _ = S.identify(square.face(0, 0), square.face(0, 1)) >>> _ = S.identify(square.face(1, 0), square.face(1, 1)) >>> S.betti_numbers() [1, 2, 1]
- boundary_matrix(dim: int) Tensor[source]
The matrix of the boundary operator \(\partial_{\mathrm{dim}}\) of the normalized chain complex.
Following Section 4.4 of Peltier and Lienhardt [PL18], the chain group \(C_k\) is spanned by the non-degenerate simploids of dimension \(k\), and the boundary of a simploid \(\sigma\) of type \((a_1, \ldots, a_n)\) is
\[\partial \sigma = \sum_{i=1}^n \sum_{j=0}^{a_i} (-1)^{a_1 + \cdots + a_{i-1} + j} d^i_j \sigma,\]the Koszul sign rule for the product \(\Delta^{a_1} \times \cdots \times \Delta^{a_n}\), where the terms whose face \(d^i_j \sigma\) is degenerate are dropped.
- Parameters:
dim – The dimension \(k \ge 0\) of the simploids whose boundary is taken.
- Returns:
An integer (
torch.long) matrix of shape(len(simploids(dim - 1)), len(simploids(dim)))column
cholds the boundary ofsimploids(dim)[c]in the basissimploids(dim - 1).boundary_matrix(0)has no rows.
- Raises:
ValueError – If
dimis negative or a face does not belong to this set.
Examples
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> square = S.add_simploid((1, 1)) >>> [[key for key, f in square.faces() if f is e] for e in S.simploids(1)] [[(0, 1)], [(0, 0)], [(1, 1)], [(1, 0)]] >>> S.boundary_matrix(2) tensor([[-1], [ 1], [ 1], [-1]]) >>> bool((S.boundary_matrix(1) @ S.boundary_matrix(2) == 0).all()) True
- check() None[source]
Checks the face commutation relations (II) of Definition 1 for every simploid.
- Raises:
ValueError – If a relation does not hold or a face does not belong to this set.
- cone(simploids: Sequence[Simploid] | None = None) tuple[Simploid, dict[Simploid, Simploid]][source]
Cones simploids with a new vertex (Algorithm 8 of the paper).
The cone of a simploid of type \((a_1, \ldots, a_k)\) is a simploid of type \((a_1, \ldots, a_k, 1)\): an extrusion whose far end is degenerated into the new vertex
v. For example, the cone of a square is a square-based pyramid of type \((1, 1, 1)\). The conecofsigmasatisfiesc.face(k, 1) is sigmaandc.face(k, 0)is the degeneracy ofvwith the type ofsigma, wherek = sigma.length.- Parameters:
simploids – The simploids to cone (their boundaries are coned too). All simploids if
None.- Returns:
vertex – The new vertex.
cones – Maps each coned non-degenerate simploid to its cone.
Examples
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> square = S.add_simploid((1, 1)) >>> v, cones = S.cone([square]) >>> pyramid = cones[square] >>> pyramid.simploid_type (1, 1, 1) >>> pyramid.face(2, 0).is_degenerate, pyramid.face(2, 0).base is v (True, True)
- degenerate(simploid: Simploid, face: Simploid, i: int, j: int) Simploid[source]
Degenerates a simploid onto a degenerate simploid of one of its faces.
This identifies
simploidwithface.degeneracy(i, j); the two must have the same type, and their boundaries are identified accordingly.- Parameters:
simploid – The simploid to degenerate.
face – The simploid it degenerates into.
i – The degeneracy operator, as in
Simploid.degeneracy().j – The degeneracy operator, as in
Simploid.degeneracy().
- Return type:
The resulting degenerate simploid.
Examples
Collapse an edge of a square into a vertex, which turns the square into a triangle-shaped simploid of type
(1, 1):>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> square = S.add_simploid((1, 1)) >>> edge = square.face(0, 0) # an edge of type (1,) >>> d = S.degenerate(edge, edge.face(0, 0), 0, -1) >>> d.is_degenerate, square.face(0, 0) is d (True, True) >>> len(S.simploids(0)) 3
- identify(a: Simploid, b: Simploid) Simploid[source]
Identifies two simploids of the same type.
This is the higher-level identification of the paper: the boundaries of
aandbare identified face by face (a.face(i, j)withb.face(i, j)), and the degenerate simploids induced by the merged simploids are identified too. If one of the identified simploids is degenerate, the result is degenerate (this is howdegenerate()works).References to simploids that are merged away become stale; use the returned simploid and the faces of the surviving simploids instead.
- Parameters:
a – The simploids to identify.
b – The simploids to identify.
- Return type:
The simploid resulting from the identification.
- Raises:
ValueError – If the simploids (or faces to be identified) have different types, do not belong to this set, or if two distinct degenerate simploids would be identified.
Examples
Glue two triangles along an edge:
>>> from torch_bsf.geometry import SimploidalSet >>> S = SimploidalSet() >>> t1, t2 = S.add_simplex(2), S.add_simplex(2) >>> e = S.identify(t1.face(0, 0), t2.face(0, 2)) >>> t1.face(0, 0) is e and t2.face(0, 2) is e True >>> len(S.simploids(0)), len(S.simploids(1)) (4, 5)
- main_simploids() list[Simploid][source]
The non-degenerate simploids that are not a face of another non-degenerate simploid.
- product(other: SimploidalSet) tuple[SimploidalSet, dict[tuple[Simploid, Simploid], Simploid]][source]
The cartesian product of two simploidal sets (Algorithm 7 of the paper).
The product of simploids of types \((a_1, \ldots, a_k)\) and \((b_1, \ldots, b_l)\) is a simploid of type \((a_1, \ldots, a_k, b_1, \ldots, b_l)\).
- Parameters:
other – The second factor.
- Returns:
product – A new simploidal set.
pairs – Maps each pair
(sigma1, sigma2)of non-degenerate simploids tosigma1 x sigma2.
Examples
>>> from torch_bsf.geometry import SimploidalSet >>> A, B = SimploidalSet(), SimploidalSet() >>> tri, edge = A.add_simplex(2), B.add_simplex(1) >>> P, pairs = A.product(B) >>> pairs[(tri, edge)].simploid_type (2, 1)
preprocessing
Explicit output transforms make scaling visible in training and inference. Fit statistics on training responses, transform exact fixed values in the same way, and invert predictions before reporting original-unit errors. See Output scaling for penalty and validation implications.
Invertible scalers for the responses of a Bézier model.
- class torch_bsf.preprocessing.MinMaxScaler[source]
Bases:
objectMin-max scaler that normalizes values to the [0, 1] range.
Floating statistics and returned values must be finite in the input dtype. Integral inputs retain integer statistics and use PyTorch’s default floating dtype for normalized values. An unrepresentable fitted range raises
ValueError; rescale the original data units first.- scales[source]
Scale factors (max - min) per feature dimension. Dimensions where max equals min are set to 1 to avoid division by zero.
- Type:
- fit(values: Tensor) None[source]
Fit the scaler to the data.
Computes the per-feature minimum and scale (max - min) from
values.- Parameters:
values (torch.Tensor) – Input tensor of shape
(n_samples, n_features).
- fit_transform(values: Tensor) Tensor[source]
Fit the scaler to the data and return the normalized tensor.
- Parameters:
values (torch.Tensor) – Input tensor of shape
(n_samples, n_features).- Returns:
Normalized tensor of the same shape as
values, with each feature scaled to the [0, 1] range.- Return type:
- inverse_transform(values: Tensor) Tensor[source]
Reverse the normalization applied by
fit_transform().- Parameters:
values (torch.Tensor) – Normalized tensor of shape
(n_samples, n_features)with the fitted feature count. Empty batches (zero samples) are accepted.- Returns:
Tensor rescaled back to the original value range.
- Return type:
- Raises:
ValueError – If the input is not two-dimensional, has a different feature count from the fitted data, or contains non-finite values or results.
- transform(values: Tensor) Tensor[source]
Normalize
valueswith the fitted minimum and scale.- Parameters:
values (torch.Tensor) – Tensor of shape
(n_samples, n_features)with the fitted feature count. Empty batches (zero samples) are accepted.- Returns:
Transformed tensor of the same shape as
values.- Return type:
- Raises:
ValueError – If the input is not two-dimensional, has a different feature count from the fitted data, or contains non-finite values or results.
- class torch_bsf.preprocessing.NoneScaler[source]
Bases:
objectPass-through scaler that leaves values unchanged.
Useful as a no-op placeholder when normalization is not required, while still providing the same
fit(),fit_transform(), andinverse_transform()interface as the other scalers.- fit(values: Tensor) None[source]
No-op fit method included for API compatibility.
- Parameters:
values (torch.Tensor) – Input tensor (ignored).
- fit_transform(values: Tensor) Tensor[source]
Return
valuesunchanged.- Parameters:
values (torch.Tensor) – Input tensor of any shape.
- Returns:
The same tensor as
valueswithout any modification.- Return type:
- inverse_transform(values: Tensor) Tensor[source]
Return
valuesunchanged.- Parameters:
values (torch.Tensor) – Input tensor of any shape.
- Returns:
The same tensor as
valueswithout any modification.- Return type:
- transform(values: Tensor) Tensor[source]
Return
valuesunchanged.- Parameters:
values (torch.Tensor) – Input tensor of any shape.
- Returns:
The same tensor as
valueswithout any modification.- Return type:
- torch_bsf.preprocessing.NormalizeType[source]
"max"(MinMaxScaler),"std"(StdScaler),"quantile"(QuantileScaler) and"none"(NoneScaler).- Type:
The value normalization methods
alias of
Literal[‘max’, ‘std’, ‘quantile’, ‘none’]
- class torch_bsf.preprocessing.QuantileScaler[source]
Bases:
objectQuantile-based scaler that normalizes values using percentile-based ranges.
Values are scaled so that the
q-th percentile maps to 0 and the(1 - q)-th percentile maps to 1, without clipping values to this range. This makes the scaler robust to outliers compared toMinMaxScaler. Non-finite input, an unrepresentable fitted range, or an unrepresentable transformed result raisesValueError. Rescale original units first.- q[source]
The lower quantile fraction used as the effective minimum. Defaults to
0.05(5th percentile), ignoring the bottom and top 5% of values.- Type:
- scales[source]
Per-feature scale factors (
(1-q)-quantile minusq-quantile). Dimensions where the scale is zero are set to 1 to avoid division by zero.- Type:
- fit(values: Tensor) None[source]
Fit the scaler to the data.
Computes per-feature quantile bounds from
values.- Parameters:
values (torch.Tensor) – Input tensor of shape
(n_samples, n_features).
- fit_transform(values: Tensor) Tensor[source]
Fit the scaler to the data and return the scaled tensor.
- Parameters:
values (torch.Tensor) – Input tensor of shape
(n_samples, n_features).- Returns:
Scaled tensor of the same shape as
values. Values outside the fitted quantile range may exceed [0, 1].- Return type:
- inverse_transform(values: Tensor) Tensor[source]
Reverse the scaling applied by
fit_transform().- Parameters:
values (torch.Tensor) – Scaled tensor of shape
(n_samples, n_features)with the fitted feature count. Empty batches (zero samples) are accepted.- Returns:
Tensor rescaled back to the original value range.
- Return type:
- Raises:
ValueError – If the input is not two-dimensional, has a different feature count from the fitted data, or contains non-finite values or results.
- transform(values: Tensor) Tensor[source]
Scale
valueswith the fitted quantile bounds.- Parameters:
values (torch.Tensor) – Tensor of shape
(n_samples, n_features)with the fitted feature count. Empty batches (zero samples) are accepted.- Returns:
Transformed tensor of the same shape as
values.- Return type:
- Raises:
ValueError – If the input is not two-dimensional, has a different feature count from the fitted data, or contains non-finite values or results.
- class torch_bsf.preprocessing.Scaler(*args, **kwargs)[source]
Bases:
ProtocolProtocol for data scalers with a fit/transform interface.
All concrete scaler classes (
MinMaxScaler,StdScaler,QuantileScaler,NoneScaler) satisfy this protocol via structural subtyping — no explicit inheritance is required.- fit(values: Tensor) None[source]
Fit the scaler to
values.- Parameters:
values (torch.Tensor) – Observation matrix used to estimate feature-wise scaling statistics.
- fit_transform(values: Tensor) Tensor[source]
Fit the scaler to
valuesand return the transformed tensor.- Parameters:
values (torch.Tensor) – Observation matrix used to fit and apply feature-wise scaling.
- Returns:
Values after feature-wise scaling.
- Return type:
- inverse_transform(values: Tensor) Tensor[source]
Reverse the transformation applied by
fit_transform().- Parameters:
values (torch.Tensor) – Transformed observation matrix to restore to its original units.
- Returns:
Values restored to the original units.
- Return type:
- transform(values: Tensor) Tensor[source]
Transform
valueswith the fitted statistics.- Parameters:
values (torch.Tensor) – Observation matrix expressed in the original output units.
- Returns:
Values after feature-wise scaling.
- Return type:
- class torch_bsf.preprocessing.StdScaler[source]
Bases:
objectStandard-score (z-score) scaler that normalizes values to zero mean and unit variance.
Statistics use a scaled fallback to avoid intermediate overflow. An unrepresentable standard deviation or result raises
ValueError.- stds[source]
Per-feature standard deviations computed during
fit(). Dimensions with zero standard deviation are set to 1 to avoid division by zero.- Type:
- fit(values: Tensor) None[source]
Fit the scaler to the data.
Computes the per-feature mean and standard deviation from
values.- Parameters:
values (torch.Tensor) – Input tensor of shape
(n_samples, n_features).
- fit_transform(values: Tensor) Tensor[source]
Fit the scaler to the data and return the standardized tensor.
- Parameters:
values (torch.Tensor) – Input tensor of shape
(n_samples, n_features).- Returns:
Standardized tensor of the same shape as
values, with each feature having zero mean and unit standard deviation.- Return type:
- inverse_transform(values: Tensor) Tensor[source]
Reverse the standardization applied by
fit_transform().- Parameters:
values (torch.Tensor) – Standardized tensor of shape
(n_samples, n_features)with the fitted feature count. Empty batches (zero samples) are accepted.- Returns:
Tensor rescaled back to the original value range.
- Return type:
- Raises:
ValueError – If the input is not two-dimensional, has a different feature count from the fitted data, or contains non-finite values or results.
- transform(values: Tensor) Tensor[source]
Standardize
valueswith the fitted mean and standard deviation.- Parameters:
values (torch.Tensor) – Tensor of shape
(n_samples, n_features)with the fitted feature count. Empty batches (zero samples) are accepted.- Returns:
Transformed tensor of the same shape as
values.- Return type:
- Raises:
ValueError – If the input is not two-dimensional, has a different feature count from the fitted data, or contains non-finite values or results.
- torch_bsf.preprocessing.make_scaler(normalize: Literal['max', 'std', 'quantile', 'none']) Scaler[source]
The scaler of a normalization method.
Every scaler is an affine map of each feature, so it commutes with Bézier simploids, whose Bernstein basis sums to one: transforming the control points transforms the values of the model.
- Parameters:
normalize –
"max","std","quantile"or"none".- Returns:
A new, unfitted scaler implementing the selected normalization.
- Return type:
- Raises:
ValueError – If
normalizeis not one of the methods.
Examples
>>> import torch >>> from torch_bsf.preprocessing import make_scaler >>> scaler = make_scaler("max") >>> scaler.fit_transform(torch.tensor([[1.0], [3.0]])).tolist() [[0.0], [1.0]]
sampling
sample returns seeded parameter draws and grid returns deterministic
lattices. Neither obtains response measurements. Product factors retain
independent barycentric normalization, and assembled samples retain cell
IDs. See Sampling and grids and Grid sampling.
sample_skeleton splits rows among the vertices, edges and higher faces of
a simplex, simploid or assembled set, preferring the cell-optimal design with
a limited dimension fallback. optimal_skeleton_ratio returns a reusable
SkeletonAllocation with the actual strategy and reason; see Optimal skeleton sampling.
Domain-aware random and deterministic sampling, using local random generators.
Skeleton sampling and its optimal ratio follow the asymptotic risk analysis of inductive skeleton fitting by Tanaka et al. (AAAI 2020), extended to every dimension and degree and to products of simplices.
- torch_bsf.sampling.grid(space: BezierSpace, resolution: int = 10, *, cells: Sequence[int | Simploid] | None = None) Points[source]
Create tensor-product barycentric grids on the selected cells.
resolution=0gives a cell’s barycenter. Boundary points of different cells remain distinct parameter-domain points even when their images agree.- Parameters:
space (BezierSpace) – Compiled domain on which to construct cell-local grids.
resolution (int, optional) – Nonnegative lattice degree per factor; zero selects each cell’s barycenter.
cells (sequence of int or Simploid, optional) – Ordered cell IDs or original builder handles; defaults to the maximal cells.
- Returns:
Concatenated CPU float64 grid rows with explicit cell IDs and padded coordinate widths.
- Return type:
- torch_bsf.sampling.optimal_skeleton_ratio(space: BezierSpace, *, evaluation_weights: Mapping[int, float] | Mapping[Simploid, float] | Mapping[int | Simploid, float] | None = None, allocation: Literal['auto', 'dimension', 'cell'] = 'auto') SkeletonAllocation[source]
Choose a skeleton allocation, preferring the individual-cell asymptotic optimum.
- Parameters:
space – Compiled simplex, simploid or assembled domain with positive factor degrees.
evaluation_weights – Risk measure, normalized independently of sampling shares as in
skeleton_risk_coefficients().allocation –
"auto"prefers cell shares proportional tosqrt(c_s). It selects equal-within-dimension shares only when zero-risk active cells prevent a cell minimum but every active dimension has positive coefficient. Bare simplex/simploid constructors directly use their original fast dimension path."cell"and"dimension"select a strict method without fallback. Cell mode requires addressable faces viaon_set.
- Returns:
One stable result type for every strategy. Inspect
strategyandfallback_reason; pass the whole result assample_skeleton’sallocationargument to preserve within-dimension fractions and the corresponding rounding rule.ratiogives dimension totals;cell_weightsgives active-cell fractions on addressable spaces.- Return type:
- Raises:
TypeError – If
spaceis not a BezierSpace.ValueError – For invalid inputs, unreliable numerical evaluation, work-limit errors, or a zero coefficient in an active dimension. Strict cell allocation also rejects zero-coefficient active cells. Zero-coefficient cases describe a mathematical infimum, not an attained positive-allocation optimum. Numerical and input errors never trigger fallback.
Examples
>>> from torch_bsf import BezierSpace, SimploidalSet >>> from torch_bsf.sampling import optimal_skeleton_ratio, sample_skeleton >>> domain = SimploidalSet() >>> _ = domain.add_simplex(1) >>> space = BezierSpace.on_set(domain, degree=2) >>> plan = optimal_skeleton_ratio(space) >>> plan.strategy, plan.fallback_reason ('cell', None) >>> [round(r, 4) for r in plan.ratio] [0.4142, 0.5858] >>> len(sample_skeleton(space, 8, allocation=plan, seed=0)) 8
- torch_bsf.sampling.sample(space: BezierSpace, n: int, *, method: Literal['random', 'sobol'] = 'random', seed: int | None = None, cell_weights: Mapping[int, float] | Mapping[Simploid, float] | Mapping[int | Simploid, float] | None = None) Points[source]
Sample a space, uniformly within each selected cell’s simplex factors.
Cell probabilities default to equal weights on maximal cells, rather than an unspecified geometric volume. Sobol coordinates use exponential normalization of scrambled quasi-random uniforms. Cell selection remains categorical random sampling. Neither method resets global random state.
- Parameters:
space (BezierSpace) – Compiled domain to sample, with cell-local barycentric coordinate blocks.
n (int) – Nonnegative number of coordinate rows to draw.
method ({"random", "sobol"}, optional) – Uniform source used before exponential normalization within each factor.
seed (int, optional) – Seed for a local generator; omitted to seed it nondeterministically.
cell_weights (mapping, optional) – Nonnegative weights by cell ID or builder handle, normalized to probabilities; defaults to equal maximal-cell weights.
- Returns:
CPU float64 coordinates with explicit cell IDs and padding to the space width.
- Return type:
Examples
>>> from torch_bsf import BezierSpace >>> from torch_bsf.sampling import sample >>> sample(BezierSpace.simplex(2, degree=3), 5, seed=0).parameters.shape torch.Size([5, 3])
- torch_bsf.sampling.sample_skeleton(space: BezierSpace, n: int, *, ratio: Sequence[float] | Tensor | None = None, method: Literal['random', 'sobol'] = 'random', seed: int | None = None, evaluation_weights: Mapping[int, float] | Mapping[Simploid, float] | Mapping[int | Simploid, float] | None = None, cell_weights: Mapping[int, float] | Mapping[Simploid, float] | Mapping[int | Simploid, float] | None = None, allocation: Literal['auto', 'dimension', 'cell'] | SkeletonAllocation = 'auto') Points[source]
Stratified sample of a simploidal space for inductive skeleton fitting.
Automatic allocation prefers individual-cell optimal shares and falls back to equal-within-dimension shares only when zero-risk cells can be rescued. Explicit
ratioor dimension allocation first rounds each dimension’s budget and splits it equally among active faces, with random remainder ties. Cell allocation uses one largest-remainder rounding in fitting order. Rows are uniform on their face, with exact zeros outside it, and are ordered by face dimension.A face of a simploid is a product of one face per simplex factor, and its rows are uniform on each factor’s face. The faces of one skeleton can differ in shape: the edges of a prism are triangle edges at an end of the interval and intervals at a vertex of the triangle. A skeleton’s rows then go only to its faces with interior controls (no factor has more vertices than its degree). For the original single-cell constructors, an explicit ratio may also sample a dimension with no interior controls, using all its faces; fitting ignores those rows. Assembled spaces sample active cells only and reject positive shares on dimensions without any active cells. On a single simplex, either every face of a skeleton has interior controls or none has.
- Parameters:
space – A simplex, simploid or assembled set with positive degrees in positive-dimensional factors.
n – Number of rows.
ratio – Relative sizes of the 0-, 1-, …, n-skeletons of a domain of dimension n: n + 1 finite nonnegative real entries with a positive sum, that is, M entries for a simplex with M vertices. When omitted,
allocationchooses the automatic plan, with no rows on skeletons without interior controls. Rows on faces without interior controls (with more vertices than the degree in some factor) are not used byfit_skeleton_regression().method –
"random"or"sobol"coordinates within each face, as insample().seed – Seed of the local generator; global random state is not used.
evaluation_weights – Evaluation measure used for automatic optimization. Validated independently of explicit sampling shares; see
skeleton_risk_coefficients().cell_weights – Explicit active-cell shares by cell ID or builder handle, normalized to probabilities. Mutually exclusive with
ratio. Requires addressable faces (BezierSpace.on_setor a zero-dimensional space). Pass thecell_weightsfield fromoptimal_skeleton_ratio()here.allocation –
"auto"(default), strict"cell"or strict"dimension"strategy, or a reusableSkeletonAllocationfromoptimal_skeleton_ratio(). Plans cannot be combined with explicitratioorcell_weightsand must match the space. Explicit shares override automatic optimization; contradictory strict methods raise.
- Returns:
Exactly
nCPU float64 rows. Assembled rows carry active nondegenerate cell IDs and zero padding. Largest-remainder rounding preserves the budget, even forn=0or budgets below the active-cell count; it does not guarantee sufficient rows or rank for fitting. Dimension shares are split equally within each dimension, with randomized remainder ties. Pass a plan to inspect its strategy and fallback reason before sampling. On assembled spaces, positive shares on empty dimensions or inactive cells are rejected. Explicit shares bypass risk conditioning and dense-computation limits.- Return type:
- Raises:
TypeError – If
spaceis not aBezierSpace.ValueError – If a positive-dimensional factor has degree zero,
nis not a nonnegative integer,methodis unknown, orratiois malformed. With the defaultratio, also ifskeleton_risk_coefficients()rejects the space (too high a degree, or too many factors on a simploid); passratiothen.
Examples
>>> from torch_bsf import BezierSpace >>> from torch_bsf.sampling import sample_skeleton >>> points = sample_skeleton(BezierSpace.simplex(2, degree=2), 100, seed=0) >>> (points.parameters > 0).sum(1).bincount().tolist() [0, 26, 74]
- torch_bsf.sampling.skeleton_risk_coefficients(space: BezierSpace, *, evaluation_weights: Mapping[int, float] | Mapping[Simploid, float] | Mapping[int | Simploid, float] | None = None, allocation: Literal['dimension'] = 'dimension') tuple[float, ...][source]
- torch_bsf.sampling.skeleton_risk_coefficients(space: BezierSpace, *, evaluation_weights: Mapping[int, float] | Mapping[Simploid, float] | Mapping[int | Simploid, float] | None = None, allocation: Literal['cell']) dict[int, float]
Asymptotic risk coefficients of inductive skeleton fitting on a simploidal space.
Inductive skeleton fitting estimates the controls interior to each face by least squares on that face’s observations, holding the controls of lower faces at their estimates (see
torch_bsf.fitting.fit_skeleton_regression()). With \(N^{(m)}\) observations on the \(m\)-skeleton, split equally among its faces and drawn uniformly on each, outputs of dimension \(L\) and independent noise of variance \(\sigma^2\), the mean integrated squared error over the uniform domain satisfies\[R = \sigma^2 L \sum_{m} a_m / N^{(m)} + o(1 / N).\]Coefficient
mis \(a_m\). It is zero for skeletons whose faces have no interior controls (on a simplex, \(m \geq\) degree). This extends the numerical tables of Tanaka et al. (AAAI 2020) to every dimension and degree.On a simploid, a product of simplices, a face is a product of one face per factor and the \(m\)-skeleton consists of the faces of dimension \(m\). A face has interior controls when no factor has more vertices than its degree, and the \(N^{(m)}\) observations are split equally among the faces of dimension \(m\) with interior controls, as
sample_skeleton()does withallocation="dimension". Observations and the error are uniform on the product.- Parameters:
space – A simplex, simploid or assembled set with positive degrees in positive-dimensional factors.
evaluation_weights – Nonnegative chart weights by cell ID or builder handle, normalized to a probability measure; omitted cells have zero weight. Defaults to equal weights on maximal cells. ID/handle aliases add. Unknown cells, nonfinite or negative weights and a nonfinite or zero total are rejected.
allocation –
"dimension"returns the usual tuple;"cell"returns a dictionary of active cell IDs to coefficients and requires addressable faces viaBezierSpace.on_set(or a zero-dimensional space).
- Returns:
\(a_0, \dots, a_n\) for a domain of dimension \(n\), which is \(M - 1\) for a simplex with \(M\) vertices.
- Return type:
- Raises:
TypeError – If
spaceis not aBezierSpace.ValueError – If a positive-dimensional factor has degree zero, weights are invalid, numerical reliability checks fail, or a computation limit is exceeded. For the original bare simplex algorithm, the largest accepted degree falls with the dimension: 14 for 2 vertices, 12 for 3, 11 for 4, 9 for 5, 8 for 6 and 7, and 7 for 8. On a bare simploid, the condition number of the Gram matrix on the whole simploid is the product of those of its factors’ Gram matrices, so it also grows with the number of factors: 12 segments of degree 1 are rejected.
Notes
On an assembled set, cell coefficients satisfy
risk = sum(c_s / q_s)up to the common noise/output/budget factor. Dimension coefficients area_m = K_m * sum(c_s for dim(s) == m). The evaluator is independent of sampling allocation and may include lower cells. The model must be correctly specified, with independent homoscedastic noise, uniform within-cell designs and unregularized least squares. Graphs, linear factor degrees and regular complexes use the closed graph, corner-collision and weighted-incidence formulas, without global dense matrices. Incidences retain conditioned factor computations with at most 512 controls per factor. Nonlinear identified sets retain dense propagation, limited to 512 global controls and 4,000,000 local Gram entries, with local/propagated conditioning checks. Unrepresentable positive weights and subnormal weighted risk contributions raise a numerical reliability error instead of being classified as mathematical zeros. Zero coefficients do not remove active cells from the estimator. See Risk on an assembled simploidal set.On a face of a single simploid, the Gram matrices and the propagated errors are Kronecker products of those of its factor faces, so a face’s trace \(\mathrm{tr}(G^I_{TT}{}^{-1} E_I^\top G E_I)\) is the product of theirs. The coefficients are therefore computed factor by factor: \(a_m = K_m\, [x^m] \prod_j p_j(x)\), where \(K_m\) is the number of faces of dimension \(m\) with interior controls and the coefficient of \(x^s\) in \(p_j\) sums the traces of the \(s\)-faces of factor \(j\) on that simplex. Vertex permutation symmetry permits one representative noise source per face dimension, propagated to all controls. Matrix products are batched by dimension and propagation uses only noises in each face closure. The dense Gram matrix still needs quadratic memory in a factor’s \(\binom{D + k}{D}\) controls. At fixed degree, its construction and the trace contractions take quadratic rather than cubic time in that count, apart from the coordinate and face bookkeeping. The number of controls of the product does not enter these matrices.
A simplex is its own only factor. The condition number of a factor’s Gram matrix is \(\binom{2D + k}{D}\) in closed form, so a space that the conditioning check rejects is rejected before anything of the size of a factor is allocated. Accepted wide simplices remain costly: the widest ones, such as \(M = 22\) vertices (\(k = 21\)) at degree 4 with 12650 controls, still need a dense float64 Gram matrix of 1.3 GB and similarly sized temporaries during its construction. If an allocation fails, PyTorch raises
RuntimeError; on systems that overcommit memory, such as Linux by default, the process may be terminated instead.Examples
>>> from torch_bsf import BezierSpace >>> from torch_bsf.sampling import skeleton_risk_coefficients >>> [round(a, 4) for a in skeleton_risk_coefficients(BezierSpace.simplex(2, degree=3))] [0.1464, 3.5357, 1.0]
fitting
fit solves regression on known parameter-response pairs and returns
FitResult. Fixed controls and quadratic penalties participate in the
same solve. Evaluate result.model(points) after checking the numerical
status; there is no initial guess for this linear least-squares operation.
fit_skeleton_regression instead fits a simplex or simploid face by face from
observations on its faces; see Optimal skeleton sampling.
Supervised fitting at known parameters: one entry point for every space and solver, and face-by-face simplex fitting.
- torch_bsf.fitting.fit(space: BezierSpace, samples: Samples, *, solver: DenseLeastSquares | SparseLeastSquares = DenseLeastSquares(rcond=None, diagnostics='basic'), penalty: QuadraticPenalty = QuadraticPenalty(ridge=0.0, smoothness=0.0), fixed: FixedControls | None = None) FitResult[source]
Fit a space to observations, returning an explicit numerical result.
Minimizes
sum(w * squared_output_error) / sum(w)plusridge * ||P||²andsmoothness * ||D P||²in the original output units. Data MSE divides the data term by the output dimension. Fixed coefficients are exact values, not initial guesses. Neither the space nor observations are modified.- Parameters:
space – A compiled single-cell or glued-cell polynomial space.
samples – Coordinates, outputs and optional sample weights.
solver – Typed dense SVD or sparse LSMR configuration. Both compute on CPU float64 and return a float64 model; no implicit downcast is performed.
penalty – Quadratic weights, independent of the number of observations.
fixed – Explicit globally shared coefficient values.
- Returns:
Model, metrics, diagnostics and termination state. This type never changes with diagnostic settings. Predict with
result.model(...). Numerical failures returnsuccess=Falseand emit ConvergenceWarning;require_success()raises with the result attached. If no finite iterate exists,diagnostics.solution_availableis false and the model contains placeholder free coefficients rather than a fit.- Return type:
Examples
>>> import torch >>> from torch_bsf import BezierSpace, Samples, fit >>> t = torch.eye(2) >>> result = fit(BezierSpace.simplex(1, degree=1), Samples(t, [[1.], [2.]])) >>> result.success, result.model(t).tolist() (True, [[1.0], [2.0]])
- torch_bsf.fitting.fit_skeleton_regression(space: BezierSpace, samples: Samples, *, solver: DenseLeastSquares | SparseLeastSquares = DenseLeastSquares(rcond=None, diagnostics='basic')) SkeletonRegressionResult[source]
Fit a simploidal space cell by cell from observations at known parameters.
This is the inductive skeleton fitting analyzed by Tanaka et al. (AAAI 2020). Vertices are fitted first, then edges, then faces of higher dimension. On each face, ordinary least squares on the observations lying on that face estimates the controls interior to it, with the controls on its boundary fixed at their estimates. Contrast
fit(), which uses every observation for every control, andfit_skeleton(), which infers unknown parameters.A row lies on the face spanned by its nonzero coordinates; zeros must be exact. A face of a simploid is a product of one face per simplex factor and is keyed by the sorted global coordinate indices of its vertices. Draw rows with
sample_skeleton()and theoptimal_skeleton_ratio()to minimize the asymptotic risk given byskeleton_risk_coefficients().For an assembled set, exact-zero boundaries are mapped through the compiled face and degeneracy maps to their nondegenerate cell. Each row is used once. Shared cells are fitted once.
facesuses integer cell IDs, while single simplex/simploid spaces retain their coordinate-tuple keys. Vertices, including isolated vertices, have one proper control and use their directly tagged rows.- Parameters:
space – A simplex, simploid or assembled set, with positive degrees in positive-dimensional factors.
samples – Observations at known parameters. Every face with at most
degreevertices in each factor needs at least one row of positive weight. Rows on other faces, which have no interior controls, are ignored and counted in the result’s message. Weights apply within each face.solver – Least-squares configuration used for every face.
- Returns:
The assembled model and each face’s
FitResult. A face whose rows do not identify its interior controls is not a failure; inspect that face’sdiagnostics.data.rank. If a face’s solve fails, as reported by itsFitResult(with aConvergenceWarning), fitting stops there:successisFalse,facesends with the failed face, and controls of later faces stay zero.- Return type:
- Raises:
TypeError – If
spaceorsampleshas the wrong type.ValueError – If a positive-dimensional factor has degree zero, or an active face/cell has no row of positive weight.
Examples
>>> import torch >>> from torch_bsf import BezierSpace, Samples >>> from torch_bsf.fitting import fit_skeleton_regression >>> from torch_bsf.sampling import sample_skeleton >>> space = BezierSpace.simplex(1, degree=2) >>> points = sample_skeleton(space, 8, seed=0) >>> t = points.parameters >>> result = fit_skeleton_regression(space, Samples(points, (t[:, 1] ** 2)[:, None])) >>> sorted(result.faces), torch.allclose(result.model(t), (t[:, 1] ** 2)[:, None]) ([(0,), (0, 1), (1,)], True)
On a square of degree one, only the vertices have interior controls; each vertex key lists one coordinate index of each factor.
>>> square = BezierSpace.simploid((1, 1), degree=1) >>> points = sample_skeleton(square, 8, seed=0) >>> result = fit_skeleton_regression(square, Samples(points, points.parameters[:, [1]] * points.parameters[:, [3]])) >>> sorted(result.faces) [(0, 2), (0, 3), (1, 2), (1, 3)]
point_cloud
fit_point_cloud fits observations whose parameter coordinates are
unknown, alternating inferred coordinates and controls. fit_skeleton
fits faces inductively while preserving their boundaries. These nonlinear
methods return explicit local convergence states. See
Fitting from values alone for assumptions, initialization and examples.
Value-only Bézier simplex and simploid fitting (Kobayashi et al., AAAI 2019).
Algorithm 2 alternates constrained Newton parameter updates and linear least squares. Algorithm 3 fits faces in increasing support size, preserving the controls already estimated on their boundaries.
- class torch_bsf.point_cloud.PointCloudFitResult(model: BezierModel, parameters: Tensor, objective_history: tuple[float, ...], success: bool, status: str, message: str, n_iterations: int, parameter_converged: bool, control_result: FitResult | None)[source]
Bases:
objectA fitted surface and inferred barycentric rows in observation order.
objective_historycontains the initial mean squared Euclidean distance and each accepted alternating update (no division by output dimension). Success describes local numerical convergence, not global optimality or identification of a unique parameterization. All tensors are CPU float64.- require_success() PointCloudFitResult[source]
Raise with the termination message if convergence was not established.
- Returns:
This result when convergence was established.
- Return type:
- class torch_bsf.point_cloud.SkeletonFitResult(model: BezierModel, faces: dict[tuple[int, ...], PointCloudFitResult], success: bool, status: str, message: str)[source]
Bases:
objectAssembled model and local results keyed by sorted zero-based vertex IDs.
Each face result contains local barycentric coordinates for that face’s observations. On a simploid, factors in which the face is a single vertex have no local coordinates, so the columns are the face’s vertices in the other factors, in key order. Previously fitted boundary controls remain exactly fixed.
- require_success() SkeletonFitResult[source]
Raise if any face failed to converge.
- Returns:
This result when every required face fit converged.
- Return type:
- torch_bsf.point_cloud.fit_point_cloud(space: BezierSpace, values: Any, *, initial: BezierModel | None = None, fixed: FixedControls | None = None, max_iter: int = 100, parameter_max_iter: int = 100, tolerance: float = 1e-05, parameter_tolerance: float = 1e-05) PointCloudFitResult[source]
Fit values without supplied domain coordinates (paper Algorithm 2).
Minimize the mean squared Euclidean distance by alternating parameter and control updates. Parameters use analytic first/second derivatives with projected Newton steps, backtracking and a gradient fallback. Controls use CPU float64 SVD least squares, never normal equations.
fixedspecifies exact controls throughout, including before the first parameter update.Automatic initialization uses the observed minimum of each output as a vertex and the affine grid between those vertices, as in the paper. It assumes minimization objectives and one output per vertex. Supply an
initialmodel for arbitrary embeddings or a different orientation. No input model or observation is modified. Exhausting either iteration budget does not certify convergence.A simploid, a product of simplices, is fitted the same way: each row’s coordinates stay on every factor’s simplex, and Newton steps keep each factor’s coordinate sum. Automatic initialization needs a single simplex, so supply
initialfor a simploid. Assembled spaces are not supported.tolerancebounds absolute change in root mean squared distance;parameter_tolerancebounds the projected gradient residual. The stopping rule uses the absolute change, correcting the signed-change expression printed in the paper. The objective uses the original units; scale observations explicitly when outputs have incompatible units.- Parameters:
space (BezierSpace) – Single simplex or simploid specifying the dimensions and polynomial degrees.
values (array-like) – Finite observations of shape
(n_samples, n_outputs)in the original output units.initial (BezierModel, optional) – Initial model with compatible space and output dimension; otherwise infer minimization vertices, which requires a single simplex.
fixed (FixedControls, optional) – Coefficient rows kept exact throughout fitting, including initialization.
max_iter (int, optional) – Positive budget of alternating parameter and control updates.
parameter_max_iter (int, optional) – Positive iteration budget for each parameter-coordinate solve.
tolerance (float, optional) – Finite nonnegative tolerance on the absolute change in root mean squared distance.
parameter_tolerance (float, optional) – Finite nonnegative tolerance on the projected parameter-gradient residual.
- Returns:
Model, inferred barycentric coordinates, objective history and convergence evidence.
- Return type:
- Raises:
TypeError – If
spaceis not aBezierSpaceorfixedis notFixedControls.ValueError – If
spacehas several cells or a positive-dimensional factor of degree zero,valuesis not a finite two-dimensional array of the required shape, a setting is invalid,initialis incompatible, or automatic initialization is impossible. Non-numericvaluesraise the error oftorch.as_tensor().
Examples
Recover a bilinear map of a square, a product of two segments, from values alone.
>>> import torch >>> from torch_bsf import BezierModel, BezierSpace, fit_point_cloud >>> from torch_bsf.sampling import grid >>> space = BezierSpace.simploid((1, 1), degree=1) >>> truth = BezierModel(space, torch.tensor([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])) >>> values = truth(grid(space, 4)).detach() >>> result = fit_point_cloud(space, values, initial=truth).require_success() >>> result.objective_history[-1] < 1e-12 True
- torch_bsf.point_cloud.fit_skeleton(space: BezierSpace, values: Any, *, face_values: Mapping[tuple[int, ...], Any] | None = None, initial: BezierModel | None = None, max_iter: int = 100, parameter_max_iter: int = 100, tolerance: float = 1e-05, parameter_tolerance: float = 1e-05) SkeletonFitResult[source]
Fit faces inductively while fixing their boundaries (paper Algorithm 3).
On a simplex, faces are sorted tuples of zero-based vertex IDs, and only support sizes up to
min(degree, number_of_vertices)are needed. By default each face’s samples are the nondominated points for its objective subset (all outputs are minimization objectives). This requires a single simplex and one output per vertex.face_valuesinstead supplies every required face’s value-only samples, allowing arbitrary embeddings withinitial. Full original output vectors must be retained on every face. No face memberships or coordinates are inferred from an arbitrary embedding.Vertex controls are sample means. Higher faces use
fit_point_cloudwith all lower-face controls fixed exactly. Inspectfacesfor local stopping evidence; failure on a boundary stops assembly and returns the finite partial model withsuccess=False.A face of a simploid, a product of simplices, is a product of one face per factor, keyed by the sorted global coordinate indices of its vertices; the faces with at most
degreevertices in every factor are needed. Each face is fitted on the product of its factors’ faces. A simploid requires bothface_valuesandinitial.- Parameters:
space (BezierSpace) – Single simplex or simploid specifying the dimensions and polynomial degrees.
values (array-like) – Finite observations of shape
(n_samples, n_outputs)in the original output units.face_values (mapping, optional) – Samples for every required sorted vertex tuple, retaining all output columns; otherwise extract nondominated faces.
initial (BezierModel, optional) – Compatible initial model, required for embeddings that cannot use automatic minimization vertices.
max_iter (int, optional) – Positive budget of alternating parameter and control updates.
parameter_max_iter (int, optional) – Positive iteration budget for each parameter-coordinate solve.
tolerance (float, optional) – Finite nonnegative tolerance on the absolute change in root mean squared distance.
parameter_tolerance (float, optional) – Finite nonnegative tolerance on the projected parameter-gradient residual.
- Returns:
Assembled model, per-face results and an explicit success or partial-failure state.
- Return type:
- Raises:
TypeError – If
spaceis not aBezierSpace.ValueError – If
spacehas several cells or a positive-dimensional factor of degree zero,valuesor aface_valuesentry is not a finite two-dimensional array of the required shape, theface_valueskeys are not the required faces, a setting is invalid,initialis incompatible, or automatic initialization or face extraction is impossible. Non-numeric arrays raise the error oftorch.as_tensor().
Examples
Fit a bilinear square from values at its four corners.
>>> import torch >>> from torch_bsf import BezierModel, BezierSpace, fit_skeleton >>> space = BezierSpace.simploid((1, 1), degree=1) >>> truth = BezierModel(space, torch.tensor([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])) >>> corners = {(0, 2): [[0.0, 0.0]], (0, 3): [[1.0, 0.0]], (1, 2): [[0.0, 1.0]], (1, 3): [[1.0, 1.0]]} >>> values = torch.tensor([v[0] for v in corners.values()]) >>> result = fit_skeleton(space, values, face_values=corners, initial=truth).require_success() >>> sorted(result.faces), torch.equal(result.model.coefficients, truth.coefficients.double()) ([(0, 2), (0, 3), (1, 2), (1, 3)], True)
results
Results separate a computed model from evidence about the computation.
require_success raises on unsuccessful termination. Data diagnostics
describe observation identifiability, whereas system diagnostics can also
include penalty rows. Successful termination is distinct from held-out
prediction accuracy.
Explicit numerical results: a computed model is not a convergence certificate.
- exception torch_bsf.results.ConvergenceError(result: FitResult)[source]
Bases:
RuntimeErrorFailure requested by
require_success(), retaining the numerical result.- Parameters:
result (FitResult) – Failed solve retained on
resultwith its termination message.
- exception torch_bsf.results.ConvergenceWarning[source]
Bases:
UserWarningA numerical method stopped without meeting its convergence criteria.
- class torch_bsf.results.FitDiagnostics(solver: str, n_samples: int, n_free_controls: int, weight_sum: float, data: MatrixDiagnostics, system: MatrixDiagnostics, compute_dtype: str = 'float64', compute_device: str = 'cpu', model_dtype: str = 'float64', model_device: str = 'cpu', iterations: tuple[int | None, ...] = (), stop_codes: tuple[int | None, ...] = (), residual_norms: tuple[float | None, ...] = (), normal_residual_norms: tuple[float | None, ...] = (), condition_estimates: tuple[float | None, ...] = (), driver: str | None = None, output_success: tuple[bool, ...] = (), output_statuses: tuple[str, ...] = (), solution_available: bool = True)[source]
Bases:
objectCompute provenance and observation/system diagnostics in original units.
driveridentifies the executed backend (gelsdorlsmr), or isNoneif no solve was executed.output_successandoutput_statusesrecord termination for each output column, in prediction-column order.solution_availableis false when a numerical failure left no finite iterate for at least one column; seeFitResultfor its placeholder. Residual norms and condition estimates from LSMR concern the augmented weighted, constrained system, not the unpenalized prediction MSE. Iterative fields useNonefor a column whose RHS overflowed and was not solved, or empty tuples if no iterative solve ran at all. Overall success can still be false after successful solves if objective evaluation or optional diagnostics fail; these failures do not erase solver termination.
- class torch_bsf.results.FitResult(model: BezierModel, metrics: dict[str, float], diagnostics: FitDiagnostics, success: bool, status: str, message: str)[source]
Bases:
objectA model, objective metrics, diagnostics and an explicit termination state.
Results are deliberately not callable. Predict with
result.model(points). Ifdiagnostics.solution_availableis false, the model is only a finite placeholder preserving fixed controls, with free rows of unavailable output columns zero. It must not be used as a fitted solution. Finite unconverged LSMR iterates are retained.- require_success() FitResult[source]
Return this result, or raise a
ConvergenceErrorcontaining it.- Returns:
This result when the solve succeeded.
- Return type:
- class torch_bsf.results.MatrixDiagnostics(shape: tuple[int, int], rank: int | None = None, rank_threshold: float | None = None, singular_values: Tensor | None = None, condition_number: float | None = None, method: Literal['svd', 'estimate', 'not_computed', 'no_free_controls'] = 'not_computed')[source]
Bases:
objectInformation about one explicitly identified least-squares matrix.
Nonemeans not computed, never zero.methoddistinguishes a direct singular-value calculation from an iterative condition estimate. A rank below the number of free columns has infinite coefficient condition number.
- class torch_bsf.results.SkeletonAllocation(space: BezierSpace, strategy: Literal['cell', 'dimension'], ratio: tuple[float, ...], cell_weights: Mapping[int, float], fallback_reason: Literal['single_cell_fast_path', 'zero_cell_coefficients'] | None = None)[source]
Bases:
objectReusable skeleton allocation and an explicit record of the selected strategy.
ratiocontains total fractions by dimension.cell_weightscontains individual active-cell fractions on addressable spaces, and is empty for bare positive-dimensional simplex/simploid constructors. Pass this entire result assample_skeleton(..., allocation=result)to preserve both the selected rounding strategy and unequal within-dimension cell fractions.strategyis the method actually used.fallback_reasonis"single_cell_fast_path"when automatic selection retains the original unassembled algorithm,"zero_cell_coefficients"when equal dimension shares rescue zero-risk cells, and otherwiseNone. The frozen space snapshot prevents accidental reuse on an incompatible domain or degree.- cell_weights[source]
Read-only active-cell fractions, empty for bare positive-dimensional spaces.
- Type:
Mapping of int to float
- fallback_reason[source]
Reason automatic selection used dimension allocation, or None otherwise.
- Type:
{“single_cell_fast_path”, “zero_cell_coefficients”} or None
Examples
>>> from torch_bsf import BezierSpace, SkeletonAllocation >>> from torch_bsf.sampling import optimal_skeleton_ratio, sample_skeleton >>> space = BezierSpace.simplex(1, degree=2) >>> plan = optimal_skeleton_ratio(space) >>> isinstance(plan, SkeletonAllocation), plan.strategy, plan.fallback_reason (True, 'dimension', 'single_cell_fast_path') >>> len(sample_skeleton(space, 8, allocation=plan, seed=0)) 8
- class torch_bsf.results.SkeletonRegressionResult(model: BezierModel, faces: dict[tuple[int, ...] | int, FitResult], success: bool, status: str, message: str)[source]
Bases:
objectAssembled model and per-stage least-squares results.
The vertex IDs of a simploid are its global coordinate indices, so a face lists the vertices of every factor. Each face result solves for that face’s interior controls with every other control fixed. Faces are fitted in increasing dimension; a failed face stops assembly, leaving later faces absent and their controls zero. For spaces created with
BezierSpace.on_setand zero-dimensional spaces, keys are integer nondegenerate cell IDs, which distinguish local coordinate indices in different cells. The original single-cell simplex/simploid API retains its sorted coordinate-tuple keys.- require_success() SkeletonRegressionResult[source]
Raise
ConvergenceErrorwith the failed face’s result if a face fit failed.- Returns:
This result when every required face fit succeeded.
- Return type:
solvers
Choose a typed dense or sparse least-squares configuration. Both compute CPU float64 models; sparse solving uses the optional SciPy dependency. Tolerances and iteration limits concern the numerical solve rather than polynomial capacity. See Solvers, performance and optional execution.
Typed numerical solver configurations; importing this module does not load SciPy.
- class torch_bsf.solvers.DenseLeastSquares(rcond: float | None = None, diagnostics: Literal['basic', 'full'] = 'basic')[source]
Bases:
objectCPU float64 SVD solve of the augmented problem, without normal equations.
model_selection
Validation operations compare predictions on withheld observations and retain fold results. Degree selection changes capacity and requires an explicit final refit when using the core selection API. Learn output preprocessing inside each fold to prevent validation leakage.
Validation and degree search return explicit evidence rather than bare models.
- class torch_bsf.model_selection.CVResult(folds: tuple[FoldResult, ...], metrics: dict[str, float], success: bool)[source]
Bases:
objectValidation evidence; never an implicitly refitted model or ensemble.
- property models: tuple[BezierModel, ...][source]
Models available for explicitly constructing an Ensemble.
- class torch_bsf.model_selection.DegreeCandidate(degree: int | tuple[int, ...], space: BezierSpace, validation: CVResult)[source]
Bases:
objectA candidate degree and all its validation evidence.
- class torch_bsf.model_selection.DegreeSearchResult(candidates: tuple[DegreeCandidate, ...], best_index: int | None, searched_all: bool = True)[source]
Bases:
objectAll evaluated candidates and a selection, without an implicit full-data fit.
- class torch_bsf.model_selection.FoldResult(train_indices: Tensor, test_indices: Tensor, result: FitResult, metrics: dict[str, float], test_weight: float)[source]
Bases:
objectA fitted training fold with its untouched held-out indices and metrics.
- class torch_bsf.model_selection.KFold(n_splits: int = 5, shuffle: bool = True, seed: int | None = None)[source]
Bases:
objectLocal-generator k-fold splits; an impossible requested split count is an error.
- torch_bsf.model_selection.cross_validate(space: BezierSpace, samples: Samples, *, cv: KFold = KFold(n_splits=5, shuffle=True, seed=None), solver: DenseLeastSquares | SparseLeastSquares = DenseLeastSquares(rcond=None, diagnostics='basic'), penalty: QuadraticPenalty = QuadraticPenalty(ridge=0.0, smoothness=0.0), fixed: FixedControls | None = None) CVResult[source]
Fit each training fold and retain split, solver and held-out evidence.
Every fold must have positive observation weight in both its train and test portions. Failed solves remain in the result and make the whole CV fail; they are never silently dropped from model selection. Non-finite held-out or aggregate metrics also make CV fail, retaining every fold for inspection. Zero-weight held-out rows are excluded from metric evaluation.
- torch_bsf.model_selection.select_degree(space: BezierSpace, samples: Samples, *, degrees: Sequence[int | Sequence[int]], cv: KFold = KFold(n_splits=5, shuffle=True, seed=None), solver: DenseLeastSquares | SparseLeastSquares = DenseLeastSquares(rcond=None, diagnostics='basic'), penalty: QuadraticPenalty = QuadraticPenalty(ridge=0.0, smoothness=0.0)) DegreeSearchResult[source]
Evaluate every specified degree on identical folds and record the result.
Invalid degrees raise; failed numerical solves remain visible and cannot win. Non-finite validation metrics likewise make candidates ineligible. If none is eligible, best_index is None and success is False. To refit, explicitly call
fit(result.best_space, samples, ...).
ensemble
An ensemble combines explicitly selected compatible models for prediction. It is a deliberate modeling choice, not an automatic consequence of cross-validation. Compare its validation behavior with a single model refitted on all available training observations.
Explicit ensembling, independent of cross-validation and model selection.
- class torch_bsf.ensemble.Ensemble(models: Sequence[BezierModel])[source]
Bases:
ModuleMean predictor over copied models with the same space and output dimension.
- Parameters:
models (sequence of BezierModel) – Nonempty collection with matching space, output dimension, coefficient dtype and device.
- forward(points: Points | Tensor) Tensor[source]
Return the ensemble mean, without changing the member models.
Finite groups near sum overflow use an exact CPU reduction to retain cancelling residuals. The overflow-risk check synchronizes accelerators; only affected groups transfer to CPU. Ordinary groups remain vectorized on their device, and finite-input gradients follow the ordinary mean.
- Parameters:
points (Points or torch.Tensor) – Evaluation coordinates accepted by every member model; multi-cell spaces require Points.
- Returns:
Mean predictions of shape
(n_points, n_outputs).- Return type:
- members(points: Points | Tensor) Tensor[source]
Predict with every member, returning shape
(n_models, n_points, n_outputs).- Parameters:
points (Points or torch.Tensor) – Evaluation coordinates accepted by every member model; multi-cell spaces require Points.
- Returns:
Member predictions of shape
(n_models, n_points, n_outputs).- Return type:
active_learning
Active learning selects additional parameter locations from a candidate pool. The application still obtains the responses and appends observations. Assess progress on an external validation set rather than only the adaptively selected training rows. See Explicit ensembles and active learning.
Acquisition functions with explicit, mathematically relevant inputs.
- torch_bsf.active_learning.farthest_points(candidates: Points, observed: Points, n: int = 1, *, chunk_size: int = 1024) Points[source]
Greedily cover parameter coordinates, comparing distances within cells.
Unobserved cells are selected first. No dummy ensemble or duplicated geometry/dimension arguments are required for this density acquisition. Equal distances select the earliest remaining candidate.
chunk_sizebounds both axes of each temporary distance matrix, using at mostchunk_size ** 2float64 distances. Smaller chunks reduce memory at the cost of more distance-kernel calls; arithmetic work is unchanged. Distances use direct coordinate differences to retain small separations between nearby points, independently of the candidate/observation count.- Parameters:
candidates (Points) – Candidate parameter coordinates with their cell IDs.
observed (Points) – Existing observations in the same coordinate layout.
n (int, default=1) – Number of distinct candidate rows to select.
chunk_size (int, default=1024) – Positive upper bound on each distance-matrix dimension.
- Returns:
Selected candidates in greedy order, retaining input gradients.
- Return type:
Examples
>>> from torch_bsf import Points >>> from torch_bsf.active_learning import farthest_points >>> farthest_points(Points([[0.0], [1.0]]), Points([[0.0]]), chunk_size=1).parameters.tolist() [[1.0]]
- torch_bsf.active_learning.query_by_committee(ensemble: Ensemble, candidates: Points, n: int = 1) Points[source]
Select candidates by mean population variance across committee outputs.
Ties preserve candidate order. Finite predictions are scored on CPU in float64 with exponent scaling to avoid variance overflow and underflow. This preserves relative output units and common-scalar ranking invariance, subject to rounding in the supplied predictions. Nearly tied nonzero scores use exact rational comparisons of those predictions, at additional CPU cost. Nonfinite predictions raise
ValueError. Returned points retain their input gradients.- Parameters:
- Returns:
Selected candidates in descending disagreement order, with stable ties.
- Return type:
optimization
Use minimize when evaluating a differentiable cost instead of observing
target responses. Supply an initial model, loss and typed method. Check
objective and stopping evidence in the optimization result. Sampling and
minibatch settings are explained in Objective minimization.
Objective minimization, separate from supervised least-squares regression.
- class torch_bsf.optimization.Iteration(iteration: int, loss: float, gradient_norm: float)[source]
Bases:
objectPre-update minibatch loss and gradient norm at a zero-based iteration.
- class torch_bsf.optimization.OptimizationResult(model: BezierModel, history: tuple[Iteration, ...], success: bool, status: str, message: str, objective: float, n_iterations: int, seed: int | None)[source]
Bases:
objectAn optimized copy, history and an honest stopping state.
Budget exhaustion and a user callback are not convergence certificates. Only all-fixed or a checked full-sample gradient tolerance yield success.
- require_success() OptimizationResult[source]
Raise if no convergence criterion was established.
- Returns:
This result when its success flag is true.
- Return type:
- class torch_bsf.optimization.PreconditionedSGD(max_iter: int = 100, step_size: float = 0.05, batch_size: int | None = None, preconditioner: Literal['gram', 'none'] = 'gram', step_decay: Literal['constant', 'sqrt'] = 'constant', gradient_tolerance: float | None = None)[source]
Bases:
objectDomain-metric SGD with a fixed sample set and uniform random minibatches.
- torch_bsf.optimization.gram_matrix(space: BezierSpace, *, cell_weights: Mapping[int | Simploid, float] | None = None) Tensor[source]
Compute the exact Bernstein Gram matrix for the sampling distribution.
- Parameters:
space (BezierSpace) – Space whose globally shared Bernstein basis defines the matrix.
cell_weights (mapping, optional) – Nonnegative cell weights, normalized to probabilities; defaults to equal maximal-cell weights.
- Returns:
CPU float64 matrix of shape
(space.n_controls, space.n_controls).- Return type:
- torch_bsf.optimization.linear_scalarization(objective: Callable[[Tensor], Tensor]) Callable[[Tensor, Points], Tensor][source]
Turn differentiable objective values into a simplex-weighted loss.
- Parameters:
objective (callable) – Differentiable map from predicted values to one objective per simplex coordinate.
- Returns:
Loss accepting predicted values and Points and returning one weighted objective per row.
- Return type:
callable
- torch_bsf.optimization.minimize(model: BezierModel, loss: Callable[[Tensor, Points], Tensor], *, method: PreconditionedSGD = PreconditionedSGD(max_iter=100, step_size=0.05, batch_size=None, preconditioner='gram', step_decay='constant', gradient_tolerance=None), points: Points | None = None, n_samples: int = 100, seed: int | None = None, cell_weights: Mapping[int | Simploid, float] | None = None, penalty: QuadraticPenalty = QuadraticPenalty(ridge=0.0, smoothness=0.0), fixed: FixedControls | None = None, callback: Callable[[Iteration, BezierModel], bool | None] | None = None) OptimizationResult[source]
Minimize a differentiable loss on a copied model with domain-aware SGD.
- Parameters:
model – Initial model; never mutated.
loss –
loss(predicted_values, points)returning one loss per sample.method – Typed preconditioned SGD settings, independent of Lightning. Float16 and bfloat16 initial models are copied and promoted to float32 on the same device for basis evaluation and gradients. Metric solves and update arithmetic use float64 before casting back to the model dtype. On MPS, float64 penalties and solves run on CPU with differentiable transfers. The returned model then has float32 coefficients. A gradient tolerance is checked on the full sample before each update and after the last one; a minibatch covering every sample reuses that checked evaluation.
points – Fixed samples. Otherwise draw
n_sampleswith the stated cell weights.n_samples – Sampling count, local random seed and explicit cell distribution.
seed – Sampling count, local random seed and explicit cell distribution.
cell_weights – Sampling count, local random seed and explicit cell distribution.
penalty – Quadratic penalties and exact fixed coefficient values.
fixed – Quadratic penalties and exact fixed coefficient values.
callback – Receives a pre-update Iteration and the current model. Returning True stops without claiming convergence.
- Returns:
Copied model, pre-update history, final full-sample objective and stopping state.
- Return type:
- Raises:
ValueError – The loss does not return one finite value per sample.
ArithmeticError – The combined objective, gradient or coefficient update is non-finite, or the step-scaled metric matrix cannot be represented in float64. Zero-weight penalties are not evaluated. Active penalties accumulate in float64 and apply weights before the final product, while gradients retain the model dtype. Truly unrepresentable results raise on every stopping path.
interpolation
SimplicialMesh compiles user-supplied simplices into a cubic
BezierSpace whose shared faces share controls, and locates points in it.
hermite_interpolant returns a continuous piecewise cubic BezierModel
through vertex data that, when clamped, stays within the data range of each
simplex. macro_interpolant chooses how simplices are split (NoSplit,
CloughTocher) and how smoothly the pieces join (C0, C1); on
triangle meshes, it builds a C1 MacroInterpolant with the same range
guarantee. lattice_mesh triangulates a simplex lattice conformingly, and
refine bisects simplices while keeping the mesh conforming. See
Shape-preserving interpolation on simplicial meshes for the construction and its error bound.
Shape-preserving cubic Bézier interpolation on simplicial complexes (“simplicial PCHIP”).
A SimplicialMesh glues user-supplied simplices of \(\mathbb{R}^d\) into one
BezierSpace, so that simplices sharing a face share the controls on it.
hermite_interpolant() fills that space with a cubic Bézier model that interpolates vertex data:
a vertex control is the datum \(f_i\);
the control on edge \(i \to j\) next to vertex \(i\) is \(f_i + g_i \cdot (x_j - x_i) / 3\), with a vertex gradient \(g_i\);
the control at the centre of face \((i, j, l)\) is the sum of the six edge controls of the face divided by four, minus \((f_i + f_j + f_l) / 6\), which reproduces quadratic data exactly.
Every control depends only on the data of the edge or face it lies on, so the interpolant is
continuous across shared faces. With clamp=True every edge (face) control is clipped into the
range of the data at the vertices of that edge (face). By the convex-hull property, the prediction
on each simplex then stays within the range of its vertex data, which bounds the sup error by the
modulus of continuity of the data at the mesh size. In one dimension the edge limiter restricts each
endpoint slope, separately on each incident interval, to the interval between zero and three times
the secant slope. The normalized slopes lie in the square \([0, 3]^2\) of the Fritsch–Carlson
monotonicity region [FC80]. The interpolant is C0, but need not be C1, even
on a chain of intervals. It agrees with PCHIP when supplied with its vertex slopes (the
Fritsch–Butland weighted harmonic means at interior vertices [FB84], with
PCHIP’s one-sided endpoint rule); the default estimate_gradients() uses different slopes.
- class torch_bsf.interpolation.C0[source]
Bases:
objectContinuous joins between neighbouring pieces (a
continuityformacro_interpolant()).
- class torch_bsf.interpolation.C1[source]
Bases:
objectContinuously differentiable joins between neighbouring pieces (a
continuityformacro_interpolant()).
- class torch_bsf.interpolation.CloughTocher[source]
Bases:
objectSplit every triangle into three at its incenter: the Clough–Tocher split (a
splitformacro_interpolant()).Two dimensions only. The incenter rather than the centroid is used because the segment joining the split points of two triangles sharing an edge then crosses that edge strictly inside it, which
C1relies on to keep every piece within the range of the data of its triangle. It is the incenter after each axis is divided by a power of two near the longest side of the mesh along it, which keeps that property (an affine map does) and makes the split, like the mesh, independent of the units of the axes. Where those powers of two are equal for both axes (e.g. onlattice_mesh()), it is the Euclidean incenter.
- class torch_bsf.interpolation.MacroInterpolant(mesh: SimplicialMesh, pieces: SimplicialMesh, parent: Tensor, model: BezierModel, *, _transfer: Callable[[Points], Points])[source]
Bases:
objectA piecewise cubic interpolant on a mesh, defined on pieces of its simplices (see
macro_interpolant()).- pieces[source]
The mesh of the pieces, on whose
spacethe model is defined. It ismeshitself without a split.
- model[source]
The float64 model. Evaluate it at
locate(), or attransfer()of points ofmesh.space.
- locate(x: Any, *, tol: float = 1e-08, batch_size: int | None = None) Points[source]
Find the piece containing each point and its barycentric coordinates there.
The points are located in
meshbySimplicialMesh.locate(), and then in the pieces of their simplex bytransfer().- Parameters:
x – Points of shape
(n_points, d).tol – Tolerance on the barycentric coordinates, as in
SimplicialMesh.locate().batch_size – Number of points located at a time, as in
SimplicialMesh.locate(), which also chooses the default.
- Returns:
Barycentric coordinates and cell IDs in
pieces.space, in the order ofx.- Return type:
- Raises:
ValueError – If
tolor a point is invalid, or a point is outside the mesh (seeSimplicialMesh.locate()).
- transfer(points: Points) Points[source]
Map points of
mesh.spaceto the pieces containing them, preserving their order.- Parameters:
points – Barycentric coordinates in simplices of
meshand their cell IDs (fromSimplicialMesh.cell_ids), e.g. fromSimplicialMesh.locate().- Returns:
Barycentric coordinates and cell IDs in
pieces.space.- Return type:
- Raises:
ValueError – If
pointsare not valid points ofmesh.space(seevalidate()), or a cell ID is not that of a simplex ofmesh(e.g. a face, or a simplex replaced byrefine()).
Examples
The interpolant of the first coordinate reproduces it, here at the centroids of the triangles:
>>> import torch >>> from torch_bsf.core import Points >>> from torch_bsf.interpolation import C1, CloughTocher, lattice_mesh, macro_interpolant >>> mesh = lattice_mesh(2, 2) >>> interpolant = macro_interpolant(mesh, mesh.vertices[:, 0], split=CloughTocher(), continuity=C1()) >>> centroids = Points(torch.full((mesh.n_simplices, 3), 1 / 3, dtype=torch.float64), mesh.cell_ids) >>> values = interpolant.model(interpolant.transfer(centroids))[:, 0] >>> torch.allclose(values, mesh.vertices[mesh.simplices, 0].mean(dim=1)) True
- class torch_bsf.interpolation.NoSplit[source]
Bases:
objectInterpolate on the simplices of the mesh as they are (a
splitformacro_interpolant()).
- class torch_bsf.interpolation.SimplicialMesh(vertices: Any, simplices: Any, *, strict: bool = True)[source]
Bases:
objectA conforming simplicial complex in \(\mathbb{R}^d\) compiled into a cubic
BezierSpace.- Parameters:
vertices – Vertex coordinates of shape
(n_vertices, d),d >= 1.simplices – Vertex numbers of shape
(n_simplices, d + 1). The simplices must be non-degenerate and meet face to face (conforming); the mesh is not generated here. A Delaunay triangulation of points in general position is conforming; one of lattice points with its flat simplices removed is not (uselattice_mesh()instead). A simplex counts as degenerate when its volume is at most1e-12times the product of its edge lengths both in the given coordinates and with each axis divided by the extent of the simplex along it (rounded down to a power of two), so that neither the units of the axes nor the scale of the coordinates matters.strict – Whether a non-conforming mesh raises
ValueError. IfFalse, a warning is issued instead andunmatched_facesreports the defect.
- simplices[source]
Each simplex with its vertex numbers sorted in ascending order. The barycentric coordinates of cell
k(as returned bylocate()) refer to the verticessimplices[k]in this order.
- space[source]
The compiled degree-3 space, in which simplices sharing a face share the controls on it.
Notes
Construction copies vertex and simplex inputs, including CPU float64 tensors and NumPy arrays. Treat the public
verticesandsimplicestensors as read-only: direct in-place edits are unsupported because geometric caches and the compiled space are not rebuilt. Construct a new mesh to change its geometry or topology. The vertex copy preserves autograd connections to tensor inputs.Examples
Two triangles sharing the diagonal of the unit square:
>>> import torch >>> from torch_bsf.interpolation import SimplicialMesh >>> mesh = SimplicialMesh([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], [[0, 1, 2], [3, 2, 1]]) >>> mesh.simplices.tolist() [[0, 1, 2], [1, 2, 3]] >>> points = mesh.locate(torch.tensor([[0.25, 0.25], [0.75, 0.75]])) >>> points.cell_ids.tolist() == mesh.cell_ids.tolist() True >>> points.parameters[0].tolist() [0.5, 0.25, 0.25]
- barycentric(x: Any) Tensor[source]
Barycentric coordinates of points with respect to every simplex.
- Parameters:
x – Points of shape
(n_points, d).- Returns:
Coordinates of shape
(n_points, n_simplices, d + 1), in the vertex order ofsimplices. They sum to one and are all nonnegative exactly when the point lies in the simplex.- Return type:
Notes
The usual path uses the equilibrated float64 inverse. If its condition estimate indicates excessive factorization error, an absolute-product estimate of roundoff exceeds
1e-12 * (1 + abs(coordinate)), or the solve overflows, the affected point/simplex pairs are evaluated with exact rational arithmetic on the input floats and rounded once to float64. This slower path caches one exact inverse per affected simplex. It recovers coordinates lost by the solve, but cannot recover information already lost when the input points were rounded. Differentiable affected pairs additionally use cached SymPy expressions. Their derivatives are evaluated exactly at every order before rounding, independently for each point. Symbolic work and retained autograd state grow with dimension and derivative order; this path is intentionally slower. Forward-mode queries with any affected pair use symbolic expressions for the whole query so thatforward_adandtorch.func.jvpretain tangents without reading dual tensor storage in Python.
- property dimension: int[source]
Dimension
dof the ambient space and of every simplex.- Returns:
Common ambient and simplex dimension.
- Return type:
- locate(x: Any, *, tol: float = 1e-08, batch_size: int | None = None) Points[source]
Find the simplex containing each point and its barycentric coordinates there.
Every simplex is tested (brute force). A point on a shared face is assigned to one of the simplices containing it; the prediction does not depend on that choice because the interpolant is continuous.
- Parameters:
x – Points of shape
(n_points, d).tol – Points whose barycentric coordinates are at least
-tolin some simplex are accepted. Their coordinates are clipped to be nonnegative and renormalized, which moves points just outside the mesh onto its boundary.batch_size – Number of points tested at a time, bounding temporary float64 storage by
O(batch_size * n_simplices * (d + 1)). By default it is chosen from the number of simplices so that each temporary array of shape(batch_size, n_simplices, d + 1)takes at most 16 MiB (about a dozen such arrays are alive at once), and at least one point is tested at a time. Exact arithmetic for cancellation-prone pairs additionally cachesO(d**2)rational coefficients per affected simplex, whose integer sizes depend on the input exponents. Those pairs are processed sequentially without an array of rational coordinates. Differentiable queries additionally retain symbolic expression caches and autograd state.
- Returns:
Barycentric coordinates and cell IDs in
space, in the order ofx.- Return type:
- Raises:
ValueError – If
tolis negative or not finite, or a point lies outside the mesh by more thantol.
- property n_simplices: int[source]
Number of simplices.
- Returns:
Number of rows in the simplex array.
- Return type:
- property n_vertices: int[source]
Number of vertices.
- Returns:
Number of rows in the vertex array.
- Return type:
- neighbors() list[list[int]][source]
The vertices sharing an edge with each vertex (its one-ring), in ascending order.
- property unmatched_faces: int[source]
Number of faces that break conformity (0 for a conforming mesh).
A face of dimension
d - 1is counted if it belongs to more than two simplices, or if it belongs to one simplex and overlaps, in a set of positive \((d - 1)\)-volume, another such face in the same hyperplane (a hanging vertex, or a flat polygon split differently on its two sides). The overlap is tested over the whole faces, however thin it is, and faces count as in the same hyperplane only if they are so up to rounding. Rounding here isdunits of rounding (machine epsilon) of the larger diameter plus the largest absolute coordinate of the two faces, which is how faces meeting at a common vertex or edge touch after rounding. In 1-D, where faces are points given as input, only equal points overlap. There is no other tolerance. Faces of the same simplex are never compared. Faces on the meshed domain are not counted, even when another part of the mesh lies close to them or touches them at a lower-dimensional face. Overlapping simplices that share no face are not detected. A mesh without boundary faces (every face in two or more simplices, which only overlapping simplices allow) has all its faces counted.- Returns:
Count of nonconforming faces according to the overlap test above.
- Return type:
- torch_bsf.interpolation.estimate_gradients(mesh: SimplicialMesh, values: Any, *, max_rings: int = 4) Tensor[source]
Estimate the gradient of vertex data at every vertex from its mesh neighbours.
At each vertex, a quadratic polynomial is fitted by least squares with weights \(1 / |H^{-1}(x_w - x_v)|^2\) to the data on the smallest \(k\)-ring neighbourhood (\(2 \le k \le\)
max_rings) that contains at least two more points than the quadratic has unknowns. If no such neighbourhood exists, a linear polynomial is fitted on the one-ring instead. The quadratic fit reproduces the gradient of quadratic data exactly, and either fit reproduces that of linear data. Vertices that belong to no simplex get a zero gradient.\(H\) is the diagonal matrix of the largest \(|x_w - x_v|\) along each axis over the neighbourhood, so that the estimate does not depend on the units of the axes: scaling an axis by \(c\) divides that component of the gradient by \(c\), even for axes of very different scales.
- Parameters:
mesh – The mesh.
values – Vertex data of shape
(n_vertices, n_outputs)or(n_vertices,).max_rings – The largest neighbourhood to try.
- Returns:
Float64 gradients of shape
(n_vertices, d, n_outputs). Components beyond the float64 range are returned as signed infinity, without a warning. Finite vertex data can produce such gradients, for example on very short edges.- Return type:
Examples
>>> import torch >>> from torch_bsf.interpolation import SimplicialMesh, estimate_gradients >>> x = torch.linspace(0, 1, 6)[:, None] >>> mesh = SimplicialMesh(x, [[k, k + 1] for k in range(5)]) >>> g = estimate_gradients(mesh, x.square()) >>> torch.allclose(g[:, 0, 0], 2 * x[:, 0].double()) True
- torch_bsf.interpolation.hermite_interpolant(mesh: SimplicialMesh, values: Any, *, gradients: Any = None, clamp: bool = True) BezierModel[source]
Interpolate vertex data by a continuous piecewise cubic Bézier model on a simplicial mesh.
The model reproduces the data at the vertices. Without clamping, it reproduces quadratic data exactly when the gradients do (for example, with enough neighbours for
estimate_gradients()). Withclamp=True, on every simplex and for every output, the prediction lies between the smallest and largest data at the vertices of that simplex; in one dimension, monotone data then give a monotone interpolant. The model is C0; it is not C1 in general, even in one dimension, because clamping can change the slope independently on either side of a vertex. For a C1 interpolant in two dimensions, usemacro_interpolant()withsplit=CloughTocher(), continuity=C1().In one dimension, each endpoint slope is limited to the interval between zero and three times the secant slope of that interval. This gives the square part of the Fritsch–Carlson monotonicity region [FC80]. The default gradient estimates are not PCHIP slopes. Supplying PCHIP slopes as
gradientsgives the PCHIP interpolant up to float64 roundoff: the interior slopes use the Fritsch–Butland weighted harmonic mean [FB84], and the endpoints use PCHIP’s one-sided rule. Those slopes already satisfy the limiter.- Parameters:
mesh – The mesh.
values – Vertex data of shape
(n_vertices, n_outputs)or(n_vertices,).gradients – Vertex gradients of shape
(n_vertices, d, n_outputs). Estimated withestimate_gradients()ifNone.clamp – Whether to clip edge and face controls into the range of their vertex data (shape preservation).
- Returns:
A float64 model on
mesh.space. Evaluate it atmesh.locate(x).- Return type:
- Raises:
ValueError – If the data or supplied gradients are invalid, or the interpolant’s control values are non-finite because the data or estimated gradients cause float64 overflow. Clamping can clip infinite controls into the data range, but cannot repair NaN controls.
Examples
>>> import torch >>> from torch_bsf.interpolation import SimplicialMesh, hermite_interpolant >>> x = torch.tensor([[0.0], [1.0], [2.0], [3.0]]) >>> mesh = SimplicialMesh(x, [[0, 1], [1, 2], [2, 3]]) >>> model = hermite_interpolant(mesh, torch.tensor([0.0, 0.0, 1.0, 1.0])) >>> y = model(mesh.locate(torch.linspace(0, 3, 31)[:, None])) >>> bool((y.diff(dim=0) >= 0).all() and y.min() >= 0 and y.max() <= 1) True
- torch_bsf.interpolation.lattice_mesh(dim: int, k: int) SimplicialMesh[source]
The Kuhn (Freudenthal) triangulation of the degree-
klattice of the standarddim-simplex.The vertices are the lattice points \(a / k\) with \(a \in \mathbb{N}^{d + 1}\) and \(|a| = k\), in the order of
grid()withresolution=k. Their coordinates are the firstdimbarycentric coordinates (the last one is one minus their sum). Thek ** dimsimplices have equal volume, have lattice points as vertices and meet face to face. They are not congruent fordim >= 3, because the map from the staircase coordinates below to these coordinates is not an isometry. In the staircase coordinates \(z_j = a_0 + \dots + a_j\) (\(j < d\)) the lattice is \(\{0 \le z_0 \le \dots \le z_{d-1} \le k\}\), and the simplices are the Kuhn simplices \((c, c + e_{\pi(1)}, c + e_{\pi(1)} + e_{\pi(2)}, \dots)\) of unit cubes whose vertices all lie in it. The mesh records these paths, so thatrefine()withrule="newest_vertex"can bisect each simplex across the diagonal of its cube.Lattice points are co-spherical, so their Delaunay triangulation is not unique; qhull returns flat simplices, and removing them leaves a mesh that is not conforming. Use this mesh instead.
- Parameters:
dim – Dimension of the simplex, at least one.
k – Lattice degree (number of intervals per edge), at least one.
- Returns:
A conforming mesh with
comb(k + dim, dim)vertices andk ** dimsimplices.- Return type:
Examples
>>> from torch_bsf.interpolation import lattice_mesh >>> mesh = lattice_mesh(2, 3) >>> mesh.n_vertices, mesh.n_simplices, mesh.unmatched_faces (10, 9, 0) >>> float(mesh.volumes().sum()) 0.5
- torch_bsf.interpolation.macro_interpolant(mesh: SimplicialMesh, values: Any, *, split: NoSplit | CloughTocher = NoSplit(), continuity: C0 | C1 = C0(), gradients: Any = None, clamp: bool = True) MacroInterpolant[source]
Interpolate vertex data by a piecewise cubic Bézier model on pieces of the simplices of a mesh.
splitsays how each simplex is divided into pieces, andcontinuityhow smoothly neighbouring pieces join. The supported constructions areNoSplit()withC0()(any dimension):hermite_interpolant();CloughTocher()withC1()(two dimensions): the Clough–Tocher macro-element, continuously differentiable, on three cubic pieces per triangle.
Both interpolate the data and reproduce quadratic data without clamping when the gradients do. With
clamp=True, on every simplex ofmeshand for every output, the prediction lies between the smallest and largest data at its vertices. For the C1 construction, the gradients are first limited for this, each projected onto the gradients keeping the controls next to its vertex in the range of every triangle containing them, which keeps the interpolant C1. In the spirit of [FC80], this is a two-dimensional range-restricted C1 interpolant. The set of such gradients can shrink abruptly as the mesh moves. For example, take a vertex whose datum is the largest datum of the triangle on one side of each of two of its edges and the smallest of the triangle on the other side. Its gradient must be orthogonal to both edges: while they are parallel, it is free along their common normal; once they are not, it must vanish. The clamped C1 interpolant therefore need not depend continuously on the vertex coordinates.The construction is not differentiated with respect to the mesh: like every
BezierModel, the model copies its coefficients without their autograd history, as forhermite_interpolant(), and the split points are computed from the vertices without it too. Derivatives with respect to the vertices therefore pass only throughSimplicialMesh.locate().- Parameters:
mesh – The mesh.
values – Vertex data of shape
(n_vertices, n_outputs)or(n_vertices,). For the C1 construction, an output with data beyond about 2**1000 (or, withoutclamp, controls beyond it) is processed in units of a power of two up to 2**24, so that its sums stay finite; its subnormal data are then interpolated only up to an absolute error below 2**-1050.split – How each simplex is divided into pieces:
NoSplitorCloughTocher.continuity – How smoothly neighbouring pieces join:
C0orC1.gradients – Vertex gradients of shape
(n_vertices, d, n_outputs). Estimated withestimate_gradients()ifNone. Withclampand the C1 construction, a gradient whose controls exceed about 2**1000 (near float64 overflow) is divided by a power of two before it is limited.clamp – Whether to keep the prediction on every simplex within the range of its vertex data.
- Returns:
The interpolant: call it at points, or evaluate its
modelat itslocate().- Return type:
- Raises:
TypeError – If
splitorcontinuityis not one of the classes above.ValueError – If the construction is not supported, the mesh is not two-dimensional for
CloughTocheror has a triangle whose split point rounds onto one of its sides, the data or supplied gradients are invalid, or the interpolant is not finite in float64 (seehermite_interpolant()).
Examples
>>> import torch >>> from torch_bsf.interpolation import C1, CloughTocher, lattice_mesh, macro_interpolant >>> mesh = lattice_mesh(2, 3) >>> x = mesh.vertices >>> interpolant = macro_interpolant(mesh, x[:, 0] * x[:, 1], split=CloughTocher(), continuity=C1()) >>> interpolant.pieces.n_simplices == 3 * mesh.n_simplices True >>> y = interpolant(x) >>> torch.allclose(y[:, 0], x[:, 0] * x[:, 1]) True
- torch_bsf.interpolation.refine(mesh: SimplicialMesh, simplices: Sequence[int], *, rule: Literal['longest_edge', 'newest_vertex'] = 'longest_edge') SimplicialMesh[source]
Bisect simplices at the midpoint of their refinement edge, keeping the mesh conforming.
An edge is bisected in every simplex that contains it, which keeps the mesh conforming. Before an edge is bisected, every simplex containing it whose refinement edge is a different one is refined first, recursively, so that cells are always cut across their own refinement edge. Refining one simplex may therefore add several vertices. The refinement edge depends on
rule:"longest_edge"(Rivara): the longest edge, measured in the vertex coordinates. Ties between edges of equal length are broken by their vertex numbers, consistently over the whole mesh."newest_vertex"(Maubach [Mau95]): every simplex is an ordered vertex tuple \((x_0, \dots, x_d)\) with a tag \(k \in \{1, \dots, d\}\), its refinement edge is \(x_0 x_k\), and the children \((x_0, \dots, x_{k-1}, z, x_{k+1}, \dots, x_d)\) and \((x_1, \dots, x_k, z, x_{k+1}, \dots, x_d)\) get the tag \(k - 1\) (\(d\) after 1). It starts from a mesh oflattice_mesh(), whose simplices are the Kuhn paths with tag \(d\), and the refined mesh carries the ordered simplices and tags for the next call. It cuts a Kuhn simplex across the diagonal of its cube, sodimrounds of refining every simplex oflattice_mesh(dim, k)give a triangulation of the degree-2klattice, and the simplices take only finitely many shapes up to similarity [Mau95]. Longest-edge bisection of a Kuhn mesh does neither: the longest edge of a Kuhn simplex is not the cube diagonal in the coordinates oflattice_mesh(), so rounds of refinement do not return to a lattice mesh and closure adds vertices that were not asked for.
- Parameters:
mesh – A conforming mesh. For
rule="newest_vertex", a mesh returned bylattice_mesh()or by this function withrule="newest_vertex".simplices – Integer indices into
mesh.simplicesof the simplices to bisect.rule –
"longest_edge"(default) or"newest_vertex".
- Returns:
The refined mesh. It keeps the vertices of
meshwith their numbers and appends the new vertices (edge midpoints) in the order they were created. Its simplices are sorted.- Return type:
- Raises:
ValueError – If
ruleis not one of the strings"longest_edge"and"newest_vertex", if an index is not one of a simplex, or ifrule="newest_vertex"is used on a mesh that carries no newest-vertex state (e.g. one built directly withSimplicialMesh).
Examples
>>> from torch_bsf.interpolation import lattice_mesh, refine >>> coarse = lattice_mesh(2, 2) >>> fine = refine(coarse, [0]) >>> fine.n_vertices - coarse.n_vertices, fine.n_simplices - coarse.n_simplices, fine.unmatched_faces (1, 2, 0)
Two rounds of newest-vertex bisection of every triangle double the lattice degree:
>>> mesh = lattice_mesh(2, 2) >>> for _ in range(2): ... mesh = refine(mesh, range(mesh.n_simplices), rule="newest_vertex") >>> mesh.n_vertices, mesh.n_simplices, mesh.unmatched_faces (15, 16, 0) >>> sorted(set((4 * mesh.vertices).flatten().tolist())) [0.0, 1.0, 2.0, 3.0, 4.0]
- torch_bsf.interpolation.refinement_indicator(mesh: SimplicialMesh, values: Any, *, gradients: Any = None, clamp: bool = True, split: NoSplit | CloughTocher = NoSplit(), continuity: C0 | C1 = C0()) Tensor[source]
Score simplices for refinement by the cubic correction to linear interpolation.
The score of a simplex is its volume times the mean over outputs of the absolute difference between the cubic interpolant (
macro_interpolant(), by defaulthermite_interpolant()) and linear interpolation at its centroid. It is large where the data bend within a large simplex. Refine the highest-scoring simplices first, for example withrefine(), and evaluate the response at the new vertices.- Parameters:
mesh – The mesh.
values – Vertex data of shape
(n_vertices, n_outputs)or(n_vertices,).gradients – Vertex gradients, as in
hermite_interpolant().clamp – Whether the cubic interpolant is clamped, as in
hermite_interpolant().split – The construction of the cubic interpolant, as in
macro_interpolant().continuity – The construction of the cubic interpolant, as in
macro_interpolant().
- Returns:
Nonnegative float64 scores of shape
(n_simplices,), one per simplex ofmesh. Finite centroid predictions and extreme vertex data use exact reductions near the float64 limits, applying the volume before final rounding. A score beyond float64 is positive infinity.- Return type:
- Raises:
TypeError – If
splitorcontinuityis not one of the classes ofmacro_interpolant().ValueError – If
macro_interpolant()cannot construct a finite interpolant, including when the vertex data or gradients cause its controls to overflow float64, or if a centroid prediction is non-finite.
Examples
>>> import torch >>> from torch_bsf.interpolation import SimplicialMesh, refinement_indicator >>> x = torch.arange(6.0)[:, None] >>> mesh = SimplicialMesh(x, [[k, k + 1] for k in range(5)]) >>> [round(s, 6) for s in refinement_indicator(mesh, x.square()).tolist()] [0.25, 0.25, 0.25, 0.25, 0.25]
splitting
Subdivision reparameterizes a model on smaller cells while preserving its represented function. Keep the child-to-parent coordinate maps when comparing values. Refitting child models is a separate operation that can change the function; see Exact local subdivision.
Exact local subdivision with explicit maps back to the parent domain.
- class torch_bsf.splitting.EdgeSplit(i: int, j: int, fraction: float = 0.5, factor: int = 0)[source]
Bases:
objectSplit one factor edge at a fraction strictly between zero and one.
- class torch_bsf.splitting.LongestEdge(fraction: float = 0.5)[source]
Bases:
objectSelect the longest chord of the selected cell’s control-net vertices.
Scaled float64 length comparisons support finite extreme endpoints and subnormal distances. Uniform output scaling preserves selection except for rounding near ties or lost input precision. Non-finite corner values raise
ValueError. Equal lengths use factor/vertex lexicographic order.
- class torch_bsf.splitting.ParameterMap(parent_cell: int, dimensions: tuple[int, ...], split: EdgeSplit, part: Literal['A', 'B'])[source]
Bases:
objectAn affine child-to-parent map and its partial inverse.
- from_parent(points: Points) tuple[Points, Tensor][source]
Return child coordinates and a mask selecting parent rows in this piece.
- to_parent(points: Points | Tensor) Points[source]
Map child barycentric points to their parent cell.
- Parameters:
points (Points or torch.Tensor) – Child barycentric coordinates, with cell ID zero when Points is supplied.
- Returns:
Parent barycentric coordinates with the original parent cell ID.
- Return type:
- class torch_bsf.splitting.ResidualSplit(samples: Samples, resolution: int = 10)[source]
Bases:
objectGroup the current residuals into two homogeneous pieces over a split-fraction grid.
Exact subdivision preserves the represented function, so each observation’s squared residual is computed once with the parent and only assigned to the pieces. Over every factor edge and
resolutioninterior fractions, the split minimizing the within-piece sum of squared deviations of these residuals from each piece’s mean is selected. Candidates leaving a piece without observations of its own are rejected; if all are, the midpoint of the longest edge is used. This does not refit the children or guarantee error reduction.
- class torch_bsf.splitting.SpaceSubdivision(model: BezierModel, parents: tuple[int, ...], split: EdgeSplit, _refinement: Refinement, _original: BezierSpace, _nodes: tuple[Simploid, ...])[source]
Bases:
objectAn exact conforming subdivision of a whole space and the map of points onto it.
- parents[source]
For every cell of the refined space, the cell of the original space whose relative interior contains it (
0for a single-cell space, whose faces are not cells).
- class torch_bsf.splitting.SubdivisionResult(models: tuple[BezierModel, BezierModel], maps: tuple[ParameterMap, ParameterMap])[source]
Bases:
objectTwo exact local models and their parent parameter maps.
This does not remesh neighboring cells of an assembled domain.
- torch_bsf.splitting.subdivide(model: BezierModel, *, cell: int = 0, policy: EdgeSplit | LongestEdge | ResidualSplit = LongestEdge(fraction=0.5)) SubdivisionResult[source]
Split a selected cell exactly, retaining its output dtype and device.
- Parameters:
model (BezierModel) – Model whose selected cell is subdivided.
cell (int, optional) – Stable integer ID of the cell to split.
policy (EdgeSplit or LongestEdge or ResidualSplit, optional) – Rule selecting the factor edge and split fraction.
- Returns:
Two exact child models and maps relating child coordinates to their parent.
- Return type:
- torch_bsf.splitting.subdivide_space(model: BezierModel, *, cell: int = 0, policy: EdgeSplit | LongestEdge | ResidualSplit = LongestEdge(fraction=0.5)) SpaceSubdivision[source]
Split a cell exactly and propagate the split so that the whole space stays conforming.
The selected factor edge is split at
fraction, and so is every factor edge of the space parallel to it through shared faces: the edges of the same factor in neighboring cells across shared faces and, through a degenerate face, the edge of its base it maps onto. Each affected cell is bisected along all of its marked edges, so a shared face is subdivided in the same way from every side and the result is again a simploidal Bézier space: closed under splitting, including spaces with degenerate cells, for any fraction in \((0, 1)\). The coefficients are computed by de Casteljau subdivision, so the refined model represents the same function.- Parameters:
model – The model to subdivide.
cell – The cell whose edge selects the split.
policy – How to choose the edge of
cell, as insubdivide().
- Returns:
The refined model, parent cell IDs and a coordinate map onto the refined space.
- Return type:
Examples
Split one triangle of a square made of two triangles; the edge shared by both triangles is split, so the neighbor is split too:
>>> import torch >>> from torch_bsf import BezierModel, BezierSpace, Points >>> from torch_bsf.geometry import SimploidalSet >>> from torch_bsf.splitting import EdgeSplit, subdivide_space >>> S = SimploidalSet() >>> a, b = S.add_simplex(2), S.add_simplex(2) >>> _ = S.identify(a.face(0, 0), b.face(0, 0)) >>> space = BezierSpace.on_set(S, degree=2) >>> model = BezierModel(space, torch.randn(space.n_controls, 1, dtype=torch.float64)) >>> result = subdivide_space(model, cell=space.cell_id(a), policy=EdgeSplit(1, 2, 0.3)) >>> len(result.model.space.main_cells) 4 >>> points = Points(torch.tensor([[0.2, 0.3, 0.5]], dtype=torch.float64), [space.cell_id(b)]) >>> torch.allclose(model(points), result.model(result.transfer(points))) True
io
save_model and load_model preserve compiled geometry and coefficients
in a versioned archive independent of filename extension. Control-point
export is a data export rather than a replacement for topology-aware
persistence. Store application parameter transforms alongside the archive.
Versioned model persistence for both single-cell and glued-cell spaces.
- torch_bsf.io.export_control_points(path: str | Path, model: BezierModel) None[source]
Export coefficient rows as CSV data, explicitly without pretending to save geometry.
- Parameters:
path (str or pathlib.Path) – Destination CSV file, replaced if it already exists.
model (BezierModel) – Model whose global coefficient rows are written with output-column headers.
- torch_bsf.io.load_model(path: str | Path) BezierModel[source]
Restore a validated model without importing a training framework.
Original mutable builder handles are not persisted. Loaded cells are addressed by the same stable integer IDs recorded in the model schema.
- Parameters:
path (str or pathlib.Path) – Model archive written by
save_model().- Returns:
Validated model with restored coefficients, dtype and stable integer cell IDs.
- Return type:
- torch_bsf.io.save_model(path: str | Path, model: BezierModel) None[source]
Save an explicit schema and non-pickled coefficient array atomically.
The format is a ZIP containing
model.jsonandcoefficients.npy, independent of the filename extension and Python class import paths. The archive is written to a temporary sibling file, flushed to disk withos.fsync()and then renamed overpath. A new file gets the usual permissions0o666 & ~umask; replacing an existing file keeps its mode.- Parameters:
path – Destination file; its parent directory must exist.
model – Any unified Bézier model, including a glued multi-cell space.
plotting
plot_model displays selected output coordinates and optional control
nets. Rendering density changes the evaluated mesh, not the fitted
coefficients. Higher-dimensional cells are shown through two-dimensional
faces; inspect interior residuals separately.
A single plotting entry point for single- and multiple-cell models.
- torch_bsf.plotting.plot_model(model: BezierModel, *, resolution: int = 20, coordinates: Sequence[int] | None = None, ax: Any = None, show_controls: bool = True, **kwargs: Any) Any[source]
Draw maximal cells and their control nets on shared Matplotlib axes.
Select two or three output coordinates for higher-dimensional models. Domains above dimension two are shown through their two-dimensional faces. Returns the axes. Matplotlib is imported only when plotting.
- Parameters:
model (BezierModel) – Model whose maximal cells and control nets are drawn.
resolution (int, optional) – Number of grid subdivisions along each plotted simplex edge.
coordinates (sequence of int, optional) – Two or three distinct output coordinates; defaults to every output.
ax (matplotlib.axes.Axes, optional) – Existing axes, including 3-D axes for three coordinates; otherwise create axes.
show_controls (bool, optional) – Whether to draw control points and their net.
**kwargs – Plot styling forwarded to the cell drawing helpers, such as
color.
- Returns:
Axes containing every drawn cell and optional control net.
- Return type:
matplotlib.axes.Axes
sklearn
BezierRegressor adapts the common fit to NumPy arrays and estimator
workflows. Features must encode valid barycentric blocks. Generic feature
standardization generally violates those constraints. See
Scikit-learn integration for degree searches and output shapes.
Scikit-learn integration using the same numerical problem and results.
- class torch_bsf.sklearn.BezierRegressor(degree: int | Sequence[int] = 3, *, dimensions: Sequence[int] | None = None, solver: DenseLeastSquares | SparseLeastSquares = DenseLeastSquares(rcond=None, diagnostics='basic'), penalty: QuadraticPenalty = QuadraticPenalty(ridge=0.0, smoothness=0.0), fixed: FixedControls | None = None)[source]
Bases:
RegressorMixin,BaseEstimatorFit a single simplex or product using explicit numerical settings.
- Parameters:
degree – Polynomial degree, or one degree per simplex factor.
dimensions – Factor dimensions; inferred as a single simplex from X when omitted.
solver – The same typed settings accepted by
torch_bsf.fit().penalty – The same typed settings accepted by
torch_bsf.fit().fixed – The same typed settings accepted by
torch_bsf.fit().
- result_, model_, diagnostics_
Complete fit evidence, fitted model, and numerical diagnostics.
- fit(X: Any, y: Any, sample_weight: Any = None) BezierRegressor[source]
Fit weighted observations and expose their complete FitResult.
- Parameters:
X (array-like) – Barycentric feature matrix of shape
(n_samples, n_features); constant spaces use one dummy column.y (array-like) – Observed outputs of shape
(n_samples,)or(n_samples, n_outputs).sample_weight (array-like, optional) – Nonnegative observation weights with a positive sum.
- Returns:
This estimator with its fitted model, diagnostics and complete solve result.
- Return type:
- predict(X: Any) NDArray[Any][source]
Predict with the fitted model; preserve the target’s original rank.
- Parameters:
X (array-like) – Feature matrix with the same coordinate layout and width used in training.
- Returns:
Predictions of shape
(n_samples,)for a one-dimensional training target, otherwise(n_samples, n_outputs).- Return type:
numpy.ndarray
integrations.lightning
The optional Lightning adapter’s regression_module trains the common
model on supervised samples through a Lightning Trainer. See
Solvers, performance and optional execution for a complete single-device Trainer example
and its training settings. Use the core fit for a direct linear least-squares solve.
Objective-only optimization uses torch_bsf.minimize() with plain
PyTorch and does not require Lightning; see Objective minimization.
Opt-in Lightning execution for the common weighted regression objective.
- torch_bsf.integrations.lightning.regression_module(model: BezierModel, samples: Samples, *, learning_rate: float = 0.01, penalty: QuadraticPenalty = QuadraticPenalty(ridge=0.0, smoothness=0.0), fixed: FixedControls | None = None) Any[source]
Create a LightningModule from an explicit initial model and observations.
The caller owns Trainer settings and a one-item DataLoader (e.g.
[0]). Each step uses the complete normalized weighted objective, so batch and device partitioning cannot silently change regularization. This adapter supports one device; use core least squares for deterministic regression. Itsbezier_model()method returns an independent CPU float64 model.- Parameters:
model (BezierModel) – Initial coefficients and compiled geometry; the original model is not modified.
samples (Samples) – Complete weighted regression observations used on every training step.
learning_rate (float, optional) – Finite positive Adam learning rate.
penalty (QuadraticPenalty, optional) – Ridge and control-net smoothness penalties.
fixed (FixedControls, optional) – Exact coefficient constraints excluded from optimization.
- Returns:
Module owning the free coefficients and exposing
bezier_model().- Return type:
lightning.pytorch.LightningModule
integrations.mlflow
The optional MLflow integration records experiments without making the numerical core depend on a tracking service. Retain the parameter schema, units and reference-problem definition with recorded metrics so that artifacts can be interpreted outside their original run.
Explicit MLflow recording, independent of core numerical execution.
- torch_bsf.integrations.mlflow.log_result(result: FitResult, *, tracking_uri: str | None = None) str[source]
Record metrics, diagnostics and the versioned model artifact in a run.
Reuse an active run; otherwise start and finish one. Requires the mlflow extra. Returns the run ID. No autologging or pickled Python models are used. Unavailable placeholder models are never written; failure metrics and diagnostics are still recorded with
bsf.solution_available=False.