Source code for lpspline.constraints.monotonicity

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

[docs] class Monotonic(Constraint): """ Monotonicity constraint enforcing strictly non-decreasing or non-increasing slopes. """ def __init__(self, start: float = None, end: float = None, decreasing: bool = False): """ Initialize the Monotonic constraint. Parameters ---------- 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. decreasing : bool, default=False If True, enforces a monotonically decreasing behavior. Otherwise, non-decreasing. """ self.start = start self.end = end self.decreasing = decreasing
[docs] def build_constraint(self, s) -> list: """ Constructs the appropriate CVXPY monotonic formulations according to the basis type. Parameters ---------- s : Spline The parent Spline applying this restriction. Returns ------- list A sequence containing formulation rules as CVXPY boolean expressions. Raises ------ ValueError If the supplied Spline instance is functionally unsupported. """ from ..spline import Linear, PiecewiseLinear, BSpline variables = s._build_variables() constraints = [] sign = -1 if self.decreasing else 1 if isinstance(s, Linear): return self._constraint_Linear(s=s, sign=sign) elif isinstance(s, PiecewiseLinear): return self._constraint_PiecewiseLinear(s=s, sign=sign) elif isinstance(s, BSpline): return self._constraint_BSpline(s=s, sign=sign) else: raise ValueError(f"Monotonic constraint not supported for spline of type '{type(s).__name__}'")
def _constraint_Linear(self, s, sign: int): """ Generates linear derivative slope restrictions evaluating global trend constants natively. Parameters ---------- s : Linear Linear spline component to limit. sign : int Slope mapping multiplier. Returns ------- list Linear domain formulations bounded by grouping iterations. """ variables = s._build_variables() slope_idx = 1 if s.bias else 0 if getattr(s, '_by_classes', None) is not None: return [sign * variables[slope_idx, :] >= 0] return [sign * variables[slope_idx] >= 0] def _constraint_PiecewiseLinear(self, s, sign): """ Enforces piecewise linear monotonic differences utilizing strictly sequential differencing. Parameters ---------- s : PiecewiseLinear Approximation basis component. sign : int Directionality multiplier. Returns ------- list Formulations targeting piecewise basis differences tracking knot gaps. """ variables = s._build_variables() constraints = [] M = len(s._by_classes) if s.by is not None else 1 dim_base = 2 + len(s.knots) for c in range(M): v_chunk = variables[:, c] if s.by is not None else variables if self.start is not None and self.end is not None: knots = np.array(s.knots) indices = np.where((knots >= self.start) & (knots <= self.end))[0] if len(indices) > 0: for i in indices: constraints.append(sign * cp.sum(v_chunk[1:i+2]) >= 0) else: constraints.append(sign * v_chunk[1] >= 0) for i in range(2, dim_base): constraints.append(sign * cp.sum(v_chunk[1:i+1]) >= 0) return constraints def _constraint_BSpline(self, s, sign): """ Bounds sequential B-Splines evaluating directly on the recursive knot control points natively. Parameters ---------- s : BSpline B-spline formulation component. sign : int Direction factor logic sign. Returns ------- list Resultant lists expressing bounds sequentially matching sequential control constraints. """ variables = s._build_variables() constraints = [] M = len(s._by_classes) if s.by is not None else 1 dim_base = len(s.knots) + s.degree - 1 for c in range(M): # Select variables for current group spline v_chunk = variables[:, c] if s.by is not None else variables if self.start is not None and self.end is not None: knots = np.array(s.knots) indices = np.where((knots >= self.start) & (knots <= self.end))[0] max_idx = len(knots) - 2 indices = indices[indices <= max_idx] if len(indices) > 0: constraints.extend([sign * (v_chunk[indices+1] - v_chunk[indices]) >= 0]) else: constraints.extend([sign * (v_chunk[1:] - v_chunk[:-1]) >= 0]) return constraints