Metadata-Version: 2.4
Name: alsgls
Version: 2.0.0
Summary: Type-safe lightweight low-rank+diagonal GLS/SUR solver via ALS
Author: Gaurav Sood
Author-email: Gaurav Sood <contact@gsood.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Requires-Dist: numpy>=1.21
Requires-Dist: scipy>=1.17.1
Requires-Dist: pandas>=1.0 ; extra == 'pandas'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/finite-sample/alsgls
Project-URL: Documentation, https://finite-sample.github.io/alsgls/
Project-URL: Repository, https://github.com/finite-sample/alsgls
Provides-Extra: pandas
Description-Content-Type: text/markdown

<!-- This file is auto-generated from README.template.md by .github/workflows/docs.yml -->
<!-- Do not edit this file directly - edit README.template.md instead -->

## A Lightweight ALS Solver for Iterative GLS

[![PyPI version](https://img.shields.io/pypi/v/alsgls.svg)](https://pypi.org/project/alsgls/)
[![PyPI Downloads](https://static.pepy.tech/badge/alsgls)](https://pepy.tech/projects/alsgls)
[![Python](https://img.shields.io/badge/dynamic/toml?url=https://raw.githubusercontent.com/finite-sample/alsgls/main/pyproject.toml&query=$.project.requires-python&label=Python)](https://github.com/finite-sample/alsgls)
[![License](https://img.shields.io/badge/dynamic/toml?url=https://raw.githubusercontent.com/finite-sample/alsgls/main/pyproject.toml&query=$.project.license.text&label=License)](https://opensource.org/licenses/MIT)


When a GLS problem involves hundreds of equations, the $K × K$ covariance matrix becomes the computational bottleneck.  A simple statistical remedy is to assume that most of the cross‑equation dependence can be captured by a *handful of latent factors* plus equation‑specific noise.  This "low‑rank + diagonal" assumption slashes the number of unknowns from roughly $K^²$ to about $K×k$ parameters, where **k** (the latent factor rank) is much smaller than $K$.  This is essentially **poor man's multi-task learning**: the shared latent factors let information flow across tasks (equations), improving estimates when tasks are related—without the complexity of neural networks.  The model alone, however, does **not** guarantee speed: we still have to fit the parameters.

## Installation

Install the library from PyPI:

```bash
pip install alsgls
```

For local development, clone the repo and use an editable install:

```bash
pip install -e .
```

## Usage

```python
from alsgls import ALSGLS, ALSGLSSystem, simulate_sur

Xs_tr, Y_tr, Xs_te, Y_te = simulate_sur(N_tr=240, N_te=120, K=60, p=3, k=4)

# Scikit-learn style estimator
est = ALSGLS(rank="auto", max_sweeps=12)
est.fit(Xs_tr, Y_tr)
test_score = est.score(Xs_te, Y_te)  # negative test NLL per observation

# Statsmodels-style system interface
system = {f"eq{j}": (Y_tr[:, j], Xs_tr[j]) for j in range(Y_tr.shape[1])}
sys_model = ALSGLSSystem(system, rank="auto")
sys_results = sys_model.fit()
params = sys_results.params_as_series()  # pandas optional
```

### Rank Selection

The package supports automatic rank selection via BIC or cross-validation:

```python
from alsgls import ALSGLS

# BIC-based rank selection
est = ALSGLS(rank="bic", max_sweeps=15)
est.fit(Xs, Y)
print(f"Selected rank: {est.rank_}")

# Cross-validation rank selection
est = ALSGLS(rank="cv", cv_folds=5, cv_random_state=42)
est.fit(Xs, Y)
print(f"Selected rank: {est.rank_}")
```

### Real Data Example

See `examples/real_data_fama_french.py` for a demonstration using Fama-French 49 industry portfolios.

The `benchmarks/compare_sur.py` script contrasts ALS-GLS with `statsmodels` and
`linearmodels` SUR implementations on matched simulation grids while recording
peak memory (via Memray, Fil, or the POSIX RSS high-water mark).

### Documentation and notebooks

Background material and reproducible experiments are available in the notebooks under [`als_sim/`](als_sim/), such as [`als_sim/als_comparison.ipynb`](als_sim/als_comparison.ipynb) and [`als_sim/als_sur.ipynb`](als_sim/als_sur.ipynb).

### Type-Safe ALS Solver

This package provides a modern, type-safe implementation of **Alternating-Least-Squares (ALS)** for low-rank GLS problems. The Woodbury identity reduces the expensive inverse to a tiny k × k system, and the β-update can be written without explicitly forming dense matrices. 

**New in v1.2.0:**
- **Standard errors** (`bse`) for all regression coefficients
- **t-statistics** (`tvalues`) and **p-values** (`pvalues`) for hypothesis testing
- **Confidence intervals** via `conf_int()` method
- **Summary tables** via `summary()` for statsmodels-style output

**New in v1.1.0:**
- **Rank selection**: BIC and cross-validation for automatic rank selection
- **Gradient-based factor update**: Cleaner theory, same convergence guarantees
- **Real-world example**: Fama-French 49 industry portfolios demonstration
- **Formal methods documentation**: Rigorous mathematical foundations

**Core features:**
- **Full type safety** with mypy compliance and comprehensive type hints
- **Numerically stable** implementation using Cholesky factorization throughout
- **Clean API** with single computational path and enhanced error messages
- **Memory efficient** with O(K k) complexity, converging in 5–6 sweeps

**Rule of thumb:** if your GLS routine keeps looping between $\beta$ and a fresh $\hat{\Sigma}$, the ALS approach yields the same statistical fit with an order‑of‑magnitude smaller memory footprint and better numerical stability.

### Beyond SUR: where the idea travels

Random‑effects models, feasible GLS with estimated heteroskedastic weights, optimal‑weight GMM, and spatial autoregressive GLS all iterate β ↔ Σ̂.  Each can adopt the same ALS trick: treat the weight matrix as low‑rank + diagonal, invert only the k × k core, and avoid the dense K × K algebra.  Memory savings in published examples range from 5× to 20×, depending on k.

### A concrete case‑study: Seemingly‑Unrelated Regressions

To demonstrate performance, we benchmark ALS against traditional methods with N = 300 observations, three regressors, rank‑3 factors, and K ranging from 50 to 120 equations. The largest array that traditional methods need is the dense Σ⁻¹ (K×K), whereas ALS's largest is the skinny factor matrix F (K×k).

|   K | β‑RMSE EM | β‑RMSE ALS | Peak MB EM | Peak MB ALS | Memory ratio |
| --: | :-------: | :--------: | ---------: | ----------: | -----------: |
|  50 |   0.021   |    0.021   |     0.020  |      0.002  |         10×  |
|  80 |   0.020   |    0.020   |     0.051  |      0.003  |         17×  |
| 120 |   0.020   |    0.020   |     0.115  |      0.004  |         29×  |

The ALS implementation achieves the same statistical performance while using only a few megabytes of memory, providing substantial computational advantages for large systems.

### Defaults, tuning knobs, and failure modes

- **Rank (`k`)** – By default the high-level APIs pick `min(8, ceil(K / 10))`, a
  conservative fraction of the number of equations. Increase `rank` if the
  cross-equation correlation matrix is slow to decay; decrease it when the
  diagonal dominates.
- **Ridge term (`lam_B`)** – Defaults to `1e-3` on the regression update, and is
  applied relative to the residual variance scale so the fit does not depend on
  the units of `Y`. Raise it (e.g. `1e-2`) if CG struggles to converge. There is
  no penalty on the factor loadings: the Σ-step is the exact conditional
  solution, so there is nothing for one to regularise.
- **Noise floor (`d_floor`)** – Keeps the diagonal component positive; the
  default `1e-8` is a fraction of the mean residual variance, not an absolute
  variance, so it transforms correctly under a change of units. Increase it in
  highly ill-conditioned settings.
- **Stopping criteria** – ALS stops when the relative drop in NLL per sweep is
  below `1e-6` (configurable via `rel_tol`) or after `max_sweeps`. Inspect
  `info["nll_trace"]` to diagnose stagnation.
- **Possible failures** – Large condition numbers or nearly-collinear regressors
  can make the β-step CG solve slow; adjust `cg_tol`/`cg_maxit`, add stronger
  ridge, or re-scale predictors. `info["sigma_iters"]` reports how many inner
  iterations each Σ-step needed; counts that sit at the cap mean the
  alternation is crawling, which happens when some diagonal variances are near
  zero (a Heywood case) and usually indicates the factor rank is too large
  relative to the sample size.