packages feed

hanalyze-0.1.0.0: demo/regression/SplineDemo.hs

{-# LANGUAGE OverloadedStrings #-}
-- | Spline 回帰のデモ (Phase N1)。
--
-- 真の関数: y = sin(2πx) + 0.3 sin(6πx)
-- これを n=80 サンプル + ノイズで観測し、B-spline (k=3) と
-- 自然立方スプラインで fit、結果を比較。
module Main where

import qualified Data.Vector as V
import qualified Numeric.LinearAlgebra as LA
import Text.Printf (printf)
import System.Random.MWC (createSystemRandom)
import qualified System.Random.MWC.Distributions as MWC

import Hanalyze.Model.Spline (SplineKind (..), fitSpline, predictSpline,
                     SplineFit (..), equalSpacedKnots)
import Hanalyze.Model.Core (rSquared1)
import Hanalyze.Viz.Core (defaultConfig, OutputFormat (..), PlotConfig (..),
                 writeSpec)
import Graphics.Vega.VegaLite

-- 真の関数
trueF :: Double -> Double
trueF x = sin (2 * pi * x) + 0.3 * sin (6 * pi * x)

main :: IO ()
main = do
  putStrLn "═══════════════════════════════════════════════════════════════"
  putStrLn "  Spline 回帰デモ (Phase N1)"
  putStrLn "═══════════════════════════════════════════════════════════════"
  putStrLn ""

  gen <- createSystemRandom
  let n = 80
      xs = V.fromList [fromIntegral i / fromIntegral (n - 1)
                       | i <- [0 .. n - 1]]
  let ysClean = V.map trueF xs
  noise <- V.replicateM n (MWC.normal 0 0.15 gen)
  let ys = V.zipWith (+) ysClean noise
  printf "観測 n=%d, 真の関数 y = sin(2πx) + 0.3 sin(6πx) + N(0, 0.15)\n" n
  putStrLn ""

  let knots = equalSpacedKnots 8 0 1
  printf "ノット (8): %s\n" (show knots)
  putStrLn ""

  let bsFit = fitSpline (BSpline 3) knots xs ys
      bsCoef = LA.toList (sfBeta bsFit)
      bsR2 = rSquared1 (sfResult bsFit)
  printf "[B-spline cubic, k=3]  係数次元 = %d, R² = %.4f\n"
         (length bsCoef) bsR2

  let ncFit = fitSpline NaturalCubic knots xs ys
      ncCoef = LA.toList (sfBeta ncFit)
      ncR2 = rSquared1 (sfResult ncFit)
  printf "[Natural cubic]        係数次元 = %d, R² = %.4f\n"
         (length ncCoef) ncR2
  putStrLn ""

  -- グリッドで予測 → 真値との RMSE
  let xGrid = V.fromList [fromIntegral i * 0.001 | i <- [0 .. 1000]]
      yGrid = V.map trueF xGrid
      yBs   = predictSpline bsFit xGrid
      yNc   = predictSpline ncFit xGrid
      rmse a b = sqrt (V.sum (V.zipWith (\u v -> (u - v)^(2::Int)) a b)
                       / fromIntegral (V.length a))
  printf "  RMSE (B-spline, vs 真値) = %.4f\n" (rmse yBs yGrid)
  printf "  RMSE (Natural,  vs 真値) = %.4f\n" (rmse yNc yGrid)
  putStrLn ""

  let cfg = (defaultConfig "Spline regression — B-spline vs Natural cubic")
              { plotWidth = 700, plotHeight = 350 }
      vlSpec = toVegaLite
        [ title (plotTitle cfg) []
        , layer
            [ asSpec
                [ dataFromColumns []
                    . dataColumn "x" (Numbers (V.toList xGrid))
                    . dataColumn "y" (Numbers (V.toList yGrid))
                    $ []
                , mark Line [MColor "#888888", MStrokeWidth 1.5,
                             MStrokeDash [4, 4]]
                , encoding
                    . position X [PName "x", PmType Quantitative]
                    . position Y [PName "y", PmType Quantitative]
                    $ []
                ]
            , asSpec
                [ dataFromColumns []
                    . dataColumn "x" (Numbers (V.toList xGrid))
                    . dataColumn "y" (Numbers (V.toList yBs))
                    $ []
                , mark Line [MColor "#1F77B4", MStrokeWidth 2.5]
                , encoding
                    . position X [PName "x", PmType Quantitative]
                    . position Y [PName "y", PmType Quantitative]
                    $ []
                ]
            , asSpec
                [ dataFromColumns []
                    . dataColumn "x" (Numbers (V.toList xGrid))
                    . dataColumn "y" (Numbers (V.toList yNc))
                    $ []
                , mark Line [MColor "#FF8C42", MStrokeWidth 2.5]
                , encoding
                    . position X [PName "x", PmType Quantitative]
                    . position Y [PName "y", PmType Quantitative]
                    $ []
                ]
            , asSpec
                [ dataFromColumns []
                    . dataColumn "x" (Numbers (V.toList xs))
                    . dataColumn "y" (Numbers (V.toList ys))
                    $ []
                , mark Point [MOpacity 0.5, MSize 25, MColor "#222222"]
                , encoding
                    . position X [PName "x", PmType Quantitative]
                    . position Y [PName "y", PmType Quantitative]
                    $ []
                ]
            ]
        , width  (plotWidth cfg)
        , height (plotHeight cfg)
        ]
  writeSpec HTML "spline.html" vlSpec
  putStrLn "  → spline.html"
  putStrLn "    真値=灰破線, B-spline=青, Natural=オレンジ, 観測=黒点"
  putStrLn ""

  putStrLn "═══════════════════════════════════════════════════════════════"
  putStrLn "  ✓ B-spline / Natural cubic spline で非線形 fit"
  putStrLn "═══════════════════════════════════════════════════════════════"