packages feed

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

-- |
-- Module      : Hanalyze.Model.MultiOutput
-- Description : Common foundation for multi-output regression (単出力 ↔ 多出力変換 + 評価指標)
-- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License     : BSD-3-Clause
--
-- Common foundation for multi-output regression.
--
-- Design policy:
--
--   * Each model's /primary/ API takes the response @Y@ as
--     @LA.Matrix Double@ (@n × q@) and returns a matrix; the @q = 1@ case
--     is a specialization.
--   * The single-output API (@V.Vector Double@) is a thin wrapper that
--     promotes the response to a one-column matrix via 'asMultiY' /
--     'fromMultiY' and reuses the multi-output implementation.
--   * Per-output evaluation metrics (R² etc.) are collected here.
module Hanalyze.Model.MultiOutput
  ( -- * 単出力 ↔ 多出力 変換
    asMultiY
  , fromMultiY
  , asMultiYV
    -- * Multi-output evaluation metrics
  , rmseMulti
  , r2Multi
  , mseMulti
  ) where

import qualified Data.Vector as V
import qualified Numeric.LinearAlgebra as LA

-- ---------------------------------------------------------------------------
-- 変換
-- ---------------------------------------------------------------------------

-- | Promote a 1D 'V.Vector' to an @n × 1@ matrix.
--
-- >>> import qualified Data.Vector as V
-- >>> LA.rows (asMultiY (V.fromList [1.0, 2.0, 3.0]))
-- 3
-- >>> LA.cols (asMultiY (V.fromList [1.0, 2.0, 3.0]))
-- 1
asMultiY :: V.Vector Double -> LA.Matrix Double
asMultiY = LA.asColumn . LA.fromList . V.toList

-- | Promote an hmatrix 'LA.Vector' to an @n × 1@ matrix.
asMultiYV :: LA.Vector Double -> LA.Matrix Double
asMultiYV = LA.asColumn

-- | Convert an @n × 1@ matrix back to a 1D vector. When @q ≠ 1@, returns
-- the first column.
fromMultiY :: LA.Matrix Double -> V.Vector Double
fromMultiY m
  | LA.cols m == 0 = V.empty
  | otherwise      = V.fromList (LA.toList (LA.flatten (m LA.¿ [0])))

-- ---------------------------------------------------------------------------
-- 評価指標
-- ---------------------------------------------------------------------------

-- | Whole-matrix MSE: sum-of-squares divided by @n × q@.
mseMulti :: LA.Matrix Double -> LA.Matrix Double -> Double
mseMulti ys yhat =
  let n = LA.rows ys
      q = LA.cols ys
      r = ys - yhat
  in LA.sumElements (r * r) / fromIntegral (n * q)

-- | Whole-matrix RMSE.
rmseMulti :: LA.Matrix Double -> LA.Matrix Double -> Double
rmseMulti ys yhat = sqrt (mseMulti ys yhat)

-- | Per-column R² (vector of length @q@).
r2Multi :: LA.Matrix Double -> LA.Matrix Double -> V.Vector Double
r2Multi ys yhat =
  let n  = LA.rows ys
      q  = LA.cols ys
      colR2 j =
        let yc  = LA.toList (LA.flatten (ys   LA.¿ [j]))
            yhc = LA.toList (LA.flatten (yhat LA.¿ [j]))
            mu  = sum yc / fromIntegral n
            sst = sum [(y - mu)^(2::Int) | y <- yc]
            sse = sum [(y - p)^(2::Int) | (y, p) <- zip yc yhc]
        in if sst == 0 then 0 else 1 - sse / sst
  in V.fromList [ colR2 j | j <- [0 .. q - 1] ]