Source code for lpspline.constraints.bound

import cvxpy as cp
import numpy as np
from .base import Constraint

[docs] class Bound(Constraint): """ Constraint enforcing lower and upper bounds on the spline output values using a grid-based discretization approach. """ def __init__(self, lower: float = None, upper: float = None, n: int = 100, start: float = None, end: float = None): """ Initialize the Bound constraint. Parameters ---------- lower : float, default=None The lower bound for the spline output. If None, no lower bound is enforced. upper : float, default=None The upper bound for the spline output. If None, no upper bound is enforced. n : int, default=100 The number of grid points used to discretize the constraint. start : float, default=None The domain starting coordinate to bound the constraint enforcement region. end : float, default=None The domain ending coordinate for the constraint region. """ self.lower = lower self.upper = upper self.n = n self.start = start self.end = end
[docs] def build_constraint(self, s) -> list: """ Constructs the CVXPY bound constraints by evaluating the spline basis on a grid. Parameters ---------- s : Spline The parent Spline applying this restriction. Returns ------- list A list containing CVXPY constraint objects. """ from ..spline import BSpline, PiecewiseLinear, CyclicSpline, Linear # Determine the default domain range for the grid if isinstance(s, BSpline): x_min_default, x_max_default = np.min(s.knots), np.max(s.knots) # TODO This is a proxy of spline domain, to improve elif isinstance(s, PiecewiseLinear): x_min_default, x_max_default = np.min(s.knots), np.max(s.knots) # TODO This is a proxy of spline domain, to improve elif isinstance(s, CyclicSpline): x_min_default, x_max_default = 0, s.period else: raise ValueError(f"Bound constraint not supported for spline of type '{type(s).__name__}'") # Use explicitly provided start/end if available x_min = self.start if self.start is not None else x_min_default x_max = self.end if self.end is not None else x_max_default grid = np.linspace(x_min, x_max, self.n) basis = s._build_basis(grid) variables = s._build_variables() constraints = [] M = len(s._by_classes) if (getattr(s, 'by', None) is not None and s._by_classes is not None) else 1 for c in range(M): v_chunk = variables[:, c] if (M > 1) else variables expr = basis @ v_chunk if self.lower is not None: constraints.append(expr >= self.lower) if self.upper is not None: constraints.append(expr <= self.upper) return constraints