basis#

Bernstein polynomial basis and analytical derivatives for transformation models.

class mltpy.basis.BernsteinBasis(order, support)[source]#

Bases: object

Bernstein polynomial basis of degree order on a compact support.

Coefficient ordering: ascending from degree 0 to order — identical to R’s basefun::Bernstein_basis. This differs from numpy.poly1d, which stores coefficients in descending degree order.

Parameters:
  • order (int) – Polynomial degree k. The basis has k+1 functions.

  • support (tuple[float, float]) – Closed interval (a, b) with a < b. Maps y → t = (y − a) / (b − a).

derivative(y, order=1)[source]#

Analytical derivative of the Bernstein design matrix.

Uses the recurrence relation — no finite differences.

First derivative (order=1):

dB_{i,k}/dy = k/(b−a) · [B_{i−1,k−1}(t) − B_{i,k−1}(t)]

Second derivative (order=2):
d²B_{i,k}/dy² = k(k−1)/(b−a)² ·

[B_{i−2,k−2}(t) − 2·B_{i−1,k−2}(t) + B_{i,k−2}(t)]

Boundary terms (B_{j,·} with j < 0 or j > k) are treated as zero.

Parameters:
  • y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in the closed interval [support[0], support[1]].

  • order (int) – Derivative order: 1 (default) or 2. Order 0 is intentionally not supported; use evaluate(y) instead.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1 or 2 (order 0 is not supported; call evaluate(y) directly), or if any observation lies outside support.

evaluate(y)[source]#

