From ad1e87247dcaf7e1a39f036905c680dff9526888 Mon Sep 17 00:00:00 2001 From: Mike Stillman Date: Mon, 1 Jun 2026 15:14:37 -0400 Subject: [PATCH 1/4] Add in lazy differentials of complexes, which required a new (lazy) constructor for complex maps. The only use so far is for differentials in free resolutions. Needed to change code of the form `applyValues(C.dd.map, ...)` as that is incompatible with lazy evaluation. --- .../packages/Complexes/ChainComplex.m2 | 38 +++++-- .../packages/Complexes/ChainComplexMap.m2 | 101 ++++++++++++++---- .../packages/Complexes/ChainComplexMapDoc.m2 | 16 +-- .../packages/Complexes/ChainComplexTests.m2 | 59 ++++++++++ .../packages/Complexes/FreeResolution.m2 | 32 ++++-- M2/Macaulay2/packages/Complexes/Tor.m2 | 12 +-- M2/Macaulay2/packages/SpectralSequences.m2 | 46 ++++---- .../packages/Varieties/SheafComplexes.m2 | 29 +++-- 8 files changed, 252 insertions(+), 81 deletions(-) diff --git a/M2/Macaulay2/packages/Complexes/ChainComplex.m2 b/M2/Macaulay2/packages/Complexes/ChainComplex.m2 index 68adb5e8aa8..e49568fb54b 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplex.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplex.m2 @@ -16,7 +16,7 @@ Complex = new Type of MutableHashTable -- -- missing ones are presumed to be the zero module. -- cache: a CacheTable -ComplexMap = new Type of HashTable +ComplexMap = new Type of MutableHashTable -- keys: -- degree: ZZ -- source: Complex over a ring R @@ -111,6 +111,27 @@ complex HashTable := Complex => complexOptions >> opts -> maps -> ( C.dd = map(C,C,maps,Degree=>-1); C ) +complex(HashTable, Function) := Complex => complexOptions >> opts -> (modules, mapfcn) -> ( + spots := sort keys modules; + if #spots === 0 then + error "expected at least one module"; + if not all(spots, k -> instance(k,ZZ)) then + error "expected modules to be labelled by integers"; + if not uniform values modules then + error "expected hash table of modules"; + if not same(ring \ values modules) then + error "expected all modules to be over the same ring"; + R := ring modules#(spots#0); + C := new Complex from { + symbol ring => R, + -- TODO: rename module to category agnostic term + symbol module => new HashTable from modules, + symbol concentration => (first spots, last spots), + symbol cache => new CacheTable + }; + C.dd = map(C, C, mapfcn, 23982138, Degree=>-1); -- the integer grabs the lazy maps version. + C + ) complex List := Complex => complexOptions >> opts -> L -> ( -- L is a list of matrices or a list of modules if not instance(opts.Base, ZZ) then @@ -258,7 +279,12 @@ isWellDefined Complex := Boolean => C -> ( ); return false; ); - if not all(keys (dd^C).map, i -> instance(i,ZZ) and i >= lo+1 and i <= hi) then ( + if member(symbol Function, keys (dd^C).map) then ( + if debugLevel > 0 then ( + << "-- lazy differential present" << endl; + ); + ); + if not all(keys (dd^C).map, i -> (i === symbol Function) or (instance(i,ZZ) and i >= lo+1 and i <= hi)) then ( if debugLevel > 0 then ( << "-- expected all maps in the differential to be indexed by integers in the concentration [lo+1,hi]" << endl; ); @@ -792,8 +818,6 @@ truncate(List, Complex) := Complex => truncateModuleOpts >> opts -> (degs, C) -> (lo, hi) := C.concentration; if lo == hi then complex(truncate(degs, C_lo, opts), Base => lo) - -- this is the simplest way to truncate the whole complex: - -- else complex applyValues(C.dd.map, f -> truncate(degs, f, opts))) else ( -- this construction requires ~half as many truncations f := truncate(degs, dd^C_lo, opts); @@ -814,8 +838,6 @@ basis(List, Complex) := Complex => opts -> (deg, C) -> ( (lo, hi) := C.concentration; if lo == hi then complex(image basis(deg, C_lo, opts), Base => lo) - -- this is the simplest way to take the basis of the whole complex: - -- else complex applyValues(C.dd.map, f -> basis(deg, f, opts))) else ( -- this construction requires ~half as many basis computations f := basis(deg, dd^C_lo, opts); @@ -832,9 +854,9 @@ importFrom_Core "residueMap" -- gives a map back to the coefficient ring cover' = method() cover' Complex := Complex => C -> ( (lo, hi) := concentration C; - if lo == hi + if lo === hi then complex(cover C_lo, Base => lo) - else complex applyValues(C.dd.map, cover)) + else complex(for i from lo+1 to hi list cover C.dd_i, Base => lo)) cover' ComplexMap := ComplexMap => f -> ( map(cover' target f, cover' source f, i -> cover f_i, Degree => degree f)) diff --git a/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 b/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 index 2a2c2c9a1e3..58feef44b5a 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 @@ -6,7 +6,10 @@ target ComplexMap := Complex => f -> f.target ring ComplexMap := Complex => f -> ring source f degree ComplexMap := ZZ => f -> f.degree -isHomogeneous ComplexMap := (f) -> all(values f.map, isHomogeneous) +isHomogeneous ComplexMap := (f) -> ( + (lo, hi) := concentration f; + all(lo..hi, i -> isHomogeneous f_i) + ) map(Complex, Complex, HashTable) := ComplexMap => opts -> (tar, src, maps) -> ( R := ring tar; @@ -80,6 +83,7 @@ map(Complex, Complex, List) := ComplexMap => opts -> (tar, src, maps) -> ( map(tar,src,mapHash,opts, Degree=>deg) ) +-- TODO: remove this version map(Complex, Complex, Function) := ComplexMap => opts -> (D,C,f) -> ( deg := if opts.Degree === null then 0 else opts.Degree; (loC,hiC) := concentration C; @@ -93,6 +97,28 @@ map(Complex, Complex, Function) := ComplexMap => opts -> (D,C,f) -> ( map(D,C,maps,Degree=>deg) ) +-- the integer 4th argument is a hack to try out lazy evaluation of matrices +map(Complex, Complex, Function, ZZ) := ComplexMap => opts -> (tar,src,f,ignored) -> ( + deg := if opts.Degree === null then 0 else opts.Degree; + (loSrc,hiSrc) := concentration src; + (loTar,hiTar) := concentration tar; + mapfcn := i -> ( + if i < max(loSrc,loTar-deg) or i > min(hiSrc,hiTar-deg) then return null; + if src_i == 0 or tar_(i+deg) == 0 then return null; + g := f i; + if g === null or g == 0 then return null; + g + ); + maps := new MutableHashTable from {symbol Function => mapfcn}; + new ComplexMap from { + symbol source => src, + symbol target => tar, + symbol degree => deg, + symbol map => maps, + symbol cache => new CacheTable + } + ) + map(Complex, Complex, ZZ) := ComplexMap => opts -> (D, C, j) -> ( if j === 0 then ( result := map(D,C,hashTable{},opts); @@ -103,10 +129,23 @@ map(Complex, Complex, ZZ) := ComplexMap => opts -> (D, C, j) -> ( return j * id_C; error "expected 0 or source and target to be the same") +-- map(Complex, Complex, ComplexMap) := ComplexMap => opts -> (tar, src, f) -> ( +-- deg := if opts.Degree === null then degree f else opts.Degree; +-- H := hashTable for k in keys f.map list k => map(tar_(deg+k), src_k, f.map#k); -- TODO: fix me +-- map(tar,src,H, Degree=>deg) +-- ) + map(Complex, Complex, ComplexMap) := ComplexMap => opts -> (tar, src, f) -> ( deg := if opts.Degree === null then degree f else opts.Degree; - H := hashTable for k in keys f.map list k => map(tar_(deg+k), src_k, f.map#k); - map(tar,src,H, Degree=>deg) + if f.map.?Function then ( + -- is lazy + mapfcn := k -> map(tar_(deg+k), src_k, f.map.Function k); + map(tar, src, mapfcn, 324732984, Degree => deg) -- TODO: get rid of magic number + ) + else ( + H := hashTable for k in keys f.map list k => map(tar_(deg+k), src_k, f.map#k); + map(tar,src,H, Degree=>deg) + ) ) ComplexMap | ComplexMap := ComplexMap => (f,g) -> ( @@ -170,7 +209,12 @@ isWellDefined ComplexMap := f -> ( return false; ); (lo,hi) := f.source.concentration; - if not all(keys f.map, i -> instance(i,ZZ) and i >= lo and i <= hi) then ( + if member(symbol Function, keys f.map) then ( + if debugLevel > 0 then ( + << "-- lazy maps present" << endl; + ); + ); + if not all(keys f.map, i -> (i === symbol Function) or (instance(i,ZZ) and i >= lo and i <= hi)) then ( if debugLevel > 0 then ( << "-- expected all maps to be indexed by integers in the concentration [lo,hi] of the source" << endl; ); @@ -227,32 +271,43 @@ isWellDefined ComplexMap := f -> ( lineOnTop := (s) -> concatenate(width s : "-") || s expression ComplexMap := Expression => f -> ( + (lo, hi) := concentration f; d := degree f; - s := sort keys f.map; - if #s === 0 then - new ZeroExpression from {0} - else new VerticalList from for i in s list + new VerticalList from for i from lo to hi list RowExpression {i+d, ":", MapExpression { target f_i, source f_i, f_i }, ":", i} ) net ComplexMap := Net => f -> ( - v := between("", - for i in sort keys f.map list ( - horizontalJoin( - net (i+f.degree), " : ", net target f_i, " <--", - lineOnTop net f_i, - "-- ", net source f_i, " : ", net i - ) - )); - if # v === 0 then net "0" - else stack v - ) + (lo, hi) := concentration f; + v := between("", + for i from lo to hi list ( + horizontalJoin( + net (i+f.degree), " : ", net target f_i, " <--", + lineOnTop net f_i, + "-- ", net source f_i, " : ", net i + ) + )); + if # v === 0 then net "0" + else stack v + ) texMath ComplexMap := String => f -> texMath expression f mathML ComplexMap := String => f -> mathML expression f +-- ComplexMap _ ZZ := Matrix => (f,i) -> ( +-- if f.map#?i then f.map#i else map((target f)_(i + degree f), (source f)_i, 0)) + ComplexMap _ ZZ := Matrix => (f,i) -> ( - if f.map#?i then f.map#i else map((target f)_(i + degree f), (source f)_i, 0)) + if f.map#?i then return f.map#i; + if f.map.?Function then ( + if debugLevel > 0 then + << "creating " << i << "-th differential" << endl; + g := f.map.Function i; + if g =!= null then return f.map#i = g + ); + map((target f)_(i + degree f), (source f)_i, 0) + ) + ComplexMap ^ ZZ := ComplexMap => (f,n) -> ( (lo,hi) := (source f).concentration; df := degree f; @@ -296,12 +351,12 @@ ComplexMap == ComplexMap := (f,g) -> ( true ) ComplexMap == ZZ := Boolean => (f,n) -> ( - if n === 0 then - all(keys f.map, k -> f.map#k == 0) + (lo,hi) := (source f).concentration; + if n === 0 then + all(lo..hi, k -> f_k == 0) else if n === 1 then ( if source f != target f then return false; if degree f =!= 0 then return false; - (lo,hi) := (source f).concentration; for i from lo to hi do if f_i != 1 then return false; f.cache.isCommutative = true; -- this is the identity, after all! diff --git a/M2/Macaulay2/packages/Complexes/ChainComplexMapDoc.m2 b/M2/Macaulay2/packages/Complexes/ChainComplexMapDoc.m2 index b12cb6747d7..6b11e2df865 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplexMapDoc.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplexMapDoc.m2 @@ -356,7 +356,7 @@ doc /// Using this function to create the identity map is the same as using @TO (id, Complex)@. Example - assert(map(C, C, 1) === id_C) + assert(map(C, C, 1) == id_C) SeeAlso ComplexMap (map, Complex, Complex, Function) @@ -871,8 +871,8 @@ doc /// f = g1 ++ g2 assert isWellDefined f L = components f - L_0 === g1 - L_1 === g2 + L_0 == g1 + L_1 == g2 indices f f' = (greg => g1) ++ (mike => g2) components f' @@ -1398,7 +1398,7 @@ doc /// then the identity map of the corresponding complex is used. Example fE = f ** E - assert(fE === f ** id_E) + assert(fE == f ** id_E) k = coker vars S gk = g ** k assert(gk == g ** id_(complex k)) @@ -2130,7 +2130,7 @@ doc /// Text This is really a shorthand for constructing complex maps via block matrices. Example - assert(h === map(D, C1 ++ C2, {{f,g}})) + assert(h == map(D, C1 ++ C2, {{f,g}})) SeeAlso (symbol++, Complex, Complex) (symbol++, ComplexMap, ComplexMap) @@ -2173,7 +2173,7 @@ doc /// Text This is really a shorthand for constructing complex maps via block matrices. Example - assert(h === map(D1 ++ D2, C, {{f},{g}})) + assert(h == map(D1 ++ D2, C, {{f},{g}})) SeeAlso (symbol++, Complex, Complex) (symbol++, ComplexMap, ComplexMap) @@ -3485,8 +3485,8 @@ doc /// f' = map(M, L, 1) g' = freeResolution f' g'' = freeResolution(f * f') - assert(g'' === g * g') - assert(freeResolution id_N === id_(freeResolution N)) + assert(g'' == g * g') + assert(freeResolution id_N == id_(freeResolution N)) Text Over a quotient ring, free resolutions are often infinite. Use the optional argument {\tt LengthLimit} to obtain diff --git a/M2/Macaulay2/packages/Complexes/ChainComplexTests.m2 b/M2/Macaulay2/packages/Complexes/ChainComplexTests.m2 index 4b886daae80..617f82cbfc3 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplexTests.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplexTests.m2 @@ -2411,3 +2411,62 @@ TEST /// C9 = constantStrand(C, 9) assert isWellDefined C9 /// + +TEST /// -- test of lazy maps in a complex map + R = ZZ/32003[x,y,z] + C = res coker vars R + fC = map(C, C, i -> map(C_i, C_i, 1), 893723834); + assert(keys fC.map === {symbol Function}) + assert(fC_1 === fC_1) + assert(fC_1 == 1) + assert(set keys fC.map === set{1, symbol Function}) + assert(fC_3 == 1) + assert(set keys fC.map === set{1, 3, symbol Function}) + assert(fC_5 == 0) + assert(set keys fC.map === set{1, 3, symbol Function}) + assert isWellDefined fC +/// + +TEST /// -- test construction of complex with lazy differentials. +restart + R = ZZ/32003[x,y,z,w] + C = res coker vars R + assert(keys (dd^C).map === {symbol Function}) + assert(C.dd_1 === C.dd_1) + assert(set keys C.dd.map === set{1, symbol Function}) + C.dd_3 + assert(set keys C.dd.map === set{1, 3, symbol Function}) + assert(C.dd_5 == 0) + assert(set keys C.dd.map === set{1, 3, symbol Function}) + assert isWellDefined C.dd + assert isWellDefined C + + -- now we construct a complex with a lazy differential + modules = hashTable for i from 0 to 4 list i => C_i + mapfcn = i -> C.dd_i + D = complex(modules, mapfcn) + assert(D.concentration === (0,4)) + assert(D.ring === R) + assert(D.module === modules) + assert(D.dd.source === D) + assert(D.dd.target === D) + assert(D.dd.degree === -1) + assert(keys D.dd.map === {symbol Function}) + assert all(1..4, i -> mapfcn i == D.dd.map.Function i) + assert(keys D.dd.map === {symbol Function}) + assert isWellDefined D +/// + +TEST /// +restart +--needsPackage "OldChainComplexes" + S = ZZ/101[x_1..x_9]; + J = ideal vars S; + T = S/J^5; + elapsedTime C = res coker presentation T + debugLevel = 1 + elapsedTime C.dd_9; + elapsedTime C.dd_6; + profile (C = res coker presentation T) + profileSummary() +/// diff --git a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 index 5ff28bde11a..6de98ebc14c 100644 --- a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 +++ b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 @@ -190,23 +190,39 @@ resolutionObjectInEngine = (opts, M, matM) -> ( lengthlimit <= RO.LengthLimit ); + -- Old non-lazy version, to be removed once lazy version is functional + -- RO.complex = (lengthlimit) -> ( + -- -- returns a Complex of length <= lengthlimit + -- i := 0; + -- modules := while i <= lengthlimit list ( + -- F := new Module from (R, rawResolutionGetFree(RO.RawComputation, i)); + -- if F == 0 then break; + -- i = i+1; + -- F + -- ); + -- if #modules === 0 then return complex R^0; + -- if #modules === 1 then return complex(modules#0, Base => 0); + -- maps := hashTable for i from 1 to #modules-1 list ( + -- i => map(modules#(i-1), modules#i, rawResolutionGetMatrix(RO.RawComputation, i)) + -- ); + -- complex maps + -- ); + RO.complex = (lengthlimit) -> ( -- returns a Complex of length <= lengthlimit i := 0; - modules := while i <= lengthlimit list ( + modules := hashTable while i <= lengthlimit list i => ( F := new Module from (R, rawResolutionGetFree(RO.RawComputation, i)); if F == 0 then break; i = i+1; F ); - if #modules === 0 then return complex R^0; - if #modules === 1 then return complex(modules#0, Base => 0); - maps := hashTable for i from 1 to #modules-1 list ( - i => map(modules#(i-1), modules#i, rawResolutionGetMatrix(RO.RawComputation, i)) - ); - complex maps + if #(keys modules) === 0 then return complex R^0; + if #(keys modules) === 1 then return complex(modules#0, Base => 0); + mapfcn := i -> map(modules#(i-1), modules#i, rawResolutionGetMatrix(RO.RawComputation, i)); + complex(modules, mapfcn) ); - + if not opts.StopBeforeComputation then RO.compute(opts.LengthLimit, opts.DegreeLimit); RO.complex(opts.LengthLimit) diff --git a/M2/Macaulay2/packages/Complexes/Tor.m2 b/M2/Macaulay2/packages/Complexes/Tor.m2 index 6b04a6d9599..88901fff709 100644 --- a/M2/Macaulay2/packages/Complexes/Tor.m2 +++ b/M2/Macaulay2/packages/Complexes/Tor.m2 @@ -11,13 +11,13 @@ Tor(ZZ, Module, Module) := Module => opts -> (i, M, N) -> ( if i === 0 then M ** N else ( C := freeResolution(M, LengthLimit => i+1); - b := C.dd.map; - if b#?i then ( - if b#?(i+1) - then homology(b#i ** N, b#(i+1) ** N) - else kernel(b#i ** N)) + b := C.dd; + if b_i != 0 then ( + if b_(i+1) != 0 + then homology(b_i ** N, b_(i+1) ** N) + else kernel(b_i ** N)) else ( - if b#?(i+1) + if b_(i+1) != 0 then error "internal error" else C_i ** N) ) diff --git a/M2/Macaulay2/packages/SpectralSequences.m2 b/M2/Macaulay2/packages/SpectralSequences.m2 index 44d8b76b91a..79e028f87f9 100644 --- a/M2/Macaulay2/packages/SpectralSequences.m2 +++ b/M2/Macaulay2/packages/SpectralSequences.m2 @@ -120,9 +120,9 @@ support Complex := List => C -> select(spots C, i -> C_i != 0) pushFwd(RingMap, Complex) := o -> (f, C) -> ( (lo, hi) := concentration C; - if dd^C == 0 - then complex(for i from lo to hi list pushFwd(f, C_i, o), Base => lo) - else complex applyValues(C.dd.map, m -> pushFwd(f, m, o))) + if lo === hi then complex(pushFwd(f, C_lo), Base => lo) + else complex(for i from lo+1 to hi list pushFwd(f, C.dd_i, o), Base => lo) + ) -- e.g. n = 2 means cut 2 from the beginning -- and n = -2 means cut 2 from the tail; n = 0 does nothing @@ -241,10 +241,12 @@ xTensormodules := (p,q,T) -> ( xTensorComplex := (T,p) ->( (lo, hi) := concentration T; - if lo == hi + if lo === hi then complex(directSum xTensormodules(p, lo, T), Base => lo) - else complex applyPairs(T.dd.map, - (i,f) -> i => inducedMap(directSum(xTensormodules(p, i-1, T)), directSum(xTensormodules(p, i, T)), f))) + else complex(for i from lo+1 to hi list + inducedMap(directSum(xTensormodules(p, i-1, T)), directSum(xTensormodules(p, i, T)), T.dd_i), + Base => lo) + ) FilteredComplex ** Complex := FilteredComplex => (K,C) -> ( supp := support K_infinity; @@ -253,10 +255,10 @@ FilteredComplex ** Complex := FilteredComplex => (K,C) -> ( N := max support K_infinity; P := min support K_infinity; T := K_infinity ** C; -filteredComplex(reverse for i from P to (N-1) list - inducedMap(T, xTensorComplex(T,i)), Shift => -P) - ) - else ( if #supp == 1 then + filteredComplex(reverse for i from P to (N-1) list + inducedMap(T, xTensorComplex(T,i)), Shift => -P) + ) + else ( if #supp == 1 then ( p := min supp; t := K_infinity ** C; @@ -278,10 +280,12 @@ yTensorModules := (p,q,T)->( yTensorComplex := (T,p) -> ( (lo, hi) := concentration T; - if lo == hi + if lo === hi then complex(directSum(yTensorModules(p, lo, T), Base => lo)) - else complex applyPairs(T.dd.map, - (i,f) -> i => inducedMap(directSum(yTensorModules(p, i-1, T)), directSum(yTensorModules(p, i, T)), f))) + else complex(for i from lo+1 to hi list + inducedMap(directSum(yTensorModules(p, i-1, T)), directSum(yTensorModules(p, i, T)), T.dd_i), + Base => lo) + ) Complex ** FilteredComplex := FilteredComplex => (C,K) -> ( supp := support K_infinity; @@ -315,10 +319,12 @@ xHomModules := (n, d, H)->( xHomComplex := (T,n) -> ( (lo, hi) := concentration T; - if lo == hi + if lo === hi then complex(directSum(xHomModules(n, lo, T), Base => lo)) - else complex applyPairs(T.dd.map, - (i,f) -> i => inducedMap(directSum(xHomModules(n, i-1, T)), directSum(xHomModules(n, i, T)), f))) + else complex(for i from lo+1 to hi list + inducedMap(directSum(xHomModules(n, i-1, T)), directSum(xHomModules(n, i, T)), T.dd_i), + Base => lo) + ) -- produce the "x-filtration" of the Hom complex. Hom (FilteredComplex, Complex):= FilteredComplex => opts -> (K, C) -> ( @@ -358,10 +364,12 @@ yHomModules := (n, d, H) -> ( yHomComplex := (T,n) -> ( (lo, hi) := concentration T; - if lo == hi + if lo === hi then complex(directSum(yHomModules(n, lo, T), Base => lo)) - else complex applyPairs(T.dd.map, - (i,f) -> i => inducedMap(directSum(yHomModules(n, i-1, T)), directSum(yHomModules(n, i, T)), f))) + else complex(for i from lo+1 to hi list + inducedMap(directSum(yHomModules(n, i-1, T)), directSum(yHomModules(n, i, T)), T.dd_i), + Base => lo) + ) Hom (Complex, FilteredComplex) := FilteredComplex => opts -> (C, K) -> ( supp := support K_infinity; diff --git a/M2/Macaulay2/packages/Varieties/SheafComplexes.m2 b/M2/Macaulay2/packages/Varieties/SheafComplexes.m2 index 4c0b34e0263..5b4a25ef92f 100644 --- a/M2/Macaulay2/packages/Varieties/SheafComplexes.m2 +++ b/M2/Macaulay2/packages/Varieties/SheafComplexes.m2 @@ -13,7 +13,7 @@ flattenComplex = C -> C.cache#"flattenComplex" ??= ( (lo, hi) := C.concentration; if lo === hi then complex(flattenModule C_lo, Base => lo) - else complex applyValues(C.dd.map, flattenMorphism)) + else complex(for i from lo+1 to hi list flattenMorphism C.dd_i, Base => lo)) clearHom = (M, N) -> ( H := youngest(M.cache.cache, N.cache.cache); @@ -40,7 +40,7 @@ tensor(CoherentSheaf, Complex) := Complex => {} >> opts -> (F, C) -> ( (lo, hi) := concentration C; if lo === hi then complex(tensor(F, C_lo, opts), Base => lo) - else complex applyValues(C.dd.map, f -> tensor(id_F, f, opts))) + else complex(for i from lo+1 to hi list tensor(id_F, C.dd_i, opts), Base => lo)) tensor(Complex, CoherentSheaf) := Complex => {} >> opts -> (C, F) -> tensor(F, C, opts) @@ -58,7 +58,7 @@ sheaf Complex := Complex => C -> C.cache.sheaf ??= ( if isSheafComplex C then return C; (lo, hi) := C.concentration; if lo === hi then return complex(sheaf C_lo, Base => lo); - D := complex applyValues(C.dd.map, sheaf); + D := complex(for i from lo+1 to hi list sheaf C.dd_i, Base => lo); D.cache.module = C; D) @@ -74,8 +74,8 @@ module Complex := Complex => D -> D.cache.module ??= ( if not isSheafComplex D then return D; (lo, hi) := D.concentration; if lo === hi then return complex(module D_lo, Base => lo); - maxTruncDeg := max apply(values D.dd.map, f -> f.degree); - C := complex applyValues(D.dd.map, f -> truncate(maxTruncDeg, f.map)); + maxTruncDeg := max apply(for i from lo+1 to hi list degree D.dd_i); + C := complex(for i from lo+1 to hi list truncate(maxTruncDeg, D.dd_i), Base => lo); C.cache.sheaf = D; C) @@ -83,8 +83,14 @@ module ComplexMap := ComplexMap => phi -> phi.cache.module ??= ( S := source phi; T := target phi; if not isSheafComplex S or not isSheafComplex T then return phi; - maxTruncDeg := max ( apply(values S.dd.map, f -> f.degree) | apply(values T.dd.map, f -> f.degree) ); - sphi := map(truncate(maxTruncDeg,module T), truncate(maxTruncDeg,module S), applyValues(phi.map, i -> truncate(maxTruncDeg, matrix i))); + (loS, hiS) := concentration S; + (loT, hiT) := concentration T; + degsS := for i from loS+1 to hiS list degree S.dd_i; + degsT := for i from loT+1 to hiT list degree T.dd_i; + maxTruncDeg := max (degsS | degsT); + (lo, hi) := concentration phi; + sphi := map(truncate(maxTruncDeg,module T), truncate(maxTruncDeg,module S), + hashTable for i from lo to hi list i => truncate(maxTruncDeg, matrix phi_i)); sphi.cache.sheaf = phi; sphi) @@ -95,7 +101,10 @@ sheafRes = method(Options => options freeResolution) sheafRes Complex := sheafRes CoherentSheaf := Complex => opts -> F -> sheaf freeResolution'(module F, opts) -Complex(ZZ) := Complex(Sequence) := Complex => (C, a) -> complex applyValues(C.dd.map, f -> f(a)) +Complex(ZZ) := Complex(Sequence) := Complex => (C, a) -> ( + (lo, hi) := concentration C; + if lo === hi then complex(C.lo(a), Base => lo) + else complex(for i from lo+1 to hi list C.dd_i(a), Base => lo)) sheafHom(Complex, Complex) := Complex => opts -> (C,D) -> ( -- signs here are based from Christensen and Foxby @@ -225,7 +234,9 @@ Ext(ZZ, CoherentSheaf, Complex) := Complex => opts -> (m, C, D) -> ( a := max for i from 0 to length(Resns)-1 list max apply(n - L_i .. P_i, j-> (max degrees (Resns_i)_j)#0 - j); r := a - l + 1; M = truncate(r, M)); - complex applyValues(D.dd.map, f -> part(0, Ext^m(M, matrix f, opts)))) + (loD, hiD) := concentration D; + if loD === hiD then complex(part(0, Ext^m(M, D_loD, opts)), Base => loD) + else complex(for i from loD+1 to hiD list part(0, Ext^m(M, matrix D.dd_i, opts)), Base => loD)) cohomology(ZZ, ProjectiveVariety, Complex) := Complex => opts -> (p, X, C) -> ( C.cache.cohomology ??= new MutableHashTable; From cc0f169b8e12c617326277e486cda1c19bff11b0 Mon Sep 17 00:00:00 2001 From: Mike Stillman Date: Mon, 1 Jun 2026 19:55:58 -0400 Subject: [PATCH 2/4] changes to fix a number of issues in #3778. --- M2/BUILD/mike/Makefile | 1 - .../packages/Complexes/ChainComplex.m2 | 2 +- .../packages/Complexes/ChainComplexDoc.m2 | 1 - .../packages/Complexes/ChainComplexMap.m2 | 2 +- .../packages/Complexes/FreeResolution.m2 | 35 ++++++++++--------- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/M2/BUILD/mike/Makefile b/M2/BUILD/mike/Makefile index 8da86c7bae3..687e404388d 100644 --- a/M2/BUILD/mike/Makefile +++ b/M2/BUILD/mike/Makefile @@ -37,7 +37,6 @@ cmake-debug-appleclang: arm64-appleclang : always mkdir -p builds.tmp/arm64-appleclang cd builds.tmp/arm64-appleclang; ../../../../configure \ - --enable-dmg \ --enable-download --with-system-gc \ --with-system-memtailor --with-system-mathic \ --with-system-mathicgb \ diff --git a/M2/Macaulay2/packages/Complexes/ChainComplex.m2 b/M2/Macaulay2/packages/Complexes/ChainComplex.m2 index e49568fb54b..e3e06f6836e 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplex.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplex.m2 @@ -318,7 +318,7 @@ isWellDefined Complex := Boolean => C -> ( Module Array := Complex => (M, v) -> ( if length v =!= 1 then error "expected array of length 1"; if class v#0 =!= ZZ then error "expected [n] with n an integer"; - complex(M, Base => v#0)) + complex(M, Base => -v#0)) Complex _ ZZ := Module => (C,i) -> if C.module#?i then C.module#i else (ring C)^0 Complex ^ ZZ := Module => (C,i) -> C_(-i) diff --git a/M2/Macaulay2/packages/Complexes/ChainComplexDoc.m2 b/M2/Macaulay2/packages/Complexes/ChainComplexDoc.m2 index c1a1759deac..121487e8132 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplexDoc.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplexDoc.m2 @@ -812,7 +812,6 @@ doc /// get the maps between the terms in a complex Usage dd^C - dd_C Inputs C:Complex Outputs diff --git a/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 b/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 index 58feef44b5a..146a8ac5d58 100644 --- a/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 +++ b/M2/Macaulay2/packages/Complexes/ChainComplexMap.m2 @@ -273,7 +273,7 @@ lineOnTop := (s) -> concatenate(width s : "-") || s expression ComplexMap := Expression => f -> ( (lo, hi) := concentration f; d := degree f; - new VerticalList from for i from lo to hi list + new Holder from new VerticalList from for i from lo to hi list RowExpression {i+d, ":", MapExpression { target f_i, source f_i, f_i }, ":", i} ) diff --git a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 index 6de98ebc14c..b563717a8c1 100644 --- a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 +++ b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 @@ -62,12 +62,8 @@ freeResolution Module := Complex => opts -> M -> ( R := ring M; local C; if M === R^0 or opts.LengthLimit < 0 - then ( - C = complex R^0; - if not M.cache.?Resolution then - M.cache.Resolution = C; - return C; - ); + then return M.cache.Resolution ??= complex R^0; + if isFreeModule M then return M.cache.Resolution ??= complex M; if M.cache.?Resolution then ( C = M.cache.Resolution; if not C.cache.?LengthLimit or not C.cache.?DegreeLimit then @@ -142,18 +138,24 @@ resolutionObjectInEngine = (opts, M, matM) -> ( -- we remove the ResolutionObject from M.cache since -- otherwise it is in an incomplete and unrecoverable state remove(M.cache, symbol ResolutionObject); - error "need to provide LengthLimit for free resolutions over skew-commutative rings"; - ); - flatR := first flattenRing R; - if ideal flatR != 0 then ( - -- we remove the ResolutionObject from M.cache since - -- otherwise it is in an incomplete and unrecoverable state - remove(M.cache, symbol ResolutionObject); - error "need to provide LengthLimit for free resolutions over quotients of polynomial rings"; - ); - numgens flatR) + << "WARNING: since no finite LengthLimit was given, it has been arbitrarily set to be the number of variables" << endl; + numgens R + --error "need to provide LengthLimit for free resolutions over skew-commutative rings"; + ) + else ( + flatR := first flattenRing R; + if ideal flatR != 0 then ( + -- we remove the ResolutionObject from M.cache since + -- otherwise it is in an incomplete and unrecoverable state + remove(M.cache, symbol ResolutionObject); + << "WARNING: since no finite LengthLimit was given, it has been arbitrarily set to be the number of variables" << endl; + --error "need to provide LengthLimit for free resolutions over quotients of polynomial rings"; + ); + numgens flatR) + ) else opts.LengthLimit; + RO.RawComputation = rawResolution( raw matM, -- the matrix true, -- whether to resolve the cokernel of the matrix @@ -721,6 +723,7 @@ minimalBetti Ideal := BettiTally => opts -> I -> minimalBetti( minimalBetti Module := BettiTally => opts -> M -> ( R := ring M; + if isFreeModule M then return betti complex M; degreelimit := opts.DegreeLimit; if degreelimit === null then degreelimit = infinity; lengthlimit := opts.LengthLimit; From 1e34c3dd86b6d74509daf89877d048aea2d839f8 Mon Sep 17 00:00:00 2001 From: Mike Stillman Date: Sat, 6 Jun 2026 14:09:41 -0400 Subject: [PATCH 3/4] fix some free resolution caching bugs (caching freemodule resolutions was not quite right). Changed book output: we are not displaying zero maps anymore, in a ComplexMap. --- .../packages/Complexes/FreeResolution.m2 | 52 ++++++++++++------- .../packages/Complexes/FreeResolutionTests.m2 | 17 +++--- M2/Macaulay2/packages/EngineTests/Res.f4.m2 | 2 +- .../completeIntersections/test.out.expected | 4 -- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 index b563717a8c1..db7a1c2877d 100644 --- a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 +++ b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 @@ -61,9 +61,25 @@ freeResolution Module := Complex => opts -> M -> ( -- DegreeLimit is a lower limit on what will be computed degree-wise, but more might be computed. R := ring M; local C; - if M === R^0 or opts.LengthLimit < 0 - then return M.cache.Resolution ??= complex R^0; - if isFreeModule M then return M.cache.Resolution ??= complex M; + if opts.LengthLimit < 0 then error "expected nonnegative LengthLimit"; + if M === R^0 + then ( + FM := M.cache.Resolution ??= complex M; + FM.cache.Module = M; + return FM; + ); + if isFreeModule M then ( + << "calling free module res code" << endl; + if not M.cache.?Resolution then ( + FM = complex M; + FM.cache.Nonminimal = false; + FM.cache.LengthLimit = infinity; + FM.cache.DegreeLimit = infinity; + FM.cache.Module = M; + M.cache.Resolution = FM; + ); + return M.cache.Resolution + ); if M.cache.?Resolution then ( C = M.cache.Resolution; if not C.cache.?LengthLimit or not C.cache.?DegreeLimit then @@ -89,7 +105,7 @@ freeResolution Module := Complex => opts -> M -> ( RO.compute(opts.LengthLimit, opts.DegreeLimit); -- it is possible to interrupt this and then the following lines do not happen. C = RO.complex(opts.LengthLimit); C.cache.Nonminimal = (RO.Strategy === 4 or RO.Strategy === 5); -- magic number: this means Nonminimal, or NonminimalWithGB to the engine... - C.cache.LengthLimit = if max C < opts.LengthLimit then infinity else opts.LengthLimit; + C.cache.LengthLimit = if max C < RO.LengthLimit then infinity else RO.LengthLimit; C.cache.DegreeLimit = opts.DegreeLimit; C.cache.Module = M; M.cache.Resolution = C; @@ -114,7 +130,7 @@ freeResolution Module := Complex => opts -> M -> ( if C =!= null then ( assert(instance(C, Complex)); C.cache.Nonminimal = (RO.Strategy === 4 or RO.Strategy === 5); -- magic number: this means Nonminimal, NonminimalWithGB to the engine... - C.cache.LengthLimit = if max C < opts.LengthLimit then infinity else opts.LengthLimit; + C.cache.LengthLimit = if max C < RO.LengthLimit then infinity else RO.LengthLimit; C.cache.DegreeLimit = opts.DegreeLimit; C.cache.Module = M; M.cache.Resolution = C; @@ -135,27 +151,20 @@ resolutionObjectInEngine = (opts, M, matM) -> ( lengthlimit := if opts.LengthLimit === infinity then ( if isSkewCommutative R then ( - -- we remove the ResolutionObject from M.cache since - -- otherwise it is in an incomplete and unrecoverable state - remove(M.cache, symbol ResolutionObject); << "WARNING: since no finite LengthLimit was given, it has been arbitrarily set to be the number of variables" << endl; + RO.LengthLimit = numgens R; numgens R - --error "need to provide LengthLimit for free resolutions over skew-commutative rings"; ) else ( flatR := first flattenRing R; - if ideal flatR != 0 then ( - -- we remove the ResolutionObject from M.cache since - -- otherwise it is in an incomplete and unrecoverable state - remove(M.cache, symbol ResolutionObject); + if ideal flatR != 0 then ( -- i.e. is R a quotient polynomial ring? << "WARNING: since no finite LengthLimit was given, it has been arbitrarily set to be the number of variables" << endl; - --error "need to provide LengthLimit for free resolutions over quotients of polynomial rings"; + RO.LengthLimit = numgens flatR; ); numgens flatR) ) else opts.LengthLimit; - - + RO.RawComputation = rawResolution( raw matM, -- the matrix true, -- whether to resolve the cokernel of the matrix @@ -224,10 +233,10 @@ resolutionObjectInEngine = (opts, M, matM) -> ( mapfcn := i -> map(modules#(i-1), modules#i, rawResolutionGetMatrix(RO.RawComputation, i)); complex(modules, mapfcn) ); - + if not opts.StopBeforeComputation then - RO.compute(opts.LengthLimit, opts.DegreeLimit); - RO.complex(opts.LengthLimit) + RO.compute(RO.LengthLimit, opts.DegreeLimit); + RO.complex(RO.LengthLimit) ) resolutionInEngine1 = (opts, M) -> ( @@ -723,7 +732,10 @@ minimalBetti Ideal := BettiTally => opts -> I -> minimalBetti( minimalBetti Module := BettiTally => opts -> M -> ( R := ring M; - if isFreeModule M then return betti complex M; + if isFreeModule M then ( + if opts.LengthLimit < 0 then return betti complex R^0; + return betti complex M; + ); degreelimit := opts.DegreeLimit; if degreelimit === null then degreelimit = infinity; lengthlimit := opts.LengthLimit; diff --git a/M2/Macaulay2/packages/Complexes/FreeResolutionTests.m2 b/M2/Macaulay2/packages/Complexes/FreeResolutionTests.m2 index 8a0f8db5c51..b79a85efa55 100644 --- a/M2/Macaulay2/packages/Complexes/FreeResolutionTests.m2 +++ b/M2/Macaulay2/packages/Complexes/FreeResolutionTests.m2 @@ -167,8 +167,8 @@ TEST /// E = ZZ/101[a..d, SkewCommutative => true] I = ideal"ab, acd" - assert try (freeResolution(I); false) else true - C = freeResolution(I, LengthLimit => 5) + freeResolution I; -- this should not give an error, just give a warning message. + C = freeResolution(I, LengthLimit => 5) -- doesn't change the length limit? assert isWellDefined C assert(length C == 5) assert(naiveTruncation(prune HH C, (1,4)) == 0) @@ -841,7 +841,7 @@ TEST /// assert(C5.cache.Module === M) -- TODO? What behavior do we want here? --assert try (C6 = freeResolution(M, LengthLimit => -1); false) else true -- this one? - assert ((C6 = freeResolution(M, LengthLimit => -1)) == 0) -- or this one? + assert try (C6 = freeResolution(M, LengthLimit => -1); false) else true assert(M.cache.?Resolution) assert(M.cache.Resolution === C2) assert(M.cache.Resolution.cache.LengthLimit === length C2) @@ -854,11 +854,12 @@ TEST /// C = freeResolution M assert(M.cache.?Resolution) assert(M.cache.Resolution === C) - - C1 = freeResolution(M, LengthLimit => -1) - assert(M.cache.Resolution === C) - assert(C1 == C) - assert(C1 == 0) + + -- Why do we want to allow negative LengthLimit? + -- C1 = freeResolution(M, LengthLimit => -1) + -- assert(M.cache.Resolution === C) + -- assert(C1 == C) + -- assert(C1 == 0) C2 = freeResolution(M, LengthLimit => 2) M.cache.Resolution === C diff --git a/M2/Macaulay2/packages/EngineTests/Res.f4.m2 b/M2/Macaulay2/packages/EngineTests/Res.f4.m2 index cb505effb9a..b4a031e54ef 100644 --- a/M2/Macaulay2/packages/EngineTests/Res.f4.m2 +++ b/M2/Macaulay2/packages/EngineTests/Res.f4.m2 @@ -80,7 +80,7 @@ TEST /// assert(betti'ans == minimalBetti (ideal I_*)) assert(betti'ans == minimalBetti (ideal I_*, LengthLimit=>5)) assert(betti'ans == minimalBetti (ideal I_*, LengthLimit=>0)) - assert((new BettiTally from {}) === minimalBetti (ideal I_*, LengthLimit=>-1)) + assert((new BettiTally from {}) === minimalBetti (ideal I_*, LengthLimit => -1)) assert(betti'ans == minimalBetti (ideal I_*, LengthLimit=>1000)) assert(betti'ans == minimalBetti (ideal I_*, DegreeLimit=>1000)) assert(betti'ans == minimalBetti (ideal I_*, DegreeLimit=>-1000)) diff --git a/M2/Macaulay2/tests/ComputationsBook/completeIntersections/test.out.expected b/M2/Macaulay2/tests/ComputationsBook/completeIntersections/test.out.expected index 5c9b4638b74..dd6c5ac175e 100644 --- a/M2/Macaulay2/tests/ComputationsBook/completeIntersections/test.out.expected +++ b/M2/Macaulay2/tests/ComputationsBook/completeIntersections/test.out.expected @@ -50,10 +50,6 @@ o8 = 1 : A <----------------- A : 0 {1} | z -x | {1} | -y w | - 2 - 2 : 0 <----- A : 1 - 0 - o8 : ComplexMap i9 : s * C.dd + C.dd * s == -f * id_C From 43354a63e673a5d7a5958b3ec2b0062702c5ba8a Mon Sep 17 00:00:00 2001 From: Mike Stillman Date: Tue, 9 Jun 2026 10:41:20 -0400 Subject: [PATCH 4/4] remove debugging statement --- M2/Macaulay2/packages/Complexes/FreeResolution.m2 | 1 - 1 file changed, 1 deletion(-) diff --git a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 index db7a1c2877d..fc353e33af2 100644 --- a/M2/Macaulay2/packages/Complexes/FreeResolution.m2 +++ b/M2/Macaulay2/packages/Complexes/FreeResolution.m2 @@ -69,7 +69,6 @@ freeResolution Module := Complex => opts -> M -> ( return FM; ); if isFreeModule M then ( - << "calling free module res code" << endl; if not M.cache.?Resolution then ( FM = complex M; FM.cache.Nonminimal = false;