% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/HelperFunctions.R
\name{compute_trace_H}
\alias{compute_trace_H}
\title{Effective degrees of freedom via trace of the hat matrix}
\usage{
compute_trace_H(
  G,
  Lambda,
  A,
  AGAInv,
  nc,
  nca,
  K,
  parallel,
  cl,
  chunk_size,
  num_chunks,
  rem_chunks,
  unique_penalty_per_partition,
  L_partition_list
)
}
\arguments{
\item{G}{List of \eqn{K+1} matrices \eqn{\mathbf{G}_k}, each \eqn{p \times p}.}

\item{Lambda}{Base penalty matrix \eqn{\boldsymbol{\Lambda}}, \eqn{p \times p},
shared across all partitions. When \code{unique_penalty_per_partition = FALSE},
this is the full per-block penalty. When \code{TRUE}, the effective penalty
for partition \eqn{k} is \eqn{\boldsymbol{\Lambda} + \mathbf{L}_k}.}

\item{A}{Constraint matrix, \eqn{P \times r}.}

\item{AGAInv}{Precomputed \eqn{(\mathbf{A}^\top\mathbf{G}\mathbf{A})^{-1}},
\eqn{r \times r}.}

\item{nc}{Number of columns per partition block (\eqn{p}).}

\item{nca}{Number of constraint columns (\eqn{r}).}

\item{K}{Number of interior knots (so there are \eqn{K+1} partitions).}

\item{parallel}{Logical; use parallel processing.}

\item{cl}{Cluster object for parallel computation.}

\item{chunk_size}{Size of parallel chunks.}

\item{num_chunks}{Number of full-size chunks.}

\item{rem_chunks}{Number of remaining partitions in the final chunk.}

\item{unique_penalty_per_partition}{Logical; if \code{TRUE}, each partition
receives an additional penalty from \code{L_partition_list}.}

\item{L_partition_list}{List of partition-specific penalty matrices
\eqn{\mathbf{L}_k}, each \eqn{p \times p}. Only used when
\code{unique_penalty_per_partition = TRUE}; ignored otherwise.}
}
\value{
Scalar effective degrees of freedom, clamped to \eqn{[0, \, (K+1)p]}.
}
\description{
Computes \eqn{\mathrm{tr}(\mathbf{X}\mathbf{U}\mathbf{G}\mathbf{X}^\top
\mathbf{W}^{1/2}\mathbf{V}^{-1}\mathbf{W}^{1/2})} without forming
the \eqn{N \times N} hat matrix, by reducing the problem to
\eqn{P \times P} and \eqn{r \times r} operations.
}
\details{
\subsection{Derivation}{
Let \eqn{\mathbf{G} = (\mathbf{X}^\top\mathbf{W}^{1/2}\mathbf{V}^{-1}\mathbf{W}^{1/2}\mathbf{X} + \boldsymbol{\Lambda})^{-1}}
and \eqn{\mathbf{U} = \mathbf{I} - \mathbf{G}\mathbf{A}(\mathbf{A}^\top\mathbf{G}\mathbf{A})^{-1}\mathbf{A}^\top}.
By the cyclic property of the trace:
\deqn{\mathrm{tr}(\mathbf{X}\mathbf{U}\mathbf{G}\mathbf{X}^\top\mathbf{W}^{1/2}\mathbf{V}^{-1}\mathbf{W}^{1/2}) = \mathrm{tr}(\mathbf{U}\mathbf{G}\,\mathbf{X}^\top\mathbf{W}^{1/2}\mathbf{V}^{-1}\mathbf{W}^{1/2}\mathbf{X})}

Substituting \eqn{\mathbf{X}^\top\mathbf{W}^{1/2}\mathbf{V}^{-1}\mathbf{W}^{1/2}\mathbf{X} = \mathbf{G}^{-1} - \boldsymbol{\Lambda}}:
\deqn{= \mathrm{tr}(\mathbf{U}\mathbf{G}(\mathbf{G}^{-1} - \boldsymbol{\Lambda})) = \mathrm{tr}(\mathbf{U}) - \mathrm{tr}(\mathbf{U}\mathbf{G}\boldsymbol{\Lambda})}

Since \eqn{\mathbf{U}} is idempotent,
\eqn{\mathrm{tr}(\mathbf{U}) = \mathrm{rank}(\mathbf{U}) = P - r}
where \eqn{r = \mathrm{rank}(\mathbf{A})}. For the second term,
expand \eqn{\mathbf{U} = \mathbf{I} - \mathbf{G}\mathbf{A}(\mathbf{A}^\top\mathbf{G}\mathbf{A})^{-1}\mathbf{A}^\top}:
\deqn{\mathrm{tr}(\mathbf{U}\mathbf{G}\boldsymbol{\Lambda}) = \mathrm{tr}(\mathbf{G}\boldsymbol{\Lambda}) - \mathrm{tr}\!\left((\mathbf{A}^\top\mathbf{G}\mathbf{A})^{-1}\mathbf{A}^\top\mathbf{G}\boldsymbol{\Lambda}\mathbf{G}\mathbf{A}\right)}

using the cyclic property on the second term. Both \eqn{\mathbf{G}}
and \eqn{\boldsymbol{\Lambda}} are block-diagonal with \eqn{K+1}
blocks of dimension \eqn{p \times p}, so:
\deqn{\mathrm{tr}(\mathbf{G}\boldsymbol{\Lambda}) = \sum_{k=1}^{K+1}\mathrm{tr}(\mathbf{G}_k\boldsymbol{\Lambda}_k)}
and \eqn{\mathbf{G}\boldsymbol{\Lambda}\mathbf{G}} is also block-diagonal
with blocks \eqn{\mathbf{G}_k\boldsymbol{\Lambda}_k\mathbf{G}_k}.

Combining:
\deqn{\mathrm{tr}(\mathbf{X}\mathbf{U}\mathbf{G}\mathbf{X}^\top\mathbf{W}^{1/2}\mathbf{V}^{-1}\mathbf{W}^{1/2}) = (P - r) - \sum_{k=1}^{K+1}\mathrm{tr}(\mathbf{G}_k\boldsymbol{\Lambda}_k) + \mathrm{tr}\!\left((\mathbf{A}^\top\mathbf{G}\mathbf{A})^{-1}\mathbf{A}^\top\mathbf{G}\boldsymbol{\Lambda}\mathbf{G}\mathbf{A}\right)}

The correction term
\eqn{\mathrm{tr}((\mathbf{A}^\top\mathbf{G}\mathbf{A})^{-1}\mathbf{A}^\top\mathbf{G}\boldsymbol{\Lambda}\mathbf{G}\mathbf{A})}
is an \eqn{r \times r} trace and captures the degrees of freedom
recovered because the constraints pin certain linear combinations of
penalized coefficients, removing them from estimation. When
\eqn{\boldsymbol{\Lambda} = \mathbf{0}} (no penalty), both
\eqn{\mathrm{tr}(\mathbf{G}\boldsymbol{\Lambda})} and the correction
vanish, giving \eqn{\mathrm{edf} = P - r}. When \eqn{\mathbf{U} = \mathbf{I}}
(no constraints), \eqn{r = 0} and the correction vanishes, giving
\eqn{\mathrm{edf} = P - \mathrm{tr}(\mathbf{G}\boldsymbol{\Lambda})}.

The result is invariant to the correlation structure \eqn{\mathbf{V}}
and GLM weights \eqn{\mathbf{W}}, which enter only through
\eqn{\mathbf{G}}.
}

\subsection{Partition-specific penalties}{
When \code{unique_penalty_per_partition = TRUE}, each partition \eqn{k}
has an additional penalty matrix \eqn{\mathbf{L}_k} from
\code{L_partition_list}, so the effective per-partition penalty becomes
\eqn{\boldsymbol{\Lambda}_k = \boldsymbol{\Lambda} + \mathbf{L}_k}.
All block-wise traces and products use \eqn{\boldsymbol{\Lambda}_k}
in place of the shared \eqn{\boldsymbol{\Lambda}}.
}

\subsection{Computational cost}{
The partition-wise traces \eqn{\mathrm{tr}(\mathbf{G}_k\boldsymbol{\Lambda}_k)}
cost \eqn{O(p^2)} each and are parallelizable. The correction term
requires forming \eqn{\mathbf{G}\boldsymbol{\Lambda}\mathbf{G}\mathbf{A}}
(block-diagonal times sparse, \eqn{O((K+1)p^2 r)}) and a single
\eqn{r \times r} trace (\eqn{O(r^2)}). The total cost is
\eqn{O((K+1)p^2 + r^2)}, compared to \eqn{O(N^2)} or \eqn{O(NP)}
for forming and tracing the full hat matrix.
}
}
\keyword{internal}
