packages feed

hanalyze-0.1.0.0: bench/haskell/BenchTSExtras.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-full-laziness -fno-cse #-}
-- | B8 残: Holt-Winters / GAM / Spline ベンチ。
--
--   * Holt-Winters seasonal n=500 period=12 (Additive)
--   * GAM n=2000 splines=10 (1D)
--   * Interp1d (Linear / NaturalSpline / PCHIP) on n=1000 grid → eval 5000 pts
--
-- 出力: bench/results/haskell/ts_extras.csv
module Main where

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

import           Hanalyze.Model.TimeSeries        (HWMode (..), holtWinters, hwFitted)
import           Hanalyze.Model.GAM               (fitGAM, gamYHat)
import           Hanalyze.Stat.Interpolate        (InterpKind (..), interp1d)

import           BenchUtil

-- ---------------------------------------------------------------------------
-- Data generators (deterministic, no RNG).
-- ---------------------------------------------------------------------------

-- Seasonal series of length n with period 12: y_t = trend + sin(2π t/12) + ε
-- where ε is small deterministic noise (sinusoidal with different period).
seasonalSeries :: Int -> LA.Vector Double
seasonalSeries n =
  LA.fromList
    [ 0.05 * fromIntegral t
        + 2.0 * sin (2 * pi * fromIntegral t / 12.0)
        + 0.1 * sin (fromIntegral t * 0.7)
    | t <- [0 .. n - 1] ]

-- 1D smooth-with-bumps function for GAM / interpolation.
smoothFn :: Double -> Double
smoothFn x = sin (2 * x) + 0.5 * x + 0.3 * sin (5 * x)

gamData :: Int -> ([V.Vector Double], V.Vector Double)
gamData n =
  let xs = V.fromList [ -3.0 + 6.0 * fromIntegral i / fromIntegral (n - 1)
                       | i <- [0 .. n - 1] ]
      ys = V.map smoothFn xs
  in ([xs], ys)

-- Returns (knots, fineEvalXs) — scattered knot data + evaluation grid.
interpData :: Int -> Int -> ([(Double, Double)], [Double])
interpData nKnots nEval =
  let knots = [ (xi, smoothFn xi)
              | i <- [0 .. nKnots - 1]
              , let xi = -3.0 + 6.0 * fromIntegral i / fromIntegral (nKnots - 1) ]
      grid  = [ -2.9 + 5.8 * fromIntegral i / fromIntegral (nEval - 1)
              | i <- [0 .. nEval - 1] ]
  in (knots, grid)

-- ---------------------------------------------------------------------------
-- Holt-Winters
-- ---------------------------------------------------------------------------

benchHW :: IO [BenchRow]
benchHW = do
  let !y = seasonalSeries 500
      run :: Int -> IO Double
      run _ = do
        let fit  = holtWinters HWAdditive 12 y
            yhat = hwFitted fit
            r    = yhat - y
            err  = LA.sumElements (LA.cmap (\d -> d * d) r)
        return err
      probe = id
  (ms, e) <- timeitTastyIO probe run
  let n = LA.size y
      rmse = sqrt (e / fromIntegral n)
  return [ BenchRow "haskell" "ts_extras"
            "HW_seasonal_n500_p12_additive" ms rmse 0
            ("Holt-Winters additive period=12 RMSE=" ++ show rmse) ]

-- ---------------------------------------------------------------------------
-- GAM
-- ---------------------------------------------------------------------------

benchGAM :: IO [BenchRow]
benchGAM = do
  let (xss, !y) = gamData 2000
      yLA = LA.fromList (V.toList y)
      run :: Int -> IO Double
      run _ = do
        let fit  = fitGAM 3 10 1e-3 xss y
            yhat = gamYHat fit
            r    = yhat - yLA
            err  = LA.sumElements (LA.cmap (\d -> d * d) r)
        return err
      probe = id
  (ms, e) <- timeitTastyIO probe run
  let n = V.length y
      rmse = sqrt (e / fromIntegral n)
  return [ BenchRow "haskell" "ts_extras"
            "GAM_n2000_splines10_1D" ms rmse 0
            ("GAM degree=3 nKnots=10 λ=1e-3 RMSE=" ++ show rmse) ]

-- ---------------------------------------------------------------------------
-- Spline interpolation: Linear / NaturalSpline / PCHIP each evaluated on 5000 pts
-- ---------------------------------------------------------------------------

benchInterp :: InterpKind -> String -> IO [BenchRow]
benchInterp kind label = do
  let nKnots = 1000
      nEval  = 5000
      (knots, grid) = interpData nKnots nEval
      run :: Int -> IO Double
      run _ = do
        let f = interp1d kind knots
            ys = map f grid
        return (sum ys)
      probe = id
  (ms, _) <- timeitTastyIO probe run
  return [ BenchRow "haskell" "ts_extras"
            ("Interp1D_" ++ label ++ "_knots1000_eval5000") ms 0 0
            (label ++ " interpolation knots=1000 eval=5000") ]

-- ---------------------------------------------------------------------------

main :: IO ()
main = do
  rows <- mconcat <$> sequence
    [ benchHW
    , benchGAM
    , benchInterp Linear        "Linear"
    , benchInterp NaturalSpline "NatSpline"
    , benchInterp PCHIP         "PCHIP"
    ]
  writeRows "bench/results/haskell/ts_extras.csv" rows
  putStrLn $ "wrote " ++ show (length rows)
          ++ " rows → bench/results/haskell/ts_extras.csv"