2 Regular Expressions Equivalence
5 include "tutorial/chapter9.ma".
7 (* We say that two pres 〈i_1,b_1〉 and 〈i_1,b_1〉 are {\em cofinal} if and
10 definition cofinal ≝ λS.λp:(pre S)×(pre S).
11 \snd (\fst p) = \snd (\snd p).
13 (* As a corollary of decidable_sem, we have that two expressions
14 e1 and e2 are equivalent iff for any word w the states reachable
15 through w are cofinal. *)
17 theorem equiv_sem: ∀S:DeqSet.∀e1,e2:pre S.
18 \sem{e1} =1 \sem{e2} ↔ ∀w.cofinal ? 〈moves ? w e1,moves ? w e2〉.
21 cut (∀b1,b2. iff (b1 = true) (b2 = true) → (b1 = b2))
22 [* * // * #H1 #H2 [@sym_eq @H1 //| @H2 //]]
23 #Hcut @Hcut @iff_trans [|@decidable_sem]
24 @iff_trans [|@same_sem] @iff_sym @decidable_sem
25 |#H #w1 @iff_trans [||@decidable_sem] <H @iff_sym @decidable_sem]
28 (* This does not directly imply decidability: we have no bound over the
29 length of w; moreover, so far, we made no assumption over the cardinality
30 of S. Instead of requiring S to be finite, we may restrict the analysis
31 to characters occurring in the given pres. *)
33 definition occ ≝ λS.λe1,e2:pre S.
34 unique_append ? (occur S (|\fst e1|)) (occur S (|\fst e2|)).
36 lemma occ_enough: ∀S.∀e1,e2:pre S.
37 (∀w.(sublist S w (occ S e1 e2))→ cofinal ? 〈moves ? w e1,moves ? w e2〉)
38 →∀w.cofinal ? 〈moves ? w e1,moves ? w e2〉.
40 cases (decidable_sublist S w (occ S e1 e2)) [@H] -H #H
41 >to_pit [2: @(not_to_not … H) #H1 #a #memba @sublist_unique_append_l1 @H1 //]
42 >to_pit [2: @(not_to_not … H) #H1 #a #memba @sublist_unique_append_l2 @H1 //]
46 (* The following is a stronger version of equiv_sem, relative to characters
47 occurring the given regular expressions. *)
49 lemma equiv_sem_occ: ∀S.∀e1,e2:pre S.
50 (∀w.(sublist S w (occ S e1 e2))→ cofinal ? 〈moves ? w e1,moves ? w e2〉)
52 #S #e1 #e2 #H @(proj2 … (equiv_sem …)) @occ_enough #w @H
59 We say that a list of pairs of pres is a bisimulation if it is closed
60 w.r.t. moves, and all its members are cofinal.
63 definition sons ≝ λS:DeqSet.λl:list S.λp:(pre S)×(pre S).
64 map ?? (λa.〈move S a (\fst (\fst p)),move S a (\fst (\snd p))〉) l.
66 lemma memb_sons: ∀S,l.∀p,q:(pre S)×(pre S). memb ? p (sons ? l q) = true →
67 ∃a.(move ? a (\fst (\fst q)) = \fst p ∧
68 move ? a (\fst (\snd q)) = \snd p).
69 #S #l elim l [#p #q normalize in ⊢ (%→?); #abs @False_ind /2/]
70 #a #tl #Hind #p #q #H cases (orb_true_l … H) -H
71 [#H @(ex_intro … a) >(\P H) /2/ |#H @Hind @H]
74 definition is_bisim ≝ λS:DeqSet.λl:list ?.λalpha:list S.
75 ∀p:(pre S)×(pre S). memb ? p l = true → cofinal ? p ∧ (sublist ? (sons ? alpha p) l).
77 (* Using lemma equiv_sem_occ it is easy to prove the following result: *)
79 lemma bisim_to_sem: ∀S:DeqSet.∀l:list ?.∀e1,e2: pre S.
80 is_bisim S l (occ S e1 e2) → memb ?〈e1,e2〉 l = true → \sem{e1}=1\sem{e2}.
81 #S #l #e1 #e2 #Hbisim #Hmemb @equiv_sem_occ
82 #w #Hsub @(proj1 … (Hbisim 〈moves S w e1,moves S w e2〉 ?))
83 lapply Hsub @(list_elim_left … w) [//]
84 #a #w1 #Hind #Hsub >moves_left >moves_left @(proj2 …(Hbisim …(Hind ?)))
85 [#x #Hx @Hsub @memb_append_l1 //
86 |cut (memb S a (occ S e1 e2) = true) [@Hsub @memb_append_l2 //] #occa
91 (* This is already an interesting result: checking if l is a bisimulation
92 is decidable, hence we could generate l with some untrusted piece of code
93 and then run a (boolean version of) is_bisim to check that it is actually
95 However, in order to prove that equivalence of regular expressions
96 is decidable we must prove that we can always effectively build such a list
97 (or find a counterexample).
98 The idea is that the list we are interested in is just the set of
99 all pair of pres reachable from the initial pair via some
102 The algorithm for computing reachable nodes in graph is a very
103 traditional one. We split nodes in two disjoint lists: a list of
104 visited nodes and a frontier, composed by all nodes connected
105 to a node in visited but not visited already. At each step we select a node
106 a from the frontier, compute its sons, add a to the set of
107 visited nodes and the (not already visited) sons to the frontier.
109 Instead of fist computing reachable nodes and then performing the
110 bisimilarity test we can directly integrate it in the algorithm:
111 the set of visited nodes is closed by construction w.r.t. reachability,
112 so we have just to check cofinality for any node we add to visited.
114 Here is the extremely simple algorithm: *)
116 let rec bisim S l n (frontier,visited: list ?) on n ≝
118 [ O ⇒ 〈false,visited〉 (* assert false *)
121 [ nil ⇒ 〈true,visited〉
123 if beqb (\snd (\fst hd)) (\snd (\snd hd)) then
124 bisim S l m (unique_append ? (filter ? (λx.notb (memb ? x (hd::visited)))
125 (sons S l hd)) tl) (hd::visited)
130 (* The integer n is an upper bound to the number of recursive call,
131 equal to the dimension of the graph. It returns a pair composed
132 by a boolean and a the set of visited nodes; the boolean is true
133 if and only if all visited nodes are cofinal.
135 The following results explicitly state the behaviour of bisim is the general
136 case and in some relevant instances *)
138 lemma unfold_bisim: ∀S,l,n.∀frontier,visited: list ?.
139 bisim S l n frontier visited =
141 [ O ⇒ 〈false,visited〉 (* assert false *)
144 [ nil ⇒ 〈true,visited〉
146 if beqb (\snd (\fst hd)) (\snd (\snd hd)) then
147 bisim S l m (unique_append ? (filter ? (λx.notb(memb ? x (hd::visited)))
148 (sons S l hd)) tl) (hd::visited)
152 #S #l #n cases n // qed.
154 lemma bisim_never: ∀S,l.∀frontier,visited: list ?.
155 bisim S l O frontier visited = 〈false,visited〉.
156 #frontier #visited >unfold_bisim //
159 lemma bisim_end: ∀Sig,l,m.∀visited: list ?.
160 bisim Sig l (S m) [] visited = 〈true,visited〉.
161 #n #visisted >unfold_bisim //
164 lemma bisim_step_true: ∀Sig,l,m.∀p.∀frontier,visited: list ?.
165 beqb (\snd (\fst p)) (\snd (\snd p)) = true →
166 bisim Sig l (S m) (p::frontier) visited =
167 bisim Sig l m (unique_append ? (filter ? (λx.notb(memb ? x (p::visited)))
168 (sons Sig l p)) frontier) (p::visited).
169 #Sig #l #m #p #frontier #visited #test >unfold_bisim whd in ⊢ (??%?); >test //
172 lemma bisim_step_false: ∀Sig,l,m.∀p.∀frontier,visited: list ?.
173 beqb (\snd (\fst p)) (\snd (\snd p)) = false →
174 bisim Sig l (S m) (p::frontier) visited = 〈false,visited〉.
175 #Sig #l #m #p #frontier #visited #test >unfold_bisim whd in ⊢ (??%?); >test //
178 (* In order to prove termination of bisim we must be able to effectively
179 enumerate all possible pres: *)
181 (* lemma notb_eq_true_l: ∀b. notb b = true → b = false.
182 #b cases b normalize //
185 let rec pitem_enum S (i:re S) on i ≝
189 | s y ⇒ (ps S y)::(pp S y)::[]
190 | o i1 i2 ⇒ compose ??? (po S) (pitem_enum S i1) (pitem_enum S i2)
191 | c i1 i2 ⇒ compose ??? (pc S) (pitem_enum S i1) (pitem_enum S i2)
192 | k i ⇒ map ?? (pk S) (pitem_enum S i)
195 lemma pitem_enum_complete : ∀S.∀i:pitem S.
196 memb (DeqItem S) i (pitem_enum S (|i|)) = true.
199 |3,4:#c normalize >(\b (refl … c)) //
200 |5,6:#i1 #i2 #Hind1 #Hind2 @(memb_compose (DeqItem S) (DeqItem S)) //
201 |#i #Hind @(memb_map (DeqItem S)) //
205 definition pre_enum ≝ λS.λi:re S.
206 compose ??? (λi,b.〈i,b〉) ( pitem_enum S i) (true::false::[]).
208 lemma pre_enum_complete : ∀S.∀e:pre S.
209 memb ? e (pre_enum S (|\fst e|)) = true.
210 #S * #i #b @(memb_compose (DeqItem S) DeqBool ? (λi,b.〈i,b〉))
211 // cases b normalize //
214 definition space_enum ≝ λS.λi1,i2: re S.
215 compose ??? (λe1,e2.〈e1,e2〉) ( pre_enum S i1) (pre_enum S i2).
217 lemma space_enum_complete : ∀S.∀e1,e2: pre S.
218 memb ? 〈e1,e2〉 ( space_enum S (|\fst e1|) (|\fst e2|)) = true.
219 #S #e1 #e2 @(memb_compose … (λi,b.〈i,b〉))
222 definition all_reachable ≝ λS.λe1,e2: pre S.λl: list ?.
224 ∀p. memb ? p l = true →
225 ∃w.(moves S w e1 = \fst p) ∧ (moves S w e2 = \snd p).
227 definition disjoint ≝ λS:DeqSet.λl1,l2.
228 ∀p:S. memb S p l1 = true → memb S p l2 = false.
230 (* We are ready to prove that bisim is correct; we use the invariant
231 that at each call of bisim the two lists visited and frontier only contain
232 nodes reachable from 〈e_1,e_2〉, hence it is absurd to suppose to meet a pair
233 which is not cofinal. *)
235 lemma bisim_correct: ∀S.∀e1,e2:pre S.\sem{e1}=1\sem{e2} →
236 ∀l,n.∀frontier,visited:list ((pre S)×(pre S)).
237 |space_enum S (|\fst e1|) (|\fst e2|)| < n + |visited|→
238 all_reachable S e1 e2 visited →
239 all_reachable S e1 e2 frontier →
240 disjoint ? frontier visited →
241 \fst (bisim S l n frontier visited) = true.
242 #Sig #e1 #e2 #same #l #n elim n
243 [#frontier #visited #abs * #unique #H @False_ind @(absurd … abs)
244 @le_to_not_lt @sublist_length // * #e11 #e21 #membp
245 cut ((|\fst e11| = |\fst e1|) ∧ (|\fst e21| = |\fst e2|))
246 [|* #H1 #H2 <H1 <H2 @space_enum_complete]
247 cases (H … membp) #w * #we1 #we2 <we1 <we2 % >same_kernel_moves //
248 |#m #HI * [#visited #vinv #finv >bisim_end //]
249 #p #front_tl #visited #Hn * #u_visited #r_visited * #u_frontier #r_frontier
251 cut (∃w.(moves ? w e1 = \fst p) ∧ (moves ? w e2 = \snd p))
252 [@(r_frontier … (memb_hd … ))] #rp
253 cut (beqb (\snd (\fst p)) (\snd (\snd p)) = true)
254 [cases rp #w * #fstp #sndp <fstp <sndp @(\b ?)
255 @(proj1 … (equiv_sem … )) @same] #ptest
256 >(bisim_step_true … ptest) @HI -HI
258 |% [whd in ⊢ (??%?); >(disjoint … (memb_hd …)) whd in ⊢ (??%?); //
259 |#p1 #H (cases (orb_true_l … H)) [#eqp >(\P eqp) // |@r_visited]
261 |whd % [@unique_append_unique @(andb_true_r … u_frontier)]
262 @unique_append_elim #q #H
263 [cases (memb_sons … (memb_filter_memb … H)) -H
264 #a * #m1 #m2 cases rp #w1 * #mw1 #mw2 @(ex_intro … (w1@(a::[])))
265 >moves_left >moves_left >mw1 >mw2 >m1 >m2 % //
266 |@r_frontier @memb_cons //
268 |@unique_append_elim #q #H
269 [@injective_notb @(memb_filter_true … H)
270 |cut ((q==p) = false)
271 [|#Hpq whd in ⊢ (??%?); >Hpq @disjoint @memb_cons //]
272 cases (andb_true … u_frontier) #notp #_ @(\bf ?)
273 @(not_to_not … not_eq_true_false) #eqqp <notp <eqqp >H //
279 (* For completeness, we use the invariant that all the nodes in visited are cofinal,
280 and the sons of visited are either in visited or in the frontier; since
281 at the end frontier is empty, visited is hence a bisimulation. *)
283 definition all_true ≝ λS.λl.∀p:(pre S) × (pre S). memb ? p l = true →
284 (beqb (\snd (\fst p)) (\snd (\snd p)) = true).
286 definition sub_sons ≝ λS,l,l1,l2.∀x:(pre S) × (pre S).
287 memb ? x l1 = true → sublist ? (sons ? l x) l2.
289 lemma bisim_complete:
290 ∀S,l,n.∀frontier,visited,visited_res:list ?.
292 sub_sons S l visited (frontier@visited) →
293 bisim S l n frontier visited = 〈true,visited_res〉 →
294 is_bisim S visited_res l ∧ sublist ? (frontier@visited) visited_res.
296 [#fron #vis #vis_res #_ #_ >bisim_never #H destruct
298 [(* case empty frontier *)
299 -Hind #vis #vis_res #allv #H normalize in ⊢ (%→?);
301 [#membp % [@(\P ?) @allv //| @H //]|#H1 @H1]
302 |#hd cases (true_or_false (beqb (\snd (\fst hd)) (\snd (\snd hd))))
303 [|(* case head of the frontier is non ok (absurd) *)
304 #H #tl #vis #vis_res #allv >(bisim_step_false … H) #_ #H1 destruct]
305 (* frontier = hd:: tl and hd is ok *)
306 #H #tl #visited #visited_res #allv >(bisim_step_true … H)
307 (* new_visited = hd::visited are all ok *)
308 cut (all_true S (hd::visited))
309 [#p #H1 cases (orb_true_l … H1) [#eqp >(\P eqp) @H |@allv]]
310 (* we now exploit the induction hypothesis *)
311 #allh #subH #bisim cases (Hind … allh … bisim) -bisim -Hind
312 [#H1 #H2 % // #p #membp @H2 -H2 cases (memb_append … membp) -membp #membp
313 [cases (orb_true_l … membp) -membp #membp
314 [@memb_append_l2 >(\P membp) @memb_hd
315 |@memb_append_l1 @sublist_unique_append_l2 //
317 |@memb_append_l2 @memb_cons //
319 |(* the only thing left to prove is the sub_sons invariant *)
320 #x #membx cases (orb_true_l … membx)
322 #eqhdx <(\P eqhdx) #xa #membxa
323 (* xa is a son of x; we must distinguish the case xa
324 was already visited form the case xa is new *)
325 cases (true_or_false … (memb ? xa (x::visited)))
326 [(* xa visited - trivial *) #membxa @memb_append_l2 //
327 |(* xa new *) #membxa @memb_append_l1 @sublist_unique_append_l1 @memb_filter_l
330 |(* case x in visited *)
331 #H1 #xa #membxa cases (memb_append … (subH x … H1 … membxa))
332 [#H2 (cases (orb_true_l … H2))
333 [#H3 @memb_append_l2 <(\P H3) @memb_hd
334 |#H3 @memb_append_l1 @sublist_unique_append_l2 @H3
336 |#H2 @memb_append_l2 @memb_cons @H2
343 (* We can now give the definition of the equivalence algorithm, and
344 prove that two expressions are equivalente if and only if they define
345 the same language. *)
347 definition equiv ≝ λSig.λre1,re2:re Sig.
348 let e1 ≝ •(blank ? re1) in
349 let e2 ≝ •(blank ? re2) in
350 let n ≝ S (length ? (space_enum Sig (|\fst e1|) (|\fst e2|))) in
351 let sig ≝ (occ Sig e1 e2) in
352 (bisim ? sig n (〈e1,e2〉::[]) []).
354 theorem euqiv_sem : ∀Sig.∀e1,e2:re Sig.
355 \fst (equiv ? e1 e2) = true ↔ \sem{e1} =1 \sem{e2}.
357 [#H @eqP_trans [|@eqP_sym @re_embedding] @eqP_trans [||@re_embedding]
358 cut (equiv ? re1 re2 = 〈true,\snd (equiv ? re1 re2)〉)
360 cases (bisim_complete … Hcut)
361 [2,3: #p whd in ⊢ ((??%?)→?); #abs @False_ind /2/]
362 #Hbisim #Hsub @(bisim_to_sem … Hbisim)
364 |#H @(bisim_correct ? (•(blank ? re1)) (•(blank ? re2)))
365 [@eqP_trans [|@re_embedding] @eqP_trans [|@H] @eqP_sym @re_embedding
367 |% // #p whd in ⊢ ((??%?)→?); #abs @False_ind /2/
368 |% // #p #H >(memb_single … H) @(ex_intro … ϵ) /2/
374 definition eqbnat ≝ λn,m:nat. eqb n m.
376 lemma eqbnat_true : ∀n,m. eqbnat n m = true ↔ n = m.
377 #n #m % [@eqb_true_to_eq | @eq_to_eqb_true]
380 definition DeqNat ≝ mk_DeqSet nat eqb eqbnat_true.
382 definition a ≝ s DeqNat O.
383 definition b ≝ s DeqNat (S O).
384 definition c ≝ s DeqNat (S (S O)).
386 definition exp1 ≝ ((a·b)^*·a).
387 definition exp2 ≝ a·(b·a)^*.
388 definition exp4 ≝ (b·a)^*.
390 definition exp6 ≝ a·(a ·a ·b^* + b^* ).
391 definition exp7 ≝ a · a^* · b^*.
393 definition exp8 ≝ a·a·a·a·a·a·a·a·(a^* ).
394 definition exp9 ≝ (a·a·a + a·a·a·a·a)^*.
396 example ex1 : \fst (equiv ? (exp8+exp9) exp9) = true.