~ubuntu-branches/ubuntu/raring/agda/raring-proposed

« back to all changes in this revision

Viewing changes to src/full/Agda/Termination/Termination.hs

  • Committer: Bazaar Package Importer
  • Author(s): Iain Lane
  • Date: 2009-07-20 19:49:41 UTC
  • Revision ID: james.westby@ubuntu.com-20090720194941-hcjy91vrn16csh7d
Tags: upstream-2.2.4+dfsg
ImportĀ upstreamĀ versionĀ 2.2.4+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-- | Termination checker, based on
 
2
--     \"A Predicative Analysis of Structural Recursion\" by
 
3
--     Andreas Abel and Thorsten Altenkirch (JFP'01).
 
4
--   and
 
5
--     \"The Size-Change Principle for Program Termination\" by
 
6
--     Chin Soon Lee, Neil Jones, and Amir Ben-Amram (POPL'01).
 
7
--
 
8
-- TODO: Note that we should also check that data type definitions are
 
9
-- strictly positive. Furthermore, for inductive-recursive families we
 
10
-- may need to do something more clever.
 
11
 
 
12
module Agda.Termination.Termination
 
13
  ( terminates
 
14
  , Agda.Termination.Termination.tests
 
15
  ) where
 
16
 
 
17
import Agda.Termination.Lexicographic
 
18
import Agda.Termination.CallGraph
 
19
import Agda.Termination.Matrix
 
20
import Agda.Utils.Either
 
21
import Agda.Utils.TestHelpers
 
22
import Control.Arrow
 
23
import Agda.Utils.QuickCheck
 
24
import qualified Data.Set as Set
 
25
import qualified Data.Array as Array
 
26
import Data.Set (Set)
 
27
import qualified Data.Set as Set
 
28
import Data.Map (Map)
 
29
import qualified Data.Map as Map
 
30
import Data.Monoid
 
31
import Data.Array (Array)
 
32
 
 
33
-- | TODO: This comment seems to be partly out of date.
 
34
--
 
35
-- @'terminates' cs@ checks if the functions represented by @cs@
 
36
-- terminate. The call graph @cs@ should have one entry ('Call') per
 
37
-- recursive function application.
 
38
--
 
39
-- @'Right' perms@ is returned if the functions are size-change terminating.
 
40
--
 
41
-- If termination can not be established, then @'Left' problems@ is
 
42
-- returned instead. Here @problems@ contains an
 
43
-- indication of why termination cannot be established. See 'lexOrder'
 
44
-- for further details.
 
45
--
 
46
-- Note that this function assumes that all data types are strictly
 
47
-- positive.
 
48
--
 
49
-- The termination criterion is taken from Jones et al.
 
50
-- In the completed call graph, each idempotent call-matrix
 
51
-- from a function to itself must have a decreasing argument.
 
52
-- Idempotency is wrt. matrix multiplication.
 
53
--
 
54
-- This criterion is strictly more liberal than searching for a
 
55
-- lexicographic order (and easier to implement, but harder to justify).
 
56
 
 
57
terminates :: (Ord meta, Monoid meta) => CallGraph meta -> Either meta ()
 
58
terminates cs = let ccs = complete cs
 
59
                in
 
60
                  checkIdems $ toList ccs
 
61
 
 
62
checkIdems :: (Ord meta,Monoid meta) => [(Call,meta)] -> Either meta ()
 
63
checkIdems [] = Right ()
 
64
checkIdems ((c,m):xs) = if (checkIdem c) then checkIdems xs else Left m
 
65
 
 
66
{- Convention (see TermCheck):
 
67
   Guardedness flag is in position (0,0) of the matrix,
 
68
   it is always present even if the functions are all recursive.
 
69
   The examples below do not include the guardedness flag, though.
 
70
 -}
 
71
 
 
72
checkIdem :: Call -> Bool
 
73
checkIdem c = let
 
74
  b = target c == source c
 
75
  idem = (c >*< c) == c
 
76
  diag =  Array.elems $ diagonal (mat (cm c))
 
77
  hasDecr = any isDecr $ diag
 
78
  in
 
79
    (not b) || (not idem) || hasDecr
 
80
 
 
81
-- | Matrix is decreasing if any diagonal element is 'Lt'.
 
82
 
 
83
isDecr :: Order -> Bool
 
84
isDecr Lt = True
 
85
isDecr (Mat m) = any isDecr $ Array.elems $ diagonal m
 
86
isDecr _ = False
 
87
 
 
88
------------------------------------------------------------------------
 
89
-- Some examples
 
90
 
 
91
-- | The call graph instantiation used by the examples below.
 
92
 
 
93
type CG = CallGraph (Set Integer)
 
94
 
 
95
-- | Constructs a call graph suitable for use with the 'R' monoid.
 
96
 
 
97
buildCallGraph :: [Call] -> CG
 
98
buildCallGraph = fromList . flip zip (map Set.singleton [1 ..])
 
99
 
 
100
-- | The example from the JFP'02 paper.
 
101
 
 
102
example1 :: CG
 
103
example1 = buildCallGraph [c1, c2, c3]
 
104
  where
 
105
  flat = 1
 
106
  aux  = 2
 
107
  c1 = Call { source = flat, target = aux
 
108
            , cm = CallMatrix $ fromLists (Size 2 1) [[Lt], [Lt]]
 
109
            }
 
110
  c2 = Call { source = aux,  target = aux
 
111
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
 
112
                                                     , [Unknown, Le]]
 
113
            }
 
114
  c3 = Call { source = aux,  target = flat
 
115
            , cm = CallMatrix $ fromLists (Size 1 2) [[Unknown, Le]]
 
116
            }
 
117
 
 
118
prop_terminates_example1 = isRight $ terminates example1
 
119
 
 
120
-- | An example which is now handled by this algorithm: argument
 
121
-- swapping addition.
 
122
--
 
123
-- @S x + y = S (y + x)@
 
124
--
 
125
-- @Z   + y = y@
 
126
 
 
127
example2 :: CG
 
128
example2 = buildCallGraph [c]
 
129
  where
 
130
  plus = 1
 
131
  c = Call { source = plus, target = plus
 
132
           , cm = CallMatrix $ fromLists (Size 2 2) [ [Unknown, Le]
 
133
                                                    , [Lt, Unknown] ]
 
134
           }
 
135
 
 
136
prop_terminates_example2 = isRight $ terminates example2
 
137
 
 
138
-- | A related example which is anyway handled: argument swapping addition
 
139
-- using two alternating functions.
 
140
--
 
141
-- @S x + y = S (y +' x)@
 
142
--
 
143
-- @Z   + y = y@
 
144
--
 
145
-- @S x +' y = S (y + x)@
 
146
--
 
147
-- @Z   +' y = y@
 
148
 
 
149
example3 :: CG
 
150
example3 = buildCallGraph [c plus plus', c plus' plus]
 
151
  where
 
152
  plus  = 1
 
153
  plus' = 2
 
154
  c f g = Call { source = f, target = g
 
155
               , cm = CallMatrix $ fromLists (Size 2 2) [ [Unknown, Le]
 
156
                                                        , [Lt, Unknown] ]
 
157
               }
 
158
 
 
159
prop_terminates_example3 = isRight $ terminates example3
 
160
 
 
161
-- | A contrived example.
 
162
--
 
163
-- @f (S x) y = f (S x) y + g x y@
 
164
--
 
165
-- @f Z     y = y@
 
166
--
 
167
-- @g x y = f x y@
 
168
--
 
169
-- TODO: This example checks that the meta information is reported properly
 
170
-- when an error is encountered.
 
171
 
 
172
example4 :: CG
 
173
example4 = buildCallGraph [c1, c2, c3]
 
174
  where
 
175
  f = 1
 
176
  g = 2
 
177
  c1 = Call { source = f, target = f
 
178
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Le, Unknown]
 
179
                                                     , [Unknown, Le] ]
 
180
            }
 
