packages feed

hanalyze-0.2.0.0: src/Hanalyze/Model/Core.hs

{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module      : Hanalyze.Model.Core
-- Description : 全回帰モデル共通の Result 型と Model 型クラス
-- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License     : BSD-3-Clause
--
-- Result type and 'Model' class shared by every regression model.
--
-- For multi-output support, the principal fields of 'FitResult' are
-- generalized to @Matrix Double@ (@n × q@) or @Vector Double@ (@q@-vector).
-- Single-output (@q = 1@) models can keep using the convenience accessors
-- ('coefficientsV', 'fittedV', 'residualsV', 'rSquared1'), which return
-- @Vector@ / @Double@ as before.
--
-- Migrating a single-output model to multi-output is just a matter of
-- calling @fitLM@ with @Matrix × Matrix@ and interpreting the result like
-- a @MultiFitResult@.
module Hanalyze.Model.Core
  ( FitResult (..)
  , Model (..)
  , PredictiveModel (..)
  , ResidualModel (..)
  , Band (..)
    -- * Vec / Scalar accessors (for @q = 1@)
  , coefficientsV
  , fittedV
  , residualsV
  , rSquared1
    -- * List conversion
  , fittedList
  , coeffList
    -- * Per-column access
  , coefficientsCol
  , fittedCol
  , residualsCol
  ) where

import qualified Numeric.LinearAlgebra as LA

-- | Multi-output regression fit result.
--
-- Shapes:
--
--   * 'coefficients' — @p × q@  (@p@ features × @q@ responses).
--   * 'fitted'       — @n × q@  (@n@ observations × @q@ responses).
--   * 'residuals'    — @n × q@.
--   * 'rSquared'     — vector of length @q@ (one R² per response).
--
-- Single-output models use @q = 1@ (a one-column matrix).
data FitResult = FitResult
  { coefficients :: LA.Matrix Double  -- ^ Coefficient matrix @p × q@.
  , fitted       :: LA.Matrix Double  -- ^ Fitted values @n × q@.
  , residuals    :: LA.Matrix Double  -- ^ Residuals @n × q@.
  , rSquared     :: LA.Vector Double  -- ^ Per-response R² (length @q@).
  } deriving (Show)

-- ---------------------------------------------------------------------------
-- Vec / Scalar アクセサ (q = 1 用)
-- ---------------------------------------------------------------------------

-- | Coefficients of a single-output fit as a @Vector@. For multi-output
-- fits this returns just the first column; use 'coefficients' to access
-- all columns.
coefficientsV :: FitResult -> LA.Vector Double
coefficientsV = LA.flatten . coefficients

-- | Fitted values @ŷ@ of a single-output fit as a @Vector@.
fittedV :: FitResult -> LA.Vector Double
fittedV = LA.flatten . fitted

-- | Residuals of a single-output fit as a @Vector@.
residualsV :: FitResult -> LA.Vector Double
residualsV = LA.flatten . residuals

-- | R² of a single-output fit as a scalar 'Double'. For multi-output
-- fits this returns the first component; use 'rSquared' for all
-- responses.
rSquared1 :: FitResult -> Double
rSquared1 r = case LA.toList (rSquared r) of
  (h : _) -> h
  []      -> 0

-- ---------------------------------------------------------------------------
-- 後方互換ヘルパ (旧 Vec API 利用者用)
-- ---------------------------------------------------------------------------

-- | Fitted values as @[Double]@ (single-output).
fittedList :: FitResult -> [Double]
fittedList = LA.toList . fittedV

-- | Coefficients as @[Double]@ (single-output).
coeffList :: FitResult -> [Double]
coeffList = LA.toList . coefficientsV

-- ---------------------------------------------------------------------------
-- 列単位アクセス (多出力時)
-- ---------------------------------------------------------------------------

-- | Coefficients for response @j@ as a @Vector@.
coefficientsCol :: Int -> FitResult -> LA.Vector Double
coefficientsCol j r = LA.flatten (coefficients r LA.¿ [j])

-- | Fitted values @ŷ@ for response @j@ as a @Vector@.
fittedCol :: Int -> FitResult -> LA.Vector Double
fittedCol j r = LA.flatten (fitted r LA.¿ [j])

-- | Residuals for response @j@ as a @Vector@.
residualsCol :: Int -> FitResult -> LA.Vector Double
residualsCol j r = LA.flatten (residuals r LA.¿ [j])

-- ---------------------------------------------------------------------------
-- 不確実性帯
-- ---------------------------------------------------------------------------

-- | Uncertainty band drawn around the mean response.
data Band
  = NoBand      -- ^ No band.
  | CI Double   -- ^ Confidence interval at the given level (e.g. 0.95).
  | PI Double   -- ^ Prediction interval (Gaussian models only).
  deriving (Show, Eq)

-- ---------------------------------------------------------------------------
-- Model クラス (多出力に対応)
-- ---------------------------------------------------------------------------

-- | Common interface implemented by every regression model.
--
-- @
-- fit     m X Y        :: FitResult       -- X (n×p), Y (n×q)
-- predict m beta Xnew  :: Matrix          -- ŷ (m × q), m = rows Xnew
-- @
class Model m where
  fit     :: m -> LA.Matrix Double -> LA.Matrix Double -> FitResult
  predict :: m
          -> LA.Matrix Double  -- ^ Coefficients @β@ of shape @p × q@.
          -> LA.Matrix Double  -- ^ Test input @X_new@ of shape @m × p@.
          -> LA.Matrix Double  -- ^ Predictions @ŷ@ of shape @m × q@.

-- ---------------------------------------------------------------------------
-- 能力別 protocol (Phase 46 / plot Phase 15 = analyze 統合 A 先行)
--
-- モデルの「能力」 を細粒度 class に割り、 持てる能力だけ instance を生やす
-- (spec §2.3 = god class を避ける)。 数値核は hmatrix で完結 (list 操作で書かない)。
-- これらは plot 非依存 = hanalyze-portable (toPlot/Plottable は別途 Hanalyze.Plot)。
-- ===========================================================================

-- | 残差を取り出せるフィット結果。 'toPlot' の残差診断図 (残差 vs fitted / QQ)
-- が要求する最小能力。
class ResidualModel r where
  -- | 残差ベクトル (単出力 @q = 1@ を想定。 多出力は 'residualsCol' を使う)。
  residualsOf :: r -> LA.Vector Double

-- | 新しい入力に対し予測できるフィット結果。 'toPlot' の回帰線・予測 band が
-- 要求する最小能力。
--
-- ⚠ 既定の意味は **線形予測子** @η = X_new · β@ (列 = 各応答)。 LM では平均応答に
-- 一致するが、 GLM の平均応答 @μ = g⁻¹(η)@ には逆リンクが要る (モデルタグ依存)
-- ため、 GLM は 'Model' の 'predict' を使うこと。 本 class は線形スケールの予測を
-- 与える低レベル能力と位置づける。
class PredictiveModel r where
  -- | @X_new (m×p)@ に対する線形予測子 @ŷ = X_new · β (m×q)@。
  predictAt :: r -> LA.Matrix Double -> LA.Matrix Double

-- ---------------------------------------------------------------------------
-- FitResult instances
--
-- 'FitResult' は LM / GLM / GLMM が共有する数値核 (= 1 instance で 3 モデルを覆う)。
-- ===========================================================================

instance ResidualModel FitResult where
  residualsOf = residualsV

instance PredictiveModel FitResult where
  -- ŷ = X_new · β  (β = coefficients、 線形予測子)
  predictAt res xNew = xNew LA.<> coefficients res