Skip to content

Commit c192723

Browse files
feat: better norm_num for rpow (#30615)
- Use recently added `Nat.nthRoot` to make `(a : ℝ) ^ (b : ℝ)` work for rational `a ≥ 0` and `b`. - Simplify the logic by using `.eqTrans`. Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 84ed40c commit c192723

File tree

2 files changed

+121
-39
lines changed

2 files changed

+121
-39
lines changed

Mathlib/Analysis/SpecialFunctions/Pow/Real.lean

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Sébasti
55
Rémy Degenne, David Loeffler
66
-/
77
import Mathlib.Analysis.SpecialFunctions.Pow.Complex
8+
import Mathlib.Data.Nat.NthRoot.Defs
89
import Qq
910

1011
/-! # Power function on `ℝ`
@@ -1042,94 +1043,155 @@ namespace Mathlib.Meta.NormNum
10421043

10431044
open Lean.Meta Qq
10441045

1046+
theorem IsNat.rpow_eq_pow {b : ℝ} {n : ℕ} (h : IsNat b n) (a : ℝ) : a ^ b = a ^ n := by
1047+
rw [h.1, Real.rpow_natCast]
1048+
1049+
theorem IsInt.rpow_eq_inv_pow {b : ℝ} {n : ℕ} (h : IsInt b (.negOfNat n)) (a : ℝ) :
1050+
a ^ b = (a ^ n)⁻¹ := by
1051+
rw [h.1, Real.rpow_intCast, Int.negOfNat_eq, zpow_neg, Int.ofNat_eq_coe, zpow_natCast]
1052+
1053+
@[deprecated IsNat.rpow_eq_pow (since := "2025-10-21")]
10451054
theorem isNat_rpow_pos {a b : ℝ} {nb ne : ℕ}
10461055
(pb : IsNat b nb) (pe' : IsNat (a ^ nb) ne) :
10471056
IsNat (a ^ b) ne := by
10481057
rwa [pb.out, rpow_natCast]
10491058

1059+
@[deprecated IsInt.rpow_eq_inv_pow (since := "2025-10-21")]
10501060
theorem isNat_rpow_neg {a b : ℝ} {nb ne : ℕ}
10511061
(pb : IsInt b (Int.negOfNat nb)) (pe' : IsNat (a ^ (Int.negOfNat nb)) ne) :
10521062
IsNat (a ^ b) ne := by
10531063
rwa [pb.out, Real.rpow_intCast]
10541064

1065+
@[deprecated IsNat.rpow_eq_pow (since := "2025-10-21")]
10551066
theorem isInt_rpow_pos {a b : ℝ} {nb ne : ℕ}
10561067
(pb : IsNat b nb) (pe' : IsInt (a ^ nb) (Int.negOfNat ne)) :
10571068
IsInt (a ^ b) (Int.negOfNat ne) := by
10581069
rwa [pb.out, rpow_natCast]
10591070

1071+
@[deprecated IsInt.rpow_eq_inv_pow (since := "2025-10-21")]
10601072
theorem isInt_rpow_neg {a b : ℝ} {nb ne : ℕ}
10611073
(pb : IsInt b (Int.negOfNat nb)) (pe' : IsInt (a ^ (Int.negOfNat nb)) (Int.negOfNat ne)) :
10621074
IsInt (a ^ b) (Int.negOfNat ne) := by
10631075
rwa [pb.out, Real.rpow_intCast]
10641076

1077+
@[deprecated IsNat.rpow_eq_pow (since := "2025-10-21")]
10651078
theorem isNNRat_rpow_pos {a b : ℝ} {nb : ℕ}
10661079
{num den : ℕ}
10671080
(pb : IsNat b nb) (pe' : IsNNRat (a ^ nb) num den) :
10681081
IsNNRat (a ^ b) num den := by
10691082
rwa [pb.out, rpow_natCast]
10701083

1084+
@[deprecated IsNat.rpow_eq_pow (since := "2025-10-21")]
10711085
theorem isRat_rpow_pos {a b : ℝ} {nb : ℕ}
10721086
{num : ℤ} {den : ℕ}
10731087
(pb : IsNat b nb) (pe' : IsRat (a ^ nb) num den) :
10741088
IsRat (a ^ b) num den := by
10751089
rwa [pb.out, rpow_natCast]
10761090

1091+
@[deprecated IsInt.rpow_eq_inv_pow (since := "2025-10-21")]
10771092
theorem isNNRat_rpow_neg {a b : ℝ} {nb : ℕ}
10781093
{num den : ℕ}
10791094
(pb : IsInt b (Int.negOfNat nb)) (pe' : IsNNRat (a ^ (Int.negOfNat nb)) num den) :
10801095
IsNNRat (a ^ b) num den := by
10811096
rwa [pb.out, Real.rpow_intCast]
10821097

1098+
@[deprecated IsInt.rpow_eq_inv_pow (since := "2025-10-21")]
10831099
theorem isRat_rpow_neg {a b : ℝ} {nb : ℕ}
10841100
{num : ℤ} {den : ℕ}
10851101
(pb : IsInt b (Int.negOfNat nb)) (pe' : IsRat (a ^ (Int.negOfNat nb)) num den) :
10861102
IsRat (a ^ b) num den := by
10871103
rwa [pb.out, Real.rpow_intCast]
10881104

1089-
/-- Evaluates expressions of the form `a ^ b` when `a` and `b` are both reals. -/
1105+
/-- Given proofs
1106+
- that `a` is a natural number `m`
1107+
- that `b` is a nonnegative rational number `n / d`
1108+
- that `r ^ d = m ^ n` (written as `r ^ d = k`, `m ^ n = l`, `k = l`)
1109+
prove that `a ^ b = r`.
1110+
-/
1111+
theorem IsNat.rpow_isNNRat {a b : ℝ} {m n d r : ℕ} (ha : IsNat a m) (hb : IsNNRat b n d)
1112+
(k : ℕ) (hr : r ^ d = k) (l : ℕ) (hm : m ^ n = l) (hkl : k = l) : IsNat (a ^ b) r := by
1113+
rcases ha with ⟨rfl⟩
1114+
constructor
1115+
have : d ≠ 0 := mod_cast hb.den_nz
1116+
rw [hb.to_eq rfl rfl, div_eq_mul_inv, Real.rpow_natCast_mul, ← Nat.cast_pow, hm, ← hkl, ← hr,
1117+
Nat.cast_pow, Real.pow_rpow_inv_natCast] <;> positivity
1118+
1119+
theorem IsNNRat.rpow_isNNRat (a b : ℝ) (na da : ℕ) (ha : IsNNRat a na da)
1120+
(nr dr : ℕ) (hnum : IsNat ((na : ℝ) ^ b) nr) (hden : IsNat ((da : ℝ) ^ b) dr) :
1121+
IsNNRat (a ^ b) nr dr := by
1122+
suffices IsNNRat (nr / dr : ℝ) nr dr by
1123+
simpa [ha.to_eq, Real.div_rpow, hnum.1, hden.1]
1124+
apply IsNNRat.of_raw
1125+
rw [← hden.1]
1126+
apply (Real.rpow_pos_of_pos _ _).ne'
1127+
exact lt_of_le_of_ne' da.cast_nonneg ha.den_nz
1128+
1129+
theorem rpow_isRat_eq_inv_rpow (a b : ℝ) (n d : ℕ) (hb : IsRat b (Int.negOfNat n) d) :
1130+
a ^ b = (a⁻¹) ^ (n / d : ℝ) := by
1131+
rw [← Real.rpow_neg_eq_inv_rpow, hb.neg_to_eq rfl rfl]
1132+
1133+
open Lean in
1134+
/-- Given proofs
1135+
- that `a` is a natural number `na`;
1136+
- that `b` is a nonnegative rational number `nb / db`;
1137+
returns a tuple of
1138+
- a natural number `r` (result);
1139+
- the same number, as an expression;
1140+
- a proof that `a ^ b = r`.
1141+
1142+
Fails if `na` is not a `db`th power of a natural number.
1143+
-/
1144+
def proveIsNatRPowIsNNRat
1145+
(a : Q(ℝ)) (na : Q(ℕ)) (pa : Q(IsNat $a $na))
1146+
(b : Q(ℝ)) (nb db : Q(ℕ)) (pb : Q(IsNNRat $b $nb $db)) :
1147+
MetaM (ℕ × Σ r : Q(ℕ), Q(IsNat ($a ^ $b) $r)) := do
1148+
let r := (Nat.nthRoot db.natLit! na.natLit!) ^ nb.natLit!
1149+
have er : Q(ℕ) := mkRawNatLit r
1150+
-- avoid evaluating powers in kernel
1151+
let .some ⟨c, pc⟩ ← liftM <| OptionT.run <| evalNatPow er db | failure
1152+
let .some ⟨d, pd⟩ ← liftM <| OptionT.run <| evalNatPow na nb | failure
1153+
guard (c.natLit! = d.natLit!)
1154+
have hcd : Q($c = $d) := (q(Eq.refl $c) : Expr)
1155+
return (r, ⟨er, q(IsNat.rpow_isNNRat $pa $pb $c $pc $d $pd $hcd)⟩)
1156+
1157+
/-- Evaluates expressions of the form `a ^ b` when `a` and `b` are both reals.
1158+
Works if `a`, `b`, and `a ^ b` are in fact rational numbers,
1159+
except for the case `a < 0`, `b` isn't integer.
1160+
1161+
TODO: simplify terms like `(-a : ℝ) ^ (b / 3 : ℝ)` and `(-a : ℝ) ^ (b / 2 : ℝ)` too,
1162+
possibly after first considering changing the junk value. -/
10901163
@[norm_num (_ : ℝ) ^ (_ : ℝ)]
1091-
def evalRPow : NormNumExt where eval {u α} e := do
1092-
let .app (.app f (a : Q(ℝ))) (b : Q(ℝ)) ← Lean.Meta.whnfR e | failure
1093-
guard <|← withNewMCtxDepth <| isDefEq f q(HPow.hPow (α := ℝ) (β := ℝ))
1094-
haveI' : u =QL 0 := ⟨⟩
1095-
haveI' : $α =Q ℝ := ⟨⟩
1096-
haveI' h : $e =Q $a ^ $b := ⟨⟩
1097-
h.check
1098-
let (rb : Result b) ← derive (α := q(ℝ)) b
1099-
match rb with
1100-
| .isBool .. | .isNNRat .. | .isNegNNRat .. => failure
1101-
| .isNat sβ nb pb =>
1102-
match ← derive q($a ^ $nb) with
1103-
| .isBool .. => failure
1104-
| .isNat sα' ne' pe' =>
1105-
assumeInstancesCommute
1106-
haveI' : $sα' =Q AddGroupWithOne.toAddMonoidWithOne := ⟨⟩
1107-
return .isNat sα' ne' q(isNat_rpow_pos $pb $pe')
1108-
| .isNegNat sα' ne' pe' =>
1109-
assumeInstancesCommute
1110-
return .isNegNat sα' ne' q(isInt_rpow_pos $pb $pe')
1111-
| .isNNRat sα' qe' nume' dene' pe' =>
1112-
assumeInstancesCommute
1113-
return .isNNRat sα' qe' nume' dene' q(isNNRat_rpow_pos $pb $pe')
1114-
| .isNegNNRat sα' qe' nume' dene' pe' =>
1115-
assumeInstancesCommute
1116-
return .isNegNNRat sα' qe' nume' dene' q(isRat_rpow_pos $pb $pe')
1117-
| .isNegNat sβ nb pb =>
1118-
match ← derive q($a ^ (-($nb : ℤ))) with
1119-
| .isBool .. => failure
1120-
| .isNat sα' ne' pe' =>
1164+
def evalRPow : NormNumExt where eval {u αR} e := do
1165+
match u, αR, e with
1166+
| 0, ~q(ℝ), ~q(($a : ℝ)^($b : ℝ)) =>
1167+
match ← derive b with
1168+
| .isNat sβ nb pb =>
11211169
assumeInstancesCommute
1122-
return .isNat sα' ne' q(isNat_rpow_neg $pb $pe')
1123-
| .isNegNat sα' ne' pe' =>
1124-
let _ := q(Real.instRing)
1170+
return .eqTrans q(IsNat.rpow_eq_pow $pb _) (← derive q($a ^ $nb))
1171+
| .isNegNat sβ nb pb =>
11251172
assumeInstancesCommute
1126-
return .isNegNat sα' ne' q(isInt_rpow_neg $pb $pe')
1127-
| .isNNRat sα' qe' nume' dene' pe' =>
1173+
return .eqTrans q(IsInt.rpow_eq_inv_pow $pb _) (← derive q(($a ^ $nb)⁻¹))
1174+
| .isNNRat _ qb nb db pb => do
11281175
assumeInstancesCommute
1129-
return .isNNRat sα' qe' nume' dene' q(isNNRat_rpow_neg $pb $pe')
1130-
| .isNegNNRat sα' qe' nume' dene' pe' =>
1176+
match ← derive a with
1177+
| .isNat sa na pa => do
1178+
let ⟨_, r, pr⟩ ← proveIsNatRPowIsNNRat a na pa b nb db pb
1179+
return .isNat sa r pr
1180+
| .isNNRat _ qα na da pa => do
1181+
assumeInstancesCommute
1182+
let ⟨rnum, ernum, pnum⟩ ←
1183+
proveIsNatRPowIsNNRat q(Nat.rawCast $na) na q(IsNat.of_raw _ _) b nb db pb
1184+
let ⟨rden, erden, pden⟩ ←
1185+
proveIsNatRPowIsNNRat q(Nat.rawCast $da) da q(IsNat.of_raw _ _) b nb db pb
1186+
return .isNNRat q(inferInstance) (rnum / rden) ernum erden
1187+
q(IsNNRat.rpow_isNNRat $a $b $na $da $pa $ernum $erden $pnum $pden)
1188+
| _ => failure
1189+
| .isNegNNRat _ qb nb db pb => do
1190+
let r ← derive q(($a⁻¹) ^ ($nb / $db : ℝ))
11311191
assumeInstancesCommute
1132-
return .isNegNNRat sα' qe' nume' dene' q(isRat_rpow_neg $pb $pe')
1192+
return .eqTrans q(rpow_isRat_eq_inv_rpow $a $b $nb $db $pb) r
1193+
| _ => failure
1194+
| _ => failure
11331195

11341196
end Mathlib.Meta.NormNum
11351197

MathlibTest/norm_num_rpow.lean

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,23 @@ example : (-1/3 : ℝ) ^ (-3 : ℝ) = -27 := by norm_num1
1313
example : (1/2 : ℝ) ^ (-3 : ℝ) = 8 := by norm_num1
1414
example : (2 : ℝ) ^ (-3 : ℝ) = 1/8 := by norm_num1
1515
example : (-2 : ℝ) ^ (-3 : ℝ) = -1/8 := by norm_num1
16+
17+
example : (8 : ℝ) ^ (2 / 6 : ℝ) = 2 := by norm_num1
18+
example : (0 : ℝ) ^ (1 / 3 : ℝ) = 0 := by norm_num1
19+
example : (8 / 27 : ℝ) ^ (1 / 3 : ℝ) = 2 / 3 := by norm_num1
20+
example : (8 : ℝ) ^ (-1 / 3 : ℝ) = 1 / 2 := by norm_num1
21+
example : (8 / 27 : ℝ) ^ (-1 / 3 : ℝ) = 3 / 2 := by norm_num1
22+
example : (1 / 27 : ℝ) ^ (-1 / 3 : ℝ) = 3 := by norm_num1
23+
24+
example : (0 : ℝ) ^ (0 : ℝ) = 1 := by norm_num1
25+
example : (0 : ℝ) ^ (1 / 3 : ℝ) = 0 := by norm_num1
26+
example : (0 : ℝ) ^ (-1 / 3 : ℝ) = 0 := by norm_num1
27+
28+
-- The following example is incorrect with the current Mathlib definition.
29+
/--
30+
error: unsolved goals
31+
⊢ (-8) ^ (1 / 3) = -2
32+
-/
33+
#guard_msgs in
34+
example : (-8 : ℝ) ^ (1 / 3 : ℝ) = -2 := by
35+
norm_num1

0 commit comments

Comments
 (0)