packages feed

symtegration-0.6.1: test/Symtegration/Symbolic/Simplify/NumericFoldingSpec.hs

-- |
-- Description: Tests Symtegration.Symbolic.Simplify.NumericFolding
-- Copyright: Copyright 2024 Yoo Chung
-- License: Apache-2.0
-- Maintainer: dev@chungyc.org
module Symtegration.Symbolic.Simplify.NumericFoldingSpec (spec) where

import Data.Text (unpack)
import Symtegration.Symbolic
import Symtegration.Symbolic.Arbitrary
import Symtegration.Symbolic.Haskell
import Symtegration.Symbolic.Simplify.NumericFolding
import Symtegration.Symbolic.Simplify.Properties
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck

spec :: Spec
spec = parallel $ do
  describe "simplify" $ do
    modifyMaxSuccess (* 100) $
      prop "maintains semantics" $
        equivalentProperty simplify

    modifyMaxSuccess (* 100) $
      prop "folds to simple numeric expressions" $
        forAll genNumeric $ \e ->
          simpleNumeric e ==>
            let e' = simplify e
             in counterexample ("e = " <> unpack (toHaskell e)) $
                  counterexample ("simplify e = " <> unpack (toHaskell e')) $
                    e' `shouldSatisfy` simpleNumeric

-- Numeric folding should be able to fold arithmetic on numbers
-- to either an integer or a fraction.  The exception is if there
-- is a divide by zero somewhere, which we intentionally leave alone.
simpleNumeric :: Expression -> Bool
simpleNumeric (Number _) = True
simpleNumeric (Number _ :/: Number _) = True
simpleNumeric x = hasDivideByZero x
  where
    hasDivideByZero (_ :/: 0) = True
    hasDivideByZero (UnaryApply _ u) = hasDivideByZero u
    hasDivideByZero (BinaryApply _ u v) = hasDivideByZero u || hasDivideByZero v
    hasDivideByZero _ = False

-- | Generate arbitrary expression involving no symbols and which are
-- guaranteed to reduce exactly to a simple numeric term.
genNumeric :: Gen Expression
genNumeric = sized $ \case
  0 -> arbitraryNumber
  n ->
    frequency
      [ (1, arbitraryNumber),
        (1, resize (max 0 (n - 1)) $ UnaryApply Negate <$> genNumeric),
        ( 1,
          resize (max 0 (n - 1)) $
            BinaryApply Power <$> genNumeric <*> (Number <$> arbitrarySizedNatural `suchThat` (/= 0))
        ),
        ( 10,
          resize (n `div` 2) $
            BinaryApply <$> elements [Add, Multiply, Subtract, Divide] <*> genNumeric <*> genNumeric
        )
      ]