constraints#

Linear constraints for monotone Bernstein coefficient optimisation.

class mltpy.constraints.BoundaryConstraint(n_params, lower, upper, total_params=None)[source]#

Bases: object

Fix one or both boundary coefficients of the Bernstein expansion.

Enforces equality constraints:

theta[0]          == lower   (if lower is not None)
theta[n_params-1] == upper   (if upper is not None)
Parameters:
  • n_params (int) – Number of Bernstein coefficients.

  • lower (float | None) – Value to fix theta[0] to, or None to leave it free.

  • upper (float | None) – Value to fix theta[n_params-1] to, or None to leave it free.

  • total_params (int | None) – Total length of the parameter vector passed to the optimiser, including any regression coefficients. When total_params > n_params the constraint rows are padded with zero columns for the beta entries so that _A has shape (n_active, total_params). Defaults to n_params (no beta).

as_LinearConstraint()[source]#

Return a LinearConstraint for use with trust-constr.

Both lb and ub are set to rhs (equality).

Return type:

LinearConstraint

as_scipy_constraint()[source]#

Return SLSQP-compatible equality constraint dicts.

Returns one dict per active boundary (one or two elements).

Return type:

list[dict[str, Any]]

lower: float | None#
n_params: int#
total_params: int | None = None#
upper: float | None#
class mltpy.constraints.ConstraintMatrices(A_ineq, b_ineq, C_eq, d_eq)[source]#

Bases: object

Constraint matrices in the canonical form used by auglag_minimize().

Represents the linear constraints as:

A_ineq @ θ ≥ b_ineq (inequality) C_eq @ θ = d_eq (equality)

Parameters:
A_ineq: ndarray[tuple[Any, ...], dtype[float64]]#
C_eq: ndarray[tuple[Any, ...], dtype[float64]]#
b_ineq: ndarray[tuple[Any, ...], dtype[float64]]#
d_eq: ndarray[tuple[Any, ...], dtype[float64]]#
class mltpy.constraints.MonotonicityConstraint(n_params)[source]#

Bases: object

Encodes the constraint that Bernstein coefficients are non-decreasing.

For a coefficient vector theta of length n_params, monotonicity of the transformation h(y) = B_k(y) · theta requires:

theta[0] <= theta[1] <= ... <= theta[n_params-1]

This is equivalent to the linear inequality:

D @ theta >= 0

where D is the (n_params-1, n_params) forward-difference matrix:

D = [[-1,  1,  0,  0, ...],
     [ 0, -1,  1,  0, ...],
     [ 0,  0, -1,  1, ...],
     ...]
Parameters:

n_params (int) – Number of Bernstein coefficients (= polynomial degree + 1).

as_LinearConstraint()[source]#

Return a LinearConstraint for use with trust-constr.

Return type:

LinearConstraint

as_matrix()[source]#

Return the (n_params-1, n_params) difference matrix D.

Return type:

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

as_scipy_constraint()[source]#

Return an SLSQP-compatible constraint dict.

fun(theta) returns the vector D @ theta; each element must be >= 0 for the constraint to be satisfied. jac(theta) returns D (constant, computed once in __post_init__).

Return type:

dict[str, Any]

n_params: int#
mltpy.constraints.build_constraint_matrices(n_params, lower=None, upper=None, *, total_params=None, nonneg_lower=False, X=None)[source]#

Build constraint matrices for the augmented Lagrangian solver.

Returns a ConstraintMatrices dataclass whose fields are passed directly to auglag_minimize().

Monotonicity (A_ineq @ θ 0) is always included. When lower or upper are provided, equality rows pinning θ[0] = lower and/or θ[n_params-1] = upper are added to C_eq/d_eq — mirroring BoundaryConstraint.

Parameters:
  • n_params (int) – Number of Bernstein coefficients (= BernsteinBasis.order + 1).

  • lower (float | None) – If not None, pins θ[0] = lower (equality).

  • upper (float | None) – If not None, pins θ[n_params-1] = upper (equality).

  • total_params (int | None) – Total parameter-vector length including any regression coefficients. When total_params > n_params both the monotonicity matrix and the boundary rows are padded with zero columns for the beta block. Defaults to n_params.

  • nonneg_lower (bool) –

    If True, append support-feasibility inequality rows so the transformation stays in the exponential support [0, ∞). Mirrors the nonneg_lower branch of build_constraints():

    • No covariates (X is None): a single row [1, 0, …, 0] enforcing θ_b[0] 0.

    • With covariates: one row [1, 0, …, 0 | X_i] per observation, enforcing θ_b[0] + X_i · β 0.

  • X (ndarray[tuple[Any, ...], dtype[double]] | None) – Covariate matrix, shape (n, q). Only consulted when nonneg_lower=True; q must equal total_params - n_params.

