|
| 1 | +module Spex.Experiment.TimedPropositionalTemporalLogic (module Spex.Experiment.TimedPropositionalTemporalLogic) where |
| 2 | + |
| 3 | +-- TPLT from "A Really Temporal Logic" (1994): |
| 4 | +-- https://www.seas.upenn.edu/~alur/JACM94.pdf |
| 5 | + |
| 6 | +import Data.Fixed |
| 7 | +import Data.Map.Strict (Map) |
| 8 | +import Data.Map.Strict qualified as Map |
| 9 | +import Data.Maybe |
| 10 | +import Data.Time |
| 11 | +import Data.Time.Clock.POSIX |
| 12 | +import Numeric.Natural |
| 13 | + |
| 14 | +------------------------------------------------------------------------ |
| 15 | + |
| 16 | +infixl 6 :+ |
| 17 | +infix 4 :<= |
| 18 | + |
| 19 | +type Var = String |
| 20 | + |
| 21 | +-- TODO: Currently the natural stands for seconds, we probably want something |
| 22 | +-- smaller to be able to be more precise. |
| 23 | +data Constraint |
| 24 | + = Var :+ Natural |
| 25 | + | RelativeTime Natural |
| 26 | + |
| 27 | +data Form a |
| 28 | + = TT |
| 29 | + | FF |
| 30 | + | Prop (a -> Bool) |
| 31 | + | Neg (Form a) |
| 32 | + | X (Form a) |
| 33 | + | U (Form a) (Form a) |
| 34 | + | Always (Form a) |
| 35 | + | Eventually (Form a) |
| 36 | + | Or (Form a) (Form a) |
| 37 | + | And (Form a) (Form a) |
| 38 | + | Constraint :<= Constraint |
| 39 | + | Congurent Constraint Constraint Natural -- XXX: I'm not sure how to use this... |
| 40 | + | FreezeQuantifier Var (Form a) |
| 41 | + |
| 42 | +-- <>x.(p /\ x <= 10) |
| 43 | +example :: Form Bool |
| 44 | +example = |
| 45 | + Eventually $ |
| 46 | + FreezeQuantifier "x" $ |
| 47 | + Prop (== True) `And` ("x" :+ 0 :<= RelativeTime 10) |
| 48 | + |
| 49 | +type Env time = Map Var time |
| 50 | + |
| 51 | +class Time t where |
| 52 | + (.==) :: t -> t -> Bool |
| 53 | + (.<=) :: t -> t -> Bool |
| 54 | + (.+) :: t -> Natural -> t |
| 55 | + (.-) :: t -> t -> t |
| 56 | + (.%) :: t -> Natural -> t |
| 57 | + fromNatural :: Natural -> t |
| 58 | + |
| 59 | +instance Time Natural where |
| 60 | + t0 .== t1 = t0 == t1 |
| 61 | + t0 .<= t1 = t0 <= t1 |
| 62 | + t0 .+ t1 = t0 + t1 |
| 63 | + t0 .- t1 = t0 - t1 |
| 64 | + t0 .% t1 = t0 `mod` t1 |
| 65 | + fromNatural = id |
| 66 | + |
| 67 | +instance Time UTCTime where |
| 68 | + t0 .== t1 = t0 == t1 |
| 69 | + t0 .<= t1 = t0 <= t1 |
| 70 | + t0 .+ n = fromIntegral n `addUTCTime` t0 |
| 71 | + t0 .- t1 = posixSecondsToUTCTime (t0 `diffUTCTime` t1) |
| 72 | + t0 .% n = |
| 73 | + posixSecondsToUTCTime $ |
| 74 | + secondsToNominalDiffTime $ |
| 75 | + nominalDiffTimeToSeconds (utcTimeToPOSIXSeconds t0) |
| 76 | + `mod'` fromIntegral n |
| 77 | + fromNatural = posixSecondsToUTCTime . fromIntegral |
| 78 | + |
| 79 | +-- NOTE: We need t0 here in order to compute the relative time to when the |
| 80 | +-- trace started. For time := Natural isn't not needed as t0 = 0, but for |
| 81 | +-- timestamps we need the supply the start time. |
| 82 | +lookupEnv :: (Time time) => Constraint -> Env time -> time |
| 83 | +lookupEnv (x :+ c) env = env Map.! x .+ c |
| 84 | +lookupEnv (RelativeTime c) env = env Map.! "t0" .+ fromNatural c |
| 85 | + |
| 86 | +-- TODO: Would be interesting to generalise this from a single trace to lists |
| 87 | +-- of traces, and make assertions about percentiles, e.g. a request is always |
| 88 | +-- eventually responded to within 200ms in p99, i.e. 1% of requests may be |
| 89 | +-- slower. |
| 90 | + |
| 91 | +sat :: |
| 92 | + (Time time) => |
| 93 | + Form a |
| 94 | + -> [(time, a)] |
| 95 | + -> Env time |
| 96 | + -> Bool |
| 97 | +sat TT _w _env = True |
| 98 | +sat FF _w _env = False |
| 99 | +sat (Prop p) w _env = p (snd (w !! 0)) |
| 100 | +sat (Neg p) w env = not (sat p w env) |
| 101 | +sat (X p) w env = sat p (drop 1 w) env |
| 102 | +sat (U p q) w env = |
| 103 | + case listToMaybe [i | i <- [0 .. length w - 1], sat q (drop i w) env] of |
| 104 | + Nothing -> False |
| 105 | + Just i -> and [sat p (drop k w) env | k <- [0 .. i - 1]] |
| 106 | +sat (Always p) w env = and [sat p (drop i w) env | i <- [0 .. length w - 1]] |
| 107 | +sat (Eventually p) w env = or [sat p (drop i w) env | i <- [0 .. length w - 1]] |
| 108 | +sat (Or p q) w env = sat p w env || sat q w env |
| 109 | +sat (And p q) w env = sat p w env && sat q w env |
| 110 | +sat (c1 :<= c2) _w env = lookupEnv c1 env .<= lookupEnv c2 env |
| 111 | +-- XXX: Not sure if Congurent is right, or how to use it, or if it works with |
| 112 | +-- timestamps as opposed to naturals as time... |
| 113 | +sat (Congurent c1 c2 d) _w env = (lookupEnv c1 env .- lookupEnv c2 env) .% d .== fromNatural 0 |
| 114 | +sat (FreezeQuantifier x p) w env = sat p w (Map.insert x t0 env) |
| 115 | + where |
| 116 | + t0 = fst (w !! 0) |
| 117 | + |
| 118 | +------------------------------------------------------------------------ |
| 119 | + |
| 120 | +test0 :: Bool |
| 121 | +test0 = |
| 122 | + sat |
| 123 | + example |
| 124 | + ( [(0, False), (1, False), (2, False), (9, False), (10, True)] :: |
| 125 | + [(Natural, Bool)] |
| 126 | + ) |
| 127 | + (Map.singleton "t0" 0) |
| 128 | + == True |
| 129 | + |
| 130 | +test1 :: Bool |
| 131 | +test1 = |
| 132 | + sat |
| 133 | + example |
| 134 | + ( [(0, False), (1, False), (2, False), (9, False), (11, True)] :: |
| 135 | + [(Natural, Bool)] |
| 136 | + ) |
| 137 | + (Map.singleton "t0" 0) |
| 138 | + == False |
| 139 | + |
| 140 | +test2 :: IO Bool |
| 141 | +test2 = do |
| 142 | + now <- getCurrentTime |
| 143 | + let t1 = now .+ 1 |
| 144 | + t2 = now .+ 2 |
| 145 | + t9 = now .+ 9 |
| 146 | + t10 = now .+ 10 |
| 147 | + print now |
| 148 | + print t1 |
| 149 | + print t10 |
| 150 | + return $ |
| 151 | + sat |
| 152 | + example |
| 153 | + [(now, False), (t1, False), (t2, False), (t9, False), (t10, True)] |
| 154 | + (Map.singleton "t0" now) |
| 155 | + == True |
| 156 | + |
| 157 | +test3 :: IO Bool |
| 158 | +test3 = do |
| 159 | + now <- getCurrentTime |
| 160 | + let t1 = now .+ 1 |
| 161 | + t2 = now .+ 2 |
| 162 | + t9 = now .+ 9 |
| 163 | + t11 = now .+ 11 |
| 164 | + print now |
| 165 | + print t11 |
| 166 | + return $ |
| 167 | + sat |
| 168 | + example |
| 169 | + [(now, False), (t1, False), (t2, False), (t9, False), (t11, True)] |
| 170 | + (Map.singleton "t0" now) |
| 171 | + == False |
| 172 | + |
| 173 | +test :: IO () |
| 174 | +test = do |
| 175 | + b2 <- test2 |
| 176 | + b3 <- test3 |
| 177 | + let bools = [test0, test1, b2, b3] |
| 178 | + if and bools then putStrLn "Passed" else print bools |
0 commit comments