packages feed

hanalyze-0.2.0.0: src/Hanalyze/Model/LiNGAM/VAR.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns      #-}
-- |
-- Module      : Hanalyze.Model.LiNGAM.VAR
-- Description : VAR-LiNGAM (Hyvärinen et al. 2010) — 時系列データに対する LiNGAM 拡張
-- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License     : BSD-3-Clause
--
-- VAR-LiNGAM (Hyvärinen et al. 2010): 時系列データに対する LiNGAM 拡張。
--
-- ## モデル
--
-- 観測時系列 Y_t ∈ ℝ^K は以下の SVAR (構造 VAR) に従う:
--
-- > Y_t = Σ_{l=1..p} A_l^* · Y_{t-l} + B_0 · Y_t + e_t
--
-- ここで B_0 は同時刻因果 (contemporaneous causal effect、 acyclic + LiNGAM)、
-- e_t は非ガウシアン独立 noise。 通常の reduced-form VAR(p) と関係:
--
-- > Y_t = Σ_l A_l · Y_{t-l} + u_t,   u_t = (I - B_0)⁻¹ · e_t
--
-- なので u_t に LiNGAM を適用すれば B_0 が求まり、 A_l^* も A_l と B_0 から
-- 回収できる。
--
-- ## アルゴリズム
--
-- 1. Phase 35 の 'Hanalyze.Model.VAR.fitVAR' で reduced-form VAR(p) を fit
-- 2. 残差 u_t (= 'varResiduals') に 'fitDirectLiNGAM' を適用 → B_0 と
--    causal order を取得
-- 3. 構造 lag 行列を A_l^* = (I - B_0) · A_l で復元 (l=1..p)
--
-- ## リファレンス
--
-- Hyvärinen et al. (2010) "Estimation of a Structural Vector Autoregression
-- Model Using Non-Gaussianity", JMLR 11. Python 実装は cdt15/lingam の
-- `lingam/var_lingam.py`。
module Hanalyze.Model.LiNGAM.VAR
  ( VARLiNGAMConfig (..)
  , VARLiNGAMFit (..)
  , defaultVARLiNGAMConfig
  , fitVARLiNGAM
  , vlDAG
  ) where

import qualified Numeric.LinearAlgebra as LA

import qualified Hanalyze.Model.VAR           as V
import qualified Hanalyze.Model.LiNGAM.Direct as DL
import qualified Hanalyze.Model.DAG           as DAG

-- ===========================================================================
-- 設定 / 結果
-- ===========================================================================

data VARLiNGAMConfig = VARLiNGAMConfig
  { vlcLagOrder  :: !Int
    -- ^ VAR の lag 数 p (≥ 1)
  , vlcDirectCfg :: !DL.DirectLiNGAMConfig
  } deriving (Show)

defaultVARLiNGAMConfig :: VARLiNGAMConfig
defaultVARLiNGAMConfig = VARLiNGAMConfig
  { vlcLagOrder  = 1
  , vlcDirectCfg = DL.defaultDirectLiNGAMConfig
  }

data VARLiNGAMFit = VARLiNGAMFit
  { vlVARFit          :: !V.VARFit
    -- ^ Phase 35 の reduced-form VAR(p) fit 結果
  , vlContempLiNGAM   :: !DL.DirectLiNGAMFit
    -- ^ 残差 u_t に対する DirectLiNGAM 結果 (= 同時刻因果 B_0)
  , vlB0              :: !(LA.Matrix Double)
    -- ^ 同時刻因果係数 (K × K)、 = vlContempLiNGAM の dlB
  , vlStructuralLags  :: ![LA.Matrix Double]
    -- ^ 構造 lag 行列 A_l^* = (I - B_0) · A_l (length = p)
  , vlContempOrder    :: ![Int]
  } deriving (Show)

-- ===========================================================================
-- 主実装
-- ===========================================================================

fitVARLiNGAM :: VARLiNGAMConfig -> LA.Matrix Double -> VARLiNGAMFit
fitVARLiNGAM cfg y =
  let !varFit = V.fitVAR (vlcLagOrder cfg) y
      !resid  = V.varResiduals varFit
      !lgFit  = DL.fitDirectLiNGAM (vlcDirectCfg cfg) resid
      !b0     = DL.dlB lgFit
      !k      = V.varK varFit
      !iMinusB0 = LA.ident k - b0
      !structLags =
        [ iMinusB0 LA.<> al | al <- V.varCoefs varFit ]
  in VARLiNGAMFit
       { vlVARFit         = varFit
       , vlContempLiNGAM  = lgFit
       , vlB0             = b0
       , vlStructuralLags = structLags
       , vlContempOrder   = DL.dlOrder lgFit
       }

-- | 同時刻因果 (B_0) の DAG 表現を返す。 lag 部分は含まない (時間方向は別の
--   表現が必要、 v0.1 では同時刻のみ DAG 化)。
vlDAG :: VARLiNGAMConfig -> VARLiNGAMFit -> DAG.DAG
vlDAG cfg fit = DAG.fromBMatrix (DL.dlcPruneThr (vlcDirectCfg cfg)) (vlB0 fit)