% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/process_qp.R
\name{process_qp}
\alias{process_qp}
\title{Prepare Quadratic Programming Constraints for lgspline}
\usage{
process_qp(
  X,
  K,
  p_expansions,
  order_list,
  colnm_expansions,
  expansion_scales,
  power1_cols,
  power2_cols,
  nonspline_cols,
  interaction_single_cols,
  interaction_quad_cols,
  triplet_cols,
  include_2way_interactions,
  include_3way_interactions,
  include_quadratic_interactions,
  family,
  mean_y,
  sd_y,
  N_obs,
  qp_observations = NULL,
  qp_positive_derivative = FALSE,
  qp_negative_derivative = FALSE,
  qp_positive_2ndderivative = FALSE,
  qp_negative_2ndderivative = FALSE,
  qp_monotonic_increase = FALSE,
  qp_monotonic_decrease = FALSE,
  qp_range_upper = NULL,
  qp_range_lower = NULL,
  qp_Amat_fxn = NULL,
  qp_bvec_fxn = NULL,
  qp_meq_fxn = NULL,
  qp_Amat = NULL,
  qp_bvec = NULL,
  qp_meq = 0,
  all_derivatives_fxn = NULL,
  og_cols = NULL,
  include_warnings = TRUE,
  ...
)
}
\arguments{
\item{X}{List of per-partition design matrices.}

\item{K}{Integer. Number of interior knots.}

\item{p_expansions}{Integer. Number of basis expansions per partition.}

\item{order_list}{List of per-partition observation index vectors.}

\item{colnm_expansions}{Character vector of expansion column names.}

\item{expansion_scales}{Numeric vector of expansion standardization scales.}

\item{power1_cols}{Integer vector of linear-term column indices.}

\item{power2_cols}{Integer vector of quadratic-term column indices.}

\item{nonspline_cols}{Integer vector of non-spline linear column indices.}

\item{interaction_single_cols}{Integer vector of linear-by-linear
interaction column indices.}

\item{interaction_quad_cols}{Integer vector of linear-by-quadratic
interaction column indices.}

\item{triplet_cols}{Integer vector of three-way interaction column indices.}

\item{include_2way_interactions}{Logical switch controlling whether
two-way interactions are included in derivative construction.}

\item{include_3way_interactions}{Logical switch controlling whether
three-way interactions are included in derivative construction.}

\item{include_quadratic_interactions}{Logical switch controlling whether
quadratic interactions are included in derivative construction.}

\item{family}{GLM family object.}

\item{mean_y, sd_y}{Numeric scalars for response standardization.}

\item{N_obs}{Integer. Total sample size.}

\item{qp_observations}{Optional integer vector of observation indices.}

\item{qp_positive_derivative, qp_negative_derivative}{Logical scalar,
character vector, or integer vector. See section
\emph{Per-Variable Derivative Constraints}.}

\item{qp_positive_2ndderivative, qp_negative_2ndderivative}{Same as above
but for second derivatives.}

\item{qp_monotonic_increase, qp_monotonic_decrease}{Logical.
Constrain fitted values to be monotonic in observation order.}

\item{qp_range_upper, qp_range_lower}{Optional numeric upper/lower bounds
for fitted values.}

\item{qp_Amat_fxn, qp_bvec_fxn, qp_meq_fxn}{Optional user-supplied
constraint-generating functions.}

\item{qp_Amat, qp_bvec, qp_meq}{Optional pre-built QP objects. Their presence
marks QP handling as active, but this helper does not append them to the
built-in constraints it constructs; they are expected to be handled
outside this constructor.}

\item{all_derivatives_fxn}{Function to compute derivatives from expansion
matrices (the \code{all_derivatives} closure from \code{lgspline.fit}).}

\item{og_cols}{Optional character vector of original predictor column names.}

\item{include_warnings}{Logical. Whether to issue warnings.}

\item{...}{Additional arguments forwarded to custom constraint functions.}
}
\value{
A list with components:
\describe{
  \item{qp_Amat}{\eqn{P \times M} combined constraint matrix.}
  \item{qp_bvec}{Numeric vector of length \eqn{M}.}
  \item{qp_meq}{Integer. Number of leading equality constraints.}
  \item{quadprog}{Logical. \code{TRUE} if any QP constraints are active.}
}
}
\description{
Constructs \code{qp_Amat}, \code{qp_bvec}, and \code{qp_meq} from the
built-in quadratic programming constraints handled here: range bounds,
derivative sign constraints (first and second), monotonicity constraints,
and user-supplied custom constraint functions.

This function was refactored from the inline QP setup block in
\code{\link{lgspline.fit}} to improve readability and testability.
The interface is fully backward-compatible: existing calls that pass
\code{TRUE}/\code{FALSE} for derivative flags continue to work.
}
\section{Per-Variable Derivative Constraints}{

The arguments \code{qp_positive_derivative}, \code{qp_negative_derivative},
\code{qp_positive_2ndderivative}, and \code{qp_negative_2ndderivative}
now accept three forms:
\describe{
  \item{\code{FALSE}}{No constraint (default).}
  \item{\code{TRUE}}{Constrain \strong{all} predictor variables (backward
    compatible with previous behavior).}
  \item{Character or integer vector}{Constrain only the specified
    predictor variables. Character entries are resolved via
    \code{og_cols}; integer entries refer to column positions in the
    predictor matrix.}
}

This allows, for example, enforcing a positive first derivative for
variable \code{"Dose"} and a negative first derivative for variable
\code{"Time"} simultaneously:
\preformatted{
  lgspline(...,
           qp_positive_derivative = "Dose",
           qp_negative_derivative = "Time")
}

The arguments \code{qp_monotonic_increase} and
\code{qp_monotonic_decrease} remain \code{TRUE}/\code{FALSE} only,
because they constrain fitted values in observation order (not
per-variable).
}

