Repository: Macaulay2/M2
Pull request #3761: Isomorphism: v2.0
Author: login: mahrud
Created: 2025-05-01T01:00:19Z | Merged: 2025-05-10T13:38:26Z

The biggest change is that `isIsomorphic` now returns a boolean, and to access the cached isomorphism the function `isomorphism` is added.

- **moved symbol Tries to Core**
- **moved symbol Strict to Core**
- **added option Tries to isIsomorphic**
- **added caching to isIsomorphic**
- **added a few quick strategies for isIsomorphic**
- **moved a test in Isomorphism**
- **moved a test in Isomorphism**
- **fixed a test in Isomorphism**
- **fixed indentation of package Isomorphism**
- **added isomorphism(Module, Module) to Isomorphism**
- **mentioned documentation of Tries to isIsomorphic**
- **changed isIsomorphic to return Boolean**
- **changed isomorphism to give a homogeneous map if possible**
- **fixed a bug in an Isomorphism example**
- **fixed a bug in isIsomorphic for multigraded modules**
- **updated version and package info for Isomorphism**
- **updated isIsomorphic and added isomorphism in Varieties**
- **commented isIsomorphic from TateOnProducts**
- **updated isIsomorphic and isomorphism methods across several packages**
- **mentioned the change in isIsomorphic in changes.m2**



