packages feed

dynobud-1.9.0.0: tests/FittingTests.hs

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}

module FittingTests
       ( fittingTests
       ) where

import Casadi.Option ( Opt(..) )
import Casadi.Overloading ( ArcTan2 )
import qualified Data.Map as M
import qualified Test.HUnit.Base as HUnit
import Test.Framework ( Test, testGroup )
import Test.Framework.Providers.HUnit ( testCase )
import Text.Printf ( printf )

import Dyno.Fitting ( l1Fit, l2Fit, lInfFit )
import Dyno.Nlp ( Bounds )
import Dyno.Solvers ( Solver, ipoptSolver )
import Dyno.TypeVecs ( Vec )
import qualified Dyno.TypeVecs as TV
import Dyno.Vectorize

toHUnit :: IO (Maybe String) -> HUnit.Assertion
toHUnit f = HUnit.assert $ do
  r <- f
  case r of
    Just msg -> return (HUnit.assertString msg)
    Nothing -> return (HUnit.assertBool "LGTM" True)

solver :: Solver
solver = ipoptSolver

-- Our data set is [1, 2, 1]
--
--  y    ^
-- 2.0 - |    *
-- 1.5 - |
-- 1.0 - | *     *
-- 0.5 - |
-- 0.0 - |
--       +------------>
--                    x
--
-- The model is f(c, x) = c
-- So the L1   minimum should be 1
--        L2   minimum should be 4/3
--        Linf minimum should be 3/2

fitModel :: Id a -> None a -> a
fitModel (Id c) None = c

qbounds :: Id Bounds
qbounds = Id (Nothing, Nothing)

gbounds :: None Bounds
gbounds = None

fitData :: Vec 3 (None Double, Double)
fitData = fmap (\x -> (None, x)) $ TV.mkVec' [1, 2, 1]

mapOptions :: M.Map String Opt
mapOptions =
  M.fromList
  [("parallelization", Opt "serial")]
  --[("parallelization", Opt "openmp")]

testFit ::
  Double
  -> (Solver
      -> (forall a . (Floating a, ArcTan2 a) => Id a -> None a -> a)
      -> (forall a . (Floating a, ArcTan2 a) => Id a -> None a)
      -> Maybe (Id Double)
      -> Id Bounds
      -> None Bounds
      -> M.Map String Opt
      -> Vec 3 (None Double, Double)
      -> IO (Either String (Id Double))
     )
  -> HUnit.Assertion
testFit expectedValue fit = toHUnit $ do
  ret <- fit solver fitModel (const None) Nothing qbounds gbounds mapOptions fitData
  return $ case ret of
    Left msg -> Just msg
    Right (Id x)
      | abs (x - expectedValue) <= 1e-9 -> Nothing
      | otherwise -> Just $ printf "expected %.4f, got %.4f" expectedValue x

fittingTests :: Test
fittingTests =
  testGroup "fitting tests"
  [ testCase "L1 fit" (testFit 1 l1Fit)
  , testCase "L2 fit" (testFit (4/3) l2Fit)
  , testCase "L-infinity fit" (testFit (3/2) lInfFit)
  ]