Bernstein design matrix at observations y.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in the closed interval [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Returns:

  • NDArray of shape (n, order+1). Row i is [B_{0,k}(y_i), …, B_{k,k}(y_i)].

  • Read-only (do not mutate in place).

Raises:

ValueError – If any observation lies outside support.

Notes

Shares the assembled (B, dB) cache with evaluate_with_derivative() (see _bernstein_assembled_cache): the returned B is memoised on y content, and this call warms the paired dB so a later derivative evaluation is a single dict lookup.

evaluate_with_derivative(y)[source]#

Bernstein design matrix and its first derivative in one pass.

Normalises and validates y once, then returns both the evaluation matrix (degree k) and the first-derivative matrix (degree k−1 recurrence). Equivalent to calling evaluate(y) followed by derivative(y, order=1) but avoids the redundant support scan.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • B (NDArray of shape (n, order+1)) – Same as evaluate(y). Read-only (do not mutate in place).

  • dB (NDArray of shape (n, order+1)) – Same as derivative(y, order=1). Read-only.

Notes

The assembled (B, dB) pair is memoised on (order, support, y.tobytes()) (see _bernstein_assembled_cache); a repeated call with the same y content is a single dict lookup that skips the support scan and the dB rebuild entirely.

integrate(y)[source]#

Running integral of each basis function from a to y.

Uses the regularised incomplete beta function:

∫_a^y B_{i,k}(s) ds = (b−a)/(k+1) · I_t(i+1, k−i+1)

where t = (y−a)/(b−a) and I is scipy.special.betainc.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in the closed interval [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any observation lies outside support.

order: int#
support: tuple[float, float]#
class mltpy.basis.InteractionBasis(y_basis, x_basis)[source]#

Bases: object

Tensor-product basis a(y) ⊗ b(x) for fully-interacting CTMs.

Models the transformation

h(y|x) = (a(y) ⊗ b(x))ᵀ vec(Θ)

where a is the y-basis (response) and b is the x-basis (covariate), and Θ is a (p, q) coefficient matrix with p = y_basis.order + 1 and q = x_basis.order + 1.

The parameter vector theta_ stores vec_C(Θ) (row-major / C-order flattening) of length p * q. See ADR 0001 for the full design rationale.

Supported x-basis types (initial release): BernsteinBasis, OrdinalBasis, InterceptBasis. Other x-basis types raise ValueError at constraint-building time because the closed-form column-wise monotonicity guarantee requires the x-basis to be non-negative and a partition of unity.

Parameters:

Notes

The evaluate and derivative signatures differ from the scalar basis interface: they accept both y and X because the Kronecker product requires both. The model layer owns this two-argument call convention.

References

See docs/adr/0001-tensor-product-interaction-basis.md.

derivative(y, X, order=1)[source]#

Row-wise Kronecker product da(y_i)/dy ⊗ b(x_i), shape (n, p*q).

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1.

evaluate(y, X)[source]#

Row-wise Kronecker product a(y_i) ⊗ b(x_i), shape (n, p*q).

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

Returns:

  • NDArray of shape (n, p*q) where row i is np.kron(a(y_i), b(x_i)).

  • The parameter layout is row-major (theta[i*q + j] = Θ[i, j].)

evaluate_with_derivative(y, X)[source]#

Return evaluate(y, X) and derivative(y, X) in one pass.

Parameters:
Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • design (NDArray of shape (n, p*q))

  • d_design (NDArray of shape (n, p*q))

integrate(y, X)[source]#

Running integral of a(y) ⊗ b(x) w.r.t. y, shape (n, p*q).

Returns kron(∫_a^y a(s) ds, b(x_i)) for each row i.

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

property n_params: int#

n_y_params * n_x_params.

Type:

Total number of free parameters

property n_x_params: int#

x_basis.order + 1.

Type:

Number of x-basis functions

property n_y_params: int#

y_basis.order + 1.

Type:

Number of y-basis functions

property order: int#

y_basis.order — used by model layer for param-count bookkeeping.

property support: tuple[float, float]#

Support of the y-basis.

x_basis: BernsteinBasis | OrdinalBasis | InterceptBasis#
y_basis: BernsteinBasis | LogBernsteinBasis | PolynomialBasis | LegendreBasis | LogBasis | InterceptBasis | OrdinalBasis#
class mltpy.basis.InterceptBasis(support)[source]#

Bases: object

Constant (intercept-only) basis: evaluate(y) = ones, shape (n, 1).

The single basis function is identically 1. This gives a single free parameter — an additive intercept — in the transformation h(y) = θ₀.

Parameters:

support (tuple[float, float]) – Closed interval (a, b) with a < b. Used for support validation only.

derivative(y, order=1)[source]#

Derivative of the constant basis: always zero, shape (n, 1).

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1 or 2.

evaluate(y)[source]#

Return a column of ones, shape (n, 1).

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any observation lies outside support.

evaluate_with_derivative(y)[source]#

Return ones and zeros, both shape (n, 1).

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,).

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • B (NDArray of shape (n, 1) — ones)

  • dB (NDArray of shape (n, 1) — zeros)

integrate(y)[source]#

Running integral ∫_a^y 1 ds = y − a, shape (n, 1).

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

property order: int#

One basis function → order = 0.

support: tuple[float, float]#
class mltpy.basis.LegendreBasis(order, support)[source]#

Bases: object

Legendre polynomial basis P_0, P_1, …, P_k on a compact support.

Maps y to t = 2 · (y a) / (b a) 1 [−1, 1] and evaluates Legendre polynomials via the 3-term recurrence. The basis is orthogonal with respect to the uniform measure on [a, b]:

∫_a^b P_m(t(y)) P_n(t(y)) dy = (b−a) / (2n+1) · δ_{mn}

Parameters:
  • order (int) – Maximum degree k. The basis has k+1 functions.

  • support (tuple[float, float]) – Closed interval (a, b) with a < b.

derivative(y, order=1)[source]#

Analytical first derivative of the Legendre design matrix.

Uses the chain rule: d/dy P_n(t(y)) = P_n’(t) · dt/dy where dt/dy = 2/(b−a).

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1.

evaluate(y)[source]#

Legendre design matrix at observations y.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any observation lies outside support.

evaluate_with_derivative(y)[source]#

Return Legendre design matrix and first derivative in one pass.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,).

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • B (NDArray of shape (n, order+1))

  • dB (NDArray of shape (n, order+1))

integrate(y)[source]#

