Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add part for graded objects and other improvements #3522

Open
wants to merge 15 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion M2/Macaulay2/m2/basis.m2
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ findHeftandVars := (R, varlist, ndegs) -> (

-- TODO: can this be done via a tensor or push forward?
-- c.f. https://github.com/Macaulay2/M2/issues/1522
-- TODO: think about how this compares with part
liftBasis = (M, phi, B, offset) -> (
-- lifts a basis B of M via a ring map phi
(R, S) := (phi.target, phi.source);
Expand Down Expand Up @@ -179,7 +180,7 @@ inducedBasisMap = (G, F, f) -> (
-- this helper routine is useful for computing basis of a pair of composable
-- matrices or a chain complex, when target f is the source of a matrix which
-- we previously computed basis for.
psi := f * inducedMap(source f, , generators F);
psi := f * F.cache.Monomials; -- equivalent to f * inducedMap(source f, , gens F)
phi := last coefficients(ambient psi, Monomials => generators G);
map(G, F, phi, Degree => degree f))
-- TODO: benchmark against inducedTruncationMap in Truncations.m2
Expand Down Expand Up @@ -309,3 +310,23 @@ algorithms#(basis, List, List, Module) = new MutableHashTable from {
-- Installing hooks for resolution
scan({Default, Torsion}, strategy ->
addHook(key := (basis, List, List, Module), algorithms#key#strategy, Strategy => strategy))

-----------------------------------------------------------------------------
-- part
-----------------------------------------------------------------------------
-- returns the graded component of an object in a specific degree

-- gives a map back to the coefficient ring
residueMap = (cacheValue symbol residueMap) (R -> map(A := coefficientRing R, R, DegreeMap => d -> take(d, - degreeLength A)))

-- TODO: document these
part(ZZ, Ring) :=
part(List, Ring) := Module => (deg, R) -> (residueMap R) source basis(deg, deg, module R)

part(ZZ, Ideal) :=
part(ZZ, Module) :=
part(List, Ideal) :=
part(List, Module) := Module => (deg, M) -> (residueMap ring M) source basis(deg, deg, M)

part(ZZ, Matrix) :=
part(List, Matrix) := Matrix => (deg, f) -> (residueMap ring f) cover basis(deg, f)
24 changes: 19 additions & 5 deletions M2/Macaulay2/m2/matrix.m2
Original file line number Diff line number Diff line change
Expand Up @@ -627,11 +627,28 @@ isSubquotient(Module,Module) := (M,N) -> (
relations N % relations M == 0
)

-----------------------------------------------------------------------------
-- inducedMap
-----------------------------------------------------------------------------

inducedMap = method (
Options => {
Verify => true,
Degree => null
})
-- TODO: hookify this, so people can add more application specific induced maps
inducedMap(Module, Module) := Matrix => opts -> (M, N) -> (
if ambient M =!= ambient N then error "inducedMap: expected modules with same ambient free module";
-- e.g. avoid a gb computation for inducedMap(M, image basis(d, M))
if N.cache.?Monomials and M === target N.cache.Monomials
then map(M, N, N.cache.Monomials, Degree => opts.Degree)
else inducedMap(M, N, id_(ambient N), opts))
inducedMap(Module, Nothing, Matrix) := Matrix => opts -> (M, N, f) -> (
B := image f;
-- e.g. avoid a gb computation for inducedMap(image f, , f)
if M === target B.cache.Monomials
then map(M, source B.cache.Monomials, B.cache.Monomials, Degree => opts.Degree)
else inducedMap(M, source f, f, opts))
inducedMap(Module,Module,Matrix) := Matrix => opts -> (N',M',f) -> (
N := target f;
M := source f;
Expand All @@ -653,7 +670,6 @@ inducedMap(Module,Module,Matrix) := Matrix => opts -> (N',M',f) -> (
if not isWellDefined f' then error "inducedMap: expected matrix to induce a well-defined map";
);
f')
inducedMap(Module,Nothing,Matrix) := o -> (M,N,f) -> inducedMap(M,source f, f,o)
inducedMap(Nothing,Module,Matrix) := o -> (M,N,f) -> inducedMap(target f,N, f,o)
inducedMap(Nothing,Nothing,Matrix) := o -> (M,N,f) -> inducedMap(target f,source f, f,o)

Expand All @@ -667,10 +683,6 @@ addHook((inducedMap, Module, Module, Matrix), Strategy => Default, (opts, N', M'
f' = map(N',M',f',Degree => if opts.Degree === null then degree f else opts.Degree);
(f', g, gbN', gbM)))

inducedMap(Module,Module) := Matrix => o -> (M,N) -> (
if ambient M =!= ambient N then error "inducedMap: expected modules with same ambient free module";
inducedMap(M,N,id_(ambient N),o))

-- TODO: deprecate this in favor of isWellDefined
inducesWellDefinedMap = method(TypicalValue => Boolean)
inducesWellDefinedMap(Module,Module,Matrix) := (M,N,f) -> (
Expand All @@ -687,6 +699,8 @@ inducesWellDefinedMap(Module,Nothing,Matrix) := (M,N,f) -> inducesWellDefinedMap
inducesWellDefinedMap(Nothing,Module,Matrix) := (M,N,f) -> inducesWellDefinedMap(target f,N,f)
inducesWellDefinedMap(Nothing,Nothing,Matrix) := (M,N,f) -> true

-----------------------------------------------------------------------------

vars Ring := Matrix => R -> (
g := generators R;
if R.?vars then R.vars else R.vars =
Expand Down
20 changes: 11 additions & 9 deletions M2/Macaulay2/m2/matrix1.m2
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,15 @@ subquotient(Nothing,Matrix) := (null,relns) -> (
Mparts = append(Mparts, symbol relations => relns);
);
new Module of Vector from hashTable Mparts)
subquotient(Matrix,Nothing) := (subgens,null) -> (
R := ring subgens;
E := target subgens;
rE := E.RawFreeModule;
subgens = align matrix subgens;
subquotient(Matrix, Nothing) := (subgens0, null) -> (
R := ring subgens0;
E := target subgens0;
rE := E.RawFreeModule;
subgens := align matrix subgens0;
if E.?generators then subgens = E.generators * subgens;
Mparts := {
symbol cache => new CacheTable from {
symbol Monomials => subgens0,
cache => new MutableHashTable -- this hash table is mutable, hence has a hash number that can serve as its age
},
symbol RawFreeModule => rE,
Expand All @@ -338,23 +339,24 @@ subquotient(Matrix,Nothing) := (subgens,null) -> (
Mparts = append(Mparts, symbol relations => E.relations);
);
new Module of Vector from hashTable Mparts)
subquotient(Matrix,Matrix) := (subgens,relns) -> (
R := ring relns;
E := target subgens;
subquotient(Matrix, Matrix) := (subgens0, relns) -> (
R := ring relns;
E := target subgens0;
if E =!= target relns then error "expected maps with the same target";
rE := E.RawFreeModule;
n := rawRank rE;
if n == 0 then new Module from (R,rE)
else (
relns = align matrix relns;
subgens = align matrix subgens;
subgens := align matrix subgens0;
if E.?generators then (
relns = E.generators * relns;
subgens = E.generators * subgens;
);
if E.?relations then relns = relns | E.relations;
Mparts := {
symbol cache => new CacheTable from {
symbol Monomials => subgens0,
cache => new MutableHashTable -- this hash table is mutable, hence has a hash number that can serve as its age
},
symbol RawFreeModule => rE,
Expand Down
76 changes: 55 additions & 21 deletions M2/Macaulay2/packages/Complexes/ChainComplex.m2
Original file line number Diff line number Diff line change
Expand Up @@ -717,30 +717,64 @@ canonicalTruncation(Complex,InfiniteNumber,InfiniteNumber) :=
canonicalTruncation(Complex,ZZ,Nothing) :=
canonicalTruncation(Complex,Nothing,ZZ) := Complex => (C,lo,hi) -> canonicalTruncation(C, (lo,hi))

part(List, Complex) := Complex => (deg, C) -> (
-- return a Complex over the coefficient ring
R := ring C;
A := coefficientRing R;
psi := map(A,R, DegreeMap => degR -> take(degR, - degreeLength A));
(lo, hi) := concentration C;
if lo === hi
then complex(psi source basis(deg, C_lo), Base => lo)
importFrom_Truncations { "inducedTruncationMap" }

truncateModuleOpts := options(truncate, List, Module)
truncate(ZZ, Complex) :=
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 (
maps := hashTable for i from lo+1 to hi list (
f := psi matrix basis(deg, dd^C_i);
if source f == 0 then continue else i => f
);
if # keys maps === 0 then complex(psi source basis(deg, C_lo), Base => lo) else complex maps
)
)
part(ZZ, Complex) := Complex => (deg, C) -> part({deg}, C)
-- this construction requires ~half as many truncations
f := truncate(degs, dd^C_lo, opts);
complex hashTable for i from lo+1 to hi list i => (
f = inducedTruncationMap(source f, truncate(degs, C_i, opts), dd^C_i))
))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid calling Truncation within part?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not call truncate within part. We do however call basis.

truncate(List, Complex) := Complex => {} >> opts -> (e, C) -> (
--------------------------------------------------------------------
-- basis -----------------------------------------------------------
--------------------------------------------------------------------
importFrom_Core { "inducedBasisMap" }

-- returns the graded component of the complex in the given degree
-- as a complex over the same ring (as opposed to the coefficient ring)
-- TODO: also define basis given a degree range and infinite ranges
basis(ZZ, Complex) :=
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);
complex hashTable for i from lo+1 to hi list i => (
f = inducedBasisMap(source f, image basis(deg, C_i, opts), dd^C_i))
))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phrase basis of complex seems particularly unfortunate.

--------------------------------------------------------------------
-- part ------------------------------------------------------------
--------------------------------------------------------------------
importFrom_Core "residueMap" -- gives a map back to the coefficient ring

-- this may not always be well-defined, so it is not exported
cover' = method()
cover' Complex := Complex => C -> (
(lo, hi) := concentration C;
if lo === hi then return complex truncate(e, C_lo);
complex hashTable for i from lo+1 to hi list i => truncate(e, dd^C_i)
)
truncate(ZZ, Complex) := Complex => {} >> opts -> (e, C) -> truncate({e}, C)
if lo == hi
then complex(cover C_lo, Base => lo)
else complex applyValues(C.dd.map, cover))
cover' ComplexMap := ComplexMap => f -> (
map(cover' target f, cover' source f, i -> cover f_i, Degree => degree f))

-- returns the graded component of the complex in the given degree
-- but as a complex over the coefficient ring instead
part(ZZ, Complex) :=
part(List, Complex) := Complex => (deg, C) -> (residueMap ring C) cover' basis(deg, C)

--------------------------------------------------------------------
-- homology --------------------------------------------------------
Expand Down
42 changes: 27 additions & 15 deletions M2/Macaulay2/packages/Complexes/ChainComplexMap.m2
Original file line number Diff line number Diff line change
Expand Up @@ -522,24 +522,36 @@ canonicalTruncation(ComplexMap,InfiniteNumber,InfiniteNumber) :=
canonicalTruncation(ComplexMap,ZZ,Nothing) :=
canonicalTruncation(ComplexMap,Nothing,ZZ) := ComplexMap => (f,lo,hi) -> canonicalTruncation(f, (lo,hi))

part(List, ComplexMap) := ComplexMap => (deg, f) -> (
R := ring f;
A := coefficientRing R;
psi := map(A,R, DegreeMap => degR -> take(degR, - degreeLength A));
C := part(deg, source f);
D := part(deg, target f);
truncateMatrixOpts := options(truncate, List, Matrix)
truncate(ZZ, ComplexMap) :=
truncate(List, ComplexMap) := ComplexMap => truncateMatrixOpts >> opts -> (degs, f) -> (
d := degree f;
map(D, C, i -> map(D_(i+d), C_i, psi matrix basis(deg, f_i)), Degree => d)
)
part(ZZ, ComplexMap) := ComplexMap => (deg, f) -> part({deg}, f)
C := truncate(degs, source f, opts);
D := if source f === target f then C else truncate(degs, target f, opts);
map(D, C, i -> inducedTruncationMap(D_(i+d), C_i, f_i), Degree => d))

truncate(List, ComplexMap) := ComplexMap => {} >> opts -> (e, f) -> (
C := truncate(e, source f);
D := truncate(e, target f);
--------------------------------------------------------------------
-- basis -----------------------------------------------------------
--------------------------------------------------------------------
-- returns the induced complex map between the graded components of
-- the source and target complexes in the given degree, over the
-- same ring as the input (as opposed to its coefficient ring)
-- TODO: also define basis given a degree range and infinite ranges
basis(ZZ, ComplexMap) :=
basis(List, ComplexMap) := ComplexMap => opts -> (deg, f) -> (
d := degree f;
map(D, C, i -> map(D_(i+d), C_i, truncate(e, f_i)), Degree => d)
)
truncate(ZZ, ComplexMap) := ComplexMap => {} >> opts -> (e, f) -> truncate({e}, f)
C := basis(deg, source f, opts);
D := if source f === target f then C else basis(deg, target f, opts);
map(D, C, i -> inducedBasisMap(D_(i+d), C_i, f_i), Degree => d))

--------------------------------------------------------------------
-- part ------------------------------------------------------------
--------------------------------------------------------------------
-- returns the induced complex map between the graded component of
-- the source and target complexes in the given degree, but as a map
-- over the coefficient ring instead
part(ZZ, ComplexMap) :=
part(List, ComplexMap) := ComplexMap => (deg, f) -> (residueMap ring f) cover' basis(deg, f)

--------------------------------------------------------------------
-- homology --------------------------------------------------------
Expand Down
28 changes: 21 additions & 7 deletions M2/Macaulay2/packages/Complexes/ChainComplexTests.m2
Original file line number Diff line number Diff line change
Expand Up @@ -2071,7 +2071,7 @@ needsPackage "Complexes"
///

TEST ///
-- example of computing part.
-- example of computing basis and part.
-*
restart
needsPackage "Complexes"
Expand All @@ -2092,16 +2092,19 @@ TEST ///
assert isWellDefined part(2, F)
assert(ring part(2, F) === kk)

assert (C = part(6, F);
(for i from 0 to length C list rank C_i) == {84, 125, 54, 1})
assert isWellDefined part(6, F)
C = part(6, F)
D = basis(6, F)
assert isWellDefined C
assert isWellDefined D
assert({84, 125, 54, 1} == apply(length C + 1, i -> rank C_i))
assert({84, 125, 54, 1} == apply(length D + 1, i -> rank cover D_i))

kk = ZZ/32003[s,t]
S = kk[a..d]
psi = map(kk, S)
I = ideal"sab, tad, (s-t)bc, tc3"

isHomogeneous I
assert isHomogeneous I
F = freeResolution comodule I

assert(part(-10, F) == 0)
Expand All @@ -2124,8 +2127,19 @@ TEST ///
assert(ring part(6, F) === kk)

C = part(2, F)
dd^C
isHomogeneous dd^C_1 -- want this to be true.
D = basis(2, F)
assert isHomogeneous C
assert isHomogeneous D
assert isHomogeneous dd^C
assert isHomogeneous dd^D

f = id_F;
g = part(2, f)
h = basis(2, f)
assert isHomogeneous g
assert isHomogeneous h
assert isCommutative g
assert isCommutative h
///

TEST ///
Expand Down
16 changes: 16 additions & 0 deletions M2/Macaulay2/packages/Truncations.m2
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,27 @@ inducedTruncationMap = (G, F, f) -> (
truncate(List, Matrix) := Matrix => truncateModuleOpts >> opts -> (degs, f) -> (
inducedTruncationMap(truncate(degs, target f, opts), truncate(degs, source f, opts), f))

-- TODO: document these
truncate(ZZ, ZZ, Matrix) :=
truncate(Nothing, ZZ, Matrix) :=
truncate(Nothing, List, Matrix) :=
truncate(List, List, Matrix) := Matrix => truncateModuleOpts >> opts -> (tardegs, srcdegs, f) -> (
-- Given a graded map f, truncate source f, possibly higher than target f,
-- then return the map induced by f and the two inclusions
-- TODO: benchmark to make sure the inducedMaps are not computing unnecessary gb
if tardegs === null then return f * inducedMap(source f, truncate(srcdegs, source f, opts));
-- TODO: assert that srcdegs >= tardegs with respect to the cone of truncation
inducedTruncationMap(truncate(tardegs, target f, opts), truncate(srcdegs, source f, opts), f))

--------------------------------------------------------------------

truncate(InfiniteNumber, Thing) := truncateModuleOpts >> o -> (d, M) -> (
if d === -infinity then M else error "unexpected degree for truncation")

-- TODO: implement union types in M2 and simplify stuff like this
truncate(Nothing, InfiniteNumber, Matrix) :=
truncate(InfiniteNumber, InfiniteNumber, Matrix) := lookup(truncate, List, List, Matrix)

--------------------------------------------------------------------
-- basis using basisPolyhedron (experimental)
--------------------------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions M2/Macaulay2/packages/Truncations/tests.m2
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,18 @@ TEST /// -- test of inducedTruncationMap
assert(src === source g)
///

TEST /// -- test of truncation of complexes
needsPackage "Complexes"
S = QQ[x,y,z]
C = koszulComplex vars S
truncate(3, C)
///

TEST /// -- test of subtruncate
M = module(QQ[x,y])
assert(truncate(1, 2, id_M) == map(truncate(1, M), truncate(2, M), {{y, 0, 0}, {0, y, x}}))
///

end--

restart
Expand Down
Loading