packages feed

sop-satisfier-0.3.4.5: tests/SystemTests.hs

import Test.Tasty
import Test.Tasty.HUnit (
  testCase,
  (@=?),
 )

import Data.Monoid (Any)
import SoPSat.Satisfier (
  SolverState,
  assert,
  declare,
  evalStatements,
  unify,
 )
import SoPSat.SoP (
  OrdRel (..),
  SoPE (..),
  (|*|),
  (|+|),
  (|-|),
  (|^|),
 )
import qualified SoPSat.SoP as SoP

type SolveTestCase = SolverState String String Bool
type SolveTestResult = Maybe Bool

type UnifyTestCase = SolverState String String [SoPE String String]
type UnifyTestResult = Maybe [SoPE String String]

equalityGiven1 :: SolveTestCase
equalityGiven1 =
  let
    one = SoP.int 1
    m = SoP.cons "m"
    n = SoP.cons "n"
    n1 = SoP.cons "n1"
   in
    do
      declare (SoPE m (n1 |+| one) EqR)
      assert (SoPE (m |+| n) (n |+| n1 |+| one) EqR)

runEqualityGiven1 :: SolveTestResult
runEqualityGiven1 = evalStatements equalityGiven1

equalityGiven2 :: SolveTestCase
equalityGiven2 =
  let
    one = SoP.int 1
    m = SoP.cons "m"
    n = SoP.cons "n"
    n1 = SoP.cons "n1"
   in
    do
      declare (SoPE m (n1 |+| one) EqR)
      assert (SoPE (m |*| n) (n |+| n |*| n1) EqR)

runEqualityGiven2 :: SolveTestResult
runEqualityGiven2 = evalStatements equalityGiven2

equalityGiven3 :: SolveTestCase
equalityGiven3 =
  let
    one = SoP.int 1
    m = SoP.cons "m"
    n = SoP.cons "n"
    n1 = SoP.cons "n1"
   in
    do
      declare (SoPE m (n1 |+| one) EqR)
      assert (SoPE (n |^| m) (n |*| n |^| n1) EqR)

runEqualityGiven3 :: SolveTestResult
runEqualityGiven3 = evalStatements equalityGiven3

transitivity :: SolveTestCase
transitivity =
  let
    i = SoP.cons "i"
    j = SoP.cons "j"
    k = SoP.cons "k"
   in
    do
      declare (SoPE i j LeR)
      declare (SoPE j k LeR)
      assert (SoPE i k LeR)

runTransitivity :: SolveTestResult
runTransitivity = evalStatements transitivity

antisymmetryZero :: SolveTestCase
antisymmetryZero =
  let
    z = SoP.int 0
    x = SoP.cons "x"
   in
    do
      declare (SoPE x z LeR)
      assert (SoPE x z EqR)

runAntisymmetryZero :: SolveTestResult
runAntisymmetryZero = evalStatements antisymmetryZero

antisymmetryNonZero :: SolveTestCase
antisymmetryNonZero =
  let
    z = SoP.int 42
    x = SoP.cons "x"
   in
    do
      declare (SoPE x z LeR)
      declare (SoPE z x LeR)
      assert (SoPE x z EqR)

runAntisymmetryNonZero :: SolveTestResult
runAntisymmetryNonZero = evalStatements antisymmetryNonZero

lemma2 :: SolveTestCase
lemma2 =
  let
    o = SoP.int 1
    j = SoP.cons "j"
    n = SoP.cons "n"
   in
    do
      declare (SoPE j n LeR)
      declare (SoPE o (n |-| j) LeR)
      assert (SoPE (o |+| j) n LeR)

runLemma2 :: SolveTestResult
runLemma2 = evalStatements lemma2

trueInEq :: SolveTestCase
trueInEq =
  let
    two = SoP.int 2
    three = SoP.int 3
    four = SoP.int 4
    x = SoP.cons "x"
    inEq1 = two |^| x |+| three |*| x |^| two |+| three
    inEq2 = x |^| three |-| two |*| x |^| two |+| four
   in
    assert (SoPE inEq2 inEq1 LeR)