Running integral ∫_a^y P_n(t(s)) ds for each Legendre polynomial.

Uses the closed-form antiderivative:

∫_a^y P_0(t) ds = y − a ∫_a^y P_n(t) ds = (b−a)/2 · [P_{n+1}(t) − P_{n-1}(t)] / (2n+1)

where the constant of integration is fixed by F_n(−1) = 0 for n ≥ 1.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

order: int#
support: tuple[float, float]#
class mltpy.basis.LogBasis(support)[source]#

Bases: object

Single-function log basis: evaluate(y) = log(y), shape (n, 1).

Returns a one-column design matrix whose sole basis function is log(y). Useful for log-linear transformations such as the Weibull model.

The support lower bound must be strictly positive.

Parameters:

support (tuple[float, float]) – Closed interval (a, b) with 0 < a < b.

derivative(y, order=1)[source]#

Analytical derivative: d/dy log(y) = 1/y, shape (n, 1).

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1.

evaluate(y)[source]#

Return log(y) as a column, shape (n, 1).

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any observation lies outside support.

evaluate_with_derivative(y)[source]#

Return log(y) and 1/y in one pass, both shape (n, 1).

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,).

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • B (NDArray of shape (n, 1) — log(y))

  • dB (NDArray of shape (n, 1) — 1/y)

integrate(y)[source]#

Running integral ∫_a^y log(s) ds, shape (n, 1).

Closed form: ∫_a^y log(s) ds = y·log(y) − y − a·log(a) + a.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

property order: int#

One basis function → order = 0.

support: tuple[float, float]#
class mltpy.basis.LogBernsteinBasis(order, support)[source]#

Bases: object

Bernstein polynomial basis evaluated at log(y) for log-scale survival models.

Models the transformation h(y) = B_k(log(y)) · θ, where B_k is a standard Bernstein basis on (log a, log b). This parameterises Survreg models: Weibull (min_extreme_value), log-normal (normal), and log-logistic (logistic).

The derivative on the original scale follows the chain rule:

dh/dy = (1/y) · dB_k(log y)/d(log y) · θ

Parameters:
  • order (int) – Polynomial degree k. The basis has k+1 functions.

  • support (tuple[float, float]) – Closed interval (a, b) with 0 < a < b on the original positive scale. Internally maps y → t = (log y − log a) / (log b − log a).

derivative(y, order=1)[source]#

Analytical derivative d/dy [B_k(log y)] = (1/y) · dB_k/d(log y).

Parameters:
  • y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in support.

  • order (int) – Derivative order: 1 (default). Order 2 is not supported.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1, or any observation lies outside support.

evaluate(y)[source]#

Evaluate B_k(log y) at each observation.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in (support[0], support[1]).

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any observation lies outside support.

evaluate_with_derivative(y)[source]#

Return B_k(log y) and d/dy B_k(log y) in one pass.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in support.

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • B (NDArray of shape (n, order+1))

  • dB (NDArray of shape (n, order+1) — derivative w.r.t. y (includes 1/y))

integrate(y)[source]#

Not implemented for LogBernsteinBasis.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]])

Return type:

ndarray[tuple[Any, ...], dtype[double]]

order: int#
support: tuple[float, float]#
class mltpy.basis.OneHotBasis(K)[source]#

Bases: object

One-hot encoding basis for K-level categorical covariates.

Each row of the design matrix is a standard basis vector e_k of length K (1 in position k, 0 elsewhere), where k is the integer category label for that observation.

The basis is non-negative and a partition of unity (each row sums to 1), making it compatible with the closed-form column-wise monotonicity constraints in InteractionBasis. See ADR 0001, Decision 3.

Parameters:

K (int) – Number of categories. Labels must be integers in {0, …, K-1}. Must satisfy K >= 2.

K: int#
evaluate(x)[source]#

One-hot design matrix for integer category labels.

Parameters:

x (ndarray[tuple[Any, ...], dtype[double]]) – Integer category labels, shape (n,). Each value must be an integer in {0, …, K-1}.

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any element of x is not an integer in {0, …, K-1}.

