1
-- | Termination checker, based on
2
-- \"A Predicative Analysis of Structural Recursion\" by
3
-- Andreas Abel and Thorsten Altenkirch (JFP'01).
5
-- \"The Size-Change Principle for Program Termination\" by
6
-- Chin Soon Lee, Neil Jones, and Amir Ben-Amram (POPL'01).
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.
12
module Agda.Termination.Termination
14
, Agda.Termination.Termination.tests
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
23
import Agda.Utils.QuickCheck
24
import qualified Data.Set as Set
25
import qualified Data.Array as Array
27
import qualified Data.Set as Set
29
import qualified Data.Map as Map
31
import Data.Array (Array)
33
-- | TODO: This comment seems to be partly out of date.
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.
39
-- @'Right' perms@ is returned if the functions are size-change terminating.
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.
46
-- Note that this function assumes that all data types are strictly
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.
54
-- This criterion is strictly more liberal than searching for a
55
-- lexicographic order (and easier to implement, but harder to justify).
57
terminates :: (Ord meta, Monoid meta) => CallGraph meta -> Either meta ()
58
terminates cs = let ccs = complete cs
60
checkIdems $ toList ccs
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
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.
72
checkIdem :: Call -> Bool
74
b = target c == source c
76
diag = Array.elems $ diagonal (mat (cm c))
77
hasDecr = any isDecr $ diag
79
(not b) || (not idem) || hasDecr
81
-- | Matrix is decreasing if any diagonal element is 'Lt'.
83
isDecr :: Order -> Bool
85
isDecr (Mat m) = any isDecr $ Array.elems $ diagonal m
88
------------------------------------------------------------------------
91
-- | The call graph instantiation used by the examples below.
93
type CG = CallGraph (Set Integer)
95
-- | Constructs a call graph suitable for use with the 'R' monoid.
97
buildCallGraph :: [Call] -> CG
98
buildCallGraph = fromList . flip zip (map Set.singleton [1 ..])
100
-- | The example from the JFP'02 paper.
103
example1 = buildCallGraph [c1, c2, c3]
107
c1 = Call { source = flat, target = aux
108
, cm = CallMatrix $ fromLists (Size 2 1) [[Lt], [Lt]]
110
c2 = Call { source = aux, target = aux
111
, cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
114
c3 = Call { source = aux, target = flat
115
, cm = CallMatrix $ fromLists (Size 1 2) [[Unknown, Le]]
118
prop_terminates_example1 = isRight $ terminates example1
120
-- | An example which is now handled by this algorithm: argument
121
-- swapping addition.
123
-- @S x + y = S (y + x)@
128
example2 = buildCallGraph [c]
131
c = Call { source = plus, target = plus
132
, cm = CallMatrix $ fromLists (Size 2 2) [ [Unknown, Le]
136
prop_terminates_example2 = isRight $ terminates example2
138
-- | A related example which is anyway handled: argument swapping addition
139
-- using two alternating functions.
141
-- @S x + y = S (y +' x)@
145
-- @S x +' y = S (y + x)@
150
example3 = buildCallGraph [c plus plus', c plus' plus]
154
c f g = Call { source = f, target = g
155
, cm = CallMatrix $ fromLists (Size 2 2) [ [Unknown, Le]
159
prop_terminates_example3 = isRight $ terminates example3
161
-- | A contrived example.
163
-- @f (S x) y = f (S x) y + g x y@
169
-- TODO: This example checks that the meta information is reported properly
170
-- when an error is encountered.
173
example4 = buildCallGraph [c1, c2, c3]
177
c1 = Call { source = f, target = f
178
, cm = CallMatrix $ fromLists (Size 2 2) [ [Le, Unknown]
181
c2 = Call { source = f, target = g
182
, cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
185
c3 = Call { source = g, target = f
186
, cm = CallMatrix $ fromLists (Size 2 2) [ [Le, Unknown]
190
prop_terminates_example4 = isLeft $ terminates example4
192
-- | This should terminate.
194
-- @f (S x) (S y) = g x (S y) + f (S (S x)) y@
196
-- @g (S x) (S y) = f (S x) (S y) + g x (S y)@
199
example5 = buildCallGraph [c1, c2, c3, c4]
203
c1 = Call { source = f, target = g
204
, cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
207
c2 = Call { source = f, target = f
208
, cm = CallMatrix $ fromLists (Size 2 2) [ [Unknown, Unknown]
211
c3 = Call { source = g, target = f
212
, cm = CallMatrix $ fromLists (Size 2 2) [ [Le, Unknown]
215
c4 = Call { source = g, target = g
216
, cm = CallMatrix $ fromLists (Size 2 2) [ [Lt, Unknown]
220
prop_terminates_example5 = isRight $ terminates example5
222
-- | Another example which should fail.
224
-- @f (S x) = f x + f (S x)@
228
-- TODO: This example checks that the meta information is reported properly
229
-- when an error is encountered.
232
example6 = buildCallGraph [c1, c2, c3]
235
c1 = Call { source = f, target = f
236
, cm = CallMatrix $ fromLists (Size 1 1) [ [Lt] ]
238
c2 = Call { source = f, target = f
239
, cm = CallMatrix $ fromLists (Size 1 1) [ [Le] ]
241
c3 = Call { source = f, target = f
242
, cm = CallMatrix $ fromLists (Size 1 1) [ [Le] ]
245
prop_terminates_example6 = isLeft $ terminates example6
247
------------------------------------------------------------------------
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