@@ -3,8 +3,9 @@ Copyright (c) 2024 Jeremy Tan. All rights reserved.
33Released under Apache 2.0 license as described in the file LICENSE.
44Authors: Jeremy Tan
55-/
6- import Mathlib.Algebra.Order.Ring.Nat
7- import Mathlib.Data.List.Nodup
6+ import Mathlib.Data.List.Indexes
7+ import Mathlib.Logic.Relation
8+ import Mathlib.Tactic.Positivity.Core
89
910/-!
1011# Dyck words
@@ -20,6 +21,7 @@ and the restriction requires the path to never go below the x-axis.
2021* `DyckWord`: a list of `U`s and `D`s with as many `U`s as `D`s and with every prefix having
2122 at least as many `U`s as `D`s.
2223* `DyckWord.semilength`: semilength (half the length) of a Dyck word.
24+ * `DyckWord.firstReturn`: for a nonempty word, the index of the `D` matching the initial `U`.
2325
2426 ## Implementation notes
2527
@@ -68,52 +70,70 @@ instance : Add DyckWord where
6870
6971instance : Zero DyckWord := ⟨[], by simp, by simp⟩
7072
71- /-- Dyck words form an additive monoid under concatenation, with the empty word as 0. -/
72- instance : AddMonoid DyckWord where
73+ /-- Dyck words form an additive cancellative monoid under concatenation,
74+ with the empty word as 0. -/
75+ instance : AddCancelMonoid DyckWord where
7376 add_zero p := by ext1; exact append_nil _
7477 zero_add p := by ext1; rfl
7578 add_assoc p q r := by ext1; apply append_assoc
7679 nsmul := nsmulRec
80+ add_left_cancel p q r h := by rw [DyckWord.ext_iff] at *; exact append_cancel_left h
81+ add_right_cancel p q r h := by rw [DyckWord.ext_iff] at *; exact append_cancel_right h
7782
7883namespace DyckWord
7984
80- variable ( p q : DyckWord)
85+ variable { p q : DyckWord}
8186
8287lemma toList_eq_nil : p.toList = [] ↔ p = 0 := by rw [DyckWord.ext_iff]; rfl
88+ lemma toList_ne_nil : p.toList ≠ [] ↔ p ≠ 0 := toList_eq_nil.ne
89+
90+ /-- The only Dyck word that is an additive unit is the empty word. -/
91+ instance : Unique (AddUnits DyckWord) where
92+ uniq p := by
93+ obtain ⟨a, b, h, -⟩ := p
94+ obtain ⟨ha, hb⟩ := append_eq_nil.mp (toList_eq_nil.mpr h)
95+ congr
96+ · exact toList_eq_nil.mp ha
97+ · exact toList_eq_nil.mp hb
98+
99+ variable (h : p ≠ 0 )
83100
84101/-- The first element of a nonempty Dyck word is `U`. -/
85- lemma head_eq_U (h : p.toList ≠ [] ) : p.toList.head h = U := by
102+ lemma head_eq_U (p : DyckWord) (h ) : p.toList.head h = U := by
86103 rcases p with - | s; · tauto
87104 rw [head_cons]
88105 by_contra f
89106 rename_i _ nonneg
90107 simpa [s.dichotomy.resolve_left f] using nonneg 1
91108
92109/-- The last element of a nonempty Dyck word is `D`. -/
93- lemma getLast_eq_D (h : p.toList ≠ [] ) : p.toList.getLast h = D := by
110+ lemma getLast_eq_D (p : DyckWord) (h ) : p.toList.getLast h = D := by
94111 by_contra f; have s := p.count_U_eq_count_D
95112 rw [← dropLast_append_getLast h, (dichotomy _).resolve_right f] at s
96113 simp_rw [dropLast_eq_take, count_append, count_singleton', ite_true, ite_false] at s
97114 have := p.count_D_le_count_U (p.toList.length - 1 ); omega
98115
99- lemma cons_tail_dropLast_concat (h : p.toList ≠ []) :
100- U :: p.toList.dropLast.tail ++ [D] = p := by
101- have : p.toList.dropLast.take 1 = [p.toList.head h] := by
116+ include h in
117+ lemma cons_tail_dropLast_concat : U :: p.toList.dropLast.tail ++ [D] = p := by
118+ have h' := toList_ne_nil.mpr h
119+ have : p.toList.dropLast.take 1 = [p.toList.head h'] := by
102120 rcases p with - | ⟨s, ⟨- | ⟨t, r⟩⟩⟩
103121 · tauto
104122 · rename_i bal _
105123 cases s <;> simp at bal
106124 · tauto
107- nth_rw 2 [← p.toList.dropLast_append_getLast h, ← p.toList.dropLast.take_append_drop 1 ]
108- rw [p. getLast_eq_D, drop_one, this, p. head_eq_U]
125+ nth_rw 2 [← p.toList.dropLast_append_getLast h' , ← p.toList.dropLast.take_append_drop 1 ]
126+ rw [getLast_eq_D, drop_one, this, head_eq_U]
109127 rfl
110128
129+ variable (p) in
111130/-- Prefix of a Dyck word as a Dyck word, given that the count of `U`s and `D`s in it are equal. -/
112131def take (i : ℕ) (hi : (p.toList.take i).count U = (p.toList.take i).count D) : DyckWord where
113132 toList := p.toList.take i
114133 count_U_eq_count_D := hi
115134 count_D_le_count_U k := by rw [take_take]; exact p.count_D_le_count_U (min k i)
116135
136+ variable (p) in
117137/-- Suffix of a Dyck word as a Dyck word, given that the count of `U`s and `D`s in the prefix
118138are equal. -/
119139def drop (i : ℕ) (hi : (p.toList.take i).count U = (p.toList.take i).count D) : DyckWord where
@@ -128,6 +148,7 @@ def drop (i : ℕ) (hi : (p.toList.take i).count U = (p.toList.take i).count D)
128148 ← count_append, hi, ← count_append, take_append_drop]
129149 exact p.count_D_le_count_U _
130150
151+ variable (p) in
131152/-- Nest `p` in one pair of brackets, i.e. `x` becomes `(x)`. -/
132153def nest : DyckWord where
133154 toList := [U] ++ p ++ [D]
@@ -142,17 +163,21 @@ def nest : DyckWord where
142163 rw [add_comm]
143164 exact add_le_add (zero_le _) ((count_le_length _ _).trans (by simp))
144165
166+ @[simp] lemma nest_ne_zero : p.nest ≠ 0 := by simp [← toList_ne_nil, nest]
167+
168+ variable (p) in
145169/-- Denest `p`, i.e. `(x)` becomes `x`, given that `p` is strictly positive in its interior
146170(this ensures that `x` is a Dyck word). -/
147- def denest (h : p.toList ≠ []) ( pos : ∀ i, 0 < i → i < p.toList.length →
171+ def denest (pos : ∀ i, 0 < i → i < p.toList.length →
148172 (p.toList.take i).count D < (p.toList.take i).count U) : DyckWord where
149173 toList := p.toList.dropLast.tail
150174 count_U_eq_count_D := by
151175 have := p.count_U_eq_count_D
152- rw [← p. cons_tail_dropLast_concat h, count_append, count_cons] at this
176+ rw [← cons_tail_dropLast_concat h, count_append, count_cons] at this
153177 simpa using this
154178 count_D_le_count_U i := by
155- have l1 : p.toList.take 1 = [p.toList.head h] := by rcases p with - | - <;> tauto
179+ have h' := toList_ne_nil.mpr h
180+ have l1 : p.toList.take 1 = [p.toList.head h'] := by rcases p with - | - <;> tauto
156181 have l3 : p.toList.length - 1 = p.toList.length - 1 - 1 + 1 := by
157182 rcases p with - | ⟨s, ⟨- | ⟨t, r⟩⟩⟩
158183 · tauto
@@ -161,22 +186,23 @@ def denest (h : p.toList ≠ []) (pos : ∀ i, 0 < i → i < p.toList.length →
161186 · tauto
162187 rw [← drop_one, take_drop, dropLast_eq_take, take_take]
163188 have ub : min (1 + i) (p.toList.length - 1 ) < p.toList.length :=
164- (min_le_right _ p.toList.length.pred).trans_lt (Nat.pred_lt ((length_pos.mpr h).ne'))
189+ (min_le_right _ p.toList.length.pred).trans_lt (Nat.pred_lt ((length_pos.mpr h' ).ne'))
165190 have lb : 0 < min (1 + i) (p.toList.length - 1 ) := by
166191 rw [l3, add_comm, min_add_add_right]; omega
167192 have eq := pos _ lb ub
168193 set j := min (1 + i) (p.toList.length - 1 )
169194 rw [← (p.toList.take j).take_append_drop 1 , count_append, count_append, take_take,
170- min_eq_left (by omega), l1, p. head_eq_U] at eq
195+ min_eq_left (by omega), l1, head_eq_U] at eq
171196 simp only [count_singleton', ite_true, ite_false] at eq
172197 omega
173198
174- lemma denest_nest (h : p.toList ≠ []) ( pos : ∀ i, 0 < i → i < p.toList.length →
199+ lemma denest_nest (pos : ∀ i, 0 < i → i < p.toList.length →
175200 (p.toList.take i).count D < (p.toList.take i).count U) : (p.denest h pos).nest = p := by
176201 simpa [DyckWord.ext_iff] using p.cons_tail_dropLast_concat h
177202
178203section Semilength
179204
205+ variable (p) in
180206/-- The semilength of a Dyck word is half of the number of `DyckStep`s in it, or equivalently
181207its number of `U`s. -/
182208def semilength : ℕ := p.toList.count U
@@ -196,4 +222,223 @@ lemma two_mul_semilength_eq_length : 2 * p.semilength = p.toList.length := by
196222
197223end Semilength
198224
225+ section FirstReturn
226+
227+ variable (p) in
228+ /-- `p.firstReturn` is 0 if `p = 0` and the index of the `D` matching the initial `U` otherwise. -/
229+ def firstReturn : ℕ :=
230+ (range p.toList.length).findIdx fun i ↦
231+ (p.toList.take (i + 1 )).count U = (p.toList.take (i + 1 )).count D
232+
233+ @[simp] lemma firstReturn_zero : firstReturn 0 = 0 := rfl
234+
235+ include h in
236+ lemma firstReturn_pos : 0 < p.firstReturn := by
237+ by_contra! f
238+ rw [Nat.le_zero, firstReturn, findIdx_eq] at f
239+ · simp only [get_eq_getElem, getElem_range] at f
240+ rw [← p.cons_tail_dropLast_concat h] at f
241+ simp at f
242+ · rw [length_range, length_pos]
243+ exact toList_ne_nil.mpr h
244+
245+ include h in
246+ lemma firstReturn_lt_length : p.firstReturn < p.toList.length := by
247+ have lp := length_pos_of_ne_nil (toList_ne_nil.mpr h)
248+ rw [← length_range p.toList.length]
249+ apply findIdx_lt_length_of_exists
250+ simp only [mem_range, decide_eq_true_eq]
251+ use p.toList.length - 1
252+ exact ⟨by omega, by rw [Nat.sub_add_cancel lp, take_of_length_le (le_refl _),
253+ p.count_U_eq_count_D]⟩
254+
255+ include h in
256+ lemma count_take_firstReturn_add_one :
257+ (p.toList.take (p.firstReturn + 1 )).count U = (p.toList.take (p.firstReturn + 1 )).count D := by
258+ have := findIdx_get (w := (length_range p.toList.length).symm ▸ firstReturn_lt_length h)
259+ simpa using this
260+
261+ lemma count_D_lt_count_U_of_lt_firstReturn {i : ℕ} (hi : i < p.firstReturn) :
262+ (p.toList.take (i + 1 )).count D < (p.toList.take (i + 1 )).count U := by
263+ have ne := not_of_lt_findIdx hi
264+ rw [decide_eq_true_eq, ← ne_eq, get_eq_getElem, getElem_range] at ne
265+ exact lt_of_le_of_ne (p.count_D_le_count_U (i + 1 )) ne.symm
266+
267+ @[simp]
268+ lemma firstReturn_add : (p + q).firstReturn = if p = 0 then q.firstReturn else p.firstReturn := by
269+ split_ifs with h; · simp [h]
270+ have u : (p + q).toList = p.toList ++ q.toList := rfl
271+ rw [firstReturn, findIdx_eq]
272+ · simp_rw [get_eq_getElem, getElem_range, u, decide_eq_true_eq]
273+ have v := firstReturn_lt_length h
274+ constructor
275+ · rw [take_append_eq_append_take, show p.firstReturn + 1 - p.toList.length = 0 by omega,
276+ take_zero, append_nil, count_take_firstReturn_add_one h]
277+ · intro j hj
278+ rw [take_append_eq_append_take, show j + 1 - p.toList.length = 0 by omega,
279+ take_zero, append_nil]
280+ exact (count_D_lt_count_U_of_lt_firstReturn hj).ne'
281+ · rw [length_range, u, length_append]
282+ exact Nat.lt_add_right _ (firstReturn_lt_length h)
283+
284+ @[simp]
285+ lemma firstReturn_nest : p.nest.firstReturn = p.toList.length + 1 := by
286+ have u : p.nest.toList = U :: p.toList ++ [D] := rfl
287+ rw [firstReturn, findIdx_eq]
288+ · simp_rw [get_eq_getElem, getElem_range, u, decide_eq_true_eq]
289+ constructor
290+ · rw [take_of_length_le (by simp), ← u, p.nest.count_U_eq_count_D]
291+ · intro j hj
292+ simp_rw [cons_append, take_cons, count_cons, beq_self_eq_true, ite_true,
293+ beq_iff_eq, ite_false, take_append_eq_append_take,
294+ show j - p.toList.length = 0 by omega, take_zero, append_nil]
295+ have := p.count_D_le_count_U j
296+ omega
297+ · simp_rw [length_range, u, length_append, length_cons]
298+ exact Nat.lt_add_one _
299+
300+ lemma firstReturn_nest_add : (p.nest + q).firstReturn = p.toList.length + 1 := by simp
301+
302+ variable (p) in
303+ /-- The left part of the Dyck word decomposition,
304+ inside the `U, D` pair that `firstReturn` refers to. `insidePart 0 = 0`. -/
305+ def insidePart : DyckWord :=
306+ if h : p = 0 then 0 else
307+ (p.take (p.firstReturn + 1 ) (count_take_firstReturn_add_one h)).denest
308+ (by rw [← toList_ne_nil, take]; simpa using toList_ne_nil.mpr h)
309+ (fun i lb ub ↦ by
310+ simp only [take, length_take, lt_min_iff] at ub ⊢
311+ replace ub := ub.1
312+ rw [take_take, min_eq_left ub.le]
313+ rw [show i = i - 1 + 1 by omega] at ub ⊢
314+ rw [Nat.add_lt_add_iff_right] at ub
315+ exact count_D_lt_count_U_of_lt_firstReturn ub)
316+
317+ variable (p) in
318+ /-- The right part of the Dyck word decomposition,
319+ outside the `U, D` pair that `firstReturn` refers to. `outsidePart 0 = 0`. -/
320+ def outsidePart : DyckWord :=
321+ if h : p = 0 then 0 else p.drop (p.firstReturn + 1 ) (count_take_firstReturn_add_one h)
322+
323+ @[simp] lemma insidePart_zero : insidePart 0 = 0 := by simp [insidePart]
324+ @[simp] lemma outsidePart_zero : outsidePart 0 = 0 := by simp [outsidePart]
325+
326+ include h in
327+ @[simp]
328+ theorem nest_insidePart_add_outsidePart : p.insidePart.nest + p.outsidePart = p := by
329+ simp_rw [insidePart, outsidePart, h, dite_false, denest_nest, DyckWord.ext_iff]
330+ apply take_append_drop
331+
332+ include h in
333+ lemma semilength_insidePart_add_semilength_outsidePart_add_one :
334+ p.insidePart.semilength + p.outsidePart.semilength + 1 = p.semilength := by
335+ rw [← congrArg semilength (nest_insidePart_add_outsidePart h), semilength_add, semilength_nest,
336+ add_right_comm]
337+
338+ include h in
339+ theorem semilength_insidePart_lt : p.insidePart.semilength < p.semilength := by
340+ have := semilength_insidePart_add_semilength_outsidePart_add_one h
341+ omega
342+
343+ include h in
344+ theorem semilength_outsidePart_lt : p.outsidePart.semilength < p.semilength := by
345+ have := semilength_insidePart_add_semilength_outsidePart_add_one h
346+ omega
347+
348+ theorem insidePart_nest_add : (p.nest + q).insidePart = p := by
349+ have : p.toList.length + 1 + 1 = p.nest.toList.length := by simp [nest]
350+ simp_rw [insidePart, firstReturn_nest_add, take,
351+ show (p.nest + q).toList = p.nest.toList ++ q.toList by rfl, take_append_eq_append_take,
352+ this, take_length, tsub_self, take_zero, append_nil, denest, nest, dropLast_concat]
353+ rfl
354+
355+ theorem outsidePart_nest_add : (p.nest + q).outsidePart = q := by
356+ have : p.toList.length + 1 + 1 = p.nest.toList.length := by simp [nest]
357+ simp_rw [outsidePart, add_eq_zero', nest_ne_zero, false_and, dite_false,
358+ firstReturn_nest_add, drop, show (p.nest + q).toList = p.nest.toList ++ q.toList by rfl,
359+ drop_append_eq_append_drop, this, drop_length, nil_append, tsub_self, drop_zero]
360+
361+ end FirstReturn
362+
363+ section Order
364+
365+ instance : Preorder DyckWord where
366+ le := Relation.ReflTransGen (fun p q ↦ p = q.insidePart ∨ p = q.outsidePart)
367+ le_refl p := Relation.ReflTransGen.refl
368+ le_trans p q r := Relation.ReflTransGen.trans
369+
370+ lemma zero_le (p : DyckWord) : 0 ≤ p := by
371+ by_cases h : p = 0
372+ · simp [h]
373+ · have := semilength_insidePart_lt h
374+ exact (zero_le _).trans (Relation.ReflTransGen.single (Or.inl rfl))
375+ termination_by p.semilength
376+
377+ lemma infix_of_le (h : p ≤ q) : p.toList <:+: q.toList := by
378+ induction h with
379+ | refl => exact infix_refl _
380+ | tail _pm mq ih =>
381+ rename_i m r
382+ rcases eq_or_ne r 0 with rfl | hr
383+ · rw [insidePart_zero, outsidePart_zero, or_self] at mq
384+ rwa [mq] at ih
385+ · have : [U] ++ r.insidePart ++ [D] ++ r.outsidePart = r :=
386+ DyckWord.ext_iff.mp (nest_insidePart_add_outsidePart hr)
387+ rcases mq with hm | hm
388+ · have : r.insidePart <:+: r.toList := by
389+ use [U], [D] ++ r.outsidePart; rwa [← append_assoc]
390+ exact ih.trans (hm ▸ this)
391+ · have : r.outsidePart <:+: r.toList := by
392+ use [U] ++ r.insidePart ++ [D], []; rwa [append_nil]
393+ exact ih.trans (hm ▸ this)
394+
395+ /-- Partial order on Dyck words: `p ≤ q` if a (possibly empty) sequence of
396+ `insidePart` and `outsidePart` operations can turn `q` into `p`. -/
397+ instance : PartialOrder DyckWord where
398+ le_antisymm p q pq qp := by
399+ have h₁ := infix_of_le pq
400+ have h₂ := infix_of_le qp
401+ exact DyckWord.ext <| h₁.eq_of_length <| h₁.length_le.antisymm h₂.length_le
402+
403+ lemma monotone_semilength : Monotone semilength := fun p q pq ↦ by
404+ induction pq with
405+ | refl => rfl
406+ | tail _ mq ih =>
407+ rename_i m r _
408+ rcases eq_or_ne r 0 with rfl | hr
409+ · rw [insidePart_zero, outsidePart_zero, or_self] at mq
410+ rwa [mq] at ih
411+ · rcases mq with hm | hm
412+ · exact ih.trans (hm ▸ semilength_insidePart_lt hr).le
413+ · exact ih.trans (hm ▸ semilength_outsidePart_lt hr).le
414+
415+ lemma strictMono_semilength : StrictMono semilength := fun p q pq ↦ by
416+ obtain ⟨plq, pnq⟩ := lt_iff_le_and_ne.mp pq
417+ apply lt_of_le_of_ne (monotone_semilength plq)
418+ contrapose! pnq
419+ replace pnq := congr(2 * $(pnq))
420+ simp_rw [two_mul_semilength_eq_length] at pnq
421+ exact DyckWord.ext ((infix_of_le plq).eq_of_length pnq)
422+
423+ end Order
424+
199425end DyckWord
426+
427+ namespace Mathlib.Meta.Positivity
428+
429+ open Lean Meta Qq
430+
431+ /-- Extension for the `positivity` tactic: `p.firstReturn` is positive if `p` is nonzero. -/
432+ @[positivity DyckWord.firstReturn _]
433+ def evalDyckWordFirstReturn : PositivityExt where eval {u α} _zα _pα e := do
434+ match u, α, e with
435+ | 0 , ~q(ℕ), ~q(DyckWord.firstReturn $a) =>
436+ let ra ← core q(inferInstance) q(inferInstance) a
437+ assertInstancesCommute
438+ match ra with
439+ | .positive pa => pure (.positive q(DyckWord.firstReturn_pos ($pa).ne'))
440+ | .nonzero pa => pure (.positive q(DyckWord.firstReturn_pos $pa))
441+ | _ => pure .none
442+ | _, _, _ => throwError "not DyckWord.firstReturn"
443+
444+ end Mathlib.Meta.Positivity
0 commit comments