Formal Proofs of Tarjan's Algorithm in Why3, Coq, and Isabelle
Ran Chen, Cyril Cohen, Jean-Jacques Levy, Stephan Merz, Laurent Thery
FFormal Proofs of Tarjan’s Algorithm in Why3,C OQ , and Isabelle Ran Chen , Cyril Cohen , Jean-Jacques L´evy ,Stephan Merz , and Laurent Th´ery Iscas, Beijing, China Universit´e Cˆote d’Azur, Inria, Sophia-Antipolis, France Inria, Paris, France Universit´e de Lorraine, CNRS, Inria, LORIA, Nancy, FranceOctober 2018
Abstract
Comparing provers on a formalization of the same problem is always a valuableexercise. In this paper, we present the formal proof of correctness of a non-trivialalgorithm from graph theory that was carried out in three proof assistants: Why3,C OQ , and Isabelle. In this paper, we consider Tarjan’s algorithm [25] for discovering the strongly con-nected components in a directed graph and present a formal proof of its correctnessin three different systems: Why3, C OQ and Isabelle/HOL. The algorithm is treated atan abstract level with a functional programming style manipulating finite sets, stacksand mappings, but it respects the linear time behaviour of the original presentation.It would not be difficult to derive and prove correct an efficient implementation withimperative programs and concrete data types such as integers, linked lists and mutablearrays from our presentation.To our knowledge this is the first time that the formal correctness proof of a non-trivial program is carried out in three very different proof assistants: Why3 is based on afirst-order logic with inductive predicates and automatic provers, C OQ on an expressivetheory of higher-order logic and dependent types, and Isabelle/HOL combines higher-order logic with automatic provers. We do not claim that our proof is the simplestpossible one, and we will discuss the design and implementation of other proofs in theconclusion, but our proof is indeed elegant and follows Tarjan’s presentation. Cruciallyfor our comparison, the algorithm is defined at the same level of abstraction in all threesystems, and the proof relies on the same arguments in the three formal systems. Note1 a r X i v : . [ c s . L O ] O c t hat a similar exercise but for a much more elementary proof (the irrationality of squareroot of 2) and using many more proof assistants (17) was presented in [29].Formal and informal proofs of algorithms about graphs were already performedin [21, 27, 22, 11, 15, 26, 17, 24, 23, 13, 7]. Some of them are part a larger library,others focus on the treatment of pointers or about concurrent algorithms. In particular,Lammich and Neumann [15] give a proof of Tarjan’s algorithm within their frameworkfor verifying graph algorithms in Isabelle/HOL. In our formalization, we are aimingfor a simple, direct, and readable proof.It is not possible to expose here the details of the full proofs in the three systems, butthe interested reader can access and run them on the Web [6, 8, 18]. In this paper, werecall the principles of the algorithm in section 2; we describe the proofs in the threesystems in sections 3, 4, and 5 by emphasizing the differences induced by the logicwhich are used; we conclude in sections 6 and 7 by commenting the developments andadvantages of each proof system. The algorithm [25] performs a depth-first search on the set vertices of all vertices inthe graph. Every vertex is visited once and is assigned a serial number of its visit.The algorithm maintains an environment e containing four fields: a stack e.stack , aset e.sccs of strongly connected components, a new fresh serial number e.sn , and afunction e.num which records the serial numbers assigned to vertices. The field e.stack contains the visited vertices which are not part of the components already stored in e.sccs . Vertices are pushed onto the stack in the order of their visit.The depth-first search is organized by two mutually recursive functions dfs1 and dfs . The function dfs takes as argument a set r of roots and an environment e . It returnsa pair consisting of an integer and the modified environment. If the set of roots isempty, the returned integer is + ∞ . Otherwise the returned integer is the minimum ofthe results of the calls to dfs1 on non-visited vertices in r and of the serial numbers ofthe already visited ones.The main procedure tarjan initializes the environment with an empty stack, anempty set of strongly connected components, the fresh number and the constant func-tion giving the number − to each vertex. The result is the set of components returnedby the function dfs called on all vertices in the graph. let rec dfs1 x e =let n0 = e.sn inlet (n1, e1) = dfs (successors x)(add_stack_incr x e) inif n1 < n0 then (n1, e1) elselet (s2, s3) = split x e1.stack in( + ∞ , {stack = s3;sccs = add (elements s2) e1.sccs;sn = e1.sn; num = set infty s2 e1.num}) ith dfs r e = if is_empty r then ( + ∞ , e) elselet x = choose r inlet r’ = remove x r inlet (n1, e1) = if e.num[x] (cid:54) = -1then (e.num[x], e) else dfs1 x e inlet (n2, e2) = dfs r’ e1 in (min n1 n2, e2)let tarjan () =let e = {stack = Nil; sccs = empty;sn = 0; num = const (-1)} inlet (_, e’) = dfs vertices e in e’.sccs The heart of the algorithm is in the body of dfs1 which visits a new vertex x . Theauxiliary function add stack incr updates the environment by pushing x on the stack,assigning it the current fresh serial number, and incrementing that number in view offuture calls. The function dfs1 performs a recursive call to dfs for the successor verticesof x as roots and the updated environment. If the returned integer value n1 is less thanthe number assigned to x , the function simply returns n1 and the current environment.Otherwise, the function declares that a new strongly connected component has beenfound, consisting of all vertices that are contained on top of x in the current stack.Therefore the stack is popped until x ; the popped vertices are stored as a new set in e.sccs ; and their numbers are all set to + ∞ , ensuring that they do not interfere withfuture calculations of min values. The auxiliary functions split and set infty are used tocarry out these updates. let add_stack_incr x e = let n = e.sn in{stack = Cons x e.stack; sccs = e.sccs;sn = n+1; num = e.num[x ← n]}let rec set infty s f = match s with Nil → f| Cons x s’ → (set infty s’ f)[x ← + ∞ ] endlet rec split x s = match s with Nil → (Nil, Nil)| Cons y s’ → if x = y then (Cons x Nil, s’)else let (s1’, s2) = split x s’ in(Cons y s1’, s2) end Figure 1 illustrates the behavior of the algorithm by an example. We presented thealgorithm as a functional program, using data structures available in the Why3 standardlibrary [3]. For lists we have the constructors
Nil and
Cons ; the function elements returns the set of elements of a list. For finite sets, we have the empty set empty , andthe functions add to add an element to a set, remove to remove an element from aset, choose to pick an arbitrary element in a (non-empty) set, and is empty to test foremptiness. We also use maps with functions const denoting the constant function, [ ] to access the value of an element, and [ ← ] for creating a map obtained from anexisting map by setting an element to a given value. We also define an abstract type vertex for vertices and a constant vertices for the finite set of all vertices in the graph.The type env of environments is a record with the four fields stack , sccs , sn and num asdescribed above. 3
83 491756 0
Figure 1: An example: the vertices are numbered and pushed onto the stack in theorder of their visit by the recursive function dfs1 . When the first component { } is discovered, vertex is popped; similarly when the second component { , , } isfound, its vertices are popped; finally all vertices are popped when the third component { , , , , , } is found. Notice that there is no cross-edge to a vertex with a numberless than when the second component is discovered. Similarly in the first component,there is no edge to a vertex with a number less than . In the third component, there isno edge to a vertex less than since we have set the number of vertex to + ∞ when was popped. type vertexconstant vertices: set vertexfunction successors vertex : set vertextype env = {stack: list vertex;sccs: set (set vertex);sn: int; num: map vertex int} For a correspondence between our presentation and the imperative programs usedin standard textbooks, the reader is referred to [7]. The present version can be directlytranslated into C OQ or Isabelle functions, and it respects the linear running time be-haviour of the algorithm, since vertices could be easily implemented by integers, + ∞ by the cardinal of vertices , finite sets by lists of integers and mappings by mutablearrays (see for instance [6]).Like many algorithms on graphs, Tarjan’s algorithm is not easy to understand andeven looks a bit magical. In the original presentation, the integer value returned by thefunction dfs1 is given by the following formula when called on vertex x . LOWLINK ( x ) = min { num [ y ] | x ∗ = ⇒ z (cid:44) → y ∧ x and y are in the same connected component } This expression is evaluated on the spanning tree (forest) corresponding to one run of dfs . The relation x = ⇒ z means that z is a son of x in the spanning tree, the relation ∗ = ⇒ is its transitive and reflexive closure, and z (cid:44) → y means that there is a cross-edgebetween z and y in the spanning tree. In figure 2, = ⇒ is drawn in thick lines and (cid:44) → in dotted lines; a table of the values of the LOWLINK function is also shown. Thus theinteger value returned by dfs1 is the minimum of the numbers of vertices in the same4onnected component accessible by just one cross-edge by all descendants of x visitedin the recursive calls. If none, + ∞ is returned (here is a slight simplification w.r.t. theoriginal algorithm). Notice that the result may be the number of a vertex which is notan ancestor of x in the spanning tree. Take for instance, vertices or in figure 2.The algorithm relies on the existence of a base with a minimal serial number foreach connected component, the members of which are among its descendants in thespanning tree. The reason is that a cross-edge reaches from x either an ancestor of x , ora descendant of a grandson in the spanning tree, or a cousin to the left of x . Intuitively,cross-edges never go right in the spanning tree. Therefore these bases are organized asa Christmas tree, and each connected component is a prefix of one sub-tree of whichthe root is its base.Thus for each environment e in the algorithm, the working stack e.stack corre-sponds to a cut of the spanning tree where connected components to its left are prunedand stored in e.sccs . In this stack, any vertex can reach any vertex higher in the stack.And if a vertex is a base of a connected component, no cross-edge can reach somevertex lower than this base in the stack, otherwise that last vertex would be in the sameconnected component with a strictly lower serial number.We therefore have to organize the proofs of the algorithm around these arguments.To maintain these invariants we will distinguish, as is common for depth-first searchalgorithms, three sets of vertices: white vertices are the non-visited ones, black verticesare those that are already fully visited, and gray vertices are those that are still beingvisited. Clearly, these sets are disjoint and white vertices can be considered as formingthe complement in vertices of the union of the gray and black ones.The previously mentioned invariant properties can now be expressed for vertices inthe stack: no such vertex is white, any vertex can reach all vertices higher in the stack,any vertex can reach some gray vertex lower in the stack. Moreover, vertices in thestack respect the numbering order, i.e. a vertex x is lower than y in the stack if and only Figure 2: Spanning forest and the
LOWLINK function.5f the number assigned to x is strictly less than the number assigned to y . The Why3 system comprises the language WhyML for writing programs and a manysorted first-order logic with inductive data types and inductive predicates to express thelogical assertions. The system generates proof obligations w.r.t. the assertions, pre- andpost-conditions and lemmas inserted in the WhyML program. The system is interfacedwith off-the-shelf automatic provers (we mainly use Alt-Ergo, CVC, E-prover and Z3)and also interactive proof assistants such as C OQ or Isabelle.There are numerous libraries that can be used in the Why3 library, for integer arith-metic, polymorphic lists, finite sets and mappings, etc. There is also a small theory forpaths in graphs. Here we define graphs, paths and strongly connected components asfollows. axiom successors_vertices: ∀ x. mem x vertices → subset (successors x) verticespredicate edge (x y: vertex) =mem x vertices ∧ mem y (successors x)inductive path vertex (list vertex) vertex =| Path_empty: ∀ x: vertex. path x Nil x| Path_cons: ∀ x y z: vertex, l: list vertex.edge x y → path y l z → path x (Cons x l) zpredicate reachable (x y: vertex) = ∃ l. path x l ypredicate in_same_scc (x y: vertex) =reachable x y ∧ reachable y xpredicate is_subscc (s: set vertex) = ∀ x y. mem x s → mem y s → in_same_scc x ypredicate is_scc (s: set vertex) = not is_empty s ∧ is_subscc s ∧ ( ∀ s’. subset s s’ → is_subscc s’ → s == s’) where mem and subset denote membership and the subset relation for finite sets.We add two ghost fields in environments for the black and gray sets of vertices.These fields are used in the proofs but not used in the calculation of the connectedcomponents, which is checked by the type-checker of the language. type env = {ghost black: set vertex;ghost gray: set vertex;stack: list vertex; sccs: set (set vertex);sn: int; num: map vertex int} The functions now become: let rec dfs1 x e =let n0 = e.sn inlet (n1, e1) = dfs (successors x)(add_stack_incr x e) inif n1 < n0 then (n1, add_black x e1) else et (s2, s3) = split x e1.stack in( + ∞ , {stack = s3;black = add x e1.black; gray = e.gray;sccs = add (elements s2) e1.sccs;sn = e1.sn; num = set infty s2 e1.num})with dfs r e = ... (* unmodified *) let tarjan () =let e = {black = empty; gray = empty;stack = Nil; sccs = empty; sn = 0;num = const (-1)} inlet (_, e’) = dfs vertices e in e’.sccs with a new function add black turning a vertex from gray to black and the modified add stack incr adding a new gray vertex with a fresh serial number to the current stack. let add_stack_incr x e =let n = e.sn in{black = e.black; gray = add x e.gray;stack = Cons x e.stack; sccs = e.sccs;sn = n+1; num = e.num[x ← n]}let add_black x e ={black = add x e.black; gray = remove x e.gray;stack = e.stack; sccs = e.sccs;sn = e.sn; num = e.num} The main invariant ( I ) of our program states that the environment is well-formed: predicate wf_env (e: env) =let {stack = s; black = b; gray = g} = e inwf_color e ∧ wf_num e ∧ simplelist s ∧ no_black_to_white b g ∧ ( ∀ x y. lmem x s → lmem y s → e.num[x] ≤ e.num[y] → reachable x y) ∧ ( ∀ y. lmem y s → ∃ x. mem x g ∧ e.num[x] ≤ e.num[y] ∧ reachable y x) ∧ ( ∀ cc. mem cc e.sccs ↔ subset cc e.black ∧ is_scc cc) where lmem stands for membership in a list. The well-formedness property is theconjunction of seven clauses. The two first clauses express quite elementary conditionsabout the colored sets of vertices and the numbering function. We do not express themformally here (see [7, 6] for a detailed description). The third clause states that thereare no repetitions in the stack, and the fourth that there is no edge from a black vertexto a white vertex. The next two clauses formally express the property already statedabove: any vertex in the stack reaches all higher vertices and any vertex in the stackcan reach a lower gray vertex. The last clause states that the sccs field is the set of allconnected components all of whose vertices are black. Since at the end of the tarjan function, all vertices are black, the sccs field will contain exactly the set of all stronglyconnected components.Our functions dfs1 and dfs modify the environment in a monotonic way. Namelythey augment the set of the fully visited vertices (the black ones); they keep invariant7he set of the ones currently under visit (the gray set); they increase the stack with newblack vertices; they also discover new connected components and they keep invariantthe serial numbers of vertices in the stack, predicate subenv (e e’: env) =subset e.black e’.black ∧ e.gray == e’.gray ∧ ( ∃ s. e’.stack = s ++ e.stack ∧ subset (elements s) e’.black) ∧ subset e.sccs e’.sccs ∧ ( ∀ x. lmem x e.stack → e.num[x] = e’.num[x]) Once these invariants are expressed, it remains to locate them in the program text and toadd assertions which help to prove them. The pre-conditions of dfs1 are quite natural:the vertex x must be a white vertex of the graph, and it must be reachable from all grayvertices. Moreover invariant ( I ) must hold. The post-conditions of dfs1 are of threekinds. Firstly ( I ) and the monotony property subenv hold in the resulting environment.Vertex x is black at the end of dfs1 . Finally we express properties of the integer value n returned by this function which should be LOWLINK ( x ) as announced previously.Notice that we do not know yet the connected component of x , but the definition of LOWLINK still works thanks to the numbering with + ∞ of the visited vertices not inits component. In this proof, we give three implicit properties for characterizing theresulting n value. First, the returned value is never higher than the number of x in thefinal environment. Secondly, the returned value is either + ∞ or the number of a vertexin the stack reachable from x . Finally, if there is an edge from a vertex y’ in the newpart of the stack to a vertex y in its old part, the resulting value n must be lower thanthe number of y . let rec dfs1 x e = (* pre-condition *) requires {mem x vertices}requires { ∀ y. mem y e.gray → reachable y x}requires {not mem x (union e.black e.gray)}requires {wf_env e} (* I *)(* post-condition *) returns {(_, e’) → wf_env e’ ∧ subenv e e’}returns {(_, e’) → mem x e’.black}returns {(n, e’) → n ≤ e’.num[x]}returns {(n, e’) → n = + ∞ ∨ num_of_reachable n x e’}returns {(n, e’) → ∀ y. xedge_to e’.stack e.stack y → n ≤ e’.num[y]} where the auxiliary predicates used in these post-conditions are formally defined in thefollowing way. predicate num_of_reachable (n: int) (x: vertex)(e: env) = ∃ y. lmem y e.stack ∧ n = e.num[y] ∧ reachable x ypredicate xedge_to (s1 s3: list vertex)(y: vertex) = ( ∃ s2. s1 = s2 ++ s3 ∧∃ y’. lmem y’ s2 ∧ edge y’ y) ∧ lmem y s3 Notice that when the integer result n of dfs1 is infinite, the number of x must also beinfinite, meaning that its connected component has been found. Also notice that the8efinition of xedge to fits the definition of LOWLINK when the cross edge ends at avertex residing in the stack before the call of dfs1 . The pre- and post-conditions forthe function dfs are quite similar up to a generalization to sets of vertices which are theroots of the algorithm (see [6]).We now add seven assertions in the body of the dfs1 function to help the automaticprovers. In contrast, the function dfs needs no extra assertions in its body. In dfs1 ,when the number n0 of x is strictly greater than the number resulting from the call toits successors, the first assertion states that vertex x can reach a strictly lower vertex inthe current stack and the second assertion states that a lower gray vertex is reachableand that thus the connected component of x is not fully black at end of dfs1 . That keyassertion is proved from the first one by transitivity of reachability. (We understandhere why the algorithm only takes care of a single cross-edge: for any visited vertex x , the spanning tree must contain at least one back-edge to a strict ancestor of x when n1 < n0 .) The next four assertions show that the connected component (elementss2) of x is on top of x in the stack when n1 ≥ n0 , and that then x is the base of thatconnected component. The seventh assertion helps proving that the coloring constraintis preserved at the end of dfs1 . let n0 = e.sn inlet (n1, e1) =dfs (successors x) (add_stack_incr x e) inif n1 < n0 then beginassert { ∃ y. y (cid:54) = x ∧ precedes x y e1.stack ∧ reachable x y};assert { ∃ y. y (cid:54) = x ∧ mem y e1.gray ∧ e1.num[y] < e1.num[x] ∧ in_same_scc x y};(n1, add_black x e1) endelselet (s2, s3) = split x e1.stack inassert {is_last x s2 ∧ s3 = e.stack ∧ subset (elements s2) (add x e1.black)};assert {is_subscc (elements s2)};assert { ∀ y. in_same_scc y x → lmem y s2};assert {is_scc (elements s2)};assert {inter e.gray (elements s2) == empty};( + ∞ , {black = add x e1.black; gray = e.gray;stack = s3; sccs = add (elements s2) e1.sccs;sn = e1.sn; num = set infty s2 e1.num}) where inter is set intersection, and precedes and is last are two auxiliary predicatesdefined below. predicate is_last (x: α ) (s: list α ) = ∃ s’. s = s’ ++ Cons x Nilpredicate precedes (x y: α ) (s: list α ) = ∃ s1 s2. s = s1 ++ (Cons x s2) ∧ lmem y (Cons x s2) All proofs are discovered by the automatic provers except for two proofs carried outinteractively in C OQ . One is the proof of the black extension of the stack in case n1 < n0 . The provers could not work with the existential quantifier, although theC OQ proof is quite short. The second C OQ proof is the fifth assertion in the body of9 rovers Alt- CVC4 E- Z3
Ergo prover
49 lemmas 1.91 26.11 3.33 70 49split 0.09 0.16 6 6add stack incr 0.01 1 1add black 0.02 1 1set infty 0.03 1 1dfs1 77.89 150.2 19.99 13.67 79 20dfs 4.71 3.52 0.26 58 25tarjan 0.85 15 5total 85.51 179.99 23.32 13.93 231 108
Table 1: Performance results of the provers (in seconds, on a 3.3 GHz Intel Core i5processor). Total time is 341.15 seconds. The two last columns contain the numbersof verification conditions and proof obligations. Notice that there may be several VCsper proof obligation. dfs1 , which asserts that any y in the connected component of x belongs to s2 . It is amaximality assertion which states that the set (elements s2) is a complete connectedcomponent. The proof of that assertion is by contradiction. If y is not in s2 , there mustbe an edge from x’ in s2 to some y’ not in s2 such that x reaches x’ and y’ reaches y .There are three cases, depending on the position of y’ . Case 1 is when y’ is in sccs :this is not possible since x would then be in sccs which contradicts x being gray. Case2 is when y’ is an element of s3 : the serial number of y’ is strictly less than the one of x which is n0 . If x’ (cid:54) = x , the cross-edge from x’ to y’ contradicts n1 ≥ n0 (post-condition5); if x’ = x , then y’ is a successor of x and again it contradicts n1 ≥ n0 (post-condition3). Case 3 is when y’ is white, which is impossible since x’ is black in s2 .The figures of the Why3 proof are listed in table 1. Alt-Ergo 2.2 and CVC4 1.5proved the bulk of the proof obligations. The proof uses 49 lemmas that were allproved automatically, but with an interactive interface providing hints to apply inlining,splitting, or induction strategies. This includes 13 lemmas on sets, 16 on lists, 5 onlists without repetitions, 3 on paths, 5 on connected components and 6 very specializedlemmas directly involved in the proof obligations of the algorithm. Among the lemmas,a critical one is the lemma xpath xedge on paths which reduces a predicate on pathsto a predicate on edges. In fact, most of the Why3 proof works on edges which arehandled more robustly by the automatic provers than paths. The two C OQ proofs are16 and 141 lines long (the C OQ files of 677 and 721 lines include preambles that areautomatically generated during the translation from Why3 to C OQ ). The interestedreader is refered to [6] where the full proof is available.The proof explained so far does not show that the functions terminate; we haveonly shown the partial correctness of the algorithm. But after adding two lemmasabout union and difference for finite sets, termination is automatically proved by thefollowing lexicographic ordering on the number of white vertices and roots. let rec dfs1 x e =variant {cardinal (diff vertices In addition to the results reported in the table, Spass was used to discharge one proof obligation. union e.black e.gray)), 0}with dfs r e =variant {cardinal (diff vertices(union e.black e.gray)), 1, cardinal r} OQ C OQ is a proof assistant based on type theory. It uses the calculus of constructions, ahigher order lambda-calculus, to express formulae and proofs. Some basic notions ofgraph theory are provided by the Mathematical Component Library [16]. The formal-ization in C OQ follows closely what has been done in Why3, so we mostly highlightdifferences. It is parameterized by a finite type V for the vertices and a function succes-sors that represents the graph, i.e. (successors v) gives all the successors of the vertex v in the graph.The environment that is passed around in the algorithm is defined as a record withfive fields: Record env := Env { black : { set V } ; stack : seq V ; esccs : { set { set V }} ; sn : nat ; num : { ffun V -> nat }} . Note that with respect to Why3, we have no ghost mechanism available for the black field and we do not hold gray vertices. They are globally defined as the elements of thestack that are not black. Also, we restrict ourselves to natural numbers, representingthe integer n in Why3 by the natural number n + 1 in C OQ .Our definition of the algorithm is very similar to the one of Why3. The only dif-ference is the way recursion is handled. We untangle the mutually recursive function tarjan into two separate functions The first one dfs1 treats a vertex x and the secondone dfs a set of vertices roots in an environment e . Definition dfs1 dfs x e :=let : ( m1 , e1 ) :=dfs [ set y in successors x ] ( add_stack x e ) inif m1 < sn e then ( m1 , add_black x e1 ) else ( ∞ , add_sccs x e1 ). Definition dfs dfs1 dfs roots e :=if [ pick x in roots ] isn ’ t Some x then ( ∞ , e ) else let roots ’ := roots : \ x inlet : ( m1 , e1 ) :=if num e x (cid:54) = then ( num e x , e ) else dfs1 x e inlet : ( m2 , e2 ) := dfs roots ’ e1 in ( minn m1 m2 , e2 ). Then, the two functions are glued together in a recursive function tarjan rec where theparameter n controls the maximal recursive height.11 ixpoint tarjan_rec n :=if n is n1 .+1 thendfs ( dfs1 ( tarjan_rec n1 )) ( tarjan_rec n1 ) else fun r e ⇒ ( ∞ , e ). Let N := | V | ∗ | V | .+1 + | V | . Definition tarjan := sccs ( tarjan_rec N setT e0 ).2. If n is not zero (i.e. it is a successor of some n1 ), tarjan rec calls dfs taking carethat its parameters can only use recursive call to tarjan rec with a smaller recursiveheight, here n1 . This ensures termination. A dummy value is returned in the casewhere n is zero. As both dfs and dfs1 cannot be applied more than the number ofvertices, the value N encodes the lexicographic product of the two maximal heights. Itgives tarjan rec enough fuel to never encounter the dummy value so tarjan correctlyterminates the computation. This last statement is of course proved formally later.The invariants are essentially the same as in the Why3 proof. There are just pack-aged in a different way so we can express more easily intermediate lemmas betweenthe different packages. We first group together the properties about connectivity Record wf_graph e := WfGraph { wf_stack_to_stack : { in stack e &, ∀ x y ,( num e x ≤ num e y ) -> gconnect x y } ; wf_stack_to_grays : { in stack e , ∀ y , ∃ x , [ ∧ x ∈ grays e , ( num e x ≤ num e y ) & gconnect y x ] } . The main invariant then collects all the properties
Record invariants ( e : env ) := Invariants { inv_wf_color : wf_color e ; inv_wf_num : wf_num e ; inv_wf_graph : wf_graph e ; wf_noblack_towhite : noblack_to_white e ; inv_sccs : sccs e = black_gsccs e ; } . Pre-conditions are stored in a record and are similar to the ones defined in Why3:all the gray vertices of e are connected to all the elements of roots . and all the invariantshold. Definition access_to e ( roots : { set V } ) := { in gray e & roots , ∀ x y , gconnect x y } . Record pre_dfs ( roots : { set V } ) ( e : env ) := PreDfs { pre_access_to : access_to e roots ; pre_invariants : invariants e } . The post-conditions are expressed slightly differently mostly because we take advan-tage of the expressivity of big operators [1]. The bigcup operator (typeset as \ bigcup )12s defined in the Mathematical Component Library and represents indexed union ofsets. The bigmin operator (typeset as \ min ) represents the minimum of a set of nat-ural numbers (and should be included in future version of the Library). Defining theminimum of the empty set is a bit problematic since one would like to preserve theproperty that the minimum of a subset is never smaller than the minimum of the fullset. This is why the bigmin does not work directly on sets of natural numbers but onsets of elements of an ordinal type I n (the type of all the natural numbers smaller than n ). This type has the key property of having a maximal element n . This is the valuegiven to the minimum of the empty set. In our use case, as ∞ is defined as the numberof vertices plus one, we simple take n = ∞ .The post-conditions are then expressed by a record that states that the invariantshold, the next environment is an extension of the old one, the new white vertices havebeen decremented by the vertices that are reachable from the roots by white verticesand finally the returned value m is exactly the smallest number from all the vertices thathave lost their white color. Record post_dfs ( roots : { set V } ) ( e e ’ : env ) ( m : nat ) := PostDfs { post_invariants : invariants e ’; post_subenv : subenv e e ’; post_whites : whites e ’ = white e : \ : \ bigcup_ ( x in roots ) wreach e x ; post_num : m = \ min_ ( y in \ bigcup_ ( x in roots ) wreach e x ) @inord ∞ ( num e ’ y ); } . Note that we have defined the predicate wreach to express the reachability throughwhite vertices and we are using the explicit cast inord to turn a number associated to avertex into an element of I ∞ .Now we can state the correctness of dfs and dfs1 Definition dfs_correct ( dfs : { set V } -> env -> nat ∗ env ) roots e :=pre_dfs roots e ->let ( m , e ’) := dfs roots e in post_dfs roots e e ’ m . Definition dfs1_correct ( dfs1 : V -> env -> nat ∗ env ) x e := ( x ∈ white e ) -> pre_dfs [ set x ] e ->let ( m , e ’) := dfs1 x e in post_dfs [ set x ] e e ’ m . where [set x] represents the set whose only element is x . The two central theorems toprove are then Lemma dfs_is_correct dfs1 dfsrec ( roots : { set V } ) e :( ∀ x , x ∈ roots -> dfs1_correct dfs1 x e ) -> ( ∀ x , x ∈ roots -> ∀ e1 , white e1 \ subset white e ->dfs_correct dfsrec ( roots : \ x ) e1 ) ->dfs_correct ( dfs dfs1 dfsrec ) roots e . Lemma dfs1_is_correct dfs ( x : V ) e :( dfs_correct dfs [ set y | edge x y ] ( add_stack x e )) ->dfs1_correct ( dfs1 dfs ) x e . = 1 l ≤ l ≤ l ≤ l = 35 l = 70 l = 328
37 25 5 3 1 1 1
Table 2: Sizes (numbers l of lines) of the 73 proofs in the file tarjan num .They simply state that the results of dfs and dfs1 are correct if their respective recursivecalls are correct. The proof of the first lemma is straightforward since dfs simply iter-ates on a list. It is mostly some book-keeping between what is known and what needsto be proved. This is done in about 70 lines. The second one is more intricate andrequires 328 lines. Gluing these two theorems together and proving termination givesus an extra 20 lines to prove the theorem Theorem tarjan_rec_terminates n roots e : n ≥ | white e | ∗ | V | .+1 + | roots | ->dfs_correct ( tarjan_rec n ) roots e . From this last theorem the correctness of tarjan follows directly.Some quantitative information should be added. The C OQ contribution is com-posed of three files. The bigmin file defines the bigmin operator and is 160 lines long.The extra file defines some notions of graph theory that were not available in the currentMathematical Component Library. For example, it is where conditional reachability isdefined. This file is 350 lines long. The main file is tarjan num and is 1185 lineslong. It is compiled in 10 seconds with a memory footprint of 900 Mb (half of whichis resident) on a Intel R (cid:13) i7 2.60GHz quad-core laptop running Linux. The proofs areperformed in the SSR EFLECT proof language [12] with very little automation. Theproof script is mostly procedural, alternating book-keeping tactics ( move ) with trans-formational ones (mostly rewrite and apply ), but often intermediate steps are explicitlydeclared with the have tactic. There are more than a hundred of such intermediate stepsin the 700 lines of proof of the file tarjan num . Table 2 gives the distribution of thenumbers of lines of these proofs. Most of them are one-liners and the only complicatedproof is the one corresponding to the lemma dfs1 is correct . Isabelle/HOL [19] is the encoding of simply typed higher-order logic in the logicalframework Isabelle [20]. Unlike Why3, it is not primarily intended as an environmentfor program verification and does not contain specific syntax for stating pre- and post-conditions or intermediate assertions in function definitions. Logics and formalismsfor program verification have been developed within Isabelle/HOL (e.g., [14]), butthey target imperative rather than functional programming, so we simply formalize thealgorithm as an Isabelle function. Isabelle/HOL provides an extensive library of datastructures and proofs; in this development we mainly rely on the set and list libraries.We start by introducing a locale , fixing parameters and assumptions for the remainderof the proof. We explicitly assume that the set of vertices is finite: by default, sets maybe infinite in Isabelle/HOL. 14 ocale graph =fixes vertices :: ν setand successors :: ν ⇒ ν setassumes finite verticesand ∀ v ∈ vertices. successors v ⊆ vertices We introduce reachability in graphs using an inductive predicate definition, rather thanvia an explicit reference to paths as in the Why3 definition. Isabelle then generatesappropriate induction theorems for use in proofs. inductive reachable wherereachable x x| [[ y ∈ successors x; reachable y z ]] = ⇒ reachable x z The definition of strongly connected components mirrors that used in Why3. The fol-lowing lemma states that SCCs are disjoint; its one-line proof is found automaticallyusing sledgehammer [2], which heuristically selects suitable lemmas from the set ofavailable facts (including Isabelle’s library), invokes several automatic provers, and incase of success reconstructs a proof that is checked by the Isabelle kernel. lemma scc-partition:assumes is-scc S and is-scc S’ and x ∈ S ∩ S’shows S = S’
Environments are represented by records, similar to the formalization in Why3, exceptthat there is no distinction between regular and “ghost” fields. Also, the definition ofthe well-formedness predicate closely mirrors that used in Why3. record ν env =black :: ν set gray :: ν setstack :: ν list sccs :: ν set setsn :: nat num :: ν ⇒ intdefinition wf_env where wf_env e ≡ wf_color e ∧ wf_num e ∧ distinct (stack e) ∧ no_black_to_white e ∧ ( ∀ x y. y (cid:22) x in (stack e) −→ reachable x y) ∧ ( ∀ y ∈ set (stack e). ∃ g ∈ gray e.y (cid:22) g in (stack e) ∧ reachable y g) ∧ sccs e = { C . C ⊆ black e ∧ is_scc C } The definition of the two mutually recursive functions dfs1 and dfs again closely fol-lows their representation in Why3. function (domintros) dfs1 and dfs wheredfs1 x e =(let (n1,e1) = dfs (successors x)(add_stack_incr x e) inif n1 < int (sn e) then (n1, add_black x e1)else (let (l,r) = split_list x (stack e1) in We use infix syntax and the symbol (cid:22) to denote precedence. The correspondence between numbers ofvertices in the stack and precedence is asserted by the invariant wf num . + ∞ , ( | black = insert x (black e1),gray = gray e, stack = r, sn = sn e1,sccs = insert (set l) (sccs e1),num = set_infty l (num e1) | ) ))) anddfs roots e =(if roots = {} then ( + ∞ , e)else (let x = SOME x. x ∈ roots;res1 = (if num e x (cid:54) = -1then (num e x, e)else dfs1 x e);res2 = dfs (roots - {x}) (snd res1)in (min (fst res1) (fst res2),snd res2) )) The function keyword introduces the definition of a recursive function. Isabelle checksthat the definition is well-formed and generates appropriate simplification and induc-tion theorems. Because HOL is a logic of total functions, it introduces two proofobligations: the first one requires the user to prove that the cases in the function def-initions cover all type-correct arguments; this holds trivially for the above definitions.The second obligation requires exhibiting a well-founded ordering on the function pa-rameters that ensures the termination of recursive function invocations, and Isabelleprovides a number of heuristics that work in many cases. However, the functions de-fined above will in fact not terminate for arbitrary calls, in particular for environmentsthat assign sequence number − to non-white vertices. The domintros attribute in-structs Isabelle to consider these functions as “partial”. More precisely, it introducesan explicit predicate representing the domains for which the functions are defined. This“domain condition” appears as a hypothesis in the simplification rules that mirror thefunction definitions so that the user can assert the equality of the left- and right-handsides of the definitions only if the domain predicate holds. Isabelle also proves (mutu-ally inductive) rules for proving when the domain condition is guaranteed to hold. Ourfirst objective is therefore to establish sufficient conditions that ensure the terminationof the two functions. Assuming the domain condition, we prove that the functionsnever decrease the set of colored vertices and that vertices are never explicitly assignedthe number − by our functions. Denoting the union of gray and black vertices as colored , we define the predicate definition colored_num where colored_num e ≡∀ v ∈ colored e. v ∈ vertices ∧ num e v (cid:54) = -1 and show that this predicate is an invariant of the functions. We can then show that thetriple defined as (vertices - colored e, {x}, 1)(vertices - colored e, roots, 2) for the arguments of dfs1 and dfs , respectively, decreases w.r.t. lexicographical orderingon finite subset inclusion and < on natural numbers across recursive function calls,provided that colored num holds when the function is called and x is a white vertex16resp., roots is a set of vertices). These conditions are therefore sufficient to ensure thatthe domain condition holds: theorem dfs1_dfs_termination: [[ x ∈ vertices - colored e; colored_num e ]] = ⇒ dfs1_dfs_dom (Inl(x,e)) [[ roots ⊆ vertices; colored_num e ]] = ⇒ dfs1_dfs_dom (Inr(roots,e)) The proof of partial correctness follows the same ideas as the proof presented for Why3.We define the pre- and post-conditions of the two functions as predicates in Isabelle.For example, the predicates for dfs1 are defined as follows: definition dfs1_pre where dfs1_pre e ≡ wf_env e ∧ x ∈ vertices ∧ x / ∈ colored e ∧ ( ∀ g ∈ gray e. reachable g x)definition dfs1_post where dfs1_post x e res ≡ let n = fst res; e’ = snd resin wf_env e’ ∧ subenv e e’ ∧ roots ⊆ colored e’ ∧ ( ∀ x ∈ roots. n ≤ num e’ x) ∧ (n = + ∞ ∨ ( ∃ x ∈ roots. ∃ y in set (stack e’).num e’ y = n ∧ reachable x y)) We now show the following theorems: • The pre-condition of each function establishes the pre-condition of every recur-sive call appearing in the body of that function. For the second recursive call inthe body of dfs we also assume the post-condition of the first recursive call. • The pre-condition of each function, plus the post-conditions of each recursivecall in the body of that function, establishes the post-condition of the function.Combining these results, we establish partial correctness: theorem dfs_partial_correct: [[ dfs1_dfs_dom (Inl(x,e)); dfs1_pre x e ]] = ⇒ dfs1_post x e (dfs1 x e) [[ dfs1_dfs_dom (Inr(roots,e)); dfs_pre roots e ]] = ⇒ dfs_post roots e (dfs roots e) We define the initial environment and the overall function. definition init_env where init_env ≡ ( | black = {}, gray = {},stack = [], sccs = {},sn = 0, num = λ _. -1 | ) definition tarjan where tarjan ≡ sccs (snd (dfs vertices init_env)) Observe that Isabelle introduces a single operator corresponding to the two mutually recursive functionswhose domain is the disjoint sum of the domains of both functions. = 1 i ≤ i ≤ i ≤ i ≤ i = 35 i = 43 i = 48
28 8 4 1 2 1 1 1
Table 3: Distribution of interactions in the Isabelle proofs.It is trivial to show that the arguments to the call of dfs in the definition of tarjan satisfy the pre-condition of dfs . Putting together the theorems establishing terminationand partial correctness, we obtain the desired total correctness results. theorem dfs_correct:dfs1_pre x e = ⇒ dfs1_post x e (dfs1 x e)dfs_pre roots e = ⇒ dfs_post roots e (dfs roots e)theorem tarjan_correct:tarjan = { C . is_scc C ∧ C ⊆ vertices } The intermediate assertions appearing in the Why3 code guided the overall proof: theyare established either as separate lemmas or as intermediate steps within the proofs ofthe above theorems. Similar as in the C OQ proof, the overall induction proof was ex-plicitly decomposed into individual lemmas as laid out above. In particular, whereasWhy3 identifies the predicates that can be used from the function code and its annota-tion with pre- and post-conditions, these assertions appear explicitly in the intermediatelemmas used in the proof of theorem dfs partial correct . The induction rules that Is-abelle generated from the function definitions was helpful for finding the appropriatedecomposition of the overall correctness proof.Despite the extensive use of sledgehammer for invoking automatic back-end provers,including the SMT solvers CVC4 and Z3, from Isabelle, we found that in comparisonto Why3, significantly more user interactions were necessary in order to guide theproof. Although many of those were straightforward, a few required thinking abouthow a given assertion could be derived from the facts available in the context. Ta-ble 3 indicates the distribution of the number of interactions used for the proofs of the46 lemmas the theory contains. These numbers cannot be compared directly to thoseshown in Table 2 for the C OQ proof because an Isabelle interaction is typically muchcoarser-grained than a line in a C OQ proof. As in the case of Why3 and C OQ , theproofs of partial correctness of dfs1 (split into two lemmas following the case distinc-tion) and of dfs required the most effort. It took about one person-month to carry outthe case study, starting from an initial version of the Why3 proof. Processing the entireIsabelle theory on a laptop with a 2.7 GHz Intel R (cid:13) Core i5 (dual-core) processor and8 GB of RAM takes 35 seconds of CPU time.
Our proof refers to colors, finite sets, and the stack, although the general argumentseems to be about properties of strongly connected components in spanning trees. Thealgorithmician would explain the algorithm with spanning trees as in Tarjan’s article.It would be nice to extract a program from such a proof, but beside the fact that this18s not so easy, programmers like to understand the proof in terms of variables and datathat their program is using.A first version of the formal proof used ranks in the working stack and a flat repre-sentation of environments by adding extra arguments to functions for the black, gray,sccs sets and the stack. That was perfect for the automatic provers of Why3. But afterremodelling the proof in Coq and Isabelle/HOL, it was simpler to gather these extraarguments in records and have a single extra argument for environments. Also ranks disappeared leaving space to the num function and the precedence relation, which areeasier to understand. The automatic provers have more difficulties with the inlining ofenvironments, but with a few hints they could still succeed.Finally, coloring of vertices is usual for graph algorithms, but we are aware thata proof without colors is feasible and has indeed been done without colors in Coq(see [8]). The stack used in our algorithm is also not necessary since it is just used toefficiently output new strongly connected components. The proof can be implementedwith just finite sets, and the components will be obtained by computing differencesbetween visited sets of vertices. However, the stack-based formulation ensures that thealgorithm works in linear time, and then it must be present in the proof, and its contentmust be related to the visited sets of vertices.There is thus a balance between the concision of the proof and its relation to thereal algorithm. In our presentation, we therefore have allowed for a few redundancies.
The formal proof expressed in this article was initially designed and implemented inWhy3 [7] after a long process, nearly a 2-year half-time work with many attempts ofproofs about various graph algorithms (depth first search, Kosaraju strong connectivity,bi-connectivity, articulation points, minimum spanning tree). The big advantage ofWhy3 is the clear separation between programs and the logic with Hoare-logic styleassertions and pre-/post-conditions about functions. It makes the correctness proofquite readable for a programmer. Also first-order logic is easy to understand. Moreover,one can prove partial correctness without caring about termination.Another important feature of Why3 is its interface with off-the-shelf theorem provers(mainly SMT provers). Thus the system benefits of the current technology in theoremprovers and clerical sub-goals can be delegated to these provers, which makes the over-all proof shorter and easier to understand. Although the proof must be split in moreelementary pieces, this has the benefit of improving its readability. Several hints aboutinlining or induction reasoning are still needed. Also, despite a useful XML file thatrecords sessions and facilitates incremental proofs, sometimes seemingly minor modi-fications to the formulation of the algorithm or the predicates may result in the proversno longer being able to handle a proof obligation automatically.The C OQ and Isabelle proofs were inspired from the Why3 proof. Their develop-ment therefore required much less time although their text is longer. The C OQ proofuses the SSR EFLECT macros and the Mathematical Components library, which helpsreduce the size of the proof compared to classical C OQ . The proof also uses the bigopslibrary and several other higher-order features which makes the proof more abstract19nd closer to Tarjan’s original proof.In C OQ , recursion cannot be used without proving termination. This requires anagile treatment of mutually recursive definitions of functions. Partial correctness canbe proved by considering the functionals of which the recursive definitions are the fixedpoint, and passing as arguments the pre/post-conditions of these functions. Moreoverthe recursive definitions take as extra argument the number of recursive calls, in orderto postpone the termination argument.Our C OQ proof does not use significant automation . All details are explicitlyexpressed, but many of them were already present in the Mathematical Componentslibrary. Moreover, a proof certificate is produced and a functional program could inprinciple be extracted. The absence of automation makes the system very stable to usesince the proof script is explicit, but it requires a higher degree of expertise from theuser. Still, this lack of automation gives the user a direct feedback of how well thedefinitions work together. This led us to develop an alternative and more concise (50%shorter) formalization without colors [8].The Isabelle/HOL proof was the last one to be implemented. It closely followsthe Why3 proof, and can be seen as a mid-point between the Why3 and C OQ proofs.It uses higher order logic and the level of abstraction is close to the one of the C OQ proof, although more readable in this case study. The proof makes use of Isabelle’sextensive support for automation. In particular, sledgehammer [2] was very usefulfor finding individual proof steps. It heuristically selects lemmas and facts availablein the context and then calls automatic provers (SMT solvers and superposition-basedprovers for first-order logic). When one of these provers finds a proof, sledgehammerattempts to find a proof that can be certified by the Isabelle kernel, using various proofmethods such as combinations of rewriting and first-order reasoning (blast, fastforceetc.), calls to the metis prover or reconstruction of SMT proofs through the smt proofmethod. Unlike in Why3, the automatic provers used to find the initial proof are notpart of the trusted code base because ultimately the proof is checked by the kernel. Theprice to pay is that the degree of automation in Isabelle is still significantly lower com-pared to Why3. Adapting the proof to modified definitions was fast: the Isabelle/jEditGUI eagerly processes the proof script and quickly indicates those steps that requireattention.The Isabelle proof also faces the termination problem to achieve general consis-tency. Since termination cannot be ensured for arbitrary arguments, the treatment oftermination is delayed with the use of the domintros predicate. The proofs of termi-nation and of partial correctness are independent; in particular, we obtain a weakerpredicate ensuring termination than the one used for partial correctness. Although thebasic principle of the termination proof is very similar to the C OQ proof and relieson considering functionals of which the recursive functions are fixpoints, the techni-cal formulation is more flexible because we rely on proving well-foundedness of anappropriate relation rather than computing an explicit upper bound on the number ofrecursive calls.One strong point of Isabelle/HOL is its nice L A TEX output and the flexibility of its Hammers exist for C OQ [9, 10] but unfortunately they currently perform badly when used in conjunctionwith the Mathematical Components library. OQ , and Isabelle/HOL are mature, and eachone has its own advantages w.r.t. readability, expressivity, stability or mechanization.Coming up with invariants that are both strong enough and understandable was byfar the hardest part in this work. This effort requires creativity and understanding,although proof assistants provide some help: missing predicates can be discovered byunderstanding which parts of the proof fail. We think that formalizing the proof inall three systems was very rewarding and helped us better understand the state of theart in computer-aided deductive program verification. It could be also interesting toexperiment this proof in other formal systems and establish comparisons based on thisquite challenging example .Another interesting work would be to verify an implementation of this algorithmwith imperative programs and concrete data structures. This will complexify the proof,since mutable variables and mutable data structures have to be considered. Severalattempts were already exposed [4, 5, 14] and it would be interesting to also developthem simultaneously in various formal systems and to understand how these proofscan be derived from ours.A final and totally different remark is about teaching of algorithms. Do we wantstudents to formally prove algorithms, or to present algorithms with assertions, pre-and post-conditions, and make them prove these assertions informally as exercises? Inboth cases, we believe that our work could make a useful contribution. Acknowledgements.
We thank the Why3 team at Inria-Saclay/LRI-Orsay for veryvaluable advice. This material is based upon work partly supported by the proofinuse project ANR-13-LAB3-0007.
References [1] Yves Bertot, Georges Gonthier, Sidi Ould Biha, and Ioana Pasca. Canonical BigOperators. In
TPHOLs , volume 5170 of
LNCS , Montreal, Canada, 2008.[2] Jasmin Christian Blanchette, Sascha B¨ohme, and Lawrence C. Paulson. Extend-ing sledgehammer with SMT solvers.
J. Automated Reasoning , 51(1):109–128,2013.[3] Franc¸ois Bobot, Jean-Christophe Filliˆatre, Claude March´e, GuillaumeMelquiond, and Andrei Paskevich.
The Why3 platform, version 0.86.1 .LRI, CNRS & Univ. Paris-Sud & INRIA Saclay, version 0.86.1 edition, May2015. Available at why3.lri.fr/download/manual-0.86.1.pdf .[4] Arthur Chargu´eraud. Characteristic formulae for the verification of imperativeprograms. (Journal version of ICFP’11) Submitted, October 2012. We have set up a Web page in order to collect formalizations.
Proceedings of the 5th ACM SIGPLAN Conference on Certified Programs andProofs , CPP 2016, pages 3–14, New York, NY, USA, January 2016. ACM.[6] Ran Chen and Jean-Jacques L´evy. Full scripts of Tarjan SCC Why3 proof. Tech-nical report, Iscas and Inria, 2017. jeanjacqueslevy.net/why3 .[7] Ran Chen and Jean-Jacques L´evy. A semi-automatic proof of strong connectivity.In
Proceedings of the 9th Working Conference on Verified Software: Theories,Tools, and Experiments , VSTTE, July 2017.[8] Cyril Cohen and Laurent Th´ery. Full script of Tarjan SCC Coq/ssreflect proof,2017. Available at .[9] Łukasz Czajka and Cezary Kaliszyk. Hammer for coq: Automation for dependenttype theory.
J. Autom. Reasoning , 61(1-4):423–453, 2018.[10] Burak Ekici, Alain Mebsout, Cesare Tinelli, Chantal Keller, Guy Katz, AndrewReynolds, and Clark W. Barrett. Smtcoq: A plug-in for integrating SMT solversinto coq. In
CAV (2) , volume 10427 of
LNCS , pages 126–133. Springer, 2017.[11] Jean-Christophe Filliˆatre et al. The why3 gallery of verified programs. Technicalreport, CNRS, Inria, U. Paris-Sud, 2015. toccata.lri.fr/gallery/why3.en.html .[12] Georges Gonthier and Assia Mahboubi. An introduction to small scale reflectionin coq.
J. Formalized Reasoning , 3(2):95–152, 2010.[13] Aquinas Hobor and Jules Villard. The ramifications of sharing in data structures.In
Proceedings of the 40th Annual ACM SIGPLAN-SIGACT Symposium on Prin-ciples of Programming Languages , POPL ’13, pages 523–536, New York, NY,USA, 2013. ACM.[14] Peter Lammich. Refinement to imperative/hol. In Christian Urban and XingyuanZhang, editors,
Proc. 6th Intl. Conf. Interactive Theorem Proving (ITP 2015) ,volume 9236 of
LNCS , pages 253–269, Nanjing, China, 2015. Springer.[15] Peter Lammich and Ren´e Neumann. A framework for verifying depth-first searchalgorithms. In
Proceedings of the 2015 Conference on Certified Programs andProofs , CPP ’15, pages 137–146, New York, NY, USA, 2015. ACM.[16] Assia Mahboubi and Enrico Tassi.
Mathematical Components . Available at: https://math-comp.github.io/mcb/ , 2016.[17] Farhad Mehta and Tobias Nipkow. Proving pointer programs in higher-orderlogic. In
CADE , 2003.[18] Stephan Merz. Isabelle formalization of tarjan’s algorithm, 2018. Available at https://members.loria.fr/SMerz/papers/cpp2019.html .2219] Tobias Nipkow, Lawrence Paulson, and Markus Wenzel.
Isabelle/HOL. A ProofAssistant for Higher-Order Logic . Number 2283 in Lecture Notes in ComputerScience. Springer Verlag, 2002.[20] Lawrence C. Paulson.
Isabelle: A Generic Theorem Prover , volume 828 of
Lec-ture Notes in Computer Science . Springer Verlag, 1994.[21] Christopher M. Poskitt and Detlef Plump. Hoare logic for graph programs. In
VSTTE , 2010.[22] Franc¸ois Pottier. Depth-first search and strong connectivity in Coq. In
Journ´eesFrancophones des Langages Applicatifs (JFLA 2015) , January 2015.[23] Azalea Raad, Aquinas Hobor, Jules Villard, and Philippa Gardner. Verifyingconcurrent graph algorithms. In Atsushi Igarashi, editor,
Proc. 14th Asian Symp.Programming Languages and Systems (APLAS 2016) , pages 314–334, Hanoi,Vietnam, 2016. Springer.[24] Ilya Sergey, Aleksandar Nanevski, and Anindya Banerjee. Mechanized verifi-cation of fine-grained concurrent programs. In
Proceedings of the 36th ACMSIGPLAN Conference on Programming Language Design and Implementation ,PLDI ’15, pages 77–87, New York, NY, USA, 2015. ACM.[25] Robert Tarjan. Depth first search and linear graph algorithms.
SIAM Journal onComputing , 1972.[26] Laurent Th´ery. Formally-proven Kosaraju’s algorithm. Inria report, Hal-01095533, 2015.[27] Ingo Wengener. A simplified correctness proof for a well-known algorithmcomputing strongly connected components.
Information Processing Letters ,83(1):17–19, 2002.[28] Markus Wenzel. Isar – a generic interpretative approach to readable formalproof documents. In Yves Bertot, Gilles Dowek, Andr´e Hirschowitz, ChristinePaulin-Mohring, and Laurent Th´ery, editors, , volume 1690 of
LNCS , pages 167–184,Nice, France, 1999. Springer.[29] Freek Wiedijk.
The Seventeen Provers of the World , volume 3600 of