# ConvexPSpline


Shape-constrained P-spline: convex or concave.


Usage

``` python
ConvexPSpline(
    k=20,
    degree=3,
    m=2,
    concave=False,
)
```


Uses the same B-spline basis and difference penalty as `~whittaker.smooths.pspline.PSpline`, but additionally requires the fitted curve `f(x) = \sum_j \beta_j B_j(x)` to be convex (or, with `concave=True`, concave) over the whole domain. As with [MonotonePSpline](MonotonePSpline.md#whittaker.MonotonePSpline), this is enforced as a linear inequality on the coefficients: for an equally-spaced B-spline basis, the curve is convex whenever the second differences of the coefficients, `\Delta^2 \beta_j = \beta_j - 2\beta_{j-1} + \beta_{j-2}`, are all non-negative. Use [ConvexPSpline](ConvexPSpline.md#whittaker.ConvexPSpline) (via `s(x, bs="cx")` for convex or `s(x, bs="cv")` for concave in a formula) when the relationship is known to have a single bend of consistent curvature -- e.g. a cost curve, a learning curve, or a concave production function -- and an unconstrained smooth would otherwise produce spurious inflection points from noise.


## Parameters


`k: int = ``20`  
Number of B-spline basis functions. The default is `20`.

`degree: int = ``3`  
B-spline polynomial degree. The default is `3` (cubic).

`m: int = ``2`  
Difference penalty order. The default is `2`.

`concave: bool = ``False`  
If `True`, enforce concavity. The default is `False` (convex).


## Notes

As with [MonotonePSpline](MonotonePSpline.md#whittaker.MonotonePSpline), the constraint is enforced during P-IRLS fitting by projecting the ordinary coefficient update onto the convex (or concave) cone after each iteration: smooth terms whose basis is a [ConvexPSpline](ConvexPSpline.md#whittaker.ConvexPSpline) have their coefficient block passed through `project_convex` before the next iteration's linear predictor is formed (see `whittaker.fitting.pirls`). The projection extends the monotone PAVA projection by one order of differencing: convexity requires the *first* differences of the coefficients, `d_j = \beta_j - \beta_{j-1}`, to form a non-decreasing sequence (equivalently, that the second differences of `\beta` are non-negative), so `project_convex` computes the first differences, projects *them* onto the monotone cone with PAVA (see the `Notes` on [MonotonePSpline](MonotonePSpline.md#whittaker.MonotonePSpline) for the algorithm), and then reconstructs `\beta` by cumulatively summing the projected differences back up from `\beta_0`. Concavity is handled by negating the differences before and after the PAVA projection, mirroring `decreasing=True` for [MonotonePSpline](MonotonePSpline.md#whittaker.MonotonePSpline).


## Examples


``` python
import numpy as np
import whittaker as wt

rng = np.random.default_rng(0)
x = np.sort(rng.uniform(-1, 1, 200))
y = x**2 + rng.normal(scale=0.1, size=200)

model = wt.GAM("y ~ s(x, bs='cx')").fit({"x": x, "y": y})

new_x = np.linspace(-1, 1, 50)
fitted = model.predict({"x": new_x}).values
second_diff = np.diff(fitted, n=2)
# Second differences are non-negative up to fitting/projection tolerance.
np.all(second_diff >= -1e-2)
```


    np.True_


## Attributes

| Name | Description |
|----|----|
| [constraint_direction](#constraint_direction) | Sign convention used by the second-difference PAVA projection. |
| [constraint_order](#constraint_order) | Order of the difference constraint enforced by projection. |

------------------------------------------------------------------------


### constraint_direction


Sign convention used by the second-difference PAVA projection.


`constraint_direction: int`


Returns `-1` when `concave=True` (the first differences of the coefficients are negated before and after the PAVA projection, yielding non-positive second differences) or `1` for the default convex case. Consulted by `project_convex` and by `whittaker.fitting.pirls` when projecting this term's coefficient block each iteration.


------------------------------------------------------------------------


### constraint_order


Order of the difference constraint enforced by projection.


`constraint_order: int`


Always `2`: convexity/concavity is a constraint on the *second* differences of the coefficients (equivalently, the first differences must form a monotone sequence), as opposed to [MonotonePSpline](MonotonePSpline.md#whittaker.MonotonePSpline)'s first-order (`1`) constraint.


## Methods

| Name | Description |
|----|----|
| [null_space_dimension()](#null_space_dimension) | Return the dimension of the basis's unpenalized null space. |

------------------------------------------------------------------------


### null_space_dimension()


Return the dimension of the basis's unpenalized null space.


Usage

``` python
null_space_dimension()
```


As with [MonotonePSpline](MonotonePSpline.md#whittaker.MonotonePSpline), the shape constraint is enforced by post-hoc projection of the coefficients rather than by removing degrees of freedom from the penalty, so this basis reports `0` unpenalized dimensions.


#### Returns


`int`  
Always `0`.