181
  c2 = Call { source = f, target = g
 
182
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
 
183
                                                     , [Unknown, Le] ]
 
184
            }
 
185
  c3 = Call { source = g, target = f
 
186
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Le, Unknown]
 
187
                                                     , [Unknown, Le] ]
 
188
            }
 
189
 
 
190
prop_terminates_example4 = isLeft $ terminates example4
 
191
 
 
192
-- | This should terminate.
 
193
--
 
194
-- @f (S x) (S y) = g x (S y) + f (S (S x)) y@
 
195
--
 
196
-- @g (S x) (S y) = f (S x) (S y) + g x (S y)@
 
197
 
 
198
example5 :: CG
 
199
example5 = buildCallGraph [c1, c2, c3, c4]
 
200
  where
 
201
  f = 1
 
202
  g = 2
 
203
  c1 = Call { source = f, target = g
 
204
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
 
205
                                                     , [Unknown, Le] ]
 
206
            }
 
207
  c2 = Call { source = f, target = f
 
208
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Unknown, Unknown]
 
209
                                                     , [Unknown, Lt] ]
 
210
            }
 
211
  c3 = Call { source = g, target = f
 
212
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Le, Unknown]
 
213
                                                     , [Unknown, Le] ]
 
214
            }
 
215
  c4 = Call { source = g, target = g
 
216
            , cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
 
217
                                                     , [Unknown, Le] ]
 
218
            }
 
219
 
 
220
prop_terminates_example5 = isRight $ terminates example5
 
221
 
 
222
-- | Another example which should fail.
 
223
--
 
224
-- @f (S x) = f x + f (S x)@
 
225
--
 
226
-- @f x     = f x@
 
227
--
 
228
-- TODO: This example checks that the meta information is reported properly
 
229
-- when an error is encountered.
 
230
 
 
231
example6 :: CG
 
232
example6 = buildCallGraph [c1, c2, c3]
 
233
  where
 
234
  f = 1
 
235
  c1 = Call { source = f, target = f
 
236
            , cm = CallMatrix $ fromLists (Size 1 1) [ [Lt] ]
 
237
            }
 
238
  c2 = Call { source = f, target = f
 
239
            , cm = CallMatrix $ fromLists (Size 1 1) [ [Le] ]
 
240
            }
 
241
  c3 = Call { source = f, target = f
 
242
            , cm = CallMatrix $ fromLists (Size 1 1) [ [Le] ]
 
243
            }
 
244
 
 
245
prop_terminates_example6 = isLeft $ terminates example6
 
246
 
 
247
------------------------------------------------------------------------
 
248
-- All tests
 
249
 
 
250
tests :: IO Bool
 
251
tests = runTests "Agda.Termination.Termination"
 
252
  [ quickCheck' prop_terminates_example1
 
253
  , quickCheck' prop_terminates_example2
 
254
  , quickCheck' prop_terminates_example3
 
255
  , quickCheck' prop_terminates_example4
 
256
  , quickCheck' prop_terminates_example5
 
257
  , quickCheck' prop_terminates_example6
 
258
  ]