Diff:
diff --git a/M2/Macaulay2/m2/exports.m2 b/M2/Macaulay2/m2/exports.m2
index 72f7184cc7f..d8f0ea1e054 100644
--- a/M2/Macaulay2/m2/exports.m2
+++ b/M2/Macaulay2/m2/exports.m2
@@ -391,6 +391,7 @@ export {
 	"StopIteration",
 	"StopWithMinimalGenerators",
 	"Strategy",
+	"Strict",
 	"String",
 	"Subnodes",
 	"SubringLimit",
@@ -419,6 +420,7 @@ export {
 	"Toric",
 	"Torsion",
 	"TotalPairs",
+	"Tries",
 	"Truncate",
 	"Type",
 	"TypicalValue",
diff --git a/M2/Macaulay2/m2/shared.m2 b/M2/Macaulay2/m2/shared.m2
index 4d51340545f..0da8ac194c8 100644
--- a/M2/Macaulay2/m2/shared.m2
+++ b/M2/Macaulay2/m2/shared.m2
@@ -45,4 +45,8 @@ protect Jacobian
 
 protect Iterate
 
+protect Strict
+
 protect Threads
+
+protect Tries
diff --git a/M2/Macaulay2/packages/EagonResolution.m2 b/M2/Macaulay2/packages/EagonResolution.m2
index c5644ec3f11..bd6867b062b 100644
--- a/M2/Macaulay2/packages/EagonResolution.m2
+++ b/M2/Macaulay2/packages/EagonResolution.m2
@@ -1519,8 +1519,6 @@ assert (golodBetti (coker vars R,b) == betti H)
 -- TODO: PLACE INTO M2 CORE
 --SOME USEFUL INTERNAL FUNCTIONS
 --    "compositions"
---    "isDegreeZeroSurjection",
---    "isIsomorphic"
 -*
 compositions(ZZ,ZZ,ZZ) := (nparts, k, maxelem) -> (
     -- nparts is the number of terms
@@ -1541,32 +1539,6 @@ compositions(ZZ,ZZ,ZZ) := (nparts, k, maxelem) -> (
      compositionn = null;
      result
      );
-
-isDegreeZeroSurjection := method()
-isDegreeZeroSurjection(Module,Module) := Boolean => (A,B) -> (
-    --tests a random degree 0 map f:A --> B to see whether its a surjection, 
-    --and returns the answer. If "true" and  o.Verbose == true then returns f.
-    H := Hom(A,B);
-    B0 := basis(0,H); -- this seems to be total degree 0 in case of degreeLength>1
-    f := homomorphism(B0*random(source B0, (ring B0)^1));
-    coker f == 0
-)
-
--- the following function works for modules in the graded case.
--- and produces (only) graded isomorphisms.
-isIsomorphic = method()
-isIsomorphic(Module,Module) := (A,B) -> (
-    --tests random degree 0 maps A->B, B->A and returns true
-    --if both are surjective.
-    if not(isHomogeneous A and isHomogeneous B) then 
-	  error"not implemented for inhomogeneous modules";
-    Ap := prune A;
-    Bp := prune B;
-    dA := set flatten degrees source gens Ap;
-    dB := set flatten degrees source gens Bp;
-    if dA =!= dB then return false;
-    degreeZeroSurjection(Ap,Bp) =!= null and degreeZeroSurjection(Bp,Ap) =!= null
-    )
 *-
 
 TEST/// --test of degreeZeroSurjection
diff --git a/M2/Macaulay2/packages/Isomorphism.m2 b/M2/Macaulay2/packages/Isomorphism.m2
index c8bcaea71b6..dcf22e49616 100644
--- a/M2/Macaulay2/packages/Isomorphism.m2
+++ b/M2/Macaulay2/packages/Isomorphism.m2
@@ -1,26 +1,52 @@
 newPackage(
     "Isomorphism",
-    Version => "1.0",
-    Date => "April 27, 2022",
-    Headline => "Probabilistic test of isomorphism between modules",
-    Authors => {{Name => "David Eisenbud", 
-                  Email => "de@msri.org", 
-                  HomePage => "http://www.msri.org/~de"}
-                  },
-    Keywords => {"Commutative Algebra", "Homological Algebra", "Projective Algebraic Geometry"},
+    Version => "2.0",
+    Date => "April 30, 2025",
+    Headline => "probabilistic test of isomorphism between modules",
+    Authors => {
+	{ Name => "David Eisenbud", Email => "de@berkeley.edu", HomePage => "https://eisenbud.github.io" },
+	{ Name => "Mahrud Sayrafi", Email => "mahrud@umn.edu",  HomePage => "https://mahrud.github.io" }
+    },
+    Keywords => {"Commutative Algebra", "Homological Algebra"},
     DebuggingMode => false
     )
 
 export {
     "isIsomorphic",
+    "isomorphism",
     "checkDegrees",
-    --
-    "Strict" -- option for checkDegrees making the homogeneous case preserve degrees.
     }
-    
+
+importFrom_Core {
+    "sortBy",
+}
 
 -* Code section *-
 
+-----------------------------------------------------------------------------
+-- things to move to Core
+-----------------------------------------------------------------------------
+
+leadCoefficient' = method()
+leadCoefficient' Number      := x -> x
+leadCoefficient' RingElement := x -> try leadCoefficient x else x
+-- not strictly speaking the "lead" coefficient, but the first nonzero coefficient
+leadCoefficient' Matrix      := m -> if zero m then 0 else (
+    for c to numcols m - 1 do for r to numrows m - 1 do (
+	if not zero m_(r,c) then return leadCoefficient' m_(r,c)))
+
+-- divides by the coefficient of the leading term
+reduceCoefficient = m -> if zero m then m else (
+    c := leadCoefficient' m;
+    if not isField ring c then return m;
+    map(target m, source m, cover m // c, Degree => degree m))
+
+-----------------------------------------------------------------------------
+-- randomMinimalDegreeHomomorphism
+-----------------------------------------------------------------------------
+
+-- also see degreeZeroSurjection from EagonResolution
+-- and isDegreeZeroSurjection and the (deprecated) isIsomorphic in TateOnProducts
 randomMinimalDegreeHomomorphism=method()
 randomMinimalDegreeHomomorphism(Matrix, Matrix, ZZ) := Matrix => (n,m,d) -> (
     --m,n homogeneous, minimal over a ring with degree length 1 (this restridd
@@ -61,15 +87,20 @@ randomMinimalDegreeHomomorphism(Matrix, Matrix, ZZ) := Matrix => (n,m,d) -> (
     p := positions(degrees source h, e -> e == -{d});
     hp := h_p;
     a := hp*random(source (hp), S^{d}); --represents general map of degree -diffdegs
-    map(coker n, coker m, matrix reshape(N0, M0, a))
+    map(coker n, coker m, matrix reshape(N0, M0, a), Degree => -d)
     )
 
-isDegreeListZero = L -> 
--- test whether a list of lists has all entries of entries 0
-   all(L, s -> 
-           all(s,  e-> e === 0)); 
+-----------------------------------------------------------------------------
+-- checkDegrees
+-----------------------------------------------------------------------------
 
-checkDegrees = method(Options =>{Verbose =>false, Strict =>false})
+-- test whether a list of lists has all zero entries
+isDegreeListZero = L -> all(L, s -> all(s, e -> e === 0))
+
+checkDegrees = method(Options => {
+	Verbose => false,
+	Strict  => false, -- make the homogeneous case preserve degrees
+    })
 checkDegrees(Module, Module) := Sequence => o -> (A,B) -> (
     v := o.Verbose;
     S := ring A;
@@ -102,438 +133,748 @@ checkDegrees(Module, Module) := Sequence => o -> (A,B) -> (
     )
 checkDegrees(Matrix, Matrix) := Sequence => o-> (m,n) -> checkDegrees(target m, target n, o)
 
-///
-restart
-loadPackage "Isomorphism"
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-A = S^{{2,1}}
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-d = checkDegrees(A,B, Verbose => true)
-assert(d == (false,null))
-d = checkDegrees(A,B1)
-B' = S^{{3,3}}**B
-d = checkDegrees(B',B)
-e = checkDegrees(S^{d_1}**B', B)
-e = checkDegrees(B', S^{-d_1}**B)
-assert (e_0 == true)
----
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-A = coker random(B, S^{2:-{2,2}})
-B = A
-checkDegrees(A,B)
-d = checkDegrees(A,B1)
-checkDegrees(B,B)
-///
+-----------------------------------------------------------------------------
+-- isIsomorphism and isomorphism
+-----------------------------------------------------------------------------
+
+-- the default number of attempts for randomized algorithms
+-- e.g. 145 tries in char 2, 10 in char 32003, and 1 in char 0
+defaultNumTries = p -> ceiling(0.1 + 100 / log p)
+--apply({2, 32003, 0}, defaultNumTries)
+
+-- key to cache known isomorphisms
+protect Isomorphisms
+
+-- we cache isomorphisms in the youngest of the modules
+-- Note: this is similar to where Hom is cached.
+setIsomorphism = (N, M, f, o) -> (
+    Y := youngest(N.cache.cache, M.cache.cache);
+    -- make a "monic" isomorphism
+    f = reduceCoefficient f;
+    o = { o.Strict or all(degree f, zero),
+	o.Homogeneous or isHomogeneous f };
+    Y.Isomorphisms ??= new MutableHashTable;
+    Y.Isomorphisms#(N, M, o) = f;
+    true)
+
+-- we check if a matching isomorphism is cached
+getIsomorphism = (N, M, o) -> (
+    Y := youngest(N.cache.cache, M.cache.cache);
+    if Y.?Isomorphisms then
+    for isom in keys Y.Isomorphisms do (
+	(B, A, o') := isom;
+	if B === N and A === M
+	and ( not o.Strict      or o.Strict      and o'#0 )
+	and ( not o.Homogeneous or o.Homogeneous and o'#1 )
+	then return Y.Isomorphisms#isom;
+	if B === M and A === N
+	and ( not o.Strict      or o.Strict      and o'#0 )
+	and ( not o.Homogeneous or o.Homogeneous and o'#1 )
+	then return inverse Y.Isomorphisms#isom))
+
+hasIsomorphism = (N, M, o) -> null =!= getIsomorphism(N, M, o)
+
+-- give an isomorphism between two free modules with same degrees
+isIsomorphismFree = (N, M0, o) -> (
+    if rank N != rank M0 then return false;
+    (d1, d2) := (degrees N, degrees M0);
+    diffdegs := min d2 - min d1;
+    if o.Strict then M := M0 else d2 = degrees(
+	M = M0 ** (ring M0)^{diffdegs});
+    if sort d1 != sort d2 then
+    return if o.Homogeneous then false
+    else setIsomorphism(N, M0, map(N, M0, 1), o);
+    -- FIXME: because of https://github.com/Macaulay2/M2/issues/3719,
+    -- this might not give the most "natural" isomorphism
+    p1 := first \ (sortBy last) toList pairs d1;
+    p2 := first \ (sortBy last) toList pairs d2;
+    setIsomorphism(N, M0, map(N, M0, id_M^p2 // id_N^p1,
+	    Degree => -diffdegs), o))
+
+-- returns a minimization M1, presented as
+-- a cokernel of m, and an isomorphism back to M
+minimization = M -> (
+    R := ring M;
+    k := if isField R then R
+    else quotient ideal vars R;
+    m := presentation M;
+    iso := if m ** k == 0
+    then map(M, M1 := coker m, 1)
+    else (
+	m = presentation(M1 = prune M);
+	M1.cache.pruningMap);
+    (M1, m, iso))
+
+-- TODO: hookify and split into strategies, then add strategies that:
+-- - check if pruning of the modules is cached already
+-- - check if Hilbert polynomial, regularity, dimension, etc. are known
+-- - check if there are surjections in both directions, a la
+--   isDegreeZeroSurjection in TateOnProducts
+--  or degreeZeroSurjection in EagonResolution
+isIsomorphic = method(
+    TypicalValue => Boolean,
+    Options => {
+	Homogeneous => true,  -- if false, allow an inhomogeneous isomorphism
+	Strict      => false, -- forces strict equality of degrees
+	Verbose     => false,
+	Strategy    => null,
+	Tries       => null,  -- number of attempts at generating a random map
+    })
+isIsomorphic(Module, Module) := Boolean => o -> (N, M) -> (
+    -- returns true iff there is an isomorphism N <- M.
+    -- and unless M === N, the isomorphism is cached under
+    -- Y.cache.cache.Isomorphisms#(N, M, {Strict?, Homogeneous?}),
+    -- where Y is the youngest of M and N based on hash, and the list
+    -- indicates whether the isomorphism was homogeneous or if it
+    -- was strict (not strict if M is isomorphic to a _twist_ of N)
+    if M === N or hasIsomorphism(N, M, o) then return true;
+    --
+    S := ring M;
+    if S =!= ring N then error "expected objects over the same ring";
+    --
+    if isFreeModule M and isFreeModule N then
+    return isIsomorphismFree(N, M, o);
+    --
+    tries := o.Tries ?? defaultNumTries char S;
+    if tries > 1 then return any(tries,
+	i -> isIsomorphic(N, M, o, Tries => 1));
+    --
+    if o.Homogeneous == true and not (
+	isHomogeneous M and isHomogeneous N)
+    then error "inputs not homogeneous";
 
-isIsomorphic = method(Options =>{Homogeneous => true, Verbose => false, Strict =>false})
-isIsomorphic(Module, Module) := Sequence => o ->  (N,M)->(
-    --returns a pair (false, null) or (true, f), or where f is an isomorphism 
-    --f: M to N.
-    --if an inhomogeneous iso is to be allowed, use the option
-    --Homogeneous => false
-        v := o.Verbose;
-	
-        if o.Homogeneous == true and 
-	        not (isHomogeneous M and isHomogeneous N) then 
-	        error"inputs not homogeneous";
-    	S := ring M;
-	resS := S/(ideal gens S);
-
-    	m := presentation M;
-
-	if m**resS == 0 then 
-	    (M1 := M; 
-	     m1 := map(M, coker m, 1)) else
-	     (m = presentation (M1 = prune M);
-	     m1 = M1.cache.pruningMap
-    	     );--iso from M1 to M
-	 
-    	n := presentation N;
-	if n**resS == 0 then 
-	    (N1 := N;
-	    n1 := map(N,coker n, 1)) else
-	    (n = presentation (N1 = prune N);
-	    n1 = N1.cache.pruningMap); --iso from N1 to N
+    (M1, m, isoM) := minimization M;
+    (N1, n, isoN) := minimization N;
+
+    -- this should catch ZZ-modules
+    if M1 === N1 then return setIsomorphism(N, M,
+	isoN * inverse isoM, o);
 
 	--handle the cases where one of M,N is 0
 	isZM1 := target m ==0;
 	isZN1 := target n ==0;	
-    	if isZM1 =!= isZN1 then return (false, null );
-	if isZM1 and isZN1 then return (true, map(N,M,0));
+	if isZM1 =!= isZN1 then return false;
+	if isZM1 and isZN1 then return setIsomorphism(N, M, map(N, M, 0), o);
 
 	-- from now on, M1 and N1 are nonzero and pruned
 	if o.Homogeneous then (
 	df := checkDegrees (N1,M1,Verbose => o.Verbose, Strict => o.Strict);
-	if class df_1 =!= List  then return (false, null));
+	if class df_1 =!= List  then return false);
 	--now there is a chance at an isomorphism up to shift, 
 	--and df is the degree diff.
 
-	--compute an appropriate random map g
-	if o.Homogeneous and degreeLength S == 1 then
-	g := randomMinimalDegreeHomomorphism(n,m, -df_1_0) else (
+    --compute an appropriate random map g
+    g := if o.Homogeneous and degreeLength S == 1
+    then randomMinimalDegreeHomomorphism(n, m, -df_1_0)
+    else (
         H := Hom(M1,N1);       
 	kk := ultimate(coefficientRing, S);
-	if o.Homogeneous === true then
-	      sH := select(H_*, f-> degree f == -df_1) else 
-	      sH = H_*;
+	sH := if not o.Homogeneous then H_*
+	else select(H_*, f -> degree f == df_1);
 	if #sH == 0 then return false;
     	g = sum(sH, f-> random(kk)*homomorphism matrix f)
-	);
-        
+    );
+
+    if o.Homogeneous and not isHomogeneous g then return false;
+
 	--test g to see if it's surjective:
 	kmodule := coker vars S;
 	gbar := kmodule ** g;
 
-	t1 := prune coker gbar == 0;
-	if t1 == false then return (false, null);
-	
-	t2 := prune ker g == 0;
-	if t2 then (true, n1*g*(m1^-1)) else (false, null)
+    if coker gbar != 0 or kernel g != 0 then false
+    else setIsomorphism(N, M, isoN * g * inverse isoM, o)
     )
-isIsomorphic(Matrix, Matrix) := Sequence => o -> (n, m) -> isIsomorphic(coker m, coker n, o)
-
--*
-restart
-loadPackage("Isomorphism", Reload =>true)
-*-
-///
-setRandomSeed 0
-S = ZZ/32003[a,b,Degrees => {{1,0},{0,1}}]
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-A = coker random(B, S^{2:-{3,3}})
-A1 = coker (a = random(B1^3, S^{2:-{3,3}}))
-A2 = coker (random(target a, target a)*a*random(source a,source a))
-C1 = coker (a = random(B1^3, S^{2:-{3,3}, -{4,5}}))
-C2 = coker (matrix random(S^3, S^3)*matrix a*matrix random(S^3,S^3))
-
---isIsomorphic(C1,C2) -- gives an error because C2 is not homogeneous
-assert((isIsomorphic(C1,C2, Homogeneous => false))_0 ==true)
-isIsomorphic(C1,C2, Homogeneous => false) -- this should be true!
-
-isIsomorphic(C1,C1, Homogeneous => false) -- this should be true!
-
-
-assert((isIsomorphic(A1,A2))_0 == true)
-assert(coker ((isIsomorphic(A1,A2))_1) == 0)
-assert((isIsomorphic (A,A))_0 == true)
-assert((isIsomorphic(B1,B1))_0 == true)
-assert((isIsomorphic(A,B1))_0 == false)
-assert((isIsomorphic(A1,B1, Verbose => true))_0 === false)
-///
+isIsomorphic(Matrix, Matrix) := Boolean => o -> (n, m) -> isIsomorphic(coker m, coker n, o)
 
+isomorphism = method(Options => options isIsomorphic)
+isomorphism(Module, Module) := Matrix => o -> (N, M) -> (
+    if M === N then id_M else
+    if isIsomorphic(N, M, o) then getIsomorphism(N, M, o)
+    else error "modules are not isomorphic")
 
+-----------------------------------------------------------------------------
 -* Documentation section *-
+-----------------------------------------------------------------------------
+
 beginDocumentation()
 
 doc ///
-Key
- Isomorphism
-Headline
- Probabilistic test for isomorphism
-Description
-  Text
-   Two modules are isomorphic if there is a surjection in each direction.
-   These routines produce random combinations of the generators of Hom
-   and test whether these are surjections.
-SeeAlso
- isIsomorphic
- checkDegrees
-Contributors
- Mike Stillman
+Node
+  Key
+    Isomorphism
+  Headline
+    probabilistic test for isomorphism of modules
+  Description
+    Text
+      Two modules are isomorphic if there a homomorphism $f:M \to N$ which is both
+      injective and surjective, or equivalently if there is a surjection in each direction.
+      These routines produce random combinations of the generators of @TO Hom@
+      and test for a homomorphism which is both injective and surjective.
+
+      Note that it suffices to check surjectivity after tensoring with the residue field,
+      so in a future version we may instead check for surjections in both directions.
+  SeeAlso
+    isIsomorphic
+    isomorphism
+    checkDegrees
+  Contributors
+    Mike Stillman and Devlin Mallory contributed to this package.
+
+Node
+  Key
+    checkDegrees
+   (checkDegrees, Module, Module)
+   (checkDegrees, Matrix, Matrix)
+   [checkDegrees, Verbose]
+  Headline
+    compares the degrees of generators of two modules
+  Usage
+    d = checkDegrees(N,M)
+    d = checkDegrees(n,m)
+  Inputs
+    N:Module
+    n:Matrix -- presentation of N
+    M:Module
+    m:Matrix -- presentation of M
+  Outputs
+    d:Sequence
+      (Boolean, a degree in the ring of M and N)
+  Description
+    Text
+      This is to be used with @TO isIsomorphic@.
+
+      The routine compares the sorted lists of degrees of generators of the two modules;
+      the degreeLength (can be anything).
+      If the numbers of generators of M,N are different, the modules are not isomorphic,
+      and the routine returns (false, null).
+
+      If the numbers are the same, and all the corresponding degrees pairs differ
+      by the same amount (so that the modules might become isomorphic after a shift,
+      then if Strict => false (the default)
+      the output (true, e) tells how to adjust the modules to make the degrees equal:
+      either tensor N with (ring N)^{e} or tensor M with (ring M)^{-e}.
+
+      If Strict => true, then the output is (false, null) unless
+      the offset e is 0.
+    Example
+      S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
+      A = S^{{2,1}}
+      B = S^{{1,1}}
+      B' = S^{{3,3}}**B
+      C = S^{{1,1}, {2,3}}
+      checkDegrees(A,B)
+      checkDegrees(A,C)
+
+      d = checkDegrees(B',B)
+      degrees (S^{d_1}**B') == degrees B
+      degrees (B') == degrees (S^{-d_1}**B)
+      checkDegrees(B',B,Strict=>true)
+  SeeAlso
+    isIsomorphic
+    [checkDegrees, Strict]
+
+Node
+  Key
+    [checkDegrees, Strict]
+  Headline
+    forces strict equality of degrees
+  Usage
+    d = checkDegrees(N, M, Strict =>true)
+  Description
+    Text
+      Used with Strict=>false, the default,
+      checkDegrees(N,M) returns (true, deg) if
+      degrees M and degrees N are equal up to a shift d.
+      With Strict => true the degree lists must be equal.
+    Example
+      S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
+      B = S^{{1,1}}
+      B' = S^{{3,3}}**B
+      d = checkDegrees(B',B)
+      degrees (S^{d_1}**B') == degrees B
+      degrees (B') == degrees (S^{-d_1}**B)
+      checkDegrees(B',B,Strict=>true)
+  SeeAlso
+    checkDegrees
+
+Node
+  Key
+    isIsomorphic
+   (isIsomorphic, Module, Module)
+   (isIsomorphic, Matrix, Matrix)
+   [isIsomorphic, Verbose]
+   [isIsomorphic, Homogeneous]
+   [isIsomorphic, Strategy]
+   [isIsomorphic, Strict]
+   [isIsomorphic, Tries]
+  Headline
+    probabilistic test for isomorphism of modules
+  Usage
+    t = isIsomorphic(N, M)
+    t = isIsomorphic(n, m)
+  Inputs
+    M:Module
+    m:Matrix -- presentation of M
+    N:Module
+    n:Matrix -- presentation of N
+    Homogeneous => Boolean
+    Verbose     => Boolean
+    Strategy    => Thing
+    Strict      => Boolean
+    Tries       => ZZ  -- the number of attempts at generating random map
+  Outputs
+    :Boolean -- whether the modules are isomorphic
+  Description
+    Text
+      In case both modules are homogeneous the program first uses @TO checkDegrees@
+      to see whether an isomorphism is possible. This may be an isomorphism up to shift
+      if @TT "Strict => false"@ (the default) or on the nose if @TT "Strict => true"@.
+    Example
+      S = ZZ/32003[x_0..x_3]
+      m = random(S^3, S^{4:-2});
+      M = coker m;
+      assert isIsomorphic(S^{-3} ** M, M)
+      assert not isIsomorphic(S^{-3} ** M, M, Strict => true)
+    Text
+      If this test is passed, the program uses a variant of the @TO Hom@ command
+      to compute a random map of minimal possible degree from $M$ to $N$,
+      and checks whether this is surjective and injective.
+      If @TT "Tries => n"@ is provided, this is attempted $n$ times,
+      otherwise a heuristic based on the characteristic of the field is used
+      to determine the number of attempts to make (1 in characteristic zero,
+      ~10 in characteristic 32003, and ~150 in characteristic 2).
+    Example
+      A = random(target m, target m);
+      B = random(source m, source m);
+      N = coker(A * m * B);
+      isIsomorphic(N, M)
+      isomorphism(N, M)
+    Text
+      The internal routine for generating a random map of minimal possible degree
+      is essentially equivalent to the code below, but caching and various other
+      optimizations make this routine more efficient.
+    Example
+      f = homomorphism random Hom(N, M, DegreeLimit => 0)
+      isIsomorphism f
+    Text
+      If the modules are inhomogeneous and @TT "Homogeneous => false"@ is passed, the random map is
+      a random linear combination of the generators of the module of homomorphisms.
+
+      If the output is false, then the conclusion of non-isomorphism is only probabilistic.
+      If the output is true, then as certificate an isomorphism $M \to N$ is cached
+      in the @TO youngest@ module and can be retried using the @TO isomorphism@ method.
+    Text
+      The following examples checks two well-known isomorphisms in homological algebra.
+    Example
+      S = ZZ/32003[x_0..x_3]
+      I = monomialCurveIdeal(S, {1,3,5})
+      c = codim I
+      W = Ext^c(S^1/I, S^1)
+      H = Hom(S^1/I, S^1/(I_0,I_1))
+      isIsomorphic(W, H)
+      isomorphism(W, H)
+    Example
+      mm = ideal gens S
+      T1 = Tor_1(W, S^1/(mm^3))
+      T2 = Tor_1(S^1/(mm^3), W)
+      elapsedTime isIsomorphic(T1, T2)
+      elapsedTime isomorphism(T1, T2)
+  Caveat
+    A negative result means that a random choice of homomorphism
+    was not an isomorphism; especially when the ground field is small,
+    this may not be definitive. Passing @TT "Tries => N"@ may be helpful.
+  SeeAlso
+    checkDegrees
+
+Node
+  Key
+    isomorphism
+   (isomorphism, Module, Module)
+   [isomorphism, Verbose]
+   [isomorphism, Homogeneous]
+   [isomorphism, Strict]
+   [isomorphism, Tries]
+  Headline
+    retrieve an isomorphism of modules
+  Usage
+    f = isomorphism(N, M)
+  Inputs
+    M:Module
+    N:Module
+    Homogeneous => Boolean -- if false, allow an inhomogeneous isomorphism
+    Verbose     => Boolean -- whether to print debugging information
+    Strict      => Boolean -- whether to force strict equality of degrees
+    Tries       => ZZ      -- the number of attempts at generating random map
+  Outputs
+    f:Matrix -- an isomorphism $M \to N$
+  Description
+    Text
+      This method retrieves the isomorphism between the given modules.
+      Note that this isomorphism is already cached if a previous call
+      to @TO isIsomorphic@ returned true, but otherwise that function is called.
+    Example
+      S = ZZ/32003[x_0..x_3]
+      m = random(S^3, S^{4:-2});
+      A = random(target m, target m);
+      B = random(source m, source m);
+      N = coker(A*m*B);
+      M = coker m;
+      f = isomorphism(N, M)
+      isIsomorphism f
+  SeeAlso
+    isIsomorphic
+    isIsomorphism
 ///
 
-doc ///
-Key
- checkDegrees
- (checkDegrees,Module,Module)
- (checkDegrees,Matrix,Matrix)
- [checkDegrees, Verbose]
- [checkDegrees, Strict] 
-Headline
- compares the degrees of generators of two modules
-Usage
- d = checkDegrees(N,M)
- d = checkDegrees(n,m) 
-Inputs
- N:Module
- n:Matrix
-  presentation of N
- M:Module
- m:Matrix
-  presentation of M
-Outputs
- d:Sequence
-  (Boolean, a degree in the ring of M and N)
-Description
-  Text
-   This is to be used with @TO isIsomorphic@.
-   
-   The routine compares the sorted lists of degrees of generators of the two modules;
-   the degreeLength (can be anything).
-   If the numbers of generators of M,N are different, the modules are not isomorphic,
-   and the routine returns (false, null).
-
-   If the numbers are the same, and all the corresponding degrees pairs differ
-   by the same amount (so that the modules might become isomorphic after a shift, 
-   then if Strict => false (the default)
-   the output (true, e) tells how to adjust the modules to make the degrees equal:
-   either tensor N with (ring N)^{e} or tensor M with (ring M)^{-e}.
-   
-   If Strict => true, then the output is (false, null) unless
-   the offset e is 0.
-  Example
-   S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-   A = S^{{2,1}}
-   B = S^{{1,1}}
-   B' = S^{{3,3}}**B
-   C = S^{{1,1}, {2,3}}
-   checkDegrees(A,B)
-   checkDegrees(A,C)
-
-   d = checkDegrees(B',B)
-   degrees (S^{d_1}**B') == degrees B
-   degrees (B') == degrees (S^{-d_1}**B)
-   checkDegrees(B',B,Strict=>true)   
-SeeAlso
- isIsomorphic
- Strict
+-----------------------------------------------------------------------------
+-* Test section *-
+-----------------------------------------------------------------------------
+
+TEST ///
+-- getting the degree shift right
+  S = ZZ/32003[x_1..x_3]
+  m = random(S^3, S^{4:-2})
+  A = random(target m, target m)
+  B = random(source m, source m)
+  m' = A*m*B
+  assert(checkDegrees (S^{-3}**coker m, coker m') == (true, {3}))
+  assert isIsomorphic (S^{-3}**coker m, coker m')
 ///
 
-doc ///
-Key
- Strict
-Headline
- Forces strict equality of degrees
-Usage
- d = checkDegrees(N,M, Strict =>true)
-Description
- Text
-   Used with Strict=>false, the default, 
-   checkDegrees(N,M) returns (true, deg) if 
-   degrees M and degrees N are equal up to a shift d.
-   With Strict => true the degree lists must be equal.
- Example
-     S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-     B = S^{{1,1}}
-     B' = S^{{3,3}}**B
-     d = checkDegrees(B',B)
-     degrees (S^{d_1}**B') == degrees B
-     degrees (B') == degrees (S^{-d_1}**B)
-     checkDegrees(B',B,Strict=>true)   
-SeeAlso
- checkDegrees
+TEST ///
+-- testing randomMinimalDegreeHomomorphism
+  debug needsPackage "Isomorphism"
+  S = ZZ/101[x,y]
+  m = matrix{{x,y}}
+  n = matrix{{x^2, y^2}}
+
+  assert all flatten for a from -2 to 2 list for b from -2 to 2 list (
+      M := S^{a}**(n++m);
+      N := S^{b}**(m++n);
+      (v, diffdegs) = checkDegrees(M, N);
+      0 == coker randomMinimalDegreeHomomorphism(M, N, -diffdegs_0)
+  )
 ///
 
+TEST ///
+-- the inhomogeneous case
+  S = ZZ/101[a,b, Degrees => {{1,0},{0,1}}]
+  C = S^{{1,1}, {2,3}}
+  C' = S^{{1,1}, {2,4}}
+  assert not isIsomorphic(C, C')
+  assert isIsomorphic(C,C', Homogeneous => false)
+  g = isomorphism(C,C', Homogeneous => false)
+  assert isWellDefined g
+  assert(source g == C')
+  assert(target g == C)
+  assert( coker g == 0)
+  assert(kernel g == 0)
+///
 
-doc ///
-Key
- isIsomorphic
- (isIsomorphic, Module, Module)
- (isIsomorphic, Matrix, Matrix) 
- [isIsomorphic, Verbose]
- [isIsomorphic, Homogeneous]
- [isIsomorphic, Strict] 
-Headline
- Probabilistic test for isomorphism of modules
-Usage
- t = isIsomorphic (N,M)
- t = isIsomorphic (n,m) 
-Inputs
- M:Module
- m:Matrix
-  presentation of M
- N:Module
- n:Matrix
-  presentation of N
- Homogeneous => Boolean
- Verbose => Boolean
- Strict => Boolean 
-Outputs
- t:Sequence
-  (Boolean, Matrix) or (Boolean, null)
-Description
-  Text
-   In case both modules are homogeneous the program first uses @TO checkDegrees@
-   to see whether an isomorphism is possible. This may be an isomorphism up to shift
-   if Strict => false (the default) or on the nose if Strict => true.
-   
-   If this test is passed, the program uses a variant of the Hom command
-   to compute a random map of minimal possible degree from M to N,
-   and checks whether this is surjective and injective.
-   
-   In the inhomogeneous case (or with Homogeneous => false) the random map is
-   a random linear combination of the generators of the module of homomorphisms.
-
-   If the output has the form (true, g), then g is guaranteed to be an
-   isomorphism. If the output is (false, null), then the
-   conclusion of non-isomorphism is only probabilistic.
-   
-  Example
-   setRandomSeed 0
-   S = ZZ/32003[x_0..x_3]     
-   m = random(S^3, S^{4:-2});
-   A = random(target m, target m)
-   B = random(source m, source m)
-   m' = A*m*B;
-   isIsomorphic (S^{-3}**coker m, coker m)
-   isIsomorphic (S^{-3}**coker m, coker m, Strict => true)
-   isIsomorphic (coker m, coker m')
-  Text   
-   The following example checks two of the well-known isomorphism
-   in homological algebra.
-  Example
-   setRandomSeed 0
-   S = ZZ/32003[x_0..x_3]   
-   I = monomialCurveIdeal(S,{1,3,5})
-   codim I
-   W = Ext^2(S^1/I, S^1)
-   W' = Hom(S^1/I, S^1/(I_0,I_1) )
-   isIsomorphic(W,W')   
-   mm = ideal gens S
-   (isIsomorphic(Tor_1(W, S^1/(mm^3)), Tor_1(S^1/(mm^3), W)))_0
-Caveat
-   A negative result means that a random choice of homomorphism
-   was not an isomorphism; especially when the ground field is small,
-   this may not be definitive.
-SeeAlso
- checkDegrees
+TEST ///
+-- the non-minimally presented case
+  S = ZZ/101[a,b]
+  m = map(S^{2}++S^1, S^{2}++S^{-1}, {{1,0},{0,a}})
+  C = coker m
+  --
+  C' = C
+  assert isIsomorphic(C,C')
+  g = isomorphism(C,C')
+  assert isWellDefined g
+  assert(source g == C')
+  assert(target g == C)
+  assert( coker g == 0)
+  assert(kernel g == 0)
+  --
+  a = random(target m, target m)
+  b = random(source m, source m)
+  C' = coker(a*m*b)
+  assert isIsomorphic(C,C')
+  g = isomorphism(C,C')
+  assert isWellDefined g
+  assert(source g == C')
+  assert(target g == C)
+  assert( coker g == 0)
+  assert(kernel g == 0)
 ///
--* Test section *-
--*
-restart
-loadPackage "Isomorphism"
-*-
-
-TEST /// -*getting the degree shift right*-
-   S = ZZ/32003[x_1..x_3]
-   m = random(S^3, S^{4:-2})
-   A = random(target m, target m)
-   B = random(source m, source m)
-   m' = A*m*B
-   assert(checkDegrees (S^{-3}**coker m, coker m') == (true, {3}))
-   assert((isIsomorphic (S^{-3}**coker m, coker m'))_0 == true)
+
+TEST ///
+-- checkDegrees
+  S = ZZ/101[a,b, Degrees => {{1,0},{0,1}}]
+  A = S^{{2,1}}
+  B = S^{{1,1}}
+  B' = S^{{3,3}}**B
+  C = S^{{1,1}, {2,3}}
+
+  checkDegrees(A,B)
+  assert(checkDegrees(A,B) == (true, {-1,0}))
+  assert(checkDegrees(A,C) == (false, null))
+
+  d = checkDegrees(B',B)
+  assert(degrees B  == degrees(S^{d_1}**B'))
+  assert(degrees B' == degrees(S^{-d_1}**B))
+  assert(checkDegrees(B',B, Strict => true) == (false, null))
 ///
 
--*
-restart
-uninstallPackage "Isomorphism"
-restart
-installPackage "Isomorphism"
-loadPackage "Isomorphism"
-*-
-
-TEST///--getting the degrees right in matrixHom
-debug needsPackage "Isomorphism"
-S = ZZ/101[x,y]
-m = matrix{{x,y}}
-n = matrix{{x^2, y^2}}
-
-setRandomSeed 0
-assert(all(flatten for a from -2 to 2 list for b from -2 to 2 list(
-a = -2;b=2;
-(v, diffdegs) = checkDegrees (S^{a}**(m++m),S^{b}**(m++m));
-((prune coker randomMinimalDegreeHomomorphism(S^{a}**(m++m),S^{b}**(m++m),-diffdegs_0) == 0))
-), t -> t))
+TEST ///
+-- isIsomorphic
+  needsPackage "Points"
+  canonicalIdeal = method()
+  canonicalIdeal Ideal := Ideal => I ->(
+      S := ring I;
+      R := S/I;
+      F := res I;
+      omega := coker sub(transpose F.dd_(length F), R);
+      H := Hom(omega,R^1);
+      deg := max degrees source gens H;
+      g := (gens H)*random(source gens H, R^-deg);
+      trim sub(ideal g,R) ---canonical ideal of a 1-dim ring.
+  )
+
+  kk = ZZ/32003
+  S = kk[x,y,z]
+
+  d = 15
+  I = points randomPointsMat(S,d);
+  elapsedTime W = canonicalIdeal I;
+  R = ring W;
+  n =2
+  M = module(trim W^n)
+  N = Hom(M, R^1);
+  assert isIsomorphic(N, M)
+  g = isomorphism(N, M)
+  assert isWellDefined g
+  assert(source g == M)
+  assert(target g == N)
+  assert( coker g == 0)
+  assert(kernel g == 0)
 ///
 
-TEST///--the inhomogeneous case
-   setRandomSeed 0
-   S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-C = S^{{1,1}, {2,3}}
-C' = S^{{1,1}, {2,4}}
-assert((isIsomorphic(C,C'))_0 == false)
-(t,g) = isIsomorphic(C,C', Homogeneous => false)
-assert(t==true)
-assert(isWellDefined g)
-assert(source g == C')
-assert(target g == C)
-assert(coker g == 0)
-assert(ker g == 0)
+TEST ///
+  S = ZZ/101[a,b, Degrees => {{1,0},{0,1}}]
+  A = S^{{2,1}}
+  B1 = S^{{1,1}}
+  B = S^{{1,1}, {2,3}}
+  d = checkDegrees(A,B, Verbose => true)
+  assert(d == (false, null))
+  d = checkDegrees(A,B1)
+  B' = S^{{3,3}}**B
+  d = checkDegrees(B',B)
+  e = checkDegrees(S^{d_1}**B', B)
+  e = checkDegrees(B', S^{-d_1}**B)
+  assert (e_0 == true)
+  ---
+  S = ZZ/101[a,b, Degrees => {{1,0},{0,1}}]
+  C = S^{{1,1}}
+  B = S^{{1,1}, {2,3}}
+  A = coker random(B, S^{2:-{2,2}})
+  assert(checkDegrees(A,B) == (true, {0,0}))
+  assert(checkDegrees(A,C) == (false, ))
+  assert(checkDegrees(B,C) == (false, ))
 ///
 
-TEST/// -- the non-minimally presented case
-   setRandomSeed 0
-      S = ZZ/101[a,b]
-C = coker (m = map(S^{2}++S^1, S^{2}++S^{-1}, matrix"1,0;0,a"))
-a = random(target m, target m)
-b = random(source m, source m)
-C' = coker (a*m*b)
-(t,g) = isIsomorphic(C,C')
-assert(t==true)
-assert(isWellDefined g)
-assert(source g == C')
-assert(target g == C)
-assert(coker g == 0)
-assert(ker g == 0)
+TEST ///
+  S = ZZ/32003[a,b,Degrees => {{1,0},{0,1}}]
+  B1 = S^{{1,1}}
+  B = S^{{1,1}, {2,3}}
+  A = coker random(B, S^{2:-{3,3}})
+  A1 = coker (a = random(B1^3, S^{2:-{3,3}}))
+  A2 = coker (random(target a, target a)*a*random(source a,source a))
+  C1 = coker (a = random(B1^3, S^{2:-{3,3}, -{4,5}}))
+  C2 = coker (matrix random(S^3, S^3)*matrix a*matrix random(S^3,S^3))
+
+  assert try ( isIsomorphic(C1,C2); false ) else true
+  assert isIsomorphic(C1,C2, Homogeneous => false)
+  assert isIsomorphic(C2,C1, Homogeneous => false)
+  assert isIsomorphism isomorphism(C1,C2, Homogeneous => false)
+  assert try ( isomorphism(C1,C2); false ) else true
+
+  assert isIsomorphic(A1, A2)
+  assert isIsomorphism isomorphism(A1,A2)
+  assert isIsomorphic(A,A)
+  assert isIsomorphic(B1,B1)
+  assert not isIsomorphic(A,B1)
+  assert not isIsomorphic(A1,B1)
 ///
 
-TEST /// -* checkDegrees *-
-   setRandomSeed 0
-   S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-   A = S^{{2,1}}
-   B = S^{{1,1}}
-   B' = S^{{3,3}}**B
-   C = S^{{1,1}, {2,3}}
-
-   checkDegrees(A,B)
-   assert(checkDegrees(A,B) ==(true,{-1,0}))
-   assert(checkDegrees(A,C) == (false,null))
-   
-   d = checkDegrees(B',B)
-   assert(degrees (S^{d_1}**B') == degrees B)
-   assert(degrees (B') == degrees (S^{-d_1}**B))
-   assert(checkDegrees(B',B,Strict=>true) == (false, null))
+TEST ///
+  debug Isomorphism
+  R = QQ[x,y, DegreeRank => 2]
+  M = R^{{1,1}, {0,1}}
+  N = R^{{0,1}, {1,1}}
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => false)
+  f = isomorphism(N, M,     Strict => false, Homogeneous => false)
+  assert same(f, map(N, M, {{0, 1}, {1, 0}}), N.cache.cache.Isomorphisms#(N, M, {true, true}))
+  assert isWellDefined f
+  assert isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert(degree f == {0,0})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => true)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => false)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => true)
+  f' = isomorphism(M, N,    Strict => false, Homogeneous => false)
+  assert same(f', map(M, N, {{0, 1}, {1, 0}}), inverse f)
+  assert(1 == #N.cache.cache.Isomorphisms)
+  assert not M.cache.cache.?Isomorphisms
+
+  N = R^{{0,1}, {1,1}} ** R^{{2,2}}
+  M = R^{{1,1}, {0,1}}
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => false)
+  f = isomorphism(N, M,     Strict => false, Homogeneous => false)
+  assert same(f, map(N, M, {{0, 1}, {1, 0}}, Degree => {-2,-2}), M.cache.cache.Isomorphisms#(N, M, {false, true}))
+  assert isWellDefined f
+  assert isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert(degree f == {-2,-2})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => true)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => false)
+  f = isomorphism(N, M,     Strict => true,  Homogeneous => false)
+  assert same(f, map(N, M, {{1, 0}, {0, 1}}), M.cache.cache.Isomorphisms#(N, M, {true, false}))
+  assert isWellDefined f
+  assert not isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert not isIsomorphic(N, M, Strict => true, Homogeneous => true)
+  assert(2 == #M.cache.cache.Isomorphisms)
+  assert not N.cache.cache.?Isomorphisms
 ///
 
+TEST ///
+  debug Isomorphism
+  R = QQ[x,y]
+  M = coker map(R^{{1}, {0}}, , {{x},{0}})
+  N = coker map(R^{{0}, {1}}, , {{0},{x}})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => false)
+  f = isomorphism(N, M,     Strict => false, Homogeneous => false)
+  -- TODO: ideally, if a homogeneous isomorphism _can_ be found, we should find it
+  --assert(f === N.cache.cache.Isomorphisms#(N, M, {true, true}))
+  assert isWellDefined f
+  --assert isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert(degree f == {0})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => true)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => false)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => true)
+  f' = isomorphism(M, N,    Strict => false, Homogeneous => false)
+  assert(inverse f === f')
+  --assert(1 == #N.cache.cache.Isomorphisms)
+  assert not M.cache.cache.?Isomorphisms
+
+  N = N ** R^{{2}}
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => false)
+  f = isomorphism(N, M,     Strict => false, Homogeneous => false)
+  --assert(f === N.cache.cache.Isomorphisms#(N, M, {false, true}))
+  assert isWellDefined f
+  --assert isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  --assert(degree f == {-2})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => true)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => false)
+  f = isomorphism(N, M,     Strict => true,  Homogeneous => false)
+  assert same(f, N.cache.cache.Isomorphisms#(N, M, {true, false}))
+  assert isWellDefined f
+  assert not isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert not isIsomorphic(N, M, Strict => true, Homogeneous => true)
+  --assert(2 == #N.cache.cache.Isomorphisms)
+  assert not M.cache.cache.?Isomorphisms
+///
 
+TEST ///
+  debug Isomorphism
+  R = QQ[x,y, DegreeRank => 2]
+  M = coker map(R^{{1,1}, {0,1}}, , {{x},{0}})
+  N = coker map(R^{{0,1}, {1,1}}, , {{0},{x}})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => false)
+  f = isomorphism(N, M,     Strict => false, Homogeneous => false)
+  -- TODO: ideally, if a homogeneous isomorphism _can_ be found, we should find it
+  --assert(f === N.cache.cache.Isomorphisms#(N, M, {true, true}))
+  assert isWellDefined f
+  --assert isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert(degree f == {0,0})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => true)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => false)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => true)
+  f' = isomorphism(M, N,    Strict => false, Homogeneous => false)
+  assert(inverse f === f')
+  --assert(1 == #N.cache.cache.Isomorphisms)
+  assert not M.cache.cache.?Isomorphisms
+
+  N = N ** R^{{2,2}}
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => false)
+  f = isomorphism(N, M,     Strict => false, Homogeneous => false)
+  --assert(f === N.cache.cache.Isomorphisms#(N, M, {false, true}))
+  assert isWellDefined f
+  --assert isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  --assert(degree f == {-2,-2})
+  assert isIsomorphic(N, M, Strict => false, Homogeneous => true)
+  assert isIsomorphic(N, M, Strict => true,  Homogeneous => false)
+  f = isomorphism(N, M,     Strict => true,  Homogeneous => false)
+  assert same(f, N.cache.cache.Isomorphisms#(N, M, {true, false}))
+  assert isWellDefined f
+  assert not isHomogeneous f
+  assert(source f === M)
+  assert(target f === N)
+  assert not isIsomorphic(N, M, Strict => true, Homogeneous => true)
+  --assert(2 == #N.cache.cache.Isomorphisms)
+  assert not M.cache.cache.?Isomorphisms
+///
 
+TEST ///
+  R = ZZ/101[x,y,z,w];
+  C = res coker vars R
+  N = image C.dd_2
+  M = ker C.dd_1
+  assert isIsomorphic(N, M)
+
+  R = ZZ/101[x,y,z,w, Degrees => {2:{1,0},2:{0,1}}];
+  C = res coker vars R
+  N = image C.dd_2
+  M = ker C.dd_1
+  assert isIsomorphic(N, M)
+///
 
-TEST///-*"isIsomorphic"*-
-needsPackage "Points"
-canonicalIdeal = method()
-canonicalIdeal Ideal := Ideal => I ->(
-    S := ring I;
-    R := S/I;
-    F := res I;
-    omega := coker sub(transpose F.dd_(length F), R);
-    H := Hom(omega,R^1);
-    deg := max degrees source gens H;
-    g := (gens H)*random(source gens H, R^-deg);
-    trim sub(ideal g,R) ---canonical ideal of a 1-dim ring.
-)
-
-kk=ZZ/32003
-S = kk[x,y,z]
-
-d = 15
-I = points randomPointsMat(S,d);
-elapsedTime W = canonicalIdeal I;
-R = ring W;
-n =2
-M = module(trim W^n)
-N = Hom(M, R^1)
-g = (isIsomorphic (N,M))_1;
-assert (isWellDefined g)
-assert(source g == M)
-assert(target g == N)
-assert(coker g == 0)
-assert(ker g == 0)
+TEST ///
+  M = ZZ^1 ++ coker(2 * id_(ZZ^1))
+  N = coker(2 * id_(ZZ^1)) ++ ZZ^1
+  assert isIsomorphic(N, M)
+  assert(isomorphism(N, M) == map(N, M, {{0, 1}, {1, 0}}))
 ///
 
+TEST ///
+  N = coker matrix{{0,1},{1,0}}
+  M = coker matrix{{1,0},{0,0}}
+  M' = coker matrix{{0,0},{0,1}}
+  scan({ ZZ, ZZ/2, QQ, GF 4, RR, CC }, k -> (
+	  printerr toString k;
+	  assert isIsomorphic(M ** k, M' ** k);
+	  assert isIsomorphism isomorphism(M ** k, M' ** k);
+	  assert not isIsomorphic(M ** k, N ** k);
+	  S := k[x];
+	  printerr toString S;
+	  assert isIsomorphic(M ** S, M' ** S);
+	  assert isIsomorphism isomorphism(M ** S, M' ** S);
+	  assert not isIsomorphic(M ** S, N ** S);
+      ))
+///
 
 end--
 
+-----------------------------------------------------------------------------
 -* Development section *-
+-----------------------------------------------------------------------------
+
 uninstallPackage "Isomorphism"
 restart
-installPackage "Isomorphism"
 check "Isomorphism"
+installPackage "Isomorphism"
 viewHelp "Isomorphism"
-restart
-
-
-
diff --git a/M2/Macaulay2/packages/Macaulay2Doc/changes.m2 b/M2/Macaulay2/packages/Macaulay2Doc/changes.m2
index 2f4cac768d5..c1267dd66b9 100644
--- a/M2/Macaulay2/packages/Macaulay2Doc/changes.m2
+++ b/M2/Macaulay2/packages/Macaulay2Doc/changes.m2
@@ -69,6 +69,8 @@ document {
     UL {
 	LI { "functionality changed in a way that could break code:",
 	    UL {
+		LI { "The method ", TO "Isomorphism :: isIsomorphism", " now only returns true or false. ",
+		    "To retrieved the computed isomorphism, use the method ", TO "Isomorphism :: isomorphism", "." },
 		LI { "The method ", TO (symbol\\, Matrix, Matrix), " is now a shortcut for ", TO (quotient', Matrix, Matrix), ". ",
 		    "The previous functionality is still available via ", TO (symbol//, Matrix, Matrix), ", which is a shortcut for ",
 		    TO (quotient, Matrix, Matrix), ". Additionally, both methods now work for maps of non-free modules."}
diff --git a/M2/Macaulay2/packages/Macaulay2Doc/options.m2 b/M2/Macaulay2/packages/Macaulay2Doc/options.m2
index 8f1043c0b49..2cff477ab36 100644
--- a/M2/Macaulay2/packages/Macaulay2Doc/options.m2
+++ b/M2/Macaulay2/packages/Macaulay2Doc/options.m2
@@ -12,7 +12,7 @@ opts  = unique opts;
 
 optionalNames := select(unique \\ flatten \\ keys \ opts, s -> instance(s, Symbol) and isGlobalSymbol toString s and
     not isUndocumented(d := makeDocumentTag s) and (isMissingDoc d or headline d === "an optional argument"))
-optionalNames = unique join({ Jacobian, Threads }, optionalNames)
+optionalNames = unique join({ Jacobian, Strict, Threads, Tries }, optionalNames)
 
 optionalValues := unique(flatten \\ values \ opts
     | { Prune, Binomial, Test, Center, Right, Left, Flexible, Postfix, Maintainer, FindOne } -- for manually added Symbols
diff --git a/M2/Macaulay2/packages/Matroids.m2 b/M2/Macaulay2/packages/Matroids.m2
index 1b04d42030e..74be5711495 100644
--- a/M2/Macaulay2/packages/Matroids.m2
+++ b/M2/Macaulay2/packages/Matroids.m2
@@ -772,7 +772,7 @@ getIsos (Matroid, Matroid) := List => (M, N) -> (
 	) else select(permutations(e), p -> all(C, c -> member(c/(i -> p#i), D)))
 )
 
-isomorphism (Matroid, Matroid) := HashTable => (M, N) -> ( -- assumes (M, N) satisfy "Could be isomorphic" by quickIsomorphismTest
+isomorphism (Matroid, Matroid) := HashTable => o -> (M, N) -> ( -- assumes (M, N) satisfy "Could be isomorphic" by quickIsomorphismTest
 	if M == N then return hashTable apply(#M_*, i -> (i, i));
 	-------------------------------
 	-- DFS: 9/12/23
diff --git a/M2/Macaulay2/packages/OldToricVectorBundles.m2 b/M2/Macaulay2/packages/OldToricVectorBundles.m2
index 522a42d9120..1c3b8ab38f3 100644
--- a/M2/Macaulay2/packages/OldToricVectorBundles.m2
+++ b/M2/Macaulay2/packages/OldToricVectorBundles.m2
@@ -35,7 +35,7 @@ newPackage("OldToricVectorBundles",
 	 },
     DebuggingMode => false,
     PackageImports => {"Varieties"},
-    PackageExports => {"OldPolyhedra"}
+    PackageExports => {"Isomorphism", "OldPolyhedra"}
     )
 
 -- Check version compatibility of Polyhedra
@@ -83,7 +83,7 @@ export {"ToricVectorBundle",
      "filtration", 
      "findWeights", 
      "isGeneral", 
-     "isomorphism", 
+     --"isomorphism", 
      "isVectorBundle", 
      "randomDeformation",
      "regCheck", 
@@ -487,8 +487,8 @@ areIsomorphic (ToricVectorBundleKlyachko,ToricVectorBundleKlyachko) := (T1,T2) -
 -- PURPOSE : Obtaining the isomorphism if two vector bundles are isomorphic
 --   INPUT : '(T1,T2)',  two ToricVectorBundleKlyachko
 --  OUTPUT : The isomorphism, if they are isomorphic, otherwise an error
-isomorphism = method(TypicalValue => Matrix)
-isomorphism (ToricVectorBundleKlyachko,ToricVectorBundleKlyachko) := (T1,T2) -> (
+--isomorphism = method(TypicalValue => Matrix)
+isomorphism (ToricVectorBundleKlyachko,ToricVectorBundleKlyachko) := o -> (T1,T2) -> (
      if not areIsomorphic(T1,T2) then error("The bundles are not isomorphic");
      T1.cache.isoMatrix#T2)				
 
@@ -2361,7 +2361,7 @@ document {
      
      PARA{}, "To obtain the isomorphism, if two bundles are isomorphic use ",TO isomorphism,".",
      
-     SeeAlso => {isomorphism,base,filtration,details}
+     SeeAlso => {(isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko),base,filtration,details}
      
      }
 
@@ -3104,7 +3104,7 @@ document {
      }
 
 document {
-     Key => {isomorphism, (isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko)},
+     Key => {(isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko)},
      Headline => " the isomorphism if the two bundles are isomorphic",
      Usage => " M = isomorphism(E,F)",
      Inputs => {
@@ -3555,7 +3555,7 @@ document {
 	  },
      
      SeeAlso => {areIsomorphic,
-	  isomorphism}
+	  (isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko)}
      
      }
 
diff --git a/M2/Macaulay2/packages/PhylogeneticTrees.m2 b/M2/Macaulay2/packages/PhylogeneticTrees.m2
index 2ef6fe817e7..b7659cdff30 100644
--- a/M2/Macaulay2/packages/PhylogeneticTrees.m2
+++ b/M2/Macaulay2/packages/PhylogeneticTrees.m2
@@ -18,6 +18,7 @@ newPackage(
 	  },
      Keywords => {"Applied Algebraic Geometry"},
      PackageImports => {
+	 "Isomorphism",	 
 	  "FourTiTwo"
 	  },
      PackageExports => {
@@ -73,7 +74,7 @@ export {
     "rootedTrees",
     "rootedBinaryTrees",
     "unlabeledTrees",
-    "isIsomorphic"
+    --"isIsomorphic"
     }
 protect \ {Group, Automorphisms, AList, Buckets}
 --------------------------------------------------------------------
@@ -550,8 +551,8 @@ isIsomorphicRooted(LeafTree,Set,LeafTree,Set) := (T1,v1,T2,v2) -> (
     AHU(G1,v1) == AHU(G2,v2)
     )
 
-isIsomorphic = method()
-isIsomorphic(LeafTree,LeafTree) := (T1,T2) -> (
+--isIsomorphic = method()
+isIsomorphic(LeafTree,LeafTree) := o -> (T1,T2) -> (
     if #(leaves T1) != #(leaves T2) or #(edges T1) != #(edges T2) then return false;
     C1 := center graph T1;
     C2 := center graph T2;
@@ -1846,7 +1847,6 @@ doc///
 -- isIsomorphic
 doc///
     Key
-        isIsomorphic
 	(isIsomorphic,LeafTree,LeafTree)
     Headline
         check isomorphism of two tree
diff --git a/M2/Macaulay2/packages/Posets.m2 b/M2/Macaulay2/packages/Posets.m2
index d1d4b15a2c0..bc9ebc5f933 100644
--- a/M2/Macaulay2/packages/Posets.m2
+++ b/M2/Macaulay2/packages/Posets.m2
@@ -26,6 +26,7 @@ newPackage(
             },
         DebuggingMode => false,
         PackageExports => {
+	    "Isomorphism",
             "SimplicialComplexes",
             "Graphs",
             "FourTiTwo",
@@ -101,7 +102,7 @@ export {
     "augmentPoset",
     "diamondProduct",
     "dropElements",
-    "isomorphism",
+  --"isomorphism",
   --"product",
     "removeIsomorphicPosets",
   --"union",
@@ -571,8 +572,7 @@ dropElements (Poset, Function) := Poset => (P, f) -> dropElements(P, select(P.Gr
 Poset - List := dropElements
 
 -- Inspired by Stembridge's Maple Package
-isomorphism = method()
-isomorphism (Poset, Poset) := HashTable => (P, Q) -> (
+isomorphism (Poset, Poset) := HashTable => o -> (P, Q) -> (
     -- Test for a quick bail-out (also puts the covering relations in the cache).
     if #P_* != #Q_* or #coveringRelations P != #coveringRelations Q then return null;
     -- Partition the vertices based on (#leq, #geq, #covering, #coveredBy).
@@ -2768,7 +2768,7 @@ doc ///
         (symbol _, Poset, ZZ)
         (symbol _, Poset, List)
         indexLabeling
-        isomorphism
+	(isomorphism, Poset, Poset)
         naturalLabeling
 ///
 
@@ -3062,7 +3062,7 @@ doc ///
             B == divisorPoset (2*3*5*7)
             B == divisorPoset (2^2*3*5)
     SeeAlso
-        isomorphism
+        (isomorphism, Poset, Poset)
         removeIsomorphicPosets
 ///
 
@@ -3167,7 +3167,6 @@ doc ///
 -- isomorphism
 doc ///
     Key
-        isomorphism
         (isomorphism,Poset,Poset)
     Headline
         computes an isomorphism between isomorphic posets
@@ -3256,7 +3255,7 @@ doc ///
             removeIsomorphicPosets L
     SeeAlso
         areIsomorphic
-        isomorphism
+        (isomorphism, Poset, Poset)
         Posets
 ///
 
diff --git a/M2/Macaulay2/packages/ReesAlgebra.m2 b/M2/Macaulay2/packages/ReesAlgebra.m2
index f4f1e835410..4dc263ebc72 100644
--- a/M2/Macaulay2/packages/ReesAlgebra.m2
+++ b/M2/Macaulay2/packages/ReesAlgebra.m2
@@ -73,7 +73,6 @@ export{
   "symmetricKernel", 
   "versalEmbedding",
   "whichGm",
-  "Tries",
   "jacobianDual",
   "symmetricAlgebraIdeal",
   "expectedReesIdeal",
@@ -872,7 +871,6 @@ doc ///
 doc ///
   Key
     [minimalReduction, Tries]
-    Tries
   Headline
     Set the number of random tries to compute a minimal reduction
   Usage
@@ -1648,7 +1646,7 @@ doc ///
       routine checks rigorously that the output ideal is a reduction, and tries
       probabilistically again if it is not. If it cannot find a minimal reduction after
       a certain number of tries, it returns an error. The number of tries defaults
-      to 20, but can be set with the optional argument @TO Tries@.
+      to 20, but can be set with the optional argument @TO [minimalReduction, Tries]@.
 
       To say that $I$ is integrally dependent on $J$ means that
       $JI^k = I^{k+1}$ for some non-negative integer $k$.  The smallest $k$ with this
diff --git a/M2/Macaulay2/packages/TateOnProducts.m2 b/M2/Macaulay2/packages/TateOnProducts.m2
index d58ae5d26bd..b85ee882554 100644
--- a/M2/Macaulay2/packages/TateOnProducts.m2
+++ b/M2/Macaulay2/packages/TateOnProducts.m2
@@ -22,7 +22,7 @@ newPackage(
 	},
     Keywords => {"Commutative Algebra"},
     PackageImports => {"Truncations", "SVDComplexes", "Complexes"},
-    PackageExports => {"SVDComplexes"},
+    PackageExports => {"Isomorphism", "SVDComplexes"},
     DebuggingMode => false
     )
 
@@ -72,7 +72,7 @@ export {
     "directImageComplex",
     "actionOnDirectImage",
     --the following could all be part of ChainComplexExtras
-    "isIsomorphic",
+    "isIsomorphicStrict",
 --    "prependZeroMap",
 --    "appendZeroMap",
 --    "removeZeroTrailingTerms",
@@ -735,8 +735,8 @@ source TA == TM
 target TA == TN
 isChainComplexMap TA
 
-isIsomorphic (truncate(regularity M, HH^0 (beilinson TM)), truncate(regularity M, M))
-isIsomorphic (truncate(regularity N, HH^0 (beilinson TN)), truncate(regularity N, N))
+isIsomorphicStrict (truncate(regularity M, HH^0 (beilinson TM)), truncate(regularity M, M))
+isIsomorphicStrict (truncate(regularity N, HH^0 (beilinson TN)), truncate(regularity N, N))
 
 ------------------------------
 restart
@@ -761,8 +761,8 @@ source TA == TM
 target TA == TN
 isChainComplexMap TA
 
-isIsomorphic (truncate(regM, HH^0 (beilinson TM)), truncate(regM, M))
-isIsomorphic (truncate(regN, HH^0 (beilinson TN)), truncate(regN, N))    
+isIsomorphicStrict (truncate(regM, HH^0 (beilinson TM)), truncate(regM, M))
+isIsomorphicStrict (truncate(regN, HH^0 (beilinson TN)), truncate(regN, N))    
 ------------------------
 ///
 
@@ -2386,24 +2386,29 @@ ring (sour^[j])
 isDegreeZeroSurjection := method()
 isDegreeZeroSurjection(Module,Module) := (A,B)->(
     --tests a random degree 0 map to see whether its a surjection
-    H := Hom(A,B);
-    B0 := basis(0,H); -- this seems to be total degree 0 in case of degreeLength>1
+    z := degree 0_A;
+    H := Hom(A, B, DegreeLimit => z);
+    B0 := basis(z, H);
     f := homomorphism(B0*random(source B0, (ring B0)^1));
     coker f == 0)
 
-isIsomorphic = method()
-isIsomorphic(Module,Module) := (A,B) -> (
-    --tests random degree 0 maps A->B, B->A and returns true
-    --if both are surjective.
-    if not(isHomogeneous A and isHomogeneous B) then 
-	  error"not implemented for inhomogeneous modules";
-    Ap := prune A;
-    Bp := prune B;
-    dA := set flatten degrees source gens Ap;
-    dB := set flatten degrees source gens Bp;
-    if dA =!= dB then false else    
-    isDegreeZeroSurjection(Ap,Bp) and isDegreeZeroSurjection(Bp,Ap)
-    )
+isIsomorphicStrict = method()
+isIsomorphicStrict(Module, Module) := (A,B) -> (
+    isIsomorphic(A, B, Strict => true, Homogeneous => true))
+-- This is now replaced and improved in Isomorphism package
+-- isIsomorphic = method()
+-- isIsomorphic(Module,Module) := o -> (A,B) -> (
+--     --tests random degree 0 maps A->B, B->A and returns true
+--     --if both are surjective.
+--     if not(isHomogeneous A and isHomogeneous B) then 
+-- 	  error"not implemented for inhomogeneous modules";
+--     Ap := prune A;
+--     Bp := prune B;
+--     dA := set flatten degrees source gens Ap;
+--     dB := set flatten degrees source gens Bp;
+--     if dA =!= dB then false else    
+--     isDegreeZeroSurjection(Ap,Bp) and isDegreeZeroSurjection(Bp,Ap)
+--     )
 
 
 
@@ -2830,7 +2835,7 @@ I=ker pphi
 isAction(I, apply(dim S, i->prune HH^1 ARphiN#1#i))
 
 R1=prune HH^1 source ARphiN#1#0
-isIsomorphic(truncate(2,R1),truncate(2,dual dual R1))
+isIsomorphicStrict(truncate(2,R1),truncate(2,dual dual R1))
 dual dual R1
 ----------------------
 s=3,d=1
@@ -2853,7 +2858,7 @@ keys ARphiN
 isAction(I, apply(dim S, i->prune HH^0 ARphiN#0#i))
 
 R0= prune HH^0 source ARphiN#0#0
-isIsomorphic(truncate(2,R0),truncate(2,dual dual R0))
+isIsomorphicStrict(truncate(2,R0),truncate(2,dual dual R0))
 dual dual R0
 ///
 
@@ -3470,7 +3475,7 @@ primaryDecomposition ann R1piL
 R0=prune HH^0 source AA#0#0
 degree R0, rank R0, ann R0
 dual dual R0
-isIsomorphic(truncate(3, R0), truncate(3,dual dual R0))
+isIsomorphicStrict(truncate(3, R0), truncate(3,dual dual R0))
 -- R0 is isomorphic to O+O(-1)+O(-2)+O(-3), which is the pushforward of O_C.
 
 R1=prune HH^1 source AA#1#0
@@ -3578,7 +3583,7 @@ composedFunctions = () -> (
       B=beilinson T
       M'=prune HH^0 B
       prune HH^1 B
-      isIsomorphic(M,M')
+      isIsomorphicStrict(M,M')
      --Text
      -- We study the corner complex of T at c={0,0} . 
      --Example
@@ -3597,7 +3602,7 @@ composedFunctions = () -> (
       apply(coLP,h->dim h)
       M1=HH^0 LP
       betti M1,betti M
-      isIsomorphic(M,M1)
+      isIsomorphicStrict(M,M1)
      --Text
      -- It works also for different syzygy modules in the corner complex.
      -- It works for all P=ker C.dd_k in the range where C.dd_k is computed 
@@ -3611,7 +3616,7 @@ composedFunctions = () -> (
       apply(coLP,h->dim h)
       M1=HH^(-k) LP
       betti M1, betti M
-      isIsomorphic(M,M1)
+      isIsomorphicStrict(M,M1)
      --Text
      -- Note that we have to take HH^{(-k)} == HH_k because of the homological position in which
      -- P sits.
@@ -3624,7 +3629,7 @@ composedFunctions = () -> (
       apply(coLP,h->dim h)
       M1=HH^(-k) LP
       betti M1,betti M
-      isIsomorphic(M,M1)
+      isIsomorphicStrict(M,M1)
      --Text
      -- Next we check the functor bgg on S-modules.
      --Example
@@ -3658,7 +3663,7 @@ composedFunctions = () -> (
      --Example
       Mc=prune truncate(c,M)**S^{c}
       betti (Mc'=HH^0 LP), betti Mc
-      isIsomorphic(Mc',Mc)
+      isIsomorphicStrict(Mc',Mc)
      --Example
       c={3,1}
       cohomologyMatrix(T1,low,2*high)
@@ -3673,7 +3678,7 @@ composedFunctions = () -> (
      
       Mc=prune truncate(c,M)**S^{c}
       betti (Mc'=HH^0 LP), betti Mc
-      isIsomorphic(Mc',Mc)
+      isIsomorphicStrict(Mc',Mc)
      --Text
      -- Now we test tateExtension.
      --Example
@@ -3697,7 +3702,7 @@ composedFunctions = () -> (
       M1=HH^0 B1
       dim M1
       betti M1, betti M
-      isIsomorphic(M1,M**S^{-d})
+      isIsomorphicStrict(M1,M**S^{-d})
      --Text
      -- Another shift:
      --Example 
@@ -3710,7 +3715,7 @@ composedFunctions = () -> (
       M2=HH^0 B2
       dim M2
       betti M2, betti M, betti truncate(-d,M)
-      isIsomorphic(M2,truncate(-d,M)**S^{-d}")
+      isIsomorphicStrict(M2,truncate(-d,M)**S^{-d}")
 
 
 
@@ -3761,7 +3766,7 @@ betti M1,betti M
 -- some how wrong twist
 M1'=M1**S^{{-2,-2}}
 betti M1',betti M
-isIsomorphic(M,M1')
+isIsomorphicStrict(M,M1')
 
 
 P=ker C.dd_2; betti P
@@ -3774,7 +3779,7 @@ M1=last coLP
 betti M1,betti M
 M1'=M1**S^{{-2,-2}}
 betti M1',betti M
-isIsomorphic(M,M1')
+isIsomorphicStrict(M,M1')
 -- we conclude: It works for various P=kerC.dd_p
 
 
@@ -3785,7 +3790,7 @@ cohomologyMatrix(RM,low,high)
 P=ker RM.dd_(-2)
 betti RM
 RM.dd_(-2)==0 -- Frank: I do not understand why the first differential is zero
-isIsomorphic(image(RM.dd_(-3)),image(T1.dd_(-3)))
+isIsomorphicStrict(image(RM.dd_(-3)),image(T1.dd_(-3)))
 
 
 --Testing Reciprocity
@@ -3799,7 +3804,7 @@ coLP=apply(toList(min LP1..max LP),i->prune HH^(-i) LP)
 apply(coLP,h->dim h)
 Mc=prune truncate(c,M)
 betti (Mc'=(first coLP)**S^{-{2,2}}), betti Mc
-isIsomorphic(Mc',Mc)
+isIsomorphicStrict(Mc',Mc)
 
 --Testing Reciprocity
 c={3,1}
@@ -3813,7 +3818,7 @@ apply(coLP,h->dim h)
 Mc=prune truncate(c,M)
 -- Frank: I do not understand why {-2,-2} is the right correction for the twist.
 betti (Mc'=(first coLP)**S^{-{2,2}}), betti Mc
-isIsomorphic(Mc',Mc)
+isIsomorphicStrict(Mc',Mc)
 
 --Testing Reciprocity
 c={3,2}
@@ -3827,7 +3832,7 @@ apply(coLP,h->dim h)
 Mc=prune truncate(c,M)
 -- Frank: I do not understand why {-2,-2} is the right correction for the twist.
 betti (Mc'=(first coLP)**S^{-{2,2}}), betti Mc
-isIsomorphic(Mc',Mc)
+isIsomorphicStrict(Mc',Mc)
 
 
 
@@ -3889,12 +3894,12 @@ document {
    }
 doc ///
    Key
-    isIsomorphic
-    (isIsomorphic,Module,Module)
+    isIsomorphicStrict
+    (isIsomorphicStrict,Module,Module)
    Headline
     probabilistic test for homogeneous isomorphism
    Usage
-    v = isIsomorphic(A,B)
+    v = isIsomorphicStrict(A,B)
    Inputs
     A:Module
     B:Module
@@ -3902,6 +3907,10 @@ doc ///
     v:Boolean
    Description
     Text
+     This is the same as @TO isIsomorphic@ but with additional
+     options @TT "Strict => true"@ and @TT "Homogeneous => true"@
+     added by default.
+
      First checks that the generator degrees are the same. Then
      computes a random degree 0 map A --> B and B --> A, 
      and returns true iff both are surjections.
@@ -3909,7 +3918,7 @@ doc ///
      S = ZZ/11[a,b]
      M = coker random(S^{-2,0,1,2}, S^{3:-3})
      N = coker (random(cover M, cover M)*presentation M)
-     tally apply(100, j->isIsomorphic(M,N))
+     tally apply(100, j->isIsomorphicStrict(M,N))
    Caveat
     If the function returns true then the modules ARE isomorphic. But if it returns false
     they may be isomorphic anyway.
@@ -3957,7 +3966,7 @@ doc ///
       B=beilinson T
       M'=prune HH^0 B
       prune HH^1 B
-      isIsomorphic(M,M')
+      isIsomorphicStrict(M,M')
      Text
       We study the corner complex of T at c=\{0,0\} . 
      Example
@@ -3976,7 +3985,7 @@ doc ///
       apply(coLP,h->dim h)
       M1=HH^0 LP
       betti M1,betti M
-      isIsomorphic(M,M1)
+      isIsomorphicStrict(M,M1)
      Text
       It works also for different syzygy modules in the corner complex.
       It works for all P=ker C.dd_k in the range where C.dd_k is computed 
@@ -3990,7 +3999,7 @@ doc ///
       apply(coLP,h->dim h)
       M1=HH^(-k) LP
       betti M1, betti M
-      isIsomorphic(M,M1)
+      isIsomorphicStrict(M,M1)
      Text
       Note that we have to take HH^{(-k)} == HH_k because of the homological position in which
       P sits.
@@ -4003,7 +4012,7 @@ doc ///
       apply(coLP,h->dim h)
       M1=HH^(-k) LP
       betti M1,betti M
-      isIsomorphic(M,M1)
+      isIsomorphicStrict(M,M1)
      Text
       Next we check the functor bgg on S-modules.
      Example
@@ -4037,7 +4046,7 @@ doc ///
      Example
       Mc=prune truncate(c,M)**S^{c}
       betti (Mc'=HH^0 LP), betti Mc
-      isIsomorphic(Mc',Mc)
+      isIsomorphicStrict(Mc',Mc)
      Example
       c={3,1}
       cohomologyMatrix(T1,low,2*high)
@@ -4052,7 +4061,7 @@ doc ///
      
       Mc=prune truncate(c,M)**S^{c}
       betti (Mc'=HH^0 LP), betti Mc
-      isIsomorphic(Mc',Mc)
+      isIsomorphicStrict(Mc',Mc)
      Text
       Now we test tateExtension.
      Example
@@ -4076,7 +4085,7 @@ doc ///
       M1=HH^0 B1
       dim M1
       betti M1, betti M
-      isIsomorphic(M1,M**S^{-d})
+      isIsomorphicStrict(M1,M**S^{-d})
      Text
       Another shift:
      Example 
@@ -4089,7 +4098,7 @@ doc ///
       M2=HH^0 B2
       dim M2
       betti M2, betti M, betti truncate(-d,M)
-      isIsomorphic(M2,truncate(-d,M)**S^{-d})
+      isIsomorphicStrict(M2,truncate(-d,M)**S^{-d})
    SeeAlso
     bgg
     upperCorner
@@ -4097,7 +4106,7 @@ doc ///
     cohomologyMatrix
     beilinson
     tateExtension
-    isIsomorphic
+    isIsomorphicStrict
 ///
 
 
@@ -6043,7 +6052,7 @@ doc ///
 	        
       M0=source (prunedActionList(0))_0
       (rank M0, degree M0, betti res M0)
-      isIsomorphic(truncate(regularity M0, M0), truncate(regularity M0, dual dual M0))
+      isIsomorphicStrict(truncate(regularity M0, M0), truncate(regularity M0, dual dual M0))
       dual dual M0
 
       M1=source (prunedActionList(1))_0
@@ -6393,8 +6402,8 @@ M = S^{{1,0},{0,1}}
 M' = S^{{0,1},{1,0}}
 A = S^{{1,-1}}
 B = S^1
-assert(isIsomorphic(M,M') ===true)
-assert(isIsomorphic(A,B) ===false)
+assert(isIsomorphicStrict(M,M') ===true)
+assert(isIsomorphicStrict(A,B) ===false)
 ///
 
 TEST ///
@@ -6443,11 +6452,11 @@ BW = beilinsonWindow C
 betti BW
 B = beilinson C
 M' = HH_0 B
-assert isIsomorphic(M',M)
+assert isIsomorphicStrict(M',M)
 --note: isomorphic, not equal!
 BW' = beilinsonWindow tateResolution(M',-high,high)
 assert( all(2, i->BW_i == BW'_i))
-assert(isIsomorphic(coker BW.dd_1, coker BW'.dd_1))
+assert(isIsomorphicStrict(coker BW.dd_1, coker BW'.dd_1))
 ///
 
 TEST ///
@@ -6457,7 +6466,7 @@ high = {3,3}
 C = tateResolution(M,-high,high);
 B = beilinson C
 M' = HH_0 B
-assert isIsomorphic(M',M)
+assert isIsomorphicStrict(M',M)
 --note: isomorphic, not equal!
 ///
 
@@ -6788,7 +6797,7 @@ assert(C'== C)
   F3a = prune truncate(tdeg,F3);
   F2a = prune truncate(tdeg,F2);
 assert(numgens F3a == numgens F2a)
---  isIsomorphic(F3a, F2a) -- this is too slow!
+--  isIsomorphicStrict(F3a, F2a) -- this is too slow!
 
 
   -- Now shift another time
@@ -7177,8 +7186,8 @@ BC = beilinson C
 betti BC
 tallyDegrees BC
 M' = HH_0 BC
-isIsomorphic(truncate({3,1},M), truncate({3,1},M'))
-isIsomorphic(truncate({2,0},M), truncate({2,0},M'))
+isIsomorphicStrict(truncate({3,1},M), truncate({3,1},M'))
+isIsomorphicStrict(truncate({2,0},M), truncate({2,0},M'))
 cohomologyMatrix(M,low,high)
 cohomologyMatrix(RM,low,high)
 
@@ -7196,8 +7205,8 @@ BT = beilinson T
 betti BT
 tallyDegrees BT
 M' = HH_0 BT
-isIsomorphic(truncate({3,0},M), truncate({3,0},M'))
-isIsomorphic(truncate({2,1},M), truncate({2,1},M'))
+isIsomorphicStrict(truncate({3,0},M), truncate({3,0},M'))
+isIsomorphicStrict(truncate({2,1},M), truncate({2,1},M'))
 
 C = cornerComplex(T,{0,0})
 cohomologyMatrix(C,low,high)
@@ -7210,7 +7219,7 @@ apply(betti C.dd_0)
 (values HH)
 (apply(8, i->ann HH_(-i)(bgg image C.dd_0)))/codim
 M' = HH_(-4)(bgg image C.dd_0);
-isIsomorphic(truncate({2,2},M), truncate({2,2},M'))
+isIsomorphicStrict(truncate({2,2},M), truncate({2,2},M'))
 cohomologyMatrix (M', low, high)
 cohomologyMatrix (M, low, high)
 
diff --git a/M2/Macaulay2/packages/ToricVectorBundles.m2 b/M2/Macaulay2/packages/ToricVectorBundles.m2
index e681711ef97..b32d37d7623 100644
--- a/M2/Macaulay2/packages/ToricVectorBundles.m2
+++ b/M2/Macaulay2/packages/ToricVectorBundles.m2
@@ -34,7 +34,7 @@ newPackage("ToricVectorBundles",
 	 },
     Configuration => {},
     PackageImports => {"Varieties"},
-    PackageExports => {"Polyhedra"}
+    PackageExports => {"Isomorphism", "Polyhedra"}
     )
 
 -- Check version compatibility of Polyhedra
@@ -69,7 +69,7 @@ export {"ToricVectorBundle",
      "addBaseChange", 
      "addDegrees", 
      "addFiltration", 
-     "areIsomorphic", 
+     "areIsomorphic",
      "base",
      "cartierIndex",
      "charts",
@@ -82,7 +82,7 @@ export {"ToricVectorBundle",
      "filtration", 
      "findWeights", 
      "isGeneral", 
-     "isomorphism", 
+     --"isomorphism", 
      "isVectorBundle", 
      "randomDeformation",
      "regCheck", 
@@ -524,8 +524,8 @@ areIsomorphic (ToricVectorBundleKlyachko,ToricVectorBundleKlyachko) := (T1,T2) -
 -- PURPOSE : Obtaining the isomorphism if two vector bundles are isomorphic
 --   INPUT : '(T1,T2)',  two ToricVectorBundleKlyachko
 --  OUTPUT : The isomorphism, if they are isomorphic, otherwise an error
-isomorphism = method(TypicalValue => Matrix)
-isomorphism (ToricVectorBundleKlyachko,ToricVectorBundleKlyachko) := (T1,T2) -> (
+--isomorphism = method(TypicalValue => Matrix)
+isomorphism (ToricVectorBundleKlyachko,ToricVectorBundleKlyachko) := o -> (T1,T2) -> (
      if not areIsomorphic(T1,T2) then error("The bundles are not isomorphic");
      T1.cache.isoMatrix#T2)				
 
@@ -2404,9 +2404,9 @@ document {
 	  " areIsomorphic(E,F)"
 	  },
      
-     PARA{}, "To obtain the isomorphism, if two bundles are isomorphic use ",TO isomorphism,".",
+     PARA{}, "To obtain the isomorphism, if two bundles are isomorphic use ",TO (isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko),".",
      
-     SeeAlso => {isomorphism,base,filtration,details},
+     SeeAlso => {(isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko),base,filtration,details},
      
      Caveat => {"If ",TT "E"," and ",TT "F"," are defined over different rings (e.g. ",TT "QQ"," and ",TT "ZZ",") then ",TT "areIsomorphic(E,F)"," will return ",TT "false",". Likewise, if the bundles are only defined over ",TT "ZZ",", the function will check for an isomorphism of the filtrations over ",TT "ZZ","."}
      }
@@ -3153,7 +3153,7 @@ document {
      }
 
 document {
-     Key => {isomorphism, (isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko)},
+     Key => {(isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko)},
      Headline => " the isomorphism if the two bundles are isomorphic",
      Usage => " M = isomorphism(E,F)",
      Inputs => {
@@ -3631,7 +3631,7 @@ document {
 	  },
      
      SeeAlso => {areIsomorphic,
-	  isomorphism}
+	  (isomorphism,ToricVectorBundleKlyachko,ToricVectorBundleKlyachko)}
      
      }
 
diff --git a/M2/Macaulay2/packages/Varieties/SheafMaps.m2 b/M2/Macaulay2/packages/Varieties/SheafMaps.m2
index cf9cc84059a..813195d9929 100644
--- a/M2/Macaulay2/packages/Varieties/SheafMaps.m2
+++ b/M2/Macaulay2/packages/Varieties/SheafMaps.m2
@@ -167,17 +167,26 @@ ZZ == SheafMap := Boolean => (n, f) -> f == n
 
 isIsomorphism SheafMap := Boolean => f -> ker f == 0 and coker f == 0
 
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
-    M := module prune F;
-    N := module prune G;
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
+-- TODO: isomorphisms of modules are cached under Y.cache.cache.Isomorphisms,
+-- where Y is the youngest of the modules, but currently isomorphisms of sheaves
+-- is simply cached under the source. We should fix this.
+importFrom_Isomorphism "Isomorphisms"
+
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
+    (M, N) := (module prune F, module prune G);
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
 
 -- TODO: perhaps better would be to construct random
 -- maps F --> G and check their kernel and cokernel.
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> F === G or (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
     -- Note: sometimes calling isIsomorphic(prune F, prune G) is faster,
     -- but we will leave it to the user to decide if that is the case.
     -- Check if F and G are already pruned or if their minimal presentation is cached
@@ -191,12 +200,25 @@ isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
 	r := 1 + max(regularity F.module, regularity G.module);
 	truncate(r, F.module, MinimalGenerators => false),
 	truncate(r, G.module, MinimalGenerators => false));
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
-
-isIsomorphic(SheafMap, SheafMap) := Sequence => o -> (psi, phi) -> isIsomorphic(coker phi, coker psi, o)
+    -- FIXME: this is incomplete, because we need to store pruning maps or embedding maps
+    -- in order to compose/precompose with the cached isomorphism on the modules.
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
+
+isomorphism(CoherentSheaf, CoherentSheaf) := SheafMap => o -> (F, G) -> (
+    if F === G then id_F else if isIsomorphic(F, G, o,
+	Strict => true, Homogeneous => true)
+    -- FIXME: this is probably not correct yet, because we may
+    -- need to compose/precompose with the pruning maps of F and G.
+    then --map(F, G,
+	--inverse (prune F).cache.pruningMap *
+	sheaf isomorphism splice(G.cache.Isomorphisms#F,
+	    Strict => true, Homogeneous => true)
+	--* (prune G).cache.pruningMap)
+    else error "sheaves are not isomorphic")
+
+isIsomorphic(SheafMap, SheafMap) := Boolean => o -> (psi, phi) -> isIsomorphic(coker phi, coker psi, o)
 
 -- arithmetic ops
 - SheafMap := f -> map(target f, source f, -matrix f)
diff --git a/M2/Macaulay2/packages/Varieties/tests-complexes.m2 b/M2/Macaulay2/packages/Varieties/tests-complexes.m2
index bf105466968..acc6aadc523 100644
--- a/M2/Macaulay2/packages/Varieties/tests-complexes.m2
+++ b/M2/Macaulay2/packages/Varieties/tests-complexes.m2
@@ -9,7 +9,7 @@ TEST ///
   assert(C.dd_1 * C.dd_2 == 0)
   assert(homology(C.dd_1, C.dd_2) == 0)
   M = sheaf(module cotangentSheaf X ** R)
-  assert first isIsomorphic(M, C_2)
+  assert isIsomorphic(M, C_2)
 
   -- testing tensor of complexes of sheaves
   D = complex OO_Y^{-1}
diff --git a/M2/Macaulay2/packages/Varieties/tests-maps.m2 b/M2/Macaulay2/packages/Varieties/tests-maps.m2
index c9c15d9a718..fa365e365f6 100644
--- a/M2/Macaulay2/packages/Varieties/tests-maps.m2
+++ b/M2/Macaulay2/packages/Varieties/tests-maps.m2
@@ -54,6 +54,7 @@ TEST /// -- tests for cached saturation map M --> Gamma_* sheaf M
   F = sheaf truncate(4,S^1)
   assert(F == OO_X^1)
   G = prune F
+  assert isIsomorphic(F, G)
   h = G.cache.pruningMap
   assert(inverse h * h === id_F)
   --assert(h * inverse h === id_G) -- FIXME
@@ -341,9 +342,9 @@ TEST ///
   X = Proj S
   M = truncate(1, S^1)
   F = sheaf M
-  --elapsedTime assert first isIsomorphic(M, module F); -- 3.6s --NOW DOES NOT FINISH
-  --elapsedTime assert first isIsomorphic(OO_X^1, F)    -- 8.4s -> 3.1s --NOW DOES NOT FINISH
-  elapsedTime assert first isIsomorphic(OO_X^1, prune F)
+  --elapsedTime assert isIsomorphic(M, module F); -- 3.6s --NOW DOES NOT FINISH
+  --elapsedTime assert isIsomorphic(OO_X^1, F)    -- 8.4s -> 3.1s --NOW DOES NOT FINISH
+  elapsedTime assert isIsomorphic(OO_X^1, prune F)
 ///
 
 
@@ -438,7 +439,7 @@ TEST ///
   X = Proj R
   f = cotangentSurjection X
   assert isWellDefined f
-  assert(first isIsomorphic(prune ker f, sheaf(J/J^2**R)))
+  assert isIsomorphic(prune ker f, sheaf(J/J^2**R))
 ///
 
 
diff --git a/M2/Macaulay2/packages/Varieties/tests-varieties.m2 b/M2/Macaulay2/packages/Varieties/tests-varieties.m2
index 13624f04fea..97ecf77d3d8 100644
--- a/M2/Macaulay2/packages/Varieties/tests-varieties.m2
+++ b/M2/Macaulay2/packages/Varieties/tests-varieties.m2
@@ -27,7 +27,7 @@ TEST /// -- twisted cubic curve
   N = truncate(maxRegs, G.module, MinimalGenerators => false)
   assert(prune sheaf M === prune F)
   assert(prune sheaf N === G)
-  assert first isIsomorphic(M, N, Strict => true)
+  assert isIsomorphic(M, N, Strict => true)
 ///
 
 TEST ///


Reviews:
user: login: eisenbud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T17:22:02Z
user: login: mahrud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T18:26:08Z
user: login: eisenbud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:46:22Z
user: login: eisenbud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:49:35Z
user: login: eisenbud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:53:00Z
user: login: eisenbud
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:07:50Z
user: login: mahrud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T02:01:32Z
user: login: Devlin-Mallory
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T08:47:43Z
user: login: Devlin-Mallory
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T08:52:06Z
user: login: Devlin-Mallory
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T09:16:22Z
user: login: mahrud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T13:58:12Z
user: login: mahrud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T14:07:12Z
user: login: mahrud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T22:15:23Z
user: login: mahrud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T22:34:06Z
user: login: Devlin-Mallory
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T08:32:28Z

Review comments:
diff_hunk: @@ -1541,32 +1539,6 @@ compositions(ZZ,ZZ,ZZ) := (nparts, k, maxelem) -> (
      compositionn = null;
      result
      );
-
-isDegreeZeroSurjection := method()
-isDegreeZeroSurjection(Module,Module) := Boolean => (A,B) -> (
-    --tests a random degree 0 map f:A --> B to see whether its a surjection, 
-    --and returns the answer. If "true" and  o.Verbose == true then returns f.
-    H := Hom(A,B);
-    B0 := basis(0,H); -- this seems to be total degree 0 in case of degreeLength>1
-    f := homomorphism(B0*random(source B0, (ring B0)^1));
-    coker f == 0
-)
-
--- the following function works for modules in the graded case.
--- and produces (only) graded isomorphisms.
-isIsomorphic = method()
-isIsomorphic(Module,Module) := (A,B) -> (
-    --tests random degree 0 maps A->B, B->A and returns true
-    --if both are surjective.
-    if not(isHomogeneous A and isHomogeneous B) then 
-	  error"not implemented for inhomogeneous modules";
-    Ap := prune A;
-    Bp := prune B;
-    dA := set flatten degrees source gens Ap;
-    dB := set flatten degrees source gens Bp;
-    if dA =!= dB then return false;
-    degreeZeroSurjection(Ap,Bp) =!= null and degreeZeroSurjection(Bp,Ap) =!= null
-    )
 *-
 
 TEST/// --test of degreeZeroSurjection
path: M2/Macaulay2/packages/EagonResolution.m2
user: login: eisenbud
body: Why is the test still here if the function has been removed?
created_at: 2025-05-01T17:22:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1541,32 +1539,6 @@ compositions(ZZ,ZZ,ZZ) := (nparts, k, maxelem) -> (
      compositionn = null;
      result
      );
-
-isDegreeZeroSurjection := method()
-isDegreeZeroSurjection(Module,Module) := Boolean => (A,B) -> (
-    --tests a random degree 0 map f:A --> B to see whether its a surjection, 
-    --and returns the answer. If "true" and  o.Verbose == true then returns f.
-    H := Hom(A,B);
-    B0 := basis(0,H); -- this seems to be total degree 0 in case of degreeLength>1
-    f := homomorphism(B0*random(source B0, (ring B0)^1));
-    coker f == 0
-)
-
--- the following function works for modules in the graded case.
--- and produces (only) graded isomorphisms.
-isIsomorphic = method()
-isIsomorphic(Module,Module) := (A,B) -> (
-    --tests random degree 0 maps A->B, B->A and returns true
-    --if both are surjective.
-    if not(isHomogeneous A and isHomogeneous B) then 
-	  error"not implemented for inhomogeneous modules";
-    Ap := prune A;
-    Bp := prune B;
-    dA := set flatten degrees source gens Ap;
-    dB := set flatten degrees source gens Bp;
-    if dA =!= dB then return false;
-    degreeZeroSurjection(Ap,Bp) =!= null and degreeZeroSurjection(Bp,Ap) =!= null
-    )
 *-
 
 TEST/// --test of degreeZeroSurjection
path: M2/Macaulay2/packages/EagonResolution.m2
user: login: mahrud
body: I kept `degreeZeroSurjection` in place because it's used elsewhere in the package, by `homologyIsomorphism`.
created_at: 2025-05-01T18:26:08Z
author_association: MEMBER
diff_hunk: @@ -102,438 +133,718 @@ checkDegrees(Module, Module) := Sequence => o -> (A,B) -> (
     )
 checkDegrees(Matrix, Matrix) := Sequence => o-> (m,n) -> checkDegrees(target m, target n, o)
 
-///
-restart
-loadPackage "Isomorphism"
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-A = S^{{2,1}}
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-d = checkDegrees(A,B, Verbose => true)
-assert(d == (false,null))
-d = checkDegrees(A,B1)
-B' = S^{{3,3}}**B
-d = checkDegrees(B',B)
-e = checkDegrees(S^{d_1}**B', B)
-e = checkDegrees(B', S^{-d_1}**B)
-assert (e_0 == true)
----
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-A = coker random(B, S^{2:-{2,2}})
-B = A
-checkDegrees(A,B)
-d = checkDegrees(A,B1)
-checkDegrees(B,B)
-///
+-----------------------------------------------------------------------------
+-- isIsomorphism and isomorphism
+-----------------------------------------------------------------------------
+
+-- the default number of attempts for randomized algorithms
+-- e.g. 145 tries in char 2, 10 in char 32003, and 1 in char 0
+defaultNumTries = p -> ceiling(0.1 + 100 / log p)
+--apply({2, 32003, 0}, defaultNumTries)
+
+-- key to cache known isomorphisms
+protect Isomorphisms
+
+-- currently we cache isomorphisms ONLY in the source
+setIsomorphism = (N, M, f, o) -> (
+    f = reduceCoefficient f;
+    o = { o.Strict or all(degree f, zero),
+	o.Homogeneous or isHomogeneous f };
+    M.cache.Isomorphisms ??= new MutableHashTable;
path: M2/Macaulay2/packages/Isomorphism.m2
user: login: eisenbud
body: cache in younger of M,N
created_at: 2025-05-07T21:46:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,438 +133,718 @@ checkDegrees(Module, Module) := Sequence => o -> (A,B) -> (
     )
 checkDegrees(Matrix, Matrix) := Sequence => o-> (m,n) -> checkDegrees(target m, target n, o)
 
-///
-restart
-loadPackage "Isomorphism"
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-A = S^{{2,1}}
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-d = checkDegrees(A,B, Verbose => true)
-assert(d == (false,null))
-d = checkDegrees(A,B1)
-B' = S^{{3,3}}**B
-d = checkDegrees(B',B)
-e = checkDegrees(S^{d_1}**B', B)
-e = checkDegrees(B', S^{-d_1}**B)
-assert (e_0 == true)
----
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-A = coker random(B, S^{2:-{2,2}})
-B = A
-checkDegrees(A,B)
-d = checkDegrees(A,B1)
-checkDegrees(B,B)
-///
+-----------------------------------------------------------------------------
+-- isIsomorphism and isomorphism
+-----------------------------------------------------------------------------
+
+-- the default number of attempts for randomized algorithms
+-- e.g. 145 tries in char 2, 10 in char 32003, and 1 in char 0
+defaultNumTries = p -> ceiling(0.1 + 100 / log p)
+--apply({2, 32003, 0}, defaultNumTries)
+
+-- key to cache known isomorphisms
+protect Isomorphisms
+
+-- currently we cache isomorphisms ONLY in the source
+setIsomorphism = (N, M, f, o) -> (
+    f = reduceCoefficient f;
+    o = { o.Strict or all(degree f, zero),
+	o.Homogeneous or isHomogeneous f };
+    M.cache.Isomorphisms ??= new MutableHashTable;
+    M.cache.Isomorphisms#(N, o) = f;
+    true)
+
+-- we check isomorphisms cached in source OR target
+getIsomorphism = (N, M, o) -> (
+    if M.cache.?Isomorphisms then
+    for isom in keys M.cache.Isomorphisms do (
+	(N', o') := isom;
+	if N' === N
+	and ( not o.Strict      or o.Strict      and o'#0 )
+	and ( not o.Homogeneous or o.Homogeneous and o'#1 )
+	then return M.cache.Isomorphisms#isom);
+    if N.cache.?Isomorphisms then
+    for isom in keys N.cache.Isomorphisms do (
+	(M', o') := isom;
+	if M' === M
+	and ( not o.Strict      or o.Strict      and o'#0 )
+	and ( not o.Homogeneous or o.Homogeneous and o'#1 )
+	then return inverse N.cache.Isomorphisms#isom))
+
path: M2/Macaulay2/packages/Isomorphism.m2
user: login: eisenbud
body: simplify b y age
created_at: 2025-05-07T21:49:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,438 +133,718 @@ checkDegrees(Module, Module) := Sequence => o -> (A,B) -> (
     )
 checkDegrees(Matrix, Matrix) := Sequence => o-> (m,n) -> checkDegrees(target m, target n, o)
 
-///
-restart
-loadPackage "Isomorphism"
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-A = S^{{2,1}}
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-d = checkDegrees(A,B, Verbose => true)
-assert(d == (false,null))
-d = checkDegrees(A,B1)
-B' = S^{{3,3}}**B
-d = checkDegrees(B',B)
-e = checkDegrees(S^{d_1}**B', B)
-e = checkDegrees(B', S^{-d_1}**B)
-assert (e_0 == true)
----
-S = ZZ/101[a,b,Degrees => {{1,0},{0,1}}]
-B1 = S^{{1,1}}
-B = S^{{1,1}, {2,3}}
-A = coker random(B, S^{2:-{2,2}})
-B = A
-checkDegrees(A,B)
-d = checkDegrees(A,B1)
-checkDegrees(B,B)
-///
+-----------------------------------------------------------------------------
+-- isIsomorphism and isomorphism
+-----------------------------------------------------------------------------
+
+-- the default number of attempts for randomized algorithms
+-- e.g. 145 tries in char 2, 10 in char 32003, and 1 in char 0
+defaultNumTries = p -> ceiling(0.1 + 100 / log p)
+--apply({2, 32003, 0}, defaultNumTries)
+
+-- key to cache known isomorphisms
+protect Isomorphisms
+
+-- currently we cache isomorphisms ONLY in the source
+setIsomorphism = (N, M, f, o) -> (
+    f = reduceCoefficient f;
+    o = { o.Strict or all(degree f, zero),
+	o.Homogeneous or isHomogeneous f };
+    M.cache.Isomorphisms ??= new MutableHashTable;
+    M.cache.Isomorphisms#(N, o) = f;
+    true)
+
+-- we check isomorphisms cached in source OR target
+getIsomorphism = (N, M, o) -> (
+    if M.cache.?Isomorphisms then
+    for isom in keys M.cache.Isomorphisms do (
+	(N', o') := isom;
+	if N' === N
+	and ( not o.Strict      or o.Strict      and o'#0 )
+	and ( not o.Homogeneous or o.Homogeneous and o'#1 )
+	then return M.cache.Isomorphisms#isom);
+    if N.cache.?Isomorphisms then
+    for isom in keys N.cache.Isomorphisms do (
+	(M', o') := isom;
+	if M' === M
+	and ( not o.Strict      or o.Strict      and o'#0 )
+	and ( not o.Homogeneous or o.Homogeneous and o'#1 )
+	then return inverse N.cache.Isomorphisms#isom))
+
+hasIsomorphism = (N, M, o) -> null =!= getIsomorphism(N, M, o)
+
+-- give an isomorphism between two free modules with same degrees
+isIsomorphismFree = (N, M0, o) -> (
+    if rank N != rank M0 then return false;
+    (d1, d2) := (degrees N, degrees M0);
+    diffdegs := min d2 - min d1;
+    if o.Strict then M := M0 else d2 = degrees(
+	M = M0 ** (ring M0)^{diffdegs});
+    if sort d1 != sort d2 then
+    return if o.Homogeneous then false
+    else setIsomorphism(N, M0, map(N, M0, 1), o);
+    -- FIXME: because of https://github.com/Macaulay2/M2/issues/3719,
+    -- this might not give the most "natural" isomorphism
+    p1 := first \ (sortBy last) toList pairs d1;
+    p2 := first \ (sortBy last) toList pairs d2;
+    setIsomorphism(N, M0, map(N, M0, id_M^p2 // id_N^p1,
+	    Degree => -diffdegs), o))
+
+-- returns a minimization M1, presented as
+-- a cokernel of m, and an isomorphism back to M
+minimization = M -> (
+    R := ring M;
+    k := if isField R then R
+    else quotient ideal vars R;
+    m := presentation M;
+    iso := if m ** k == 0
+    then map(M, M1 := coker m, 1)
+    else (
+	m = presentation(M1 = prune M);
+	M1.cache.pruningMap);
+    (M1, m, iso))
+
+-- TODO: hookify and split into strategies, then add strategies that:
+-- - check if pruning of the modules is cached already
+-- - check if Hilbert polynomial, regularity, dimension, etc. are known
+isIsomorphic = method(
+    TypicalValue => Boolean,
+    Options => {
+	Homogeneous => true,  -- if false, allow an inhomogeneous isomorphism
+	Strict      => false, -- forces strict equality of degrees
+	Verbose     => false,
+	Tries       => null,  -- number of attempts at generating a random map
+    })
+isIsomorphic(Module, Module) := Boolean => o -> (N, M) -> (
+    -- returns a pair (false, null) or (true, f), where f is an isomorphism M --> N.
path: M2/Macaulay2/packages/Isomorphism.m2
user: login: eisenbud
body: does not return pair
created_at: 2025-05-07T21:53:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -191,12 +200,25 @@ isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
 	r := 1 + max(regularity F.module, regularity G.module);
 	truncate(r, F.module, MinimalGenerators => false),
 	truncate(r, G.module, MinimalGenerators => false));
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
-
-isIsomorphic(SheafMap, SheafMap) := Sequence => o -> (psi, phi) -> isIsomorphic(coker phi, coker psi, o)
+    -- FIXME: this is incomplete, because we need to store pruning maps or embedding maps
+    -- in order to compose/precompose with the cached isomorphism on the modules.
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
+
+isomorphism(CoherentSheaf, CoherentSheaf) := SheafMap => o -> (F, G) -> (
+    if F === G then id_F else if isIsomorphic(F, G, o,
+	Strict => true, Homogeneous => true)
+    -- FIXME: this is probably not correct yet, because we may
+    -- need to compose/precompose with the pruning maps of F and G.
+    then --map(F, G,
+	--inverse (prune F).cache.pruningMap *
+	sheaf isomorphism splice(G.cache.Isomorphisms#F,
+	    Strict => true, Homogeneous => true)
+	--* (prune G).cache.pruningMap)
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: mahrud
body: @Devlin-Mallory the easy way I tried to get `isomorphism` to give a map with correct source and target didn't seem to work. I think there's something wrong in pruning map that should be fixed separately.
created_at: 2025-05-08T02:01:32Z
author_association: MEMBER
diff_hunk: @@ -191,12 +200,25 @@ isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
 	r := 1 + max(regularity F.module, regularity G.module);
 	truncate(r, F.module, MinimalGenerators => false),
 	truncate(r, G.module, MinimalGenerators => false));
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
-
-isIsomorphic(SheafMap, SheafMap) := Sequence => o -> (psi, phi) -> isIsomorphic(coker phi, coker psi, o)
+    -- FIXME: this is incomplete, because we need to store pruning maps or embedding maps
+    -- in order to compose/precompose with the cached isomorphism on the modules.
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
+
+isomorphism(CoherentSheaf, CoherentSheaf) := SheafMap => o -> (F, G) -> (
+    if F === G then id_F else if isIsomorphic(F, G, o,
+	Strict => true, Homogeneous => true)
+    -- FIXME: this is probably not correct yet, because we may
+    -- need to compose/precompose with the pruning maps of F and G.
+    then --map(F, G,
+	--inverse (prune F).cache.pruningMap *
+	sheaf isomorphism splice(G.cache.Isomorphisms#F,
+	    Strict => true, Homogeneous => true)
+	--* (prune G).cache.pruningMap)
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: Devlin-Mallory
body: is there a good example where this doesn't give the right answer? (in any case, it seems fine for now)
created_at: 2025-05-08T08:47:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,17 +167,26 @@ ZZ == SheafMap := Boolean => (n, f) -> f == n
 
 isIsomorphism SheafMap := Boolean => f -> ker f == 0 and coker f == 0
 
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
-    M := module prune F;
-    N := module prune G;
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
+-- TODO: isomorphisms of modules are cached under Y.cache.cache.Isomorphisms,
+-- where Y is the youngest of the modules, but currently isomorphisms of sheaves
+-- is simply cached under the source. We should fix this.
+importFrom_Isomorphism "Isomorphisms"
+
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
+    (M, N) := (module prune F, module prune G);
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
 
 -- TODO: perhaps better would be to construct random
 -- maps F --> G and check their kernel and cokernel.
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> F === G or (
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: Devlin-Mallory
body: I do want to reiterate what @kellerlv and I suggested a while back: that the default method should be to prune both sheaves, with the user able to opt out, since the current method will essentially always do a *nontrivial* truncation, even if F and G are both equal to their prunes and then check for isomorphism of the nontrivial truncations, which can be quite slow. (No need to go back and forth on this further, so feel free to disregard.) 
created_at: 2025-05-08T08:52:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,17 +167,26 @@ ZZ == SheafMap := Boolean => (n, f) -> f == n
 
 isIsomorphism SheafMap := Boolean => f -> ker f == 0 and coker f == 0
 
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
-    M := module prune F;
-    N := module prune G;
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
+-- TODO: isomorphisms of modules are cached under Y.cache.cache.Isomorphisms,
+-- where Y is the youngest of the modules, but currently isomorphisms of sheaves
+-- is simply cached under the source. We should fix this.
+importFrom_Isomorphism "Isomorphisms"
+
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
+    (M, N) := (module prune F, module prune G);
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
 
 -- TODO: perhaps better would be to construct random
 -- maps F --> G and check their kernel and cokernel.
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> F === G or (
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: mahrud
body: Yeah, I know. I am warming up to that idea, but just for the record, my objection is that the user can always call `isIsomorphic(prune F, prune G)` to opt into what you suggest. So the question is which should be the default, and I actually think using the current default has been helpful for discovering bugs.

That said, getting the isomorphism to have actually the right source and target may be harder in the truncation strategy, so I may end up commenting it out out of laziness ...
created_at: 2025-05-08T13:58:11Z
author_association: MEMBER
diff_hunk: @@ -167,17 +167,26 @@ ZZ == SheafMap := Boolean => (n, f) -> f == n
 
 isIsomorphism SheafMap := Boolean => f -> ker f == 0 and coker f == 0
 
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
-    M := module prune F;
-    N := module prune G;
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
+-- TODO: isomorphisms of modules are cached under Y.cache.cache.Isomorphisms,
+-- where Y is the youngest of the modules, but currently isomorphisms of sheaves
+-- is simply cached under the source. We should fix this.
+importFrom_Isomorphism "Isomorphisms"
+
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
+    (M, N) := (module prune F, module prune G);
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
 
 -- TODO: perhaps better would be to construct random
 -- maps F --> G and check their kernel and cokernel.
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> F === G or (
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: mahrud
body: I just added an unused Strategy option for isIsomorphic and isomorphism, which we may later use for this purpose.

(I also tempted fate and decided to skip the tests, since the addition was minimal and the documentation built fine ...)
created_at: 2025-05-08T14:07:12Z
author_association: MEMBER
diff_hunk: @@ -167,17 +167,26 @@ ZZ == SheafMap := Boolean => (n, f) -> f == n
 
 isIsomorphism SheafMap := Boolean => f -> ker f == 0 and coker f == 0
 
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
-    M := module prune F;
-    N := module prune G;
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
+-- TODO: isomorphisms of modules are cached under Y.cache.cache.Isomorphisms,
+-- where Y is the youngest of the modules, but currently isomorphisms of sheaves
+-- is simply cached under the source. We should fix this.
+importFrom_Isomorphism "Isomorphisms"
+
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
+    (M, N) := (module prune F, module prune G);
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
 
 -- TODO: perhaps better would be to construct random
 -- maps F --> G and check their kernel and cokernel.
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> F === G or (
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: mahrud
body: Aha! David found an example where comparing pruned sheaves is incorrect but truncation works:
```m2
  R = QQ[x,y,z]
  X = Proj R
  F = sheaf(R^1/(x,y))
  F1 = sheaf HH^0(F(>=-1))
  F2 = sheaf HH^0(F(>=-10))
  isIsomorphic(F1,F2) -- gives true, using truncation
  isIsomorphic(
      module prune F1,
      module prune F2, Strict => true) -- gives false
```
Of course, the solution would be to truncate the pruned sheaves and compare the truncations, which is my point exactly :)
created_at: 2025-05-12T22:15:23Z
author_association: MEMBER
diff_hunk: @@ -191,12 +200,25 @@ isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
 	r := 1 + max(regularity F.module, regularity G.module);
 	truncate(r, F.module, MinimalGenerators => false),
 	truncate(r, G.module, MinimalGenerators => false));
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
-
-isIsomorphic(SheafMap, SheafMap) := Sequence => o -> (psi, phi) -> isIsomorphic(coker phi, coker psi, o)
+    -- FIXME: this is incomplete, because we need to store pruning maps or embedding maps
+    -- in order to compose/precompose with the cached isomorphism on the modules.
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
+
+isomorphism(CoherentSheaf, CoherentSheaf) := SheafMap => o -> (F, G) -> (
+    if F === G then id_F else if isIsomorphic(F, G, o,
+	Strict => true, Homogeneous => true)
+    -- FIXME: this is probably not correct yet, because we may
+    -- need to compose/precompose with the pruning maps of F and G.
+    then --map(F, G,
+	--inverse (prune F).cache.pruningMap *
+	sheaf isomorphism splice(G.cache.Isomorphisms#F,
+	    Strict => true, Homogeneous => true)
+	--* (prune G).cache.pruningMap)
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: mahrud
body: Incidentally, `isomorphism(F1,F2)` from the other thread is an example of this an isomorphism where the source and target are not set correctly.
created_at: 2025-05-12T22:34:05Z
author_association: MEMBER
diff_hunk: @@ -167,17 +167,26 @@ ZZ == SheafMap := Boolean => (n, f) -> f == n
 
 isIsomorphism SheafMap := Boolean => f -> ker f == 0 and coker f == 0
 
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
-    M := module prune F;
-    N := module prune G;
-    -- TODO: isIsomorphic should check === first
-    if M === N then return (true, id_F);
-    (ret, isom) := isIsomorphic(M, N, o, Strict => true);
-    (ret, if ret then sheaf(F.variety, isom)))
+-- TODO: isomorphisms of modules are cached under Y.cache.cache.Isomorphisms,
+-- where Y is the youngest of the modules, but currently isomorphisms of sheaves
+-- is simply cached under the source. We should fix this.
+importFrom_Isomorphism "Isomorphisms"
+
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> (
+    if F === G then return true;
+    if G.cache.?Isomorphisms
+    and G.cache.Isomorphisms#?F then return true;
+    (M, N) := (module prune F, module prune G);
+    if isIsomorphic(M, N, o, Strict => true, Homogeneous => true)
+    then (( G.cache.Isomorphisms ??= new MutableHashTable )#F = (M, N); true)
+    else false)
 
 -- TODO: perhaps better would be to construct random
 -- maps F --> G and check their kernel and cokernel.
-isIsomorphic(CoherentSheaf, CoherentSheaf) := Sequence => o -> (F, G) -> (
+isIsomorphic(CoherentSheaf, CoherentSheaf) := Boolean => o -> (F, G) -> F === G or (
path: M2/Macaulay2/packages/Varieties/SheafMaps.m2
user: login: Devlin-Mallory
body: Ah this is a very nice, simple example! I think there's a simple mathematical point here I overlooked for the pruning method, which is that if F is a sheaf with 0-dimensional support, then F and F(1) are the same sheaf (and this should characterize 0-dimensional sheaves I believe). 

So a simple fix for the pruning method is to do Strict => false if dim ann F <= 1 (perhaps another time we should discuss the convention for dimensions of supports of sheaves!). In this example, the dimension check is very fast compared to the time of either pruning or truncating (though of course this is a very simple example - I'd want to do more checking on how fast dim is, though I suspect that it will be much faster than doing any comparison of nontrivial truncations, or nontrivial prunings).  

For the time being, we should at least add the dimension check to both the disabled and the active method, since right now the truncation method will try to compare the pruned modules if it's fed pruned modules, and I don't think we want to disable that. I can make the changes later today. 
created_at: 2025-05-13T08:32:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: Devlin-Mallory
created_at: 2025-05-01T11:22:19Z
body: Am I misunderstanding what it should do, or is `isomorphism` (and `getIsomorphism`) not working? Running the following:
```
S = ZZ/2[x]
M = S^1
N = S^1
isIsomorphic(M, N) -- true
isomorphism(M,N) -- returns nothing 
```
doesn't give me the isomorphism M->N it found in isIsomorphic. (I think the issue is the same for non-free modules.) 

(Now fixed!)
author_association: CONTRIBUTOR
user: login: Devlin-Mallory
created_at: 2025-05-01T11:44:58Z
body: There may also be a small bug when checking isIsomorphic over rings that are fields:
```
S=(ZZ/2) -- or QQ
isIsomorphic (coker matrix{{0,1_S},{1,0}},coker matrix{{1_S,0},{0,0}}) -- fails with "error: expected ideal of the same ring"
S=(ZZ/2)[x]
isIsomorphic (coker matrix{{0,1_S},{1,0}},coker matrix{{1_S,0},{0,0}}) -- correctly answers false
S=(GF 4)
isIsomorphic (coker matrix{{0,1_S},{1,0}},coker matrix{{1_S,0},{0,0}}) -- fails with "error: expected a polynomial ring or free algebra"
S=(GF 4)[x]
isIsomorphic (coker matrix{{0,1_S},{1,0}},coker matrix{{1_S,0},{0,0}}) -- correctly answers false
S=ZZ
isIsomorphic (coker matrix{{0,1_S},{1,0}},coker matrix{{1_S,0},{0,0}}) -- also correctly answers false
```
author_association: CONTRIBUTOR
user: login: mahrud
created_at: 2025-05-01T11:45:02Z
body: Isomorphism wasn't designed for vector spaces or ZZ-modules, so I'm not surprised. Try checking your examples with the old version.
author_association: MEMBER
user: login: Devlin-Mallory
created_at: 2025-05-01T11:47:53Z
body: > Isomorphism wasn't designed for vector spaces or ZZ-modules, so I'm not surprised. Try checking your examples with the old version.

Issue is the same there! Just wanted to flag it in case it should be changed. The other issue however (of not returning the isomorphism) is still the case when S=(ZZ/2)[x] (I've updated above). 

(Also, just wanted to note that isIsomorphic worked over ZZ, just not over fields, which I find surprising!)
author_association: CONTRIBUTOR
user: login: mahrud
created_at: 2025-05-01T11:59:19Z
body: > (Also, just wanted to note that isIsomorphic worked over ZZ, just not over fields, which I find surprising!)

It didn't:
```m2
i1 :   M = ZZ^1 ++ coker(2 * id_(ZZ^1))

o1 = cokernel | 0 |
              | 2 |

                              2
o1 : ZZ-module, quotient of ZZ

i2 :   N = coker(2 * id_(ZZ^1)) ++ ZZ^1

o2 = cokernel | 2 |
              | 0 |

                              2
o2 : ZZ-module, quotient of ZZ

i3 :   isIsomorphic(N, M)

o3 = (false, )

o3 : Sequence
```
author_association: MEMBER