@@ -156,8 +156,30 @@ def EqCnstr.simplifyUsingNumEq0 (c : EqCnstr) : RingM EqCnstr := do
156156 let .num k := c'.p | return c
157157 return { c with p := c.p.normEq0 k.natAbs, h := .numEq0 k.natAbs c' c }
158158
159- /-- Simplify the given equation constraint using the current basis. -/
160- def EqCnstr.simplify (c : EqCnstr) : RingM EqCnstr := do
159+ /--
160+ Simplify the given equation constraint using the current basis.
161+
162+ **Note** : This function (and `simplifyBasis`) runs with `checkCoeffDvd := true`. It rewrites a
163+ monomial `k'*m'` using an equation with leading term `k*m` only if `k ∣ k'` (or the ring
164+ implements `NoNatZeroDivisors`). Rewriting without this restriction requires multiplying the
165+ equation by `k/gcd k k' ≠ ±1` first, and *replacing* an equation with the result loses
166+ information: `p = 0` cannot be recovered from `(k/gcd k k') * p = 0` in an arbitrary
167+ commutative ring. The consequences of the skipped rewrites are not lost: a skipped rewrite at
168+ the leading monomial coincides with the S-polynomial computed by `superposeWith`, so it is
169+ derived non-destructively. The price is that the basis is not
170+ fully interreduced (e.g., `2*x = 0` and `3*x = 0` may coexist), and reduction is an incomplete
171+ ideal-membership test in rings without `NoNatZeroDivisors` (e.g., `x = 0` is not derived from
172+ `2*x = 0` and `3*x = 0`). The complete alternative is a strong Gröbner basis over `Int`
173+ (Kandri-Rody & Kapur): replace multiply-through rewriting with Euclidean remainder reduction
174+ on coefficients (`k' = q*k + r`, so the equation is never multiplied by a constant), and
175+ additionally superpose gcd-polynomials (Bézout combinations producing `gcd k k'` times the lcm
176+ of the leading monomials). We should consider this option in the future.
177+
178+ **Note** : If using `withCheckCoeffDvd` becomes too expensive when one does not have
179+ `NoNatZeroDivisors`, we will add a flag to perform the simplification anyway even if
180+ information is lost.
181+ -/
182+ def EqCnstr.simplify (c : EqCnstr) : RingM EqCnstr := withCheckCoeffDvd do
161183 let mut c := c
162184 repeat
163185 c ← simplifyUsingNumEq0 c
@@ -186,6 +208,14 @@ def EqCnstr.checkConstant (c : EqCnstr) : RingM Bool := do
186208 if let some c' := (← getCommRing).numEq0? then
187209 let .num m := c'.p | unreachable!
188210 let (g, a, b) := gcdExt n m
211+ if g == m then
212+ /-
213+ `n` is a multiple of the current `numEq0`; the new constraint is redundant.
214+ We must not set `numEq0Updated` here: superposition may keep regenerating the
215+ same constant, and requeueing the basis each time would loop until `maxSteps`.
216+ -/
217+ trace_goal[grind.ring.assert.trivial] "{ ← c.denoteExpr} "
218+ return true
189219 c := { c with p := .num g, h := .gcd a b c c' }
190220 modifyCommRing fun s => { s with numEq0? := some c, numEq0Updated := true }
191221 trace_goal[grind.ring.assert.store] "{ ← c.denoteExpr} "
@@ -220,6 +250,12 @@ def addToBasisCore (c : EqCnstr) : RingM Unit := do
220250
221251def EqCnstr.addToQueue (c : EqCnstr) : RingM Unit := do
222252 if (← checkMaxSteps) then return ()
253+ /-
254+ Superposition may produce constant polynomials since the basis is not fully
255+ interreduced (see **Note** at `EqCnstr.simplify`). `checkConstant` processes them
256+ immediately; the queue must not contain constant polynomials.
257+ -/
258+ if (← c.checkConstant) then return ()
223259 trace_goal[grind.ring.assert.queue] "{ ← c.denoteExpr} "
224260 if (← checkMaxDegree c.p) then return () -- discard
225261 modifyCommRing fun s => { s with queue := s.queue.insert c }
@@ -265,7 +301,13 @@ def EqCnstr.toMonic (c : EqCnstr) : RingM EqCnstr := do
265301 return { c with p := c.p.mulConst (-1 ), h := .mul (-1 ) c }
266302 return c
267303
268- def EqCnstr.simplifyBasis (c : EqCnstr) : RingM Unit := do
304+ /--
305+ Simplifies the basis using `c`. Every equation that `c` rewrites is removed from the basis
306+ and requeued in its rewritten form.
307+
308+ See **Note** at `EqCnstr.simplify` for why `checkCoeffDvd` must be enabled here.
309+ -/
310+ def EqCnstr.simplifyBasis (c : EqCnstr) : RingM Unit := withCheckCoeffDvd do
269311 trace[grind.debug.ring.simpBasis] "using: { ← c.denoteExpr} "
270312 let .add _ m _ := c.p | return ()
271313 let rec go (basis : List EqCnstr) (acc : List EqCnstr) : RingM (List EqCnstr) := do
@@ -275,11 +317,20 @@ def EqCnstr.simplifyBasis (c : EqCnstr) : RingM Unit := do
275317 match c'.p with
276318 | .add _ m' _ =>
277319 if m.divides m' then
278- let c'' ← c'.simplifyWithExhaustively c
279- trace[grind.debug.ring.simpBasis] "simplified: { ← c''.denoteExpr} "
280- unless (← checkConstant c'') do
281- addToQueue c''
282- go basis acc
320+ /-
321+ **Note** : Only the first `simplifyWithCore` step is inspected because it decides basis
322+ membership: if it returns `none` (the leading monomial divides, but the rewrite is blocked by
323+ the `checkCoeffDvd` restriction), `c'` must remain in the basis untouched.
324+ -/
325+ if let some c'' ← c'.simplifyWithCore c then
326+ let c'' ← c''.simplifyWithExhaustively c
327+ trace[grind.debug.ring.simpBasis] "simplified: { ← c''.denoteExpr} "
328+ unless (← checkConstant c'') do
329+ addToQueue c''
330+ go basis acc
331+ else
332+ -- The rewrite was skipped because of the `checkCoeffDvd` restriction. Keep `c'`.
333+ go basis (c' :: acc)
283334 else
284335 go basis (c' :: acc)
285336 | _ => go basis (c' :: acc)
0 commit comments