Source code for skfolio.prior._model._covariance_sqrt

"""Covariance square root dataclass."""

# Copyright (c) 2023-2026
# Author: Hugo Delatte <hugo.delatte@skfoliolabs.com>
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

from dataclasses import dataclass

from skfolio.typing import FloatArray

__all__ = ["CovarianceSqrt"]


[docs] @dataclass(frozen=True, eq=False) class CovarianceSqrt: r"""Matrix square root decomposition of a covariance matrix. Encodes :math:`\Sigma = \sum_i A_i A_i^\top + \operatorname{diag}(d)^2` in a form suitable for second-order cone (SOC) constraints: .. math:: \left\lVert \begin{pmatrix} A_1^\top w \\ \vdots \\ A_m^\top w \\ d \odot w \end{pmatrix} \right\rVert_2 \le v \;\Longleftrightarrow\; w^\top \Sigma\, w \le v^2 This representation avoids forming a full :math:`(n \times n)` Cholesky factor when the covariance has lower-dimensional components and a diagonal component. Attributes ---------- components : tuple of ndarray of shape (n, k_i) Matrices :math:`A_i` of shape :math:`(n, k_i)` contributing :math:`\sum_i A_i A_i^\top` to the covariance. diagonal : ndarray of shape (n,) or None Vector :math:`d` contributing :math:`\operatorname{diag}(d)^2` to the covariance. """ components: tuple[FloatArray, ...] = () diagonal: FloatArray | None = None def __post_init__(self) -> None: """Validate component dimensions.""" if len(self.components) == 0 and self.diagonal is None: raise ValueError( "At least one covariance square root component is required." ) n_assets = None for component in self.components: if component.ndim != 2: raise ValueError("Covariance square root components must be 2D arrays.") if component.shape[0] == 0 or component.shape[1] == 0: raise ValueError("Covariance square root components cannot be empty.") if n_assets is None: n_assets = component.shape[0] elif component.shape[0] != n_assets: raise ValueError( "Covariance square root components must have matching row counts." ) if self.diagonal is None: return if self.diagonal.ndim != 1: raise ValueError("Covariance square root diagonal must be a 1D array.") if self.diagonal.shape[0] == 0: raise ValueError("Covariance square root diagonal cannot be empty.") if n_assets is not None and self.diagonal.shape[0] != n_assets: raise ValueError( "Covariance square root diagonal must match component row counts." )