% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/blockfit_solve.R
\name{blockfit_solve}
\alias{blockfit_solve}
\title{Backfitting Solver for Blockfit Models}
\usage{
blockfit_solve(
  X,
  y,
  flat_cols,
  K,
  p_expansions,
  Lambda,
  L_partition_list,
  unique_penalty_per_partition,
  A,
  R_constraints,
  constraint_values,
  X_gram,
  Ghalf_full,
  GhalfInv_full,
  family,
  order_list,
  glm_weight_function,
  schur_correction_function,
  need_dispersion_for_estimation,
  dispersion_function,
  observation_weights,
  homogenous_weights = TRUE,
  iterate,
  tol,
  parallel_eigen,
  cl,
  chunk_size,
  num_chunks,
  rem_chunks,
  return_G_getB,
  quadprog = FALSE,
  qp_Amat = NULL,
  qp_bvec = NULL,
  qp_meq = NULL,
  qp_score_function = NULL,
  keep_weighted_Lambda = FALSE,
  max_backfit_iter = 100,
  Vhalf = NULL,
  VhalfInv = NULL,
  include_warnings = TRUE,
  verbose = FALSE,
  ...
)
}
\arguments{
\item{X}{List of \eqn{K+1} design matrices
(\eqn{N_k \times p_expansions}). Unwhitened even when GEE.}

\item{y}{List of \eqn{K+1} response vectors. Unwhitened even when GEE.}

\item{flat_cols}{Integer vector indicating flat columns of
\eqn{\mathbf{X}^{(k)}}.}

\item{K}{Integer; number of interior knots.}

\item{p_expansions}{Integer; number of coefficients per partition.}

\item{Lambda}{\eqn{p_expansions \times p_expansions} penalty matrix.}

\item{L_partition_list}{List of partition-specific penalty matrices.}

\item{unique_penalty_per_partition}{Logical.}

\item{A}{Full \eqn{P \times R_constraints} constraint matrix.}

\item{R_constraints}{Integer; number of columns of \eqn{\mathbf{A}}.}

\item{constraint_values}{List of constraint right-hand sides.}

\item{X_gram}{List of Gram matrices
\eqn{\mathbf{X}_k^{\top} \mathbf{X}_k}.}

\item{Ghalf_full, GhalfInv_full}{Lists of
\eqn{\mathbf{G}^{1/2}} and \eqn{\mathbf{G}^{-1/2}} matrices.}

\item{family}{GLM family object.}

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

\item{glm_weight_function}{GLM weight function.}

\item{schur_correction_function}{Schur complement correction function.}

\item{need_dispersion_for_estimation}{Logical.}

\item{dispersion_function}{Dispersion estimation function.}

\item{observation_weights}{List of observation weights.}

\item{homogenous_weights}{Logical.}

\item{iterate}{Logical; if FALSE, single pass (no iteration).}

\item{tol}{Convergence tolerance.}

\item{parallel_eigen, cl, chunk_size, num_chunks, rem_chunks}{Parallel arguments.}

\item{return_G_getB}{Logical.}

\item{quadprog}{Logical; apply inequality constraint refinement if TRUE.}

\item{qp_Amat}{Inequality constraint matrix for
\code{quadprog::solve.QP}.}

\item{qp_bvec}{Inequality constraint right-hand side.}

\item{qp_meq}{Number of leading equality constraints.}

\item{qp_score_function}{Score function for QP subproblem.}

\item{keep_weighted_Lambda}{Logical.}

\item{max_backfit_iter}{Integer.}

\item{Vhalf}{Square root of the working correlation matrix in the
original observation ordering.}

\item{VhalfInv}{Inverse square root of the working correlation matrix
in the original observation ordering.  When both are non-\code{NULL},
the GEE paths are used.}

\item{include_warnings}{Logical.}

\item{verbose}{Logical.}

\item{...}{Additional arguments passed to weight and dispersion functions.}
}
\value{
A list with elements:
\describe{
  \item{B}{
    List of \eqn{K+1} coefficient vectors
    (\eqn{p_expansions \times 1}), flat coefficients replicated across partitions.
  }
  \item{G_list}{
    List containing \eqn{\mathbf{G}},
    \eqn{\mathbf{G}^{1/2}}, and
    \eqn{\mathbf{G}^{-1/2}},
    each a list of \eqn{K+1}
    \eqn{p_expansions \times p_expansions} matrices,
    or NULL if \code{return_G_getB} is FALSE.

    \eqn{\mathbf{G}} satisfies
    \eqn{\mathbf{G}
    =
    \mathbf{G}^{1/2}
    (\mathbf{G}^{1/2})^{\top}} exactly.

    When a correlation structure is present, these matrices are obtained
    from the dense whitened information matrix and then split back into
    per-partition blocks.  When flat columns are present, the returned
    matrices also reflect the pooled flat-column information across
    partitions.
  }
  \item{qp_info}{
    NULL when no inequality-refinement metadata are produced.
    Otherwise a list of available QP or active-set diagnostics.
    Dense SQP solves include \code{solution}, \code{lagrangian},
    \code{active_constraints}, \code{iact}, \code{Amat_active},
    \code{bvec_active}, \code{meq_active}, \code{converged}, and
    \code{final_deviance}; non-GEE dense solves also carry
    \code{info_matrix}, \code{Amat_combined}, \code{bvec_combined},
    and \code{meq_combined}.  Partition-wise active-set refinement
    returns the corresponding active-constraint summary only.
  }
}
}
\description{
Fits models with mixed spline and non-interactive linear ("flat") terms
using an iterative backfitting approach. Flat terms receive a single
shared coefficient across partitions (rather than \eqn{K+1}
partition-specific coefficients constrained to equality), reducing the
effective parameter count and improving efficiency when the number of
flat terms is large relative to spline terms.

The backfitting loop alternates between:
\enumerate{

\item \strong{Spline step}

Fit spline terms on the response adjusted for the current flat
contribution using a constrained penalized least squares solve.

If \eqn{\mathbf{y}_k} is the response vector for partition \eqn{k},
\eqn{\mathbf{Z}_k} the spline design matrix,
\eqn{\mathbf{X}_{\mathrm{flat}}^{(k)}} the flat design matrix,
and \eqn{\mathbf{v}} the flat coefficient vector, then

\deqn{
\boldsymbol{\beta}_{\mathrm{spline}}^{(k)} =
\arg\min_{\boldsymbol{\beta}}
\left\|
\mathbf{y}_k
- \mathbf{Z}_k \boldsymbol{\beta}
- \mathbf{X}_{\mathrm{flat}}^{(k)} \mathbf{v}
\right\|^2
+
\boldsymbol{\beta}^{\top}
\mathbf{\Lambda}_{\mathrm{spline}}
\boldsymbol{\beta}
}

subject to

\deqn{
\mathbf{A}_{\mathrm{spline}}^{\top}
\boldsymbol{\beta}
=
\mathbf{c}_{\mathrm{spline}}.
}

\item \strong{Flat step}

Update flat coefficients via pooled penalized regression on residuals,
subject to the residual equality constraint from any mixed constraints:

\deqn{
\mathbf{v}
=
\arg\min_{\mathbf{v}}
\sum_{k=0}^{K}
\left\|
\mathbf{y}_k
-
\mathbf{Z}_k
\boldsymbol{\beta}_{\mathrm{spline}}^{(k)}
-
\mathbf{X}_{\mathrm{flat}}^{(k)}
\mathbf{v}
\right\|^2
+
\mathbf{v}^{\top}
\mathbf{\Lambda}_{\mathrm{flat}}
\mathbf{v}
}

subject to

\deqn{
\tilde{\mathbf{A}}_{\mathrm{flat}}^{\top}
\mathbf{v}
=
\mathbf{c}
-
\mathbf{A}_{\mathrm{spline}}^{\top}
\boldsymbol{\beta}_{\mathrm{spline}}.
}
}

Four estimation paths are selected automatically based on the model
configuration:
\describe{
  \item{Case (a)}{Gaussian identity + GEE: closed-form solve via
    \code{.bf_case_gauss_gee}.}
  \item{Case (b)}{Gaussian identity, no correlation: standard
    backfitting via \code{.bf_case_gauss_no_corr}.}
  \item{Case (c)}{GLM + GEE: two-stage (damped Newton-Raphson warm start then
    damped SQP) via \code{.bf_case_glm_gee}.}
  \item{Case (d)}{GLM without GEE: damped Newton-Raphson + backfitting via
    \code{.bf_case_glm_no_corr}.}
}

When \code{quadprog = TRUE} without GEE, inequality constraints are
enforced after backfitting convergence.  The constraint handling
method is selected automatically by inspecting the sparsity pattern
of \code{qp_Amat}: if every column has nonzeros in only one
partition block (e.g.\ derivative sign or range constraints),
a partition-wise active-set method is used via
\code{.active_set_refine}, avoiding the dense \eqn{P \times P}
system.  If any column spans multiple partition blocks (e.g.\
cross-knot monotonicity), or if the active-set method does not
converge, the dense SQP fallback via \code{.bf_sqp_loop} is used.
GEE paths always use the dense system.

After convergence, coefficients are reassembled into the standard
per-partition format (flat coefficients replicated across partitions)
for compatibility with downstream inference.
}
\examples{
\dontrun{
## Minimal verification example: Gaussian identity, no correlation,
## 2 partitions, 3 spline columns + 1 flat column per partition.
##
## This confirms that blockfit_solve returns compatible output and
## that flat coefficients are replicated across partitions.

set.seed(1234)
n1 <- 50; n2 <- 50
p_expansions <- 4  # intercept + x + x^2 + z (flat)
K  <- 1  # 2 partitions

X1 <- cbind(1, rnorm(n1), rnorm(n1)^2, rnorm(n1))
X2 <- cbind(1, rnorm(n2), rnorm(n2)^2, rnorm(n2))
beta_true <- c(1, 0.5, -0.3, 0.8)
y1 <- X1 \%*\% beta_true + rnorm(n1, 0, 0.5)
y2 <- X2 \%*\% beta_true + rnorm(n2, 0, 0.5)

## Constraint: spline coefficients equal across partitions
## (columns 1:3 constrained, column 4 is flat)
A <- matrix(0, nrow = p_expansions * (K + 1), ncol = 3)
for(j in 1:3){
  A[j, j]      <-  1
  A[p_expansions + j, j]  <- -1
}
qr_A <- qr(A)
A <- qr.Q(qr_A)[, 1:qr_A$rank, drop = FALSE]

Lambda <- diag(p_expansions) * 1e-4
L_partition_list <- list(0, 0)
X_gram <- list(crossprod(X1), crossprod(X2))

G_list <- compute_G_eigen(
  X_gram, Lambda, K,
  parallel = FALSE, cl = NULL,
  chunk_size = NULL, num_chunks = NULL, rem_chunks = NULL,
  family = gaussian(),
  unique_penalty_per_partition = FALSE,
  L_partition_list = L_partition_list,
  keep_G = TRUE,
  schur_corrections = list(0, 0))

result <- blockfit_solve(
  X = list(X1, X2),
  y = list(y1, y2),
  flat_cols = 4L,
  K = K, p_expansions = p_expansions,
  Lambda = Lambda,
  L_partition_list = L_partition_list,
  unique_penalty_per_partition = FALSE,
  A = A, R_constraints = ncol(A),
  constraint_values = list(),
  X_gram = X_gram,
  Ghalf_full = G_list$Ghalf,
  GhalfInv_full = G_list$GhalfInv,
  family = gaussian(),
  order_list = list(1:n1, (n1+1):(n1+n2)),
  glm_weight_function = function(mu, y, oi, fam, d, ow, ...) rep(1, length(y)),
  schur_correction_function = function(X, y, B, d, ol, K, fam, ow, ...) {
    lapply(1:(K + 1), function(k) 0)
  },
  need_dispersion_for_estimation = FALSE,
  dispersion_function = function(...) 1,
  observation_weights = list(rep(1, n1), rep(1, n2)),
  homogenous_weights = TRUE,
  iterate = TRUE, tol = 1e-8,
  parallel = FALSE, cl = NULL,
  chunk_size = NULL, num_chunks = NULL, rem_chunks = NULL,
  return_G_getB = TRUE,
  verbose = FALSE)

## Verify: flat coefficient (col 4) is identical across partitions
stopifnot(abs(result$B[[1]][4] - result$B[[2]][4]) < 1e-10)

## Verify: output structure
stopifnot(length(result$B) == K + 1)
stopifnot(length(result$B[[1]]) == p_expansions)
stopifnot(!is.null(result$G_list))
}

}
\seealso{
\code{\link{lgspline}}, \code{\link{lgspline.fit}},
  \code{\link{get_B}}
}
\keyword{internal}
