File tree Expand file tree Collapse file tree
Lean/Meta/Tactic/Grind/Arith/Cutsat Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -980,6 +980,10 @@ theorem dvd_norm (ctx : Context) (d : Int) (p₁ p₂ : Poly) : p₁.norm.beq' p
980980 intro h₁
981981 simp [Poly.denote_norm ctx p₁, h₁]
982982
983+ theorem eq_of_zero_dvd (ctx : Context) (p : Poly) : 0 ∣ p.denote' ctx → p.denote' ctx = 0 := by
984+ intro ⟨k, h⟩
985+ rw [h, Int.zero_mul]
986+
983987theorem le_norm (ctx : Context) (p₁ p₂ : Poly) (h : p₁.norm.beq' p₂) : p₁.denote' ctx ≤ 0 → p₂.denote' ctx ≤ 0 := by
984988 simp at h
985989 replace h := congrArg (Poly.denote ctx) h
Original file line number Diff line number Diff line change @@ -62,6 +62,12 @@ partial def DvdCnstr.assert (c : DvdCnstr) : GoalM Unit := withIncRecDepth do
6262 if c.isTrivial then
6363 trace[grind.lia.assert.trivial] "{ ← c.pp} "
6464 return ()
65+ if c.d == 0 then
66+ -- `0 ∣ p` is equivalent to `p = 0`. The model search assumes `d ≠ 0` for
67+ -- stored divisibility constraints (it computes `_ % d` and `_ / d`).
68+ let c' : EqCnstr := { p := c.p, h := .ofZeroDvd c }
69+ c'.assert
70+ return ()
6571 let d₁ := c.d
6672 let .add a₁ x p₁ := c.p | c.throwUnexpected
6773 if (← c.satisfied) == .false then
Original file line number Diff line number Diff line change @@ -360,6 +360,9 @@ private partial def EqCnstr.toExprProofImpl (c' : EqCnstr) : ProofM Expr := cach
360360 return mkApp6 (mkConst ``Int.Internal.Linear.eq_of_le_ge)
361361 (← getContext) (← mkPolyDecl c₁.p) (← mkPolyDecl c₂.p)
362362 eagerReflBoolTrue (← c₁.toExprProof) (← c₂.toExprProof)
363+ | .ofZeroDvd c =>
364+ return mkApp3 (mkConst ``Int.Internal.Linear.eq_of_zero_dvd)
365+ (← getContext) (← mkPolyDecl c.p) (← c.toExprProof)
363366 | .reorder c => withUnordered <| c.toExprProof
364367 | .commRingNorm c e p =>
365368 let h := mkApp4 (mkConst ``Grind.CommRing.norm_int) (← getRingContext) (← mkRingExprDecl e) (← mkRingPolyDecl p) eagerReflBoolTrue
@@ -635,6 +638,7 @@ partial def EqCnstr.collectDecVars (c' : EqCnstr) : CollectDecVarsM Unit := do u
635638 | .core0 .. | .core .. | .defn .. | .defnNat ..
636639 | .defnCommRing .. | .defnNatCommRing .. | .coreToInt .. => return () -- Equalities coming from the core never contain cutsat decision variables
637640 | .commRingNorm c .. | .reorder c | .norm c | .divCoeffs c | .div _ _ c | .mod _ _ c => c.collectDecVars
641+ | .ofZeroDvd c => c.collectDecVars
638642 | .subst _ c₁ c₂ | .ofLeGe c₁ c₂ => c₁.collectDecVars; c₂.collectDecVars
639643 | .mul _ cs => cs.forM fun (_, _, c) => c.collectDecVars
640644 | .pow _ ca? _ cb? => ca?.forM (·.collectDecVars); cb?.forM (·.collectDecVars)
Original file line number Diff line number Diff line change @@ -90,6 +90,8 @@ inductive EqCnstrProof where
9090 | divCoeffs (c : EqCnstr)
9191 | subst (x : Var) (c₁ : EqCnstr) (c₂ : EqCnstr)
9292 | ofLeGe (c₁ : LeCnstr) (c₂ : LeCnstr)
93+ | /-- `p = 0` derived from the divisibility constraint `c` of the form `0 ∣ p`. -/
94+ ofZeroDvd (c : DvdCnstr)
9395 | reorder (c : EqCnstr)
9496 | commRingNorm (c : EqCnstr) (e : CommRing.RingExpr) (p : CommRing.Poly)
9597 | defnCommRing (e : Expr) (p : Poly) (re : CommRing.RingExpr) (rp : CommRing.Poly) (p' : Poly)
Original file line number Diff line number Diff line change 1+ /-!
2+ Tests for divisibility constraints with divisor `0` in `grind`'s cutsat procedure.
3+ Cutsat used to store `0 ∣ p` constraints, but the model search assumes a nonzero
4+ divisor: it could loop forever in `DvdSolution.geAvoiding` (the original example
5+ below), or weaken bounds via `tightUsingDvd` and miss refutations. `0 ∣ p` is now
6+ converted into the equality `p = 0` when asserted.
7+ -/
8+
9+ -- Used to hang: `0 ∣ b` with `b ≠ 0` excludes the single solution of the
10+ -- divisibility constraint, and the model search looped bumping the candidate.
11+ theorem mwe {b : Nat} (p : Nat) (x1 x2 : Nat) (hb0 : b ≠ 0 ) (hab : 0 ∣ b) :
12+ 0 / p ^ x1 ∣ b / p ^ x2 := by
13+ grind
14+
15+ -- Used to fail: the diseq was folded into the bound `b ≥ 1`, which `tightUsingDvd`
16+ -- then weakened back to `b ≥ 0` using the `0 ∣ b` constraint, producing the bogus
17+ -- model `b := 0`.
18+ example (b : Nat) (h1 : b ≠ 0 ) (h2 : 0 ∣ b) : False := by
19+ grind
20+
21+ example (a b : Nat) (h : 0 ∣ a + b) (h1 : a + b ≠ 0 ) : False := by
22+ grind
You can’t perform that action at this time.
0 commit comments