Skip to content
Snippets Groups Projects
Test.hs 7.59 KiB
Newer Older
  • Learn to ignore specific revisions
  • module LogicIR.Backend.QuickCheck.Test ( testEquality
                                           , LExprTeacher
                                           , LExprStudent
                                           , FeedbackCount
                                           , equal
                                           ) where
    
    
    import LogicIR.Expr
    import LogicIR.Fold
    import LogicIR.Eval
    
    import LogicIR.Backend.QuickCheck.ModelGenerator
    import Data.Maybe (fromMaybe, isNothing, fromJust)
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    import Data.List (find, (\\))
    
    type LExprTeacher = LExpr
    type LExprStudent = LExpr
    
    -- | Contains how often any type of feedback occured over x runs (where x is
    --   obviously the sum of the four integers). Order of Integers in tuple
    --   is the same as in Model.SingleFeedback
    type FeedbackCount = ( Int -- T-T
                         , Int -- T-F
                         , Int -- F-T
                         , Int -- F-F
                         )
    
    defFbCount :: FeedbackCount
    defFbCount = (0,0,0,0)
    
    add :: FeedbackCount -> FeedbackCount -> FeedbackCount
    add (w,x,y,z) (w',x',y',z') = (w+w',x+x',y+y',z+z')
    
    -- | Converts two Bools to FeedbackCount. First Bool is the Bool you get when
    --   the TeacherLExpr was evaluated using some Model+ArrayModel m, the second
    --   one the bool you get when you evaluate te StudentLExpr using m.
    toFeedbackCount :: Bool -> Bool -> FeedbackCount
    toFeedbackCount True  True  = (1,0,0,0)
    toFeedbackCount True  False = (0,1,0,0)
    toFeedbackCount False True  = (0,0,1,0)
    toFeedbackCount False False = (0,0,0,1)
    
    -- | Returns True if, on we have 0 TF or FT occurences (in other words: occurences
    --   where the teacher and student specification gave a different result)
    equal :: FeedbackCount -> Bool
    equal (_,tf,ft,_) = tf == 0 && ft == 0
    
    -- Calls 'test' multiple times.
    
    testEquality :: Int -> LExprTeacher -> LExprStudent -> IO (FeedbackCount, (Model, ArrayModel))
    testEquality 0 _ _ = error "testEquality won't work with 0 iterations."
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    testEquality x e1 e2 = do
    
      results <- mapM (const (test e1 e2)) [1..x]
      let feedback = foldr add defFbCount (map fst results) -- sum of feedbacks
      -- get list of counter models where
      let counters = map snd (filter (not . equal . fst) results)
      if null counters then
        return (feedback, ([], M.empty))
      else
        return (feedback, head counters)
    
    test :: LExprTeacher -> LExprStudent -> IO (FeedbackCount, (Model, ArrayModel))
    test e1 e2 = do
        ((CBool res1),(m,arrM,_)) <- testLExpr e1
        evalInfo'                 <- expandEvalInfo e2 (m, arrM)
        (CBool res2)              <- testModel e2 evalInfo'
        let (primitivesModel, arrayModel, _) = evalInfo'
        let fb = toFeedbackCount res1 res2
        return (fb, (primitivesModel, arrayModel))
    
    -- Given an LExpr, generates a substitution model for all variables in it,
    -- and returns whether the formula evaluates to true or to false when that model
    -- is used, and the model itself. If the formula, after substitution, cannot
    -- be evaluated - i.e. there's still a variable in the LExpr - an error will be thrown.
    
    testLExpr e = do
    
        (primitivesModel, arrayModel, quantVs) <- generateEvalInfo e
    
        testResult       <- testModel e (primitivesModel, arrayModel, quantVs)
        let evalInfo      = (primitivesModel, arrayModel, quantVs)
        return (testResult, evalInfo)
    
    testModel :: LExpr -> EvalInfo -> IO LConst
    testModel e evalInfo = do
        let substitutedE = applyModel e evalInfo
    
        if evalPossible substitutedE then do
            let res = eval substitutedE
    
    -- Applies substitution to an LExpr given the necessary EvalInfo.
    applyModel :: LExpr -> EvalInfo -> LExpr
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    applyModel e evalInfo = foldLExpr (LAlgebra fcns fvar funi fbin fiff fqnt farr fnll flen) e
        where fcns            = LConst
              funi op x       = evalIfPossible $ LUnop op x
              fbin le op re   = evalIfPossible $ LBinop le op re
              fiff ge e1 e2   = evalIfPossible $ LIf ge e1 e2
              fvar x          = subst (LVar x) evalInfo
              fqnt op x e1 e2 = subst (LQuant op x (subst e1 evalInfo) (subst e2 evalInfo)) evalInfo
              farr x i        = subst (LArray x i) evalInfo
              fnll x          = subst (LIsnull x) evalInfo
              flen x          = subst (LLen x) evalInfo
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    get :: (Ord a, Show b, Show a) => a -> M.Map a [b] -> [b]
    get a model =
      fromMaybe
        (error $ "Impossible substitution of Array expression. Array: " ++ show a ++ ", ArrayModel: " ++ show model)
        (M.lookup a model)
    
    
    -- Substitutes an LExpr if a valid subst can be found in the given models.
    -- Otherwise simply returns the input expression as output.
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    subst e@(LArray x indexE) (_,arrayModel,_)
    
              let (CInt index) = eval indexE
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
              let a            = get x arrayModel
              let l          = length a
              if index < l && index >= 0 then
    
                  LConst $ a !! index
              else
                  e -- Just return the input LExpr, since the index is out of the domain.
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
          else
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    subst q@(LQuant qop x domain e) evalInfo = do
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
        let results = evalQuantifier x domain e evalInfo
    
            LConst $ CBool $ op $ map fromJust results
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    subst (LLen x) (_, arrayModel, _) = LConst (CInt (length (get x arrayModel)))
    subst x (model,_,_)               = maybe x snd $ find (\(old, _) -> x == old) model
    
    
    -- Returns, given a domain LExpr that concerns a certain Var, all
    -- Ints that fall in that domain.
    
    evalQuantifier :: Var -> LExpr -> LExpr -> EvalInfo -> [Maybe Bool]
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    evalQuantifier x domain e (m1,m2,quantVs) = do
    
        -- Check which integers in domain [-20,20] fall in the domain of the
        -- quantifier and evaluate the quantifier with those integers.
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
        let newM1s = map (\i -> (LVar x, LConst (CInt i)) : m1) [-20..20]
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
        where isTrue expr = eval expr == CBool True
    
                  let newEvalInfo = (newM1,m2,quantVs)
                  let newDomain = applyModel domain newEvalInfo
    
                  if evalPossible newDomain then
                      if isTrue newDomain then do
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
                          if evalPossible newE && isTrue newE then
                            [Just True]
                          else
                            -- This can happen if newDomain is true but the actual quantifier expression can not be evaluated.
                            -- e.g.   a.length==1 && forall 0<i<2: a[i]<a[i+1] (here, a[i+1] == 2, but a is only 1 in length)
                            -- We view such quantifier iterations as failures, i.e. False.
                            [Just False]
    
                      else
                          [] -- domain expression is false, so we skip the iteration.
                  else do
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
                      let (primitives,(_,_),_) = findVariables newDomain
    
                      -- If null == True, then there is a primitive in the newDomain that is bound by a parent/ancestor quantifier.
                      if null (primitives \\ quantVs) then
                          -- newDomain could not be evaluated because it is nested in another quantifier that has
                          -- not been evaluated yet. Therefore, we just wait with the evaluation of this quantifier.
                          [Nothing]
    
                          -- newDomain could not be evaluated because there are variables in it that should have been
                          -- substituted by now. This should never happen.
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
                          error $ "Quantifier domain could not be evaluated. This should never happen. " ++ show newDomain