property order: int#

K - 1 so that order + 1 == K (K basis functions).

class mltpy.basis.OrdinalBasis(K)[source]#

Bases: object

Degenerate “one-hot cutpoint” basis used by ordinal regression (Polr).

For K ordered levels the transformation has K-1 cutpoints θ = (θ_1, ..., θ_{K-1}). Given an integer cut position k {1, ..., K-1} (representing the boundary between level k and level k+1), the basis returns the one-hot row e_k of length K-1, so B(k) @ θ = θ_k exactly — the basis selects the cutpoint. Combined with MonotonicityConstraint of n_params = K-1 this yields θ_1 ... θ_{K-1}.

The class duck-types BernsteinBasis (order, support, evaluate, derivative, integrate) so that it drops into the existing likelihood / optimisation code paths unchanged.

Parameters:

K (int) – Number of ordered levels. Must satisfy K >= 2.

Notes

The transformation h(y) = B(y) @ θ is a step function across cut positions, so its analytical derivative w.r.t. y is zero almost everywhere. derivative() returns zero accordingly; the exact- likelihood paths in mltpy.likelihood would log(0) but are never invoked for ordinal data — every observation is interval-censored (or one-sided open) and routes through the censored likelihoods.

K: int#
derivative(y, order=1)[source]#

Derivative w.r.t. y — zero, because h is a step function.

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

evaluate(y)[source]#

Return one-hot rows for integer cut positions in {1, ..., K-1}.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Each value must be an integer in {1, ..., K-1} (the synthetic cut positions emitted by mltpy.variables.OrderedVariable.from_labels()).

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any element of y is not an integer in {1, ..., K-1}.

evaluate_with_derivative(y)[source]#

Return evaluate(y) and derivative(y, order=1) in one pass.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]])

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

integrate(y)[source]#

Not defined for the ordinal basis — raises NotImplementedError.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]])

Return type:

ndarray[tuple[Any, ...], dtype[double]]

property order: int#

K - 2 so that order + 1 == K - 1.

Type:

Polynomial-degree analogue

property support: tuple[float, float]#

Wide enough to bracket integer cut positions 1..K-1.

Rows that resolve to ±∞ bypass the support check in mltpy.model.ConditionalTransformationModel._validate_input().

class mltpy.basis.PolynomialBasis(order, support)[source]#

Bases: object

Power/monomial basis [1, t, t², …, tᵏ] on a compact support.

Normalises y to t = (y a) / (b a) [0, 1] and returns the Vandermonde matrix [1, t, t², …, tᵏ]. Unlike Bernstein polynomials these basis functions are not non-negative and the coefficient vector has no built-in monotonicity; constraints must be imposed externally.

Parameters:
  • order (int) – Polynomial degree k. The basis has k+1 functions.

  • support (tuple[float, float]) – Closed interval (a, b) with a < b.

derivative(y, order=1)[source]#

Analytical derivative of the monomial design matrix.

Parameters:
Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If order is not 1 or 2.

evaluate(y)[source]#

Vandermonde design matrix at observations y.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

Raises:

ValueError – If any observation lies outside support.

evaluate_with_derivative(y)[source]#

Return design matrix and first derivative in one pass.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,).

Return type:

tuple[ndarray[tuple[Any, ...], dtype[double]], ndarray[tuple[Any, ...], dtype[double]]]

Returns:

  • B (NDArray of shape (n, order+1))

  • dB (NDArray of shape (n, order+1))

integrate(y)[source]#

Running integral ∫_a^y tⁱ · (b−a) dt for each basis function.

Uses the closed-form antiderivative: ∫_0^t sⁱ ds = tⁱ⁺¹/(i+1), scaled by (b−a) to convert dt → dy.

Parameters:

y (ndarray[tuple[Any, ...], dtype[double]]) – Observations, shape (n,). Must lie in [support[0], support[1]].

Return type:

ndarray[tuple[Any, ...], dtype[double]]

order: int#
support: tuple[float, float]#