hanalyze-0.2.0.0: src/Hanalyze/Model/VAR.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}
-- |
-- Module : Hanalyze.Model.VAR
-- Description : 多変量自己回帰 VAR(p) モデルの方程式別 OLS 推定と予測
-- Copyright : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License : BSD-3-Clause
--
-- VAR(p) — Vector AutoRegressive model.
--
-- Multivariate generalization of AR(p): for a @K@-dimensional series
-- @yₜ ∈ ℝᴷ@,
--
-- @
-- yₜ = c + A₁·yₜ₋₁ + A₂·yₜ₋₂ + … + Aₚ·yₜ₋ₚ + εₜ
-- @
--
-- where each @Aₗ@ is a @K × K@ coefficient matrix and @c@ is a length-@K@
-- intercept. Estimation is by equation-by-equation OLS, which is the
-- maximum-likelihood estimator for VAR under Gaussian innovations (the
-- stacked system has the same regressors in every equation, so SUR
-- collapses to OLS — Lütkepohl 2005 §3.2).
--
-- @
-- import Hanalyze.Model.VAR
--
-- let fit = fitVAR 2 yMat -- VAR(2) on n × K series
-- fc = forecastVAR fit yMat 10 -- 10-step ahead
-- @
--
-- == Implemented
--
-- * 'fitVAR' (equation-by-equation OLS, joint estimation)
-- * 'forecastVAR' (deterministic point forecast, h steps)
module Hanalyze.Model.VAR
( VARFit (..)
, fitVAR
, forecastVAR
) where
import qualified Numeric.LinearAlgebra as LA
-- ---------------------------------------------------------------------------
-- Types
-- ---------------------------------------------------------------------------
-- | Fitted VAR(p) model.
data VARFit = VARFit
{ varP :: !Int -- ^ Lag order @p@.
, varK :: !Int -- ^ Series dimensionality @K@.
, varConst :: !(LA.Vector Double) -- ^ Intercept @c@ (length @K@).
, varCoefs :: ![LA.Matrix Double] -- ^ @[A₁, …, Aₚ]@, each @K × K@.
, varResiduals :: !(LA.Matrix Double) -- ^ Residuals, @(n − p) × K@.
, varSigma :: !(LA.Matrix Double) -- ^ Residual covariance @K × K@.
} deriving (Show)
-- ---------------------------------------------------------------------------
-- Fitting
-- ---------------------------------------------------------------------------
-- | Fit a VAR(@p@) model to an @n × K@ series @Y@ by equation-by-equation
-- OLS. The first @p@ rows are consumed as the initial lag window;
-- @n − p@ effective observations are used. Requires @n > p · K + 1@.
fitVAR :: Int -> LA.Matrix Double -> VARFit
fitVAR p y =
let n = LA.rows y
k = LA.cols y
neff = n - p
-- Design matrix Z: each row t = [1, y_{t-1}, y_{t-2}, …, y_{t-p}]
-- (1 + p·K columns), for t = p, p+1, …, n-1.
buildRow t =
1.0 : concat [ LA.toList (LA.flatten (y LA.? [t - l]))
| l <- [1 .. p] ]
zRows = [ buildRow t | t <- [p .. n - 1] ]
z = LA.fromLists zRows -- (neff × (1 + p·K))
yLag = y LA.?? (LA.Drop p, LA.All) -- (neff × K)
-- OLS: B = (Zᵀ Z)⁻¹ Zᵀ Y. Use linearSolveLS (least squares) for
-- numerical stability.
bMat = LA.linearSolveLS z yLag -- ((1 + p·K) × K)
cVec = LA.flatten (bMat LA.? [0]) -- intercept (K,)
coefs =
[ LA.tr (bMat LA.?? ( LA.Pos (LA.idxs [ 1 + (l - 1) * k + j
| j <- [0 .. k - 1] ])
, LA.All ))
| l <- [1 .. p] ]
-- Each block row of B is K rows giving Aₗᵀ; transpose for K × K Aₗ.
yhat = z LA.<> bMat
resid = yLag - yhat
sigma = (LA.tr resid LA.<> resid)
/ fromIntegral (max 1 (neff - (1 + p * k)))
in VARFit
{ varP = p
, varK = k
, varConst = cVec
, varCoefs = coefs
, varResiduals = resid
, varSigma = sigma
}
-- ---------------------------------------------------------------------------
-- Forecasting
-- ---------------------------------------------------------------------------
-- | Deterministic @h@-step-ahead point forecast (ε set to zero):
--
-- @
-- ŷ_{T+k} = c + Σₗ Aₗ · ŷ_{T+k-ℓ}
-- @
--
-- where @ŷ_{T+j} = y_{T+j}@ for @j ≤ 0@. The full input series @y@ is
-- accepted to supply the last @p@ rows used as initial history.
forecastVAR :: VARFit -> LA.Matrix Double -> Int -> LA.Matrix Double
forecastVAR fit y h
| h <= 0 = LA.fromLists []
| otherwise =
let p = varP fit
n = LA.rows y
-- Initial history: last p rows of y, as a [Vector Double] list
-- with index 0 = y_{T-1}, index 1 = y_{T-2}, …, index p-1 = y_{T-p}.
hist0 = [ LA.flatten (y LA.? [n - 1 - i]) | i <- [0 .. p - 1] ]
step !hist =
let !pred_ =
varConst fit
+ foldr1 (+)
[ (varCoefs fit !! (l - 1)) LA.#> (hist !! (l - 1))
| l <- [1 .. p] ]
in (pred_, pred_ : init hist)
go !k !hist acc
| k > h = reverse acc
| otherwise =
let (yk, hist') = step hist
in go (k + 1) hist' (yk : acc)
in LA.fromRows (go 1 hist0 [])