fix: div/norm normalization assumptions in grind (#9919)

This PR ensures `grind cutsat` does not rely on div/mod terms to have
been normalized. The `grind` preprocessor has normalizers for them, but
sometimes they cannot be applied because of type dependencies.

Closes #9907
This commit is contained in:
Leonardo de Moura 2025-08-14 15:28:25 -07:00 committed by GitHub
parent 76971a88ff
commit fe7e0859d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 7 deletions

View file

@ -956,6 +956,12 @@ theorem neg_mul_ediv_cancel_left (a b : Int) (h : a ≠ 0) : -(a * b) / a = -b :
@[simp] theorem emod_one (a : Int) : a % 1 = 0 := by
simp [emod_def, Int.one_mul, Int.sub_self]
theorem ediv_minus_one (a : Int) : a / (-1) = -a := by
simp
theorem emod_minus_one (a : Int) : a % (-1) = 0 := by
simp
@[deprecated sub_emod_right (since := "2025-04-11")]
theorem emod_sub_cancel (x y : Int) : (x - y) % y = x % y :=
sub_emod_right ..

View file

@ -396,15 +396,36 @@ private def internalizeInt (e : Expr) : GoalM Unit := do
c.assert
private def expandDivMod (a : Expr) (b : Int) : GoalM Unit := do
if b == 0 || b == 1 || b == -1 then
throwError "`grind` internal error, found non-normalized div/mod by {b}"
if (← get').divMod.contains (a, b) then return ()
modify' fun s => { s with divMod := s.divMod.insert (a, b) }
let n : Int := 1 - b.natAbs
let b := mkIntLit b
pushNewFact <| mkApp2 (mkConst ``Int.Linear.ediv_emod) a b
pushNewFact <| mkApp3 (mkConst ``Int.Linear.emod_nonneg) a b eagerReflBoolTrue
pushNewFact <| mkApp4 (mkConst ``Int.Linear.emod_le) a b (toExpr n) eagerReflBoolTrue
let b' ← shareCommon (mkIntLit b)
if b == 0 || b == 1 || b == -1 then
/-
We cannot assume terms have been normalized.
Recall that terms may not be normalized because of dependencies.
-/
let gen ← getGeneration a
internalize b' gen
let ediv ← shareCommon (mkIntDiv a b'); internalize ediv gen
let emod ← shareCommon (mkIntMod a b'); internalize emod gen
if b == 0 then
pushEq emod a <| mkApp (mkConst ``Int.emod_zero) a
pushEq ediv b' <| mkApp (mkConst ``Int.ediv_zero) a
else if b == 1 then
let zero ← shareCommon (mkIntLit 0); internalize zero gen
pushEq emod zero <| mkApp (mkConst ``Int.emod_one) a
pushEq ediv a <| mkApp (mkConst ``Int.ediv_one) a
else
assert! b == -1
let zero ← shareCommon (mkIntLit 0); internalize zero gen
let neg_a ← shareCommon (mkIntNeg a); internalize neg_a gen
pushEq emod zero <| mkApp (mkConst ``Int.emod_minus_one) a
pushEq ediv neg_a <| mkApp (mkConst ``Int.ediv_minus_one) a
else
let n : Int := 1 - b.natAbs
pushNewFact <| mkApp2 (mkConst ``Int.Linear.ediv_emod) a b'
pushNewFact <| mkApp3 (mkConst ``Int.Linear.emod_nonneg) a b' eagerReflBoolTrue
pushNewFact <| mkApp4 (mkConst ``Int.Linear.emod_le) a b' (toExpr n) eagerReflBoolTrue
private def propagateDiv (e : Expr) : GoalM Unit := do
let_expr HDiv.hDiv _ _ _ inst a b ← e | return ()

View file

@ -0,0 +1,15 @@
structure DepThing {α : Type u} (l : List α) : Type u where
suffix : List α
property : suffix = l
example (n : Nat) (c : DepThing (List.range' 1 (n/1))) (h : 0 < c.suffix.length) : True := by
grind
opaque p (n : Int) : Type
opaque q (n : Int) (h : p n) : Prop
example (h : p (a / 0)) : q (a / 0) h → q (a / 0) h := by grind
example (h : p (a / -1)) : q (a / -1) h → q (a / -1) h := by grind
example (h : p (a / 1)) : q (a / 1) h → q (a / 1) h := by grind
example (h₁ : p (a / 0)) (h₂ : p 0) : h₁ ≍ h₂ → q (a / 0) h₁ → q 0 h₂ := by grind
example (h₁ : p (a / 1)) (h₂ : p a) : h₁ ≍ h₂ → q (a / 1) h₁ → q (a) h₂ := by grind
example (h₁ : p (a / -1)) (h₂ : p (-a)) : h₁ ≍ h₂ → q (a / -1) h₁ → q (-a) h₂ := by grind