Hook in and improve linear relations.

There is still an issue with the encoding of finitness.
For example, if we know:

a = 1 + min b a

And we know that `b`z is finite, we SHOULD know that `a` is finite too,
but apparently we don't.
This commit is contained in:
Iavor S. Diatchki 2015-02-24 18:19:01 -08:00
parent e70e1153fb
commit ce09d23d74
4 changed files with 224 additions and 102 deletions

View File

@ -109,7 +109,7 @@ proveImplicationIO lname as ps gs =
-- XXX: Do we need the su? -- XXX: Do we need the su?
-- XXX: Minimize the goals invovled in the conflict -- XXX: Minimize the goals invovled in the conflict
(us,su2) -> (us,su2) ->
do debugBlock s "2nd su:" (debugLog s su2) do debugBlock s "FAILED: 2nd su:" (debugLog s su2)
return $ Left return $ Left
$ UnsolvedDelcayedCt $ UnsolvedDelcayedCt
$ DelayedCt { dctSource = lname $ DelayedCt { dctSource = lname
@ -134,7 +134,8 @@ numericRight g = case Num.exportProp (goal g) of
_testSimpGoals :: IO () _testSimpGoals :: IO ()
_testSimpGoals = Num.withSolver $ \s -> _testSimpGoals = Num.withSolver $ \s ->
do mb <- simpGoals s gs do Num.assumeProps s asmps
mb <- simpGoals s gs
case mb of case mb of
Just (gs1,su) -> Just (gs1,su) ->
do debugBlock s "Simplified goals" do debugBlock s "Simplified goals"
@ -142,8 +143,9 @@ _testSimpGoals = Num.withSolver $ \s ->
debugLog s (show (pp su)) debugLog s (show (pp su))
Nothing -> debugLog s "Impossible" Nothing -> debugLog s "Impossible"
where where
gs = map fakeGoal [ tv 1 .*. tv 2 >== tv 1 .*. tv 2 ] asmps = [ pFin (tv 1) ]
gs = map fakeGoal [ tv 0 =#= (num 1 .+. tMin (tv 1) (tv 0)) ]
-- ?g4 == 1 + min m ?g4
-- [ tv 0 =#= tInf, tMod (num 3) (tv 0) =#= num 4 ] -- [ tv 0 =#= tInf, tMod (num 3) (tv 0) =#= num 4 ]

View File

@ -11,6 +11,8 @@ module Cryptol.TypeCheck.Solver.CrySAT
, DebugLog(..) , DebugLog(..)
) where ) where
import Cryptol.Utils.Debug(trace)
import qualified Cryptol.TypeCheck.AST as Cry import qualified Cryptol.TypeCheck.AST as Cry
import Cryptol.TypeCheck.PP(pp) import Cryptol.TypeCheck.PP(pp)
import Cryptol.TypeCheck.InferTypes(Goal(..)) import Cryptol.TypeCheck.InferTypes(Goal(..))
@ -28,14 +30,16 @@ import MonadLib
import Data.Maybe ( mapMaybe, fromMaybe ) import Data.Maybe ( mapMaybe, fromMaybe )
import Data.Map ( Map ) import Data.Map ( Map )
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Foldable ( any, all )
import Data.Traversable ( traverse ) import Data.Traversable ( traverse )
import Data.Set ( Set ) import Data.Set ( Set )
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.IORef ( IORef, newIORef, readIORef, modifyIORef', import Data.IORef ( IORef, newIORef, readIORef, modifyIORef',
atomicModifyIORef' ) atomicModifyIORef' )
import Prelude hiding (any,all)
import qualified SimpleSMT as SMT import qualified SimpleSMT as SMT
import Text.PrettyPrint(Doc) import Text.PrettyPrint(Doc,vcat,text)
-- | We use this to rememebr what we simplified -- | We use this to rememebr what we simplified
newtype SimpProp = SimpProp Prop newtype SimpProp = SimpProp Prop
@ -44,24 +48,24 @@ simpProp :: Prop -> SimpProp
simpProp p = SimpProp (crySimplify p) simpProp p = SimpProp (crySimplify p)
-- | 'dpSimpProp' and 'dpSimpExprProp' should be logicaly equivalent,
-- to each other, and to whatever 'a' represenets (usually 'a' is a 'Goal').
data DefinedProp a = DefinedProp data DefinedProp a = DefinedProp
{ dpData :: a { dpData :: a
-- ^ Optional data to assocate with prop. -- ^ Optional data to assocate with prop.
-- Often, the origianl `Goal` from which the prop was extracted. -- Often, the origianl `Goal` from which the prop was extracted.
, dpSimpProp :: SimpProp , dpSimpProp :: SimpProp
-- ^ Fully simplified (may have ORs) {- ^ Fully simplified: may mention ORs, and named non-linear terms.
-- These are used in the proofs, and may not be translatable back These are what we send to the prover, and we don't attempt to
-- into Cryptol. convert them back into Cryptol types. -}
, dpSimpExprProp :: Prop , dpSimpExprProp :: Prop
-- ^ Expressions are simplified (no ORs) {- ^ A version of the proposition where just the expression terms
-- These should be importable back into Cryptol props. have been simplified. These should not contain ORs or named non-linear
-- XXX: What about `sys` variables? terms because we want to import them back into Crytpol types. -}
} }
type ImpMap = Map Name Expr
{- | Check if a collection of things are defined. {- | Check if a collection of things are defined.
It does not modify the solver's state. It does not modify the solver's state.
@ -124,6 +128,7 @@ checkDefined s updCt uniVars props0 = withScope s (go Map.empty [] props0)
Nothing -> (g,p) Nothing -> (g,p)
Just p' -> (updCt p' g,p') Just p' -> (updCt p' g,p')
-- Try to prove something by unification (e.g., ?x = a * b)
findImpByDef _ [] = Nothing findImpByDef _ [] = Nothing
findImpByDef qs (p : ps) = findImpByDef qs (p : ps) =
case improveByDefn uniVars p of case improveByDefn uniVars p of
@ -427,8 +432,8 @@ getNLSubst Solver { .. } =
-- | Execute a computation with a fresh solver instance. -- | Execute a computation with a fresh solver instance.
withSolver :: (Solver -> IO a) -> IO a withSolver :: (Solver -> IO a) -> IO a
withSolver k = withSolver k =
do -- logger <- SMT.newLogger do logger <- SMT.newLogger
let logger = quietLogger -- let logger = quietLogger
solver <- SMT.newSolver "cvc4" [ "--lang=smt2" solver <- SMT.newSolver "cvc4" [ "--lang=smt2"
, "--incremental" , "--incremental"
@ -524,40 +529,38 @@ check s@Solver { .. } uniVars =
case res of case res of
SMT.Unsat -> return (False, Map.empty) SMT.Unsat -> return (False, Map.empty)
SMT.Unknown -> return (True, Map.empty) SMT.Unknown -> return (True, Map.empty)
SMT.Sat -> debugBlock s "improvements" (getImpSubst s uniVars) SMT.Sat ->
do impMap <- debugBlock s "improvements" (getImpSubst s uniVars)
return (True, impMap)
-- | The set of unification variables is used to guide ordering of {- | The set of unification variables is used to guide ordering of
-- assignments (we prefer assigning to them, as that amounts to doing assignments (we prefer assigning to them, as that amounts to doing
-- type inference). type inference).
getImpSubst :: Solver -> Set Name -> IO (Bool,Subst)
Returns an improving substitution, which may mention the names of
non-linear terms.
-}
getImpSubst :: Solver -> Set Name -> IO Subst
getImpSubst s@Solver { .. } uniVars = getImpSubst s@Solver { .. } uniVars =
do names <- viUnmarkedNames `fmap` readIORef declared do names <- viUnmarkedNames `fmap` readIORef declared
m <- fmap Map.fromList (mapM getVal names) m <- fmap Map.fromList (mapM getVal names)
model <- cryImproveModel solver uniVars m impSu <- cryImproveModel solver uniVars m
nlSu <- getNLSubst s
let su = composeSubst nlSu (toSubst model)
-- XXX: The improvemnts that are being ignored here could let isNonLinName (SysName {}) = True
-- lead to extar, potentially useful, constraints. isNonLinName (UserName {}) = False
su1 = Map.filterWithKey (\k _ -> case k of
SysName {} -> False
_ -> True) su
return (True, su1)
{-
let imps = Map.toList (toSubst model)
debugBlock s "before" $
mapM_ (\(x,e) -> debugLog s (Var x :== e)) imps
res <- mapM (checkImpBind s) imps
let (agains,eqs) = unzip res
debugBlock s "after" $ keep k e = not (isNonLinName k) &&
mapM_ (\(x,e) -> debugLog s (Var x :== e)) (concat eqs) all (not . isNonLinName) (cryExprFVS e)
(easy,tricky) = Map.partitionWithKey keep impSu
dump (x,e) = debugLog s (show (ppProp (Var x :== e)))
when (not (Map.null tricky)) $
debugBlock s "Tricky subst" $ mapM_ dump (Map.toList tricky)
return easy
(possible,more) <- if or agains then check s uniVars
else return (True,Map.empty)
return (possible, Map.union more (Map.fromList (concat eqs)))
-}
where where
getVal a = getVal a =
do yes <- isInf a do yes <- isInf a
@ -575,19 +578,11 @@ getImpSubst s@Solver { .. } uniVars =
[ "Not a boolean value", show yes ] [ "Not a boolean value", show yes ]
-- If subst contains:
-- x := e(_y) (where _y = e', a NL term)
-- we should replace `x` with `e(e'/_y)`, not just e
-- If subst contains:
-- _y := e (where _y = e1, a NL term)
-- we get that a new equations should always hold: e = e'
-- this is "derived" fact in GHC parlance: if we notice that it is
-- impossible, then we can be sure
-- XXX: NOT QUITE, I THINK.
-- | Given a computed improvement `x = e`, check to see if we can get -- | Given a computed improvement `x = e`, check to see if we can get
-- some additional information by interacting with the non-linear assignment. -- some additional information by interacting with the non-linear assignment.
checkImpBind :: Solver -> (Name, Expr) -> IO (Bool, [(Name,Expr)]) checkImpBind :: Solver -> (Name, Expr) -> IO (Bool, [(Name,Expr)])
@ -666,26 +661,6 @@ checkImpBind s (x,e) =
-- | Convert a bunch of improving equations into an idempotent substitution.
-- Assumes that the equations don't have loops.
toSubst :: ImpMap -> Subst
toSubst m =
case normMap (apSubst m) m of
Nothing -> m
Just m1 -> toSubst m1
-- | Apply a function to all elements of a map. Returns `Nothing` if nothing
-- changed, and @Just new_map@ otherwise.
normMap :: (a -> Maybe a) -> Map k a -> Maybe (Map k a)
normMap f m = mk $ runId $ runStateT False $ traverse upd m
where
mk (a,changes) = if changes then Just a else Nothing
upd x = case f x of
Just y -> set True >> return y
Nothing -> return x
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
debugBlock :: Solver -> String -> IO a -> IO a debugBlock :: Solver -> String -> IO a -> IO a

View File

@ -42,8 +42,15 @@ nonLinProp s0 prop = runNL s0 (nonLinPropM prop)
{- | Apply a substituin to the non-linear expression database. {- | Apply a substituin to the non-linear expression database.
Returns `Nothing` if nothing was affected. Returns `Nothing` if nothing was affected.
Otherwise returns `Just`, and a substitutuin for non-linear expressions Otherwise returns `Just`, and a substitution for non-linear expressions
that became linear. -} that became linear.
The definitions of NL terms do not contain other named NL terms,
so it does not matter if the substitution contains bindings like @_a = e@.
There should be no bindings that mention NL term names in the definitions
of the substition (i.e, things like @x = _a@ are NOT ok).
-}
apSubstNL :: Subst -> NonLinS -> Maybe (Subst, NonLinS) apSubstNL :: Subst -> NonLinS -> Maybe (Subst, NonLinS)
apSubstNL su s0 = case runNL s0 (mApSubstNL su) of apSubstNL su s0 = case runNL s0 (mApSubstNL su) of
(Nothing,_) -> Nothing (Nothing,_) -> Nothing
@ -81,7 +88,7 @@ Otherwise returns `Just`, and a substituion mapping names that used
to be non-linear but became linear. to be non-linear but became linear.
Note that we may return `Just empty`, indicating that some non-linear Note that we may return `Just empty`, indicating that some non-linear
expressions were update, but they remained non-linear. -} expressions were updated, but they remained non-linear. -}
mApSubstNL :: Subst -> NonLinM (Maybe Subst) mApSubstNL :: Subst -> NonLinM (Maybe Subst)
mApSubstNL su = mApSubstNL su =
do s <- get do s <- get

View File

@ -10,16 +10,18 @@ module Cryptol.TypeCheck.Solver.Numeric.SMT
import Cryptol.TypeCheck.Solver.InfNat import Cryptol.TypeCheck.Solver.InfNat
import Cryptol.TypeCheck.Solver.Numeric.AST import Cryptol.TypeCheck.Solver.Numeric.AST
import Cryptol.TypeCheck.Solver.Numeric.Simplify(crySimplify)
import Cryptol.Utils.Misc ( anyJust )
import Cryptol.Utils.Panic ( panic ) import Cryptol.Utils.Panic ( panic )
import Control.Monad ( ap, guard ) import Data.List ( partition, unfoldr )
import Data.List ( partition )
import Data.Map ( Map ) import Data.Map ( Map )
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Set ( Set ) import Data.Set ( Set )
import qualified Data.Set as Set import qualified Data.Set as Set
import SimpleSMT ( SExpr ) import SimpleSMT ( SExpr )
import qualified SimpleSMT as SMT import qualified SimpleSMT as SMT
import MonadLib
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -113,7 +115,7 @@ propToSmtLib prop =
_ :> _ -> unexpected _ :> _ -> unexpected
where where
unexpected = panic "desugarProp" [ show (ppProp prop) ] unexpected = panic "propToSmtLib" [ show (ppProp prop) ]
fin x = SMT.const (smtFinName x) fin x = SMT.const (smtFinName x)
addFin e = foldr (\x' e' -> SMT.and (fin x') e') e addFin e = foldr (\x' e' -> SMT.and (fin x') e') e
@ -160,11 +162,43 @@ smtFinName x = "fin_" ++ show (ppName x)
-- Models -- Models
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Get the value for the given variable.
-- Assumes that we are in a SAT state.
getVal :: SMT.Solver -> Name -> IO Expr
getVal s a =
do yes <- isInf a
if yes
then return (K Inf)
else do v <- SMT.getConst s (smtName a)
case v of
SMT.Int x | x >= 0 -> return (K (Nat x))
_ -> panic "cryCheck.getVal" [ "Not a natural number", show v ]
where
isInf v = do yes <- SMT.getConst s (smtFinName v)
case yes of
SMT.Bool ans -> return (not ans)
_ -> panic "cryCheck.isInf"
[ "Not a boolean value", show yes ]
-- | Get the values for the given names.
getVals :: SMT.Solver -> [Name] -> IO (Map Name Expr)
getVals s xs =
do es <- mapM (getVal s) xs
return (Map.fromList (zip xs es))
{- | Given a model, compute a set of equations of the form `x = e`,
that are impleied by the model. These have the form: -- | Convert a bunch of improving equations into an idempotent substitution.
-- Assumes that the equations don't have loops.
toSubst :: Map Name Expr -> Subst
toSubst m0 = last (m0 : unfoldr step m0)
where step m = do m1 <- anyJust (apSubst m) m
return (m1,m1)
{- | Given a model, compute an improving substitution, implied by the model.
The entries in the substitution look like this:
* @x = A@ variable @x@ must equal constant @A@ * @x = A@ variable @x@ must equal constant @A@
@ -177,8 +211,7 @@ that are impleied by the model. These have the form:
cryImproveModel :: SMT.Solver -> Set Name -> Map Name Expr -> IO (Map Name Expr) cryImproveModel :: SMT.Solver -> Set Name -> Map Name Expr -> IO (Map Name Expr)
cryImproveModel solver uniVars m = go Map.empty initialTodo cryImproveModel solver uniVars m = toSubst `fmap` go Map.empty initialTodo
-- XXX: Hook in linRel
where where
-- Process unification variables first. That way, if we get `x = y`, we'd -- Process unification variables first. That way, if we get `x = y`, we'd
-- prefer `x` to be a unificaiton variabl. -- prefer `x` to be a unificaiton variabl.
@ -190,20 +223,42 @@ cryImproveModel solver uniVars m = go Map.empty initialTodo
go done ((x,e) : rest) = go done ((x,e) : rest) =
-- x = K? -- x = K?
do yesK <- cryMustEqualK solver x e do mbCounter <- cryMustEqualK solver (Map.keys m) x e
if yesK case mbCounter of
then go (Map.insert x e done) rest Nothing -> go (Map.insert x e done) rest
else goV done [] x e rest Just ce -> goV ce done [] x e rest
goV done todo x e ((y,e') : more) goV ce done todo x e ((y,e') : more)
-- x = y? -- x = y?
| e == e' = do yesK <- cryMustEqualV solver x y | e == e' = do yesK <- cryMustEqualV solver x y
if yesK then go (Map.insert x (Var y) done) if yesK then go (Map.insert x (Var y) done)
(reverse todo ++ more) (reverse todo ++ more)
else goV done ((y,e'):todo) x e more else tryLR
| otherwise = goV done ((y,e') : todo) x e more
| otherwise = next
where
next = goV ce done ((y,e'):todo) x e more
tryLR =
case ( x `Set.member` uniVars
, e
, e'
, Map.lookup x ce
, Map.lookup y ce
) of
(True, K x1, K y1, Just (K x2), Just (K y2)) ->
do mb <- cryCheckLinRel solver y x (y1,x1) (y2,x2)
case mb of
Just r -> go (Map.insert x r done)
(reverse todo ++ more)
Nothing -> next
_ -> next
goV _ done todo _ _ [] = go done (reverse todo)
goV done todo _ _ [] = go done (reverse todo)
@ -217,14 +272,36 @@ checkUnsat s e =
return (res == SMT.Unsat) return (res == SMT.Unsat)
-- | Try to prove the given expression.
-- If we fail, we try to give a counter example.
-- If the answer is unknown, then we return an empty counter example.
getCounterExample :: SMT.Solver -> [Name] -> SExpr -> IO (Maybe (Map Name Expr))
getCounterExample s xs e =
do SMT.push s
SMT.assert s e
res <- SMT.check s
ans <- case res of
SMT.Unsat -> return Nothing
SMT.Unknown -> return (Just Map.empty)
SMT.Sat -> Just `fmap` getVals s xs
SMT.pop s
return ans
-- | Is this the only possible value for the constant, under the current -- | Is this the only possible value for the constant, under the current
-- assumptions. -- assumptions.
-- Assumes that we are in a 'Sat' state. -- Assumes that we are in a 'Sat' state.
cryMustEqualK :: SMT.Solver -> Name -> Expr -> IO Bool -- Returns 'Nothing' if the variables must always match the given value.
cryMustEqualK solver x expr = -- Otherwise, we return a counter-example (which may be empty, if uniknown)
cryMustEqualK :: SMT.Solver -> [Name] ->
Name -> Expr -> IO (Maybe (Map Name Expr))
cryMustEqualK solver xs x expr =
case expr of case expr of
K Inf -> checkUnsat solver (SMT.const (smtFinName x)) K Inf -> getCounterExample solver xs (SMT.const (smtFinName x))
K (Nat n) -> checkUnsat solver $ K (Nat n) -> getCounterExample solver xs $
SMT.not $ SMT.not $
SMT.and (SMT.const $ smtFinName x) SMT.and (SMT.const $ smtFinName x)
(SMT.eq (SMT.const (smtName x)) (SMT.int n)) (SMT.eq (SMT.const (smtName x)) (SMT.int n))
@ -245,25 +322,86 @@ cryMustEqualV solver x y =
where fin a = SMT.const (smtFinName a) where fin a = SMT.const (smtFinName a)
var a = SMT.const (smtName a) var a = SMT.const (smtName a)
-- | Try to find a linear relation between the two variables, based -- | Try to find a linear relation between the two variables, based
-- on two observed data points. -- on two observed data points.
-- NOTE: The variable being defined is the SECOND name. -- NOTE: The variable being defined is the SECOND name.
cryCheckLinRel :: SMT.Solver -> cryCheckLinRel :: SMT.Solver ->
{- x -} Name {- ^ Definition in terms of this variable. -} -> {- x -} Name {- ^ Definition in terms of this variable. -} ->
{- y -} Name {- ^ Define this variable. -} -> {- y -} Name {- ^ Define this variable. -} ->
(Integer,Integer) {- ^ Values in one model (x,y) -} -> (Nat',Nat') {- ^ Values in one model (x,y) -} ->
(Integer,Integer) {- ^ Values in antoher model (x,y) -} -> (Nat',Nat') {- ^ Values in antoher model (x,y) -} ->
IO (Maybe (Name,Expr)) IO (Maybe Expr)
cryCheckLinRel s x y p1 p2 = cryCheckLinRel s x y p1 p2 =
case linRel p1 p2 of -- First, try to find a linear relation that holds in all finite cases.
Nothing -> return Nothing do SMT.push s
Just (a,b) -> SMT.assert s (isFin x)
do let expr = K (Nat a) :* Var x :+ K (Nat b) SMT.assert s (isFin y)
proved <- checkUnsat s $ propToSmtLib $ Not $ Var y :==: expr ans <- case (p1,p2) of
((Nat x1, Nat y1), (Nat x2, Nat y2)) ->
checkLR x1 y1 x2 y2
((Inf, Inf), (Nat x2, Nat y2)) ->
mbGoOn (getFinPt x2) $ \(x1,y1) -> checkLR x1 y1 x2 y2
((Nat x1, Nat y1), (Inf, Inf)) ->
mbGoOn (getFinPt x1) $ \(x2,y2) -> checkLR x1 y1 x2 y2
_ -> return Nothing
SMT.pop s
-- Next, check the infinite cases: if @y = A * x + B@, then
-- either both @x@ and @y@ must be infinite or they both must be finite.
-- Note that we don't consider relations where A = 0: because they
-- would be handled when we checked that @y@ is a constant.
case ans of
Nothing -> return Nothing
Just e ->
do SMT.push s
SMT.assert s (SMT.not (SMT.eq (isFin x) (isFin y)))
c <- SMT.check s
SMT.pop s
case c of
SMT.Unsat -> return (Just e)
_ -> return Nothing
where
isFin a = SMT.const (smtFinName a)
checkLR x1 y1 x2 y2 =
mbGoOn (return (linRel (x1,y1) (x2,y2))) $ \(a,b) ->
do let expr = case a of
1 -> Var x :+ K (Nat b)
_ -> K (Nat a) :* Var x :+ K (Nat b)
proved <- checkUnsat s $ propToSmtLib $ crySimplify
$ Not $ Var y :==: expr
if not proved if not proved
then return Nothing then return Nothing
else return (Just (y,expr)) else return (Just expr)
-- Try to get an example of another point, which is finite, and at
-- different @x@ coordinate.
getFinPt otherX =
do SMT.push s
SMT.assert s (SMT.not (SMT.eq (SMT.const (smtName x)) (SMT.int otherX)))
smtAns <- SMT.check s
ans <- case smtAns of
SMT.Sat ->
do vX <- SMT.getConst s (smtName x)
vY <- SMT.getConst s (smtName y)
case (vX, vY) of
(SMT.Int vx, SMT.Int vy)
| vx >= 0 && vy >= 0 -> return (Just (vx,vy))
_ -> return Nothing
_ -> return Nothing
SMT.pop s
return ans
mbGoOn m k = do ans <- m
case ans of
Nothing -> return Nothing
Just a -> k a
{- | Compute a linear relation through two concrete points. {- | Compute a linear relation through two concrete points.
Try to find a relation of the form `y = a * x + b`, where both `a` and `b` Try to find a relation of the form `y = a * x + b`, where both `a` and `b`
@ -282,7 +420,7 @@ linRel :: (Integer,Integer) {- ^ First point -} ->
linRel (x1,y1) (x2,y2) = linRel (x1,y1) (x2,y2) =
do guard (x1 /= x2) do guard (x1 /= x2)
let (a,r) = divMod (y2 - y1) (x2 - x1) let (a,r) = divMod (y2 - y1) (x2 - x1)
guard (r == 0 && a >= 0) guard (r == 0 && a > 0) -- Not interested in A = 0
let b = y1 - a * x1 let b = y1 - a * x1
guard (b >= 0) guard (b >= 0)
return (a,b) return (a,b)