Scikit-learn integration

When to use the estimator

Use BezierRegressor when a Bézier surrogate belongs in a scikit-learn workflow: cloning estimators, cross-validation, a parameter grid, or a larger pipeline. It delegates fitting to the same numerical core as fit and exposes the complete numerical result. It does not introduce a different meaning of degree, penalty or sample weighting.

Install pytorch-bsf[sklearn]. The estimator handles one simplex or product domain with NumPy-compatible arrays. Use the Python Points and Samples API for assembled mixed-cell observations, because a feature matrix alone does not carry the required per-row cell IDs.

Prepare barycentric features

A segment input uses two columns [1-x, x], where 0 <= x <= 1. These are dependent barycentric coordinates, not two arbitrary tabular features. Do not pass a single x column expecting an ordinary polynomial regressor. By default one input column denotes a constant domain and its numeric values are ignored; the column is only a scikit-learn-compatible dummy feature.

import numpy as np
from sklearn.model_selection import GridSearchCV, KFold
from torch_bsf import QuadraticPenalty
from torch_bsf.sklearn import BezierRegressor

x = np.linspace(0, 1, 30)
X = np.column_stack([1 - x, x])
y = np.sin(np.pi * x) + .2 * x
estimator = BezierRegressor(degree=3)
estimator.fit(X, y)
prediction = estimator.predict(X)
assert prediction.shape == y.shape
assert estimator.result_.success
assert estimator.diagnostics_.compute_dtype == "float64"

dimensions=None infers one simplex from the column count. A triangle uses three columns. A square represented as a product uses four columns, [1-u, u, 1-v, v], and requires dimensions=(1, 1). A prism requires dimensions=(2, 1) and five columns. Each factor block must independently sum to one; the entire product row generally does not.

Choose degree and penalty through validation

search = GridSearchCV(
    BezierRegressor(),
    {"degree": [1, 2, 3, 5],
     "penalty": [QuadraticPenalty(), QuadraticPenalty(ridge=1e-4)]},
    scoring="neg_mean_squared_error",
    cv=KFold(3, shuffle=True, random_state=7),
)
search.fit(X, y)
best = search.best_estimator_
assert best.result_.success
assert best.predict(X).shape == (30,)

Scikit-learn maximizes its score, so negative MSE closer to zero is better. GridSearchCV refits its chosen estimator by default, unlike the core select_degree operation, which requires an explicit full-data refit. Inspect cv_results_ rather than reporting only the winning degree.

Increasing degree adds controls and can overfit noise. Increasing ridge shrinks controls toward the output origin; increasing smoothness draws adjacent controls together. Pass complete typed QuadraticPenalty objects in the grid, as above; the penalty is an immutable numerical configuration, not a nested scikit-learn estimator. See Regularization and numerical evidence for units and Cross-validation and degree search for an actual before/after degree comparison.

Candidate curve fits and their cross-validation errors.

Fig. 8 The numerical core’s degree comparison illustrates the same capacity trade-off used by the estimator. The figure is generated by the core three-fold search; it is not the output of the separate GridSearchCV example above, whose scoring and refit policy should be recorded separately.

Weights, outputs and evidence

estimator.fit(X, y, sample_weight=w) supplies row weights. Larger relative weights emphasize particular observations. Uniformly scaling all weights leaves the normalized regression problem unchanged. A one-dimensional y returns a one-dimensional prediction; multioutput y returns a two-dimensional array with the same output count. Predictions are NumPy arrays from the CPU float64 model, even if the original input arrays were float32.

result_ holds metrics, diagnostics, model and termination status; model_ is the shared BezierModel; diagnostics_ exposes solver evidence directly. The estimator requires numerical success during fitting, so a failed solve raises through require_success. A successful fit can still be unidentifiable or statistically poor; inspect data rank and held-out error. A high default R-squared score is not an application-specific error guarantee, especially for low-variance outputs.

Avoid using a generic feature standardizer on barycentric columns inside a pipeline: it generally breaks their domain constraints. If ordinary scalar features need conversion into barycentric blocks, perform that explicit conversion first. Output transforms must be inverted when reporting original units and fitted independently within each validation fold. See Output scaling for that distinction.

For product degrees use a tuple matching dimensions. Fixed controls and typed solvers use the same constructor arguments as the core API. If a search changes degree, fixed global row numbers cannot be reused blindly: resolve constraints for each space in a manual core validation loop. Save best.model_ with io.save_model when only the common numerical model is needed; store preprocessing and feature schemas alongside it in the application.

Fit independent input blocks as a product

u, v = np.meshgrid(np.linspace(0, 1, 8), np.linspace(0, 1, 7), indexing="ij")
u, v = u.ravel(), v.ravel()
product_X = np.column_stack([1 - u, u, 1 - v, v])
product_y = np.square(u) + v
product = BezierRegressor(dimensions=(1, 1), degree=(2, 1))
product.fit(product_X, product_y)
test_u = np.array([.13, .47, .81])
test_v = np.array([.22, .56, .73])
test_X = np.column_stack([1 - test_u, test_u, 1 - test_v, test_v])
assert np.allclose(product.predict(test_X), np.square(test_u) + test_v)

The domain is the square pictured in the simploid overview. Its two factors have different degrees because the target is quadratic in u and linear in v. Without explicit dimensions, four columns would imply one three-dimensional simplex, whose sum-to-one constraint these rows violate.

Random row folds assess interpolation among the sampled combinations. If the intended task is prediction at entirely unseen operating conditions, hold out complete v slices or use an appropriate grouped split instead. Choose a validation design that matches the use of the fitted model rather than relying on a default splitter to define that use implicitly.