From 313037328d0cfc90e3ef02115f120a2a4e6bf732 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 14:27:28 +0000 Subject: [PATCH 01/76] Add reflected half-line root count transport --- RealRooted/DegreeDropReversal.lean | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/RealRooted/DegreeDropReversal.lean b/RealRooted/DegreeDropReversal.lean index 895cd2bb..605b352a 100644 --- a/RealRooted/DegreeDropReversal.lean +++ b/RealRooted/DegreeDropReversal.lean @@ -526,6 +526,26 @@ theorem card_roots_reverse_Ioi {p : K[X]} (hp : p.Splits) (h0 : p.coeff 0 ≠ 0) congrArg Multiset.card (Multiset.filter_congr (fun r _ => mem_Ioi_inv_iff ha)) +/-- Half-line root-count transport under reflection at a degree bound. -/ +theorem card_roots_reflect_Ioi {p : K[X]} (hp : p.Splits) (h0 : p.coeff 0 ≠ 0) + {N : ℕ} (hN : p.natDegree ≤ N) {a : K} (ha : 0 < a) : + ((reflect N p).roots.filter (fun x => a < x)).card = + (p.roots.filter (fun r => 0 < r ∧ r < a⁻¹)).card := by + have hpad : + Multiset.filter (fun x => a < x) + ((N - p.natDegree) • ({0} : Multiset K)) = 0 := by + rw [Multiset.filter_eq_nil] + intro x hx + rw [Multiset.mem_nsmul, Multiset.mem_singleton] at hx + grind + rw [reflect_eq_X_pow_mul_reverse p hN, + Polynomial.roots_mul + (mul_ne_zero (pow_ne_zero _ Polynomial.X_ne_zero) + (reverse_ne_zero_of_coeff_zero_ne h0)), + Multiset.filter_add, Multiset.card_add, Polynomial.roots_pow, + Polynomial.roots_X, card_roots_reverse_Ioi hp h0 ha, hpad, + Multiset.card_zero, zero_add] + /-- No-gap emptiness on a positive interval under reversal. -/ theorem card_roots_reverse_Ioo_eq_zero_iff {p : K[X]} (hp : p.Splits) (h0 : p.coeff 0 ≠ 0) {a b : K} (ha : 0 < a) (hb : 0 < b) : From dc5ea25935ff3ca454eba530bca123f6ee435482 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 14:40:17 +0000 Subject: [PATCH 02/76] Add reflected root endpoint transport --- RealRooted/DegreeDropReversal.lean | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RealRooted/DegreeDropReversal.lean b/RealRooted/DegreeDropReversal.lean index 605b352a..25241097 100644 --- a/RealRooted/DegreeDropReversal.lean +++ b/RealRooted/DegreeDropReversal.lean @@ -296,6 +296,15 @@ theorem splits_reflect_iff {p : K[X]} {N : ℕ} (hN : p.natDegree ≤ N) : Polynomial.natDegree_reflect_le.trans <| by simp_all simpa using splits_reflect_of_splits h hreflect_deg +/-- Reflection sends a nonzero root to its inverse at any valid degree bound. -/ +theorem isRoot_reflect_inv_iff {p : K[X]} {b : K} (hb : b ≠ 0) + {N : ℕ} (hN : p.natDegree ≤ N) : + (reflect N p).IsRoot b⁻¹ ↔ p.IsRoot b := by + letI : Invertible b := invertibleOfNonzero hb + change eval b⁻¹ (reflect N p) = 0 ↔ eval b p = 0 + simpa [Polynomial.eval₂_id, invOf_eq_inv] using + (Polynomial.eval₂_reflect_eq_zero_iff (RingHom.id K) b N p hN) + /-- Reversal preserves and reflects splitting over a field. -/ theorem splits_reverse_iff {p : K[X]} : p.reverse.Splits ↔ p.Splits := by From 7be5d9f5606a89b6a436bb68482d5400770c2bc5 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 14:42:34 +0000 Subject: [PATCH 03/76] Transport bounded root counts through degree drops --- RealRooted.lean | 1 + .../ReflectedRootCountLocalConstancy.lean | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 RealRooted/ReflectedRootCountLocalConstancy.lean diff --git a/RealRooted.lean b/RealRooted.lean index 4bca6b20..85b9fb56 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -231,6 +231,7 @@ import RealRooted.RectangularConvolutionIdentity import RealRooted.RectangularPolarization import RealRooted.RectangularPolarizationComplement import RealRooted.RecurrenceDiscriminant +import RealRooted.ReflectedRootCountLocalConstancy import RealRooted.RootContinuity import RealRooted.RootCountFinite import RealRooted.RootCountJump diff --git a/RealRooted/ReflectedRootCountLocalConstancy.lean b/RealRooted/ReflectedRootCountLocalConstancy.lean new file mode 100644 index 00000000..e2a4a2db --- /dev/null +++ b/RealRooted/ReflectedRootCountLocalConstancy.lean @@ -0,0 +1,80 @@ +import RealRooted.DegreeDropReversal +import RealRooted.PositiveParameterLocalLowerCount + +/-! +# Root-count local constancy through degree drops + +Reflection at a common degree bound turns a possible leading-degree drop into +zero-root padding. When the original affine family is nonzero at the left +endpoint of a positive interval, the reflected family therefore has constant +degree, so the existing analytic local-constancy theorem applies. +-/ + +open Polynomial + +namespace RealRooted + +open DegreeDropReversal + +/-- Root counts in `(0, b)` are constant along a split affine family when the +family stays nonzero at both interval endpoints, even if its degree drops. -/ +theorem rightFamily_card_roots_Ioo_zero_eq_zero_param_of_degree_bound + {f g : ℝ[X]} {b μ : ℝ} (hb : 0 < b) (hμ : 0 < μ) {N : ℕ} + (hN : ∀ η ∈ Set.Icc (0 : ℝ) μ, (f + C η * g).natDegree ≤ N) + (hzero : ∀ η ∈ Set.Icc (0 : ℝ) μ, (f + C η * g).coeff 0 ≠ 0) + (hsplit : ∀ η ∈ Set.Icc (0 : ℝ) μ, (f + C η * g).Splits) + (hbroot : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (f + C η * g).IsRoot b) : + ((f + C μ * g).roots.filter (fun r ↦ 0 < r ∧ r < b)).card = + (f.roots.filter (fun r ↦ 0 < r ∧ r < b)).card := by + have hzero_mem : (0 : ℝ) ∈ Set.Icc (0 : ℝ) μ := ⟨le_rfl, hμ.le⟩ + have hμ_mem : μ ∈ Set.Icc (0 : ℝ) μ := ⟨hμ.le, le_rfl⟩ + have hfN : f.natDegree ≤ N := by + simpa using hN 0 hzero_mem + have hfzero : f.coeff 0 ≠ 0 := by + simpa using hzero 0 hzero_mem + have hf_split : f.Splits := by + simpa using hsplit 0 hzero_mem + have hreflect_degree : ∀ η ∈ Set.Icc (0 : ℝ) μ, + (reflect N f + C η * reflect N g).natDegree = + (reflect N f + C (0 : ℝ) * reflect N g).natDegree := by + intro η hη + calc + (reflect N f + C η * reflect N g).natDegree = + (reflect N (f + C η * g)).natDegree := by rw [reflect_add_C_mul] + _ = N := natDegree_reflect_eq_of_coeff_zero_ne (hN η hη) (hzero η hη) + _ = (reflect N f).natDegree := + (natDegree_reflect_eq_of_coeff_zero_ne hfN hfzero).symm + _ = (reflect N f + C (0 : ℝ) * reflect N g).natDegree := by simp + have hreflect_split : ∀ η ∈ Set.Icc (0 : ℝ) μ, + (reflect N f + C η * reflect N g).Splits := by + intro η hη + rw [← reflect_add_C_mul] + exact splits_reflect_of_splits (hsplit η hη) (hN η hη) + have hreflect_not_root : ∀ η ∈ Set.Icc (0 : ℝ) μ, + ¬ (reflect N f + C η * reflect N g).IsRoot b⁻¹ := by + intro η hη + rw [← reflect_add_C_mul] + exact (not_congr (isRoot_reflect_inv_iff hb.ne' (hN η hη))).mpr + (hbroot η hη) + have hreflect_count := + rightFamily_card_roots_gt_eq_zero_param_of_constant_degree + (f := reflect N f) (g := reflect N g) (x := b⁻¹) (hμ_pos := hμ) + hreflect_degree hreflect_split hreflect_not_root + have hcount : + ((reflect N (f + C μ * g)).roots.filter (b⁻¹ < ·)).card = + ((reflect N f).roots.filter (b⁻¹ < ·)).card := by + simpa only [reflect_add_C_mul] using hreflect_count + have hμ_transport := card_roots_reflect_Ioi + (p := f + C μ * g) (hsplit μ hμ_mem) (hzero μ hμ_mem) + (hN μ hμ_mem) (a := b⁻¹) (inv_pos.mpr hb) + have hf_transport := card_roots_reflect_Ioi + (p := f) hf_split hfzero hfN (a := b⁻¹) (inv_pos.mpr hb) + calc + ((f + C μ * g).roots.filter (fun r ↦ 0 < r ∧ r < b)).card = + ((reflect N (f + C μ * g)).roots.filter (b⁻¹ < ·)).card := by + simpa using hμ_transport.symm + _ = ((reflect N f).roots.filter (b⁻¹ < ·)).card := hcount + _ = (f.roots.filter (fun r ↦ 0 < r ∧ r < b)).card := by + simpa using hf_transport + +end RealRooted From 2547bf0284ab92daf820fc70c1f0db047cfb672b Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 14:48:52 +0000 Subject: [PATCH 04/76] Translate bounded root count constancy --- RealRooted/Linear.lean | 13 +++++ .../ReflectedRootCountLocalConstancy.lean | 52 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/RealRooted/Linear.lean b/RealRooted/Linear.lean index 3ef7cdd5..c72d4d47 100644 --- a/RealRooted/Linear.lean +++ b/RealRooted/Linear.lean @@ -298,6 +298,19 @@ lemma roots_comp_X_add_C {p : ℝ[X]} (r : ℝ) : (Multiset.count_map_eq_count' (fun y : ℝ => y - r) p.roots (fun a b hab => by simp_all) (x + r)).symm +/-- Translating a polynomial by `a` identifies its roots in `(0, b - a)` +with the original roots in `(a, b)`, including multiplicities. -/ +lemma card_roots_comp_X_add_C_Ioo (p : ℝ[X]) (a b : ℝ) : + ((p.comp (X + C a)).roots.filter (fun x => 0 < x ∧ x < b - a)).card = + (p.roots.filter (fun x => a < x ∧ x < b)).card := by + rw [roots_comp_X_add_C, Multiset.filter_map, Multiset.card_map] + congr 1 + apply Multiset.filter_congr + intro y _ + constructor <;> intro h + · constructor <;> linarith [h.1, h.2] + · constructor <;> linarith [h.1, h.2] + /-- Translation by `r` preserves positive leading coefficient. -/ lemma HasPosLeadingCoeff.comp_X_add_C {p : ℝ[X]} (hp : HasPosLeadingCoeff p) (r : ℝ) : diff --git a/RealRooted/ReflectedRootCountLocalConstancy.lean b/RealRooted/ReflectedRootCountLocalConstancy.lean index e2a4a2db..e91f336d 100644 --- a/RealRooted/ReflectedRootCountLocalConstancy.lean +++ b/RealRooted/ReflectedRootCountLocalConstancy.lean @@ -1,4 +1,5 @@ import RealRooted.DegreeDropReversal +import RealRooted.Linear import RealRooted.PositiveParameterLocalLowerCount /-! @@ -77,4 +78,55 @@ theorem rightFamily_card_roots_Ioo_zero_eq_zero_param_of_degree_bound _ = (f.roots.filter (fun r ↦ 0 < r ∧ r < b)).card := by simpa using hf_transport +/-- Root counts in any bounded open interval are constant along a split affine +family whose interval endpoints stay root-free, even if its degree drops. -/ +theorem rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound + {f g : ℝ[X]} {a b μ : ℝ} (hab : a < b) (hμ : 0 < μ) {N : ℕ} + (hN : ∀ η ∈ Set.Icc (0 : ℝ) μ, (f + C η * g).natDegree ≤ N) + (haroot : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (f + C η * g).IsRoot a) + (hsplit : ∀ η ∈ Set.Icc (0 : ℝ) μ, (f + C η * g).Splits) + (hbroot : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (f + C η * g).IsRoot b) : + ((f + C μ * g).roots.filter (fun r ↦ a < r ∧ r < b)).card = + (f.roots.filter (fun r ↦ a < r ∧ r < b)).card := by + let f' := f.comp (X + C a) + let g' := g.comp (X + C a) + have hfamily_comp (eta : ℝ) : + f' + C eta * g' = (f + C eta * g).comp (X + C a) := by + simp [f', g', Polynomial.add_comp, Polynomial.mul_comp] + have hN' : ∀ eta ∈ Set.Icc (0 : ℝ) μ, (f' + C eta * g').natDegree ≤ N := by + intro eta heta + rw [hfamily_comp, Polynomial.natDegree_comp] + simpa using hN eta heta + have hzero' : ∀ eta ∈ Set.Icc (0 : ℝ) μ, (f' + C eta * g').coeff 0 ≠ 0 := by + intro eta heta + rw [hfamily_comp] + simpa [Polynomial.coeff_zero_eq_eval_zero, Polynomial.eval_comp] using + haroot eta heta + have hsplit' : ∀ eta ∈ Set.Icc (0 : ℝ) μ, (f' + C eta * g').Splits := by + intro eta heta + rw [hfamily_comp] + exact (hsplit eta heta).comp_X_add_C a + have hbroot' : ∀ eta ∈ Set.Icc (0 : ℝ) μ, + ¬ (f' + C eta * g').IsRoot (b - a) := by + intro eta heta + rw [hfamily_comp] + intro hroot + apply hbroot eta heta + change eval b (f + C eta * g) = 0 + change eval (b - a) ((f + C eta * g).comp (X + C a)) = 0 at hroot + simpa [Polynomial.eval_comp] using hroot + have hshifted := rightFamily_card_roots_Ioo_zero_eq_zero_param_of_degree_bound + (f := f') (g := g') (b := b - a) (sub_pos.mpr hab) hμ + hN' hzero' hsplit' hbroot' + have htop := card_roots_comp_X_add_C_Ioo (f + C μ * g) a b + have hbase := card_roots_comp_X_add_C_Ioo f a b + calc + ((f + C μ * g).roots.filter (fun r ↦ a < r ∧ r < b)).card = + (((f + C μ * g).comp (X + C a)).roots.filter + (fun r ↦ 0 < r ∧ r < b - a)).card := htop.symm + _ = ((f' + C μ * g').roots.filter (fun r ↦ 0 < r ∧ r < b - a)).card := by + rw [hfamily_comp] + _ = (f'.roots.filter (fun r ↦ 0 < r ∧ r < b - a)).card := hshifted + _ = (f.roots.filter (fun r ↦ a < r ∧ r < b)).card := hbase + end RealRooted From a64e2cb3cc872569e83b1d53b783d7fc2eaa6fda Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 14:57:35 +0000 Subject: [PATCH 05/76] Prove Liu cross-owned gaps by bounded continuity --- RealRooted.lean | 1 + .../BoundedIntervalContinuity.lean | 111 ++++++++++++++++++ RealRooted/LiuOppositeSignsTheorem.lean | 1 + 3 files changed, 113 insertions(+) create mode 100644 RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean diff --git a/RealRooted.lean b/RealRooted.lean index 85b9fb56..916935fd 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -141,6 +141,7 @@ import RealRooted.LiebSokalPointwise import RealRooted.Linear import RealRooted.LinearPowerFamily import RealRooted.LiuOppositeSigns +import RealRooted.LiuOppositeSigns.BoundedIntervalContinuity import RealRooted.LiuOppositeSigns.CommonInterleaverConsequences import RealRooted.LiuOppositeSigns.Corollary22 import RealRooted.LiuOppositeSigns.DeletionBranches diff --git a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean new file mode 100644 index 00000000..87e3a3d2 --- /dev/null +++ b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean @@ -0,0 +1,111 @@ +import RealRooted.LiuOppositeSigns.Theorem21Statements +import RealRooted.ReflectedRootCountLocalConstancy + +/-! +# Liu's bounded-interval continuity argument + +This module implements the continuity step in the forward direction of Liu's +Theorem 2.1. A same-owned combined-root gap is followed from the endpoint +polynomial that is root-free on the closed gap. Fixed-degree reflection lets +the root count stay constant even when the original affine pencil drops degree. +-/ + +open Polynomial + +namespace RealRooted + +private theorem natDegree_add_C_mul_le_max (p q : ℝ[X]) (eta : ℝ) : + (p + C eta * q).natDegree ≤ max p.natDegree q.natDegree := by + exact (Polynomial.natDegree_add_le _ _).trans + (max_le (le_max_left _ _) + ((Polynomial.natDegree_C_mul_le eta q).trans (le_max_right _ _))) + +private theorem false_of_rightFamily_root_of_Ioo_count_eq_zero_param + {p q : ℝ[X]} {a b x mu : ℝ} (hax : a < x) (hxb : x < b) + (hp_no : ∀ z : ℝ, a < z → z < b → ¬ p.IsRoot z) + (ha_no : ¬ (p + C mu * q).IsRoot a) + (hxroot : (p + C mu * q).IsRoot x) + (hcount : ((p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b)).card = + (p.roots.filter (fun r ↦ a < r ∧ r < b)).card) : False := by + have hp_card_zero : (p.roots.filter (fun r ↦ a < r ∧ r < b)).card = 0 := by + rw [Multiset.card_eq_zero, Multiset.filter_eq_nil] + intro z hz hzab + exact hp_no z hzab.1 hzab.2 (Polynomial.isRoot_of_mem_roots hz) + have hfamily_card_zero : + ((p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b)).card = 0 := + hcount.trans hp_card_zero + have hfamily_ne : p + C mu * q ≠ 0 := by + intro hzero + apply ha_no + simp [hzero, Polynomial.IsRoot.def] + have hx_mem : x ∈ (p + C mu * q).roots := + (Polynomial.mem_roots hfamily_ne).mpr hxroot + have hx_filter : x ∈ (p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b) := + Multiset.mem_filter.mpr ⟨hx_mem, hax, hxb⟩ + have hfilter_zero : + (p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b) = 0 := + Multiset.card_eq_zero.mp hfamily_card_zero + rw [hfilter_zero] at hx_filter + simp at hx_filter + +/-- Liu's bounded-interval continuity argument forces every parity-relevant +finite combined-root gap to have endpoints owned by opposite polynomials. -/ +theorem OppositeLeadingSigns.crossOwnedNotOddGaps_of_boundedIntervalContinuity + {f g : ℝ[X]} (hsgn : OppositeLeadingSigns f g) + (hfg : PosComboRealRooted f g) (hno : NoCommonRoots f g) + (hf : f.Splits) (hg : g.Splits) : + CrossOwnedNotOddGaps f g := by + intro a b x hax hxb ha_root hb_root hgap hnot_odd + have hf_no : ∀ z : ℝ, a < z → z < b → ¬ f.IsRoot z := + fun z haz hzb ↦ (hgap z haz hzb).1 + have hg_no : ∀ z : ℝ, a < z → z < b → ¬ g.IsRoot z := + fun z haz hzb ↦ (hgap z haz hzb).2 + obtain ⟨mu, hmu_pos, hmu_root, _⟩ := + hsgn.exists_unique_pos_crossing_add_right_of_not_odd_intCard_roots_gt_sub + hfg hno hf hg (hf_no x hax hxb) (hg_no x hax hxb) hnot_odd + rcases ha_root with hfa | hga + · rcases hb_root with hfb | hgb + · have hmu_inv_pos : 0 < mu⁻¹ := inv_pos.mpr hmu_pos + have hmu_inv_root : (g + C mu⁻¹ * f).IsRoot x := + (add_right_isRoot_iff_add_left_inv hmu_pos.ne').mp hmu_root + have hcount : + ((g + C mu⁻¹ * f).roots.filter (fun r ↦ a < r ∧ r < b)).card = + (g.roots.filter (fun r ↦ a < r ∧ r < b)).card := by + apply rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound + (f := g) (g := f) (N := max g.natDegree f.natDegree) + (lt_trans hax hxb) hmu_inv_pos + · intro eta _ + exact natDegree_add_C_mul_le_max g f eta + · intro eta _ + exact hno.symm.rightFamily_not_isRoot_of_right_root hfa + · intro eta heta + exact PosComboRealRooted.splits_add_right_of_nonneg + (PosComboRealRooted.comm hfg) hg heta.1 + · intro eta _ + exact hno.symm.rightFamily_not_isRoot_of_right_root hfb + exact False.elim <| false_of_rightFamily_root_of_Ioo_count_eq_zero_param + hax hxb hg_no + (hno.symm.rightFamily_not_isRoot_of_right_root (mu := mu⁻¹) hfa) + hmu_inv_root hcount + · exact Or.inl ⟨hfa, hgb⟩ + · rcases hb_root with hfb | hgb + · exact Or.inr ⟨hga, hfb⟩ + · have hcount : + ((f + C mu * g).roots.filter (fun r ↦ a < r ∧ r < b)).card = + (f.roots.filter (fun r ↦ a < r ∧ r < b)).card := by + apply rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound + (f := f) (g := g) (N := max f.natDegree g.natDegree) + (lt_trans hax hxb) hmu_pos + · intro eta _ + exact natDegree_add_C_mul_le_max f g eta + · intro eta _ + exact hno.rightFamily_not_isRoot_of_right_root hga + · intro eta heta + exact PosComboRealRooted.splits_add_right_of_nonneg hfg hf heta.1 + · intro eta _ + exact hno.rightFamily_not_isRoot_of_right_root hgb + exact False.elim <| false_of_rightFamily_root_of_Ioo_count_eq_zero_param + hax hxb hf_no (hno.rightFamily_not_isRoot_of_right_root (mu := mu) hga) + hmu_root hcount + +end RealRooted diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index 7182b3cc..ae875d95 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -1,3 +1,4 @@ +import RealRooted.LiuOppositeSigns.BoundedIntervalContinuity import RealRooted.LiuOppositeSigns.CommonInterleaverConsequences import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderAssembly import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderLower From e0cea04982262a9d9f25ef6fbd32b738ac789e6b Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:05:09 +0000 Subject: [PATCH 06/76] Deduplicate Liu bounded-continuity proof --- .../BoundedIntervalContinuity.lean | 91 +++++++------------ 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean index 87e3a3d2..3cddf2a1 100644 --- a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean +++ b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean @@ -14,36 +14,38 @@ open Polynomial namespace RealRooted -private theorem natDegree_add_C_mul_le_max (p q : ℝ[X]) (eta : ℝ) : - (p + C eta * q).natDegree ≤ max p.natDegree q.natDegree := by - exact (Polynomial.natDegree_add_le _ _).trans - (max_le (le_max_left _ _) - ((Polynomial.natDegree_C_mul_le eta q).trans (le_max_right _ _))) - -private theorem false_of_rightFamily_root_of_Ioo_count_eq_zero_param - {p q : ℝ[X]} {a b x mu : ℝ} (hax : a < x) (hxb : x < b) +private theorem false_of_rightFamily_root_of_boundedIntervalContinuity + {p q : ℝ[X]} {a b x μ : ℝ} (hax : a < x) (hxb : x < b) (hμ : 0 < μ) (hp_no : ∀ z : ℝ, a < z → z < b → ¬ p.IsRoot z) - (ha_no : ¬ (p + C mu * q).IsRoot a) - (hxroot : (p + C mu * q).IsRoot x) - (hcount : ((p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b)).card = - (p.roots.filter (fun r ↦ a < r ∧ r < b)).card) : False := by + (ha_no : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (p + C η * q).IsRoot a) + (hsplit : ∀ η ∈ Set.Icc (0 : ℝ) μ, (p + C η * q).Splits) + (hb_no : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (p + C η * q).IsRoot b) + (hxroot : (p + C μ * q).IsRoot x) : False := by + have hcount := rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound + (f := p) (g := q) (N := max p.natDegree q.natDegree) + (lt_trans hax hxb) hμ + (fun η _ ↦ (Polynomial.natDegree_add_le _ _).trans + (max_le (le_max_left _ _) + ((Polynomial.natDegree_C_mul_le η q).trans (le_max_right _ _)))) + ha_no hsplit hb_no have hp_card_zero : (p.roots.filter (fun r ↦ a < r ∧ r < b)).card = 0 := by rw [Multiset.card_eq_zero, Multiset.filter_eq_nil] intro z hz hzab exact hp_no z hzab.1 hzab.2 (Polynomial.isRoot_of_mem_roots hz) have hfamily_card_zero : - ((p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b)).card = 0 := + ((p + C μ * q).roots.filter (fun r ↦ a < r ∧ r < b)).card = 0 := hcount.trans hp_card_zero - have hfamily_ne : p + C mu * q ≠ 0 := by + have hμ_mem : μ ∈ Set.Icc (0 : ℝ) μ := ⟨hμ.le, le_rfl⟩ + have hfamily_ne : p + C μ * q ≠ 0 := by intro hzero - apply ha_no + apply ha_no μ hμ_mem simp [hzero, Polynomial.IsRoot.def] - have hx_mem : x ∈ (p + C mu * q).roots := + have hx_mem : x ∈ (p + C μ * q).roots := (Polynomial.mem_roots hfamily_ne).mpr hxroot - have hx_filter : x ∈ (p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b) := + have hx_filter : x ∈ (p + C μ * q).roots.filter (fun r ↦ a < r ∧ r < b) := Multiset.mem_filter.mpr ⟨hx_mem, hax, hxb⟩ have hfilter_zero : - (p + C mu * q).roots.filter (fun r ↦ a < r ∧ r < b) = 0 := + (p + C μ * q).roots.filter (fun r ↦ a < r ∧ r < b) = 0 := Multiset.card_eq_zero.mp hfamily_card_zero rw [hfilter_zero] at hx_filter simp at hx_filter @@ -60,52 +62,25 @@ theorem OppositeLeadingSigns.crossOwnedNotOddGaps_of_boundedIntervalContinuity fun z haz hzb ↦ (hgap z haz hzb).1 have hg_no : ∀ z : ℝ, a < z → z < b → ¬ g.IsRoot z := fun z haz hzb ↦ (hgap z haz hzb).2 - obtain ⟨mu, hmu_pos, hmu_root, _⟩ := + obtain ⟨μ, hμ_pos, hμ_root, _⟩ := hsgn.exists_unique_pos_crossing_add_right_of_not_odd_intCard_roots_gt_sub hfg hno hf hg (hf_no x hax hxb) (hg_no x hax hxb) hnot_odd rcases ha_root with hfa | hga · rcases hb_root with hfb | hgb - · have hmu_inv_pos : 0 < mu⁻¹ := inv_pos.mpr hmu_pos - have hmu_inv_root : (g + C mu⁻¹ * f).IsRoot x := - (add_right_isRoot_iff_add_left_inv hmu_pos.ne').mp hmu_root - have hcount : - ((g + C mu⁻¹ * f).roots.filter (fun r ↦ a < r ∧ r < b)).card = - (g.roots.filter (fun r ↦ a < r ∧ r < b)).card := by - apply rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound - (f := g) (g := f) (N := max g.natDegree f.natDegree) - (lt_trans hax hxb) hmu_inv_pos - · intro eta _ - exact natDegree_add_C_mul_le_max g f eta - · intro eta _ - exact hno.symm.rightFamily_not_isRoot_of_right_root hfa - · intro eta heta - exact PosComboRealRooted.splits_add_right_of_nonneg - (PosComboRealRooted.comm hfg) hg heta.1 - · intro eta _ - exact hno.symm.rightFamily_not_isRoot_of_right_root hfb - exact False.elim <| false_of_rightFamily_root_of_Ioo_count_eq_zero_param - hax hxb hg_no - (hno.symm.rightFamily_not_isRoot_of_right_root (mu := mu⁻¹) hfa) - hmu_inv_root hcount + · exact False.elim <| false_of_rightFamily_root_of_boundedIntervalContinuity + (p := g) (q := f) hax hxb (inv_pos.mpr hμ_pos) hg_no + (fun _ _ ↦ hno.symm.rightFamily_not_isRoot_of_right_root hfa) + (fun _ hη ↦ PosComboRealRooted.splits_add_right_of_nonneg + (PosComboRealRooted.comm hfg) hg hη.1) + (fun _ _ ↦ hno.symm.rightFamily_not_isRoot_of_right_root hfb) + ((add_right_isRoot_iff_add_left_inv hμ_pos.ne').mp hμ_root) · exact Or.inl ⟨hfa, hgb⟩ · rcases hb_root with hfb | hgb · exact Or.inr ⟨hga, hfb⟩ - · have hcount : - ((f + C mu * g).roots.filter (fun r ↦ a < r ∧ r < b)).card = - (f.roots.filter (fun r ↦ a < r ∧ r < b)).card := by - apply rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound - (f := f) (g := g) (N := max f.natDegree g.natDegree) - (lt_trans hax hxb) hmu_pos - · intro eta _ - exact natDegree_add_C_mul_le_max f g eta - · intro eta _ - exact hno.rightFamily_not_isRoot_of_right_root hga - · intro eta heta - exact PosComboRealRooted.splits_add_right_of_nonneg hfg hf heta.1 - · intro eta _ - exact hno.rightFamily_not_isRoot_of_right_root hgb - exact False.elim <| false_of_rightFamily_root_of_Ioo_count_eq_zero_param - hax hxb hf_no (hno.rightFamily_not_isRoot_of_right_root (mu := mu) hga) - hmu_root hcount + · exact False.elim <| false_of_rightFamily_root_of_boundedIntervalContinuity + (p := f) (q := g) hax hxb hμ_pos hf_no + (fun _ _ ↦ hno.rightFamily_not_isRoot_of_right_root hga) + (fun _ hη ↦ PosComboRealRooted.splits_add_right_of_nonneg hfg hf hη.1) + (fun _ _ ↦ hno.rightFamily_not_isRoot_of_right_root hgb) hμ_root end RealRooted From c1591c9bcca35c008a434f1c6613652a23e4e0b1 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:08:19 +0000 Subject: [PATCH 07/76] Centralize bounded interval degree plumbing --- RealRooted/Linear.lean | 4 +--- .../BoundedIntervalContinuity.lean | 9 ++------- .../ReflectedRootCountLocalConstancy.lean | 20 +++++++++++++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/RealRooted/Linear.lean b/RealRooted/Linear.lean index c72d4d47..e93fd8dc 100644 --- a/RealRooted/Linear.lean +++ b/RealRooted/Linear.lean @@ -307,9 +307,7 @@ lemma card_roots_comp_X_add_C_Ioo (p : ℝ[X]) (a b : ℝ) : congr 1 apply Multiset.filter_congr intro y _ - constructor <;> intro h - · constructor <;> linarith [h.1, h.2] - · constructor <;> linarith [h.1, h.2] + constructor <;> intro h <;> constructor <;> linarith [h.1, h.2] /-- Translation by `r` preserves positive leading coefficient. -/ lemma HasPosLeadingCoeff.comp_X_add_C {p : ℝ[X]} diff --git a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean index 3cddf2a1..5bb666cb 100644 --- a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean +++ b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean @@ -21,13 +21,8 @@ private theorem false_of_rightFamily_root_of_boundedIntervalContinuity (hsplit : ∀ η ∈ Set.Icc (0 : ℝ) μ, (p + C η * q).Splits) (hb_no : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (p + C η * q).IsRoot b) (hxroot : (p + C μ * q).IsRoot x) : False := by - have hcount := rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound - (f := p) (g := q) (N := max p.natDegree q.natDegree) - (lt_trans hax hxb) hμ - (fun η _ ↦ (Polynomial.natDegree_add_le _ _).trans - (max_le (le_max_left _ _) - ((Polynomial.natDegree_C_mul_le η q).trans (le_max_right _ _)))) - ha_no hsplit hb_no + have hcount := rightFamily_card_roots_Ioo_eq_zero_param + (f := p) (g := q) (lt_trans hax hxb) hμ ha_no hsplit hb_no have hp_card_zero : (p.roots.filter (fun r ↦ a < r ∧ r < b)).card = 0 := by rw [Multiset.card_eq_zero, Multiset.filter_eq_nil] intro z hz hzab diff --git a/RealRooted/ReflectedRootCountLocalConstancy.lean b/RealRooted/ReflectedRootCountLocalConstancy.lean index e91f336d..fb9e6e94 100644 --- a/RealRooted/ReflectedRootCountLocalConstancy.lean +++ b/RealRooted/ReflectedRootCountLocalConstancy.lean @@ -129,4 +129,24 @@ theorem rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound _ = (f'.roots.filter (fun r ↦ 0 < r ∧ r < b - a)).card := hshifted _ = (f.roots.filter (fun r ↦ a < r ∧ r < b)).card := hbase +/-- Root counts in a bounded open interval are constant along a split affine +family whose endpoints stay root-free, with the common degree bound inferred +from the two endpoint polynomials. -/ +theorem rightFamily_card_roots_Ioo_eq_zero_param + {f g : ℝ[X]} {a b μ : ℝ} (hab : a < b) (hμ : 0 < μ) + (haroot : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (f + C η * g).IsRoot a) + (hsplit : ∀ η ∈ Set.Icc (0 : ℝ) μ, (f + C η * g).Splits) + (hbroot : ∀ η ∈ Set.Icc (0 : ℝ) μ, ¬ (f + C η * g).IsRoot b) : + ((f + C μ * g).roots.filter (fun r ↦ a < r ∧ r < b)).card = + (f.roots.filter (fun r ↦ a < r ∧ r < b)).card := by + apply rightFamily_card_roots_Ioo_eq_zero_param_of_degree_bound + (N := max f.natDegree g.natDegree) hab hμ + · intro η _ + exact (Polynomial.natDegree_add_le _ _).trans + (max_le (le_max_left _ _) + ((Polynomial.natDegree_C_mul_le η g).trans (le_max_right _ _))) + · exact haroot + · exact hsplit + · exact hbroot + end RealRooted From 1ba0438beb0d79c7d8d8750da8e6a25015b9441b Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:15:30 +0000 Subject: [PATCH 08/76] Connect Liu bounded continuity to finite descent --- .../BoundedIntervalContinuity.lean | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean index 5bb666cb..de5e9729 100644 --- a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean +++ b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean @@ -78,4 +78,26 @@ theorem OppositeLeadingSigns.crossOwnedNotOddGaps_of_boundedIntervalContinuity (fun _ hη ↦ PosComboRealRooted.splits_add_right_of_nonneg hfg hf hη.1) (fun _ _ ↦ hno.rightFamily_not_isRoot_of_right_root hgb) hμ_root +/-- Liu's forward implication in the no-common, nonconstant, simple-root regime. + +This is the rigorous part of the reduction used in the proof of Theorem 2.1: +compatibility supplies positive-combination real-rootedness, bounded root-count +continuity rules out same-owner odd gaps, and the finite descent gives the Liu +branch. The paper's assertion that no common zeros imply simple zeros is false +without an additional perturbation argument, so simplicity remains explicit +here rather than being inferred from `hno`. -/ +theorem LiuOppositeSigns.theorem21RootCountBranches_of_compatible_noCommon_nonconstant_of_simple + {f g : ℝ[X]} (hf : f.Splits) (hg : g.Splits) + (hsgn : OppositeLeadingSigns f g) (hno : NoCommonRoots f g) + (hf_deg : f.natDegree ≠ 0) (hg_deg : g.natDegree ≠ 0) + (hsimple_f : HasSimpleRoots f) (hsimple_g : HasSimpleRoots g) + (hcompat : Compatible f g) : + theorem21RootCountBranches f g := by + have hfg : PosComboRealRooted f g := + posComboRealRooted_of_compatible_noCommon_nonconstant + hcompat hno hg hg_deg + exact theorem21RootCountBranches_of_crossOwned hsgn hf hg + hf_deg hg_deg hsimple_f hsimple_g hno + (hsgn.crossOwnedNotOddGaps_of_boundedIntervalContinuity hfg hno hf hg) + end RealRooted From a1f92d93d8907be8fcd0fd47a848c530ba30c91e Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:22:23 +0000 Subject: [PATCH 09/76] Preserve compatibility under derivative regularization --- RealRooted/Compatibility/Basic.lean | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/RealRooted/Compatibility/Basic.lean b/RealRooted/Compatibility/Basic.lean index f629a269..3ff67b48 100644 --- a/RealRooted/Compatibility/Basic.lean +++ b/RealRooted/Compatibility/Basic.lean @@ -106,6 +106,27 @@ lemma derivative {f g : ℝ[X]} (h : Compatible f g) : · rw [hcomb] exact derivative_eq_zero_or_ne_zero_and_splits hrr.2 +/-- Compatibility is preserved by the degree-preserving root regularizer +`iterateTDeriv` at every positive shift. -/ +lemma iterateTDeriv {f g : ℝ[X]} (h : Compatible f g) + {eps : ℝ} (heps : 0 < eps) (n : ℕ) : + Compatible (RealRooted.iterateTDeriv eps n f) + (RealRooted.iterateTDeriv eps n g) := by + intro α β hα hβ + have hcomb : + C α * RealRooted.iterateTDeriv eps n f + + C β * RealRooted.iterateTDeriv eps n g = + RealRooted.iterateTDeriv eps n (C α * f + C β * g) := by + rw [RealRooted.iterateTDeriv_add, RealRooted.iterateTDeriv_C_mul, + RealRooted.iterateTDeriv_C_mul] + rcases h α β hα hβ with hzero | hrr + · left + simp [hcomb, hzero] + · right + rw [hcomb] + exact ⟨RealRooted.iterateTDeriv_ne_zero hrr.1, + RealRooted.splits_iterateTDeriv heps hrr.2⟩ + lemma isRealRooted_left {f g : ℝ[X]} (h : Compatible f g) (hf_pos : HasPosLeadingCoeff f) : (f ≠ 0 ∧ f.Splits) := by From a33ab408cae5e2054d4ce8961ac5ca53317d6cff Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:26:02 +0000 Subject: [PATCH 10/76] Centralize derivative regularizer linearity --- RealRooted/AllCombo.lean | 5 ----- RealRooted/Compatibility/Basic.lean | 11 +++-------- RealRooted/IteratedDerivativeShift.lean | 5 +++++ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/RealRooted/AllCombo.lean b/RealRooted/AllCombo.lean index 24c817f1..740519fd 100644 --- a/RealRooted/AllCombo.lean +++ b/RealRooted/AllCombo.lean @@ -87,11 +87,6 @@ lemma allComboRealRooted_iterate_derivative exact allComboRealRooted_derivative (allComboRealRooted_iterate_derivative hall n) -lemma iterateTDeriv_linear_combo (eps α β : ℝ) (n : ℕ) (f g : ℝ[X]) : - iterateTDeriv eps n (C α * f + C β * g) = - C α * iterateTDeriv eps n f + C β * iterateTDeriv eps n g := by - rw [iterateTDeriv_add, iterateTDeriv_C_mul, iterateTDeriv_C_mul] - lemma TDeriv_eq_zero_iff (eps : ℝ) {p : ℝ[X]} : TDeriv eps p = 0 ↔ p = 0 := by constructor diff --git a/RealRooted/Compatibility/Basic.lean b/RealRooted/Compatibility/Basic.lean index 3ff67b48..9a35b601 100644 --- a/RealRooted/Compatibility/Basic.lean +++ b/RealRooted/Compatibility/Basic.lean @@ -113,17 +113,12 @@ lemma iterateTDeriv {f g : ℝ[X]} (h : Compatible f g) Compatible (RealRooted.iterateTDeriv eps n f) (RealRooted.iterateTDeriv eps n g) := by intro α β hα hβ - have hcomb : - C α * RealRooted.iterateTDeriv eps n f + - C β * RealRooted.iterateTDeriv eps n g = - RealRooted.iterateTDeriv eps n (C α * f + C β * g) := by - rw [RealRooted.iterateTDeriv_add, RealRooted.iterateTDeriv_C_mul, - RealRooted.iterateTDeriv_C_mul] rcases h α β hα hβ with hzero | hrr · left - simp [hcomb, hzero] + rw [← RealRooted.iterateTDeriv_linear_combo, hzero] + simp · right - rw [hcomb] + rw [← RealRooted.iterateTDeriv_linear_combo] exact ⟨RealRooted.iterateTDeriv_ne_zero hrr.1, RealRooted.splits_iterateTDeriv heps hrr.2⟩ diff --git a/RealRooted/IteratedDerivativeShift.lean b/RealRooted/IteratedDerivativeShift.lean index 881223c9..4f120a25 100644 --- a/RealRooted/IteratedDerivativeShift.lean +++ b/RealRooted/IteratedDerivativeShift.lean @@ -343,6 +343,11 @@ lemma iterateTDeriv_C_mul (eps c : ℝ) : rw [iterateTDeriv_succ, iterateTDeriv_succ, iterateTDeriv_C_mul] exact TDeriv_C_mul eps c _ +lemma iterateTDeriv_linear_combo (eps α β : ℝ) (n : ℕ) (f g : ℝ[X]) : + iterateTDeriv eps n (C α * f + C β * g) = + C α * iterateTDeriv eps n f + C β * iterateTDeriv eps n g := by + rw [iterateTDeriv_add, iterateTDeriv_C_mul, iterateTDeriv_C_mul] + private lemma TDeriv_X_sub_C (eps r : ℝ) : TDeriv eps (X - C r) = X - C (r + eps) := by simp [TDeriv] From e968692bb18d32886b5f20292ca523b29d04a491 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:31:36 +0000 Subject: [PATCH 11/76] Match roots under small derivative shifts --- RealRooted.lean | 1 + RealRooted/DerivativeShiftRootMatching.lean | 60 +++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 RealRooted/DerivativeShiftRootMatching.lean diff --git a/RealRooted.lean b/RealRooted.lean index 916935fd..68d7f146 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -99,6 +99,7 @@ import RealRooted.DegreeDropDivXPrec import RealRooted.DegreeDropReversal import RealRooted.DegreeIncreasingLocalLowerCount import RealRooted.Derivative +import RealRooted.DerivativeShiftRootMatching import RealRooted.DifferentialBlocks import RealRooted.ElementaryDifferential import RealRooted.EulerOperator diff --git a/RealRooted/DerivativeShiftRootMatching.lean b/RealRooted/DerivativeShiftRootMatching.lean new file mode 100644 index 00000000..3f9df9f2 --- /dev/null +++ b/RealRooted/DerivativeShiftRootMatching.lean @@ -0,0 +1,60 @@ +import RealRooted.IteratedDerivativeShift +import RealRooted.RootMultiplicityMatching +import RealRooted.SameDegreeMultiplicityLowerCount + +/-! +# Root matching for derivative shifts + +This module combines coefficient continuity of `TDeriv` with the +multiplicity-aware finite matching API. It provides the root transport needed +when derivative shifts are used to regularize a compatible polynomial pair. +-/ + +open Polynomial + +noncomputable section + +namespace RealRooted + +/-- For every positive radius, all roots of a sufficiently small positive +`TDeriv` shift can be matched, with multiplicity, to roots of the original +splitting polynomial within that radius. -/ +theorem exists_delta_roots_rel_TDeriv + {p : ℝ[X]} (hp : p.Splits) {ρ : ℝ} (hρ : 0 < ρ) : + ∃ δ > 0, ∀ ⦃eps : ℝ⦄, 0 < eps → eps < δ → + Multiset.Rel (fun r q ↦ |q - r| < ρ) p.roots (TDeriv eps p).roots := by + obtain ⟨η, hη_pos, hηρ, hsep⟩ := + Multiset.exists_pos_lt_and_two_mul_le_abs_sub_toFinset p.roots hρ + have hp0 : (p + C (0 : ℝ) * (-p.derivative)).Splits := by + simpa using hp + obtain ⟨δ, hδ_pos, hlocal⟩ := + exists_eps_forall_root_count_le_card_filter_near + (f := p) (g := -p.derivative) (μ0 := 0) hp0 η hη_pos + refine ⟨δ, hδ_pos, ?_⟩ + intro eps heps_pos hepsδ + have heps_abs : |eps - 0| < δ := by + simpa [abs_of_pos heps_pos] using hepsδ + have hsplit : (p + C eps * (-p.derivative)).Splits := by + simpa [TDeriv] using splits_tderiv heps_pos hp + have hdeg : + (p + C eps * (-p.derivative)).natDegree = + (p + C (0 : ℝ) * (-p.derivative)).natDegree := by + simp [TDeriv] + have hcount := hlocal eps heps_abs hsplit hdeg + have hcount' : ∀ a ∈ p.roots.toFinset, + p.roots.count a ≤ + ((TDeriv eps p).roots.filter (fun q ↦ |q - a| < η)).card := by + simpa [TDeriv] using hcount + obtain ⟨u, hu, hrel⟩ := + Multiset.exists_rel_le_of_forall_le_count hsep hcount' + have hcard : (TDeriv eps p).roots.card = p.roots.card := by + rw [card_roots_of_splits (splits_tderiv heps_pos hp), natDegree_TDeriv, + card_roots_of_splits hp] + have hu_card : u.card = (TDeriv eps p).roots.card := by + simpa [hcard] using (Multiset.card_eq_card_of_rel hrel).symm + have hut : u = (TDeriv eps p).roots := + Multiset.eq_of_le_of_card_le hu hu_card.ge + rw [hut] at hrel + exact hrel.mono fun _ _ hclose ↦ lt_trans hclose hηρ + +end RealRooted From 8907f1cae14920be68ffe75c8e51a2fcbc7f6bb1 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:35:17 +0000 Subject: [PATCH 12/76] Preserve no-common roots under small shifts --- RealRooted.lean | 1 + .../DerivativeShiftRegularization.lean | 77 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean diff --git a/RealRooted.lean b/RealRooted.lean index 68d7f146..7fa7efeb 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -146,6 +146,7 @@ import RealRooted.LiuOppositeSigns.BoundedIntervalContinuity import RealRooted.LiuOppositeSigns.CommonInterleaverConsequences import RealRooted.LiuOppositeSigns.Corollary22 import RealRooted.LiuOppositeSigns.DeletionBranches +import RealRooted.LiuOppositeSigns.DerivativeShiftRegularization import RealRooted.LiuOppositeSigns.FactorReturnAssembly import RealRooted.LiuOppositeSigns.FactorReturnLeft import RealRooted.LiuOppositeSigns.FactorReturnStatements diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean new file mode 100644 index 00000000..014bdbd4 --- /dev/null +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean @@ -0,0 +1,77 @@ +import RealRooted.DerivativeShiftRootMatching +import RealRooted.LiuOppositeSigns.Theorem21Statements + +/-! +# Derivative-shift regularization for Liu pairs + +This module proves that sufficiently small common positive derivative shifts +preserve the no-common-root condition used in Liu's finite root-count descent. +-/ + +open Polynomial + +noncomputable section + +namespace RealRooted +namespace LiuOppositeSigns + +private theorem exists_left_mem_of_right_mem_of_rel + {α β : Type*} {r : α → β → Prop} {s : Multiset α} {t : Multiset β} + (hrel : Multiset.Rel r s t) {b : β} (hb : b ∈ t) : + ∃ a ∈ s, r a b := by + induction hrel with + | zero => simp at hb + | @cons a b s t hab _ ih => + rw [Multiset.mem_cons] at hb + rcases hb with rfl | hb + · exact ⟨a, Multiset.mem_cons_self, hab⟩ + · obtain ⟨a', ha', ha'b⟩ := ih hb + exact ⟨a', Multiset.mem_cons_of_mem ha', ha'b⟩ + +/-- A common positive `TDeriv` shift of two nonzero splitting polynomials with +no common roots still has no common roots when the shift is sufficiently +small. -/ +theorem NoCommonRoots.exists_delta_TDeriv + {f g : ℝ[X]} (hno : NoCommonRoots f g) + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) (hf : f.Splits) (hg : g.Splits) : + ∃ δ > 0, ∀ ⦃eps : ℝ⦄, 0 < eps → eps < δ → + NoCommonRoots (TDeriv eps f) (TDeriv eps g) := by + obtain ⟨η, hη_pos, _, hsep⟩ := + Multiset.exists_pos_lt_and_two_mul_le_abs_sub_toFinset + (f.roots + g.roots) zero_lt_one + obtain ⟨δf, hδf_pos, hfrel⟩ := + exists_delta_roots_rel_TDeriv hf hη_pos + obtain ⟨δg, hδg_pos, hgrel⟩ := + exists_delta_roots_rel_TDeriv hg hη_pos + refine ⟨min δf δg, lt_min hδf_pos hδg_pos, ?_⟩ + intro eps heps_pos hepsδ x hfx hgx + have hfx_mem : x ∈ (TDeriv eps f).roots := + (Polynomial.mem_roots (TDeriv_ne_zero hf_ne)).mpr hfx + have hgx_mem : x ∈ (TDeriv eps g).roots := + (Polynomial.mem_roots (TDeriv_ne_zero hg_ne)).mpr hgx + obtain ⟨a, ha_mem, hax⟩ := + exists_left_mem_of_right_mem_of_rel + (hfrel heps_pos (hepsδ.trans_le (min_le_left _ _))) hfx_mem + obtain ⟨b, hb_mem, hbx⟩ := + exists_left_mem_of_right_mem_of_rel + (hgrel heps_pos (hepsδ.trans_le (min_le_right _ _))) hgx_mem + have hab_ne : a ≠ b := by + rintro rfl + exact (hno a ((Polynomial.mem_roots hf_ne).mp ha_mem)) + ((Polynomial.mem_roots hg_ne).mp hb_mem) + have ha_sum : a ∈ (f.roots + g.roots).toFinset := by + simp [ha_mem] + have hb_sum : b ∈ (f.roots + g.roots).toFinset := by + simp [hb_mem] + have hab_sep : 2 * η ≤ |a - b| := + hsep a ha_sum b hb_sum hab_ne + have hab_lt : |a - b| < 2 * η := by + calc + |a - b| ≤ |a - x| + |x - b| := + abs_sub_le_abs_sub_add_abs_sub a x b + _ = |x - a| + |x - b| := by rw [abs_sub_comm a x] + _ < 2 * η := by linarith + exact (not_lt_of_ge hab_sep) hab_lt + +end LiuOppositeSigns +end RealRooted From 5e00ceaa10badb4f8ce737337aad388d7746625c Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:38:12 +0000 Subject: [PATCH 13/76] Centralize multiset matching eliminators --- RealRooted/DerivativeShiftRootMatching.lean | 9 +---- .../DerivativeShiftRegularization.lean | 17 +------- RealRooted/RootMultiplicityMatching.lean | 39 ++++++++++++++++--- RealRooted/SameDegreeCountFromAnalytic.lean | 11 ++---- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/RealRooted/DerivativeShiftRootMatching.lean b/RealRooted/DerivativeShiftRootMatching.lean index 3f9df9f2..057abe42 100644 --- a/RealRooted/DerivativeShiftRootMatching.lean +++ b/RealRooted/DerivativeShiftRootMatching.lean @@ -45,16 +45,11 @@ theorem exists_delta_roots_rel_TDeriv p.roots.count a ≤ ((TDeriv eps p).roots.filter (fun q ↦ |q - a| < η)).card := by simpa [TDeriv] using hcount - obtain ⟨u, hu, hrel⟩ := - Multiset.exists_rel_le_of_forall_le_count hsep hcount' have hcard : (TDeriv eps p).roots.card = p.roots.card := by rw [card_roots_of_splits (splits_tderiv heps_pos hp), natDegree_TDeriv, card_roots_of_splits hp] - have hu_card : u.card = (TDeriv eps p).roots.card := by - simpa [hcard] using (Multiset.card_eq_card_of_rel hrel).symm - have hut : u = (TDeriv eps p).roots := - Multiset.eq_of_le_of_card_le hu hu_card.ge - rw [hut] at hrel + have hrel := + Multiset.rel_of_forall_le_count_of_card_eq hsep hcount' hcard exact hrel.mono fun _ _ hclose ↦ lt_trans hclose hηρ end RealRooted diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean index 014bdbd4..3ed91ebd 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean @@ -15,19 +15,6 @@ noncomputable section namespace RealRooted namespace LiuOppositeSigns -private theorem exists_left_mem_of_right_mem_of_rel - {α β : Type*} {r : α → β → Prop} {s : Multiset α} {t : Multiset β} - (hrel : Multiset.Rel r s t) {b : β} (hb : b ∈ t) : - ∃ a ∈ s, r a b := by - induction hrel with - | zero => simp at hb - | @cons a b s t hab _ ih => - rw [Multiset.mem_cons] at hb - rcases hb with rfl | hb - · exact ⟨a, Multiset.mem_cons_self, hab⟩ - · obtain ⟨a', ha', ha'b⟩ := ih hb - exact ⟨a', Multiset.mem_cons_of_mem ha', ha'b⟩ - /-- A common positive `TDeriv` shift of two nonzero splitting polynomials with no common roots still has no common roots when the shift is sufficiently small. -/ @@ -50,10 +37,10 @@ theorem NoCommonRoots.exists_delta_TDeriv have hgx_mem : x ∈ (TDeriv eps g).roots := (Polynomial.mem_roots (TDeriv_ne_zero hg_ne)).mpr hgx obtain ⟨a, ha_mem, hax⟩ := - exists_left_mem_of_right_mem_of_rel + Multiset.Rel.exists_left_of_mem_right (hfrel heps_pos (hepsδ.trans_le (min_le_left _ _))) hfx_mem obtain ⟨b, hb_mem, hbx⟩ := - exists_left_mem_of_right_mem_of_rel + Multiset.Rel.exists_left_of_mem_right (hgrel heps_pos (hepsδ.trans_le (min_le_right _ _))) hgx_mem have hab_ne : a ≠ b := by rintro rfl diff --git a/RealRooted/RootMultiplicityMatching.lean b/RealRooted/RootMultiplicityMatching.lean index 68ef374c..1210bebe 100644 --- a/RealRooted/RootMultiplicityMatching.lean +++ b/RealRooted/RootMultiplicityMatching.lean @@ -142,6 +142,37 @@ theorem exists_rel_le_of_forall_le_count {s t : Multiset ℝ} {δ : ℝ} · exact le_trans (Finset.sum_le_sum fun a ha => (hcluster a ha).1) (sum_filter_ball_le hsep) +/-- Every target member in a lifted multiset relation has a related source +member. -/ +theorem Rel.exists_left_of_mem_right + {α β : Type*} {r : α → β → Prop} {s : Multiset α} {t : Multiset β} + (hrel : Rel r s t) {b : β} (hb : b ∈ t) : + ∃ a ∈ s, r a b := by + induction hrel with + | zero => simp at hb + | @cons a b s t hab _ ih => + rw [mem_cons] at hb + rcases hb with rfl | hb + · exact ⟨a, mem_cons_self, hab⟩ + · obtain ⟨a', ha', ha'b⟩ := ih hb + exact ⟨a', mem_cons_of_mem ha', ha'b⟩ + +/-- Local multiplicity lower bounds give a full proximity matching when the +source and target multisets have the same cardinality. -/ +theorem rel_of_forall_le_count_of_card_eq {s t : Multiset ℝ} {δ : ℝ} + (hsep : ∀ a ∈ s.toFinset, ∀ b ∈ s.toFinset, + a ≠ b → 2 * δ ≤ |a - b|) + (hcount : ∀ a ∈ s.toFinset, + s.count a ≤ (t.filter (fun q => |q - a| < δ)).card) + (hcard : t.card = s.card) : + Rel (fun r q => |q - r| < δ) s t := by + obtain ⟨u, hu, hrel⟩ := + exists_rel_le_of_forall_le_count hsep hcount + have hu_card : u.card = t.card := by + simpa [hcard] using (card_eq_card_of_rel hrel).symm + have hut : u = t := eq_of_le_of_card_le hu hu_card.ge + rwa [hut] at hrel + /-- Finite local-count bridge for same-cardinality perturbations. @@ -158,12 +189,8 @@ theorem card_filter_gt_eq_of_forall_le_count_and_card_eq s.count a ≤ (t.filter (fun q => |q - a| < δ)).card) (hcard : t.card = s.card) : (t.filter (fun q => x < q)).card = (s.filter (fun r => x < r)).card := by - obtain ⟨u, hu, hRel⟩ := - exists_rel_le_of_forall_le_count hsep_centers hcount - have hu_card : u.card = t.card := by - simpa [hcard] using (Multiset.card_eq_card_of_rel hRel).symm - have hut : u = t := Multiset.eq_of_le_of_card_le hu hu_card.ge - rw [hut] at hRel + have hRel := + rel_of_forall_le_count_of_card_eq hsep_centers hcount hcard have hgt : (s.filter (fun r => x < r)).card ≤ (t.filter (fun q => x < q)).card := card_filter_gt_le_of_rel_abs_sub_lt hsep_x hRel diff --git a/RealRooted/SameDegreeCountFromAnalytic.lean b/RealRooted/SameDegreeCountFromAnalytic.lean index 855e37b1..ed865ae9 100644 --- a/RealRooted/SameDegreeCountFromAnalytic.lean +++ b/RealRooted/SameDegreeCountFromAnalytic.lean @@ -57,17 +57,12 @@ theorem exists_eps_card_roots_gt_bounds_near_simple_root ((f + C ν * g).roots.filter (fun q ↦ |q - a| < ρ)).card := fun a ha ↦ by simp_all - obtain ⟨u, hu, hrel⟩ := - Multiset.exists_rel_le_of_forall_le_count (s := p.roots) - (t := (f + C ν * g).roots) hsep_centers hcount_local have hcard : (f + C ν * g).roots.card = p.roots.card := by simpa [hν_split.natDegree_eq_card_roots.symm, hp_split.natDegree_eq_card_roots.symm] using hν_deg - have hu_card : u.card = (f + C ν * g).roots.card := by - simpa [hcard] using (Multiset.card_eq_card_of_rel hrel).symm - have hu_eq : u = (f + C ν * g).roots := - Multiset.eq_of_le_of_card_le hu hu_card.ge - rw [hu_eq] at hrel + have hrel := + Multiset.rel_of_forall_le_count_of_card_eq + hsep_centers hcount_local hcard have hsep_gt : ∀ r ∈ p.roots, x < r → ρ ≤ |r - x| := fun r hr hxr => by have hr_ne : r ≠ x := ne_of_gt hxr exact le_trans (le_of_lt hρ_lt_η) From 7ed53642235ce616ce7d1ca5f2054f61c4f72a5c Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:43:56 +0000 Subject: [PATCH 14/76] Add finite derivative shift sequences --- RealRooted.lean | 1 + RealRooted/DerivativeShiftSequence.lean | 98 +++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 RealRooted/DerivativeShiftSequence.lean diff --git a/RealRooted.lean b/RealRooted.lean index 7fa7efeb..fa7a893b 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -100,6 +100,7 @@ import RealRooted.DegreeDropReversal import RealRooted.DegreeIncreasingLocalLowerCount import RealRooted.Derivative import RealRooted.DerivativeShiftRootMatching +import RealRooted.DerivativeShiftSequence import RealRooted.DifferentialBlocks import RealRooted.ElementaryDifferential import RealRooted.EulerOperator diff --git a/RealRooted/DerivativeShiftSequence.lean b/RealRooted/DerivativeShiftSequence.lean new file mode 100644 index 00000000..850f54eb --- /dev/null +++ b/RealRooted/DerivativeShiftSequence.lean @@ -0,0 +1,98 @@ +import RealRooted.IteratedDerivativeShift + +/-! +# Finite sequences of derivative shifts + +This file extends the fixed-parameter iteration of `TDeriv` to a finite list of +possibly different positive parameters. The varying-parameter form is needed +when each derivative shift must satisfy a new local smallness bound. + +The main result is `Polynomial.hasSimpleRoots_applyTDerivList_of_natDegree_le_length`: +after at least `p.natDegree` positive shifts, a nonzero splitting polynomial has +simple roots. Its proof follows the multiplicity descent used for fixed +iterations, tracing a hypothetical multiple root backward through the list. +-/ + +namespace Polynomial + +noncomputable section + +/-- Apply the derivative shifts in `epss` from left to right. -/ +def applyTDerivList : List ℝ → ℝ[X] → ℝ[X] + | [], p => p + | eps :: epss, p => applyTDerivList epss (TDeriv eps p) + +@[simp] +theorem applyTDerivList_nil (p : ℝ[X]) : applyTDerivList [] p = p := rfl + +@[simp] +theorem applyTDerivList_cons (eps : ℝ) (epss : List ℝ) (p : ℝ[X]) : + applyTDerivList (eps :: epss) p = applyTDerivList epss (TDeriv eps p) := rfl + +/-- A finite sequence of derivative shifts preserves nonvanishing. -/ +theorem applyTDerivList_ne_zero {epss : List ℝ} {p : ℝ[X]} (hp : p ≠ 0) : + applyTDerivList epss p ≠ 0 := by + induction epss generalizing p with + | nil => simpa + | cons eps epss ih => + simpa using ih (TDeriv_ne_zero hp) + +/-- Positive derivative shifts preserve splitting throughout a finite sequence. -/ +theorem Splits.applyTDerivList {epss : List ℝ} {p : ℝ[X]} + (hp : p.Splits) (hpos : ∀ eps ∈ epss, 0 < eps) : + (applyTDerivList epss p).Splits := by + induction epss generalizing p with + | nil => simpa + | cons eps epss ih => + have heps : 0 < eps := hpos eps (by simp) + have htail : ∀ eta ∈ epss, 0 < eta := by + intro eta heta + exact hpos eta (by simp [heta]) + exact ih (splits_tderiv heps hp) htail + +/-- A multiple final root gains one unit of multiplicity at every backward step. -/ +theorem rootMultiplicity_applyTDerivList_eq_add_length_of_ge_two + {epss : List ℝ} {p : ℝ[X]} {a : ℝ} + (hpos : ∀ eps ∈ epss, 0 < eps) (hp : p.Splits) + (hm : 2 ≤ (applyTDerivList epss p).rootMultiplicity a) : + p.rootMultiplicity a = + (applyTDerivList epss p).rootMultiplicity a + epss.length := by + induction epss generalizing p with + | nil => simp + | cons eps epss ih => + have heps : 0 < eps := hpos eps (by simp) + have htail : ∀ eta ∈ epss, 0 < eta := by + intro eta heta + exact hpos eta (by simp [heta]) + have hTsplit : (TDeriv eps p).Splits := splits_tderiv heps hp + have htail_eq := ih htail hTsplit hm + have hTmult : 2 ≤ (TDeriv eps p).rootMultiplicity a := by + lia + have hstep := rootMultiplicity_eq_succ_of_TDeriv_ge_two heps hp hTmult + simp only [applyTDerivList_cons, List.length_cons] + lia + +/-- Enough positive derivative shifts make every root simple. -/ +theorem hasSimpleRoots_applyTDerivList_of_natDegree_le_length + {epss : List ℝ} {p : ℝ[X]} (hpos : ∀ eps ∈ epss, 0 < eps) + (hp : p ≠ 0) (hsplit : p.Splits) (hdeg : p.natDegree ≤ epss.length) : + HasSimpleRoots (applyTDerivList epss p) := by + intro a ha + have hfinal_ne : applyTDerivList epss p ≠ 0 := applyTDerivList_ne_zero hp + have hmult_pos : 0 < (applyTDerivList epss p).rootMultiplicity a := + (rootMultiplicity_pos hfinal_ne).mpr ha + by_contra hne + have hmult : 2 ≤ (applyTDerivList epss p).rootMultiplicity a := by + lia + have hback := + rootMultiplicity_applyTDerivList_eq_add_length_of_ge_two hpos hsplit hmult + have hmult_le : p.rootMultiplicity a ≤ p.natDegree := by + calc + p.rootMultiplicity a = p.roots.count a := (count_roots p).symm + _ ≤ p.roots.card := p.roots.count_le_card a + _ ≤ p.natDegree := card_roots' p + lia + +end + +end Polynomial From f4be700413fe17a18ecf07898f30f71b9c04e94e Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:50:54 +0000 Subject: [PATCH 15/76] Regularize Liu endpoints by bounded shifts --- RealRooted.lean | 1 + RealRooted/DerivativeShiftSequence.lean | 18 +++ ...DerivativeShiftSequenceRegularization.lean | 121 ++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean diff --git a/RealRooted.lean b/RealRooted.lean index fa7a893b..a3364570 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -148,6 +148,7 @@ import RealRooted.LiuOppositeSigns.CommonInterleaverConsequences import RealRooted.LiuOppositeSigns.Corollary22 import RealRooted.LiuOppositeSigns.DeletionBranches import RealRooted.LiuOppositeSigns.DerivativeShiftRegularization +import RealRooted.LiuOppositeSigns.DerivativeShiftSequenceRegularization import RealRooted.LiuOppositeSigns.FactorReturnAssembly import RealRooted.LiuOppositeSigns.FactorReturnLeft import RealRooted.LiuOppositeSigns.FactorReturnStatements diff --git a/RealRooted/DerivativeShiftSequence.lean b/RealRooted/DerivativeShiftSequence.lean index 850f54eb..56f1c8dd 100644 --- a/RealRooted/DerivativeShiftSequence.lean +++ b/RealRooted/DerivativeShiftSequence.lean @@ -50,6 +50,24 @@ theorem Splits.applyTDerivList {epss : List ℝ} {p : ℝ[X]} exact hpos eta (by simp [heta]) exact ih (splits_tderiv heps hp) htail +/-- A finite sequence of derivative shifts preserves natural degree. -/ +@[simp] +theorem natDegree_applyTDerivList (epss : List ℝ) (p : ℝ[X]) : + (applyTDerivList epss p).natDegree = p.natDegree := by + induction epss generalizing p with + | nil => simp + | cons eps epss ih => + rw [applyTDerivList_cons, ih, natDegree_TDeriv] + +/-- A finite sequence of derivative shifts preserves the leading coefficient. -/ +@[simp] +theorem leadingCoeff_applyTDerivList (epss : List ℝ) (p : ℝ[X]) : + (applyTDerivList epss p).leadingCoeff = p.leadingCoeff := by + induction epss generalizing p with + | nil => simp + | cons eps epss ih => + rw [applyTDerivList_cons, ih, leadingCoeff_TDeriv] + /-- A multiple final root gains one unit of multiplicity at every backward step. -/ theorem rootMultiplicity_applyTDerivList_eq_add_length_of_ge_two {epss : List ℝ} {p : ℝ[X]} {a : ℝ} diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean new file mode 100644 index 00000000..5f939d9f --- /dev/null +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean @@ -0,0 +1,121 @@ +import RealRooted.Compatibility.Basic +import RealRooted.DerivativeShiftSequence +import RealRooted.LiuOppositeSigns.DerivativeShiftRegularization + +/-! +# Finite derivative-shift regularization for Liu's opposite-sign theorem + +Liu's source proof reduces to endpoints with no common zeros and then treats +those endpoints as if all zeros were simple. That implication is false for +individual endpoints. The theorems below provide the required rigorous +replacement: repeatedly apply a common positive derivative shift, choosing a +new sufficiently small parameter at each stage. + +Every parameter can additionally be bounded by an arbitrary `κ > 0`. This +quantitative bound is retained for the later limiting argument rather than +merely producing one unrelated pair with simple roots. +-/ + +namespace RealRooted +namespace LiuOppositeSigns + +open Polynomial + +noncomputable section + +/-- Choose a prescribed number of bounded positive common derivative shifts. + +At every stage, the next shift is smaller than both `κ` and the local radius +from `NoCommonRoots.exists_delta_TDeriv`. Consequently compatibility and the +absence of common roots survive the entire sequence. +-/ +theorem NoCommonRoots.exists_applyTDerivList + {f g : ℝ[X]} (hcomp : Compatible f g) (hno : NoCommonRoots f g) + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) (hf : f.Splits) (hg : g.Splits) + {κ : ℝ} (hκ : 0 < κ) (n : ℕ) : + ∃ epss : List ℝ, + epss.length = n ∧ + (∀ eps ∈ epss, 0 < eps ∧ eps < κ) ∧ + Compatible (applyTDerivList epss f) (applyTDerivList epss g) ∧ + NoCommonRoots (applyTDerivList epss f) (applyTDerivList epss g) := by + induction n generalizing f g with + | zero => + exact ⟨[], rfl, by simp, by simpa using hcomp, by simpa using hno⟩ + | succ n ih => + obtain ⟨δ, hδ, hpreserve⟩ := + hno.exists_delta_TDeriv hf_ne hg_ne hf hg + let eps := min δ κ / 2 + have hmin : 0 < min δ κ := lt_min hδ hκ + have heps : 0 < eps := by + dsimp [eps] + linarith + have heps_delta : eps < δ := by + have hle : min δ κ ≤ δ := min_le_left δ κ + dsimp [eps] + linarith + have heps_kappa : eps < κ := by + have hle : min δ κ ≤ κ := min_le_right δ κ + dsimp [eps] + linarith + have hcomp_shift : Compatible (TDeriv eps f) (TDeriv eps g) := by + simpa using hcomp.iterateTDeriv heps 1 + have hno_shift : NoCommonRoots (TDeriv eps f) (TDeriv eps g) := + hpreserve heps heps_delta + obtain ⟨epss, hlength, hbounds, hcomp_final, hno_final⟩ := + ih hcomp_shift hno_shift (TDeriv_ne_zero hf_ne) (TDeriv_ne_zero hg_ne) + (splits_tderiv heps hf) (splits_tderiv heps hg) + refine ⟨eps :: epss, by simp [hlength], ?_, ?_, ?_⟩ + · intro eta heta + rcases List.mem_cons.mp heta with rfl | heta + · exact ⟨heps, heps_kappa⟩ + · exact hbounds eta heta + · simpa using hcomp_final + · simpa using hno_final + +/-- Bounded common shifts regularize both endpoints while preserving all Liu hypotheses. -/ +theorem NoCommonRoots.exists_simple_applyTDerivList + {f g : ℝ[X]} (hcomp : Compatible f g) (hno : NoCommonRoots f g) + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) (hf : f.Splits) (hg : g.Splits) + (hopp : f.leadingCoeff * g.leadingCoeff < 0) {κ : ℝ} (hκ : 0 < κ) : + ∃ epss : List ℝ, + epss.length = max f.natDegree g.natDegree ∧ + (∀ eps ∈ epss, 0 < eps ∧ eps < κ) ∧ + Compatible (applyTDerivList epss f) (applyTDerivList epss g) ∧ + NoCommonRoots (applyTDerivList epss f) (applyTDerivList epss g) ∧ + applyTDerivList epss f ≠ 0 ∧ + applyTDerivList epss g ≠ 0 ∧ + (applyTDerivList epss f).Splits ∧ + (applyTDerivList epss g).Splits ∧ + (applyTDerivList epss f).natDegree = f.natDegree ∧ + (applyTDerivList epss g).natDegree = g.natDegree ∧ + (applyTDerivList epss f).leadingCoeff = f.leadingCoeff ∧ + (applyTDerivList epss g).leadingCoeff = g.leadingCoeff ∧ + (applyTDerivList epss f).leadingCoeff * + (applyTDerivList epss g).leadingCoeff < 0 ∧ + HasSimpleRoots (applyTDerivList epss f) ∧ + HasSimpleRoots (applyTDerivList epss g) := by + obtain ⟨epss, hlength, hbounds, hcomp_final, hno_final⟩ := + hno.exists_applyTDerivList hcomp hf_ne hg_ne hf hg hκ + (max f.natDegree g.natDegree) + have hpos : ∀ eps ∈ epss, 0 < eps := by + intro eps heps + exact (hbounds eps heps).1 + have hf_simple : HasSimpleRoots (applyTDerivList epss f) := by + apply hasSimpleRoots_applyTDerivList_of_natDegree_le_length hpos hf_ne hf + rw [hlength] + exact Nat.le_max_left _ _ + have hg_simple : HasSimpleRoots (applyTDerivList epss g) := by + apply hasSimpleRoots_applyTDerivList_of_natDegree_le_length hpos hg_ne hg + rw [hlength] + exact Nat.le_max_right _ _ + refine ⟨epss, hlength, hbounds, hcomp_final, hno_final, + applyTDerivList_ne_zero hf_ne, applyTDerivList_ne_zero hg_ne, + hf.applyTDerivList hpos, hg.applyTDerivList hpos, by simp, by simp, + by simp, by simp, ?_, hf_simple, hg_simple⟩ + simpa using hopp + +end + + +end LiuOppositeSigns +end RealRooted From 439c0368aea04f0fadd172e1b1fb86253dee1ae7 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 15:53:28 +0000 Subject: [PATCH 16/76] Golf derivative shift root matching --- .../DerivativeShiftRegularization.lean | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean index 3ed91ebd..143c7361 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean @@ -32,16 +32,14 @@ theorem NoCommonRoots.exists_delta_TDeriv exists_delta_roots_rel_TDeriv hg hη_pos refine ⟨min δf δg, lt_min hδf_pos hδg_pos, ?_⟩ intro eps heps_pos hepsδ x hfx hgx - have hfx_mem : x ∈ (TDeriv eps f).roots := - (Polynomial.mem_roots (TDeriv_ne_zero hf_ne)).mpr hfx - have hgx_mem : x ∈ (TDeriv eps g).roots := - (Polynomial.mem_roots (TDeriv_ne_zero hg_ne)).mpr hgx obtain ⟨a, ha_mem, hax⟩ := Multiset.Rel.exists_left_of_mem_right - (hfrel heps_pos (hepsδ.trans_le (min_le_left _ _))) hfx_mem + (hfrel heps_pos (hepsδ.trans_le (min_le_left _ _))) + ((Polynomial.mem_roots (TDeriv_ne_zero hf_ne)).mpr hfx) obtain ⟨b, hb_mem, hbx⟩ := Multiset.Rel.exists_left_of_mem_right - (hgrel heps_pos (hepsδ.trans_le (min_le_right _ _))) hgx_mem + (hgrel heps_pos (hepsδ.trans_le (min_le_right _ _))) + ((Polynomial.mem_roots (TDeriv_ne_zero hg_ne)).mpr hgx) have hab_ne : a ≠ b := by rintro rfl exact (hno a ((Polynomial.mem_roots hf_ne).mp ha_mem)) From fe7412542ef1761dbada7b64f6b4f9a8b7e70ac0 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:07:05 +0000 Subject: [PATCH 17/76] Track roots through bounded Liu regularization --- RealRooted.lean | 1 + ...DerivativeShiftSequenceRegularization.lean | 93 +++++++++++++++++++ RealRooted/Mathlib/Data/Multiset/Rel.lean | 44 +++++++++ 3 files changed, 138 insertions(+) create mode 100644 RealRooted/Mathlib/Data/Multiset/Rel.lean diff --git a/RealRooted.lean b/RealRooted.lean index a3364570..dbfa993b 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -204,6 +204,7 @@ import RealRooted.Mathlib.Analysis.SpecialFunctions.Trigonometric.Basic import RealRooted.Mathlib.Combinatorics.Enumerative.OrderedSubsetPairs import RealRooted.Mathlib.Data.List.Interleave import RealRooted.Mathlib.Data.List.Zip +import RealRooted.Mathlib.Data.Multiset.Rel import RealRooted.Mathlib.Data.Nat.Cast.Basic import RealRooted.Mathlib.Data.Nat.Choose.Cast import RealRooted.Mathlib.LinearAlgebra.Matrix.Determinant.Basic diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean index 5f939d9f..ea0b79bc 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean @@ -1,6 +1,7 @@ import RealRooted.Compatibility.Basic import RealRooted.DerivativeShiftSequence import RealRooted.LiuOppositeSigns.DerivativeShiftRegularization +import RealRooted.Mathlib.Data.Multiset.Rel /-! # Finite derivative-shift regularization for Liu's opposite-sign theorem @@ -72,6 +73,98 @@ theorem NoCommonRoots.exists_applyTDerivList · simpa using hcomp_final · simpa using hno_final +/-- Choose bounded common shifts whose final roots stay uniformly close to the originals. -/ +theorem NoCommonRoots.exists_applyTDerivList_roots_rel + {f g : ℝ[X]} (hcomp : Compatible f g) (hno : NoCommonRoots f g) + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) (hf : f.Splits) (hg : g.Splits) + {κ ρ : ℝ} (hκ : 0 < κ) (hρ : 0 < ρ) (n : ℕ) : + ∃ epss : List ℝ, + epss.length = n ∧ + (∀ eps ∈ epss, 0 < eps ∧ eps < κ) ∧ + Compatible (applyTDerivList epss f) (applyTDerivList epss g) ∧ + NoCommonRoots (applyTDerivList epss f) (applyTDerivList epss g) ∧ + Multiset.Rel (fun r q ↦ |q - r| < ρ) + f.roots (applyTDerivList epss f).roots ∧ + Multiset.Rel (fun r q ↦ |q - r| < ρ) + g.roots (applyTDerivList epss g).roots := by + induction n generalizing f g ρ with + | zero => + refine ⟨[], rfl, by simp, by simpa using hcomp, by simpa using hno, ?_, ?_⟩ + · apply Multiset.Rel.diag + intro r hr + simpa using hρ + · apply Multiset.Rel.diag + intro r hr + simpa using hρ + | succ n ih => + have hhalf : 0 < ρ / 2 := by linarith + obtain ⟨δno, hδno, hpreserve⟩ := + hno.exists_delta_TDeriv hf_ne hg_ne hf hg + obtain ⟨δf, hδf, hfclose⟩ := + exists_delta_roots_rel_TDeriv hf hhalf + obtain ⟨δg, hδg, hgclose⟩ := + exists_delta_roots_rel_TDeriv hg hhalf + let δ := min δno (min δf (min δg κ)) + have hδ : 0 < δ := lt_min hδno (lt_min hδf (lt_min hδg hκ)) + have hδ_no : δ ≤ δno := min_le_left _ _ + have hδ_tail : δ ≤ min δf (min δg κ) := min_le_right _ _ + have hδ_f : δ ≤ δf := hδ_tail.trans (min_le_left _ _) + have hδ_g : δ ≤ δg := + hδ_tail.trans ((min_le_right _ _).trans (min_le_left _ _)) + have hδ_kappa : δ ≤ κ := + hδ_tail.trans ((min_le_right _ _).trans (min_le_right _ _)) + let eps := δ / 2 + have heps : 0 < eps := by + dsimp [eps] + linarith + have heps_no : eps < δno := by + dsimp [eps] + linarith + have heps_f : eps < δf := by + dsimp [eps] + linarith + have heps_g : eps < δg := by + dsimp [eps] + linarith + have heps_kappa : eps < κ := by + dsimp [eps] + linarith + have hcomp_shift : Compatible (TDeriv eps f) (TDeriv eps g) := by + simpa using hcomp.iterateTDeriv heps 1 + have hno_shift : NoCommonRoots (TDeriv eps f) (TDeriv eps g) := + hpreserve heps heps_no + have hf_step := hfclose heps heps_f + have hg_step := hgclose heps heps_g + obtain ⟨epss, hlength, hbounds, hcomp_final, hno_final, + hf_tail, hg_tail⟩ := + ih hcomp_shift hno_shift (TDeriv_ne_zero hf_ne) (TDeriv_ne_zero hg_ne) + (splits_tderiv heps hf) (splits_tderiv heps hg) hhalf + have hf_final := Multiset.Rel.comp + (fun a b c hab hbc => by + calc + |c - a| = |(c - b) + (b - a)| := by ring + _ ≤ |c - b| + |b - a| := abs_add _ _ + _ < ρ / 2 + ρ / 2 := add_lt_add hbc hab + _ = ρ := by ring) + hf_step hf_tail + have hg_final := Multiset.Rel.comp + (fun a b c hab hbc => by + calc + |c - a| = |(c - b) + (b - a)| := by ring + _ ≤ |c - b| + |b - a| := abs_add _ _ + _ < ρ / 2 + ρ / 2 := add_lt_add hbc hab + _ = ρ := by ring) + hg_step hg_tail + refine ⟨eps :: epss, by simp [hlength], ?_, ?_, ?_, ?_, ?_⟩ + · intro eta heta + rcases List.mem_cons.mp heta with rfl | heta + · exact ⟨heps, heps_kappa⟩ + · exact hbounds eta heta + · simpa using hcomp_final + · simpa using hno_final + · simpa using hf_final + · simpa using hg_final + /-- Bounded common shifts regularize both endpoints while preserving all Liu hypotheses. -/ theorem NoCommonRoots.exists_simple_applyTDerivList {f g : ℝ[X]} (hcomp : Compatible f g) (hno : NoCommonRoots f g) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean new file mode 100644 index 00000000..aeca7755 --- /dev/null +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -0,0 +1,44 @@ +import Mathlib.Data.Multiset.ZeroCons + +/-! +# Additional lemmas for multiset relations + +This file provides reusable constructors for diagonal multiset relations and +heterogeneous composition. It is intended for upstreaming to +`Mathlib.Data.Multiset.ZeroCons`. +-/ + +namespace Multiset + +/-- Lift a pointwise reflexivity proof to a multiset relation. -/ +theorem Rel.diag {α : Type*} {R : α → α → Prop} {s : Multiset α} + (h : ∀ a ∈ s, R a a) : Multiset.Rel R s s := by + induction s using Multiset.induction_on with + | empty => exact Rel.zero + | cons a s ih => + apply Rel.cons + · exact h a (by simp) + · apply ih + intro b hb + exact h b (by simp [hb]) + +/-- Compose multiset relations whose element relations may differ. -/ +theorem Rel.comp {α β γ : Type*} + {R : α → β → Prop} {S : β → γ → Prop} {T : α → γ → Prop} + {xs : Multiset α} {ys : Multiset β} {zs : Multiset γ} + (hcomp : ∀ a b c, R a b → S b c → T a c) + (h₁ : Multiset.Rel R xs ys) (h₂ : Multiset.Rel S ys zs) : + Multiset.Rel T xs zs := by + induction ys using Multiset.induction_on generalizing xs zs with + | empty => + have hxs : xs = 0 := Multiset.rel_zero_right.mp h₁ + have hzs : zs = 0 := Multiset.rel_zero_left.mp h₂ + subst xs + subst zs + exact Rel.zero + | cons b ys ih => + obtain ⟨a, xs, hab, h₁, rfl⟩ := Multiset.rel_cons_right.mp h₁ + obtain ⟨c, zs, hbc, h₂, rfl⟩ := Multiset.rel_cons_left.mp h₂ + exact Rel.cons (hcomp a b c hab hbc) (ih h₁ h₂) + +end Multiset From 8ae49d80a7a63b4aeecc2f07a604728134c47d80 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:16:32 +0000 Subject: [PATCH 18/76] Stabilize Liu root counts under matching --- RealRooted.lean | 1 + .../RootCountRelStability.lean | 57 +++++++++++++++++++ RealRooted/Mathlib/Data/Multiset/Rel.lean | 33 +++++++++++ 3 files changed, 91 insertions(+) create mode 100644 RealRooted/LiuOppositeSigns/RootCountRelStability.lean diff --git a/RealRooted.lean b/RealRooted.lean index dbfa993b..73ef5e38 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -167,6 +167,7 @@ import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderUpper import RealRooted.LiuOppositeSigns.ForwardLowDegree import RealRooted.LiuOppositeSigns.NonnegCoeffs import RealRooted.LiuOppositeSigns.PositiveSplitPair +import RealRooted.LiuOppositeSigns.RootCountRelStability import RealRooted.LiuOppositeSigns.Theorem21Assembly import RealRooted.LiuOppositeSigns.Theorem21Statements import RealRooted.LiuOppositeSigns.XSub.CubicCubic diff --git a/RealRooted/LiuOppositeSigns/RootCountRelStability.lean b/RealRooted/LiuOppositeSigns/RootCountRelStability.lean new file mode 100644 index 00000000..ab96018f --- /dev/null +++ b/RealRooted/LiuOppositeSigns/RootCountRelStability.lean @@ -0,0 +1,57 @@ +import RealRooted.LiuOppositeSigns +import RealRooted.Mathlib.Data.Multiset.Rel +import RealRooted.RootMultiplicityMatching + +/-! +# Root-count stability under close multiset matchings + +These lemmas provide the local closedness input for the limiting step in Liu's +opposite-leading-sign theorem. They compare root counts only at thresholds +separated from the original roots and compare the selected largest roots. +-/ + +namespace RealRooted + +open Polynomial + +/-- A close root matching preserves the root count at a separated threshold. -/ +theorem rootCountAtOrAbove_eq_of_roots_rel_abs_sub_lt + {p q : ℝ[X]} {x δ : ℝ} + (hsep : ∀ r ∈ p.roots, δ ≤ |r - x|) + (hmatch : Multiset.Rel (fun r s ↦ |s - r| < δ) p.roots q.roots) : + rootCountAtOrAbove p x = rootCountAtOrAbove q x := by + unfold rootCountAtOrAbove + apply Multiset.Rel.card_filter_eq hmatch (fun r => x ≤ r) (fun s => x ≤ s) + intro r hr s hs hrs + have habs := abs_lt.mp hrs + constructor + · intro hxr + have hrx : δ ≤ r - x := by + simpa [abs_of_nonneg (sub_nonneg.mpr hxr)] using hsep r hr + linarith [habs.1] + · intro hxs + by_contra hxr + have hrx : δ ≤ x - r := by + have hrx' : r ≤ x := le_of_lt (lt_of_not_ge hxr) + simpa [abs_of_nonpos (sub_nonpos.mpr hrx')] using hsep r hr + linarith [habs.2] + +/-- Largest roots of closely matched nonzero polynomials are close. -/ +theorem IsLargestRoot.abs_sub_lt_of_roots_rel + {p q : ℝ[X]} {r s δ : ℝ} (hp_ne : p ≠ 0) (hq_ne : q ≠ 0) + (hr : IsLargestRoot p r) (hs : IsLargestRoot q s) + (hmatch : Multiset.Rel (fun a b ↦ |b - a| < δ) p.roots q.roots) : + |s - r| < δ := by + have hr_mem : r ∈ p.roots := (Polynomial.mem_roots hp_ne).mpr hr.isRoot + have hs_mem : s ∈ q.roots := (Polynomial.mem_roots hq_ne).mpr hs.isRoot + obtain ⟨b, hb_mem, hrb⟩ := hmatch.exists_right_of_mem_left hr_mem + obtain ⟨a, ha_mem, has⟩ := + Multiset.Rel.exists_left_of_mem_right hmatch hs_mem + have hb_le : b ≤ s := hs.roots_le b hb_mem + have ha_le : a ≤ r := hr.roots_le a ha_mem + rw [abs_lt] + constructor + · linarith [(abs_lt.mp hrb).1] + · linarith [(abs_lt.mp has).2] + +end RealRooted diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index aeca7755..3a00f9d2 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -41,4 +41,37 @@ theorem Rel.comp {α β γ : Type*} obtain ⟨c, zs, hbc, h₂, rfl⟩ := Multiset.rel_cons_left.mp h₂ exact Rel.cons (hcomp a b c hab hbc) (ih h₁ h₂) +/-- Every element on the left of a multiset relation has a related element on the right. -/ +theorem Rel.exists_right_of_mem_left {α β : Type*} {R : α → β → Prop} + {s : Multiset α} {t : Multiset β} (hrel : Multiset.Rel R s t) + {a : α} (ha : a ∈ s) : ∃ b ∈ t, R a b := by + induction s using Multiset.induction_on generalizing t with + | empty => simp at ha + | cons a' s ih => + obtain ⟨b, t, hab, hrel, rfl⟩ := Multiset.rel_cons_left.mp hrel + rw [Multiset.mem_cons] at ha + rcases ha with rfl | ha + · exact ⟨b, by simp, hab⟩ + · obtain ⟨c, hc, hac⟩ := ih hrel ha + exact ⟨c, by simp [hc], hac⟩ + +/-- Related multisets have equally many elements satisfying corresponding predicates. -/ +theorem Rel.card_filter_eq {α β : Type*} {R : α → β → Prop} + {s : Multiset α} {t : Multiset β} (hrel : Multiset.Rel R s t) + (P : α → Prop) (Q : β → Prop) [DecidablePred P] [DecidablePred Q] + (hpred : ∀ a ∈ s, ∀ b ∈ t, R a b → (P a ↔ Q b)) : + (s.filter P).card = (t.filter Q).card := by + induction hrel with + | zero => simp + | @cons a b s t hab hrel ih => + have hpq : P a ↔ Q b := hpred a (by simp) b (by simp) hab + have htail : ∀ c ∈ s, ∀ d ∈ t, R c d → (P c ↔ Q d) := by + intro c hc d hd hcd + exact hpred c (by simp [hc]) d (by simp [hd]) hcd + by_cases ha : P a + · have hb : Q b := hpq.mp ha + simp [ha, hb, ih htail] + · have hb : ¬Q b := fun hb => ha (hpq.mpr hb) + simp [ha, hb, ih htail] + end Multiset From 2fa6bdecfbedeb75ef7bf734e95891be0c287fca Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:18:32 +0000 Subject: [PATCH 19/76] Golf multiset relation diagonal --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 3a00f9d2..099b8042 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -16,11 +16,8 @@ theorem Rel.diag {α : Type*} {R : α → α → Prop} {s : Multiset α} induction s using Multiset.induction_on with | empty => exact Rel.zero | cons a s ih => - apply Rel.cons - · exact h a (by simp) - · apply ih - intro b hb - exact h b (by simp [hb]) + exact Rel.cons (h a (by simp)) + (ih fun b hb => h b (by simp [hb])) /-- Compose multiset relations whose element relations may differ. -/ theorem Rel.comp {α β γ : Type*} From 3c480d759d5653ab73de322d86ca09ab74897c84 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:27:21 +0000 Subject: [PATCH 20/76] Match sorted roots through multiset relations --- RealRooted.lean | 1 + RealRooted/Mathlib/Data/Multiset/Rel.lean | 83 +++++++++++++++++++++++ RealRooted/RootMatchingSort.lean | 25 +++++++ 3 files changed, 109 insertions(+) create mode 100644 RealRooted/RootMatchingSort.lean diff --git a/RealRooted.lean b/RealRooted.lean index 73ef5e38..8ef5c967 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -206,6 +206,7 @@ import RealRooted.Mathlib.Combinatorics.Enumerative.OrderedSubsetPairs import RealRooted.Mathlib.Data.List.Interleave import RealRooted.Mathlib.Data.List.Zip import RealRooted.Mathlib.Data.Multiset.Rel +import RealRooted.RootMatchingSort import RealRooted.Mathlib.Data.Nat.Cast.Basic import RealRooted.Mathlib.Data.Nat.Choose.Cast import RealRooted.Mathlib.LinearAlgebra.Matrix.Determinant.Basic diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 099b8042..014864e7 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -1,3 +1,5 @@ +import Mathlib.Data.List.Forall2 +import Mathlib.Data.Multiset.Sort import Mathlib.Data.Multiset.ZeroCons /-! @@ -71,4 +73,85 @@ theorem Rel.card_filter_eq {α β : Type*} {R : α → β → Prop} · have hb : ¬Q b := fun hb => ha (hpq.mpr hb) simp [ha, hb, ih htail] +private theorem rel_uncross_min_cons {α : Type*} [LinearOrder α] + {R : α → α → Prop} {a b : α} {as bs : Multiset α} + (huncross : ∀ {a c b d}, a ≤ c → b ≤ d → R a d → R c b → + R a b ∧ R c d) + (ha : ∀ c ∈ as, a ≤ c) (hb : ∀ d ∈ bs, b ≤ d) + (hrel : Multiset.Rel R (a ::ₘ as) (b ::ₘ bs)) : + R a b ∧ Multiset.Rel R as bs := by + obtain ⟨d, ds, hdb, hds, heq⟩ := Multiset.rel_cons_right.mp hrel + rcases Multiset.cons_eq_cons.mp heq with + (⟨rfl, rfl⟩ | ⟨_, cs, rfl, rfl⟩) + · exact ⟨hdb, hds⟩ + · obtain ⟨c, ts, hac, hct, rfl⟩ := Multiset.rel_cons_left.mp hds + have had : a ≤ d := ha d (by simp) + have hbc : b ≤ c := hb c (by simp) + obtain ⟨hab, hdc⟩ := huncross had hbc hac hdb + exact ⟨hab, Multiset.Rel.cons hdc hct⟩ + +/-- An uncrossable multiset relation pairs the corresponding sorted elements. -/ +theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop} + (huncross : ∀ {a c b d}, a ≤ c → b ≤ d → R a d → R c b → + R a b ∧ R c d) + {s t : Multiset α} (hrel : Multiset.Rel R s t) : + List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by + revert t + apply Multiset.strongInductionOn s + intro s ih t hrel + cases hs : s.sort (· ≤ ·) with + | nil => + have hs0 : s = 0 := by + calc + s = ↑(s.sort (· ≤ ·)) := + (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm + _ = 0 := by simp [hs] + subst s + have ht0 : t = 0 := Multiset.rel_zero_left.mp hrel + subst t + simp + | cons a as => + have hsrepr : s = a ::ₘ (as : Multiset α) := by + calc + s = ↑(s.sort (· ≤ ·)) := + (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm + _ = a ::ₘ (as : Multiset α) := by rw [hs] + have hsord : (a :: as).Pairwise (· ≤ ·) := by + simpa [hs] using + (Multiset.pairwise_sort (s := s) (r := (· ≤ ·))) + have ha : ∀ c ∈ (as : Multiset α), a ≤ c := by + intro c hc + exact (List.pairwise_cons.mp hsord).1 c (by simpa using hc) + cases ht : t.sort (· ≤ ·) with + | nil => + have ht0 : t = 0 := by + calc + t = ↑(t.sort (· ≤ ·)) := + (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm + _ = 0 := by simp [ht] + subst t + have hs0 : s = 0 := Multiset.rel_zero_right.mp hrel + have : False := by simpa [hsrepr] using hs0 + exact this.elim + | cons b bs => + have htrepr : t = b ::ₘ (bs : Multiset α) := by + calc + t = ↑(t.sort (· ≤ ·)) := + (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm + _ = b ::ₘ (bs : Multiset α) := by rw [ht] + have htord : (b :: bs).Pairwise (· ≤ ·) := by + simpa [ht] using + (Multiset.pairwise_sort (s := t) (r := (· ≤ ·))) + have hb : ∀ d ∈ (bs : Multiset α), b ≤ d := by + intro d hd + exact (List.pairwise_cons.mp htord).1 d (by simpa using hd) + rw [hsrepr, htrepr] at hrel + obtain ⟨hab, htail⟩ := rel_uncross_min_cons huncross ha hb hrel + have hsmall : (as : Multiset α) < s := by + rw [hsrepr] + exact Multiset.lt_cons_self _ _ + have ih_tail := ih (as : Multiset α) hsmall (bs : Multiset α) htail + rw [hsrepr, htrepr, Multiset.sort_cons ha, Multiset.sort_cons hb] + exact List.Forall₂.cons hab ih_tail + end Multiset diff --git a/RealRooted/RootMatchingSort.lean b/RealRooted/RootMatchingSort.lean new file mode 100644 index 00000000..1fbc2f87 --- /dev/null +++ b/RealRooted/RootMatchingSort.lean @@ -0,0 +1,25 @@ +import RealRooted.Mathlib.Data.Multiset.Rel + +/-! +# Sorted root matching + +An arbitrary close perfect matching of two real multisets can be uncrossed into +a close pointwise matching of their ascending sorts. This is the finite +ordered-root input for limiting interlacing and root-count inequalities. +-/ + +namespace RealRooted + +/-- A close multiset matching pairs corresponding ascending order statistics. -/ +theorem forall₂_sort_of_rel_abs_sub_lt + {s t : Multiset ℝ} {ρ : ℝ} + (hrel : Multiset.Rel (fun x y : ℝ => |y - x| < ρ) s t) : + List.Forall₂ (fun x y : ℝ => |y - x| < ρ) + (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by + apply Multiset.Rel.forall₂_sort + · intro a c b d hac hbd had hcb + rw [abs_lt] at had hcb ⊢ + constructor <;> constructor <;> linarith + · exact hrel + +end RealRooted From 7c9107067f4ca57b3413568dfc14fa07c585646e Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:36:03 +0000 Subject: [PATCH 21/76] Match roots after deleting largest factors --- RealRooted/RootMatchingSort.lean | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/RealRooted/RootMatchingSort.lean b/RealRooted/RootMatchingSort.lean index 1fbc2f87..b532dc46 100644 --- a/RealRooted/RootMatchingSort.lean +++ b/RealRooted/RootMatchingSort.lean @@ -1,3 +1,4 @@ +import RealRooted.LiuOppositeSigns import RealRooted.Mathlib.Data.Multiset.Rel /-! @@ -22,4 +23,49 @@ theorem forall₂_sort_of_rel_abs_sub_lt constructor <;> constructor <;> linarith · exact hrel +/-- A close multiset matching pairs corresponding descending order statistics. -/ +theorem forall₂_sort_ge_of_rel_abs_sub_lt + {s t : Multiset ℝ} {ρ : ℝ} + (hrel : Multiset.Rel (fun x y : ℝ => |y - x| < ρ) s t) : + List.Forall₂ (fun x y : ℝ => |y - x| < ρ) + (s.sort (· ≥ ·)) (t.sort (· ≥ ·)) := by + change List.Forall₂ + (fun x y : OrderDual ℝ => + |OrderDual.ofDual y - OrderDual.ofDual x| < ρ) + ((show Multiset (OrderDual ℝ) from s).sort (· ≤ ·)) + ((show Multiset (OrderDual ℝ) from t).sort (· ≤ ·)) + apply Multiset.Rel.forall₂_sort + · intro a c b d hac hbd had hcb + change OrderDual.ofDual c ≤ OrderDual.ofDual a at hac + change OrderDual.ofDual d ≤ OrderDual.ofDual b at hbd + rw [abs_lt] at had hcb ⊢ + constructor <;> constructor <;> linarith + · exact hrel + +/-- A largest root is the head of the descending root list. -/ +theorem IsLargestRoot.roots_sort_ge_eq_cons + {p : ℝ[X]} {r : ℝ} (hp_ne : p ≠ 0) (h : IsLargestRoot p r) : + p.roots.sort (· ≥ ·) = + r :: (deleteRootFactor p r).roots.sort (· ≥ ·) := by + rw [h.roots_eq_singleton_add_roots_deleteRootFactor hp_ne, + Multiset.singleton_add] + apply Multiset.sort_cons + intro x hx + exact h.root_deleteRootFactor_le hp_ne + ((Polynomial.mem_roots (h.deleteRootFactor_ne_zero hp_ne)).mp hx) + +/-- Deleting matched largest roots preserves the matching of descending roots. -/ +theorem forall₂_sort_ge_deleteRootFactor_of_roots_rel + {p q : ℝ[X]} {r s ρ : ℝ} (hp_ne : p ≠ 0) (hq_ne : q ≠ 0) + (hr : IsLargestRoot p r) (hs : IsLargestRoot q s) + (hrel : Multiset.Rel + (fun x y : ℝ => |y - x| < ρ) p.roots q.roots) : + List.Forall₂ (fun x y : ℝ => |y - x| < ρ) + ((deleteRootFactor p r).roots.sort (· ≥ ·)) + ((deleteRootFactor q s).roots.sort (· ≥ ·)) := by + have hsort := forall₂_sort_ge_of_rel_abs_sub_lt hrel + rw [hr.roots_sort_ge_eq_cons hp_ne, + hs.roots_sort_ge_eq_cons hq_ne] at hsort + exact (List.forall₂_cons.mp hsort).2 + end RealRooted From 4305d9a999af694fcc3322ca1a571d858dc76211 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:37:37 +0000 Subject: [PATCH 22/76] Golf sorted root matching wrapper --- RealRooted/RootMatchingSort.lean | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/RealRooted/RootMatchingSort.lean b/RealRooted/RootMatchingSort.lean index b532dc46..5abcb73e 100644 --- a/RealRooted/RootMatchingSort.lean +++ b/RealRooted/RootMatchingSort.lean @@ -17,11 +17,10 @@ theorem forall₂_sort_of_rel_abs_sub_lt (hrel : Multiset.Rel (fun x y : ℝ => |y - x| < ρ) s t) : List.Forall₂ (fun x y : ℝ => |y - x| < ρ) (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by - apply Multiset.Rel.forall₂_sort - · intro a c b d hac hbd had hcb - rw [abs_lt] at had hcb ⊢ - constructor <;> constructor <;> linarith - · exact hrel + refine Multiset.Rel.forall₂_sort ?_ hrel + intro a c b d hac hbd had hcb + rw [abs_lt] at had hcb ⊢ + constructor <;> constructor <;> linarith /-- A close multiset matching pairs corresponding descending order statistics. -/ theorem forall₂_sort_ge_of_rel_abs_sub_lt From 3a1615a12ada3b95613cb60910a665202b550a52 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:42:43 +0000 Subject: [PATCH 23/76] Close ordered inequalities under root matching --- RealRooted/RootMatchingSort.lean | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/RealRooted/RootMatchingSort.lean b/RealRooted/RootMatchingSort.lean index 5abcb73e..7e2ccad0 100644 --- a/RealRooted/RootMatchingSort.lean +++ b/RealRooted/RootMatchingSort.lean @@ -67,4 +67,39 @@ theorem forall₂_sort_ge_deleteRootFactor_of_roots_rel hs.roots_sort_ge_eq_cons hq_ne] at hsort exact (List.forall₂_cons.mp hsort).2 +/-- A weak scalar inequality is closed under arbitrarily close approximations. -/ +theorem le_of_forall_pos_exists_close_le {x y : ℝ} + (h : ∀ ρ : ℝ, 0 < ρ → + ∃ x' y' : ℝ, |x' - x| < ρ ∧ |y' - y| < ρ ∧ x' ≤ y') : + x ≤ y := by + apply le_of_forall_pos_le_add + intro ε hε + obtain ⟨x', y', hx, hy, hxy⟩ := h (ε / 2) (half_pos hε) + rw [abs_lt] at hx hy + linarith + +/-- Pointwise list inequalities are closed under arbitrarily close approximations. -/ +theorem forall₂_le_of_forall_pos_exists_close + {xs ys : List ℝ} + (h : ∀ ρ : ℝ, 0 < ρ → + ∃ xs' ys' : List ℝ, + List.Forall₂ (fun x x' => |x' - x| < ρ) xs xs' ∧ + List.Forall₂ (fun y y' => |y' - y| < ρ) ys ys' ∧ + List.Forall₂ (· ≤ ·) xs' ys') : + List.Forall₂ (· ≤ ·) xs ys := by + have hlen : xs.length = ys.length := by + obtain ⟨xs', ys', hxs, hys, hxy⟩ := h 1 zero_lt_one + exact hxs.length_eq.trans (hxy.length_eq.trans hys.length_eq.symm) + apply List.forall₂_of_length_eq_of_get hlen + intro i hix hiy + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨xs', ys', hxs, hys, hxy⟩ := h ρ hρ + have hix' : i < xs'.length := by + simpa only [hxs.length_eq] using hix + have hiy' : i < ys'.length := by + simpa only [hys.length_eq] using hiy + exact ⟨xs'.get ⟨i, hix'⟩, ys'.get ⟨i, hiy'⟩, + hxs.get hix hix', hys.get hiy hiy', hxy.get hix' hiy'⟩ + end RealRooted From 9bfa6fb74262de757f12e66a8f8d44c370cfe2c0 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:48:23 +0000 Subject: [PATCH 24/76] Close same-degree Liu root counts --- RealRooted.lean | 1 + .../LiuOppositeSigns/RootCountClosure.lean | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 RealRooted/LiuOppositeSigns/RootCountClosure.lean diff --git a/RealRooted.lean b/RealRooted.lean index 8ef5c967..ebac2d9b 100644 --- a/RealRooted.lean +++ b/RealRooted.lean @@ -168,6 +168,7 @@ import RealRooted.LiuOppositeSigns.ForwardLowDegree import RealRooted.LiuOppositeSigns.NonnegCoeffs import RealRooted.LiuOppositeSigns.PositiveSplitPair import RealRooted.LiuOppositeSigns.RootCountRelStability +import RealRooted.LiuOppositeSigns.RootCountClosure import RealRooted.LiuOppositeSigns.Theorem21Assembly import RealRooted.LiuOppositeSigns.Theorem21Statements import RealRooted.LiuOppositeSigns.XSub.CubicCubic diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean new file mode 100644 index 00000000..80e74ed0 --- /dev/null +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -0,0 +1,80 @@ +import RealRooted.CommonInterleaver.SameDegreeRootCount +import RealRooted.RootMatchingSort + +/-! +# Closure of Liu root-count compatibility + +This file closes the finite same-degree root inequalities under arbitrarily +small pointwise perturbations. It is the ordered-root limit step used after +derivative-shift regularization. +-/ + +namespace RealRooted +namespace LiuOppositeSigns + +open Polynomial + +/-- Same-degree root-count compatibility is closed under close finite crossings. -/ +theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing + {f g : ℝ[X]} (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) (hdeg : g.natDegree = f.natDegree) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ rf rg : List ℝ, + List.Forall₂ (fun x x' : ℝ => |x' - x| < ρ) (rootSeqDesc f) rf ∧ + List.Forall₂ (fun y y' : ℝ => |y' - y| < ρ) (rootSeqDesc g) rg ∧ + (∀ j, 1 ≤ j → j < f.natDegree → + rg.getD j 0 ≤ rf.getD (j - 1) 0) ∧ + (∀ j, 1 ≤ j → j < f.natDegree → + rf.getD j 0 ≤ rg.getD (j - 1) 0)) : + RootCountCompatible f g := by + have hcross : + (∀ j, 1 ≤ j → j < f.natDegree → + (rootSeqDesc g).getD j 0 ≤ + (rootSeqDesc f).getD (j - 1) 0) ∧ + (∀ j, 1 ≤ j → j < f.natDegree → + (rootSeqDesc f).getD j 0 ≤ + (rootSeqDesc g).getD (j - 1) 0) := by + constructor + · intro j hj1 hj + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨rf, rg, hff, hgg, hfg⟩ := hclose ρ hρ + have hjg : j < (rootSeqDesc g).length := by + simpa only [rootSeqDesc_length hg, hdeg] using hj + have hjf : j - 1 < (rootSeqDesc f).length := by + rw [rootSeqDesc_length hf] + lia + have hjrg : j < rg.length := by + rw [← hgg.length_eq] + exact hjg + have hjrf : j - 1 < rf.length := by + rw [← hff.length_eq] + exact hjf + exact ⟨rg.getD j 0, rf.getD (j - 1) 0, + by simpa using hgg.get hjg hjrg, + by simpa using hff.get hjf hjrf, hfg.1 j hj1 hj⟩ + · intro j hj1 hj + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨rf, rg, hff, hgg, hfg⟩ := hclose ρ hρ + have hjf : j < (rootSeqDesc f).length := by + simpa only [rootSeqDesc_length hf] using hj + have hjg : j - 1 < (rootSeqDesc g).length := by + rw [rootSeqDesc_length hg, hdeg] + lia + have hjrf : j < rf.length := by + rw [← hff.length_eq] + exact hjf + have hjrg : j - 1 < rg.length := by + rw [← hgg.length_eq] + exact hjg + exact ⟨rf.getD j 0, rg.getD (j - 1) 0, + by simpa using hff.get hjf hjrf, + by simpa using hgg.get hjg hjrg, hfg.2 j hj1 hj⟩ + have hlower := sameDegreeRootCount_of_rootCrossing hf hg hdeg hcross + have hupper := sameDegreeRootCountAbove_of_rootCount hf hg hdeg hlower + exact RootCountCompatible.of_rootCountAbove_bounds_of_nonRoot + hf_ne hg_ne (fun x _ _ => hupper x) + +end LiuOppositeSigns +end RealRooted From fd2cbf568360843266e06b6b8af628a216341e2c Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 16:50:53 +0000 Subject: [PATCH 25/76] Golf root count closure length bounds --- RealRooted/LiuOppositeSigns/RootCountClosure.lean | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index 80e74ed0..ce8de4f2 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -44,12 +44,9 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing have hjf : j - 1 < (rootSeqDesc f).length := by rw [rootSeqDesc_length hf] lia - have hjrg : j < rg.length := by - rw [← hgg.length_eq] - exact hjg + have hjrg : j < rg.length := by simpa only [hgg.length_eq] using hjg have hjrf : j - 1 < rf.length := by - rw [← hff.length_eq] - exact hjf + simpa only [hff.length_eq] using hjf exact ⟨rg.getD j 0, rf.getD (j - 1) 0, by simpa using hgg.get hjg hjrg, by simpa using hff.get hjf hjrf, hfg.1 j hj1 hj⟩ @@ -62,12 +59,9 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing have hjg : j - 1 < (rootSeqDesc g).length := by rw [rootSeqDesc_length hg, hdeg] lia - have hjrf : j < rf.length := by - rw [← hff.length_eq] - exact hjf + have hjrf : j < rf.length := by simpa only [hff.length_eq] using hjf have hjrg : j - 1 < rg.length := by - rw [← hgg.length_eq] - exact hjg + simpa only [hgg.length_eq] using hjg exact ⟨rf.getD j 0, rg.getD (j - 1) 0, by simpa using hff.get hjf hjrf, by simpa using hgg.get hjg hjrg, hfg.2 j hj1 hj⟩ From a7f16c4eb437bc0ff344cf4b231e24397add62b7 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:02:35 +0000 Subject: [PATCH 26/76] Add successor root crossing converse --- .../RootCountCombinatorics.lean | 24 +++++++ RealRooted/RootOrderBridge.lean | 72 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/RealRooted/CommonInterleaver/RootCountCombinatorics.lean b/RealRooted/CommonInterleaver/RootCountCombinatorics.lean index d41bb945..2c9076f1 100644 --- a/RealRooted/CommonInterleaver/RootCountCombinatorics.lean +++ b/RealRooted/CommonInterleaver/RootCountCombinatorics.lean @@ -462,6 +462,30 @@ theorem succDegreeRootCrossing_of_rootCount rw [card_roots_of_splits hg, hdeg] exact succRootCrossing_of_count_le_two hMcard hNcard hcount +/-- Converse root-count bridge from successor-degree descending-root crossing +to the asymmetric lower-threshold formulation. -/ +theorem succDegreeRootCount_of_rootCrossing + {f g : ℝ[X]} (hf : f.Splits) (hg : g.Splits) + (hdeg : g.natDegree = f.natDegree + 1) + (hcross : + (∀ j, 1 ≤ j → j ≤ f.natDegree → + (rootSeqDesc g).getD j 0 ≤ + (rootSeqDesc f).getD (j - 1) 0) ∧ + (∀ j, 1 ≤ j → j < f.natDegree → + (rootSeqDesc f).getD j 0 ≤ + (rootSeqDesc g).getD (j - 1) 0)) : + ∀ x : ℝ, + ((f.roots.filter (· ≤ x)).card : ℤ) - + (g.roots.filter (· ≤ x)).card ≤ 0 ∧ + ((g.roots.filter (· ≤ x)).card : ℤ) - + (f.roots.filter (· ≤ x)).card ≤ 2 := by + have hMcard : f.roots.card = f.natDegree := card_roots_of_splits hf + have hNcard : g.roots.card = f.natDegree + 1 := by + rw [card_roots_of_splits hg, hdeg] + simpa [rootSeqDesc] using + (count_le_two_of_succRootCrossing (M := f.roots) (N := g.roots) + hMcard hNcard hcross) + /-- Root-count bridge from the upper-threshold formulation to the succ-degree root-crossing target. -/ theorem succDegreeRootCrossing_of_rootCountAbove diff --git a/RealRooted/RootOrderBridge.lean b/RealRooted/RootOrderBridge.lean index 4fed1d91..75125ec1 100644 --- a/RealRooted/RootOrderBridge.lean +++ b/RealRooted/RootOrderBridge.lean @@ -157,6 +157,78 @@ theorem count_diff_le_one_of_rootCrossing grind grind +/-- Converse of `succRootCrossing_of_count_le_two`. + +If `N` has one more element than `M` and their descending sorted lists satisfy +the successor-degree crossing inequalities, then the lower count for `M` is at +most the lower count for `N`, while the latter is at most two larger. -/ +theorem count_le_two_of_succRootCrossing + {M N : Multiset ℝ} {d : ℕ} + (hM : M.card = d) (hN : N.card = d + 1) + (hcross : + (∀ j, 1 ≤ j → j ≤ d → + ((N.sort (· ≤ ·)).reverse).getD j 0 ≤ + ((M.sort (· ≤ ·)).reverse).getD (j - 1) 0) ∧ + (∀ j, 1 ≤ j → j < d → + ((M.sort (· ≤ ·)).reverse).getD j 0 ≤ + ((N.sort (· ≤ ·)).reverse).getD (j - 1) 0)) : + ∀ x : ℝ, + ((M.filter (· ≤ x)).card : ℤ) - (N.filter (· ≤ x)).card ≤ 0 ∧ + ((N.filter (· ≤ x)).card : ℤ) - (M.filter (· ≤ x)).card ≤ 2 := by + set sM := M.sort (· ≤ ·) + set sN := N.sort (· ≤ ·) + have hM_sorted : sM.Pairwise (· ≤ ·) := Multiset.pairwise_sort _ _ + have hN_sorted : sN.Pairwise (· ≤ ·) := Multiset.pairwise_sort _ _ + have hsM : sM.length = d := by + subst hM + simp_all only [ + tsub_le_iff_right, zero_add, Nat.cast_le, + Multiset.pairwise_sort, Multiset.length_sort, sM, sN] + have hsN : sN.length = d + 1 := by + subst hM + simp_all only [ + tsub_le_iff_right, zero_add, Nat.cast_le, + Multiset.pairwise_sort, Multiset.length_sort, sM, sN] + have hsM_eq : Multiset.ofList sM = M := Multiset.sort_eq M (· ≤ ·) + have hsN_eq : Multiset.ofList sN = N := Multiset.sort_eq N (· ≤ ·) + have h_helper := sorted_getElem_le_iff_lt_card_filter + have h_reverse_getD (l : List ℝ) (hl : l.length = d) (i : ℕ) (hi : i < d) : + l.reverse.getD i 0 = l[d - 1 - i]! := by + grind + have h_reverse_getD_succ (l : List ℝ) (hl : l.length = d + 1) + (i : ℕ) (hi : i < d + 1) : + l.reverse.getD i 0 = l[d - i]! := by + simp_all + have hint1 : ∀ i, i < d → sN[i]! ≤ sM[i]! := by + grind + have hint2 : ∀ i, i + 1 < d → sM[i]! ≤ sN[i + 2]! := by + grind + intro x + have ha_le_d : (M.filter (· ≤ x)).card ≤ d := + hM ▸ Multiset.card_le_card (Multiset.filter_le _ _) + have hb_le_succ : (N.filter (· ≤ x)).card ≤ d + 1 := + hN ▸ Multiset.card_le_card (Multiset.filter_le _ _) + have hab0 : (M.filter (· ≤ x)).card ≤ (N.filter (· ≤ x)).card := by + by_contra hcon + push Not at hcon + have hb_lt_d : (N.filter (· ≤ x)).card < d := by lia + have hMx : sM[(N.filter (· ≤ x)).card]! ≤ x := by + grind + have hNb : sN[(N.filter (· ≤ x)).card]! ≤ x := + le_trans (hint1 _ hb_lt_d) hMx + grind + have hba2 : + (N.filter (· ≤ x)).card ≤ (M.filter (· ≤ x)).card + 2 := by + by_contra hcon + push Not at hcon + have ha1_lt_d : (M.filter (· ≤ x)).card + 1 < d := by lia + have hNx : sN[(M.filter (· ≤ x)).card + 2]! ≤ x := by + grind + have hMa : sM[(M.filter (· ≤ x)).card]! ≤ x := + le_trans (hint2 _ ha1_lt_d) hNx + grind + grind + /-- Succ-degree version of `rootCrossing_of_count_diff_le_one`. If `N` has one more element than `M`, and at every lower threshold the count From c7ea7e42fea2816d747d53ebf3f81b73c502f460 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:05:51 +0000 Subject: [PATCH 27/76] Close successor root count compatibility --- .../LiuOppositeSigns/RootCountClosure.lean | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index ce8de4f2..3b7ff34c 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -4,9 +4,9 @@ import RealRooted.RootMatchingSort /-! # Closure of Liu root-count compatibility -This file closes the finite same-degree root inequalities under arbitrarily -small pointwise perturbations. It is the ordered-root limit step used after -derivative-shift regularization. +This file closes finite same-degree and successor-degree root inequalities +under arbitrarily small pointwise perturbations. It is the ordered-root limit +step used after derivative-shift regularization. -/ namespace RealRooted @@ -70,5 +70,63 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing exact RootCountCompatible.of_rootCountAbove_bounds_of_nonRoot hf_ne hg_ne (fun x _ _ => hupper x) +/-- Successor-degree root-count compatibility is closed under close finite crossings. -/ +theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCrossing + {f g : ℝ[X]} (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) + (hdeg : g.natDegree = f.natDegree + 1) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ rf rg : List ℝ, + List.Forall₂ (fun x x' : ℝ => |x' - x| < ρ) (rootSeqDesc f) rf ∧ + List.Forall₂ (fun y y' : ℝ => |y' - y| < ρ) (rootSeqDesc g) rg ∧ + (∀ j, 1 ≤ j → j ≤ f.natDegree → + rg.getD j 0 ≤ rf.getD (j - 1) 0) ∧ + (∀ j, 1 ≤ j → j < f.natDegree → + rf.getD j 0 ≤ rg.getD (j - 1) 0)) : + RootCountCompatible f g := by + have hcross : + (∀ j, 1 ≤ j → j ≤ f.natDegree → + (rootSeqDesc g).getD j 0 ≤ + (rootSeqDesc f).getD (j - 1) 0) ∧ + (∀ j, 1 ≤ j → j < f.natDegree → + (rootSeqDesc f).getD j 0 ≤ + (rootSeqDesc g).getD (j - 1) 0) := by + constructor + · intro j hj1 hj + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨rf, rg, hff, hgg, hfg⟩ := hclose ρ hρ + have hjg : j < (rootSeqDesc g).length := by + rw [rootSeqDesc_length hg, hdeg] + lia + have hjf : j - 1 < (rootSeqDesc f).length := by + rw [rootSeqDesc_length hf] + lia + have hjrg : j < rg.length := by simpa only [hgg.length_eq] using hjg + have hjrf : j - 1 < rf.length := by + simpa only [hff.length_eq] using hjf + exact ⟨rg.getD j 0, rf.getD (j - 1) 0, + by simpa using hgg.get hjg hjrg, + by simpa using hff.get hjf hjrf, hfg.1 j hj1 hj⟩ + · intro j hj1 hj + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨rf, rg, hff, hgg, hfg⟩ := hclose ρ hρ + have hjf : j < (rootSeqDesc f).length := by + simpa only [rootSeqDesc_length hf] using hj + have hjg : j - 1 < (rootSeqDesc g).length := by + rw [rootSeqDesc_length hg, hdeg] + lia + have hjrf : j < rf.length := by simpa only [hff.length_eq] using hjf + have hjrg : j - 1 < rg.length := by + simpa only [hgg.length_eq] using hjg + exact ⟨rf.getD j 0, rg.getD (j - 1) 0, + by simpa using hff.get hjf hjrf, + by simpa using hgg.get hjg hjrg, hfg.2 j hj1 hj⟩ + have hlower := succDegreeRootCount_of_rootCrossing hf hg hdeg hcross + have hupper := succDegreeRootCountAbove_of_rootCount hf hg hdeg hlower + exact RootCountCompatible.of_rootCountAbove_bounds_of_nonRoot + hf_ne hg_ne (fun x _ _ => hupper x) + end LiuOppositeSigns end RealRooted From 6efa1a6e6a587cb638546bb4f7b2cff50b8997b2 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:10:08 +0000 Subject: [PATCH 28/76] Golf successor root count wrapper --- RealRooted/CommonInterleaver/RootCountCombinatorics.lean | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/RealRooted/CommonInterleaver/RootCountCombinatorics.lean b/RealRooted/CommonInterleaver/RootCountCombinatorics.lean index 2c9076f1..1eac64ae 100644 --- a/RealRooted/CommonInterleaver/RootCountCombinatorics.lean +++ b/RealRooted/CommonInterleaver/RootCountCombinatorics.lean @@ -482,9 +482,7 @@ theorem succDegreeRootCount_of_rootCrossing have hMcard : f.roots.card = f.natDegree := card_roots_of_splits hf have hNcard : g.roots.card = f.natDegree + 1 := by rw [card_roots_of_splits hg, hdeg] - simpa [rootSeqDesc] using - (count_le_two_of_succRootCrossing (M := f.roots) (N := g.roots) - hMcard hNcard hcross) + exact count_le_two_of_succRootCrossing hMcard hNcard hcross /-- Root-count bridge from the upper-threshold formulation to the succ-degree root-crossing target. -/ From 04c609d8144ff1c90f1bb1a074ce0b9d9cddebaf Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:19:04 +0000 Subject: [PATCH 29/76] Bridge descending root sequence to sorted roots --- RealRooted/CommonInterleaverSeq.lean | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/RealRooted/CommonInterleaverSeq.lean b/RealRooted/CommonInterleaverSeq.lean index a70eb163..07456bbc 100644 --- a/RealRooted/CommonInterleaverSeq.lean +++ b/RealRooted/CommonInterleaverSeq.lean @@ -80,6 +80,20 @@ lemma rootSeqDesc_eq_reverse_of_pairwise · grind · exact Multiset.coe_eq_coe.mp (by simp [rootSeqDesc, hrs_eq, Multiset.sort_eq]) +/-- The canonical descending root sequence is the roots sorted in decreasing order. -/ +lemma rootSeqDesc_eq_sort_ge (f : ℝ[X]) : + rootSeqDesc f = f.roots.sort (· ≥ ·) := by + have hpair : + ((f.roots.sort (· ≥ ·)).reverse).Pairwise (· ≤ ·) := by + simpa using + (Multiset.pairwise_sort (s := f.roots) (r := (· ≥ ·))).reverse + have hroots : + (↑((f.roots.sort (· ≥ ·)).reverse) : Multiset ℝ) = f.roots := by + simp [Multiset.sort_eq] + simpa using + (rootSeqDesc_eq_reverse_of_pairwise (f := f) + (rs := (f.roots.sort (· ≥ ·)).reverse) hpair hroots) + /-- The `j`th Chudnovsky--Seymour interval attached to a descending root sequence `rs = [r₁, ..., r_d]`. From 2f08dc281016d8ce89036e7aa39fec3dfe36df2a Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:25:21 +0000 Subject: [PATCH 30/76] Close compatible root approximants --- .../LiuOppositeSigns/RootCountClosure.lean | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index 3b7ff34c..b02fb21a 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -128,5 +128,89 @@ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCrossing exact RootCountCompatible.of_rootCountAbove_bounds_of_nonRoot hf_ne hg_ne (fun x _ _ => hupper x) +/-- Same-degree root-count compatibility is closed under close compatible pairs. -/ +theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCompatible + {p q : ℝ[X]} (hp_ne : p ≠ 0) (hq_ne : q ≠ 0) + (hp : p.Splits) (hq : q.Splits) + (hdeg : q.natDegree = p.natDegree) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ p' q' : ℝ[X], + p' ≠ 0 ∧ + q' ≠ 0 ∧ + p'.Splits ∧ + q'.Splits ∧ + List.Forall₂ + (fun x x' : ℝ => |x' - x| < ρ) + (rootSeqDesc p) (rootSeqDesc p') ∧ + List.Forall₂ + (fun y y' : ℝ => |y' - y| < ρ) + (rootSeqDesc q) (rootSeqDesc q') ∧ + RootCountCompatible p' q') : + RootCountCompatible p q := by + refine RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing + hp_ne hq_ne hp hq hdeg ?_ + intro ρ hρ + obtain ⟨p', q', hp'_ne, hq'_ne, hp'_split, hq'_split, + hpp', hqq', hcompat'⟩ := + hclose ρ hρ + have hpdeg : p'.natDegree = p.natDegree := by + rw [← rootSeqDesc_length hp'_split, ← rootSeqDesc_length hp] + exact hpp'.length_eq.symm + have hqdeg : q'.natDegree = q.natDegree := by + rw [← rootSeqDesc_length hq'_split, ← rootSeqDesc_length hq] + exact hqq'.length_eq.symm + have hdeg' : q'.natDegree = p'.natDegree := by + rw [hqdeg, hpdeg, hdeg] + have hcount' := + sameDegreeRootCountAbove_of_nonRoot_bound hp'_ne hq'_ne + (fun _ hpx hqx => + hcompat'.rootCountAbove_bounds_of_nonRoot + hp'_ne hq'_ne hpx hqx) + exact ⟨rootSeqDesc p', rootSeqDesc q', hpp', hqq', + rootCrossing_of_rootCountAbove_diff_le_one + hp'_split hq'_split hdeg' hcount'⟩ + +/-- Successor-degree root-count compatibility is closed under close compatible pairs. -/ +theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible + {p q : ℝ[X]} (hp_ne : p ≠ 0) (hq_ne : q ≠ 0) + (hp : p.Splits) (hq : q.Splits) + (hdeg : q.natDegree = p.natDegree + 1) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ p' q' : ℝ[X], + p' ≠ 0 ∧ + q' ≠ 0 ∧ + p'.Splits ∧ + q'.Splits ∧ + List.Forall₂ + (fun x x' : ℝ => |x' - x| < ρ) + (rootSeqDesc p) (rootSeqDesc p') ∧ + List.Forall₂ + (fun y y' : ℝ => |y' - y| < ρ) + (rootSeqDesc q) (rootSeqDesc q') ∧ + RootCountCompatible p' q') : + RootCountCompatible p q := by + refine RootCountCompatible.of_forall_pos_exists_close_succDegreeCrossing + hp_ne hq_ne hp hq hdeg ?_ + intro ρ hρ + obtain ⟨p', q', hp'_ne, hq'_ne, hp'_split, hq'_split, + hpp', hqq', hcompat'⟩ := + hclose ρ hρ + have hpdeg : p'.natDegree = p.natDegree := by + rw [← rootSeqDesc_length hp'_split, ← rootSeqDesc_length hp] + exact hpp'.length_eq.symm + have hqdeg : q'.natDegree = q.natDegree := by + rw [← rootSeqDesc_length hq'_split, ← rootSeqDesc_length hq] + exact hqq'.length_eq.symm + have hdeg' : q'.natDegree = p'.natDegree + 1 := by + rw [hqdeg, hpdeg, hdeg] + have hcount' := + sameDegreeRootCountAbove_of_nonRoot_bound hp'_ne hq'_ne + (fun _ hpx hqx => + hcompat'.rootCountAbove_bounds_of_nonRoot + hp'_ne hq'_ne hpx hqx) + exact ⟨rootSeqDesc p', rootSeqDesc q', hpp', hqq', + succDegreeRootCrossing_of_rootCountAbove + hp'_split hq'_split hdeg' hcount'⟩ + end LiuOppositeSigns end RealRooted From 514266df6f5f1cf2de606059fee69a3fe0f9fc62 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:26:47 +0000 Subject: [PATCH 31/76] Golf compatible approximant closure --- RealRooted/LiuOppositeSigns/RootCountClosure.lean | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index b02fb21a..c64c3254 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -163,9 +163,7 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCompatible rw [hqdeg, hpdeg, hdeg] have hcount' := sameDegreeRootCountAbove_of_nonRoot_bound hp'_ne hq'_ne - (fun _ hpx hqx => - hcompat'.rootCountAbove_bounds_of_nonRoot - hp'_ne hq'_ne hpx hqx) + (hcompat'.rootCountAbove_bounds_of_nonRoot hp'_ne hq'_ne) exact ⟨rootSeqDesc p', rootSeqDesc q', hpp', hqq', rootCrossing_of_rootCountAbove_diff_le_one hp'_split hq'_split hdeg' hcount'⟩ @@ -205,9 +203,7 @@ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible rw [hqdeg, hpdeg, hdeg] have hcount' := sameDegreeRootCountAbove_of_nonRoot_bound hp'_ne hq'_ne - (fun _ hpx hqx => - hcompat'.rootCountAbove_bounds_of_nonRoot - hp'_ne hq'_ne hpx hqx) + (hcompat'.rootCountAbove_bounds_of_nonRoot hp'_ne hq'_ne) exact ⟨rootSeqDesc p', rootSeqDesc q', hpp', hqq', succDegreeRootCrossing_of_rootCountAbove hp'_split hq'_split hdeg' hcount'⟩ From 2158cb67beea9ed01926cf3c61ff153beaeb4899 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:33:37 +0000 Subject: [PATCH 32/76] Close equal-degree left root-count branches --- .../LiuOppositeSigns/RootCountClosure.lean | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index c64c3254..f1d6715b 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -1,4 +1,5 @@ import RealRooted.CommonInterleaver.SameDegreeRootCount +import RealRooted.LiuOppositeSigns.RootCountRelStability import RealRooted.RootMatchingSort /-! @@ -208,5 +209,62 @@ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible succDegreeRootCrossing_of_rootCountAbove hp'_split hq'_split hdeg' hcount'⟩ +/-- A fixed left branch is closed under close same-degree approximations. -/ +theorem LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree + {f g : ℝ[X]} {r s : ℝ} + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) + (hr : IsLargestRoot f r) (hs : IsLargestRoot g s) + (hdeg : f.natDegree = g.natDegree) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ f' g' : ℝ[X], ∃ r' s' : ℝ, + f' ≠ 0 ∧ + g' ≠ 0 ∧ + f'.Splits ∧ + g'.Splits ∧ + Multiset.Rel + (fun x x' : ℝ => |x' - x| < ρ) + f.roots f'.roots ∧ + Multiset.Rel + (fun y y' : ℝ => |y' - y| < ρ) + g.roots g'.roots ∧ + LeftRootCountBranch f' g' r' s') : + LeftRootCountBranch f g r s := by + have hlargest : s ≤ r := by + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + exact ⟨s', r', + hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', + hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', + hbranch'.largest_ge⟩ + have hdelete_ne : deleteRootFactor f r ≠ 0 := + hr.deleteRootFactor_ne_zero hf_ne + have hdelete_split : (deleteRootFactor f r).Splits := + hr.deleteRootFactor_splits hf + have hdelete_degree : + g.natDegree = (deleteRootFactor f r).natDegree + 1 := by + rw [natDegree_deleteRootFactor] + have hf_degree_pos := hr.natDegree_pos hf_ne + lia + have hcount : RootCountCompatible (deleteRootFactor f r) g := by + refine RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible + hdelete_ne hg_ne hdelete_split hg hdelete_degree ?_ + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + refine ⟨deleteRootFactor f' r', g', + hbranch'.f_largest.deleteRootFactor_ne_zero hf'_ne, + hg'_ne, + hbranch'.f_largest.deleteRootFactor_splits hf'_split, + hg'_split, ?_, ?_, hbranch'.count⟩ + · simpa only [rootSeqDesc_eq_sort_ge] using + (forall₂_sort_ge_deleteRootFactor_of_roots_rel + hf_ne hf'_ne hr hbranch'.f_largest hff') + · simpa only [rootSeqDesc_eq_sort_ge] using + (forall₂_sort_ge_of_rel_abs_sub_lt hgg') + exact ⟨hr, hs, hlargest, hcount⟩ + end LiuOppositeSigns end RealRooted From a9ecb09d1f5080eebda3444d9dae17ce0dfed2a7 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:36:57 +0000 Subject: [PATCH 33/76] Close successor-degree left root-count branches --- .../LiuOppositeSigns/RootCountClosure.lean | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index f1d6715b..a481508d 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -266,5 +266,62 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree (forall₂_sort_ge_of_rel_abs_sub_lt hgg') exact ⟨hr, hs, hlargest, hcount⟩ +/-- A fixed left branch is closed when the first degree is one larger. -/ +theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ + {f g : ℝ[X]} {r s : ℝ} + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) + (hr : IsLargestRoot f r) (hs : IsLargestRoot g s) + (hdeg : f.natDegree = g.natDegree + 1) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ f' g' : ℝ[X], ∃ r' s' : ℝ, + f' ≠ 0 ∧ + g' ≠ 0 ∧ + f'.Splits ∧ + g'.Splits ∧ + Multiset.Rel + (fun x x' : ℝ => |x' - x| < ρ) + f.roots f'.roots ∧ + Multiset.Rel + (fun y y' : ℝ => |y' - y| < ρ) + g.roots g'.roots ∧ + LeftRootCountBranch f' g' r' s') : + LeftRootCountBranch f g r s := by + have hlargest : s ≤ r := by + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + exact ⟨s', r', + hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', + hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', + hbranch'.largest_ge⟩ + have hdelete_ne : deleteRootFactor f r ≠ 0 := + hr.deleteRootFactor_ne_zero hf_ne + have hdelete_split : (deleteRootFactor f r).Splits := + hr.deleteRootFactor_splits hf + have hdelete_degree : + g.natDegree = (deleteRootFactor f r).natDegree := by + rw [natDegree_deleteRootFactor] + have hf_degree_pos := hr.natDegree_pos hf_ne + lia + have hcount : RootCountCompatible (deleteRootFactor f r) g := by + refine RootCountCompatible.of_forall_pos_exists_close_sameDegreeCompatible + hdelete_ne hg_ne hdelete_split hg hdelete_degree ?_ + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + refine ⟨deleteRootFactor f' r', g', + hbranch'.f_largest.deleteRootFactor_ne_zero hf'_ne, + hg'_ne, + hbranch'.f_largest.deleteRootFactor_splits hf'_split, + hg'_split, ?_, ?_, hbranch'.count⟩ + · simpa only [rootSeqDesc_eq_sort_ge] using + (forall₂_sort_ge_deleteRootFactor_of_roots_rel + hf_ne hf'_ne hr hbranch'.f_largest hff') + · simpa only [rootSeqDesc_eq_sort_ge] using + (forall₂_sort_ge_of_rel_abs_sub_lt hgg') + exact ⟨hr, hs, hlargest, hcount⟩ + end LiuOppositeSigns end RealRooted From 4aaf4f0c0d58a4d0e590a744695ec7f3682ad4ec Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:39:46 +0000 Subject: [PATCH 34/76] Golf fixed left branch closure --- RealRooted/LiuOppositeSigns/RootCountClosure.lean | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index a481508d..c6bd8513 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -239,10 +239,6 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', hbranch'.largest_ge⟩ - have hdelete_ne : deleteRootFactor f r ≠ 0 := - hr.deleteRootFactor_ne_zero hf_ne - have hdelete_split : (deleteRootFactor f r).Splits := - hr.deleteRootFactor_splits hf have hdelete_degree : g.natDegree = (deleteRootFactor f r).natDegree + 1 := by rw [natDegree_deleteRootFactor] @@ -250,7 +246,8 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree lia have hcount : RootCountCompatible (deleteRootFactor f r) g := by refine RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible - hdelete_ne hg_ne hdelete_split hg hdelete_degree ?_ + (hr.deleteRootFactor_ne_zero hf_ne) hg_ne + (hr.deleteRootFactor_splits hf) hg hdelete_degree ?_ intro ρ hρ obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, hff', hgg', hbranch'⟩ := hclose ρ hρ @@ -296,10 +293,6 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', hbranch'.largest_ge⟩ - have hdelete_ne : deleteRootFactor f r ≠ 0 := - hr.deleteRootFactor_ne_zero hf_ne - have hdelete_split : (deleteRootFactor f r).Splits := - hr.deleteRootFactor_splits hf have hdelete_degree : g.natDegree = (deleteRootFactor f r).natDegree := by rw [natDegree_deleteRootFactor] @@ -307,7 +300,8 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ lia have hcount : RootCountCompatible (deleteRootFactor f r) g := by refine RootCountCompatible.of_forall_pos_exists_close_sameDegreeCompatible - hdelete_ne hg_ne hdelete_split hg hdelete_degree ?_ + (hr.deleteRootFactor_ne_zero hf_ne) hg_ne + (hr.deleteRootFactor_splits hf) hg hdelete_degree ?_ intro ρ hρ obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, hff', hgg', hbranch'⟩ := hclose ρ hρ From 2e326885f7e624b43523c8baf324fcc77946b8e1 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:43:10 +0000 Subject: [PATCH 35/76] Close two-step-degree left root-count branches --- .../LiuOppositeSigns/RootCountClosure.lean | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index c6bd8513..4cda0a28 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -317,5 +317,62 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ (forall₂_sort_ge_of_rel_abs_sub_lt hgg') exact ⟨hr, hs, hlargest, hcount⟩ +/-- A fixed left branch is closed when the first degree is two larger. -/ +theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ_succ + {f g : ℝ[X]} {r s : ℝ} + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) + (hr : IsLargestRoot f r) (hs : IsLargestRoot g s) + (hdeg : f.natDegree = g.natDegree + 2) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ f' g' : ℝ[X], ∃ r' s' : ℝ, + f' ≠ 0 ∧ + g' ≠ 0 ∧ + f'.Splits ∧ + g'.Splits ∧ + Multiset.Rel + (fun x x' : ℝ => |x' - x| < ρ) + f.roots f'.roots ∧ + Multiset.Rel + (fun y y' : ℝ => |y' - y| < ρ) + g.roots g'.roots ∧ + LeftRootCountBranch f' g' r' s') : + LeftRootCountBranch f g r s := by + have hlargest : s ≤ r := by + apply le_of_forall_pos_exists_close_le + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + exact ⟨s', r', + hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', + hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', + hbranch'.largest_ge⟩ + have hdelete_degree : + (deleteRootFactor f r).natDegree = g.natDegree + 1 := by + rw [natDegree_deleteRootFactor] + have hf_degree_pos := hr.natDegree_pos hf_ne + lia + have hcount : RootCountCompatible (deleteRootFactor f r) g := by + have hcount_symm : RootCountCompatible g (deleteRootFactor f r) := by + refine RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible + hg_ne (hr.deleteRootFactor_ne_zero hf_ne) hg + (hr.deleteRootFactor_splits hf) hdelete_degree ?_ + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + refine ⟨g', deleteRootFactor f' r', + hg'_ne, + hbranch'.f_largest.deleteRootFactor_ne_zero hf'_ne, + hg'_split, + hbranch'.f_largest.deleteRootFactor_splits hf'_split, + ?_, ?_, hbranch'.count.symm⟩ + · simpa only [rootSeqDesc_eq_sort_ge] using + (forall₂_sort_ge_of_rel_abs_sub_lt hgg') + · simpa only [rootSeqDesc_eq_sort_ge] using + (forall₂_sort_ge_deleteRootFactor_of_roots_rel + hf_ne hf'_ne hr hbranch'.f_largest hff') + exact hcount_symm.symm + exact ⟨hr, hs, hlargest, hcount⟩ + end LiuOppositeSigns end RealRooted From 9c96321491d87cd5f03d3edacbc5c643e767a291 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:46:15 +0000 Subject: [PATCH 36/76] Dispatch fixed left branch closure by degree --- .../LiuOppositeSigns/RootCountClosure.lean | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index 4cda0a28..a679dcdb 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -374,5 +374,50 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ_succ exact hcount_symm.symm exact ⟨hr, hs, hlargest, hcount⟩ +/-- A fixed left branch is closed under close approximations. -/ +theorem LeftRootCountBranch.of_forall_pos_exists_close + {f g : ℝ[X]} {r s : ℝ} + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) + (hr : IsLargestRoot f r) (hs : IsLargestRoot g s) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ f' g' : ℝ[X], ∃ r' s' : ℝ, + f' ≠ 0 ∧ + g' ≠ 0 ∧ + f'.Splits ∧ + g'.Splits ∧ + Multiset.Rel + (fun x x' : ℝ => |x' - x| < ρ) + f.roots f'.roots ∧ + Multiset.Rel + (fun y y' : ℝ => |y' - y| < ρ) + g.roots g'.roots ∧ + LeftRootCountBranch f' g' r' s') : + LeftRootCountBranch f g r s := by + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose 1 zero_lt_one + have hf_degree : f'.natDegree = f.natDegree := by + calc + f'.natDegree = f'.roots.card := (card_roots_of_splits hf'_split).symm + _ = f.roots.card := (Multiset.card_eq_card_of_rel hff').symm + _ = f.natDegree := card_roots_of_splits hf + have hg_degree : g'.natDegree = g.natDegree := by + calc + g'.natDegree = g'.roots.card := (card_roots_of_splits hg'_split).symm + _ = g.roots.card := (Multiset.card_eq_card_of_rel hgg').symm + _ = g.natDegree := card_roots_of_splits hg + rcases hbranch'.natDegree_eq_or_eq_succ_or_eq_succ_succ + hf'_ne hf'_split hg'_split with hdeg | hdeg | hdeg + · rw [hf_degree, hg_degree] at hdeg + exact LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree + hf_ne hg_ne hf hg hr hs hdeg hclose + · rw [hf_degree, hg_degree] at hdeg + exact LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ + hf_ne hg_ne hf hg hr hs hdeg hclose + · rw [hf_degree, hg_degree] at hdeg + exact + LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ_succ + hf_ne hg_ne hf hg hr hs hdeg hclose + end LiuOppositeSigns end RealRooted From 4bbcb1f69c6fbe18d8ec0c0c8fbfbeadc1a001d1 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:48:00 +0000 Subject: [PATCH 37/76] Golf fixed left branch dispatcher --- .../LiuOppositeSigns/RootCountClosure.lean | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index a679dcdb..df07bd8b 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -406,16 +406,16 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close g'.natDegree = g'.roots.card := (card_roots_of_splits hg'_split).symm _ = g.roots.card := (Multiset.card_eq_card_of_rel hgg').symm _ = g.natDegree := card_roots_of_splits hg - rcases hbranch'.natDegree_eq_or_eq_succ_or_eq_succ_succ - hf'_ne hf'_split hg'_split with hdeg | hdeg | hdeg - · rw [hf_degree, hg_degree] at hdeg - exact LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree + have hdegrees := + hbranch'.natDegree_eq_or_eq_succ_or_eq_succ_succ + hf'_ne hf'_split hg'_split + rw [hf_degree, hg_degree] at hdegrees + rcases hdegrees with hdeg | hdeg | hdeg + · exact LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree hf_ne hg_ne hf hg hr hs hdeg hclose - · rw [hf_degree, hg_degree] at hdeg - exact LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ + · exact LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ hf_ne hg_ne hf hg hr hs hdeg hclose - · rw [hf_degree, hg_degree] at hdeg - exact + · exact LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ_succ hf_ne hg_ne hf hg hr hs hdeg hclose From 7e8863a700a1102dc39464951194d5368c1a85be Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:50:10 +0000 Subject: [PATCH 38/76] Close fixed right root-count branches --- .../LiuOppositeSigns/RootCountClosure.lean | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index df07bd8b..82d9a399 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -419,5 +419,40 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ_succ hf_ne hg_ne hf hg hr hs hdeg hclose +/-- A fixed right branch is closed under close approximations. -/ +theorem RightRootCountBranch.of_forall_pos_exists_close + {f g : ℝ[X]} {r s : ℝ} + (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) + (hr : IsLargestRoot f r) (hs : IsLargestRoot g s) + (hrlt : r < s) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ f' g' : ℝ[X], ∃ r' s' : ℝ, + f' ≠ 0 ∧ + g' ≠ 0 ∧ + f'.Splits ∧ + g'.Splits ∧ + Multiset.Rel + (fun x x' : ℝ => |x' - x| < ρ) + f.roots f'.roots ∧ + Multiset.Rel + (fun y y' : ℝ => |y' - y| < ρ) + g.roots g'.roots ∧ + RightRootCountBranch f' g' r' s') : + RightRootCountBranch f g r s := by + have hleft : LeftRootCountBranch g f s r := by + apply LeftRootCountBranch.of_forall_pos_exists_close + hg_ne hf_ne hg hf hs hr + intro ρ hρ + obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranch'⟩ := hclose ρ hρ + exact + ⟨g', f', s', r', + hg'_ne, hf'_ne, + hg'_split, hf'_split, + hgg', hff', + hbranch'.toLeftBranch_symm⟩ + exact hleft.toRightBranch_symm_of_lt hrlt + end LiuOppositeSigns end RealRooted From 8a2b5d07c808ace7e3e9fdf9572b855d782d4287 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 17:57:28 +0000 Subject: [PATCH 39/76] Close disjunctive Liu root-count branches --- .../LiuOppositeSigns/RootCountClosure.lean | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index 82d9a399..561cba4c 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -454,5 +454,84 @@ theorem RightRootCountBranch.of_forall_pos_exists_close hbranch'.toLeftBranch_symm⟩ exact hleft.toRightBranch_symm_of_lt hrlt +/-- Liu's disjunctive root-count branch is closed under close splitting approximations. -/ +theorem theorem21RootCountBranches_of_forall_pos_exists_roots_rel + {f g : ℝ[X]} (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) + (hf : f.Splits) (hg : g.Splits) (hno : NoCommonRoots f g) + (hf_deg : f.natDegree ≠ 0) (hg_deg : g.natDegree ≠ 0) + (hclose : ∀ ρ : ℝ, 0 < ρ → + ∃ f' g' : ℝ[X], + f' ≠ 0 ∧ + g' ≠ 0 ∧ + f'.Splits ∧ + g'.Splits ∧ + Multiset.Rel + (fun x x' : ℝ => |x' - x| < ρ) + f.roots f'.roots ∧ + Multiset.Rel + (fun y y' : ℝ => |y' - y| < ρ) + g.roots g'.roots ∧ + theorem21RootCountBranches f' g') : + theorem21RootCountBranches f g := by + obtain ⟨r, hr⟩ := + exists_isLargestRoot hf_ne hf (Nat.pos_of_ne_zero hf_deg) + obtain ⟨s, hs⟩ := + exists_isLargestRoot hg_ne hg (Nat.pos_of_ne_zero hg_deg) + have hrs_ne : r ≠ s := by + intro hrs + subst s + exact (hno r hr.isRoot) hs.isRoot + rcases lt_or_gt_of_ne hrs_ne with hrs | hsr + · apply theorem21RootCountBranches_of_right + apply RightRootCountBranch.of_forall_pos_exists_close + hf_ne hg_ne hf hg hr hs hrs + intro ρ hρ + let δ : ℝ := min ρ ((s - r) / 2) + have hδ : 0 < δ := + lt_min hρ (half_pos (sub_pos.mpr hrs)) + have hδ_le_ρ : δ ≤ ρ := min_le_left _ _ + have hδ_le_gap : δ ≤ (s - r) / 2 := min_le_right _ _ + obtain ⟨f', g', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranches⟩ := hclose δ hδ + obtain ⟨r', s', hbranch'⟩ := hbranches + rcases hbranch' with hleft' | hright' + · exfalso + have hr_close := + hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hleft'.f_largest hff' + have hs_close := + hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hleft'.g_largest hgg' + rw [abs_lt] at hr_close hs_close + linarith [hleft'.largest_ge, hδ_le_gap] + · exact + ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), + hgg'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), + hright'⟩ + · apply theorem21RootCountBranches_of_left + apply LeftRootCountBranch.of_forall_pos_exists_close + hf_ne hg_ne hf hg hr hs + intro ρ hρ + let δ : ℝ := min ρ ((r - s) / 2) + have hδ : 0 < δ := + lt_min hρ (half_pos (sub_pos.mpr hsr)) + have hδ_le_ρ : δ ≤ ρ := min_le_left _ _ + have hδ_le_gap : δ ≤ (r - s) / 2 := min_le_right _ _ + obtain ⟨f', g', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff', hgg', hbranches⟩ := hclose δ hδ + obtain ⟨r', s', hbranch'⟩ := hbranches + rcases hbranch' with hleft' | hright' + · exact + ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, + hff'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), + hgg'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), + hleft'⟩ + · exfalso + have hr_close := + hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hright'.f_largest hff' + have hs_close := + hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hright'.g_largest hgg' + rw [abs_lt] at hr_close hs_close + linarith [hright'.largest_lt, hδ_le_gap] + end LiuOppositeSigns end RealRooted From fa723640a6e6f25b9e4750a1c97987fce54a52e3 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:00:28 +0000 Subject: [PATCH 40/76] Deduplicate Liu root relation widening --- RealRooted/LiuOppositeSigns/RootCountClosure.lean | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index 561cba4c..8f25d806 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -473,6 +473,11 @@ theorem theorem21RootCountBranches_of_forall_pos_exists_roots_rel g.roots g'.roots ∧ theorem21RootCountBranches f' g') : theorem21RootCountBranches f g := by + have widenRootsRel {δ ρ : ℝ} (hδρ : δ ≤ ρ) {u v : Multiset ℝ} + (h : Multiset.Rel + (fun x x' : ℝ => |x' - x| < δ) u v) : + Multiset.Rel (fun x x' : ℝ => |x' - x| < ρ) u v := + h.mono fun _ _ _ _ hxy => hxy.trans_le hδρ obtain ⟨r, hr⟩ := exists_isLargestRoot hf_ne hf (Nat.pos_of_ne_zero hf_deg) obtain ⟨s, hs⟩ := @@ -504,8 +509,8 @@ theorem theorem21RootCountBranches_of_forall_pos_exists_roots_rel linarith [hleft'.largest_ge, hδ_le_gap] · exact ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, - hff'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), - hgg'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), + widenRootsRel hδ_le_ρ hff', + widenRootsRel hδ_le_ρ hgg', hright'⟩ · apply theorem21RootCountBranches_of_left apply LeftRootCountBranch.of_forall_pos_exists_close @@ -522,8 +527,8 @@ theorem theorem21RootCountBranches_of_forall_pos_exists_roots_rel rcases hbranch' with hleft' | hright' · exact ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, - hff'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), - hgg'.mono (fun _ _ _ _ h => h.trans_le hδ_le_ρ), + widenRootsRel hδ_le_ρ hff', + widenRootsRel hδ_le_ρ hgg', hleft'⟩ · exfalso have hr_close := From 8ca681dedb7b07c3b138f8e27b2e23e3edca894a Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:08:57 +0000 Subject: [PATCH 41/76] Prove Liu nonconstant forward implication --- RealRooted/LiuOppositeSignsTheorem.lean | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index ae875d95..b9bb5bd0 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -1,8 +1,10 @@ import RealRooted.LiuOppositeSigns.BoundedIntervalContinuity import RealRooted.LiuOppositeSigns.CommonInterleaverConsequences +import RealRooted.LiuOppositeSigns.DerivativeShiftSequenceRegularization import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderAssembly import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderLower import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderUpper +import RealRooted.LiuOppositeSigns.RootCountClosure import RealRooted.LiuOppositeSigns.XSub.IntervalRootCount /-! @@ -19,6 +21,66 @@ open Polynomial Filter namespace RealRooted namespace LiuOppositeSigns +/-- The no-common, nonconstant forward implication in Liu Theorem 2.1. + +The derivative-shift regularization repairs the source's invalid inference +from no common roots to simple roots. The simple-root argument is applied to +arbitrarily close regularizations, and root matching closes the result. -/ +theorem theorem21CompatibleToRootCountBranchesNoCommonNonconstant : + theorem21CompatibleToRootCountBranchesNoCommonNonconstantStatement := by + intro f g hf hg hsgn hno hf_deg hg_deg hcompat + apply theorem21RootCountBranches_of_forall_pos_exists_roots_rel + hsgn.left_ne_zero hsgn.right_ne_zero hf hg hno hf_deg hg_deg + intro ρ hρ + obtain ⟨epss, hlen, hbounds, hcompat', hno', hfrel, hgrel⟩ := + hno.exists_applyTDerivList_roots_rel (κ := 1) (ρ := ρ) + hcompat hsgn.left_ne_zero hsgn.right_ne_zero hf hg + zero_lt_one hρ (max f.natDegree g.natDegree) + let f' : ℝ[X] := applyTDerivList epss f + let g' : ℝ[X] := applyTDerivList epss g + have hpos : ∀ eps ∈ epss, 0 < eps := + fun eps heps => (hbounds eps heps).1 + have hf'_ne : f' ≠ 0 := by + simpa [f'] using + applyTDerivList_ne_zero (epss := epss) hsgn.left_ne_zero + have hg'_ne : g' ≠ 0 := by + simpa [g'] using + applyTDerivList_ne_zero (epss := epss) hsgn.right_ne_zero + have hf'_split : f'.Splits := by + simpa [f'] using hf.applyTDerivList hpos + have hg'_split : g'.Splits := by + simpa [g'] using hg.applyTDerivList hpos + have hf'_deg : f'.natDegree ≠ 0 := by + simpa [f'] using hf_deg + have hg'_deg : g'.natDegree ≠ 0 := by + simpa [g'] using hg_deg + have hsgn' : OppositeLeadingSigns f' g' := by + simpa [f', g', OppositeLeadingSigns] using hsgn + have hf'_simple : HasSimpleRoots f' := by + change HasSimpleRoots (applyTDerivList epss f) + apply hasSimpleRoots_applyTDerivList_of_natDegree_le_length + hpos hsgn.left_ne_zero hf + rw [hlen] + exact Nat.le_max_left _ _ + have hg'_simple : HasSimpleRoots g' := by + change HasSimpleRoots (applyTDerivList epss g) + apply hasSimpleRoots_applyTDerivList_of_natDegree_le_length + hpos hsgn.right_ne_zero hg + rw [hlen] + exact Nat.le_max_right _ _ + have hcompat'' : Compatible f' g' := by + simpa [f', g'] using hcompat' + have hno'' : NoCommonRoots f' g' := by + simpa [f', g'] using hno' + have hbranches : theorem21RootCountBranches f' g' := + theorem21RootCountBranches_of_compatible_noCommon_nonconstant_of_simple + hf'_split hg'_split hsgn' hno'' hf'_deg hg'_deg + hf'_simple hg'_simple hcompat'' + refine + ⟨f', g', hf'_ne, hg'_ne, hf'_split, hg'_split, ?_, ?_, hbranches⟩ + · simpa [f'] using hfrel + · simpa [g'] using hgrel + /-- The isolated forward direction of Liu Theorem 2.1 gives the pointwise root-count gap bound. -/ theorem rootCountAtOrAbove_abs_sub_le_two_of_compatible_of_forward From 15e5818e2d6dfd2027a22994bfcbf478b9fa0695 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:18:28 +0000 Subject: [PATCH 42/76] Assemble Liu no-common nonconstant equivalence --- RealRooted/LiuOppositeSignsTheorem.lean | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index b9bb5bd0..a68f8714 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -5,6 +5,7 @@ import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderAssembly import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderLower import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderUpper import RealRooted.LiuOppositeSigns.RootCountClosure +import RealRooted.LiuOppositeSigns.Theorem21Assembly import RealRooted.LiuOppositeSigns.XSub.IntervalRootCount /-! @@ -81,6 +82,16 @@ theorem theorem21CompatibleToRootCountBranchesNoCommonNonconstant : · simpa [f'] using hfrel · simpa [g'] using hgrel +/-- The nonconstant no-common-root form of Liu Theorem 2.1. -/ +theorem theorem21CompatibleRootCountNoCommonNonconstant : + theorem21CompatibleRootCountNoCommonNonconstantStatement := by + intro f g hf hg hsgn hno hf_deg hg_deg + exact + ⟨theorem21CompatibleToRootCountBranchesNoCommonNonconstant + hf hg hsgn hno hf_deg hg_deg, + theorem21RootCountBranchesToCompatibleNonconstant_of_xSub + hf hg hsgn hf_deg hg_deg⟩ + /-- The isolated forward direction of Liu Theorem 2.1 gives the pointwise root-count gap bound. -/ theorem rootCountAtOrAbove_abs_sub_le_two_of_compatible_of_forward From 2e0240e58fffe1e7f0018587d02db042b6e5c65e Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:20:16 +0000 Subject: [PATCH 43/76] Golf Liu regularization assembly --- RealRooted/LiuOppositeSignsTheorem.lean | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index a68f8714..e110f0b5 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -69,14 +69,10 @@ theorem theorem21CompatibleToRootCountBranchesNoCommonNonconstant : hpos hsgn.right_ne_zero hg rw [hlen] exact Nat.le_max_right _ _ - have hcompat'' : Compatible f' g' := by - simpa [f', g'] using hcompat' - have hno'' : NoCommonRoots f' g' := by - simpa [f', g'] using hno' have hbranches : theorem21RootCountBranches f' g' := theorem21RootCountBranches_of_compatible_noCommon_nonconstant_of_simple - hf'_split hg'_split hsgn' hno'' hf'_deg hg'_deg - hf'_simple hg'_simple hcompat'' + hf'_split hg'_split hsgn' hno' hf'_deg hg'_deg + hf'_simple hg'_simple hcompat' refine ⟨f', g', hf'_ne, hg'_ne, hf'_split, hg'_split, ?_, ?_, hbranches⟩ · simpa [f'] using hfrel From e93c4537844efca04249b17504cbcd0112131f0a Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:31:30 +0000 Subject: [PATCH 44/76] Prove corrected Liu common-root equivalences --- RealRooted/LiuOppositeSignsTheorem.lean | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index e110f0b5..1be9eaa8 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -88,6 +88,54 @@ theorem theorem21CompatibleRootCountNoCommonNonconstant : theorem21RootCountBranchesToCompatibleNonconstant_of_xSub hf hg hsgn hf_deg hg_deg⟩ +/-- Correct nonconstant Liu equivalence with common roots retained explicitly. +The legacy branch-only equivalence is false when the endpoints share a largest +root: the right branch requires a strict largest-root inequality, while deleting +the largest root from only the left endpoint can leave a root-count gap of two. +The common-root deletion alternative is therefore necessary. + +The reduced predicate is the strongest form because its ordinary root-count +branch retains the accompanying `NoCommonRoots` witness. -/ +theorem compatible_iff_theorem21RootCountBranchesReduced_nonconstant + {f g : ℝ[X]} (hf : f.Splits) (hg : g.Splits) + (hsgn : OppositeLeadingSigns f g) + (hf_deg : f.natDegree ≠ 0) (hg_deg : g.natDegree ≠ 0) : + Compatible f g ↔ theorem21RootCountBranchesReduced f g := by + constructor + · intro hcompat + by_cases hno : NoCommonRoots f g + · exact Or.inl + ⟨hno, + theorem21CompatibleToRootCountBranchesNoCommonNonconstant + hf hg hsgn hno hf_deg hg_deg hcompat⟩ + · exact Or.inr + (CommonRootDeletionCompatibleBranch.of_compatible_of_not_noCommonRoots + hcompat hno) + · intro hbranches + rcases hbranches with hbranches | hcommon + · exact theorem21RootCountBranchesToCompatibleNonconstant_of_xSub + hf hg hsgn hf_deg hg_deg hbranches.2 + · exact hcommon.compatible + +/-- Public-facing correct nonconstant Liu equivalence. Compared with the +legacy branch-only predicate, this conclusion includes the necessary explicit +common-root deletion alternative. -/ +theorem compatible_iff_theorem21RootCountBranchesWithCommon_nonconstant + {f g : ℝ[X]} (hf : f.Splits) (hg : g.Splits) + (hsgn : OppositeLeadingSigns f g) + (hf_deg : f.natDegree ≠ 0) (hg_deg : g.natDegree ≠ 0) : + Compatible f g ↔ theorem21RootCountBranchesWithCommon f g := by + constructor + · intro hcompat + exact theorem21RootCountBranchesReduced.withCommon + ((compatible_iff_theorem21RootCountBranchesReduced_nonconstant + hf hg hsgn hf_deg hg_deg).mp hcompat) + · intro hbranches + rcases hbranches with hbranches | hcommon + · exact theorem21RootCountBranchesToCompatibleNonconstant_of_xSub + hf hg hsgn hf_deg hg_deg hbranches + · exact hcommon.compatible + /-- The isolated forward direction of Liu Theorem 2.1 gives the pointwise root-count gap bound. -/ theorem rootCountAtOrAbove_abs_sub_le_two_of_compatible_of_forward From 1a3cba53756c3042bf2d270cf1c48895cfb6b3c1 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:43:15 +0000 Subject: [PATCH 45/76] Refute false Liu nonconstant branch statement --- .../LiuOppositeSigns/Theorem21Statements.lean | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/RealRooted/LiuOppositeSigns/Theorem21Statements.lean b/RealRooted/LiuOppositeSigns/Theorem21Statements.lean index 8188a22b..9e6d073a 100644 --- a/RealRooted/LiuOppositeSigns/Theorem21Statements.lean +++ b/RealRooted/LiuOppositeSigns/Theorem21Statements.lean @@ -1918,6 +1918,53 @@ def theorem21CompatibleToRootCountBranchesNonconstantStatement : Prop := f.natDegree ≠ 0 → g.natDegree ≠ 0 → Compatible f g → theorem21RootCountBranches f g +/-- The unreduced nonconstant forward statement is false when the endpoints +share their largest root. The minimal counterexample is `X` and `-(X ^ 2)`. -/ +theorem not_theorem21CompatibleToRootCountBranchesNonconstantStatement : + ¬ theorem21CompatibleToRootCountBranchesNonconstantStatement := by + intro hforward + have hbase : Compatible (1 : ℝ[X]) (-X) := by + intro α β _hα _hβ + by_cases hzero : C α * (1 : ℝ[X]) + C β * (-X) = 0 + · exact Or.inl hzero + · refine Or.inr (RealRooted.isRealRooted_of_natDegree_le_one hzero ?_) + exact + (Polynomial.natDegree_add_le _ _).trans + (max_le + ((Polynomial.natDegree_C_mul_le α (1 : ℝ[X])).trans (by simp)) + ((Polynomial.natDegree_C_mul_le β (-X : ℝ[X])).trans (by simp))) + have hgsplits : (-(X ^ 2) : ℝ[X]).Splits := by + simpa [pow_two] using + (Polynomial.Splits.X.mul Polynomial.Splits.X).neg + have hcompat : Compatible (X : ℝ[X]) (-(X ^ 2)) := by + simpa [pow_two] using + compatible_mul_common_factor + (d := (X : ℝ[X])) Polynomial.Splits.X hbase + have hsgn : OppositeLeadingSigns (X : ℝ[X]) (-(X ^ 2)) := by + norm_num [OppositeLeadingSigns] + have hfdeg : (X : ℝ[X]).natDegree ≠ 0 := by + simp + have hgdeg : (-(X ^ 2) : ℝ[X]).natDegree ≠ 0 := by + norm_num [Polynomial.natDegree_neg, Polynomial.natDegree_pow] + obtain ⟨r, s, hleft | hright⟩ := + hforward + (f := (X : ℝ[X])) (g := -(X ^ 2)) + Polynomial.Splits.X hgsplits hsgn hfdeg hgdeg hcompat + · have hgap := + hleft.count.natDegree_abs_sub_le_one + (deleteRootFactor_splits_of_isRoot + Polynomial.Splits.X hleft.f_largest.isRoot) + hgsplits + norm_num [natDegree_deleteRootFactor, Polynomial.natDegree_neg, + Polynomial.natDegree_pow] at hgap + · have hr : r = 0 := by + simpa [Polynomial.IsRoot.def] using hright.f_largest.isRoot + have hs : s = 0 := by + simpa [Polynomial.IsRoot.def] using hright.g_largest.isRoot + have hfalse : (0 : ℝ) < 0 := by + simpa [hr, hs] using hright.largest_lt + exact (lt_irrefl 0) hfalse + /-- Reverse half of Liu Theorem 2.1, isolated as a statement target. -/ def theorem21RootCountBranchesToCompatibleStatement : Prop := ∀ {f g : ℝ[X]}, From 6c25cf7e0a3bcf39aaf6eac356cc7213fec2972d Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 18:50:10 +0000 Subject: [PATCH 46/76] Reuse low-degree compatibility in Liu counterexample --- .../LiuOppositeSigns/Theorem21Statements.lean | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/Theorem21Statements.lean b/RealRooted/LiuOppositeSigns/Theorem21Statements.lean index 9e6d073a..fd0679c4 100644 --- a/RealRooted/LiuOppositeSigns/Theorem21Statements.lean +++ b/RealRooted/LiuOppositeSigns/Theorem21Statements.lean @@ -1923,16 +1923,12 @@ share their largest root. The minimal counterexample is `X` and `-(X ^ 2)`. -/ theorem not_theorem21CompatibleToRootCountBranchesNonconstantStatement : ¬ theorem21CompatibleToRootCountBranchesNonconstantStatement := by intro hforward - have hbase : Compatible (1 : ℝ[X]) (-X) := by - intro α β _hα _hβ - by_cases hzero : C α * (1 : ℝ[X]) + C β * (-X) = 0 - · exact Or.inl hzero - · refine Or.inr (RealRooted.isRealRooted_of_natDegree_le_one hzero ?_) - exact - (Polynomial.natDegree_add_le _ _).trans - (max_le - ((Polynomial.natDegree_C_mul_le α (1 : ℝ[X])).trans (by simp)) - ((Polynomial.natDegree_C_mul_le β (-X : ℝ[X])).trans (by simp))) + have hbase : Compatible (1 : ℝ[X]) (-X) := + Compatible.of_allComboRealRooted <| + (allComboRealRooted_of_natDegree_le_one + hasPosLeadingCoeff_one + (by unfold HasPosLeadingCoeff; simp) + (by simp) (by simp)).neg_right have hgsplits : (-(X ^ 2) : ℝ[X]).Splits := by simpa [pow_two] using (Polynomial.Splits.X.mul Polynomial.Splits.X).neg From dbe2e41dc9ad2df2f9e61d2fe9b1ff3f47405199 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:01:05 +0000 Subject: [PATCH 47/76] Prove Liu no-common degree gap --- RealRooted/LiuOppositeSignsTheorem.lean | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index 1be9eaa8..c2ab841a 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -1,5 +1,6 @@ import RealRooted.LiuOppositeSigns.BoundedIntervalContinuity import RealRooted.LiuOppositeSigns.CommonInterleaverConsequences +import RealRooted.LiuOppositeSigns.Corollary22 import RealRooted.LiuOppositeSigns.DerivativeShiftSequenceRegularization import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderAssembly import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderLower @@ -88,6 +89,18 @@ theorem theorem21CompatibleRootCountNoCommonNonconstant : theorem21RootCountBranchesToCompatibleNonconstant_of_xSub hf hg hsgn hf_deg hg_deg⟩ +/-- Compatible no-common nonconstant opposite-sign pairs have degree gap at +most two. -/ +theorem natDegree_abs_sub_le_two_of_compatible_noCommon_nonconstant + {f g : ℝ[X]} (hf : f.Splits) (hg : g.Splits) + (hsgn : OppositeLeadingSigns f g) (hno : NoCommonRoots f g) + (hf_deg : f.natDegree ≠ 0) (hg_deg : g.natDegree ≠ 0) + (hcompat : Compatible f g) : + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := + natDegree_abs_sub_le_two_of_theorem21RootCountBranches hf hg hsgn + (theorem21CompatibleToRootCountBranchesNoCommonNonconstant + hf hg hsgn hno hf_deg hg_deg hcompat) + /-- Correct nonconstant Liu equivalence with common roots retained explicitly. The legacy branch-only equivalence is false when the endpoints share a largest root: the right branch requires a strict largest-root inequality, while deleting From 732b3a18ffea173d7e1ed745f4669b56c90c69a8 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:16:53 +0000 Subject: [PATCH 48/76] Prove odd-degree downward shift obstruction --- RealRooted/ObreschkoffConverse.lean | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 54053d55..d5a1962b 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1635,6 +1635,43 @@ lemma exists_pos_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two /-- Constant shifts eventually destroy real-rootedness once the polynomial has positive leading coefficient and degree at least `2`. -/ +/-- For an odd-degree positive-leading real-rooted polynomial, a sufficiently +large positive downward constant shift is not real-rooted. Reflecting across +the vertical axis and negating preserves the positive leading coefficient in +odd degree and converts the downward shift into a positive upward shift. -/ +lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_odd_natDegree + {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) + (hdeg : 2 ≤ p.natDegree) (hodd : Odd p.natDegree) : + ∃ t : ℝ, 0 < t ∧ ¬ ((p - C t) ≠ 0 ∧ (p - C t).Splits) := by + let q : ℝ[X] := -(p.comp (-X)) + have hpow : (-1 : ℝ) ^ p.natDegree = -1 := hodd.neg_one_pow + have hq_splits : q.Splits := by + dsimp [q] + exact hp_splits.comp_neg_X.neg + have hq_pos : HasPosLeadingCoeff q := by + simpa [q, HasPosLeadingCoeff, hpow] using hp_pos + have hq_natDegree : q.natDegree = p.natDegree := by + dsimp [q] + rw [Polynomial.natDegree_neg, + Polynomial.natDegree_comp_eq_of_mul_ne_zero (by simp [hp_pos.ne_zero])] + simp + obtain ⟨t, ht_pos, ht_bad⟩ := + exists_pos_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two + hq_splits hq_pos (by rw [hq_natDegree]; exact hdeg) + refine ⟨t, ht_pos, ?_⟩ + intro hdown + apply ht_bad + have hcomp_ne : (p - C t).comp (-X) ≠ 0 := by + intro hzero + exact hdown.1 (Polynomial.comp_neg_X_eq_zero_iff.mp hzero) + have hshift : + C t + q = -((p - C t).comp (-X)) := by + dsimp [q] + rw [Polynomial.sub_comp, Polynomial.C_comp] + ring + rw [hshift] + exact ⟨neg_ne_zero.mpr hcomp_ne, hdown.2.comp_neg_X.neg⟩ + lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) (hdeg : 2 ≤ p.natDegree) : From cfb8590ee9b32b6e802ce5642bff0c786f38bfbf Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:20:28 +0000 Subject: [PATCH 49/76] Golf odd-degree shift nonzeroness --- RealRooted/ObreschkoffConverse.lean | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index d5a1962b..853353cb 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1662,8 +1662,7 @@ lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_odd_natDegree intro hdown apply ht_bad have hcomp_ne : (p - C t).comp (-X) ≠ 0 := by - intro hzero - exact hdown.1 (Polynomial.comp_neg_X_eq_zero_iff.mp hzero) + simpa using hdown.1 have hshift : C t + q = -((p - C t).comp (-X)) := by dsimp [q] From 790605ff1b54de529c4f57d59a8335d5c26aa138 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:36:45 +0000 Subject: [PATCH 50/76] Prove nonnegative penultimate critical value --- RealRooted/Derivative.lean | 97 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/RealRooted/Derivative.lean b/RealRooted/Derivative.lean index 2895eb76..7fb69347 100644 --- a/RealRooted/Derivative.lean +++ b/RealRooted/Derivative.lean @@ -638,6 +638,69 @@ lemma mkInterleaving_sub_multiset (f : ℝ[X]) · -- a ≠ s, a ≠ r₁ grind +/-- The recursively selected witness is the penultimate entry of `ss`. +The product statement permits repeated entries and endpoint equality. -/ +private lemma exists_penultimate_listInterlaces_prod_nonneg : + ∀ {ss rs : List ℝ}, + ss.Pairwise (· ≤ ·) → + ListInterlaces ss rs → + 2 ≤ ss.length → + ∃ c ∈ ss, 0 ≤ (rs.map (c - ·)).prod := by + intro ss + induction ss with + | nil => + intro rs _ _ hlen + simp at hlen + | cons s ss ih => + cases ss with + | nil => + intro rs _ _ hlen + simp at hlen + | cons t ts => + intro rs hss hint _ + cases rs with + | nil => + simp [ListInterlaces] at hint + | cons r₁ rs => + cases rs with + | nil => + simp [ListInterlaces] at hint + | cons r₂ rest => + obtain ⟨hr₁s, hsr₂, htail⟩ := hint + by_cases hts : ts = [] + · subst ts + have hrest_len : rest.length = 1 := by + simpa using + (listInterlaces_cons_length_eq htail).symm + obtain ⟨r₃, rfl⟩ := + List.length_eq_one_iff.mp hrest_len + change r₂ ≤ t ∧ t ≤ r₃ ∧ True at htail + obtain ⟨hr₂t, htr₃, _⟩ := htail + refine ⟨s, by simp, ?_⟩ + have h₁ : 0 ≤ s - r₁ := + sub_nonneg.mpr hr₁s + have h₂ : s - r₂ ≤ 0 := + sub_nonpos.mpr hsr₂ + have h₃ : s - r₃ ≤ 0 := + sub_nonpos.mpr + (hsr₂.trans (hr₂t.trans htr₃)) + simpa [mul_assoc] using + mul_nonneg h₁ + (mul_nonneg_of_nonpos_of_nonpos h₂ h₃) + · have htail_len : 2 ≤ (t :: ts).length := by + grind + have hss_cons := List.pairwise_cons.mp hss + obtain ⟨c, hc, hcprod⟩ := + ih hss_cons.2 htail htail_len + have hsc : s ≤ c := + hss_cons.1 c hc + have hr₁c : r₁ ≤ c := + hr₁s.trans hsc + refine + ⟨c, List.mem_cons_of_mem s hc, ?_⟩ + simpa [List.map, List.prod_cons] using + mul_nonneg (sub_nonneg.mpr hr₁c) hcprod + /-! ## Main theorem -/ /-- **Derivative interlacing**: if `f` is real-rooted of degree ≥ 2, @@ -687,6 +750,40 @@ theorem derivative_interlaces {f : ℝ[X]} (hf : f.Splits) (hdeg : 2 ≤ f.natDe exact ⟨⟨by rintro rfl; simp at hf'_ne, hf⟩, hf'_rr, by rw [f.natDegree_derivative]; lia, rs, ss, hrs_sorted, hss_sorted, hrs_multiset, hss_eq, hss_interlaces⟩ +/-- A positive-leading splitting polynomial of degree at least four is +nonnegative at the penultimate derivative-root occurrence. + +Repeated roots are retained: if the selected derivative root is also a root +of `p`, the conclusion is equality. -/ +theorem exists_derivative_root_eval_nonneg_of_four_le_natDegree + {p : ℝ[X]} (hp : p.Splits) (hp_pos : HasPosLeadingCoeff p) + (hdeg : 4 ≤ p.natDegree) : + ∃ c ∈ p.derivative.roots, 0 ≤ p.eval c := by + obtain + ⟨_, hpd, _, rs, ss, _, hss_sorted, hrs_eq, hss_eq, hint⟩ := + derivative_interlaces hp (by lia) + have hss_length : ss.length = p.derivative.natDegree := by + calc + ss.length = (↑ss : Multiset ℝ).card := by simp + _ = p.derivative.roots.card := congrArg Multiset.card hss_eq + _ = p.derivative.natDegree := card_roots_of_splits hpd.2 + have hss_two : 2 ≤ ss.length := by + rw [hss_length, p.natDegree_derivative] + lia + obtain ⟨c, hc, hcprod⟩ := + exists_penultimate_listInterlaces_prod_nonneg + hss_sorted hint hss_two + have hc_roots : c ∈ p.derivative.roots := by + rw [← hss_eq] + exact Multiset.mem_coe.mpr hc + refine ⟨c, hc_roots, ?_⟩ + have heval : + p.eval c = p.leadingCoeff * (rs.map (c - ·)).prod := by + rw [hp.eval_eq_prod_roots c, ← hrs_eq] + rfl + rw [heval] + exact mul_nonneg hp_pos.le hcprod + /-- A nonzero degree-zero real-rooted polynomial precedes a nonzero degree-one real-rooted polynomial. -/ lemma prec_degree_zero_right_of_degree_one From af30c16da1c3a29fa95b1b46df4bf5e6734956ad Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:45:35 +0000 Subject: [PATCH 51/76] Prove downward shift obstruction --- RealRooted/ObreschkoffConverse.lean | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 853353cb..7ba56bc5 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1671,6 +1671,59 @@ lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_odd_natDegree rw [hshift] exact ⟨neg_ne_zero.mpr hcomp_ne, hdown.2.comp_neg_X.neg⟩ +/-- A positive downward constant shift destroys real-rootedness for every +positive-leading real-rooted polynomial of degree at least three. -/ +lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three + {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) + (hdeg : 3 ≤ p.natDegree) : + ∃ t : ℝ, 0 < t ∧ ¬ ((p - C t) ≠ 0 ∧ (p - C t).Splits) := by + classical + by_cases hthree : p.natDegree = 3 + · exact + exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_odd_natDegree + hp_splits hp_pos (by lia) (by norm_num [hthree]) + have hfour : 4 ≤ p.natDegree := by + lia + let t : ℝ := + p.derivative.roots.toFinset.sum (fun c => |p.eval c|) + 1 + have hsum_nonneg : + 0 ≤ p.derivative.roots.toFinset.sum (fun c => |p.eval c|) := + Finset.sum_nonneg fun _ _ => abs_nonneg _ + have ht_pos : 0 < t := by + dsimp [t] + linarith + refine ⟨t, ht_pos, ?_⟩ + intro hshift + have hC_deg : (C t : ℝ[X]).natDegree < p.natDegree := by + simp only [natDegree_C] + lia + have hshift_deg : (p - C t).natDegree = p.natDegree := + natDegree_sub_eq_left_of_natDegree_lt hC_deg + have hshift_pos : HasPosLeadingCoeff (p - C t) := by + unfold HasPosLeadingCoeff at hp_pos ⊢ + rw [leadingCoeff_sub_of_degree_lt (degree_lt_degree hC_deg)] + exact hp_pos + have hshift_four : 4 ≤ (p - C t).natDegree := by + rw [hshift_deg] + exact hfour + obtain ⟨c, hc_derivative, hc_nonneg⟩ := + exists_derivative_root_eval_nonneg_of_four_le_natDegree + hshift.2 hshift_pos hshift_four + have hc_derivative_p : c ∈ p.derivative.roots := by + simpa only [derivative_sub, derivative_C, sub_zero] using hc_derivative + have hc_finset : c ∈ p.derivative.roots.toFinset := + Multiset.mem_toFinset.mpr hc_derivative_p + have hc_bound : + |p.eval c| ≤ + p.derivative.roots.toFinset.sum (fun x => |p.eval x|) := + Finset.single_le_sum + (f := fun x => |p.eval x|) (fun x _ => abs_nonneg _) hc_finset + have hc_neg : (p - C t).eval c < 0 := by + rw [eval_sub, eval_C] + dsimp [t] + linarith [le_abs_self (p.eval c)] + exact (not_lt_of_ge hc_nonneg) hc_neg + lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) (hdeg : 2 ≤ p.natDegree) : From e16919d531dde9c1ad925595f39224f52cefce85 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:46:51 +0000 Subject: [PATCH 52/76] Golf downward shift positivity proof --- RealRooted/ObreschkoffConverse.lean | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 7ba56bc5..2ced1c0a 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1686,12 +1686,9 @@ lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_thr lia let t : ℝ := p.derivative.roots.toFinset.sum (fun c => |p.eval c|) + 1 - have hsum_nonneg : - 0 ≤ p.derivative.roots.toFinset.sum (fun c => |p.eval c|) := - Finset.sum_nonneg fun _ _ => abs_nonneg _ have ht_pos : 0 < t := by dsimp [t] - linarith + positivity refine ⟨t, ht_pos, ?_⟩ intro hshift have hC_deg : (C t : ℝ[X]).natDegree < p.natDegree := by From a558145f3fe85c3c9a46fc8dde772c5341a01601 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 19:58:16 +0000 Subject: [PATCH 53/76] Prove constant endpoint degree bound --- RealRooted/ObreschkoffConverse.lean | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 2ced1c0a..0814c60d 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1721,6 +1721,71 @@ lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_thr linarith [le_abs_self (p.eval c)] exact (not_lt_of_ge hc_nonneg) hc_neg +/-- The constant endpoint omitted by the source formulation of Corollary 2.2: +compatibility with an oppositely signed nonzero constant forces degree at most two. -/ +lemma natDegree_le_two_of_compatible_C_left + {c : ℝ} {p : ℝ[X]} (hp : p.Splits) + (hsgn : OppositeLeadingSigns (C c) p) (hcompat : Compatible (C c) p) : + p.natDegree ≤ 2 := by + by_contra hnot + have hthree : 3 ≤ p.natDegree := by + lia + have shift_ne {q : ℝ[X]} (t : ℝ) (hqdeg : 3 ≤ q.natDegree) : + q - C t ≠ 0 := by + have hdegree : (q - C t).natDegree = q.natDegree := by + apply natDegree_sub_eq_left_of_natDegree_lt + simp only [natDegree_C] + lia + intro hzero + have : q.natDegree = 0 := by + rw [← hdegree, hzero, natDegree_zero] + lia + rcases hsgn.pos_neg_or_neg_pos with hpos | hneg + · have hc_pos : 0 < c := by + simpa [HasPosLeadingCoeff] using hpos.1 + have hneg_three : 3 ≤ (-p).natDegree := by + simpa only [natDegree_neg] using hthree + obtain ⟨t, ht, hshift⟩ := + exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three + hp.neg hpos.2 hneg_three + have hc_ne : c ≠ 0 := ne_of_gt hc_pos + have hq_ne : (-p) - C t ≠ 0 := shift_ne t hneg_three + have hcomb := + hcompat (t / c) 1 (div_nonneg ht.le hc_pos.le) zero_le_one + have hpoly : + C (t / c) * C c + C 1 * p = -((-p) - C t) := by + calc + C (t / c) * C c + C 1 * p = C ((t / c) * c) + p := by + rw [C_mul, C_1, one_mul] + _ = C t + p := by rw [div_mul_cancel₀ t hc_ne] + _ = -((-p) - C t) := by ring + rw [hpoly] at hcomb + rcases hcomb with hzero | hreal + · exact hq_ne (neg_eq_zero.mp hzero) + · exact hshift ⟨hq_ne, by simpa using hreal.2.neg⟩ + · have hc_pos : 0 < -c := by + simpa [HasPosLeadingCoeff] using hneg.1 + obtain ⟨t, ht, hshift⟩ := + exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three + hp hneg.2 hthree + have hc_ne : -c ≠ 0 := ne_of_gt hc_pos + have hq_ne : p - C t ≠ 0 := shift_ne t hthree + have hcomb := + hcompat (t / (-c)) 1 (div_nonneg ht.le hc_pos.le) zero_le_one + have hpoly : C (t / (-c)) * C c + C 1 * p = p - C t := by + have hc' : c ≠ 0 := neg_ne_zero.mp hc_ne + calc + C (t / (-c)) * C c + C 1 * p = C ((t / (-c)) * c) + p := by + rw [C_mul, C_1, one_mul] + _ = C (-t) + p := by + congr 2 + field_simp + _ = p - C t := by simp only [map_neg]; ring + rw [hpoly] at hcomb + rcases hcomb with hzero | hreal + · exact hq_ne hzero + · exact hshift hreal + lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) (hdeg : 2 ≤ p.natDegree) : From ac1b9ae9a5351e597d26cbe0789641ba50cc8ed3 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 20:00:20 +0000 Subject: [PATCH 54/76] Package constant degree endpoints --- RealRooted/ObreschkoffConverse.lean | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 0814c60d..62e61e3b 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1786,6 +1786,46 @@ lemma natDegree_le_two_of_compatible_C_left · exact hq_ne hzero · exact hshift hreal +/-- The right polynomial has degree at most two when the left polynomial is constant. -/ +lemma natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero + {f g : ℝ[X]} (hg : g.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hfdeg : f.natDegree = 0) : + g.natDegree ≤ 2 := by + rw [eq_C_of_natDegree_eq_zero hfdeg] at hsgn hcompat + exact natDegree_le_two_of_compatible_C_left hg hsgn hcompat + +/-- The left polynomial has degree at most two when the right polynomial is constant. -/ +lemma natDegree_left_le_two_of_compatible_of_right_natDegree_eq_zero + {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : + f.natDegree ≤ 2 := + natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero + hf hsgn.symm hcompat.comm hgdeg + +/-- Corollary 2.2's degree bound when the left polynomial is constant. -/ +lemma natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero + {f g : ℝ[X]} (hg : g.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hfdeg : f.natDegree = 0) : + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by + have hle := + natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero + hg hsgn hcompat hfdeg + rw [hfdeg] + norm_num + exact_mod_cast hle + +/-- Corollary 2.2's degree bound when the right polynomial is constant. -/ +lemma natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero + {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by + have hle := + natDegree_left_le_two_of_compatible_of_right_natDegree_eq_zero + hf hsgn hcompat hgdeg + rw [hgdeg] + norm_num + exact_mod_cast hle + lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) (hdeg : 2 ≤ p.natDegree) : From 872eb8a463e08a0bc4226a959b76caec36300919 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 20:01:19 +0000 Subject: [PATCH 55/76] Deduplicate constant endpoint bound --- RealRooted/ObreschkoffConverse.lean | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 62e61e3b..28b2b9b3 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1819,12 +1819,9 @@ lemma natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by - have hle := - natDegree_left_le_two_of_compatible_of_right_natDegree_eq_zero - hf hsgn hcompat hgdeg - rw [hgdeg] - norm_num - exact_mod_cast hle + simpa only [abs_sub_comm] using + natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero + hf hsgn.symm hcompat.comm hgdeg lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) From 18b674e052993c56b906fcb21eef7e34eea4e1b5 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 20:08:27 +0000 Subject: [PATCH 56/76] Prove Liu Corollary 2.2 --- RealRooted/ObreschkoffConverse.lean | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 28b2b9b3..178d4669 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1823,6 +1823,61 @@ lemma natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero hf hsgn.symm hcompat.comm hgdeg +/-- Liu's Corollary 2.2: compatible real-rooted polynomials with opposite +leading signs have degrees differing by at most two. -/ +theorem corollary22DegreeDiff : corollary22DegreeDiffStatement := by + unfold corollary22DegreeDiffStatement + suffices h : + ∀ n : ℕ, ∀ f g : ℝ[X], f.natDegree + g.natDegree = n → + f.Splits → g.Splits → OppositeLeadingSigns f g → Compatible f g → + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 by + intro f g hf hg hsgn hcompat + exact h _ f g rfl hf hg hsgn hcompat + intro n + induction n using Nat.strong_induction_on with + | h n ih => + intro f g hsum hf hg hsgn hcompat + by_cases hfdeg : f.natDegree = 0 + · exact + natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero + hg hsgn hcompat hfdeg + by_cases hgdeg : g.natDegree = 0 + · exact + natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero + hf hsgn hcompat hgdeg + by_cases hno : NoCommonRoots f g + · exact + natDegree_abs_sub_le_two_of_compatible_noCommon_nonconstant + hf hg hsgn hno hfdeg hgdeg hcompat + obtain ⟨r, hfr, hgr⟩ := exists_common_root_of_not_noCommonRoots hno + let f' : ℝ[X] := deleteRootFactor f r + let g' : ℝ[X] := deleteRootFactor g r + have hf'_splits : f'.Splits := + deleteRootFactor_splits_of_isRoot hf hfr + have hg'_splits : g'.Splits := + deleteRootFactor_splits_of_isRoot hg hgr + have hsgn' : OppositeLeadingSigns f' g' := by + simpa [f', g'] using + (hsgn.deleteRootFactor_left hfr).deleteRootFactor_right hgr + have hcompat' : Compatible f' g' := + compatible_deleteRootFactor_of_common_root hcompat hfr hgr + have hf'_degree : f'.natDegree = f.natDegree - 1 := + natDegree_deleteRootFactor f r + have hg'_degree : g'.natDegree = g.natDegree - 1 := + natDegree_deleteRootFactor g r + have hf_pos : 0 < f.natDegree := Nat.pos_of_ne_zero hfdeg + have hg_pos : 0 < g.natDegree := Nat.pos_of_ne_zero hgdeg + have hlt : f'.natDegree + g'.natDegree < n := by + rw [hf'_degree, hg'_degree, ← hsum] + lia + have hrec := + ih (f'.natDegree + g'.natDegree) hlt f' g' rfl + hf'_splits hg'_splits hsgn' hcompat' + rw [hf'_degree, hg'_degree] at hrec + rw [Nat.cast_sub hf_pos, Nat.cast_sub hg_pos] at hrec + norm_num at hrec + simpa only [sub_sub_sub_cancel_right] using hrec + lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) (hdeg : 2 ≤ p.natDegree) : From b9b6b9a4ee07eb6c5e01e875b6204405c38c39cc Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 20:24:16 +0000 Subject: [PATCH 57/76] Remove duplicate multiset relation helpers --- .../DerivativeShiftRegularization.lean | 20 +-- ...DerivativeShiftSequenceRegularization.lean | 4 +- .../RootCountRelStability.lean | 8 +- RealRooted/Mathlib/Data/Multiset/Rel.lean | 123 ------------------ RealRooted/RootMultiplicityMatching.lean | 15 --- 5 files changed, 19 insertions(+), 151 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean index 143c7361..cc1bd90c 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean @@ -32,14 +32,18 @@ theorem NoCommonRoots.exists_delta_TDeriv exists_delta_roots_rel_TDeriv hg hη_pos refine ⟨min δf δg, lt_min hδf_pos hδg_pos, ?_⟩ intro eps heps_pos hepsδ x hfx hgx - obtain ⟨a, ha_mem, hax⟩ := - Multiset.Rel.exists_left_of_mem_right - (hfrel heps_pos (hepsδ.trans_le (min_le_left _ _))) - ((Polynomial.mem_roots (TDeriv_ne_zero hf_ne)).mpr hfx) - obtain ⟨b, hb_mem, hbx⟩ := - Multiset.Rel.exists_left_of_mem_right - (hgrel heps_pos (hepsδ.trans_le (min_le_right _ _))) - ((Polynomial.mem_roots (TDeriv_ne_zero hg_ne)).mpr hgx) + obtain ⟨a, ha_mem, hax⟩ := by + simpa only [Function.flip_def] using + Multiset.exists_mem_of_rel_of_mem + ((Multiset.rel_flip).2 + (hfrel heps_pos (hepsδ.trans_le (min_le_left _ _)))) + ((Polynomial.mem_roots (TDeriv_ne_zero hf_ne)).mpr hfx) + obtain ⟨b, hb_mem, hbx⟩ := by + simpa only [Function.flip_def] using + Multiset.exists_mem_of_rel_of_mem + ((Multiset.rel_flip).2 + (hgrel heps_pos (hepsδ.trans_le (min_le_right _ _)))) + ((Polynomial.mem_roots (TDeriv_ne_zero hg_ne)).mpr hgx) have hab_ne : a ≠ b := by rintro rfl exact (hno a ((Polynomial.mem_roots hf_ne).mp ha_mem)) diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean index ea0b79bc..161eb538 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean @@ -90,10 +90,10 @@ theorem NoCommonRoots.exists_applyTDerivList_roots_rel induction n generalizing f g ρ with | zero => refine ⟨[], rfl, by simp, by simpa using hcomp, by simpa using hno, ?_, ?_⟩ - · apply Multiset.Rel.diag + · apply Multiset.rel_refl_of_refl_on intro r hr simpa using hρ - · apply Multiset.Rel.diag + · apply Multiset.rel_refl_of_refl_on intro r hr simpa using hρ | succ n ih => diff --git a/RealRooted/LiuOppositeSigns/RootCountRelStability.lean b/RealRooted/LiuOppositeSigns/RootCountRelStability.lean index ab96018f..464ba32a 100644 --- a/RealRooted/LiuOppositeSigns/RootCountRelStability.lean +++ b/RealRooted/LiuOppositeSigns/RootCountRelStability.lean @@ -44,9 +44,11 @@ theorem IsLargestRoot.abs_sub_lt_of_roots_rel |s - r| < δ := by have hr_mem : r ∈ p.roots := (Polynomial.mem_roots hp_ne).mpr hr.isRoot have hs_mem : s ∈ q.roots := (Polynomial.mem_roots hq_ne).mpr hs.isRoot - obtain ⟨b, hb_mem, hrb⟩ := hmatch.exists_right_of_mem_left hr_mem - obtain ⟨a, ha_mem, has⟩ := - Multiset.Rel.exists_left_of_mem_right hmatch hs_mem + obtain ⟨b, hb_mem, hrb⟩ := + Multiset.exists_mem_of_rel_of_mem hmatch hr_mem + obtain ⟨a, ha_mem, has⟩ := by + simpa only [Function.flip_def] using + Multiset.exists_mem_of_rel_of_mem ((Multiset.rel_flip).2 hmatch) hs_mem have hb_le : b ≤ s := hs.roots_le b hb_mem have ha_le : a ≤ r := hr.roots_le a ha_mem rw [abs_lt] diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 014864e7..832adeea 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -12,15 +12,6 @@ heterogeneous composition. It is intended for upstreaming to namespace Multiset -/-- Lift a pointwise reflexivity proof to a multiset relation. -/ -theorem Rel.diag {α : Type*} {R : α → α → Prop} {s : Multiset α} - (h : ∀ a ∈ s, R a a) : Multiset.Rel R s s := by - induction s using Multiset.induction_on with - | empty => exact Rel.zero - | cons a s ih => - exact Rel.cons (h a (by simp)) - (ih fun b hb => h b (by simp [hb])) - /-- Compose multiset relations whose element relations may differ. -/ theorem Rel.comp {α β γ : Type*} {R : α → β → Prop} {S : β → γ → Prop} {T : α → γ → Prop} @@ -40,118 +31,4 @@ theorem Rel.comp {α β γ : Type*} obtain ⟨c, zs, hbc, h₂, rfl⟩ := Multiset.rel_cons_left.mp h₂ exact Rel.cons (hcomp a b c hab hbc) (ih h₁ h₂) -/-- Every element on the left of a multiset relation has a related element on the right. -/ -theorem Rel.exists_right_of_mem_left {α β : Type*} {R : α → β → Prop} - {s : Multiset α} {t : Multiset β} (hrel : Multiset.Rel R s t) - {a : α} (ha : a ∈ s) : ∃ b ∈ t, R a b := by - induction s using Multiset.induction_on generalizing t with - | empty => simp at ha - | cons a' s ih => - obtain ⟨b, t, hab, hrel, rfl⟩ := Multiset.rel_cons_left.mp hrel - rw [Multiset.mem_cons] at ha - rcases ha with rfl | ha - · exact ⟨b, by simp, hab⟩ - · obtain ⟨c, hc, hac⟩ := ih hrel ha - exact ⟨c, by simp [hc], hac⟩ - -/-- Related multisets have equally many elements satisfying corresponding predicates. -/ -theorem Rel.card_filter_eq {α β : Type*} {R : α → β → Prop} - {s : Multiset α} {t : Multiset β} (hrel : Multiset.Rel R s t) - (P : α → Prop) (Q : β → Prop) [DecidablePred P] [DecidablePred Q] - (hpred : ∀ a ∈ s, ∀ b ∈ t, R a b → (P a ↔ Q b)) : - (s.filter P).card = (t.filter Q).card := by - induction hrel with - | zero => simp - | @cons a b s t hab hrel ih => - have hpq : P a ↔ Q b := hpred a (by simp) b (by simp) hab - have htail : ∀ c ∈ s, ∀ d ∈ t, R c d → (P c ↔ Q d) := by - intro c hc d hd hcd - exact hpred c (by simp [hc]) d (by simp [hd]) hcd - by_cases ha : P a - · have hb : Q b := hpq.mp ha - simp [ha, hb, ih htail] - · have hb : ¬Q b := fun hb => ha (hpq.mpr hb) - simp [ha, hb, ih htail] - -private theorem rel_uncross_min_cons {α : Type*} [LinearOrder α] - {R : α → α → Prop} {a b : α} {as bs : Multiset α} - (huncross : ∀ {a c b d}, a ≤ c → b ≤ d → R a d → R c b → - R a b ∧ R c d) - (ha : ∀ c ∈ as, a ≤ c) (hb : ∀ d ∈ bs, b ≤ d) - (hrel : Multiset.Rel R (a ::ₘ as) (b ::ₘ bs)) : - R a b ∧ Multiset.Rel R as bs := by - obtain ⟨d, ds, hdb, hds, heq⟩ := Multiset.rel_cons_right.mp hrel - rcases Multiset.cons_eq_cons.mp heq with - (⟨rfl, rfl⟩ | ⟨_, cs, rfl, rfl⟩) - · exact ⟨hdb, hds⟩ - · obtain ⟨c, ts, hac, hct, rfl⟩ := Multiset.rel_cons_left.mp hds - have had : a ≤ d := ha d (by simp) - have hbc : b ≤ c := hb c (by simp) - obtain ⟨hab, hdc⟩ := huncross had hbc hac hdb - exact ⟨hab, Multiset.Rel.cons hdc hct⟩ - -/-- An uncrossable multiset relation pairs the corresponding sorted elements. -/ -theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop} - (huncross : ∀ {a c b d}, a ≤ c → b ≤ d → R a d → R c b → - R a b ∧ R c d) - {s t : Multiset α} (hrel : Multiset.Rel R s t) : - List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by - revert t - apply Multiset.strongInductionOn s - intro s ih t hrel - cases hs : s.sort (· ≤ ·) with - | nil => - have hs0 : s = 0 := by - calc - s = ↑(s.sort (· ≤ ·)) := - (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm - _ = 0 := by simp [hs] - subst s - have ht0 : t = 0 := Multiset.rel_zero_left.mp hrel - subst t - simp - | cons a as => - have hsrepr : s = a ::ₘ (as : Multiset α) := by - calc - s = ↑(s.sort (· ≤ ·)) := - (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm - _ = a ::ₘ (as : Multiset α) := by rw [hs] - have hsord : (a :: as).Pairwise (· ≤ ·) := by - simpa [hs] using - (Multiset.pairwise_sort (s := s) (r := (· ≤ ·))) - have ha : ∀ c ∈ (as : Multiset α), a ≤ c := by - intro c hc - exact (List.pairwise_cons.mp hsord).1 c (by simpa using hc) - cases ht : t.sort (· ≤ ·) with - | nil => - have ht0 : t = 0 := by - calc - t = ↑(t.sort (· ≤ ·)) := - (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm - _ = 0 := by simp [ht] - subst t - have hs0 : s = 0 := Multiset.rel_zero_right.mp hrel - have : False := by simpa [hsrepr] using hs0 - exact this.elim - | cons b bs => - have htrepr : t = b ::ₘ (bs : Multiset α) := by - calc - t = ↑(t.sort (· ≤ ·)) := - (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm - _ = b ::ₘ (bs : Multiset α) := by rw [ht] - have htord : (b :: bs).Pairwise (· ≤ ·) := by - simpa [ht] using - (Multiset.pairwise_sort (s := t) (r := (· ≤ ·))) - have hb : ∀ d ∈ (bs : Multiset α), b ≤ d := by - intro d hd - exact (List.pairwise_cons.mp htord).1 d (by simpa using hd) - rw [hsrepr, htrepr] at hrel - obtain ⟨hab, htail⟩ := rel_uncross_min_cons huncross ha hb hrel - have hsmall : (as : Multiset α) < s := by - rw [hsrepr] - exact Multiset.lt_cons_self _ _ - have ih_tail := ih (as : Multiset α) hsmall (bs : Multiset α) htail - rw [hsrepr, htrepr, Multiset.sort_cons ha, Multiset.sort_cons hb] - exact List.Forall₂.cons hab ih_tail - end Multiset diff --git a/RealRooted/RootMultiplicityMatching.lean b/RealRooted/RootMultiplicityMatching.lean index 1210bebe..62e648d4 100644 --- a/RealRooted/RootMultiplicityMatching.lean +++ b/RealRooted/RootMultiplicityMatching.lean @@ -142,21 +142,6 @@ theorem exists_rel_le_of_forall_le_count {s t : Multiset ℝ} {δ : ℝ} · exact le_trans (Finset.sum_le_sum fun a ha => (hcluster a ha).1) (sum_filter_ball_le hsep) -/-- Every target member in a lifted multiset relation has a related source -member. -/ -theorem Rel.exists_left_of_mem_right - {α β : Type*} {r : α → β → Prop} {s : Multiset α} {t : Multiset β} - (hrel : Rel r s t) {b : β} (hb : b ∈ t) : - ∃ a ∈ s, r a b := by - induction hrel with - | zero => simp at hb - | @cons a b s t hab _ ih => - rw [mem_cons] at hb - rcases hb with rfl | hb - · exact ⟨a, mem_cons_self, hab⟩ - · obtain ⟨a', ha', ha'b⟩ := ih hb - exact ⟨a', mem_cons_of_mem ha', ha'b⟩ - /-- Local multiplicity lower bounds give a full proximity matching when the source and target multisets have the same cardinality. -/ theorem rel_of_forall_le_count_of_card_eq {s t : Multiset ℝ} {δ : ℝ} From 67a654a314cbdea22d1082221f99ea5d2e47378b Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Sun, 2 Aug 2026 20:26:38 +0000 Subject: [PATCH 58/76] Restore multiset relation infrastructure --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 832adeea..2495b8cd 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -31,4 +31,104 @@ theorem Rel.comp {α β γ : Type*} obtain ⟨c, zs, hbc, h₂, rfl⟩ := Multiset.rel_cons_left.mp h₂ exact Rel.cons (hcomp a b c hab hbc) (ih h₁ h₂) +/-- Related multisets have equally many elements satisfying corresponding predicates. -/ +theorem Rel.card_filter_eq {α β : Type*} {R : α → β → Prop} + {s : Multiset α} {t : Multiset β} (hrel : Multiset.Rel R s t) + (P : α → Prop) (Q : β → Prop) [DecidablePred P] [DecidablePred Q] + (hpred : ∀ a ∈ s, ∀ b ∈ t, R a b → (P a ↔ Q b)) : + (s.filter P).card = (t.filter Q).card := by + induction hrel with + | zero => simp + | @cons a b s t hab hrel ih => + have hpq : P a ↔ Q b := hpred a (by simp) b (by simp) hab + have htail : ∀ c ∈ s, ∀ d ∈ t, R c d → (P c ↔ Q d) := by + intro c hc d hd hcd + exact hpred c (by simp [hc]) d (by simp [hd]) hcd + by_cases ha : P a + · have hb : Q b := hpq.mp ha + simp [ha, hb, ih htail] + · have hb : ¬Q b := fun hb => ha (hpq.mpr hb) + simp [ha, hb, ih htail] + +private theorem rel_uncross_min_cons {α : Type*} [LinearOrder α] + {R : α → α → Prop} {a b : α} {as bs : Multiset α} + (huncross : ∀ {a c b d}, a ≤ c → b ≤ d → R a d → R c b → + R a b ∧ R c d) + (ha : ∀ c ∈ as, a ≤ c) (hb : ∀ d ∈ bs, b ≤ d) + (hrel : Multiset.Rel R (a ::ₘ as) (b ::ₘ bs)) : + R a b ∧ Multiset.Rel R as bs := by + obtain ⟨d, ds, hdb, hds, heq⟩ := Multiset.rel_cons_right.mp hrel + rcases Multiset.cons_eq_cons.mp heq with + (⟨rfl, rfl⟩ | ⟨_, cs, rfl, rfl⟩) + · exact ⟨hdb, hds⟩ + · obtain ⟨c, ts, hac, hct, rfl⟩ := Multiset.rel_cons_left.mp hds + have had : a ≤ d := ha d (by simp) + have hbc : b ≤ c := hb c (by simp) + obtain ⟨hab, hdc⟩ := huncross had hbc hac hdb + exact ⟨hab, Multiset.Rel.cons hdc hct⟩ + +/-- An uncrossable multiset relation pairs the corresponding sorted elements. -/ +theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop} + (huncross : ∀ {a c b d}, a ≤ c → b ≤ d → R a d → R c b → + R a b ∧ R c d) + {s t : Multiset α} (hrel : Multiset.Rel R s t) : + List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by + revert t + apply Multiset.strongInductionOn s + intro s ih t hrel + cases hs : s.sort (· ≤ ·) with + | nil => + have hs0 : s = 0 := by + calc + s = ↑(s.sort (· ≤ ·)) := + (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm + _ = 0 := by simp [hs] + subst s + have ht0 : t = 0 := Multiset.rel_zero_left.mp hrel + subst t + simp + | cons a as => + have hsrepr : s = a ::ₘ (as : Multiset α) := by + calc + s = ↑(s.sort (· ≤ ·)) := + (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm + _ = a ::ₘ (as : Multiset α) := by rw [hs] + have hsord : (a :: as).Pairwise (· ≤ ·) := by + simpa [hs] using + (Multiset.pairwise_sort (s := s) (r := (· ≤ ·))) + have ha : ∀ c ∈ (as : Multiset α), a ≤ c := by + intro c hc + exact (List.pairwise_cons.mp hsord).1 c (by simpa using hc) + cases ht : t.sort (· ≤ ·) with + | nil => + have ht0 : t = 0 := by + calc + t = ↑(t.sort (· ≤ ·)) := + (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm + _ = 0 := by simp [ht] + subst t + have hs0 : s = 0 := Multiset.rel_zero_right.mp hrel + have : False := by simpa [hsrepr] using hs0 + exact this.elim + | cons b bs => + have htrepr : t = b ::ₘ (bs : Multiset α) := by + calc + t = ↑(t.sort (· ≤ ·)) := + (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm + _ = b ::ₘ (bs : Multiset α) := by rw [ht] + have htord : (b :: bs).Pairwise (· ≤ ·) := by + simpa [ht] using + (Multiset.pairwise_sort (s := t) (r := (· ≤ ·))) + have hb : ∀ d ∈ (bs : Multiset α), b ≤ d := by + intro d hd + exact (List.pairwise_cons.mp htord).1 d (by simpa using hd) + rw [hsrepr, htrepr] at hrel + obtain ⟨hab, htail⟩ := rel_uncross_min_cons huncross ha hb hrel + have hsmall : (as : Multiset α) < s := by + rw [hsrepr] + exact Multiset.lt_cons_self _ _ + have ih_tail := ih (as : Multiset α) hsmall (bs : Multiset α) htail + rw [hsrepr, htrepr, Multiset.sort_cons ha, Multiset.sort_cons hb] + exact List.Forall₂.cons hab ih_tail + end Multiset From c07bfc1cbd2bb6b3b108243fa4ed609904b0f431 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:28:30 +0000 Subject: [PATCH 59/76] Repair Liu constant-shift proof block --- RealRooted/ObreschkoffConverse.lean | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 178d4669..7fe5aef9 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1633,8 +1633,6 @@ lemma exists_pos_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two simp_all linarith -/-- Constant shifts eventually destroy real-rootedness once the polynomial has -positive leading coefficient and degree at least `2`. -/ /-- For an odd-degree positive-leading real-rooted polynomial, a sufficiently large positive downward constant shift is not real-rooted. Reflecting across the vertical axis and negating preserves the positive leading coefficient in @@ -1662,7 +1660,8 @@ lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_odd_natDegree intro hdown apply ht_bad have hcomp_ne : (p - C t).comp (-X) ≠ 0 := by - simpa using hdown.1 + rw [ne_eq, Polynomial.comp_eq_zero_iff] + simp [hdown.1] have hshift : C t + q = -((p - C t).comp (-X)) := by dsimp [q] From c2702d642d49305e08e5542b9c6d49289d7c05c3 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:32:44 +0000 Subject: [PATCH 60/76] chore: remove redundant derivative-shift simp argument --- RealRooted/DerivativeShiftRootMatching.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RealRooted/DerivativeShiftRootMatching.lean b/RealRooted/DerivativeShiftRootMatching.lean index 057abe42..8c2deb74 100644 --- a/RealRooted/DerivativeShiftRootMatching.lean +++ b/RealRooted/DerivativeShiftRootMatching.lean @@ -39,7 +39,7 @@ theorem exists_delta_roots_rel_TDeriv have hdeg : (p + C eps * (-p.derivative)).natDegree = (p + C (0 : ℝ) * (-p.derivative)).natDegree := by - simp [TDeriv] + simp have hcount := hlocal eps heps_abs hsplit hdeg have hcount' : ∀ a ∈ p.roots.toFinset, p.roots.count a ≤ From f2b58f3a7659af8c63032adef26df0723b69dd59 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:34:52 +0000 Subject: [PATCH 61/76] fix: import multiset strong induction --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 1 + 1 file changed, 1 insertion(+) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 2495b8cd..56086be0 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -1,4 +1,5 @@ import Mathlib.Data.List.Forall2 +import Mathlib.Data.Multiset.Basic import Mathlib.Data.Multiset.Sort import Mathlib.Data.Multiset.ZeroCons From e90d393d3525c62792f39f9f35a350e0b9a96004 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:37:48 +0000 Subject: [PATCH 62/76] fix: avoid root-order reverse-index timeout --- RealRooted/RootOrderBridge.lean | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RealRooted/RootOrderBridge.lean b/RealRooted/RootOrderBridge.lean index 75125ec1..ab5a1979 100644 --- a/RealRooted/RootOrderBridge.lean +++ b/RealRooted/RootOrderBridge.lean @@ -198,7 +198,11 @@ theorem count_le_two_of_succRootCrossing have h_reverse_getD_succ (l : List ℝ) (hl : l.length = d + 1) (i : ℕ) (hi : i < d + 1) : l.reverse.getD i 0 = l[d - i]! := by - simp_all + simp only [List.getD_eq_getElem?_getD, List.length_reverse, hl, + getElem?_pos, hi, List.getElem_reverse, Option.getD_some] + have hidx : d + 1 - 1 - i = d - i := by lia + have hdi : d - i < l.length := by lia + simp only [hidx, getElem!_pos, hdi] have hint1 : ∀ i, i < d → sN[i]! ≤ sM[i]! := by grind have hint2 : ∀ i, i + 1 < d → sM[i]! ≤ sN[i + 2]! := by From 2f263fc1686cdeaaa2fef35f18ab87dcbd1cd64a Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:38:57 +0000 Subject: [PATCH 63/76] refactor: simplify root-order length facts --- RealRooted/RootOrderBridge.lean | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/RealRooted/RootOrderBridge.lean b/RealRooted/RootOrderBridge.lean index ab5a1979..f5de1da5 100644 --- a/RealRooted/RootOrderBridge.lean +++ b/RealRooted/RootOrderBridge.lean @@ -180,15 +180,9 @@ theorem count_le_two_of_succRootCrossing have hM_sorted : sM.Pairwise (· ≤ ·) := Multiset.pairwise_sort _ _ have hN_sorted : sN.Pairwise (· ≤ ·) := Multiset.pairwise_sort _ _ have hsM : sM.length = d := by - subst hM - simp_all only [ - tsub_le_iff_right, zero_add, Nat.cast_le, - Multiset.pairwise_sort, Multiset.length_sort, sM, sN] + simpa [sM] using hM have hsN : sN.length = d + 1 := by - subst hM - simp_all only [ - tsub_le_iff_right, zero_add, Nat.cast_le, - Multiset.pairwise_sort, Multiset.length_sort, sM, sN] + simpa [sN] using hN have hsM_eq : Multiset.ofList sM = M := Multiset.sort_eq M (· ≤ ·) have hsN_eq : Multiset.ofList sN = N := Multiset.sort_eq N (· ≤ ·) have h_helper := sorted_getElem_le_iff_lt_card_filter From b8f324669b2277c41b23cd1f251f26c224ff18fd Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:40:49 +0000 Subject: [PATCH 64/76] fix: expose derivative-shift declarations --- RealRooted/DerivativeShiftSequence.lean | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RealRooted/DerivativeShiftSequence.lean b/RealRooted/DerivativeShiftSequence.lean index 56f1c8dd..18c6986c 100644 --- a/RealRooted/DerivativeShiftSequence.lean +++ b/RealRooted/DerivativeShiftSequence.lean @@ -13,6 +13,8 @@ simple roots. Its proof follows the multiplicity descent used for fixed iterations, tracing a hypothetical multiple root backward through the list. -/ +open RealRooted + namespace Polynomial noncomputable section From 7da47c41556f17a6b786a32763734747c98b1e01 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:43:34 +0000 Subject: [PATCH 65/76] fix: normalize derivative-shift root transport --- RealRooted/DerivativeShiftRootMatching.lean | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/RealRooted/DerivativeShiftRootMatching.lean b/RealRooted/DerivativeShiftRootMatching.lean index 8c2deb74..c7ce40b0 100644 --- a/RealRooted/DerivativeShiftRootMatching.lean +++ b/RealRooted/DerivativeShiftRootMatching.lean @@ -25,6 +25,9 @@ theorem exists_delta_roots_rel_TDeriv Multiset.Rel (fun r q ↦ |q - r| < ρ) p.roots (TDeriv eps p).roots := by obtain ⟨η, hη_pos, hηρ, hsep⟩ := Multiset.exists_pos_lt_and_two_mul_le_abs_sub_toFinset p.roots hρ + have hshift (nu : ℝ) : + p + C nu * (-p.derivative) = TDeriv nu p := by + simp only [TDeriv, sub_eq_add_neg, mul_neg] have hp0 : (p + C (0 : ℝ) * (-p.derivative)).Splits := by simpa using hp obtain ⟨δ, hδ_pos, hlocal⟩ := @@ -35,12 +38,14 @@ theorem exists_delta_roots_rel_TDeriv have heps_abs : |eps - 0| < δ := by simpa [abs_of_pos heps_pos] using hepsδ have hsplit : (p + C eps * (-p.derivative)).Splits := by - simpa [TDeriv] using splits_tderiv heps_pos hp + rw [hshift] + exact splits_tderiv heps_pos hp have hdeg : (p + C eps * (-p.derivative)).natDegree = (p + C (0 : ℝ) * (-p.derivative)).natDegree := by - simp + rw [hshift eps, hshift 0, natDegree_TDeriv, natDegree_TDeriv] have hcount := hlocal eps heps_abs hsplit hdeg + rw [hshift eps, hshift 0] at hcount have hcount' : ∀ a ∈ p.roots.toFinset, p.roots.count a ≤ ((TDeriv eps p).roots.filter (fun q ↦ |q - a| < η)).card := by @@ -50,6 +55,6 @@ theorem exists_delta_roots_rel_TDeriv card_roots_of_splits hp] have hrel := Multiset.rel_of_forall_le_count_of_card_eq hsep hcount' hcard - exact hrel.mono fun _ _ hclose ↦ lt_trans hclose hηρ + exact hrel.mono fun _ _ _ _ hclose ↦ lt_trans hclose hηρ end RealRooted From 0a49c23532ff50f72f125468704b272e957392be Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 07:45:06 +0000 Subject: [PATCH 66/76] refactor: simplify derivative-shift tail positivity --- RealRooted/DerivativeShiftSequence.lean | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/RealRooted/DerivativeShiftSequence.lean b/RealRooted/DerivativeShiftSequence.lean index 18c6986c..9b990e20 100644 --- a/RealRooted/DerivativeShiftSequence.lean +++ b/RealRooted/DerivativeShiftSequence.lean @@ -47,9 +47,8 @@ theorem Splits.applyTDerivList {epss : List ℝ} {p : ℝ[X]} | nil => simpa | cons eps epss ih => have heps : 0 < eps := hpos eps (by simp) - have htail : ∀ eta ∈ epss, 0 < eta := by - intro eta heta - exact hpos eta (by simp [heta]) + have htail : ∀ eta ∈ epss, 0 < eta := + fun eta heta ↦ hpos eta (List.mem_cons_of_mem eps heta) exact ih (splits_tderiv heps hp) htail /-- A finite sequence of derivative shifts preserves natural degree. -/ @@ -81,9 +80,8 @@ theorem rootMultiplicity_applyTDerivList_eq_add_length_of_ge_two | nil => simp | cons eps epss ih => have heps : 0 < eps := hpos eps (by simp) - have htail : ∀ eta ∈ epss, 0 < eta := by - intro eta heta - exact hpos eta (by simp [heta]) + have htail : ∀ eta ∈ epss, 0 < eta := + fun eta heta ↦ hpos eta (List.mem_cons_of_mem eps heta) have hTsplit : (TDeriv eps p).Splits := splits_tderiv heps hp have htail_eq := ih htail hTsplit hm have hTmult : 2 ≤ (TDeriv eps p).rootMultiplicity a := by From b05368861166d4c79af92f735a60565755f50638 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 08:13:57 +0000 Subject: [PATCH 67/76] Repair multiset relation eliminators --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 56086be0..1b708306 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -28,9 +28,11 @@ theorem Rel.comp {α β γ : Type*} subst zs exact Rel.zero | cons b ys ih => - obtain ⟨a, xs, hab, h₁, rfl⟩ := Multiset.rel_cons_right.mp h₁ - obtain ⟨c, zs, hbc, h₂, rfl⟩ := Multiset.rel_cons_left.mp h₂ - exact Rel.cons (hcomp a b c hab hbc) (ih h₁ h₂) + obtain ⟨a, xs', hab, htail₁, hxs⟩ := Multiset.rel_cons_right.mp h₁ + obtain ⟨c, zs', hbc, htail₂, hzs⟩ := Multiset.rel_cons_left.mp h₂ + subst xs + subst zs + exact Rel.cons (hcomp a b c hab hbc) (ih htail₁ htail₂) /-- Related multisets have equally many elements satisfying corresponding predicates. -/ theorem Rel.card_filter_eq {α β : Type*} {R : α → β → Prop} @@ -59,10 +61,13 @@ private theorem rel_uncross_min_cons {α : Type*} [LinearOrder α] (hrel : Multiset.Rel R (a ::ₘ as) (b ::ₘ bs)) : R a b ∧ Multiset.Rel R as bs := by obtain ⟨d, ds, hdb, hds, heq⟩ := Multiset.rel_cons_right.mp hrel - rcases Multiset.cons_eq_cons.mp heq with - (⟨rfl, rfl⟩ | ⟨_, cs, rfl, rfl⟩) - · exact ⟨hdb, hds⟩ - · obtain ⟨c, ts, hac, hct, rfl⟩ := Multiset.rel_cons_left.mp hds + rcases Multiset.cons_eq_cons.mp heq with hsame | hswap + · rcases hsame with ⟨rfl, rfl⟩ + exact ⟨hdb, hds⟩ + · rcases hswap with ⟨_, cs, has, hds'⟩ + subst as + subst ds + obtain ⟨c, ts, hac, hct, rfl⟩ := Multiset.rel_cons_left.mp hds have had : a ≤ d := ha d (by simp) have hbc : b ≤ c := hb c (by simp) obtain ⟨hab, hdc⟩ := huncross had hbc hac hdb From 98082647c2347e39f3f6dadcef4541f1b0f680b3 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 08:15:52 +0000 Subject: [PATCH 68/76] Golf multiset relation composition --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 1b708306..b49f9672 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -28,10 +28,8 @@ theorem Rel.comp {α β γ : Type*} subst zs exact Rel.zero | cons b ys ih => - obtain ⟨a, xs', hab, htail₁, hxs⟩ := Multiset.rel_cons_right.mp h₁ - obtain ⟨c, zs', hbc, htail₂, hzs⟩ := Multiset.rel_cons_left.mp h₂ - subst xs - subst zs + obtain ⟨a, xs', hab, htail₁, rfl⟩ := Multiset.rel_cons_right.mp h₁ + obtain ⟨c, zs', hbc, htail₂, rfl⟩ := Multiset.rel_cons_left.mp h₂ exact Rel.cons (hcomp a b c hab hbc) (ih htail₁ htail₂) /-- Related multisets have equally many elements satisfying corresponding predicates. -/ From 198878a9e3aa77d6fb6271adc3d8a472aab38edd Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 08:50:55 +0000 Subject: [PATCH 69/76] Repair Liu comparator dependencies --- RealRooted/DerivativeShiftSequence.lean | 6 +- RealRooted/LiuOppositeSignsTheorem.lean | 160 ++++++++++++++++++++++ RealRooted/Mathlib/Data/Multiset/Rel.lean | 4 +- RealRooted/ObreschkoffConverse.lean | 156 --------------------- RealRooted/RootOrderBridge.lean | 20 ++- 5 files changed, 187 insertions(+), 159 deletions(-) diff --git a/RealRooted/DerivativeShiftSequence.lean b/RealRooted/DerivativeShiftSequence.lean index 9b990e20..10f561f0 100644 --- a/RealRooted/DerivativeShiftSequence.lean +++ b/RealRooted/DerivativeShiftSequence.lean @@ -84,8 +84,12 @@ theorem rootMultiplicity_applyTDerivList_eq_add_length_of_ge_two fun eta heta ↦ hpos eta (List.mem_cons_of_mem eps heta) have hTsplit : (TDeriv eps p).Splits := splits_tderiv heps hp have htail_eq := ih htail hTsplit hm + have hm' : + 2 ≤ (applyTDerivList epss (TDeriv eps p)).rootMultiplicity a := by + simpa only [applyTDerivList_cons] using hm have hTmult : 2 ≤ (TDeriv eps p).rootMultiplicity a := by - lia + rw [htail_eq] + exact le_trans hm' (Nat.le_add_right _ _) have hstep := rootMultiplicity_eq_succ_of_TDeriv_ge_two heps hp hTmult simp only [applyTDerivList_cons, List.length_cons] lia diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index c2ab841a..9f0f32cf 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -8,6 +8,7 @@ import RealRooted.LiuOppositeSigns.ForwardCubicQuadratic.RootOrderUpper import RealRooted.LiuOppositeSigns.RootCountClosure import RealRooted.LiuOppositeSigns.Theorem21Assembly import RealRooted.LiuOppositeSigns.XSub.IntervalRootCount +import RealRooted.ObreschkoffConverse /-! # Liu opposite-sign compatibility theorem @@ -440,5 +441,164 @@ theorem hforward) hcases + +/-- The constant endpoint omitted by the source formulation of Corollary 2.2: +compatibility with an oppositely signed nonzero constant forces degree at most two. -/ +lemma natDegree_le_two_of_compatible_C_left + {c : ℝ} {p : ℝ[X]} (hp : p.Splits) + (hsgn : OppositeLeadingSigns (C c) p) (hcompat : Compatible (C c) p) : + p.natDegree ≤ 2 := by + by_contra hnot + have hthree : 3 ≤ p.natDegree := by + lia + have shift_ne {q : ℝ[X]} (t : ℝ) (hqdeg : 3 ≤ q.natDegree) : + q - C t ≠ 0 := by + have hdegree : (q - C t).natDegree = q.natDegree := by + apply natDegree_sub_eq_left_of_natDegree_lt + simp only [natDegree_C] + lia + intro hzero + have : q.natDegree = 0 := by + rw [← hdegree, hzero, natDegree_zero] + lia + rcases hsgn.pos_neg_or_neg_pos with hpos | hneg + · have hc_pos : 0 < c := by + simpa [HasPosLeadingCoeff] using hpos.1 + have hneg_three : 3 ≤ (-p).natDegree := by + simpa only [natDegree_neg] using hthree + obtain ⟨t, ht, hshift⟩ := + exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three + hp.neg hpos.2 hneg_three + have hc_ne : c ≠ 0 := ne_of_gt hc_pos + have hq_ne : (-p) - C t ≠ 0 := shift_ne t hneg_three + have hcomb := + hcompat (t / c) 1 (div_nonneg ht.le hc_pos.le) zero_le_one + have hpoly : + C (t / c) * C c + C 1 * p = -((-p) - C t) := by + calc + C (t / c) * C c + C 1 * p = C ((t / c) * c) + p := by + rw [C_mul, C_1, one_mul] + _ = C t + p := by rw [div_mul_cancel₀ t hc_ne] + _ = -((-p) - C t) := by ring + rw [hpoly] at hcomb + rcases hcomb with hzero | hreal + · exact hq_ne (neg_eq_zero.mp hzero) + · exact hshift ⟨hq_ne, by simpa using hreal.2.neg⟩ + · have hc_pos : 0 < -c := by + simpa [HasPosLeadingCoeff] using hneg.1 + obtain ⟨t, ht, hshift⟩ := + exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three + hp hneg.2 hthree + have hc_ne : -c ≠ 0 := ne_of_gt hc_pos + have hq_ne : p - C t ≠ 0 := shift_ne t hthree + have hcomb := + hcompat (t / (-c)) 1 (div_nonneg ht.le hc_pos.le) zero_le_one + have hpoly : C (t / (-c)) * C c + C 1 * p = p - C t := by + have hc' : c ≠ 0 := neg_ne_zero.mp hc_ne + calc + C (t / (-c)) * C c + C 1 * p = C ((t / (-c)) * c) + p := by + rw [C_mul, C_1, one_mul] + _ = C (-t) + p := by + congr 2 + field_simp + _ = p - C t := by simp only [map_neg]; ring + rw [hpoly] at hcomb + rcases hcomb with hzero | hreal + · exact hq_ne hzero + · exact hshift hreal + +/-- The right polynomial has degree at most two when the left polynomial is constant. -/ +lemma natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero + {f g : ℝ[X]} (hg : g.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hfdeg : f.natDegree = 0) : + g.natDegree ≤ 2 := by + rw [eq_C_of_natDegree_eq_zero hfdeg] at hsgn hcompat + exact natDegree_le_two_of_compatible_C_left hg hsgn hcompat + +/-- The left polynomial has degree at most two when the right polynomial is constant. -/ +lemma natDegree_left_le_two_of_compatible_of_right_natDegree_eq_zero + {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : + f.natDegree ≤ 2 := + natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero + hf hsgn.symm hcompat.comm hgdeg + +/-- Corollary 2.2's degree bound when the left polynomial is constant. -/ +lemma natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero + {f g : ℝ[X]} (hg : g.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hfdeg : f.natDegree = 0) : + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by + have hle := + natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero + hg hsgn hcompat hfdeg + rw [hfdeg] + norm_num + exact_mod_cast hle + +/-- Corollary 2.2's degree bound when the right polynomial is constant. -/ +lemma natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero + {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) + (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by + simpa only [abs_sub_comm] using + natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero + hf hsgn.symm hcompat.comm hgdeg + +/-- Liu's Corollary 2.2: compatible real-rooted polynomials with opposite +leading signs have degrees differing by at most two. -/ +theorem corollary22DegreeDiff : corollary22DegreeDiffStatement := by + unfold corollary22DegreeDiffStatement + suffices h : + ∀ n : ℕ, ∀ f g : ℝ[X], f.natDegree + g.natDegree = n → + f.Splits → g.Splits → OppositeLeadingSigns f g → Compatible f g → + |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 by + intro f g hf hg hsgn hcompat + exact h _ f g rfl hf hg hsgn hcompat + intro n + induction n using Nat.strong_induction_on with + | h n ih => + intro f g hsum hf hg hsgn hcompat + by_cases hfdeg : f.natDegree = 0 + · exact + natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero + hg hsgn hcompat hfdeg + by_cases hgdeg : g.natDegree = 0 + · exact + natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero + hf hsgn hcompat hgdeg + by_cases hno : NoCommonRoots f g + · exact + natDegree_abs_sub_le_two_of_compatible_noCommon_nonconstant + hf hg hsgn hno hfdeg hgdeg hcompat + obtain ⟨r, hfr, hgr⟩ := exists_common_root_of_not_noCommonRoots hno + let f' : ℝ[X] := deleteRootFactor f r + let g' : ℝ[X] := deleteRootFactor g r + have hf'_splits : f'.Splits := + deleteRootFactor_splits_of_isRoot hf hfr + have hg'_splits : g'.Splits := + deleteRootFactor_splits_of_isRoot hg hgr + have hsgn' : OppositeLeadingSigns f' g' := by + simpa [f', g'] using + (hsgn.deleteRootFactor_left hfr).deleteRootFactor_right hgr + have hcompat' : Compatible f' g' := + compatible_deleteRootFactor_of_common_root hcompat hfr hgr + have hf'_degree : f'.natDegree = f.natDegree - 1 := + natDegree_deleteRootFactor f r + have hg'_degree : g'.natDegree = g.natDegree - 1 := + natDegree_deleteRootFactor g r + have hf_pos : 0 < f.natDegree := Nat.pos_of_ne_zero hfdeg + have hg_pos : 0 < g.natDegree := Nat.pos_of_ne_zero hgdeg + have hlt : f'.natDegree + g'.natDegree < n := by + rw [hf'_degree, hg'_degree, ← hsum] + lia + have hrec := + ih (f'.natDegree + g'.natDegree) hlt f' g' rfl + hf'_splits hg'_splits hsgn' hcompat' + rw [hf'_degree, hg'_degree] at hrec + rw [Nat.cast_sub hf_pos, Nat.cast_sub hg_pos] at hrec + norm_num at hrec + simpa only [sub_sub_sub_cancel_right] using hrec + + end LiuOppositeSigns end RealRooted diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index b49f9672..afdb1a6e 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -78,7 +78,9 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop {s t : Multiset α} (hrel : Multiset.Rel R s t) : List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by revert t - apply Multiset.strongInductionOn s + refine Multiset.strongInductionOn s + (p := fun s => ∀ t, Multiset.Rel R s t → + List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·))) ?_ intro s ih t hrel cases hs : s.sort (· ≤ ·) with | nil => diff --git a/RealRooted/ObreschkoffConverse.lean b/RealRooted/ObreschkoffConverse.lean index 7fe5aef9..7dc4fb98 100644 --- a/RealRooted/ObreschkoffConverse.lean +++ b/RealRooted/ObreschkoffConverse.lean @@ -1720,162 +1720,6 @@ lemma exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_thr linarith [le_abs_self (p.eval c)] exact (not_lt_of_ge hc_nonneg) hc_neg -/-- The constant endpoint omitted by the source formulation of Corollary 2.2: -compatibility with an oppositely signed nonzero constant forces degree at most two. -/ -lemma natDegree_le_two_of_compatible_C_left - {c : ℝ} {p : ℝ[X]} (hp : p.Splits) - (hsgn : OppositeLeadingSigns (C c) p) (hcompat : Compatible (C c) p) : - p.natDegree ≤ 2 := by - by_contra hnot - have hthree : 3 ≤ p.natDegree := by - lia - have shift_ne {q : ℝ[X]} (t : ℝ) (hqdeg : 3 ≤ q.natDegree) : - q - C t ≠ 0 := by - have hdegree : (q - C t).natDegree = q.natDegree := by - apply natDegree_sub_eq_left_of_natDegree_lt - simp only [natDegree_C] - lia - intro hzero - have : q.natDegree = 0 := by - rw [← hdegree, hzero, natDegree_zero] - lia - rcases hsgn.pos_neg_or_neg_pos with hpos | hneg - · have hc_pos : 0 < c := by - simpa [HasPosLeadingCoeff] using hpos.1 - have hneg_three : 3 ≤ (-p).natDegree := by - simpa only [natDegree_neg] using hthree - obtain ⟨t, ht, hshift⟩ := - exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three - hp.neg hpos.2 hneg_three - have hc_ne : c ≠ 0 := ne_of_gt hc_pos - have hq_ne : (-p) - C t ≠ 0 := shift_ne t hneg_three - have hcomb := - hcompat (t / c) 1 (div_nonneg ht.le hc_pos.le) zero_le_one - have hpoly : - C (t / c) * C c + C 1 * p = -((-p) - C t) := by - calc - C (t / c) * C c + C 1 * p = C ((t / c) * c) + p := by - rw [C_mul, C_1, one_mul] - _ = C t + p := by rw [div_mul_cancel₀ t hc_ne] - _ = -((-p) - C t) := by ring - rw [hpoly] at hcomb - rcases hcomb with hzero | hreal - · exact hq_ne (neg_eq_zero.mp hzero) - · exact hshift ⟨hq_ne, by simpa using hreal.2.neg⟩ - · have hc_pos : 0 < -c := by - simpa [HasPosLeadingCoeff] using hneg.1 - obtain ⟨t, ht, hshift⟩ := - exists_pos_shift_down_not_isRealRooted_of_isRealRooted_of_natDegree_ge_three - hp hneg.2 hthree - have hc_ne : -c ≠ 0 := ne_of_gt hc_pos - have hq_ne : p - C t ≠ 0 := shift_ne t hthree - have hcomb := - hcompat (t / (-c)) 1 (div_nonneg ht.le hc_pos.le) zero_le_one - have hpoly : C (t / (-c)) * C c + C 1 * p = p - C t := by - have hc' : c ≠ 0 := neg_ne_zero.mp hc_ne - calc - C (t / (-c)) * C c + C 1 * p = C ((t / (-c)) * c) + p := by - rw [C_mul, C_1, one_mul] - _ = C (-t) + p := by - congr 2 - field_simp - _ = p - C t := by simp only [map_neg]; ring - rw [hpoly] at hcomb - rcases hcomb with hzero | hreal - · exact hq_ne hzero - · exact hshift hreal - -/-- The right polynomial has degree at most two when the left polynomial is constant. -/ -lemma natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero - {f g : ℝ[X]} (hg : g.Splits) (hsgn : OppositeLeadingSigns f g) - (hcompat : Compatible f g) (hfdeg : f.natDegree = 0) : - g.natDegree ≤ 2 := by - rw [eq_C_of_natDegree_eq_zero hfdeg] at hsgn hcompat - exact natDegree_le_two_of_compatible_C_left hg hsgn hcompat - -/-- The left polynomial has degree at most two when the right polynomial is constant. -/ -lemma natDegree_left_le_two_of_compatible_of_right_natDegree_eq_zero - {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) - (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : - f.natDegree ≤ 2 := - natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero - hf hsgn.symm hcompat.comm hgdeg - -/-- Corollary 2.2's degree bound when the left polynomial is constant. -/ -lemma natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero - {f g : ℝ[X]} (hg : g.Splits) (hsgn : OppositeLeadingSigns f g) - (hcompat : Compatible f g) (hfdeg : f.natDegree = 0) : - |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by - have hle := - natDegree_right_le_two_of_compatible_of_left_natDegree_eq_zero - hg hsgn hcompat hfdeg - rw [hfdeg] - norm_num - exact_mod_cast hle - -/-- Corollary 2.2's degree bound when the right polynomial is constant. -/ -lemma natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero - {f g : ℝ[X]} (hf : f.Splits) (hsgn : OppositeLeadingSigns f g) - (hcompat : Compatible f g) (hgdeg : g.natDegree = 0) : - |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 := by - simpa only [abs_sub_comm] using - natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero - hf hsgn.symm hcompat.comm hgdeg - -/-- Liu's Corollary 2.2: compatible real-rooted polynomials with opposite -leading signs have degrees differing by at most two. -/ -theorem corollary22DegreeDiff : corollary22DegreeDiffStatement := by - unfold corollary22DegreeDiffStatement - suffices h : - ∀ n : ℕ, ∀ f g : ℝ[X], f.natDegree + g.natDegree = n → - f.Splits → g.Splits → OppositeLeadingSigns f g → Compatible f g → - |((f.natDegree : ℤ) - (g.natDegree : ℤ))| ≤ 2 by - intro f g hf hg hsgn hcompat - exact h _ f g rfl hf hg hsgn hcompat - intro n - induction n using Nat.strong_induction_on with - | h n ih => - intro f g hsum hf hg hsgn hcompat - by_cases hfdeg : f.natDegree = 0 - · exact - natDegree_abs_sub_le_two_of_compatible_of_left_natDegree_eq_zero - hg hsgn hcompat hfdeg - by_cases hgdeg : g.natDegree = 0 - · exact - natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero - hf hsgn hcompat hgdeg - by_cases hno : NoCommonRoots f g - · exact - natDegree_abs_sub_le_two_of_compatible_noCommon_nonconstant - hf hg hsgn hno hfdeg hgdeg hcompat - obtain ⟨r, hfr, hgr⟩ := exists_common_root_of_not_noCommonRoots hno - let f' : ℝ[X] := deleteRootFactor f r - let g' : ℝ[X] := deleteRootFactor g r - have hf'_splits : f'.Splits := - deleteRootFactor_splits_of_isRoot hf hfr - have hg'_splits : g'.Splits := - deleteRootFactor_splits_of_isRoot hg hgr - have hsgn' : OppositeLeadingSigns f' g' := by - simpa [f', g'] using - (hsgn.deleteRootFactor_left hfr).deleteRootFactor_right hgr - have hcompat' : Compatible f' g' := - compatible_deleteRootFactor_of_common_root hcompat hfr hgr - have hf'_degree : f'.natDegree = f.natDegree - 1 := - natDegree_deleteRootFactor f r - have hg'_degree : g'.natDegree = g.natDegree - 1 := - natDegree_deleteRootFactor g r - have hf_pos : 0 < f.natDegree := Nat.pos_of_ne_zero hfdeg - have hg_pos : 0 < g.natDegree := Nat.pos_of_ne_zero hgdeg - have hlt : f'.natDegree + g'.natDegree < n := by - rw [hf'_degree, hg'_degree, ← hsum] - lia - have hrec := - ih (f'.natDegree + g'.natDegree) hlt f' g' rfl - hf'_splits hg'_splits hsgn' hcompat' - rw [hf'_degree, hg'_degree] at hrec - rw [Nat.cast_sub hf_pos, Nat.cast_sub hg_pos] at hrec - norm_num at hrec - simpa only [sub_sub_sub_cancel_right] using hrec lemma exists_shift_not_isRealRooted_of_isRealRooted_of_natDegree_ge_two {p : ℝ[X]} (hp_splits : p.Splits) (hp_pos : HasPosLeadingCoeff p) diff --git a/RealRooted/RootOrderBridge.lean b/RealRooted/RootOrderBridge.lean index f5de1da5..e34d0780 100644 --- a/RealRooted/RootOrderBridge.lean +++ b/RealRooted/RootOrderBridge.lean @@ -200,7 +200,25 @@ theorem count_le_two_of_succRootCrossing have hint1 : ∀ i, i < d → sN[i]! ≤ sM[i]! := by grind have hint2 : ∀ i, i + 1 < d → sM[i]! ≤ sN[i + 2]! := by - grind + intro i hi + let j := d - 1 - i + have hj_pos : 1 ≤ j := by + dsimp [j] + lia + have hj_lt : j < d := by + dsimp [j] + lia + have hjN : j - 1 < d + 1 := by lia + have h := hcross.2 j hj_pos hj_lt + rw [h_reverse_getD sM hsM j hj_lt, + h_reverse_getD_succ sN hsN (j - 1) hjN] at h + have hleft : d - 1 - j = i := by + dsimp [j] + lia + have hright : d - (j - 1) = i + 2 := by + dsimp [j] + lia + simpa only [hleft, hright] using h intro x have ha_le_d : (M.filter (· ≤ x)).card ≤ d := hM ▸ Multiset.card_le_card (Multiset.filter_le _ _) From bcb80a85afcb17e63ca1b9afaf1583ca724cf907 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 09:51:54 +0000 Subject: [PATCH 70/76] Repair Liu comparator regressions --- RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean | 2 +- .../LiuOppositeSigns/DerivativeShiftRegularization.lean | 5 +++-- RealRooted/Mathlib/Data/Multiset/Rel.lean | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean index de5e9729..107129fa 100644 --- a/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean +++ b/RealRooted/LiuOppositeSigns/BoundedIntervalContinuity.lean @@ -47,7 +47,7 @@ private theorem false_of_rightFamily_root_of_boundedIntervalContinuity /-- Liu's bounded-interval continuity argument forces every parity-relevant finite combined-root gap to have endpoints owned by opposite polynomials. -/ -theorem OppositeLeadingSigns.crossOwnedNotOddGaps_of_boundedIntervalContinuity +theorem LiuOppositeSigns.OppositeLeadingSigns.crossOwnedNotOddGaps_of_boundedIntervalContinuity {f g : ℝ[X]} (hsgn : OppositeLeadingSigns f g) (hfg : PosComboRealRooted f g) (hno : NoCommonRoots f g) (hf : f.Splits) (hg : g.Splits) : diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean index cc1bd90c..2f720e8d 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftRegularization.lean @@ -56,8 +56,9 @@ theorem NoCommonRoots.exists_delta_TDeriv hsep a ha_sum b hb_sum hab_ne have hab_lt : |a - b| < 2 * η := by calc - |a - b| ≤ |a - x| + |x - b| := - abs_sub_le_abs_sub_add_abs_sub a x b + |a - b| ≤ |a - x| + |x - b| := by + simpa only [sub_add_sub_cancel] using + abs_add_le (a - x) (x - b) _ = |x - a| + |x - b| := by rw [abs_sub_comm a x] _ < 2 * η := by linarith exact (not_lt_of_ge hab_sep) hab_lt diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index afdb1a6e..c149a0e3 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -78,9 +78,9 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop {s t : Multiset α} (hrel : Multiset.Rel R s t) : List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by revert t - refine Multiset.strongInductionOn s + apply Multiset.strongInductionOn s (p := fun s => ∀ t, Multiset.Rel R s t → - List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·))) ?_ + List.Forall₂ R (s.sort (· ≤ ·)) (t.sort (· ≤ ·))) intro s ih t hrel cases hs : s.sort (· ≤ ·) with | nil => From 4cd3e0c7da36e964c798deaf4166d893157a523b Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 10:37:07 +0000 Subject: [PATCH 71/76] fix multiset sorted relation induction --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index c149a0e3..5a9cfe9e 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -98,7 +98,7 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop calc s = ↑(s.sort (· ≤ ·)) := (Multiset.sort_eq (s := s) (r := (· ≤ ·))).symm - _ = a ::ₘ (as : Multiset α) := by rw [hs] + _ = a ::ₘ (as : Multiset α) := by simp [hs] have hsord : (a :: as).Pairwise (· ≤ ·) := by simpa [hs] using (Multiset.pairwise_sort (s := s) (r := (· ≤ ·))) @@ -121,7 +121,7 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop calc t = ↑(t.sort (· ≤ ·)) := (Multiset.sort_eq (s := t) (r := (· ≤ ·))).symm - _ = b ::ₘ (bs : Multiset α) := by rw [ht] + _ = b ::ₘ (bs : Multiset α) := by simp [ht] have htord : (b :: bs).Pairwise (· ≤ ·) := by simpa [ht] using (Multiset.pairwise_sort (s := t) (r := (· ≤ ·))) @@ -134,7 +134,14 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop rw [hsrepr] exact Multiset.lt_cons_self _ _ have ih_tail := ih (as : Multiset α) hsmall (bs : Multiset α) htail - rw [hsrepr, htrepr, Multiset.sort_cons ha, Multiset.sort_cons hb] - exact List.Forall₂.cons hab ih_tail + have hasort : (as : Multiset α).sort (· ≤ ·) = as := by + have h := hs + rw [hsrepr, Multiset.sort_cons ha] at h + exact List.cons.inj h |>.2 + have hbsort : (bs : Multiset α).sort (· ≤ ·) = bs := by + have h := ht + rw [htrepr, Multiset.sort_cons hb] at h + exact List.cons.inj h |>.2 + exact List.Forall₂.cons hab (by simpa [hasort, hbsort] using ih_tail) end Multiset From 93b0bb8a40ebc617232805ca610ecf43fe277fc3 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 11:20:26 +0000 Subject: [PATCH 72/76] repair multiset sorted-tail reconstruction --- RealRooted/Mathlib/Data/Multiset/Rel.lean | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/RealRooted/Mathlib/Data/Multiset/Rel.lean b/RealRooted/Mathlib/Data/Multiset/Rel.lean index 5a9cfe9e..952287e2 100644 --- a/RealRooted/Mathlib/Data/Multiset/Rel.lean +++ b/RealRooted/Mathlib/Data/Multiset/Rel.lean @@ -114,8 +114,8 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop _ = 0 := by simp [ht] subst t have hs0 : s = 0 := Multiset.rel_zero_right.mp hrel - have : False := by simpa [hsrepr] using hs0 - exact this.elim + rw [hsrepr] at hs0 + simp at hs0 | cons b bs => have htrepr : t = b ::ₘ (bs : Multiset α) := by calc @@ -136,11 +136,13 @@ theorem Rel.forall₂_sort {α : Type*} [LinearOrder α] {R : α → α → Prop have ih_tail := ih (as : Multiset α) hsmall (bs : Multiset α) htail have hasort : (as : Multiset α).sort (· ≤ ·) = as := by have h := hs - rw [hsrepr, Multiset.sort_cons ha] at h + rw [hsrepr, + Multiset.sort_cons a (as : Multiset α) (· ≤ ·) ha] at h exact List.cons.inj h |>.2 have hbsort : (bs : Multiset α).sort (· ≤ ·) = bs := by have h := ht - rw [htrepr, Multiset.sort_cons hb] at h + rw [htrepr, + Multiset.sort_cons b (bs : Multiset α) (· ≤ ·) hb] at h exact List.cons.inj h |>.2 exact List.Forall₂.cons hab (by simpa [hasort, hbsort] using ih_tail) From 3b416cbac66d24510722eee4a6ecb741d36fd14c Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 13:39:42 +0000 Subject: [PATCH 73/76] Repair Liu root matching CI failures --- .../DerivativeShiftSequenceRegularization.lean | 8 ++++---- RealRooted/LiuOppositeSigns/RootCountRelStability.lean | 2 +- RealRooted/RootMatchingSort.lean | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean index 161eb538..06dd948e 100644 --- a/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean +++ b/RealRooted/LiuOppositeSigns/DerivativeShiftSequenceRegularization.lean @@ -142,16 +142,16 @@ theorem NoCommonRoots.exists_applyTDerivList_roots_rel have hf_final := Multiset.Rel.comp (fun a b c hab hbc => by calc - |c - a| = |(c - b) + (b - a)| := by ring - _ ≤ |c - b| + |b - a| := abs_add _ _ + |c - a| = |(c - b) + (b - a)| := by ring_nf + _ ≤ |c - b| + |b - a| := abs_add_le _ _ _ < ρ / 2 + ρ / 2 := add_lt_add hbc hab _ = ρ := by ring) hf_step hf_tail have hg_final := Multiset.Rel.comp (fun a b c hab hbc => by calc - |c - a| = |(c - b) + (b - a)| := by ring - _ ≤ |c - b| + |b - a| := abs_add _ _ + |c - a| = |(c - b) + (b - a)| := by ring_nf + _ ≤ |c - b| + |b - a| := abs_add_le _ _ _ < ρ / 2 + ρ / 2 := add_lt_add hbc hab _ = ρ := by ring) hg_step hg_tail diff --git a/RealRooted/LiuOppositeSigns/RootCountRelStability.lean b/RealRooted/LiuOppositeSigns/RootCountRelStability.lean index 464ba32a..cb6717a1 100644 --- a/RealRooted/LiuOppositeSigns/RootCountRelStability.lean +++ b/RealRooted/LiuOppositeSigns/RootCountRelStability.lean @@ -12,7 +12,7 @@ separated from the original roots and compare the selected largest roots. namespace RealRooted -open Polynomial +open LiuOppositeSigns Polynomial /-- A close root matching preserves the root count at a separated threshold. -/ theorem rootCountAtOrAbove_eq_of_roots_rel_abs_sub_lt diff --git a/RealRooted/RootMatchingSort.lean b/RealRooted/RootMatchingSort.lean index 7e2ccad0..819b8a89 100644 --- a/RealRooted/RootMatchingSort.lean +++ b/RealRooted/RootMatchingSort.lean @@ -11,6 +11,8 @@ ordered-root input for limiting interlacing and root-count inequalities. namespace RealRooted +open LiuOppositeSigns Polynomial + /-- A close multiset matching pairs corresponding ascending order statistics. -/ theorem forall₂_sort_of_rel_abs_sub_lt {s t : Multiset ℝ} {ρ : ℝ} @@ -19,7 +21,7 @@ theorem forall₂_sort_of_rel_abs_sub_lt (s.sort (· ≤ ·)) (t.sort (· ≤ ·)) := by refine Multiset.Rel.forall₂_sort ?_ hrel intro a c b d hac hbd had hcb - rw [abs_lt] at had hcb ⊢ + simp only [abs_lt] at had hcb ⊢ constructor <;> constructor <;> linarith /-- A close multiset matching pairs corresponding descending order statistics. -/ @@ -37,7 +39,7 @@ theorem forall₂_sort_ge_of_rel_abs_sub_lt · intro a c b d hac hbd had hcb change OrderDual.ofDual c ≤ OrderDual.ofDual a at hac change OrderDual.ofDual d ≤ OrderDual.ofDual b at hbd - rw [abs_lt] at had hcb ⊢ + simp only [abs_lt] at had hcb ⊢ constructor <;> constructor <;> linarith · exact hrel From b1e9e07a619f1050aa64ade710d18ad9fe2cce6e Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 20:08:30 +0000 Subject: [PATCH 74/76] Qualify Liu sorted-root helper --- RealRooted/RootMatchingSort.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RealRooted/RootMatchingSort.lean b/RealRooted/RootMatchingSort.lean index 819b8a89..6242e44d 100644 --- a/RealRooted/RootMatchingSort.lean +++ b/RealRooted/RootMatchingSort.lean @@ -44,7 +44,7 @@ theorem forall₂_sort_ge_of_rel_abs_sub_lt · exact hrel /-- A largest root is the head of the descending root list. -/ -theorem IsLargestRoot.roots_sort_ge_eq_cons +theorem LiuOppositeSigns.IsLargestRoot.roots_sort_ge_eq_cons {p : ℝ[X]} {r : ℝ} (hp_ne : p ≠ 0) (h : IsLargestRoot p r) : p.roots.sort (· ≥ ·) = r :: (deleteRootFactor p r).roots.sort (· ≥ ·) := by From faaf41e4eb352aa0fe032c59e170ef11b0e7fe82 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Mon, 3 Aug 2026 23:35:02 +0000 Subject: [PATCH 75/76] Repair Liu root-count closure APIs --- .../LiuOppositeSigns/RootCountClosure.lean | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/RootCountClosure.lean b/RealRooted/LiuOppositeSigns/RootCountClosure.lean index 8f25d806..2825d1cb 100644 --- a/RealRooted/LiuOppositeSigns/RootCountClosure.lean +++ b/RealRooted/LiuOppositeSigns/RootCountClosure.lean @@ -1,5 +1,7 @@ import RealRooted.CommonInterleaver.SameDegreeRootCount +import RealRooted.CommonInterleaver.RootCountCombinatorics import RealRooted.LiuOppositeSigns.RootCountRelStability +import RealRooted.LiuOppositeSigns.Theorem21Statements import RealRooted.RootMatchingSort /-! @@ -15,6 +17,15 @@ namespace LiuOppositeSigns open Polynomial +private theorem abs_getD_sub_getD_lt_of_forall₂ + {xs ys : List ℝ} {ρ : ℝ} + (h : List.Forall₂ (fun x y : ℝ => |y - x| < ρ) xs ys) + {i : ℕ} (hxs : i < xs.length) (hys : i < ys.length) : + |ys.getD i 0 - xs.getD i 0| < ρ := by + rw [list_getD_eq_getElem_of_lt ys i 0 hys, + list_getD_eq_getElem_of_lt xs i 0 hxs] + exact h.get hxs hys + /-- Same-degree root-count compatibility is closed under close finite crossings. -/ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing {f g : ℝ[X]} (hf_ne : f ≠ 0) (hg_ne : g ≠ 0) @@ -49,8 +60,9 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing have hjrf : j - 1 < rf.length := by simpa only [hff.length_eq] using hjf exact ⟨rg.getD j 0, rf.getD (j - 1) 0, - by simpa using hgg.get hjg hjrg, - by simpa using hff.get hjf hjrf, hfg.1 j hj1 hj⟩ + abs_getD_sub_getD_lt_of_forall₂ hgg hjg hjrg, + abs_getD_sub_getD_lt_of_forall₂ hff hjf hjrf, + hfg.1 j hj1 hj⟩ · intro j hj1 hj apply le_of_forall_pos_exists_close_le intro ρ hρ @@ -64,8 +76,9 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCrossing have hjrg : j - 1 < rg.length := by simpa only [hgg.length_eq] using hjg exact ⟨rf.getD j 0, rg.getD (j - 1) 0, - by simpa using hff.get hjf hjrf, - by simpa using hgg.get hjg hjrg, hfg.2 j hj1 hj⟩ + abs_getD_sub_getD_lt_of_forall₂ hff hjf hjrf, + abs_getD_sub_getD_lt_of_forall₂ hgg hjg hjrg, + hfg.2 j hj1 hj⟩ have hlower := sameDegreeRootCount_of_rootCrossing hf hg hdeg hcross have hupper := sameDegreeRootCountAbove_of_rootCount hf hg hdeg hlower exact RootCountCompatible.of_rootCountAbove_bounds_of_nonRoot @@ -107,8 +120,9 @@ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCrossing have hjrf : j - 1 < rf.length := by simpa only [hff.length_eq] using hjf exact ⟨rg.getD j 0, rf.getD (j - 1) 0, - by simpa using hgg.get hjg hjrg, - by simpa using hff.get hjf hjrf, hfg.1 j hj1 hj⟩ + abs_getD_sub_getD_lt_of_forall₂ hgg hjg hjrg, + abs_getD_sub_getD_lt_of_forall₂ hff hjf hjrf, + hfg.1 j hj1 hj⟩ · intro j hj1 hj apply le_of_forall_pos_exists_close_le intro ρ hρ @@ -122,8 +136,9 @@ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCrossing have hjrg : j - 1 < rg.length := by simpa only [hgg.length_eq] using hjg exact ⟨rf.getD j 0, rg.getD (j - 1) 0, - by simpa using hff.get hjf hjrf, - by simpa using hgg.get hjg hjrg, hfg.2 j hj1 hj⟩ + abs_getD_sub_getD_lt_of_forall₂ hff hjf hjrf, + abs_getD_sub_getD_lt_of_forall₂ hgg hjg hjrg, + hfg.2 j hj1 hj⟩ have hlower := succDegreeRootCount_of_rootCrossing hf hg hdeg hcross have hupper := succDegreeRootCountAbove_of_rootCount hf hg hdeg hlower exact RootCountCompatible.of_rootCountAbove_bounds_of_nonRoot @@ -164,10 +179,12 @@ theorem RootCountCompatible.of_forall_pos_exists_close_sameDegreeCompatible rw [hqdeg, hpdeg, hdeg] have hcount' := sameDegreeRootCountAbove_of_nonRoot_bound hp'_ne hq'_ne - (hcompat'.rootCountAbove_bounds_of_nonRoot hp'_ne hq'_ne) + (fun x hpx hqx => + hcompat'.rootCountAbove_bounds_of_nonRoot hp'_ne hq'_ne hpx hqx) exact ⟨rootSeqDesc p', rootSeqDesc q', hpp', hqq', - rootCrossing_of_rootCountAbove_diff_le_one - hp'_split hq'_split hdeg' hcount'⟩ + by simpa only [hpdeg] using + (rootCrossing_of_rootCountAbove_diff_le_one + hp'_split hq'_split hdeg' hcount')⟩ /-- Successor-degree root-count compatibility is closed under close compatible pairs. -/ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible @@ -204,10 +221,12 @@ theorem RootCountCompatible.of_forall_pos_exists_close_succDegreeCompatible rw [hqdeg, hpdeg, hdeg] have hcount' := sameDegreeRootCountAbove_of_nonRoot_bound hp'_ne hq'_ne - (hcompat'.rootCountAbove_bounds_of_nonRoot hp'_ne hq'_ne) + (fun x hpx hqx => + hcompat'.rootCountAbove_bounds_of_nonRoot hp'_ne hq'_ne hpx hqx) exact ⟨rootSeqDesc p', rootSeqDesc q', hpp', hqq', - succDegreeRootCrossing_of_rootCountAbove - hp'_split hq'_split hdeg' hcount'⟩ + by simpa only [hpdeg] using + (succDegreeRootCrossing_of_rootCountAbove + hp'_split hq'_split hdeg' hcount')⟩ /-- A fixed left branch is closed under close same-degree approximations. -/ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree @@ -236,8 +255,10 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_sameDegree obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, hff', hgg', hbranch'⟩ := hclose ρ hρ exact ⟨s', r', - hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', - hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hg_ne hg'_ne hs hbranch'.g_largest hgg', + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hf_ne hf'_ne hr hbranch'.f_largest hff', hbranch'.largest_ge⟩ have hdelete_degree : g.natDegree = (deleteRootFactor f r).natDegree + 1 := by @@ -290,8 +311,10 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, hff', hgg', hbranch'⟩ := hclose ρ hρ exact ⟨s', r', - hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', - hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hg_ne hg'_ne hs hbranch'.g_largest hgg', + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hf_ne hf'_ne hr hbranch'.f_largest hff', hbranch'.largest_ge⟩ have hdelete_degree : g.natDegree = (deleteRootFactor f r).natDegree := by @@ -344,8 +367,10 @@ theorem LeftRootCountBranch.of_forall_pos_exists_close_of_degree_eq_succ_succ obtain ⟨f', g', r', s', hf'_ne, hg'_ne, hf'_split, hg'_split, hff', hgg', hbranch'⟩ := hclose ρ hρ exact ⟨s', r', - hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hbranch'.g_largest hgg', - hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hbranch'.f_largest hff', + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hg_ne hg'_ne hs hbranch'.g_largest hgg', + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hf_ne hf'_ne hr hbranch'.f_largest hff', hbranch'.largest_ge⟩ have hdelete_degree : (deleteRootFactor f r).natDegree = g.natDegree + 1 := by @@ -502,9 +527,11 @@ theorem theorem21RootCountBranches_of_forall_pos_exists_roots_rel rcases hbranch' with hleft' | hright' · exfalso have hr_close := - hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hleft'.f_largest hff' + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hf_ne hf'_ne hr hleft'.f_largest hff' have hs_close := - hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hleft'.g_largest hgg' + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hg_ne hg'_ne hs hleft'.g_largest hgg' rw [abs_lt] at hr_close hs_close linarith [hleft'.largest_ge, hδ_le_gap] · exact @@ -532,9 +559,11 @@ theorem theorem21RootCountBranches_of_forall_pos_exists_roots_rel hleft'⟩ · exfalso have hr_close := - hr.abs_sub_lt_of_roots_rel hf_ne hf'_ne hright'.f_largest hff' + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hf_ne hf'_ne hr hright'.f_largest hff' have hs_close := - hs.abs_sub_lt_of_roots_rel hg_ne hg'_ne hright'.g_largest hgg' + _root_.RealRooted.IsLargestRoot.abs_sub_lt_of_roots_rel + hg_ne hg'_ne hs hright'.g_largest hgg' rw [abs_lt] at hr_close hs_close linarith [hright'.largest_lt, hδ_le_gap] From f1b9da02e0959f410b9d30d249fa8a8889e1bc76 Mon Sep 17 00:00:00 2001 From: Per Alexandersson Date: Wed, 5 Aug 2026 11:20:44 +0000 Subject: [PATCH 76/76] Repair Liu bounded-continuity integration --- RealRooted/LiuOppositeSigns/Theorem21Statements.lean | 2 +- RealRooted/LiuOppositeSignsTheorem.lean | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RealRooted/LiuOppositeSigns/Theorem21Statements.lean b/RealRooted/LiuOppositeSigns/Theorem21Statements.lean index fd0679c4..2edc5e7c 100644 --- a/RealRooted/LiuOppositeSigns/Theorem21Statements.lean +++ b/RealRooted/LiuOppositeSigns/Theorem21Statements.lean @@ -1930,7 +1930,7 @@ theorem not_theorem21CompatibleToRootCountBranchesNonconstantStatement : (by unfold HasPosLeadingCoeff; simp) (by simp) (by simp)).neg_right have hgsplits : (-(X ^ 2) : ℝ[X]).Splits := by - simpa [pow_two] using + simpa only [pow_two] using (Polynomial.Splits.X.mul Polynomial.Splits.X).neg have hcompat : Compatible (X : ℝ[X]) (-(X ^ 2)) := by simpa [pow_two] using diff --git a/RealRooted/LiuOppositeSignsTheorem.lean b/RealRooted/LiuOppositeSignsTheorem.lean index 9f0f32cf..1fefe965 100644 --- a/RealRooted/LiuOppositeSignsTheorem.lean +++ b/RealRooted/LiuOppositeSignsTheorem.lean @@ -483,7 +483,7 @@ lemma natDegree_le_two_of_compatible_C_left rw [hpoly] at hcomb rcases hcomb with hzero | hreal · exact hq_ne (neg_eq_zero.mp hzero) - · exact hshift ⟨hq_ne, by simpa using hreal.2.neg⟩ + · exact hshift ⟨hq_ne, by simpa only [neg_neg] using hreal.2.neg⟩ · have hc_pos : 0 < -c := by simpa [HasPosLeadingCoeff] using hneg.1 obtain ⟨t, ht, hshift⟩ := @@ -546,7 +546,7 @@ lemma natDegree_abs_sub_le_two_of_compatible_of_right_natDegree_eq_zero /-- Liu's Corollary 2.2: compatible real-rooted polynomials with opposite leading signs have degrees differing by at most two. -/ -theorem corollary22DegreeDiff : corollary22DegreeDiffStatement := by +theorem corollary22DegreeDiff_proof : corollary22DegreeDiffStatement := by unfold corollary22DegreeDiffStatement suffices h : ∀ n : ℕ, ∀ f g : ℝ[X], f.natDegree + g.natDegree = n →