\examples{
\dontrun{
## Standalone verification: simple 1-D monotonic increase
set.seed(42)
t <- seq(-5, 5, length.out = 200)
y <- 3 * sin(t) + t + rnorm(200, 0, 0.5)

## Fit with positive first-derivative constraint on all variables
fit1 <- lgspline(t, y, K = 3,
                 qp_positive_derivative = TRUE)

## Verify: first derivative should be >= 0 everywhere
derivs1 <- predict(fit1, new_predictors = sort(t),
                   take_first_derivatives = TRUE)
stopifnot(all(derivs1$first_deriv >= -1e-8))

## Fit with monotonic increase (observation-order)
fit2 <- lgspline(t, y, K = 3,
                 qp_monotonic_increase = TRUE)
preds2 <- predict(fit2, new_predictors = sort(t))
stopifnot(all(diff(preds2) >= -1e-8))

## Per-variable constraints: 2-D example
x1 <- runif(500, -5, 5)
x2 <- runif(500, -5, 5)
y2 <- x1 + sin(x2) + rnorm(500, 0, 0.5)
dat2 <- data.frame(x1 = x1, x2 = x2, y = y2)

## Constrain x1 to have positive derivative, x2 to have negative
fit3 <- lgspline(y ~ spl(x1, x2), data = dat2, K = 2,
                 qp_positive_derivative = "x1",
                 qp_negative_derivative = "x2")

## Verify per-variable derivatives
newdat <- expand.grid(x1 = seq(-4, 4, length.out = 50),
                      x2 = seq(-4, 4, length.out = 50))
d3 <- predict(fit3, new_predictors = newdat,
              take_first_derivatives = TRUE)

## x1 derivative should be >= 0
stopifnot(all(unlist(d3$first_deriv[["_1_"]]) >= -1e-6))
## x2 derivative should be <= 0
stopifnot(all(unlist(d3$first_deriv[["_2_"]]) <= 1e-6))

## Numeric column indices work identically
fit4 <- lgspline(y ~ spl(x1, x2), data = dat2, K = 2,
                 qp_positive_derivative = 1,
                 qp_negative_derivative = 2)

## Range + derivative constraints simultaneously
fit5 <- lgspline(t, y, K = 3,
                 qp_positive_derivative = TRUE,
                 qp_range_lower = -5,
                 qp_range_upper = 15)
preds5 <- predict(fit5)
stopifnot(all(preds5 >= -5 - 1e-6))
stopifnot(all(preds5 <= 15 + 1e-6))
}

}
