Zulip Chat Archive

Stream: mathlib4

Topic: reduce_mod_char doesn't work


Floris van Doorn (Mar 15 2024 at 15:14):

I don't know how to get the tactic reduce_mod_char to do anything for me in a (variable) ring with a CharP hypothesis. @Anne Baanen

import Mathlib.Tactic

example {R} [CommRing R] [CharP R 3] (x : R) : x + x + x + x = x := by
  ring
  reduce_mod_char -- no progress

example {R} [CommRing R] [CharP R 2] (x y : R) : (x + y) ^ 2 = x ^ 2 + y ^ 2 := by
  ring
  reduce_mod_char -- no progress

example {R : Type*} [Ring R] [CharP R 2] : (2 : R) = 0 := by
  reduce_mod_char -- no progress

example {R : Type*} [Ring R] [CharP R 2] (r : R) : 2  r = 0 := by
  reduce_mod_char -- no progress

example {R : Type*} [Ring R] [CharP R 2] (r : R) : (2 : )  r = 0 := by
  reduce_mod_char -- no progress

Markus Himmel (Mar 15 2024 at 21:59):

I don't think reduce_mod_char searches for a CharP instance at all. It contains a hardcoded list of supported types:

/-- Determine the characteristic of a ring from the type.
This should be fast, so this pattern-matches on the type, rather than searching for a
`CharP` instance. -/
partial def typeToCharP (t : Q(Type u)) : TypeToCharPResult t :=
match Expr.getAppFnArgs t with
| (``ZMod, #[(n : Q())]) => .intLike n
  (q((ZMod.commRing _).toRing) : Q(Ring (ZMod $n)))
  (q(ZMod.charP _) : Q(CharP (ZMod $n) $n))
| (``Polynomial, #[(R : Q(Type u)), _]) => match typeToCharP R with
  | (.intLike n _ _) => .intLike n
    (q(Polynomial.ring) : Q(Ring (Polynomial $R)))
    (q(Polynomial.instCharP _) : Q(CharP (Polynomial $R) $n))
  | .failure => .failure
| _ => .failure

Alex J. Best (Mar 16 2024 at 00:45):

Huh, that seems like it should be easy to support (iirc Anne's Lean 3 version did support this, and it certainly seems coded up with that in mind also)

Anne Baanen (Mar 18 2024 at 09:13):

(I posted this reply a couple days ago, but I can't see it now so I'll repost. Sorry for possibly spamming the topic.)

Markus is right. The reason that this is not supported currently is that the tactic avoids searching for a CharP instance on every type of every subexpression, which saves quite a bit of time in my testcases. I suppose adding a reduce_mod_char! variant that does do the instance search would be an easy fix. (Or maybe the user can pass the type on which to operate?)

Anne Baanen (Mar 18 2024 at 10:02):

I tried implementing the typeclass search approach and ran into an issue: docs#CharP in CharP R n doesn't have n as an outParam, so it's not supposed to be assignable during instance search. So when we ask for an instance of CharP R ?n with [CharP R 2] in the context, the unification ?n =?= 2 fails. Is there a workaround?

Yaël Dillies (Mar 18 2024 at 10:02):

Try typeclass search with every possible n? :speak_no_evil:

Anne Baanen (Mar 18 2024 at 10:13):

(Modifying the depth of the metavariable after creating it doesn't work, because it's subsequently checked in the original depth, so the metavariable won't be assignable during that check.)

Anne Baanen (Mar 18 2024 at 10:15):

Here's the code I came up with:

partial def typeToCharP (expensive := false) (t : Q(Type u)) : MetaM (TypeToCharPResult t) :=
match Expr.getAppFnArgs t with
| (``ZMod, #[(n : Q())]) =>
  return .intLike n
    (q((ZMod.commRing _).toRing) : Q(Ring (ZMod $n)))
    (q(ZMod.charP _) : Q(CharP (ZMod $n) $n))
| (``Polynomial, #[(R : Q(Type u)), _]) => do match  typeToCharP (expensive := expensive) R with
  | (.intLike n _ _) =>
    return .intLike n
      (q(Polynomial.ring) : Q(Ring (Polynomial $R)))
      (q(Polynomial.instCharP _) : Q(CharP (Polynomial $R) $n))
  | .failure => return .failure
| _ => if ! expensive then return .failure else do
  -- Fallback: run an expensive procedures to determine a characteristic,
  -- by looking for a `CharP` instance.
  withNewMCtxDepth do
    /- If we want to support semirings, here we could implement the `natLike` fallback. -/
    let .some instRing  trySynthInstanceQ q(Ring $t) | return .failure

    let n  mkFreshExprMVarQ q()
    /-
    -- Evil hack because we do want `n` to be assigned by `synthInstance`.
    modifyMCtx fun mctx => mctx.modifyExprMVarDecl n.mvarId!
      fun decl => { decl with depth := decl.depth + 1 }
    -/
    let .some instCharP  trySynthInstanceQ q(CharP $t $n) | return .failure

    return .intLike ( instantiateMVarsQ n) instRing instCharP

Yaël Dillies (Mar 18 2024 at 10:15):

Can't you create a new metavariable at the correct depth, unify that one, then unify it with the old one too?

Yaël Dillies (Mar 18 2024 at 10:16):

The point being that both unifications can be done at different depths

Anne Baanen (Mar 18 2024 at 10:20):

synthInstance does both unifications internally, so I don't see how to pass in two different metavariables in one call.

Yaël Dillies (Mar 18 2024 at 10:21):

Do the first one using synthInstance, the second one manually?

Anne Baanen (Mar 18 2024 at 10:23):

No no, I mean that synthInstance unifies CharP R ?n =?= CharP R 2 twice: once inside a withNewMCtxDepth (when trying candidate instances) and once outside (when checking that the resulting instance is correct). I don't see a way to separate these out without reimplementing a large part of the instance synthesis functions.

Alex J. Best (Mar 18 2024 at 10:35):

Is docs#ringChar.charP the only reason we don't just make the n an out param? Maybe its time we reconsidered how this is all set up

Anne Baanen (Mar 18 2024 at 10:43):

Yes, I assume the problem is that we might get equal-but-not-defeq values for ?n from a CharP R ?n goal. But that shouldn't be a big issue, right?

Anne Baanen (Mar 18 2024 at 10:45):

Here's a PR that makes the first few examples in this thread work: #11478. The last ones require support for (n, z, ...)-smul which we can do in a separate, and hopefully easier, PR.

Floris van Doorn (Mar 20 2024 at 11:18):

Anne Baanen said:

(I posted this reply a couple days ago, but I can't see it now so I'll repost. Sorry for possibly spamming the topic.)

Markus is right. The reason that this is not supported currently is that the tactic avoids searching for a CharP instance on every type of every subexpression, which saves quite a bit of time in my testcases. I suppose adding a reduce_mod_char! variant that does do the instance search would be an easy fix. (Or maybe the user can pass the type on which to operate?)

Other tactics like ring and norm_num also search for a lot of type classes, so that doesn't sound like an inherent problem for a performant tactic.
The fact that the n is a metavariable during type-class search is a problem of course.
I think looking for variables in the local context is a good compromise.


Last updated: May 02 2025 at 03:31 UTC