@@ -42,6 +42,11 @@ where f is friendly.
4242 are friendly.
4343* `gcorec`: a generalization of `Seq.corec` that allows a corecursive call to be guarded by
4444 a friendly function.
45+ * `FriendlyOperation.coind`, `FriendlyOperation.coind_comp_friend_left`,
46+ `FriendlyOperation.coind_comp_friend_right`: coinduction principles for proving that an operation
47+ is friendly.
48+ * `FriendlyOperation.eq_of_bisim`: a generalisation of `Seq.eq_of_bisim'` that allows using a
49+ friendly operation in the tail of the sequences.
4550
4651 ## Implementation details
4752
@@ -163,6 +168,14 @@ theorem dist_nil_cons (x : α) (s : Seq α) : dist nil (cons x s) = 1 := by
163168 rw [dist_comm]
164169 simp
165170
171+ theorem dist_le_half_iff {s t : Seq α} :
172+ dist s t ≤ 2 ⁻¹ ↔ (s = .nil ∧ t = .nil) ∨ ∃ hd s' t', s = .cons hd s' ∧ t = .cons hd t' where
173+ mp h := by
174+ cases s <;> cases t <;> norm_num at h <;> simp
175+ grind [dist_cons_cons_eq_one]
176+ mpr h := by
177+ obtain ⟨rfl, rfl⟩ | ⟨hd, s', t', rfl, rfl⟩ := h <;> simp
178+
166179/-- A function on sequences is called a "friend" if any `n`-prefix of its output depends only on
167180the `n`-prefix of the input. Such functions can be used in the tail of (non-primitive) corecursive
168181definitions. -/
@@ -172,6 +185,10 @@ def FriendlyOperation (op : Seq α → Seq α) : Prop := LipschitzWith 1 op
172185class FriendlyOperationClass (F : γ → Seq α → Seq α) : Prop where
173186 friend : ∀ c : γ, FriendlyOperation (F c)
174187
188+ theorem friendlyOperation_iff_dist_le_dist (op : Seq α → Seq α) :
189+ FriendlyOperation op ↔ ∀ s t, dist (op s) (op t) ≤ dist s t := by
190+ simp [FriendlyOperation, lipschitzWith_iff_dist_le_mul]
191+
175192theorem FriendlyOperation.id : FriendlyOperation (id : Seq α → Seq α) :=
176193 LipschitzWith.id
177194
@@ -183,7 +200,7 @@ theorem FriendlyOperation.comp {op op' : Seq α → Seq α}
183200 simp
184201
185202theorem FriendlyOperation.const {s : Seq α} : FriendlyOperation (fun _ ↦ s) := by
186- simp [FriendlyOperation, lipschitzWith_iff_dist_le_mul ]
203+ simp [friendlyOperation_iff_dist_le_dist ]
187204
188205theorem FriendlyOperationClass.comp (F : γ → Seq α → Seq α) (g : γ' → γ)
189206 [h : FriendlyOperationClass F] : FriendlyOperationClass (fun c ↦ F (g c)) := by
@@ -193,7 +210,7 @@ theorem FriendlyOperation.ite {op₁ op₂ : Seq α → Seq α}
193210 (h₁ : FriendlyOperation op₁) (h₂ : FriendlyOperation op₂)
194211 {P : Option α → Prop } [DecidablePred P] :
195212 FriendlyOperation (fun s ↦ if P s.head then op₁ s else op₂ s) := by
196- rw [FriendlyOperation, lipschitzWith_iff_dist_le_mul, NNReal.coe_one ] at h₁ h₂ ⊢
213+ rw [friendlyOperation_iff_dist_le_dist ] at h₁ h₂ ⊢
197214 intro s t
198215 by_cases! h_head : s.head ≠ t.head
199216 · simp [dist_eq_one_of_head h_head]
@@ -291,4 +308,110 @@ theorem gcorec_some {F : β → Option (α × γ × β)} {op : γ → Seq α →
291308 have := (FriendlyOperation.exists_fixed_point F op).choose_spec b
292309 simpa [h] using this
293310
311+ /-- The operation `cons hd ·` is friendly. -/
312+ theorem FriendlyOperation.cons (hd : α) : FriendlyOperation (cons hd) := by
313+ simp only [friendlyOperation_iff_dist_le_dist, dist_cons_cons]
314+ intro x y
315+ linarith [dist_nonneg (x := x) (y := y)]
316+
317+ /-- If two sequences have the same head and applying `op` reduces their distance, then
318+ it also reduces the distance of their tails. -/
319+ lemma dist_const_tail_cons_tail_le
320+ {op : Seq α → Seq α} {hd : α} {x y : Stream'.Seq α}
321+ (h : dist (op (cons hd x)) (op (cons hd y)) ≤ dist (cons hd x) (cons hd y)) :
322+ dist (op (cons hd x)).tail (op (cons hd y)).tail ≤ dist x y := by
323+ rwa [dist_cons_cons, dist_eq_half_of_head, mul_le_mul_iff_right₀ (by norm_num)] at h
324+ grw [dist_le_one x y, mul_one] at h
325+ obtain (⟨hx, hy⟩ | ⟨_, _, _, hx, hy⟩) := dist_le_half_iff.mp h <;> simp [hx, hy]
326+
327+ /-- The operation `(op (.cons hd ·)).tail` is friendly if `op` is friendly. -/
328+ theorem FriendlyOperation.cons_tail {op : Seq α → Seq α} {hd : α} (h : FriendlyOperation op) :
329+ FriendlyOperation (fun s ↦ (op (.cons hd s)).tail) := by
330+ simp_rw [friendlyOperation_iff_dist_le_dist] at h ⊢
331+ intro x y
332+ specialize h (.cons hd x) (.cons hd y)
333+ exact dist_const_tail_cons_tail_le h
334+
335+ /-- The first element of `op (a :: s)` depends only on `a`. -/
336+ theorem FriendlyOperation.op_cons_head_eq {op : Seq α → Seq α} (h : FriendlyOperation op) {a : α}
337+ {s t : Seq α} : (op <| .cons a s).head = (op <| .cons a t).head := by
338+ rw [friendlyOperation_iff_dist_le_dist] at h
339+ specialize h (.cons a s) (.cons a t)
340+ simp only [dist_cons_cons] at h
341+ replace h : dist (op (.cons a s)) (op (.cons a t)) ≤ 2 ⁻¹ := by
342+ apply h.trans
343+ simp
344+ rw [dist_le_half_iff] at h
345+ generalize op (Seq.cons a s) = s' at *
346+ generalize op (Seq.cons a t) = t' at *
347+ obtain ⟨rfl, rfl⟩ | ⟨hd, s_tl, t_tl, rfl, rfl⟩ := h <;> rfl
348+
349+ /-- Decomposes a friendly operation by the head of the input sequence. Returns `none` if the output
350+ is `nil`, or `some (out_hd, op')` where `out_hd` is the head of the output and `op'` is a friendly
351+ operation mapping the tail of the input to the tail of the output. See
352+ `destruct_apply_eq_unfold` for the correctness statement. -/
353+ def FriendlyOperation.unfold {op : Seq α → Seq α} (h : FriendlyOperation op) (hd? : Option α) :
354+ Option (α × Subtype (@FriendlyOperation α)) :=
355+ match hd? with
356+ | none =>
357+ match (op nil).destruct with
358+ | none => none
359+ | some (t_hd, t_tl) =>
360+ some (t_hd, ⟨fun _ ↦ t_tl, FriendlyOperation.const⟩)
361+ | some s_hd =>
362+ match (op <| .cons s_hd nil).destruct with
363+ | none => none
364+ | some (t_hd, _) =>
365+ some (t_hd, ⟨fun s_tl ↦ (op (.cons s_hd s_tl)).tail, FriendlyOperation.cons_tail h⟩)
366+
367+ set_option backward.isDefEq.respectTransparency false in
368+ /-- `unfold` correctly decomposes a friendly operation: the head of `op s` depends only on the
369+ head of `s` (and is given by `unfold`), while the tail of `op s` is obtained by applying the
370+ friendly operation returned by `unfold` to the tail of `s`. This gives a coinductive
371+ characterization of `FriendlyOperation`. For the coinduction principle, see
372+ `FriendlyOperation.coind`. -/
373+ theorem FriendlyOperation.destruct_apply_eq_unfold {op : Seq α → Seq α} (h : FriendlyOperation op)
374+ {s : Seq α} :
375+ destruct (op s) = (h.unfold s.head).map (fun (hd, op') => (hd, op'.val s.tail)) := by
376+ unfold unfold
377+ cases s with
378+ | nil =>
379+ generalize op nil = t
380+ cases t <;> simp
381+ | cons s_hd s_tl =>
382+ simp only [Seq.tail_cons, Seq.head_cons]
383+ generalize ht0 : op (.cons s_hd nil) = t0 at *
384+ generalize ht : op (.cons s_hd s_tl) = t at *
385+ have : t0.head = t.head := by
386+ rw [← ht0, ← ht, FriendlyOperation.op_cons_head_eq h]
387+ cases t0 with
388+ | nil =>
389+ cases t with
390+ | nil => simp
391+ | cons => simp at this
392+ | cons =>
393+ cases t with
394+ | nil => simp at this
395+ | cons => simp_all
396+
397+ set_option backward.isDefEq.respectTransparency false in
398+ /-- If `op` is friendly, then `op s` and `op t` have the same head if `s` and `t`
399+ have the same head. -/
400+ theorem FriendlyOperation.op_head_eq {op : Seq α → Seq α} (h : FriendlyOperation op) {s t : Seq α}
401+ (h_head : s.head = t.head) : (op s).head = (op t).head := by
402+ simp only [head_eq_destruct, Option.map_eq_map, h.destruct_apply_eq_unfold, Option.map_map]
403+ at h_head ⊢
404+ simp [h_head]
405+ rfl
406+
407+ theorem FriendlyOperation.of_dist_le_pow {op : Seq α → Seq α}
408+ (h : ∀ s t n, dist s t ≤ (2 ⁻¹ : ℝ) ^ n → dist (op s) (op t) ≤ (2 ⁻¹ : ℝ) ^ n) :
409+ FriendlyOperation op := by
410+ rw [friendlyOperation_iff_dist_le_dist]
411+ intro s t
412+ by_cases hst : s = t
413+ · simp [hst]
414+ obtain ⟨n, hst⟩ := dist_eq_two_inv_pow hst
415+ grind
416+
294417end Tactic.ComputeAsymptotics.Seq
0 commit comments