getNonUnit is empty and the error generic when dividing by a zero divisor in a univariate toField ring, but not when a spare variable is added
Nobody has claimed this yet.
- Dominant language
- Macaulay2
- Stars
- 435
- Forks
- 297
- Avg merge
- 4d 20h
- Merged PRs (30d)
- 11
Description
This issue was triaged from [`bugs/mike/0-GF-division.m2`](https://github.com/Macaulay2/M2/blob/388c1ff0ce30d83751dea7bc7eac77fdc1305dd7/bugs/mike/0-GF-division.m2), one of the 857 files removed from the pre-GitHub `bugs/` tree by [`d2c8d27826`](https://github.com/Macaulay2/M2/commit/d2c8d27826) and catalogued in [#36](https://github.com/Macaulay2/M2/issues/36). **The commentary below was written by Claude (Claude Opus 5, via Claude Code)**, not by @d-torrance, whose account posted it -- please weigh it accordingly.
### The original file, verbatim
```m2
-- After changing the choice of gb for 'toField' rings, the
-- following fails:
R = ZZ/101[a]
F = a^2+a+1
factor F
A = R/F
k = toField A
assert( isField k )
assert( class a === k )
1/a
assert( oo * a == 1 )
(a-1) * (1/(a-1))
assert( oo == 1 )
1//a
assert( oo * a == 1 )
1//(1-a)
assert( oo * (1-a) == 1 )
R = ZZ/101[a]/(a^2-1)
k = toField R
assert( isField k )
assert( class a === k )
1/a
assert( oo * a == 1 )
1//a
assert( oo == a )
1//(1-a)
assert( oo != 0 ) -- still failing
1/(a-1)
assert( oo * (a-1) == 1 ) -- still failing
-- bug: one of these operations should say something about finding a zero divisor!!
```
### Where it stands today
When `toField` is applied to a ring that is not in fact a field, dividing by a zero divisor is
diagnosed well over a multivariate quotient and poorly over a univariate one. Adding an unused second
variable to the same ring changes both the error message and whether the offending element is
recorded:
```m2
i1 : A = ZZ/101[u]/(u^2-1);
i2 : k = toField A;
i3 : 1/(u-1)
stdio:3:1:(3):[1]: error: either element not invertible, or no method available to compute its inverse
i4 : getNonUnit k
i5 : B = ZZ/101[p,q]/(p^2-1);
i6 : l = toField B;
i7 : 1/(p-1)
stdio:7:1:(3):[1]: error: a non unit was found in a ring declared to be a field
i8 : getNonUnit l
o8 = p - 1
o8 : l
```
`u^2-1 = (u-1)(u+1)`, so `u-1` is a zero divisor and erroring is correct in both cases. The difference
is what the user is told. Over `B` the message names the actual situation and `getNonUnit` hands back
the element, which is exactly what that function exists for. Over `A` the message is the engine's
generic fallback and `getNonUnit` returns `null`, so there is no way to find out which element caused
it.
The same split appears with `QQ[s]/(s^2-1)` (generic message, `getNonUnit` null) versus
`ZZ/101[w,z]/(w*z)` (field-specific message, `getNonUnit` returns `w+z`), so it tracks the number of
variables rather than the coefficient ring or the shape of the relation.
### Where the paths diverge
The good path runs through `Ring::set_non_unit`, which records the element and then raises:
https://github.com/Macaulay2/M2/blob/development/M2/Macaulay2/e/rings/ring.cpp#L88-L95
The univariate path never reaches it, and errors from the generic fallback in `Ring::power` instead:
https://github.com/Macaulay2/M2/blob/development/M2/Macaulay2/e/rings/ring.cpp#L127-L139
Since `get_non_unit` returns zero unless `set_non_unit` has run, `getNonUnit` comes back empty.
This looks like the tail of the same machinery fixed in
[#4259](https://github.com/Macaulay2/M2/pull/4259) (`4cbb7fef7d`, 2026-05-05), which made
`set_non_unit` throw rather than only set the engine's error flag, and which set the recorded element
before throwing so that `getNonUnit` is populated on the way out. That fix works on the multivariate
path; the univariate one does not get there.
### Where this came from
Cataloguing the `bugs/` directory removed in d2c8d27826 (#36). `bugs/mike/0-GF-division.m2` works
through division in `toField` rings and ends:
```
-- bug: one of these operations should say something about finding a zero divisor!!
```
That request is now met over multivariate quotients and unmet over univariate ones. The rest of the
file has resolved: its first block, over the genuine field `ZZ/101[a]/(a^2+a+1)`, passes all six of
its assertions, and the two assertions it marks `-- still failing` in the second block are both asking
for the wrong thing — `1//(1-a)` returning `0` is correct division with remainder, since
`1%(1-a) == 1` and `0*(1-a) + 1 == 1`, and `1/(a-1)` failing an equality test is a consequence of its
raising, which it should.
`open` · disposition `issue` · source of truth: [`bug-triage/catalog.tsv`](https://github.com/d-torrance/M2/blob/bug-triage/bug-triage/catalog.tsv)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in M2/Macaulay2/e/rings/ring.cpp, comparing Ring::set_non_unit with the fallback in Ring::power and get_non_unit. Reproduce the univariate and multivariate quotient examples from the issue, then add or update coverage for the univariate path. Done means zero-divisor division reports the specific situation and getNonUnit returns the offending element consistently.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100