runTrueInEq :: SolveTestResult
runTrueInEq = evalStatements trueInEq

falseInEq :: SolveTestCase
falseInEq =
  let
    two = SoP.int 2
    three = SoP.int 3
    four = SoP.int 4
    x = SoP.cons "x"
    inEq1 = two |^| x |+| x |^| two |+| three
    inEq2 = x |^| three |-| two |*| x |^| two |+| four
   in
    assert (SoPE inEq1 inEq2 GeR)

runFalseInEq :: SolveTestResult
runFalseInEq = evalStatements falseInEq

falseInEq2 :: SolveTestCase
falseInEq2 =
  let
    one = SoP.int 1
    m = SoP.cons "m"
    rp = SoP.cons "rp"
   in
    do
      declare (SoPE one m LeR)
      declare (SoPE m rp LeR)
      assert (SoPE one (rp |-| m) LeR)

runFalseInEq2 :: SolveTestResult
runFalseInEq2 = evalStatements falseInEq2

overlapInEq :: SolveTestCase
overlapInEq =
  let
    t = SoP.int 2
    f = SoP.int 4
    x = SoP.cons "x"
   in
    do
      declare (SoPE f x LeR)
      declare (SoPE t x LeR)
      assert (SoPE t x LeR)

runOverlapInEq :: SolveTestResult
runOverlapInEq = evalStatements overlapInEq

eqSubst :: SolveTestCase
eqSubst =
  let
    o = SoP.int 1
    x = SoP.cons "x"
    m = SoP.cons "m"
    m1 = SoP.cons "m1"
    n1 = SoP.cons "n1"
    n2 = SoP.cons "n2"
   in
    do
      declare (SoPE (x |+| o) (n1 |+| m |+| o) EqR)
      declare (SoPE m n1 EqR)
      declare (SoPE n1 (n2 |+| m1 |+| o) EqR)
      assert (SoPE (o |+| n2 |+| m1) n1 EqR)

runEqSubst :: SolveTestResult
runEqSubst = evalStatements eqSubst

eqSubst2 :: SolveTestCase
eqSubst2 =
  let
    o = SoP.int 1
    t = SoP.int 2
    y = SoP.cons "y"
    x = SoP.cons "x"
   in
    do
      declare (SoPE o y LeR)
      declare (SoPE (o |+| x) (t |*| y) EqR)
      assert (SoPE (t |*| y |-| o) x EqR)

runEqSubst2 :: SolveTestResult
runEqSubst2 = evalStatements eqSubst2

multistep :: SolveTestCase
multistep =
  let
    o = SoP.int 1
    t = SoP.int 2
    n1 = SoP.cons "n1"
    n2 = SoP.cons "n2"
   in
    do
      declare (SoPE o n1 LeR)
      declare (SoPE (t |*| n2) n1 EqR)
      r1 <- assert (SoPE (t |*| n2 |-| o) (n1 |-| o) EqR)
      r2 <- assert (SoPE o n2 LeR)
      return (r1 && r2)

runMultistep :: SolveTestResult
runMultistep = evalStatements multistep

multistep2 :: SolveTestCase
multistep2 =
  let
    o = SoP.int 1
    m = SoP.cons "m"
    n = SoP.cons "n"
    n1 = SoP.cons "n1"
    n2 = SoP.cons "n2"
   in
    do
      declare (SoPE m (n1 |+| o) EqR)
      declare (SoPE (m |+| n) (n2 |+| o) EqR)
      assert (SoPE (n1 |+| n) n2 EqR)

runMultistep2 :: SolveTestResult
runMultistep2 = evalStatements multistep2

