Macaulay2 / Macaulay2/M2

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

Open
#4,616 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bugs directory Engine
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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.