Zulip Chat Archive

Stream: new members

Topic: How to use Rational Root Theorem?


Ilmārs Cīrulis (Apr 16 2025 at 13:23):

Got this far for now. Is this good start?
(Polynomial taken from #mathlib4 > polynomials123 )

import Mathlib
open Polynomial

noncomputable def f: [X] := X^3 + C 3 * X^2 - C 8

theorem natDegree_of_f_is_three: f.natDegree = 3 := by rw [f]; compute_degree!

theorem leading_coef_of_f: f.leadingCoeff = 1 := by
  rw [leadingCoeff, natDegree_of_f_is_three, f]; simp

theorem aux (r: ): f.aeval r  0 := by
  intro H
  have H0 := num_dvd_of_is_root H
  have H1 := den_dvd_of_is_root H
  rw [leading_coef_of_f] at H1
  unfold f at H0; simp at *
  have H2: (IsFractionRing.den  r)  [(-1: ), 1] := by sorry
  have H3: IsFractionRing.num  r  [-8, -4, -2, -1, 1, 2, 4, 8] := by sorry
  simp at H3 H2; obtain H3 | H3 | H3 | H3 | H3 | H3 | H3 | H3 := H3 <;> obtain H2 | H2 := H2
  . have Hr: r = (8: ) := by
      have H4 := IsFractionRing.mk'_num_den'  r
      rw [<- H4, H3, H2]; simp
    rw [Hr, f] at H; simp at H; rw [map_ofNat, map_ofNat] at H; norm_num at H
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry
  . sorry

Aaron Liu (Apr 16 2025 at 13:34):

Unfortunately one of your sorrys is not provable, because both 1 and -1 divide 1.

Ilmārs Cīrulis (Apr 16 2025 at 13:35):

Ok, that's fixable

Ilmārs Cīrulis (Apr 16 2025 at 14:46):

[Edited proof part in the original post.]

Now I have only to prove H2 and H3 (about integer divisors of 1 and 8) and (similarly to the first case) fill remaining 15 cases. Then aux theorem will be done.

Ilmārs Cīrulis (Apr 16 2025 at 16:30):

What's the best way to say (in Lean) that p: ℚ[X] has only integer coefficients?

Ilmārs Cīrulis (Apr 16 2025 at 16:35):

WIll go with
def all_coeffs_integer(p: ℚ[X]): Prop := ∀ (c: ℚ), c ∈ p.coeffs → c.den = 1

Ilmārs Cīrulis (Apr 16 2025 at 16:39):

Would such two theorems be useful?

import Mathlib
open Polynomial

def all_coeffs_integer (p: [X]): Prop :=  (c: ), c  p.coeffs  c.den = 1

theorem rational_root_theorem_den (p: [X]) (Hp: all_coeffs_integer p) (r: ): r  p.roots  r.den  p.leadingCoeff.num.natAbs := by
  sorry

theorem rational_root_theorem_num (p: [X]) (Hp: all_coeffs_integer p) (r: ): r  p.roots  r.num  (p.coeff 0).num := by
  sorry

Ilmārs Cīrulis (Apr 16 2025 at 18:50):

Current state (stuck in multiple ways :sob: ):

import Mathlib
open Polynomial

theorem aux (p: [X]) (n: ):
    ((p.map (Int.castRingHom )).coeff n).den = 1 := by
  simp

theorem aux0 (r: ): r.den = (IsFractionRing.den  r: ).natAbs := by
  sorry

theorem rational_root_theorem_den (p: [X]) (r: ):
    r  (p.map (Int.castRingHom )).roots  r.den  p.leadingCoeff.natAbs := by
  simp; intro H H0
  have H1: p.aeval r = 0 := by
    sorry
  apply den_dvd_of_is_root at H1
  obtain d, H1 := H1
  rw [aux0, H1]; simp

theorem rational_root_theorem_num (p: [X]) (r: ):
    r  (p.map (Int.castRingHom )).roots  r.num.natAbs  (p.coeff 0).natAbs := by
  sorry

Ruben Van de Velde (Apr 16 2025 at 20:21):

You nerd-sniped me for the first one, but I'll leave the rest to you.

Ruben Van de Velde (Apr 16 2025 at 20:21):

import Mathlib
open Polynomial

theorem aux (p: [X]) (n: ):
    ((p.map (Int.castRingHom )).coeff n).den = 1 := by
  simp
theorem aux2 (q : ) (n:  ) (d : ) (hd : d  0) (red: IsCoprime n d) (h : q * d = n) : d = q.den  n = q.num := by
  replace h : q = n / d := by rw [ h]; rw [mul_div_cancel_right₀]; simp_all
  subst h
  constructor
  · apply Nat.cast_injective (R := )
    convert (Rat.den_div_eq_of_coprime (a := n) (b := d) ?_ ?_).symm
    · simp_all [pos_iff_ne_zero]
    · rwa [ @Int.isCoprime_iff_nat_coprime]
  · convert (Rat.num_div_eq_of_coprime (a := n) (b := d) ?_ ?_).symm
    · simp_all [pos_iff_ne_zero]
    · rwa [ @Int.isCoprime_iff_nat_coprime]


theorem aux1 (r : ) : (IsFractionRing.den  r: ).natAbs = r.den  (IsFractionRing.num  r: ).natAbs = r.num.natAbs := by
  have h0 := IsFractionRing.mk'_num_den  r
  rw [ IsLocalization.mk'_eq_iff_eq_mul, eq_comm] at h0
  simp at h0
  have := Int.natAbs_eq (IsFractionRing.den  r: )
  cases this with
  | inl h =>
    have := aux2 r (IsFractionRing.num  r) ((IsFractionRing.den  r: ).natAbs) ?_ ?_ ?_
    refine this.1, by rw [this.2]
    · simp
    · have h1 := IsFractionRing.num_den_reduced  r
      have := IsRelPrime.isCoprime h1
      simp
      exact (IsCoprime.abs_right_iff (IsFractionRing.num  r) (IsFractionRing.den  r)).mpr this
    · convert h0
      apply_fun Int.cast (R := ) at h
      convert h.symm
  | inr h =>
    rw [h] at h0
    simp only [Int.cast_neg, mul_neg] at h0
    rw [@neg_eq_iff_eq_neg] at h0
    have := aux2 _ _ _ ?_ ?_ h0
    refine by simpa using this.1, by rw [ this.2]; simp
    · simp
    · have h1 := IsFractionRing.num_den_reduced  r
      have := IsRelPrime.isCoprime h1
      simp [IsCoprime.neg_left_iff]
      exact (IsCoprime.abs_right_iff (IsFractionRing.num  r) (IsFractionRing.den  r)).mpr this

theorem aux0 (r: ): r.den = (IsFractionRing.den  r: ).natAbs := by
  exact (aux1 r).1.symm

Ruben Van de Velde (Apr 16 2025 at 20:24):

Actually, here's two as well

  have H1: p.aeval r = 0 := by
    rw [ @eval_map_algebraMap]
    simpa using H0

Ilmārs Cīrulis (Apr 16 2025 at 21:50):

Here is what I have done:

/-
Copyright (c) 2025 Ilmārs Cīrulis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Ilmārs Cīrulis
-/
import Mathlib.Analysis.Normed.Field.Lemmas
import Mathlib.Data.Int.Star
import Mathlib.RingTheory.Polynomial.RationalRoot

/-!
# Rational root theorem (for usual rational numbers and integers)

This file contains specialization of the rational root theorem -
to use with usual rational numbers and integers.
It (`num_dvd_of_is_root_standard_case` and `den_dvd_of_is_root_standard_case`)
states that for any `p : ℤ[X]` any rational root `r : ℚ` of polynomial
`p.map (Int.castRingHom ℚ)` are such that `r.den ∣ p.leadingCoeff.natAbs`
and `r.num.natAbs ∣ (p.coeff 0).natAbs`.

The file also contains the corresponding integral root theorem for integers
(`root_dvd_of_is_root_of_monic_standard_case`).

## References

 * https://en.wikipedia.org/wiki/Rational_root_theorem
-/

open Polynomial

lemma rat_as_divInt_in_fraction_ring (r : ) :
    r = Rat.divInt (IsFractionRing.num  r : ) (IsFractionRing.den  r : ) := by
  nth_rw 1 [<- IsFractionRing.mk'_num_den  r]
  simp only [IsFractionRing.mk'_eq_div, algebraMap_int_eq, eq_intCast]
  norm_cast

lemma rat_eq_divInt_of_relprimes_aux {r : } {a b : }
    (Hb : 0 < b) (H : IsRelPrime a b) (H0 : r = Rat.divInt a b) :
    r.num = a  (r.den : ) = b := by
  apply Rat.div_int_inj (Int.ofNat_lt.mpr (Rat.den_pos r)) Hb r.reduced
  . rw [Nat.Coprime, <- Int.gcd_eq_natAbs]
    rw [<- gcd_isUnit_iff_isRelPrime, Int.isUnit_iff] at H
    simp only [Int.reduceNeg, reduceCtorEq, or_false] at H
    exact (Int.ofNat_inj.mp H)
  . rw [Int.cast_natCast, H0, Rat.num_div_den, Rat.intCast_div_eq_divInt]

lemma nonzero_rat_eq_divInt_of_relprimes {r : } {a b : }
    (Hr : r  0) (H : IsRelPrime a b) (H0 : r = Rat.divInt a b) :
    r.num.natAbs = a.natAbs  r.den = b.natAbs := by
  obtain H1 | H1 | H1 := Int.lt_trichotomy b 0
  . have H2 : r = Rat.divInt (-a) (-b) := by rw [Rat.divInt_neg, neg_neg]; exact H0
    have H3 := rat_eq_divInt_of_relprimes_aux (Int.neg_pos_of_neg H1) (IsRelPrime.neg_neg H) H2
    omega
  . subst b; simp only [Rat.divInt_zero] at H0; exact (Hr H0).elim
  . have H2 := rat_eq_divInt_of_relprimes_aux H1 H H0
    omega

lemma rat_num_abs_and_den_in_fraction_ring (r : ) :
    r.num.natAbs = (IsFractionRing.num  r : ).natAbs 
    r.den = (IsFractionRing.den  r : ).natAbs := by
  obtain H | H := eq_or_ne r 0
  . subst r
    simp only [Rat.num_ofNat, Int.natAbs_zero, IsFractionRing.num_zero, Rat.den_ofNat, true_and]
    have H0 := IsFractionRing.isUnit_den_zero (A := ) (K := )
    obtain H0 | H0 := Int.isUnit_iff.mp H0 <;> simp [H0]
  . exact (nonzero_rat_eq_divInt_of_relprimes H
           (IsFractionRing.num_den_reduced  r) (rat_as_divInt_in_fraction_ring r))

lemma aeval_eq_zero_of_eval_eq_zero {p : [X]} {r : }
    (H : eval r (p.map (Int.castRingHom )) = 0) :
    p.aeval r = 0 := by
  rw [<- eval_map_algebraMap, algebraMap_int_eq]; exact H

/-- **Rational root theorem**, part 1 --/
theorem den_dvd_of_is_root_standard_case {p : [X]} {r : }
    (H : r  (p.map (Int.castRingHom )).roots) :
    r.den  p.leadingCoeff.natAbs := by
  simp only [mem_roots', ne_eq, IsRoot.def] at H; obtain H, H0 := H
  obtain d, H1 := den_dvd_of_is_root (aeval_eq_zero_of_eval_eq_zero H0)
  rw [H1, Int.natAbs_mul, (rat_num_abs_and_den_in_fraction_ring r).2]
  apply dvd_mul_right

/-- **Rational root theorem**, part 2 --/
theorem num_dvd_of_is_root_standard_case {p : [X]} {r : }
    (H : r  (p.map (Int.castRingHom )).roots) :
    r.num.natAbs  (p.coeff 0).natAbs := by
  simp only [mem_roots', ne_eq, IsRoot.def] at H
  simp [Int.natAbs_dvd_natAbs]
  obtain H, H0 := H
  obtain d, H1 := num_dvd_of_is_root (aeval_eq_zero_of_eval_eq_zero H0)
  rw [H1, <- Int.sign_mul_natAbs r.num, <- Int.sign_mul_natAbs (IsFractionRing.num  r),
      (rat_num_abs_and_den_in_fraction_ring r).1]
  use (d * (IsFractionRing.num  r).sign * r.num.sign)
  ring_nf
  obtain H2 | H2 | H2 := Int.lt_trichotomy r.num 0
  . simp [Int.sign_eq_neg_one_iff_neg.mpr H2]
  . simp only [Rat.num_eq_zero] at H2; subst r; simp
  . simp [Int.sign_eq_one_iff_pos.mpr H2]

/-- **Integral root theorem** --/
theorem root_dvd_of_is_root_of_monic_standard_case {p : [X]} {n: }
    (H : p.Monic) (H0 : n  p.roots) :
    n.natAbs  (p.coeff 0).natAbs := by
  have H1 : (n : )  (p.map (Int.castRingHom )).roots := by
    simp only [mem_roots', ne_eq, IsRoot.def] at H0
    simp [map_monic_ne_zero H, H0.2]
  have H2 : (n : ).num = n := by rfl
  rw [<- H2]
  exact num_dvd_of_is_root_standard_case H1


--- Example of use

example (r : ) (H : r  (2 * X ^ 3 + 3 * X ^ 2 - 3 : [X]).roots) :
    r.den  2  r.num.natAbs  3 := by
  have H0 : (2 * X ^ 3 + 3 * X ^ 2 - 3 : [X]) =
            (2 * X ^ 3 + 3 * X ^ 2 - 3 : [X]).map (Int.castRingHom ) := by simp
  have H1 : (2 * X ^ 3 + 3 * X ^ 2 - 3: [X]).natDegree = 3 := by compute_degree!
  rw [H0] at H; constructor
  . apply den_dvd_of_is_root_standard_case at H
    unfold leadingCoeff at H; rw [H1] at H; simp at H; exact H
  . apply num_dvd_of_is_root_standard_case at H
    simp at H; exact H

Ilmārs Cīrulis (Apr 16 2025 at 21:52):

Thank you! :heart:

Tomorrow will take more serious look at your approach (from which I took the proof of aux6 which I couldn't prove :sweat_smile:), but now falling asleep. :bed:

Ilmārs Cīrulis (Apr 16 2025 at 22:36):

Forgot to ask.
Is such result useful enough to put it in Mathlib? What do you think?

(It's just that the existing Rational Root Theorem is impressively general and for this reason difficult to use in the standard case of rational root and integer coefficients (at least for me).)


Last updated: May 02 2025 at 03:31 UTC