step3 :: SolveTestCase
step3 =
  let
    o = SoP.int 1
    t = SoP.int 2
    m = SoP.cons "m"
    n = SoP.cons "n"
    n1 = SoP.cons "n1"
   in
    do
      declare (SoPE (o |+| n) m EqR)
      declare (SoPE (o |+| n1) m EqR)
      r1 <- assert (SoPE n n1 EqR)
      r2 <- assert (SoPE (t |+| t |*| n) (t |*| m) EqR)
      return (r1 && r2)

runStep3 :: SolveTestResult
runStep3 = evalStatements step3

implication :: SolveTestCase
implication =
  let
    t = SoP.int 2
    m = SoP.cons "m"
    n = SoP.cons "n"
   in
    do
      declare (SoPE t (t |^| (n |+| m)) LeR)
      assert (SoPE t (t |^| (m |+| n)) LeR)

runImplication :: SolveTestResult
runImplication = evalStatements implication

func1 :: SolveTestCase
func1 =
  let
    a = SoP.cons "a"
    b = SoP.cons "b"
    c = SoP.cons "c"
    eq1 = a |+| SoP.func "max" [a |+| b, c]
    eq2 = SoP.func "max" [b |+| a, c] |+| a
   in
    assert (SoPE eq1 eq2 EqR)

runFunc1 :: SolveTestResult
runFunc1 = evalStatements func1

func2 :: SolveTestCase
func2 =
  let
    a = SoP.cons "a"
    b = SoP.cons "b"
    c = SoP.cons "c"
    eq1 = a |+| SoP.func "bar" [a |+| b, c]
    eq2 = SoP.func "bar" [c, b |+| a] |+| a
   in
    assert (SoPE eq1 eq2 EqR)

runFunc2 :: SolveTestResult
runFunc2 = evalStatements func2

func3 :: SolveTestCase
func3 =
  let
    a = SoP.cons "a"
    b = SoP.cons "b"
    c = SoP.cons "c"
   in
    assert (SoPE (SoP.func "foo" [a, b, a |^| c]) a GeR)

runFunc3 :: SolveTestResult
runFunc3 = evalStatements func3

func4 :: SolveTestCase
func4 =
  let
    x = SoP.cons "x"
    g = SoP.func "g" [x]
    f = SoP.func "f" [x]
   in
    do
      declare (SoPE (SoP.int 1) g LeR)
      assert (SoPE f (f |*| g) LeR)

runFunc4 :: SolveTestResult
runFunc4 = evalStatements func4

unifyExp :: UnifyTestCase
unifyExp =
  let
    t = SoP.int 2
    x1 = SoP.cons "x1"
    x2 = SoP.cons "x2"
   in
    do
      unify (SoPE ((t |^| x1) |*| (t |^| (x1 |+| x1))) ((t |^| x2) |*| (t |^| (x2 |+| x2))) EqR)

