Skip to content

Commit 44faade

Browse files
DavidLedvinkaDavid Ledvinka
andcommitted
feat(Tactic): linarith and rify for NNReal (leanprover-community#35155)
Co-authored-by: David Ledvinka <dledvinka.ledvinka@mail.utoronto.ca>
1 parent 7f14c97 commit 44faade

9 files changed

Lines changed: 190 additions & 2 deletions

File tree

Mathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6805,6 +6805,7 @@ public import Mathlib.Tactic.Linarith
68056805
public import Mathlib.Tactic.Linarith.Datatypes
68066806
public import Mathlib.Tactic.Linarith.Frontend
68076807
public import Mathlib.Tactic.Linarith.Lemmas
6808+
public import Mathlib.Tactic.Linarith.NNRealPreprocessor
68086809
public import Mathlib.Tactic.Linarith.Oracle.FourierMotzkin
68096810
public import Mathlib.Tactic.Linarith.Oracle.SimplexAlgorithm
68106811
public import Mathlib.Tactic.Linarith.Oracle.SimplexAlgorithm.Datatypes

Mathlib/Data/NNReal/Basic.lean

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ typeclass. For lemmas about subtraction and addition see lemmas about `OrderedSu
140140
theorem sub_div (a b c : ℝ≥0) : (a - b) / c = a / c - b / c :=
141141
tsub_div _ _ _
142142

143+
/-- This lemma is needed for the `norm_cast` simp set. Outside of this use case `Nat.coe_sub`
144+
should be used. -/
145+
@[norm_cast]
146+
protected theorem coe_sub_of_lt {a b : ℝ≥0} (h : a < b) :
147+
((b - a : ℝ≥0) : ℝ) = b - a := NNReal.coe_sub h.le
148+
143149
end Sub
144150

145151
section Csupr
@@ -210,6 +216,18 @@ set_option backward.isDefEq.respectTransparency false in
210216

211217
end Csupr
212218

219+
section rify
220+
221+
@[rify_simps] lemma toReal_eq (a b : ℝ≥0) : a = b ↔ (a : ℝ) = (b : ℝ) := by simp
222+
223+
@[rify_simps] lemma toReal_le (a b : ℝ≥0) : a ≤ b ↔ (a : ℝ) ≤ (b : ℝ) := by simp
224+
225+
@[rify_simps] lemma toReal_lt (a b : ℝ≥0) : a < b ↔ (a : ℝ) < (b : ℝ) := by simp
226+
227+
@[rify_simps] lemma toReal_ne (a b : ℝ≥0) : a ≠ b ↔ (a : ℝ) ≠ (b : ℝ) := by simp
228+
229+
end rify
230+
213231
@[simp]
214232
theorem range_coe : range toReal = Ici 0 := Subtype.range_coe
215233

Mathlib/Tactic.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public import Mathlib.Tactic.Linarith
138138
public import Mathlib.Tactic.Linarith.Datatypes
139139
public import Mathlib.Tactic.Linarith.Frontend
140140
public import Mathlib.Tactic.Linarith.Lemmas
141+
public import Mathlib.Tactic.Linarith.NNRealPreprocessor
141142
public import Mathlib.Tactic.Linarith.Oracle.FourierMotzkin
142143
public import Mathlib.Tactic.Linarith.Oracle.SimplexAlgorithm
143144
public import Mathlib.Tactic.Linarith.Oracle.SimplexAlgorithm.Datatypes
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/-
2+
Copyright (c) 2026 David Ledvinka. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: David Ledvinka
5+
-/
6+
module
7+
8+
public meta import Mathlib.Tactic.Linarith
9+
public meta import Mathlib.Tactic.Rify
10+
public import Mathlib.Data.NNReal.Basic
11+
12+
/-!
13+
# NNReal linarith preprocessing
14+
15+
This file contains a `linarith` preprocessor for converting (in)equalities in `ℝ≥0` to `ℝ`.
16+
17+
By overriding the behaviour of the placeholder preprocessor `nnrealToReal` (which does nothing
18+
unless this file is imported) `linarith` can still be used without importing `NNReal`.
19+
-/
20+
21+
public meta section
22+
23+
namespace Mathlib.Tactic.Linarith
24+
25+
open Lean Meta
26+
27+
/--
28+
`isNNRealProp tp` is true iff `tp` is an inequality or equality between nonnegative real numbers
29+
or the negation thereof.
30+
-/
31+
partial def isNNRealProp (e : Expr) : MetaM Bool := succeeds do
32+
let (_, _, .const ``NNReal _, _, _) ← e.ineqOrNotIneq? | failure
33+
34+
/-- If `e` is of the form `((x : ℝ≥0) : ℝ)`, `NNReal.toReal e` returns `x`. -/
35+
def isNNRealtoReal (e : Expr) : Option Expr :=
36+
match e with
37+
| .app (.const ``NNReal.toReal _) n => some n
38+
| _ => none
39+
40+
/--
41+
`getNNRealComparisons e` returns a list of all subexpressions of `e` of the form `(x : ℝ)`.
42+
-/
43+
partial def getNNRealCoes (e : Expr) : List Expr :=
44+
match isNNRealtoReal e with
45+
| some x => [x]
46+
| none => match e.getAppFnArgs with
47+
| (``HAdd.hAdd, #[_, _, _, _, a, b]) => getNNRealCoes a ++ getNNRealCoes b
48+
| (``HMul.hMul, #[_, _, _, _, a, b]) => getNNRealCoes a ++ getNNRealCoes b
49+
| (``HSub.hSub, #[_, _, _, _, a, b]) => getNNRealCoes a ++ getNNRealCoes b
50+
| (``HDiv.hDiv, #[_, _, _, _, a, _]) => getNNRealCoes a
51+
| (``Neg.neg, #[_, _, a]) => getNNRealCoes a
52+
| _ => []
53+
54+
/-- If `e : ℝ≥0`, returns a proof of `0 ≤ (e : ℝ)`. -/
55+
def mk_toReal_nonneg_prf (e : Expr) : MetaM (Option Expr) :=
56+
try commitIfNoEx (mkAppM ``NNReal.coe_nonneg #[e])
57+
catch e => do
58+
trace[linarith] "Got exception when using `coe_nonneg` {e.toMessageData}"
59+
return none
60+
61+
initialize nnrealToRealTransform.set fun l => do
62+
let l ← l.mapM fun e => do
63+
let t ← whnfR (← instantiateMVars (← inferType e))
64+
if ← isNNRealProp t then
65+
return (← Rify.rifyProof e t).1
66+
else
67+
return e
68+
let atoms : List Expr ← withNewMCtxDepth <| AtomM.run .reducible do
69+
for e in l do
70+
let (_, _, a, b) ← (← inferType e).ineq?
71+
discard <| (getNNRealCoes a).mapM AtomM.addAtom
72+
discard <| (getNNRealCoes b).mapM AtomM.addAtom
73+
return (← get).atoms.toList
74+
let nonneg_pfs : List Expr ← atoms.filterMapM mk_toReal_nonneg_prf
75+
return nonneg_pfs ++ l
76+
77+
end Mathlib.Tactic.Linarith

Mathlib/Tactic/Linarith/Preprocessing.lean

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,23 @@ def removeNe : GlobalBranchingPreprocessor where
389389
transform := removeNe_aux
390390
end removeNe
391391

392+
/-- Definition overidden in `Mathlib.Tactic.Linarith.NNRealPreprocessor`. -/
393+
initialize nnrealToRealTransform : IO.Ref (List Expr → MetaM (List Expr)) ← IO.mkRef pure
394+
395+
/--
396+
If `h` is an equality or inequality between NNReals, `nnrealToReal` lifts this inequality to the
397+
Reals. It also adds the facts that the reals involved are nonnegative. To avoid adding the same
398+
nonnegativity facts many times, it is a global preprocessor. This preprocessor does nothing unless
399+
`Mathlib.Tactic.Linarith.NNRealPreprocessor` is imported -/
400+
def nnrealToReal : GlobalPreprocessor where
401+
description := "move nnreals to reals"
402+
transform l := do (← nnrealToRealTransform.get) l
392403

393404
/--
394405
The default list of preprocessors, in the order they should typically run.
395406
-/
396407
def defaultPreprocessors : List GlobalBranchingPreprocessor :=
397-
[filterComparisons, removeNegations, natToInt, strengthenStrictInt,
408+
[filterComparisons, removeNegations, nnrealToReal, natToInt, strengthenStrictInt,
398409
compWithZero, cancelDenoms]
399410

400411
/--

Mathlib/Tactic/Rify.lean

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ module
77

88
public import Mathlib.Data.Rat.Cast.Order
99
public import Mathlib.Data.Real.Basic
10+
public import Mathlib.Tactic.Zify
1011
public import Mathlib.Tactic.Qify -- shake: keep (for `@[qify_simps]`)
1112

1213
/-!
1314
# `rify` tactic
1415
15-
The `rify` tactic is used to shift propositions from `ℕ`, `ℤ` or `` to `ℝ`.
16+
The `rify` tactic is used to shift propositions from `ℕ`, `ℤ`, `ℚ` or `ℝ≥0` to `ℝ`.
1617
1718
Although less useful than its cousins `zify` and `qify`, it can be useful when your
1819
goal or context already involves real numbers.
@@ -78,11 +79,32 @@ macro_rules
7879
simp -decide only [zify_simps, qify_simps, rify_simps, push_cast, $args,*]
7980
$[at $location]?)
8081

82+
/-- The `Simp.Context` generated by `rify` (with no optional arguments or local context). -/
83+
def mkRifyContext : MetaM Simp.Context := do
84+
let result ← #[`zify_simps, `qify_simps, `rify_simps, `push_cast].mapM fun ext ↦ do
85+
let some ext ← getSimpExtension? ext | failure
86+
ext.getTheorems
87+
Simp.mkContext {failIfUnchanged := false} (simpTheorems := result)
88+
89+
/-- Translate a proof and the proposition into rified form. -/
90+
def rifyProof (proof : Expr) (prop : Expr) : MetaM (Expr × Expr) := do
91+
let (r, _) ← simp prop (← mkRifyContext)
92+
Zify.applySimpResultToProp' proof prop r
93+
8194
@[rify_simps] lemma ratCast_eq (a b : ℚ) : a = b ↔ (a : ℝ) = (b : ℝ) := by simp
8295
@[rify_simps] lemma ratCast_le (a b : ℚ) : a ≤ b ↔ (a : ℝ) ≤ (b : ℝ) := by simp
8396
@[rify_simps] lemma ratCast_lt (a b : ℚ) : a < b ↔ (a : ℝ) < (b : ℝ) := by simp
8497
@[rify_simps] lemma ratCast_ne (a b : ℚ) : a ≠ b ↔ (a : ℝ) ≠ (b : ℝ) := by simp
8598

99+
/- The following lemmas are included in `Mathlib.Data.NNReal.Basic` (so that they
100+
don't need to be imported when using this tactic for `ℕ, ℤ`, or `ℚ`)
101+
102+
`@[rify_simps] lemma toReal_eq (a b : ℝ≥0) : a = b ↔ (a : ℝ) = (b : ℝ) := by simp`
103+
`@[rify_simps] lemma toReal_le (a b : ℝ≥0) : a ≤ b ↔ (a : ℝ) ≤ (b : ℝ) := by simp`
104+
`@[rify_simps] lemma toReal_lt (a b : ℝ≥0) : a < b ↔ (a : ℝ) < (b : ℝ) := by simp`
105+
`@[rify_simps] lemma toReal_ne (a b : ℝ≥0) : a ≠ b ↔ (a : ℝ) ≠ (b : ℝ) := by simp`
106+
-/
107+
86108
@[rify_simps] lemma ofNat_rat_real (a : ℕ) [a.AtLeastTwo] :
87109
((ofNat(a) : ℚ) : ℝ) = (ofNat(a) : ℝ) := rfl
88110

MathlibTest/Linarith/NNReal.lean

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Mathlib.Tactic.Linarith.NNRealPreprocessor
2+
import Mathlib.Data.ENNReal.Operations
3+
import Mathlib.Data.ENNReal.Inv
4+
5+
open NNReal
6+
7+
example {a b c : ℝ≥0} (h1 : 2 * a < b + 1) (h2 : b ≤ c) : a < (c + 1) / 2 := by
8+
linarith
9+
10+
example {a b c d : ℝ≥0} (h1 : 3 * a + 2 * b ≤ 5 * c + 7) (h2 : c ≤ d) : a ≤ (5 * d + 7) / 3 := by
11+
linarith
12+
13+
example {a b c d e : ℝ≥0} (h1 : (a + b) / 2 + c ≤ d) (h2 : d ≤ e) : a ≤ 2 * e := by
14+
linarith
15+
16+
example {a b c d : ℝ≥0} (h1 : (a + 3 * b) / 4 < c + 1) (h2 : c ≤ d) : a < 4 * (d + 1) := by
17+
linarith
18+
19+
example {a b c d : ℝ≥0} (h1 : 2 * a + b ≤ 3 * c) (h2 : c < d + 5) : a < (3 * (d + 5)) / 2 := by
20+
linarith
21+
22+
example {a b c d : ℝ≥0} (h1 : a + b ≤ c) (h2 : c ≤ d / 2) : a ≤ d / 2 := by
23+
linarith
24+
25+
example {a b c d : ℝ≥0} (h1 : a + b ≤ 2 * c + 3) (h2 : c ≤ d) : a ≤ 2 * d + 3 := by
26+
linarith

MathlibTest/Rify.lean

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Mathlib.Tactic.Linarith
22
import Mathlib.Tactic.Rify
3+
import Mathlib.Data.NNReal.Basic
34

45
set_option linter.unusedVariables false
56

@@ -36,3 +37,34 @@ example {n k : ℕ} (h₁ : 8 ≤ n) (h₂ : 2 * k > n) (h₃ : k + 1 < n) :
3637
have f₂ : n - (k + 1) ≤ n := by rify [f₁]; linarith
3738
rify [f₁, f₂] at *
3839
linarith
40+
41+
/- ℝ≥0 Tests -/
42+
43+
open NNReal
44+
45+
example {a : ℝ≥0} {b : ℝ} (ha : 8 ≤ a) (hb : 2 * b ≤ a + 2) :
46+
(0 : ℝ) < a - b - 1 := by
47+
rify at ha hb
48+
linarith
49+
50+
example {a : ℝ≥0} {b : ℝ} (ha : 8 ≤ a) (hb : (2 : ℚ) * b ≤ b + 2) :
51+
(0 : ℝ) < a - b - 1 := by
52+
rify at ha hb
53+
linarith
54+
55+
example (a b c : ℝ≥0) (h : a - b < c) (hab : b ≤ a) : a < b + c := by
56+
rify [hab] at h ⊢
57+
linarith
58+
59+
example {a : ℝ≥0} (h : 8 ≤ a) : (0 : ℝ) < a - 1 := by
60+
rify at h
61+
linarith
62+
63+
example {a b : ℝ≥0} (h : 2 * b ≤ a + 2) (h' : 8 ≤ a) : (0 : ℝ) ≤ 3 * a - 4 - 4 * b := by
64+
rify at *
65+
linarith
66+
67+
example {a b : ℝ≥0} (h₁ : 8 ≤ a) (h₂ : 2 * b > a) (h₃ : b + 1 < a) :
68+
a - (b + 1) + 3 ≤ a := by
69+
rify [h₃] at *
70+
linarith

0 commit comments

Comments
 (0)