File: Range.hs

package info (click to toggle)
haskell-sbv 10.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,148 kB
  • sloc: haskell: 31,176; makefile: 4
file content (213 lines) | stat: -rw-r--r-- 10,632 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
-----------------------------------------------------------------------------
-- |
-- Module    : Data.SBV.Tools.Range
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Single variable valid range detection.
-----------------------------------------------------------------------------

{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE ScopedTypeVariables #-}

{-# OPTIONS_GHC -Wall -Werror #-}

module Data.SBV.Tools.Range (

         -- * Boundaries and ranges
         Boundary(..), Range(..)

         -- * Computing valid ranges
       , ranges, rangesWith

       ) where

import Data.SBV
import Data.SBV.Control

import Data.SBV.Internals hiding (Range, free_)

-- $setup
-- >>> -- For doctest purposes only:
-- >>> import Data.SBV
-- >>> :set -XScopedTypeVariables

-- | A boundary value
data Boundary a = Unbounded -- ^ Unbounded
                | Open   a  -- ^ Exclusive of the point
                | Closed a  -- ^ Inclusive of the point

-- | Is this a closed value?
isClosed :: Boundary a -> Bool
isClosed Unbounded  = False
isClosed (Open   _) = False
isClosed (Closed _) = True

-- | A range is a pair of boundaries: Lower and upper bounds
data Range a = Range (Boundary a) (Boundary a)

-- | Show instance for 'Range'
instance Show a => Show (Range a) where
   show (Range l u) = sh True l ++ "," ++ sh False u
     where sh onLeft b = case b of
                           Unbounded | onLeft -> "(-oo"
                                     | True   -> "oo)"
                           Open   v  | onLeft -> "(" ++ show v
                                     | True   -> show v ++ ")"
                           Closed v  | onLeft -> "[" ++ show v
                                     | True   -> show v ++ "]"

-- | Given a single predicate over a single variable, find the contiguous ranges over which the predicate
-- is satisfied. SBV will make one call to the optimizer, and then as many calls to the solver as there are
-- disjoint ranges that the predicate is satisfied over. (Linear in the number of ranges.) Note that the
-- number of ranges is large, this can take a long time!
--
-- Beware that, as of June 2021, z3 no longer supports optimization with 'SReal' in the presence of
-- strict inequalities. See <https://github.com/Z3Prover/z3/issues/5314> for details. So, if you
-- have 'SReal' variables, it is important that you do /not/ use a strict inequality, i.e., '.>', '.<', './=' etc.
-- Inequalities of the form '.<=', '.>=' should be OK. Please report if you see any fishy
-- behavior due to this change in z3's behavior.
--
-- Some examples:
--
-- >>> ranges (\(_ :: SInteger) -> sFalse)
-- []
-- >>> ranges (\(_ :: SInteger) -> sTrue)
-- [(-oo,oo)]
-- >>> ranges (\(x :: SInteger) -> sAnd [x .<= 120, x .>= -12, x ./= 3])
-- [[-12,3),(3,120]]
-- >>> ranges (\(x :: SInteger) -> sAnd [x .<= 75, x .>= 5, x ./= 6, x ./= 67])
-- [[5,6),(6,67),(67,75]]
-- >>> ranges (\(x :: SInteger) -> sAnd [x .<= 75, x ./= 3, x ./= 67])
-- [(-oo,3),(3,67),(67,75]]
-- >>> ranges (\(x :: SReal) -> sAnd [x .>= 3.2, x .<= 12.7])
-- [[3.2,12.7]]
-- >>> ranges (\(x :: SReal) -> sAnd [x .<= 12.7, x ./= 8])
-- [(-oo,8.0),(8.0,12.7]]
-- >>> ranges (\(x :: SReal) -> sAnd [x .>= 12.7, x ./= 15])
-- [[12.7,15.0),(15.0,oo)]
-- >>> ranges (\(x :: SInt8) -> sAnd [x .<= 7, x ./= 6])
-- [[-128,6),(6,7]]
-- >>> ranges $ \x -> x .>= (0::SReal)
-- [[0.0,oo)]
-- >>> ranges $ \x -> x .<= (0::SReal)
-- [(-oo,0.0]]
-- >>> ranges $ \(x :: SWord8) -> 2*x .== 4
-- [[2,3),(129,130]]
ranges :: forall a. (Ord a, Num a, SymVal a,  SatModel a, Metric a, SymVal (MetricSpace a), SatModel (MetricSpace a)) => (SBV a -> SBool) -> IO [Range a]
ranges = rangesWith defaultSMTCfg

-- | Compute ranges, using the given solver configuration.
rangesWith :: forall a. (Ord a, Num a, SymVal a,  SatModel a, Metric a, SymVal (MetricSpace a), SatModel (MetricSpace a)) => SMTConfig -> (SBV a -> SBool) -> IO [Range a]
rangesWith cfg prop = do mbBounds <- getInitialBounds
                         case mbBounds of
                           Nothing -> return []
                           Just r  -> search [r] []

  where getInitialBounds :: IO (Maybe (Range a))
        getInitialBounds = do
            let getGenVal :: GeneralizedCV -> Boundary a
                getGenVal (RegularCV  cv)  = Closed $ getRegVal cv
                getGenVal (ExtendedCV ecv) = getExtVal ecv

                getExtVal :: ExtCV -> Boundary a
                getExtVal (Infinite _) = Unbounded
                getExtVal (Epsilon  k) = Open $ getRegVal (mkConstCV k (0::Integer))
                getExtVal i@Interval{} = error $ unlines [ "*** Data.SBV.ranges.getExtVal: Unexpected interval bounds!"
                                                         , "***"
                                                         , "*** Found bound: " ++ show i
                                                         , "*** Please report this as a bug!"
                                                         ]
                getExtVal (BoundedCV cv) = Closed $ getRegVal cv
                getExtVal (AddExtCV a b) = getExtVal a `addBound` getExtVal b
                getExtVal (MulExtCV a b) = getExtVal a `mulBound` getExtVal b

                opBound :: (a -> a -> a) -> Boundary a -> Boundary a -> Boundary a
                opBound f x y = case (fromBound x, fromBound y, isClosed x && isClosed y) of
                                  (Just a, Just b, True)  -> Closed $ a `f` b
                                  (Just a, Just b, False) -> Open   $ a `f` b
                                  _                       -> Unbounded
                  where fromBound Unbounded  = Nothing
                        fromBound (Open   a) = Just a
                        fromBound (Closed a) = Just a

                addBound, mulBound :: Boundary a -> Boundary a -> Boundary a
                addBound = opBound (+)
                mulBound = opBound (*)

                getRegVal :: CV -> a
                getRegVal cv = case parseCVs [cv] of
                                 Just (v :: MetricSpace a, []) -> case unliteral (fromMetricSpace (literal v)) of
                                                                    Nothing -> error $ "Data.SBV.ranges.getRegVal: Cannot extract value from metric space equivalent: " ++ show cv
                                                                    Just r  -> r
                                 _                             -> error $ "Data.SBV.ranges.getRegVal: Cannot parse " ++ show cv


                getBound cstr = do let objName = "boundValue"
                                   res@(LexicographicResult m) <- optimizeWith cfg Lexicographic $ do x <- free_
                                                                                                      constrain $ prop x
                                                                                                      cstr objName x
                                   case m of
                                     Unsatisfiable{} -> return Nothing
                                     Unknown{}       -> error "Solver said Unknown!"
                                     ProofError{}    -> error (show res)
                                     _               -> return $ getModelObjectiveValue objName m

            mi <- getBound minimize
            ma <- getBound maximize
            case (mi, ma) of
              (Just minV, Just maxV) -> return $ Just $ Range (getGenVal minV) (getGenVal maxV)
              _                      -> return Nothing

        -- Is this range satisfiable? Returns a witness to it.
        witness :: Range a -> Symbolic (SBV a)
        witness (Range lo hi) = do x :: SBV a <- free_

                                   let restrict v open closed = case v of
                                                                  Unbounded -> sTrue
                                                                  Open   a  -> x `open`   literal a
                                                                  Closed a  -> x `closed` literal a

                                       lower = restrict lo (.>) (.>=)
                                       upper = restrict hi (.<) (.<=)

                                   constrain $ lower .&& upper

                                   return x

        isFeasible :: Range a -> IO Bool
        isFeasible r = runSMTWith cfg $ do _ <- witness r

                                           query $ do cs <- checkSat
                                                      case cs of
                                                        Unsat  -> return False
                                                        DSat{} -> error "Data.SBV.interval.isFeasible: Solver returned a delta-satisfiable result!"
                                                        Unk    -> error "Data.SBV.interval.isFeasible: Solver said unknown!"
                                                        Sat    -> return True

        bisect :: Range a -> IO (Maybe [Range a])
        bisect r@(Range lo hi) = runSMTWith cfg $ do x <- witness r

                                                     constrain $ sNot (prop x)

                                                     query $ do cs <- checkSat
                                                                case cs of
                                                                  Unsat  -> return Nothing
                                                                  DSat{} -> error "Data.SBV.interval.bisect: Solver returned a delta-satisfiable result!"
                                                                  Unk    -> error "Data.SBV.interval.bisect: Solver said unknown!"
                                                                  Sat    -> do midV <- Open <$> getValue x
                                                                               return $ Just [Range lo midV, Range midV hi]

        search :: [Range a] -> [Range a] -> IO [Range a]
        search []     sofar = return $ reverse sofar
        search (c:cs) sofar = do feasible <- isFeasible c
                                 if feasible
                                    then do mbCS <- bisect c
                                            case mbCS of
                                              Nothing  -> search cs          (c:sofar)
                                              Just xss -> search (xss ++ cs) sofar
                                    else search cs sofar

{- HLint ignore rangesWith "Use fromMaybe" -}