runUnifyExp :: UnifyTestResult
runUnifyExp = evalStatements unifyExp

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests =
  testGroup
    "lib-tests"
    [ testGroup
        "Equality tests"
        [ testGroup
            "True"
            [ testCase "m = n1 + 1 implies n + m = n + n1 + 1" $
                Just True @=? runEqualityGiven1
            , testCase "m = n1 + 1 implies n * m = n + n * n1" $
                Just True @=? runEqualityGiven2
            , testCase "m = n1 + 1 implies n^m = n*n^n1" $
                Just True @=? runEqualityGiven3
            , testCase "n + 1 = n1 + m + 1 and m = n1 and n1 = n2 + m1 + 1 implies 1 + n2 + m1 = n1" $
                Just True @=? runEqSubst
            , testCase "1 <= y and x + 1 = 2 * y implies 2 * y - 1 = x" $
                Just True @=? runEqSubst2
            , testCase "Combined: 1 <= m and 2 * n = m implies 2 * n - 1 = m - 2 and 1 <= m" $
                Just True @=? runMultistep
            , testCase "Multistep: m = n1 + 1 and m + n = n2 + 1 implies n1 + n = n2" $
                Just True @=? runMultistep2
            , testCase "1 + a = c and 1 + b = c implies a = b and 2 + 2 * a = 2 * c" $
                Just True @=? runStep3
            , testGroup
                "Functions"
                [ testCase "a + max(a + b, c) = max(b + a, c) + a" $
                    Just True @=? runFunc1
                ]
            , testGroup
                "Natural numbers"
                [ testCase "m = n - 1 implies m + 1 = n" $
                    Nothing
                      @=? evalStatements
                        ( declare (SoPE (SoP.cons "m") (SoP.cons "n" |-| SoP.int 1) EqR)
                            >> assert (SoPE (SoP.cons "m" |+| SoP.int 1) (SoP.cons "n") EqR) ::
                            SolveTestCase
                        )
                ]
            ]
        , testGroup
            "False"
            [ testCase "x + 2 /= x + 3" $
                Just False
                  @=? evalStatements
                    ( assert
                        (SoPE (SoP.cons "x" |+| SoP.int 2) (SoP.cons "x" |+| SoP.int 3) EqR) ::
                        SolveTestCase
                    )
            , testCase "8 /= x + x + x" $
                Just False
                  @=? evalStatements
                    ( assert
                        (SoPE (SoP.int 3 |*| SoP.cons "x") (SoP.int 8) EqR) ::
                        SolveTestCase
                    )
            , testCase "7 /= 2*y+4" $
                Just False
                  @=? evalStatements
                    ( assert
                        (SoPE (SoP.int 2 |*| SoP.cons "y" |+| SoP.int 4) (SoP.int 7) EqR) ::
                        SolveTestCase
                    )
            , testGroup
                "Functions"
                [ testCase "a + bar(a + b, c) /= bar(c, b + a) + a" $
                    Just False @=? runFunc2
                ]
            ]
        ]
    , testGroup
        "Inequality tests"
        [ testGroup
            "True"
            [ testCase "Transitivity: i <= j and j <= k implies i <= k" $
                Just True @=? runTransitivity
            , testCase "Antisymmetry with zero: x is Natural and x <= 0 implies x = 0" $
                Just True @=? runAntisymmetryZero
            , testCase "Antisymmetry with non-zero: x <= 5 and x >= 5 implies x = 5" $
                Just True @=? runAntisymmetryNonZero
            , testCase "Strongly greater: j <= n and 1 <= n - j imples 1 + j <= n" $
                Just True @=? runLemma2
            , testCase "Composite function: x^3-2x^2+4<=2^x+3x^2+3" $
                Just True @=? runTrueInEq
            , testCase "Overlapping ranges: 4 <= x implies 2 <= x" $
                Just True @=? runOverlapInEq
            , testGroup
                "Functions"
                [ testCase "g(x) >= 1 implies f(x) <= g(x) * f(x)" $
                    Just True @=? runFunc4
                ]
            , testGroup
                "Trivial"
                [ testCase "a <= a + 1" $
                    Just True
                      @=? evalStatements
                        ( assert
                            (SoPE (SoP.cons "a") (SoP.cons "a" |+| SoP.int 1) LeR) ::
                            SolveTestCase
                        )
                , testCase "1 <= 2^a" $
                    Just True
                      @=? evalStatements
                        ( assert
                            (SoPE (SoP.int 1) (SoP.int 2 |^| SoP.cons "a") LeR) ::
                            SolveTestCase
                        )
                , testCase "2 <= 2^(n + m) implies 2 <= 2^(m + n)" $
                    Just True @=? runImplication
                ]
            , testGroup
                "Implications"
                [ testCase "a = b - 1 implies b >= 1" $
                    Nothing
                      @=? evalStatements
                        ( declare
                            (SoPE (SoP.cons "a") (SoP.cons "b" |-| SoP.int 1) EqR)
                            >> assert (SoPE (SoP.cons "b") (SoP.int 1) GeR) ::
                            SolveTestCase
                        )
                ]
            ]
        , testGroup
            "False"
            [ testCase "Composite function x^3-2x^2+4<=2^x+x^2+3" $
                Just False @=? runFalseInEq
            , testCase "1 <= m and m <= rp implies 1 <= rp - m" $
                Just False @=? runFalseInEq2
            , testCase "4a <= 2a" $
                Just False
                  @=? evalStatements
                    ( assert
                        (SoPE (SoP.int 4 |*| SoP.cons "a") (SoP.int 2 |*| SoP.cons "a") LeR) ::
                        SolveTestCase
                    )
            , testCase "foo(a, b, a ^ c) >= a" $
                Just False @=? runFunc3
            ]
        ]
    , testGroup
        "Ranges"
        []
    , -- TODO: Add test cases for range narrowing consistency

      testGroup
        "Unifiers"
        [ testCase "x = x always holds" $
            Just []
              @=? evalStatements (unify (SoPE (SoP.cons "x") (SoP.cons "x") EqR) :: UnifyTestCase)
        , testCase "t = a + b does not produce unifiers" $
            Just []
              @=? evalStatements
                ( unify (SoPE (SoP.cons "t") (SoP.cons "a" |+| SoP.cons "b") EqR) ::
                    UnifyTestCase
                )
        , testCase "a + b = a + c if b = c" $
            Just [SoPE (SoP.cons "b") (SoP.cons "c") EqR]
              @=? evalStatements
                ( unify
                    ( SoPE
                        (SoP.cons "a" |+| SoP.cons "b")
                        (SoP.cons "a" |+| SoP.cons "c")
                        EqR
                    ) ::
                    UnifyTestCase
                )
        , testCase "n = n + d" $
            Just []
              @=? evalStatements
                ( unify
                    ( SoPE
                        (SoP.cons "n")
                        (SoP.cons "n" |+| SoP.cons "d")
                        EqR
                    ) ::
                    UnifyTestCase
                )
        , testCase "c = n implies n = n + d never holds" $
            Just []
              @=? evalStatements
                ( declare (SoPE (SoP.cons "c") (SoP.cons "n") EqR)
                    >> unify
                      ( SoPE
                          (SoP.cons "n" |+| SoP.cons "d")
                          (SoP.cons "n")
                          EqR
                      ) ::
                    UnifyTestCase
                )
        , testCase "9 = x + x + x if x = 3" $
            Just [SoPE (SoP.cons "x") (SoP.int 3) EqR]
              @=? evalStatements
                ( unify
                    (SoPE (SoP.int 3 |*| SoP.cons "x") (SoP.int 9) EqR) ::
                    UnifyTestCase
                )
        , testCase "6 = 2 * y + 4 if x = 1" $
            Just [SoPE (SoP.cons "y") (SoP.int 1) EqR]
              @=? evalStatements
                ( unify
                    (SoPE (SoP.int 2 |*| SoP.cons "y" |+| SoP.int 4) (SoP.int 6) EqR) ::
                    UnifyTestCase
                )
        , testCase "8 /= x + x + x never holds" $
            Just []
              @=? evalStatements
                ( unify
                    (SoPE (SoP.int 3 |*| SoP.cons "x") (SoP.int 8) EqR) ::
                    UnifyTestCase
                )
        , testCase "7 /= 2*y+4 never holds" $
            Just []
              @=? evalStatements
                ( unify
                    (SoPE (SoP.int 2 |*| SoP.cons "y" |+| SoP.int 4) (SoP.int 7) EqR) ::
                    UnifyTestCase
                )
        , testCase "a^b = a^c if b = c" $
            Just [SoPE (SoP.cons "b") (SoP.cons "c") EqR]
              @=? evalStatements
                ( unify
                    ( SoPE
                        (SoP.cons "a" |^| SoP.cons "b")
                        (SoP.cons "a" |^| SoP.cons "c")
                        EqR
                    ) ::
                    UnifyTestCase
                )
        , testCase "2^x1 * 2^(x1 + x1) = 2^x2 * 2^(x2 + x2) holds if x1 = x2" $
            Just [SoPE (SoP.cons "x1") (SoP.cons "x2") EqR] @=? runUnifyExp
        ]
    ]