Zulip Chat Archive
Stream: mathlib4
Topic: Instability of the denominator in `field_simp` tactic
Weiyi Wang (Dec 24 2025 at 13:59):
(here is a demo. Not exactly my real code but it reproduces the problem
import Mathlib
variable (x y u r z : ℂ)
example (h : -2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2) ≠ 0 ) :
(1 / (-2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2))) = z + 3 / (-2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2)) := by
field_simp
sorry
)
First I have this in the context
have hdeno : -2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2) ≠ 0 = by ...
(all involved variables are complex numbers, if it matters)
I have a goal of a rational equation involving the same set of variables, and the only ones that appear in the denominator are the one proven non-zero above.
However, when I do field_simp, it couldn't resolve all the denominators. When I looked closely, I see that it randomly permutates the term order in the denominator, leaving me to prove things such as
have deno' : (-(2 * r ^ 2 * ((u + r) ^ 2 - 1) * y * (r * x - u)) +
(r * x + u) * (r * x * (r * (u + r) * x + 2 * (1 - r * (u + r))) + u ^ 2 * (u + r))) ≠ 0 := by
and it is not a consistent order, either. I have two sub goals with similar expression, and field_simp permutates it in different way.
I could add more auxiliary haves to make it work, but I am concerned that this will randomly break after updates. Is there a better way to teach field_simp to look for all possible permutation of non-zero lemmas?
Kevin Buzzard (Dec 24 2025 at 14:13):
(deleted)
Michael Rothgang (Dec 25 2025 at 09:32):
I presume what happens is that your have is not in "field-simp normal form", and what happens is that
- field_simp converts the goal into normal form
- this changes the denominator into normal form also
- as your
haveis not in field_simp normal form, it cannot be used to discharge the non-zeroness hypotheses
You can
- change your
havestatement to bring it into field_simp normal form, or - do so automatically using
field_simp at h, or - use
setto extract this as a constant.
I'm not sure what exactly your question is. Does that answer it?
Michael Rothgang (Dec 25 2025 at 09:32):
Here's code for the "field_simp at h" approach:
import Mathlib
variable (x y u r z : ℂ)
example (h : -2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2) ≠ 0 ) :
(1 / (-2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2))) = z + 3 / (-2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2)) := by
field_simp at h ⊢
sorry
Michael Rothgang (Dec 25 2025 at 09:32):
Here's how to write it using set:
import Mathlib
variable (x y u r z : ℂ)
example (h : -2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2) ≠ 0 ) :
(1 / (-2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2))) = z + 3 / (-2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2)) := by
set A := -2 * r ^ 2 * ((u + r) ^ 2 - 1) * (r * x - u) * y +
(r * x + u) *
(r ^ 2 * (u + r) * x ^ 2 + 2 * r * (1 - r * (u + r)) * x + (u + r) * u ^ 2)
field_simp
-- goal is now "1 = A * z + 3"
sorry
Michael Rothgang (Dec 25 2025 at 09:32):
This question is independent of real or complex numbers, by the way. (Making all variables real does not change the behaviour here.)
Michael Rothgang (Dec 25 2025 at 09:33):
I agree that field_simp could be more helpful by telling you which denominators I cannot prove are non-zero. This will be coming; @Arend Mellendijk is working on that.
Michael Rothgang (Dec 25 2025 at 09:34):
Let me also add that the field_simp normal form is not random (though I don't remember the precise details how it is decided). In general, you should not have to rely on it too much - and with one of the strategies above, you indeed need not.
Weiyi Wang (Dec 25 2025 at 12:10):
Thanks! I do have a case where the normal form is seemingly random (as in, the same denominator got transformed differently in two expressions). I haven't been able to minimize it, but I can soon upload the code for further investigation
Weiyi Wang (Dec 25 2025 at 12:13):
I also wish field_simp can have a mode where I can say "I guarantee that all denominators are not zero. Could you try harder with the list of hypothesis I provided? You might need to use ring to transform it, or multiply it by -1"
Ruben Van de Velde (Dec 25 2025 at 13:36):
There's been discussion before about having an option to field_simp that leaves subgoals for any denominators that it can't prove to be nonzero on its own. That seems like a more scalable approach
Weiyi Wang (Dec 25 2025 at 13:39):
@Michael Rothgang Here is an example of seemingly inconsistent normal form: https://github.com/wwylele/Poncelet/blob/0532e61afdc89a0032978b07fa53eaf9c3a08385/Poncelet/TransferAux.lean#L73-L74
fieldon line 73 requires the presence ofhdeno'on line 35field_simpon line 74 requires the presence ofhdeno''on line 40field_simpon line 107 requires the presence ofhdeno'''on line 45
By commenting out either hypothesis, you can observe the error on the corresponding tactic.
(warning: this file also contains very ugly long expressions that makes vscode slow. You can comment those out first and it won't impact this problem)
Weiyi Wang (Dec 25 2025 at 13:43):
Let me try the approaches you suggested
Weiyi Wang (Dec 25 2025 at 13:49):
The set approach works pretty well! and I think it also makes my code cleaner. Funnily enough, this also resulted in another weird inconsistency: the field on line 35 still doesn't work after I use set to hide the denominator, and it seems that it can see through it, but when I replaced it with field_simp it worked (It is just a giant A = A / B * B expression so I guess it doesn't need extra ring power)
Last updated: Feb 28 2026 at 14:05 UTC