Returns:

A_ineq is the padded forward-difference matrix D (shape (n_params-1, total_params)) optionally followed by the support rows; b_ineq is all-zeros. C_eq has 0, 1, or 2 rows depending on which of lower/upper are provided; d_eq carries the corresponding right-hand-side values.

Return type:

ConstraintMatrices

Raises:

ValueError – If X has invalid shape, if X columns do not match total_params - n_params, or if nonneg_lower=True with X but total_params is omitted.

mltpy.constraints.build_constraint_matrices_interaction(basis)[source]#

Build constraint matrices for an InteractionBasis.

Constructs the Kronecker inequality (D I_q) @ vec(Θ) 0 that enforces column-wise monotonicity: D @ Θ[:, j] 0 for every column j = 0, …, q-1.

Parameters:

basis (InteractionBasis) – The InteractionBasis to build constraints for.

Returns:

A_ineq has shape ((p-1)*q, p*q). b_ineq is all-zeros. C_eq is a zero-row matrix (no equality constraints). d_eq is a zero-length array.

Return type:

ConstraintMatrices

Raises:

ValueError – If the x-basis type is not supported for closed-form constraints. (This is already checked at InteractionBasis construction time, so this branch is a safety net.)

mltpy.constraints.build_constraints(n_params, lower=None, upper=None, *, solver='slsqp', total_params=None, nonneg_lower=False, X=None)[source]#
Overloads:
  • n_params (int), lower (float | None), upper (float | None), solver (Literal[‘slsqp’]), total_params (int | None), nonneg_lower (bool), X (NDArray[np.float64] | None) → list[dict[str, Any]]

  • n_params (int), lower (float | None), upper (float | None), solver (Literal[‘trust-constr’]), total_params (int | None), nonneg_lower (bool), X (NDArray[np.float64] | None) → list[LinearConstraint]

Parameters:
Return type:

list[dict[str, Any]] | list[LinearConstraint]

Build all optimisation constraints for a Bernstein model.

Always includes the monotonicity constraint (non-decreasing theta). Optionally adds boundary equality constraints when lower or upper are specified, and support-feasibility inequalities when nonneg_lower is set (see below).

optimizer.py calls this function — it does not instantiate the constraint classes directly.

Parameters:
  • n_params (int) – Number of Bernstein coefficients (= BernsteinBasis.order + 1).

  • lower (float | None) – If not None, fix theta[0] == lower.

  • upper (float | None) – If not None, fix theta[-1] == upper.

  • solver (Literal['slsqp', 'trust-constr']) –

    "slsqp" → returns list[dict] (for scipy.optimize.minimize

    with method="SLSQP").

    "trust-constr" → returns list[LinearConstraint] (for

    method="trust-constr").

  • total_params (int | None) – Total length of the parameter vector passed to the optimiser, including any regression coefficients (beta). When total_params > n_params the constraint matrix is padded with zero columns so that it maps the full theta vector. Defaults to n_params (no beta). If nonneg_lower=True and X is passed, this must be provided as the full parameter length n_params + X.shape[1].

  • nonneg_lower (bool) –

    If True, require h(y|x) >= 0. Used for base_distribution="exponential", whose support is [0, ∞).

    • No covariates (X is None): the single inequality theta_b[0] >= 0 is sufficient, since h(y) = B_k(y) · theta_b and min_y B_k(y) · theta_b = theta_b[0] under monotonicity.

    • With covariates: h(y|x) = B_k(y) · theta_b + x'β; the minimum over y is attained at y_min (because theta_b is non-decreasing and B_k(y_min) = [1, 0, ..., 0]), giving min_y h(y|x_i) = theta_b[0] + X_i · β per observation i. One inequality theta_b[0] + X_i · β >= 0 is added per row of X, making the training fit feasible under the exponential support.

    Kept distinct from lower because lower is an equality that pins theta[0].

  • X (ndarray[tuple[Any, ...], dtype[double]] | None) – Optional covariate matrix of shape (n, q). Only consulted when nonneg_lower=True — see above. q must equal total_params - n_params.

Raises:

ValueError – If X has invalid shape, if X columns do not match total_params - n_params, or if nonneg_lower=True with X but total_params is omitted.

Returns:

  • list[dict] for solver="slsqp", list[LinearConstraint] for

  • solver="trust-constr".

Return type:

list[dict[str, Any]] | list[LinearConstraint]