prune of a quotient ring recomputes everything on every call, where prune of a module caches
- Dominant language
- Macaulay2
- Stars
- 435
- Forks
- 297
- Avg merge
- 4d 20h
- Merged PRs (30d)
- 11
Description
This issue was triaged from [`bugs/mike/0-quotient-rings.m2`](https://github.com/Macaulay2/M2/blob/388c1ff0ce30d83751dea7bc7eac77fdc1305dd7/bugs/mike/0-quotient-rings.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 (95 lines)
```m2
gbTrace = 3
S = ZZ/32003[a..d]
I = ideal random(S^1, S^{5:-8});
time trim I;
time gens gb I;
time R = S/I -- PROBLEM: we do not want to copy the GB!!
time trim R; -- recomputing the GB FIX. GB no longer recomputed, but same problem as previous line.
-- one PROBLEM: gens gb I recomputes the matrix each time.
time prune R; -- recomputes GB FIX
time flattenRing R; -- very fast
f = presentation R; -- this seems to come with the GB, that is good
keys f.cache
time gens gb f;
I1 = ideal R; -- this seems to come with the GB, that is good
time gens gb I1;
J = ideal random(R^1, R^{3:-8});
time B = R/J;
time flattenRing B; -- the engine knows a non-reduced GB of this. We should be able
-- to just minimalize this rather than recomputing.
time C = R[x,y]; -- quite fast, so this is possibly OK.
time flattenRing C; -- takes a while: FIX. The engine knows this one.
end
excerpt from email: 3/3/09 to Dan from Mike
David and I are working on improving integral closure, hopefully to a point
where it is actually useful.
In the process, I have run into some inefficiencies in our code involving
quotient rings. The main problem (I think!) is that we are forgetting groebner
basis information about our quotients. I'm not sure if this is for me or or you
to fix, but probably something that together we can do faster.
Here are some situations that cause problems now:
1. Suppose that B is already a quotient ring S/I, and that we make a new ring C
= B/J. Then: 'D = (flattenRing C)_0' seems to be recomputing a Groebner
basis although the engine knows what it is (I think). (In the example I
have just been working on, this recomputation took 161 seconds!)
2. In the situation of (1), 'E = trim D' should just change the presenting
ideal, but the GB itself will not change. Also, perhaps the engine object
should not change either. This step cost 321 seconds!
3. Given a quotient ring R = S/I, I should be able to get the GB of I. Can we
do that easily?
4. minimalPresentation E (this is code I wrote) in the above example took 322
seconds, probably recomputing the GB.
I'll look into these a bit more, but fixing this should improve ALOT of
computations involving towers of rings!
-- Other problems:
a. minPressy should do almost NOTHING if it detects that nothing has changed.
b. trim QuotientRing: (in matrix2.m2. Why there???!)
presentation Ring: should return a matrix with a GB. Does it?
trim Ideal: if the ideal already has a GB, then trim should use the trim of that!
then when quotient-ing by this ideal a GB doesnt have to be recomputed.
R[x]: if R is a quotient ring, check to make sure that a GB is not recomputed.
------------------------------------------------------------------
-- engine to do about this:
- GBobject --> quotient poly ring (R/I)
-- (GBObject, polyring) --> quotient poly ring (R[x]/I, where I \subset R)
-- (quotient ring, coeff ring, GB object) --> GB object
-- selectInSubring on a GBObject gives a GBObject
G : GBObject of an ideal
quotientRing G --> quotient ring
quotientRing --> G
(G, G') --> elements of G which are not div by leadterms of elems of G'
(n, G) --> G' (select in subring)
(G,G') --> G'' -- concatenate the GB's "above" and including G, but outside of G'.
-- All of these should be GB's of ideals, in component 0 or 1.
getGBComputation(PolynomialRing) returns GBComputation
makeQuotientRing(GBComputation) returns PolynomialRing
subtractGB(GBComputation, GBComputation) returns GBComputation
selectInSubring(int,GBComputation) returns GBComputation
addGB(GBComputation, GBComputation) returns GBComputation
copyGB(GBComputation) returns GBComputation -- is this needed?
```
### Where it stands today
`prune` on a quotient ring recomputes everything on every call. The module version caches; the ring
version does not.
```m2
S = ZZ/32003[a,b,c,d];
I = ideal random(S^1, S^{5:-8});
R = S/I;
M = coker vars R;
for i to 4 do elapsedTime prune R;
for i to 4 do elapsedTime prune M;
```
```
-- .868023s elapsed <- prune R
-- .838359s elapsed
-- .89107s elapsed
-- .874442s elapsed
-- .888008s elapsed
-- .082846s elapsed <- prune M
-- .00000909s elapsed
-- .00000235s elapsed
-- .00000223s elapsed
-- .00000291s elapsed
```
Each `prune R` costs about what the Gröbner basis of `I` costs on its own (0.95 s here), and
`prune R === prune R` is false, so a fresh ring is built every time.
### Cause
`minPressyRing` caches the isomorphism *maps* on `R` but never the ring it just computed:
https://github.com/Macaulay2/M2/blob/development/M2/Macaulay2/m2/minPres.m2#L390-L404
`R.minimalPresentationMap` and `R.minimalPresentationMapInv` are set — verified: `R.?minimalPresentationMap`
is false before the first call and true after — but the returned `finalRing` is discarded, so the next
call redoes `minPressy(ideal R, ...)` and overwrites those maps with equivalent ones.
Modules already have exactly this caching, through `hasMinPres`:
https://github.com/Macaulay2/M2/blob/development/M2/Macaulay2/m2/modules2.m2#L129
added in [#4134](https://github.com/Macaulay2/M2/pull/4134). Rings were not given the analogue, which
is why the two columns above differ by five orders of magnitude after the first call.
### Where this came from
Cataloguing the `bugs/` directory removed in d2c8d27826 (#36). `bugs/mike/0-quotient-rings.m2` is
Mike's 2009 timing session on Gröbner basis information being lost around quotient rings, sent to Dan
with the note *"we are forgetting groebner basis information about our quotients"*. Its line 9 is
```m2
time prune R; -- recomputes GB FIX
```
The rest of that file has since been addressed, which is worth saying so nobody re-opens it wholesale:
`R = S/I` no longer copies the Gröbner basis (0.024 s), `trim R` caches (0.86 s then 0.019 s),
`flattenRing R` is instant, and `C = R[x,y]` is fast. `flattenRing C` still costs a full Gröbner basis
on first call, about 1.0 s, but caches thereafter.
`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
Research direction
Start in M2/Macaulay2/m2/minPres.m2 at minPressyRing and compare its caching with hasMinPres in M2/Macaulay2/m2/modules2.m2. Reproduce the issue with the quotient-ring and module prune loops in the report, then verify that repeated prune R calls reuse the computed result rather than rebuilding the ring and Gröbner basis.
Written by the indexing model from the issue text.
Assessment
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100