-*- coding: utf-8 -*-
newPackage("DGAlgebras",
     Headline => "Data type for DG algebras",
     Version => "1.1.1--with fix of killCycles and new code and docs for displayBlockDiff and blockDiff",
     Date => "August 20, 2020",
     Authors => {
	  {Name => "Frank Moore",
	   HomePage => "http://www.math.wfu.edu/Faculty/Moore.html",
	   Email => "moorewf@wfu.edu"},
       {Name => "Some documentation added by Daniel Rostamloo and David Eisenbud"}},
     Keywords => {"Commutative Algebra"},
     DebuggingMode => false,
     PackageExports => {"IntegralClosure"}
     )
export {"DGAlgebra", 
        "DGAlgebraMap", 
	"dgAlgebraMap", 
	"freeDGAlgebra", 
	"setDiff", 
	"natural", 
	"cycles",
	"getBasis", 
	"koszulComplexDGA", 
	"acyclicClosure", 
	"toComplex", 
	"toComplexMap", 
	"liftToDGMap",
        "killCycles", 
	"getGenerators", 
	"adjoinVariables", 
	"deviations", 
	"deviationsToPoincare", 
	"expandGeomSeries", 
	"zerothHomology",
        "torMap", 
	"homologyAlgebra", 
	"torAlgebra", 
	"maxDegree", 
	"StartDegree", 
	"EndDegree", 
	"ringMap",
	"isHomologyAlgebraTrivial", 
	"findTrivialMasseyOperation", 
	"findNaryTrivialMasseyOperation", 
	"AssertWellDefined",
	"isGolod", 
	"isGolodHomomorphism", 
	"GenDegreeLimit", 
	"RelDegreeLimit", 
	"TMOLimit",
	"InitializeDegreeZeroHomology", 
	"InitializeComplex", 
	"isAcyclic", 
	"getDegNModule",
	"masseyTripleProduct",
	"getBoundaryPreimage",
	 "homologyClass",
	"homologyModule",
	"dgAlgebraMultMap",
	"displayBlockDiff",
        "blockDiff"
}

-- Questions:
-- is there a way to present graded pieces of graded A-modules as modules over A_0?
-- is there a way to make f act like a function for f a DGAlgebraMap?

-- Things to do before version 2
-- [user v1.5] Change toComplex to chainComplex as per conversation with Dan on the M2 Google group (9/14/2010)
-- [functionality v1.5] Single degree homology(DGAlgebraMap,ZZ)
-- [functionality v1.5] Lift a map from a semifree DGA to another DGA along a quism
-- [functionality v1.5] Minimal Models
-- [functionality v1.5] Fix setDiff preparation preferences (symbols, etc)
-- [functionality v2] Present a degree of the homology algebra as a module over H_0(A) *using the monomial basis* of HA as generators
--                    In fact, one should do the following:  Let R be a ring, A a f.g. graded R-algebra with A_0 = R, and M a f.g. graded A-module.
--                                                           Is it possible to compute a presentation of M_i as an R-module (graded, if R is)?
-- [functionality v2] Allow non-polynomial underlying algebras (Major undertaking.  Would have to test extensively.)
--                    [--- In order to do this, the 'basis' command needs to be a bit more robust; things have changed in 1.4 but the package does not use the new basis yet. ---]
-- [functionality v2] Koszul Models and other things with Alex's results regarding Koszul Models (requires non-polynomial underlying algebras)
-- [functionality v2] A check that the algebra, together with the differential, is indeed a DG Algebra (up to a certain degree...)
-- [functionality v2] Update isWellDefined for DGAlgebraMaps between non-polynomial DGAs
-- [functionality v2] taylorResolutionDGA - Needs non-polynomial underlying algebra
-- [functionality v2] ekResolutionDGA - Needs non-polynomial underlying algebra
-- [functionality v2] Gorenstein pdim3 Pfaffians resolution (pg 15 of green book) - needs non-polynomial underlying algebra
-- [functionality v2] isSemiFree
-- [functionality v2] DGIdeals
-- [functionality v2] DGModules
-- [functionality v2] DGModuleMap
-- [functionality v2] torDelta - is this possible?  Would be great.
-- [functionality v2] Golod/Levin/Avramov index? (see paper of Liana)
-- [functionality v2] Matrix Massey products? (see Gulliksen-Levin)
-- [functionality v2] Computing Betti numbers using Massey products? (Ref?)

-- Not sure if the below are possible
-- [functionality v?] torModule - is this possible? 
-- [functionality v?] incorporate divided powers somehow?
-- [functionality v?] Compute obstructions for the existence of a minimal DG Algebra resolution (see pg 31 of the green book) (req. torModule)

-----------------------------------------------------
-- Set DG algebra types and constructor functions. -- 
-----------------------------------------------------

-- protected symbols (I don't want to export these symbols, but I use them internally in the code at various places)
protect diffs
protect basisAlgebra

-- Defining the new type DGAlgebra
DGAlgebra = new Type of MutableHashTable
globalAssignment DGAlgebra
--DGAlgebraMap still in development
DGAlgebraMap = new Type of MutableHashTable
globalAssignment DGAlgebraMap

-- this command is in the core, but we need it here.
spots  = C -> select(keys C, i -> class i === ZZ)

-- Modify the standard output for a DGAlgebra
net DGAlgebra := A -> (
   local diffList;
   myOutput := {net "Ring => " | net A.ring};
   myOutput = myOutput | {net "Underlying algebra => " | net A.natural};
   if A.diff =!= {} then diffList = take(flatten entries matrix (A.diff),numgens A.natural);
   myOutput = myOutput | {net "Differential => " | net diffList};
   -- Take out this part of the output.
   --myOutput = myOutput | {net "isHomogeneous => " | net A.isHomogeneous};
   horizontalJoin flatten ("{", stack myOutput, "}")
)

freeDGAlgebra = method(TypicalValue => DGAlgebra, Options => {Variable => "T"})
freeDGAlgebra (Ring,List) := opts -> (R,degList) -> (
   -- Input:  A ring, a list of degrees of the variables, and a list that defines the differential
   -- Output:  A hash table of type DGAlgebra
   A := new MutableHashTable;
   T := getSymbol(opts.Variable);
   A#(symbol ring) = R;
   varsList := toList (T_1..T_(#degList));
   A#(symbol diff) = {};
   if isHomogeneous R then (
      -- make sure the degree list has the right form.
      if #(first degList) != #(first degrees A.ring) + 1 then degList = apply(degList, i -> i | {0});
      A#(symbol natural) = (A.ring)[varsList, Degrees => degList, Join => false, SkewCommutative => select(toList(0..(#degList-1)), i -> odd first degList#i)];
   )
   else (
      A#(symbol natural) = (A.ring)[varsList, Degrees => degList, SkewCommutative => select(toList(0..(#degList-1)), i -> odd first degList#i)];
   );
   A#(symbol isHomogeneous) = false;
   A.natural.cache = new CacheTable;
   A.natural.cache#(symbol basisAlgebra) = (A.ring)[varsList, Join => false, MonomialOrder => GRevLex, Degrees => apply(degList, i -> {first i}), SkewCommutative => select(toList(0..(#degList-1)), i -> odd first degList#i)];
   use A.natural;
   A#(symbol Degrees) = degList;
   A#(symbol cache) = new CacheTable;
   A.cache#(symbol homology) = new MutableHashTable;
   A.cache#(symbol homologyAlgebra) = new MutableHashTable;
   A.cache#(symbol diffs) = new MutableHashTable;
   -- *should* verify that the differential is indeed of degree -1
   new DGAlgebra from A
)     

maxDegree = method(TypicalValue => ZZ)
maxDegree DGAlgebra := A -> (
   degreesList := degrees A.natural / first;
   if any(degreesList, i -> even i) then infinity else sum degreesList
)

totalOddDegree := A -> sum select(degrees A.natural / first, i -> odd i)

setDiff = method(TypicalValue => DGAlgebra, Options => {InitializeDegreeZeroHomology => true, InitializeComplex => true})
setDiff (DGAlgebra,List) := opts -> (A,diffList) -> (
   A.diff = map(A.natural,A.natural, substitute(matrix {diffList}, A.natural));
   A.isHomogeneous = isHomogeneous A.ring and checkIsHomogeneous(A);
   if opts.InitializeDegreeZeroHomology then (
      definingIdeal := ideal mingens (ideal A.ring + sub(ideal polyDifferential(1,A), ambient A.ring));
      if definingIdeal == ideal vars ambient A.ring then A#(symbol zerothHomology) = coefficientRing A.ring else A#(symbol zerothHomology) = (ambient A.ring)/definingIdeal;
   );
   if opts.InitializeComplex then A.dd = (toComplex(A,totalOddDegree(A)+1)).dd;
   A
)

setKoszulDiff = method(TypicalValue => DGAlgebra, Options => {InitializeDegreeZeroHomology => true, InitializeComplex => true})
setKoszulDiff (DGAlgebra,List) := opts -> (A,diffList) -> (
   A.diff = map(A.natural,A.natural, substitute(matrix {diffList}, A.natural));
   A.isHomogeneous = isHomogeneous A.ring and checkIsHomogeneous(A);
   if opts.InitializeDegreeZeroHomology then (
      definingIdeal := ideal mingens (ideal A.ring + sub(ideal polyDifferential(1,A), ambient A.ring));
      if definingIdeal == ideal vars ambient A.ring then A#(symbol zerothHomology) = coefficientRing A.ring else A#(symbol zerothHomology) = (ambient A.ring)/definingIdeal;
   );
   if opts.InitializeComplex then A.dd = (koszul(matrix{diffList})).dd;
   A
)

checkIsHomogeneous = method()
checkIsHomogeneous DGAlgebra := A -> (
   gensList := gens A.natural;
   diffList := apply(gensList, f -> A.diff(f));
   homDegreeShift := {1} | (toList ((#(degree first gensList)-1):0));
   all(#diffList, i -> degree gensList#i - homDegreeShift == degree diffList#i)
)

-- cache the basis of a DGAlgebra?
getBasis = method(TypicalValue => Matrix, Options => {Limit => -1})
getBasis (ZZ,DGAlgebra) := opts -> (homDegree,A) -> getBasis(homDegree,A.natural, Limit => opts.Limit)

getBasis (ZZ,Ring) := opts -> (homDegree,R) -> (
   local retVal;
   myMap := map(R, R.cache.basisAlgebra);
   tempList := (flatten entries basis(homDegree, R.cache.basisAlgebra, Limit => opts.Limit)) / myMap;
   if tempList == {} then retVal = map((R)^1,(R)^0, 0) else
   (
      -- move this to an assert?
      tempList = reverse sort tempList;
      degList := apply(tempList, m -> -degree m);
      retVal = map(R^1, R^degList, matrix {tempList});
   );
   retVal
)

isHomogeneous DGAlgebra := A -> A.isHomogeneous

isAcyclic = method(TypicalValue => Boolean, Options => {EndDegree => -1})
isAcyclic DGAlgebra := opts -> A -> (
  endDegree := maxDegree A;
  if endDegree == infinity and opts.EndDegree == -1 then
    error "Must supply an upper bound to check for acyclicity.";
  if opts.EndDegree != -1 then endDegree = opts.EndDegree;
  not any(1..endDegree,i -> prune HH_i(A) != 0)
)

koszulComplexDGA = method(TypicalValue => DGAlgebra, Options => {Variable => "T"})
koszulComplexDGA Ring := opts -> R -> (
   local A;
   local initComplex;
   --initComplex = (numgens R < 8);
   initComplex = true;
   if isHomogeneous R then (
      degList := apply(degrees R, i -> {1} | i);
      A = freeDGAlgebra(R, degList, opts);
      use A.ring;
      setKoszulDiff(A, gens R, InitializeComplex=>initComplex);
   )
   else (
      A = freeDGAlgebra(R, toList ((numgens R):{1}),opts);
      use A.ring;
      setKoszulDiff(A, gens R, InitializeComplex=>initComplex);
   );
   A
)

koszulComplexDGA Ideal := opts -> I -> (
   local A;
   if isHomogeneous I then (
      degList := apply(flatten entries gens I, i -> {1} | degree i); 
      A = freeDGAlgebra(ring I, degList, opts);
      use A.ring;
      setDiff(A,I_*);
   )
   else
   (
      A = freeDGAlgebra(ring I, toList ((numgens I):{1}), opts);
      use A.ring;
      setDiff(A, I_*);
   );
   A
)

koszulComplexDGA List := opts -> ringElts -> koszulComplexDGA(ideal ringElts, opts);

toComplex = method(TypicalValue=>ChainComplex)
toComplex DGAlgebra := A -> (
   maxDeg := maxDegree A;
   if maxDeg == infinity then error "Must specify an upper degree bound if an even generator exists.";
   toComplex(A,maxDeg)
)

toComplex (DGAlgebra,ZZ) := (A,n) -> chainComplex(apply(n, i -> polyDifferential(i+1,A)))

killCycles = method(TypicalValue=>DGAlgebra,Options => {StartDegree => 1, EndDegree => -1})
killCycles DGAlgebra := opts -> A -> (
   -- for now, this will only work for DG algebras with H_0(A) = k
   retVal := 0;
   endDegree := if opts.EndDegree == -1 then opts.StartDegree else opts.EndDegree;
   if opts.StartDegree > endDegree then error "Starting degree is not less than or equal to ending degree.";
   n := opts.StartDegree;
   foundHomology := false;
   if opts.StartDegree > endDegree then error "Starting degree is not less than or equal to ending degree.";
   n = opts.StartDegree;
   foundHomology = false;
   nthHomology := 0;
   while n <= endDegree and not foundHomology do (
      nthHomology = prune homology(n,A);
      if nthHomology == 0 then n = n + 1 else foundHomology = true
   );
   -- at this point we have found a degree with nontrivial homology.
   -- we now add variables in one degree higher to make these cycles boundaries.
   if not foundHomology then retVal = A else (  
      homologyGenerators := entries transpose gens image (nthHomology.cache.pruningMap);
      basisList := flatten entries getBasis(n,A);
      cycleList := apply(homologyGenerators, gen -> sum apply(#gen, i -> gen#i*basisList#i));
      retVal = adjoinVariables(A,cycleList);
   );
   retVal
)

adjoinVariables = method(TypicalValue=>DGAlgebra)
adjoinVariables (DGAlgebra, List) := (A,cycleList) -> (
   -- this function will add a new variable to make the elements of cycles boundaries in a new DG algebra (semifree over the input)
   local newDegreesList;
   tempDegree := {1} | toList ((#(degree first cycleList)-1):0);
   if A.isHomogeneous then
      newDegreesList = A.Degrees | apply(cycleList, z -> degree z + tempDegree)
   else
      newDegreesList = A.Degrees | apply(cycleList, z -> {first degree z + 1});
   B := freeDGAlgebra(A.ring,newDegreesList);
   phi := map(B.natural,A.natural,matrix {take(gens B.natural, numgens A.natural)});
   newDiffList := (take(flatten entries matrix A.diff, numgens A.natural) | cycleList) / phi;
   setDiff(B,newDiffList,InitializeComplex=>false);
   B
)

acyclicClosure = method(TypicalValue=>DGAlgebra,Options => {StartDegree => 1, EndDegree => 3})
acyclicClosure DGAlgebra := opts -> A -> (
  n := opts.StartDegree;
  endDegree := 3;
  if opts.EndDegree != 3 then endDegree = opts.EndDegree;
  while n <= endDegree do (
     A = killCycles(A,StartDegree => n);
     n = n + 1;
  );
  A
)

acyclicClosure Ring := opts -> R -> (
   K := koszulComplexDGA(R);
   acyclicClosure(K, opts)
)

polyDiffMonomial := (A,m) -> (
  -- uses the Leibniz rule to compute the differential of a traditional monomial
  dgSign := 1;
  justMon := first flatten entries first coefficients m;
  justCoeff := substitute(first flatten entries last coefficients m, ring m);
  monSupport := support justMon;
  monExponents := select(first exponents justMon, i -> i > 0);
  monSupportPowers := apply(#monSupport, i -> (monSupport#i)^(monExponents#i));
  allTerms := apply(#monSupport, i -> (product apply(i, j -> (-1)^((first degree monSupport#j)*(monExponents#j))))*
                                      (product take(monSupportPowers,i))*
  -- below are both versions of differential, for divided powers and without, should we want to use divided powers later.
  --                                  (A.diff(monSupport#i)*(monSupport#i)^((monExponents#i)-1))*  -- divided powers
                                      (A.diff(monSupport#i)*(monExponents#i)*(monSupport#i)^((monExponents#i)-1))* -- polynomial subalgebra
                                      (product drop(monSupportPowers,i+1)));
  justCoeff * (sum allTerms)
)

polyDifferential = method(TypicalValue=>Matrix)
polyDifferential (ZZ,DGAlgebra) := (n,A) -> (
  local newDiffl;
  if A.cache.diffs#?n then A.cache.diffs#n
  else if n == 0 then map((A.ring)^0,(A.ring)^1,0)
  else (
     -- here, check to see if the ring A is graded with graded differential.  If so, then produce
     -- a graded map.  Otherwise, just treat things as ungraded (should be slower)
     sourceList := getBasis(n,A);
     sourceDegreeList := apply(degrees source sourceList, l -> -drop(l,1));
     sourceList = flatten entries sourceList;
     targetList := getBasis(n-1,A);
     targetDegreeList := apply(degrees source targetList, l -> -drop(l,1));
     targetList = flatten entries targetList;
     mDegree := maxDegree A;
     if (n == mDegree + 1) then newDiffl = map((A.ring)^(targetDegreeList), (A.ring)^0, 0)
     else if n > mDegree + 1 then newDiffl = map((A.ring)^0,(A.ring)^0,0) else (
        diffList := matrix {apply(sourceList, m -> polyDiffMonomial(A,m))};
        coeffMatrix := substitute((coefficients(diffList, Monomials => targetList))#1, A.ring);
        newDiffl = map((A.ring)^(targetDegreeList), (A.ring)^(sourceDegreeList), coeffMatrix);
        A.cache.diffs#n = newDiffl;
     );
     newDiffl
  )
)

polyDifferential (DGAlgebra,RingElement) := (A,f) -> sum apply(terms f, m -> polyDiffMonomial(A,m))

diff (DGAlgebra,RingElement) := (A,f) -> polyDifferential(A,f);

homology (ZZ,DGAlgebra) := opts -> (n,A) -> (
  dn := 0;
  dnplus1 := 0;
  retVal := 0;
  if A.cache.homology#?n then retVal = A.cache.homology#n
  else if #(flatten entries getBasis(n, A, Limit => 1)) != 0 then (
     if n == 0 then dn = map((A.ring)^0, (A.ring)^1, 0) else dn = polyDifferential(n,A);
     if #(flatten entries getBasis(n+1, A, Limit => 1)) != 0 then
        dnplus1 = polyDifferential(n+1,A)
     else
        dnplus1 = map(source dn, (A.ring)^0, 0);
     retVal = homology(dn,dnplus1);
     A.cache.homology#n = retVal;
  )
  else
     retVal = (A.ring)^0;
  retVal
)

homology DGAlgebra := opts -> A -> homologyAlgebra(A)

zerothHomology = method(TypicalValue => Ring)
zerothHomology (DGAlgebra) := A -> A.zerothHomology

getDegNModule = method(TypicalValue => Module)
getDegNModule (ZZ,Ring,Ring) := (n,R,A) -> (
   -- first, set up the 'alternate' ring that forgets the grading (if any) of R from A
   local myRowDegree;
   --A := ring M;
   varsList := gens A;
   degList := apply(degrees A, i -> {first i});
   skewList := {};
   if A#?SkewCommutative then skewList = A.SkewCommutative;
   if not A#?cache then A#cache = new CacheTable;
   if not A.cache#?basisAlgebra then (
      tempRing := R[varsList, Join => false, Degrees => degList, SkewCommutative => skewList];
      A.cache#(symbol basisAlgebra) = tempRing/substitute(ideal A,tempRing);
   );
   myBasis := getBasis(n,A);
   tempMatrix := syz myBasis;
   colDegList := degrees source tempMatrix;
   rowDegList := degrees target tempMatrix;
   myCols := select(toList (0..rank source tempMatrix - 1), i -> first colDegList#i == n);
   myColDegree := apply(colDegList_myCols, h -> -drop(h,1));
   tempMatrix = tempMatrix_myCols;
   myRowDegree = apply(rowDegList, h -> -drop(h,1));
   tempMatrix = map(R^myRowDegree, R^myColDegree,substitute(tempMatrix,R));
   prune coker tempMatrix
)

deviations = method(TypicalValue=>Tally,Options=>{DegreeLimit=>3})
deviations Ring := opts -> R -> (
  --tally degrees torAlgebra(R,GenDegreeLimit=>opts.DegreeLimit)
  kRes := res(coker vars R, LengthLimit => opts.DegreeLimit);
  deviations kRes
)

deviations ChainComplex := opts -> C -> (
  R := ring C;
  pSeries := poincareN C;
  degreesR := toList ((numgens degreesRing R):0);
  if isHomogeneous R then degreesR = degrees R;
  deviations(pSeries,degreesR)
)

deviations (RingElement,List) := opts -> (pSeries,degreesR) -> (
  isHomogeneousR := any(degreesR, deg -> deg =!= 0);
  A := ring pSeries;
  homVar := first gens A;
  homDegree := degree(homVar,pSeries);
  internalVars := drop(gens A,1);
  if not isHomogeneousR then pSeries = substitute(pSeries, (first internalVars)=>1);
  subB := ZZ[internalVars];
  tempB := subB[homVar];
  B := tempB/ideal{(substitute(homVar,tempB))^(homDegree+1)};
  pSeries = substitute(pSeries,B);
  n := 1;
  tempSeries := 1_B;
  tempDiff := pSeries - tempSeries;
  returnHash := hashTable {};
  allDeviations := {};
  while (n <= homDegree and tempDiff != 0) do (
     tempCoeffs := coefficients someTerms(tempDiff,-1,1);
     tempCoeffs = (first flatten entries first tempCoeffs, substitute(first flatten entries last tempCoeffs,subB));
     -- here, tally up the deviations.
     tempDegrees := (terms last tempCoeffs) / exponents / flatten;
     leadCoeffs := (terms last tempCoeffs) / leadCoefficient;
     if isHomogeneousR then allDeviations = allDeviations | apply(#tempDegrees, i -> ((n,tempDegrees#i),leadCoeffs#i))
                       else allDeviations = allDeviations | apply(#tempDegrees, i -> (n,leadCoeffs#i));
     newSeries := 1_B;
     if even n then newSeries = product apply(terms last tempCoeffs, v -> (expandGeomSeries((leadMonomial v)*(first tempCoeffs), homDegree))^(substitute(leadCoefficient v,ZZ)))
               else newSeries = product apply(terms last tempCoeffs, v -> (1+(leadMonomial v)*(first tempCoeffs))^(substitute(leadCoefficient v,ZZ)));
     tempSeries = tempSeries*newSeries;
     n = n + 1;
     tempDiff = pSeries - tempSeries;
  );
  hashTable allDeviations
)

expandGeomSeries = method()
expandGeomSeries (RingElement,ZZ) := (f,n) -> expandGeomSeries({f},n)

expandGeomSeries (List,ZZ) := (fList,n) -> (
   A := ring first fList;
   homVar := first gens A;
   B := A/ideal (homVar^(n+1));
   phi := map(B,A);
   substitute(product apply(fList, f -> sum apply(n,i -> phi(f)^i)),A)
)

deviationsToPoincare = method(Options=>{DegreeLimit=>0})
deviationsToPoincare HashTable := opts -> devHash -> (
   S := getSymbol("S");
   T := getSymbol("T");
   pairsDevHash := pairs devHash;
   if instance(first first pairsDevHash, Sequence) then pairsDevHash = apply(pairsDevHash, p -> (p#0#0,p#0#1,p#1));
   numTVars := #(first pairsDevHash) - 3;
   tVars := toList (T_0..T_numTVars);
   n := opts.DegreeLimit;
   n = max(n,max apply(pairsDevHash, p -> first p));
   A := ZZ[S,tVars];
   B := A/ideal (first gens A)^(n+1);
   tVars = drop(gens B,1);
   powerFunc := p -> if numTVars == -1 then 1_B else product apply(#(p#1), i -> (tVars#i)^(p#1#i));
   product apply(pairsDevHash, p -> if even first p then (expandGeomSeries((first gens B)^(first p)*(powerFunc p),n))^(last p)
                                                    else (1+((first gens B)^(first p)*(powerFunc p)))^(last p))
)

torAlgebra = method(TypicalValue=>Ring, Options=>{GenDegreeLimit=>infinity,RelDegreeLimit=>infinity})
torAlgebra Ring := opts -> R -> (
  n := 3;
  if opts.GenDegreeLimit != infinity then n = opts.GenDegreeLimit;
  baseRing := coefficientRing R;
  kRes := res(coker vars R, LengthLimit => n);
  X := getSymbol("X");
  -- now build the degreeList and skewList out of the output from deviations
  RDevs := deviations(R,DegreeLimit=>opts.GenDegreeLimit);
  degreesList := sort flatten apply(pairs RDevs, p -> toList ((p#1):(flatten{p#0#0,p#0#1})));
  skewList := select(#degreesList, i -> odd first degreesList#i);
  torVars := toList(X_1..X_(#degreesList));
  baseRing[torVars,Degrees=>degreesList, SkewCommutative=>skewList]
)

torAlgebra (Ring,Ring) := opts -> (R,S) -> homologyAlgebra(acyclicClosure(R,EndDegree=>opts.GenDegreeLimit) ** S, opts)

representativeCycles = (n,A) -> (
  temp := transpose generators image ((prune homology(n,A)).cache.pruningMap);
  homologyGenerators := entries temp;
  basisList := flatten entries getBasis(n,A);
  cycleList := apply(homologyGenerators, gen -> sum apply(#gen, i -> gen#i*basisList#i));
  cycleList
)

makeHomologyRing = method()
makeHomologyRing (DGAlgebra,List,List,Boolean) := (A, cycleList, relList, ForceGB) -> (
  local HA;
  local degreesList;
  baseRing := A.zerothHomology;
  if isHomogeneous A then degreesList = (cycleList / degree) else degreesList = pack((cycleList / degree) / first, 1);
  X := getSymbol("X");
  varList := toList (X_1..X_(#cycleList));
  polyRing := baseRing[varList, Join => false, Degrees => degreesList, SkewCommutative => select(toList(0..(#degreesList-1)), i -> odd first degreesList#i)];
  polyRing' := baseRing[varList, Join => false, Degrees => (degreesList / first), SkewCommutative => select(toList(0..(#degreesList-1)), i -> odd first degreesList#i)];
  if relList == {} then (
     HA = polyRing;
     HA.cache = new CacheTable;
     HA.cache#(symbol basisAlgebra) = polyRing';
  )
  else (
     I := ideal relList;
     myMap := map(polyRing, ring I, gens polyRing);
     I = myMap(I);
     -- this command causes hard crashes on occasion...
     --if ForceGB then forceGB gens I else if A.isHomogeneous then I = ideal gens gb(I,Algorithm=>Homogeneous2) else I = ideal gens gb I;
     if ForceGB then forceGB gens I else if A.isHomogeneous then I = ideal gens gb I else I = ideal gens gb I;
     HA = polyRing/I;
     --- set up the cached algebra for basis computations too
     myMap' := map(polyRing', polyRing, gens polyRing');
     I' := myMap'(I);
     if myMap'(leadTerm(gens I)) - leadTerm gens I' != 0 then error "Monomial order error.";
     forceGB gens I';
     HA.cache = new CacheTable;
     HA.cache#(symbol basisAlgebra) = polyRing'/I';
  );
  use A.natural;
  use A.ring;
  HA
)

-- This code finds the relations that exist in the homology algebra that come from simply the relations that exist
-- in the ring, not including the ones that come because one must include the boundaries in determining the relations
--findEasyRelations = method(Options => {Hilbert => null})
--findEasyRelations(DGAlgebra,List) := opts -> (A, cycleList) -> (
findEasyRelations = method()
findEasyRelations (DGAlgebra,List) := (A, cycleList) -> (
  -- need to document this code!
  -- this function should only be called (at this point) if H_0(A) is the residue field.  Not sure how to use this trick to quickly compute the easy relations
  -- unless this is the case.
  X := getSymbol("X");
  baseRing := coefficientRing A.ring;
  varsList := apply(gens A.ring | gens A.natural, f -> sub(f,A.natural)) | toList (X_1..X_(#cycleList));
  numAvars := numgens A.ring + numgens A.natural;
  naturalGens := gens A.natural;
  skewList := apply(select(toList(0..#naturalGens-1), i -> odd first degree naturalGens#i), i -> i + numgens A.ring);
  skewList = skewList | apply(select(toList(0..#cycleList-1), i -> odd first degree cycleList#i), i -> i + numgens A.natural + numgens A.ring);
  degList := apply(numgens A.natural + numgens A.ring, i -> degree varsList#i);
  degList = degList | apply(cycleList, i -> degree i);
  if (not isHomogeneous A) then degList = pack(degList / first, 1);
  monB := monoid [varsList,MonomialOrder=>{numgens A.natural + numgens A.ring,#cycleList},Degrees=>degList, SkewCommutative=>skewList];
  B := baseRing monB;
  K := substitute(ideal A.natural, B) + substitute(ideal A.ring, B) + ideal apply(#cycleList, i -> B_(numAvars+i) - substitute(cycleList#i,B));
  if A.isHomogeneous then assert(isHomogeneous K);
  easyRels := 0;
  --if isHomogeneous K then easyRels = ideal selectInSubring(1,gens gb(K,Algorithm=>Homogeneous2)) else easyRels = ideal selectInSubring(1,gens gb K);
  if isHomogeneous K then easyRels = ideal selectInSubring(1,gens gb K) else easyRels = ideal selectInSubring(1,gens gb K);
  degList = apply(cycleList, i -> degree i);
  skewList = select(toList(0..#degList-1), i -> odd first degList#i);
  C := baseRing[X_1..X_(#cycleList), Degrees => degList, SkewCommutative=>skewList];
  makeHomologyRing(A,cycleList,(sub(easyRels,C))_*,true)
)

getCycleProductMatrix = method()
getCycleProductMatrix (DGAlgebra,Ring,List,ZZ) := (A,HA,cycleList,N) -> (
  -- the input is the dga A, the homology algebra HA (so far), the list of cycle generators, and the degree.
  -- the output is a matrix over A.ring that expresses the monomials in HA as elements of A in the standard basis
  -- this version does use the knowledge of the homologyAlgebra so far to return the cycles products in a given degree.
  local retVal;
  local myMap;
  monListHA := flatten entries getBasis(N,HA);
  monListA := flatten entries getBasis(N,A);
  myMap = map(A.natural,HA,cycleList);
  -- this is needed when there is nothing in HA in this degree so far.
  if monListHA != {} then (
     (junk,cycleProductMatrix) := coefficients(myMap(matrix{monListHA}),Monomials=>monListA);
     -- make sure the degrees are correct if the ring is homogeneous
     if isHomogeneous A then (
        sourceDegreeList := apply((monListHA / degree), l -> -drop(l,1));
        targetDegreeList := apply((monListA / degree), l -> -drop(l,1));
        cycleProductMatrix = map((A.ring)^targetDegreeList, (A.ring)^sourceDegreeList, sub(cycleProductMatrix, A.ring));
     )
     else (
        myMap = map(A.ring, A.natural);
        cycleProductMatrix = map((A.ring)^(#monListA), (A.ring)^(#monListHA), sub(cycleProductMatrix, A.ring));
     );
     retVal = (cycleProductMatrix,monListHA);
  )
  else retVal = (map((A.ring)^(#monListA), (A.ring)^0,0), {});
  retVal
)

getCycleProductMatrix (DGAlgebra,List,ZZ) := (A,cycleList,N) -> getCycleProductMatrix(A,makeHomologyRing(A,cycleList,{},true),cycleList,N)

-- The goal of this function is to return the generators in degree n.
findDegNGenerators := (A,oldCycleList,N) -> (
  cycleList := {};
  relsList := {};
  varList := {};
  if oldCycleList == {} then (
     -- here, we know all the degree 1 elements are generators
     cycleList = representativeCycles(N,A);
  )
  else if flatten entries getBasis(N,A, Limit => 1) == {} then cycleList = {}
  else (
     nthHomology := homology(N,A);
     if prune nthHomology != 0 then (
	(cycleProductMatrix,monListHA) := getCycleProductMatrix(A,oldCycleList,N);
	if monListHA != {} then (
	   -- TODO: Document the below block of code.
	   newHomology := prune (nthHomology / (image cycleProductMatrix));
	   monListA := flatten entries getBasis(N,A);
           cycleList = apply(entries transpose gens image newHomology.cache.pruningMap, zList -> apply(#zList, i -> zList#i*monListA#i)) / sum;
        )
        else (
	   -- if we are here, then we need to add all of this degree as generators.
	   cycleList = representativeCycles(N,A);
	);
     ); 
  );
  cycleList
)

findDegNRelations := (A,HA,algGens,N) -> (
  -- this function tries to find the relations in degree N that involve the generators in the list algGens
  -- no checking is done to see if algGens are actually minimal generators at this point.
  local cycleProductMatrix;
  local monListHA;
  retVal := {0_HA};
  -- check if DGA is zero in this degree. If so, just return back the monomials in the given degree
  if flatten entries getBasis(N, A, Limit => 1) != {} or #algGens == 0 then (
     -- using HA, check if there are indeed any new relations in degree n
     nthHomology := homology(N,A);
     pruneNthHomology := prune nthHomology;
     rankOfNthHomology := numgens pruneNthHomology;
     rankOfAlgebraSoFar := #(flatten entries getBasis(N,HA));
     if rankOfNthHomology != rankOfAlgebraSoFar or not isField A.zerothHomology then (
       -- when in here, we know there is a relation in degree N.
       -- so take each monomial of the correct degree, build the cycle corresponding to that
       -- and define a map from the residue field to the homology class representing each cycle.
       -- then take the kernel, prune, and use cache.pruningMap to get the actual minimal generating
       -- set of the kernel.  Finally, reconstruct the elements from the monomials and viola!
       if pruneNthHomology == 0 then (
          -- if we are here, all monomials in the HA of this degree are zero.
	  retVal = flatten entries getBasis(N,HA);
       )
       else (
          (cycleProductMatrix,monListHA) = getCycleProductMatrix(A,HA,algGens,N);
          if monListHA != {} then (
             -- TODO: Document this code.
	     multMap := map(coker relations nthHomology,(A.ring)^(rank source cycleProductMatrix),cycleProductMatrix);
             kerMultMap := gens ker multMap;
	     kerMultMap = compress sub(kerMultMap,A.zerothHomology);
	     kernelGens := entries transpose kerMultMap;
	     retVal = apply(kernelGens, z -> sum apply(#z, i -> (monListHA#i)*(z#i)));
          );
       );
     );
  )
  else (
     retVal = flatten entries getBasis(N,HA); 
  );
  -- return the nonzero relations
  select(retVal, i -> i != 0)
)

getGenerators = method(TypicalValue=>List, Options => {StartDegree => 1, DegreeLimit => -1, Verbosity => 0})
getGenerators DGAlgebra := opts -> A -> (
  maxDeg := maxDegree A;
  if opts.DegreeLimit != -1 then maxDeg = opts.DegreeLimit;
  if maxDeg == infinity then error "Must specify maximum homological degree of generators.";
  n := opts.StartDegree;
  cycleList := {};
  local newCycleList;
  while n <= maxDeg do (
     if opts.Verbosity >= 2 then (
        << "Computing generators in degree " << n << " : ";
        time newCycleList = findDegNGenerators(A,cycleList,n);
     ) else newCycleList = findDegNGenerators(A,cycleList,n);
     cycleList = cycleList | newCycleList;
     n = n + 1;
  );
  cycleList
)

getRelations = method(TypicalValue=>Ring, Options => {Verbosity => 0})
getRelations (DGAlgebra,Ring,List,ZZ) := opts -> (A,HA,cycleList,relDegreeLimit) -> (
   relList := (ideal HA)_*;
   n := 1;
   local newRelList;
   while n <= relDegreeLimit do (
      if opts.Verbosity >= 2 then (
         << "Computing relations in degree " << n << "  : ";
         time newRelList = findDegNRelations(A,HA,cycleList,n);
      )
      else newRelList = findDegNRelations(A,HA,cycleList,n);
      if relList == {} then relList = newRelList
      else if newRelList != {} then (
         -- make sure newRelList and relList are in the same ring
         myMap := map(ring first relList, ring first newRelList, flatten entries vars ring first relList);
         relList = relList | (newRelList / myMap);
      );
      -- now reset HA using relList for the next iteration.
      doForceGB := (n != relDegreeLimit);
      HA = makeHomologyRing(A,cycleList,relList,doForceGB);
      n = n + 1;
   );
   -- put the cycles that the variables represent in the cache.
   HA.cache#cycles = cycleList;
   HA
)

homologyAlgebra = method(TypicalValue=>Ring,Options=>{GenDegreeLimit=>infinity,RelDegreeLimit=>infinity,Verbosity=>0})
homologyAlgebra DGAlgebra := opts -> A -> (
  local HA;
  if A.cache.homologyAlgebra#?GenDegreeLimit and A.cache.homologyAlgebra#GenDegreeLimit >= opts.GenDegreeLimit and
     A.cache.homologyAlgebra#?RelDegreeLimit and A.cache.homologyAlgebra#RelDegreeLimit >= opts.RelDegreeLimit then HA = A.cache.homologyAlgebra#homologyAlgebra else (
     maxDeg := maxDegree A;
  
     if maxDeg == infinity and (opts.GenDegreeLimit == infinity or opts.RelDegreeLimit == infinity) then
        return "Must supply upper degree bound on generators and relations if there is a DG algebra generator of even degree.";
     if opts.GenDegreeLimit != infinity then maxDeg = opts.GenDegreeLimit;
  
     n := maxDeg;
     while n <= maxDeg and prune homology(n,A) == 0 do n = n - 1;
     maxHomologyDegree := n + 1;
     if opts.RelDegreeLimit != infinity then maxHomologyDegree = opts.RelDegreeLimit;

     cycleList := getGenerators(A,DegreeLimit=>maxDeg,Verbosity=>opts.Verbosity);

     if cycleList == {} then (
        -- put the cycles that the variables represent in the cache.
        -- return H_0(A) as a ring.
        HA = A.zerothHomology;
        HA.cache = new CacheTable;
        HA.cache#cycles = cycleList;
        A.cache.homologyAlgebra#homologyAlgebra = HA;
	A.cache.homologyAlgebra#GenDegreeLimit = opts.GenDegreeLimit;
	A.cache.homologyAlgebra#RelDegreeLimit = opts.RelDegreeLimit;
     )
     else (
        if (isField A.zerothHomology) then (
           << "Finding easy relations           : ";
           time HA = findEasyRelations(A,cycleList);
        )
        else HA = makeHomologyRing(A,cycleList,{},true);
        HA = getRelations(A,HA,cycleList,maxHomologyDegree,Verbosity=>opts.Verbosity);
        A.cache.homologyAlgebra#homologyAlgebra = HA;
	A.cache.homologyAlgebra#GenDegreeLimit = opts.GenDegreeLimit;
	A.cache.homologyAlgebra#RelDegreeLimit = opts.RelDegreeLimit;
     );
  );
  HA     
)

--------- homologyModule code ----------------------

--- this function takes a DGAlgebra A and a cycle z as input
--- it returns the ChainComplexMap corresponding to left multiplication
--- by z
dgAlgebraMultMap = method()
dgAlgebraMultMap (DGAlgebra,RingElement) := (A,z) -> (
   R := A.ring;
   d := first degree z;
   cxA := toComplex A;
   zChainMap := map(cxA,cxA ** R^(-(drop(degree z,1))),
          i -> sub(last coefficients(z*getBasis(i,A),Monomials=>getBasis(i+d,A)), R),
	  Degree=>d);
   -- uncomment the next line if you would like this function
   -- to check that the result is indeed a chain map.
   --assert(zChainMap*KR.dd - (-1)^d*KR.dd*zChainMap);
   zChainMap
)

-- internal use function
moduleRelationsFromCycleAction = method()
moduleRelationsFromCycleAction (DGAlgebra,RingElement,Module) := (A,z,M) -> (
   R := ring M;
   if A.ring =!= R then error "Expected a DGAlgebra and module over same ring.";
   HA := HH(A);
   h := homologyClass(A,z);
   -- may add ability to provide maxdeg as an optional argument for toComplex later
   cxA := toComplex A;
   
   -- since z is a cycle, left multiplication by z is a chain map on A
   zChainMap := dgAlgebraMultMap(A,z);
   -- tensoring with M gives the action of z on A ** M
   zCMtensM := zChainMap ** M;
   -- determine the action of the homology class of z at the level of homology
   HM := HH(target zCMtensM);  
   -- prune the homology to ensure we have minimal generators
   pruneHM := prune HM;
   pruneZActionHM := prune HH(zCMtensM);
   -- ll for Loewy length
   ll := #(spots pruneHM);

   -- at this point we have all the multiplication tables, but we need to put them
   -- together to get a module structure.  Since HM is finite dimensional, this is not
   -- too bad.  The next part of the code combines all the actions into a large matrix
   -- representing the full multiplication table by z and uses this to construct a list of relations
   -- that represent the action of z, of the form ze_j = (z acting on e_j written in terms of basis).

   -- this next block of code constructs the full multiplication table
   -- for z on HM.  It constructs it as a block matrix, where the (i,j)th block
   -- is the left multiplication map M_i --> M_j.  Of course most of these are zero
   -- since z should be a cycle in a single homological degree.

   -- these commands ensure that the resulting matrix will have the right degrees
   -- over HA so that the result will be a graded module.
   degsHMTarget := flatten apply(spots pruneHM, p -> apply(degrees pruneHM#p, d -> {p} | d));
   degsHMSource := flatten apply(spots pruneHM, p -> apply(degrees pruneHM#p, d -> ({p} | d) + degree z));
   -- this function builds the blocks as mentioned above
   buildBlocks := (i,j) -> (
       if j + first degree z != i then map(pruneHM#i,pruneHM#j,0)
       else if not pruneZActionHM#?j then map(pruneHM#i,pruneHM#j,0)
       else pruneZActionHM#j
       );
   -- use map to build the matrix (using the matrix constructor for block matrices)
   matActOfZ := map(HA^(-degsHMTarget),HA^(-degsHMSource),tensor(map(HA,R),matrix table(ll,ll,buildBlocks)));
   -- now subtract from this z times an appropriately graded identity to indicate
   -- that this matrix is the result of z acting on HM.
   relsFromZAction := map(HA^(-degsHMTarget),HA^(-degsHMSource),h) - matActOfZ;
   relsFromZAction
)

homologyModule = method()
homologyModule (DGAlgebra,Module) := (A,M) -> (
   -- A is a DG module over a ring R, and M is an R-module.
   -- the return value is a minimal presentation of H(A ** M) as
   -- an H(A)-module.
   HA := HH(A);
   -- first compute all the 'relations' from a cycle acting on HH(A ** M)
   allActions := apply(HA.cache.cycles, z -> moduleRelationsFromCycleAction(A,z,M));
   -- place these in a block row vector and compute a minimal presentation
   -- of the result
   HMoverHA := minimalPresentation coker matrix {allActions};
   HMoverHA
)

isHomologyAlgebraTrivial = method(TypicalValue=>Boolean,Options=>{GenDegreeLimit=>infinity})
isHomologyAlgebraTrivial DGAlgebra := opts -> A -> first findTrivialMasseyOperation(A,opts,TMOLimit=>2)

isGolod = method(TypicalValue=>Boolean)
isGolod Ring := R -> first findTrivialMasseyOperation(koszulComplexDGA(R))

isGolodHomomorphism = method(TypicalValue=>Boolean,Options=>{GenDegreeLimit=>infinity,TMOLimit=>infinity})
isGolodHomomorphism QuotientRing := opts -> R -> first findTrivialMasseyOperation(acyclicClosure(ambient R, EndDegree=>opts.GenDegreeLimit) ** R, opts)

DGAlgebra ** Ring := (A,S) -> (
  B := freeDGAlgebra(S, A.Degrees);
  newDiff := apply(flatten entries matrix (A.diff), f -> substitute(f,B.natural));
  setDiff(B,newDiff);
  B
)

DGAlgebra ** DGAlgebra := (A,B) -> (
  if A.ring =!= B.ring then error "DGAlgebras must be defined over the same ring.";
  -- should I use a block ordering here since it is a tensor product?
  C := freeDGAlgebra(A.ring, A.Degrees | B.Degrees);
  newDiff := apply(take(flatten entries matrix (A.diff),numgens A.natural), f -> substitute(f,C.natural));
  newDiff = newDiff | apply(flatten entries matrix (B.diff), f -> substitute(f,C.natural));
  setDiff(C,newDiff);
  C
)

getBoundaryPreimage = method()
getBoundaryPreimage (DGAlgebra,List) := (A,boundaryList) -> (
   nonzeroes := select(boundaryList, b -> b != 0);
   if nonzeroes == {} then return (true,boundaryList);
   homDegree := first degree first nonzeroes;
   if any(boundaryList, b -> b != 0 and first degree b != homDegree) then
      error "Expected a list of elements of the same homological degree.";
   dnplus1 := polyDifferential(homDegree+1,A);
   Anbasis := flatten entries getBasis(homDegree,A);
   Anplus1basis := getBasis(homDegree+1,A);
   local retVal;
   isBoundary := true;
   if Anbasis == {} then retVal = 0 else (
      boundaryVec := (coefficients(matrix{boundaryList}, Monomials => Anbasis))#1;
      degreeList := apply(degrees target boundaryVec, l -> -drop(l,1));
      boundaryVec = map((A.ring)^degreeList,(A.ring)^(rank source boundaryVec), sub(boundaryVec,A.ring));
      retVal = boundaryVec // dnplus1;
      -- if not all elements of the list are boundaries, then return null - the DGA does not admit a trivial massey operation.
      if dnplus1 * retVal != boundaryVec then (
         -- the below error is just for debugging purposes.
	 -- error "err";

	 -- previously, null was returned if dnplus1 * retVal != boundaryVec.
	 isBoundary = false;
	 retVal = (false, boundaryVec - dnplus1*retVal);
	 -- now, we wish to return a pair.  The first entry is whether the lift is possible
	 -- The second entry the reduction of the input mod the image.
      );
   );
   if isBoundary then
      if retVal == 0 then
         retVal = (true,apply(#boundaryList, i -> 0_(A.natural)))
      else 
         retVal = (true,flatten entries (Anplus1basis * substitute(retVal, A.ring)));
   retVal
)

getBoundaryPreimage (DGAlgebra,RingElement) := (A,b) -> (
   (lifted,myLift) := getBoundaryPreimage(A,{b});
   (lifted,first myLift)
)

findTrivialMasseyOperation = method(TypicalValue=>Sequence, Options=>{GenDegreeLimit=>infinity,TMOLimit=>infinity})
findTrivialMasseyOperation DGAlgebra := opts -> A -> (
   maxDeg := maxDegree A;
   if maxDeg == infinity and opts.GenDegreeLimit == infinity then error "Must specify an upper bound on the generating degree";
   if maxDeg == infinity and opts.TMOLimit == infinity then error "Must specify an upper bound of order of Massey operations.";
   if opts.GenDegreeLimit != infinity then maxDeg = opts.GenDegreeLimit; 
   cycleList := flatten apply(maxDeg, i -> representativeCycles(i+1,A));
   tmoSoFar := hashTable apply(#cycleList, i -> ({i},cycleList#i));
   hasTMO := true;
   n := 2;
   maxMasseys := min(maxDeg, opts.TMOLimit);
   while n <= maxMasseys do (
      (hasTMO,tmoSoFar) = findNaryTrivialMasseyOperation(A,cycleList,tmoSoFar,n);
      if not hasTMO then ( 
         -- if we are here, tmoSoFar instead has the TMOs to degree n-1, as well as the degree n 
	 -- nonvanishing Massey operations (at least I think)
	 -- << "No trivial Massey operation exists for this algebra.  The " << n << "-ary Massey operations" << endl;
	 -- << "do not vanish and the lifts made thus far are reported in the return value.";
     	 n = maxMasseys;
      );
      n = n + 1;
   );
   (hasTMO, tmoSoFar)
)

-- This method computes all the Nary TMOs.  It takes as input a DGAlgebra A, a list of cycles whose classes
-- form a basis of the homology of the input (possibly up to a certain degree), a hash table containing the
-- previous TMOs, and the order of TMOs we are currently computing.
-- The hash table has (key,values) of the form (list giving the 'tensor monomial' of the MO, the MO itself)
-- It returns a hash table which, given the 'tensor monomial' gives the element of Massey operation for that monomial.
-- If at some stage, this can't be computed, then null is returned.
findNaryTrivialMasseyOperation = method(TypicalValue=>Sequence)
findNaryTrivialMasseyOperation(DGAlgebra,List,HashTable,ZZ) := (A,cycleList,prevTMOs,N) -> (
   -- build the list of 'monomials'
   degreeList := cycleList / degree / first;
   tensMons := masseyMonomials(degreeList,prevTMOs,N);
   -- below are some auxiliary functions to make things a little easier.
   getTMO := (h,m) -> if h#?m then h#m else 0_(A.natural);
   cleanTMOHash := h -> select(h, z -> z != 0_(A.natural));
   tensMonDegree := m -> sum apply(m, i -> degreeList#i);
   -- the elements of prodList are of the form (degree of cycle, corresponding tensor monomial, cycle).  I realize this is redundant, but it makes
   -- the code cleaner.
   prodList := apply(tensMons, m -> ((tensMonDegree m) + #m - 2, m, sum apply(#m-1, i -> ((-1)^((tensMonDegree take(m,i+1)) + i + 1))*(getTMO(prevTMOs,take(m,i+1)))*(getTMO(prevTMOs,drop(m,i+1))))));
   n := min (prodList / first);
   maxDegree := max (prodList / first);
   retVal := hashTable {};
   hasTrivialMasseyOperation := true;
   while n <= maxDegree do (
      boundaryList := select(prodList, z -> z#0 == n);
      if boundaryList != {} then (
         (isBoundary,tempVar) := getBoundaryPreimage(A,boundaryList / last);
	 if (isBoundary == false) then (
	    -- if we are in here, then no trivial Massey operation exists, but tempVar contains
	    -- the reduction of the possible nary products modulo the boundaries, and
	    -- all lower massey operations vanish.  Can we report what the products are?
	    -- yes, by using cycleList, together with boundaryList.  We wish to also
	    -- put this information in retVal.
            hasTrivialMasseyOperation = false;
	    nontrivialTMOs := boundaryList_(select(#boundaryList, i -> tempVar_{i} != 0));
      	    retVal = merge(prevTMOs, hashTable apply(nontrivialTMOs, p -> (p#1,p#2)), first);
	    n = maxDegree;
	 )
	 else retVal = merge(retVal, hashTable apply(#tempVar, i -> (boundaryList#i#1,tempVar#i)),first);
      );
      n = n + 1;
   );
   -- at this point, we want to concatenate all the new TMOs to the old hash table.
   if hasTrivialMasseyOperation then retVal = cleanTMOHash merge(prevTMOs, retVal, first);
   (hasTrivialMasseyOperation,retVal)
)

masseyMonomials = method(TypicalValue=>List)
masseyMonomials (List,HashTable,ZZ) := (degreeList, nonzeroMons, n) -> (
   select((cartesianPower(2,keys nonzeroMons)) / flatten, m -> #m == n)
)

cartesianPower = (n,myList) -> (
   retVal := if n == 2 then toList ((set myList) ** (set myList)) else toList ((set myList) ** (set cartesianPower(n-1,myList)));
   if n > 2 then apply(retVal, p -> flatten {p#0,flatten p#1}) else retVal / toList
)

----- Triple Massey operations code  ------

representativeCycle = method()
representativeCycle(DGAlgebra, RingElement) := (A, h) -> (
   --- this function takes an element h in HH(A) and returns
   --- a cycle representing h
   H := HH(A);
   phi := map(A.natural,H,H.cache.cycles);
   phi h
)

homologyClass = method()
homologyClass(DGAlgebra, RingElement) := (A,z) -> (
   -- this function returns an element of H that is represented by
   -- the element z
   if diff(A,z) != 0 then
      error "Expected a cycle.";
   H := HH(A);
   if z == 0 then return 0_H;
   R := A.ring;
   d := degree z;
   basisHd := basis(d,H);
   phi := map(A.natural,H,H.cache.cycles);
   basisHdCycles := phi basisHd;
   bound := A.dd_((first d)+1);
   cycleCoeffs := sub(last coefficients(z, Monomials => getBasis(first d,A)),R);
   homolCoeffs := sub(last coefficients(basisHdCycles, Monomials => getBasis(first d,A)),R);
   cycleInHomol := sub((cycleCoeffs // (homolCoeffs | bound))^{0..(numcols homolCoeffs - 1)},coefficientRing R);
   first flatten entries (basisHd * cycleInHomol)
)

masseyTripleProductOnCycles = method()
masseyTripleProductOnCycles(DGAlgebra, RingElement, RingElement, RingElement) := (A,z1,z2,z3) -> (
   -- this function computes a cycle representing the Massey operation
   -- of <h1,h2,h3> where hi is represented by zi.
   if ring z1 =!= A.natural or ring z2 =!= A.natural or ring z3 =!= A.natural then
      error "Expected elements of the underlying algebra of input DGAlgebra.";
   l := first degree z1;
   m := first degree z2;
   n := first degree z3;
   --lift12 := diffPreimage(A,(-1)^(l+1)*z1*z2);
   --lift23 := diffPreimage(A,(-1)^(m+1)*z2*z3);
   (lifted12,lift12) := getBoundaryPreimage(A,(-1)^(l+1)*z1*z2);
   if not lifted12 then error "The product z1z2 was not a boundary.";
   (lifted23,lift23) := getBoundaryPreimage(A,(-1)^(m+1)*z2*z3);
   if not lifted23 then error "The product z2z3 was not a boundary.";
   result := (-1)^(l+m)*lift12*z3 + (-1)^(l+1)*z1*lift23;
   result
)

-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-- %%%%%%%  Brief note on quadruple Massey operations   %%%%%%

-- To compute Massey quadruple product, one computes:
-- lift13*z4 + lift12*lift34 + z1*lift24

-- Furthermore, according to O'Niell's paper, the two Massey
-- operations must vanish *simultaneously*.  That is, one must
-- trivialize the Massey operations defining lift13 and lift24 with
-- the same lifts lift12, lift23 and lift34.  to check that they
-- simultaneously vanish, one must perform a calculation similar to
-- the findTMO code in DGAlgebras, but only for those two Massey
-- operations.

-- Note that if all (r-1)-ary Massey operations are defined and
-- vanish, then all r-ary Massey operations are defined and uniquely
-- determined.  This doesn't really help us in the general case of
-- defining a quadruple product as discussed above, however.

-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

masseyTripleProduct = method()
masseyTripleProduct(DGAlgebra, RingElement, RingElement, RingElement) := (A,h1,h2,h3) -> (
   -- this function computes the Massey triple product <h1,h2,h3>
   H := HH(A);
   if ring h1 =!= H or ring h2 =!= H or ring h3 =!= H then
   (
      if ring h1 =!= A.natural or ring h2 =!= A.natural or ring h3 =!= A.natural then
         error "Expected elements of the homology algebra of input DGAlgebra."
      else
         return masseyTripleProductOnCycles(A,h1,h2,h3);
   );
   if (h1*h2 != 0 or h2*h3 != 0) then return 0_H;
   z1 := representativeCycle(A,h1);
   z2 := representativeCycle(A,h2);
   z3 := representativeCycle(A,h3);
   homologyClass(A, masseyTripleProductOnCycles(A,z1,z2,z3))
)

masseyTripleProduct(DGAlgebra, ZZ, ZZ, ZZ) := (A,l,m,n) -> (
   -- this function computes the map representing the
   -- triple Massey products H_l \otimes H_m \otimes H_n \to H_{l+m+n+1}
   H := HH(A);
   basisHl := flatten entries basis(l,H);
   basisHm := flatten entries basis(m,H);
   basisHn := flatten entries basis(n,H);
   domainBasis := (basisHl ** basisHm ** basisHn) / splice;
   codomainBasis := basis(l+m+n+1,H);
   masseyProducts := matrix {apply(domainBasis, d -> masseyTripleProduct(A,d#0,d#1,d#2))};
   sub(last coefficients(masseyProducts,Monomials=>codomainBasis), coefficientRing A.ring)
)

----- DGAlgebraMap functions --------

net DGAlgebraMap := f -> net f.natural

dgAlgebraMap = method(TypicalValue => DGAlgebraMap)
dgAlgebraMap (DGAlgebra,DGAlgebra,Matrix) := (B,A,fnMatrix) -> (
   f := new MutableHashTable;
   f#(symbol source) = A;
   f#(symbol target) = B;
   f#(symbol natural) = map(B.natural,A.natural,fnMatrix);
   f#(symbol ringMap) = map(B.ring,A.ring,drop(flatten entries matrix f.natural,numgens A.natural) / (f -> substitute(f,B.ring)));
   new DGAlgebraMap from f
)

target DGAlgebraMap := f -> f.target
source DGAlgebraMap := f -> f.source

-- overload isWellDefined for DGAlgebraMap
isWellDefined DGAlgebraMap := f -> (
   A := source f;
   B := target f;
   all(apply(gens A.natural, x -> f.natural(A.diff(x)) == B.diff(f.natural(x))), identity)
)

toComplexMap = method(TypicalValue=>ChainComplexMap,Options=>{EndDegree=>-1,AssertWellDefined=>true})
toComplexMap DGAlgebraMap := opts -> f -> (
   A := source f;
   B := target f;
   maxDeg := maxDegree A;
   if (opts.EndDegree != -1) then maxDeg = opts.EndDegree;
   if maxDeg == infinity then error "Must specify an upper degree bound if an even generator exists.";
   if (opts.AssertWellDefined) then assert isWellDefined f;
   Acc := toComplex(A,maxDeg);
   Bcc := toComplex(B,maxDeg);
   if A.ring === B.ring then
      map(Bcc,Acc,i -> toComplexMap(f,i,opts))
   else
      -- the below doesn't work yet since I haven't made pushForward functorial.
      map(pushForward(f.ringMap,Bcc),Acc, t -> toComplexMap(f,t,opts))
)

toComplexMap (DGAlgebraMap,ZZ) := opts -> (f,n) -> (
   A := source f;
   B := target f;
   R := A.ring;
   S := B.ring;
   sourceList := {};
   targetList := {};
   coeffMatrix := {};
   -- If the rings of A and B differ, then there needs to be a ring map from A.ring to B.ring (unless substitute would work)
   -- then, need to pullback the rings to be modules over the base so that M2 will recognize the module maps.
   -- The code below is if the base rings agree.
   aDiff := polyDifferential(n,A);
   bDiff := polyDifferential(n,B);
   if R === S then (
      sourceList = flatten entries getBasis(n,A);
      targetList = flatten entries getBasis(n,B);
      coeffMatrix = substitute((coefficients(matrix {sourceList / f.natural}, Monomials => targetList))#1, B.ring);
      map(source bDiff, source aDiff, coeffMatrix)
   )
   else (
      sourceList = flatten entries getBasis(n,A);
      targetList = flatten entries getBasis(n,B);
      sCoeffMatrix := substitute((coefficients(matrix {sourceList / f.natural}, Monomials => targetList))#1, B.ring);
      -- the rest of this code converts the matrix over S to a matrix over R.  It's slightly
      -- different than pushForward(RingMap,Matrix), since the map is between free modules over
      -- different rings.
      J := graphIdeal(f.ringMap,VariableBaseName=>(local XX));
      T := ring J;
      Tbar := T/J;
      h := map(Tbar,S,matrix {take((gens T) / (k -> substitute(k,Tbar)),numgens S)});
      I := ideal 0_Tbar;
      -- have to be a little careful if graphIdeal is zero.
      if (Tbar =!= S) then I = image matrix {drop(gens Tbar,numgens S)};
      Tbarbar := Tbar/I;
      phi := map(Tbar,Tbarbar);
      sVars := (flatten entries basis Tbarbar) / (z -> phi(z));
      -- sVars is so that the coefficients are in the same order as the generators
      -- chosen in pushForward
      h2 := map(R,Tbar,matrix map(R^1,R^(numgens S),0_R) | matrix {gens R});
      entriesOverR := apply(entries sCoeffMatrix, row -> h2(last coefficients(matrix{row / h}, Variables=>take(gens Tbar, numgens R),Monomials=>sVars)));
      sPushForward := pushForward(f.ringMap,source bDiff);
      sRank := rank target first entriesOverR;
      rRank := rank source aDiff;
      -- finally build the map
      map(sPushForward, source aDiff, (i,j) -> (entries entriesOverR#(i//sRank))#(i%sRank)#(j%rRank))
   )
)

-- make pushForward functorial for maps of free modules
pushForward(RingMap,Matrix) := opts -> (f,M) -> (
   -- converts a map of free S-modules (a finite R-algebra) to a map over R
   R := source f;
   S := target f;
   assert (ring M === S);
   J := graphIdeal(f,VariableBaseName=>(local XX));
   T := ring J;
   Tbar := T/J;
   I := ideal 0_Tbar;
   -- have to be a little careful if graphIdeal is zero.
   if (Tbar =!= S) then I = image matrix {drop(gens Tbar,numgens S)};
   Tbarbar := Tbar/I;
   phi := map(Tbar,Tbarbar);
   h := map(Tbar,S,matrix {take(gens Tbar,numgens R)});
   sVars := (flatten entries basis Tbarbar) / (z -> phi(z));
   -- sVars is so that the coefficients are in the same order as the generators
   -- chosen in pushForward (is this true?)
   h2 := map(R,Tbar,matrix map(R^1,R^(numgens S),0_R) | matrix {gens R});
   MEntriesOverR := apply(entries M, row -> apply(row, p -> h2(last coefficients(matrix{h(p)*sVars}, Variables=>take(gens Tbar, numgens R),Monomials=>sVars))));
   targetM := pushForward(f,target M,opts);
   sourceM := pushForward(f,source M,opts);
   sRank := #sVars;
   map(targetM, sourceM, (i,j) -> (entries MEntriesOverR#(i//sRank)#(j//sRank))#(i%sRank)#(j%sRank))
)

pushForward(RingMap,ChainComplex) := opts -> (f,C) -> chainComplex apply(0..((length C)-1), i -> pushForward(f,C.dd_(i+1),opts))

-- The function below will return HH(f) as a module map over HH_0(A) (provided HH_0(B)
-- is a finite HH_0(A)-module). 
--homology (DGAlgebraMap,ZZ) := opts -> (f,n) -> (
--)

homology DGAlgebraMap := opts -> f -> (
   A := source f;
   B := target f;
   -- the following commands will fail if A and B have generators in even degree, since one
   -- needs to specify the degrees to look for gens and relations.
   HA := HH A;
   HB := HH B;
   R := B.ring;
   HBToB := map(B.natural,HB,HB.cache.cycles);
   BToR := map(R,B.natural);
   RToH0B := map(zerothHomology B,R); 
   -- now need to take the generators of HA, and write their image in terms of the basis
   -- we have chosen of HB.
   cycleImages := pack(2,mingle {gens HA,HA.cache.cycles / f.natural});
   -- this way we use whatever grading the image has to speed up this process.
   cycleDegrees := cycleImages / (z -> degree last z);
   degreeTable := hashTable(join, pack(2,mingle {cycleDegrees,apply(cycleImages, z -> {z})}));
   -- now for each degree (i.e. each (k,v) pair in the hash table), we need to compute a GB for the submodule generated by the cycles of that
   -- particular degree, together with the image.  Then take the coefficients of the cycles (using getChangeMatrix)
   -- modulo this GB, and this will give us the element in HB to map that variable from HA to.
   imageHash := hashTable flatten apply(pairs degreeTable, (cycleDegree,cycleImageGroup) -> (
	                      hbBasis := flatten entries basis(cycleDegree,HB);
   	                      bBasis := flatten entries getBasis(first cycleDegree,B);
			      thisDiff := polyDifferential((first cycleDegree)+1,B);
			      cycleTargetCoeff := last coefficients(matrix{hbBasis / HBToB},Monomials=>bBasis);
			      cycleImageGroupCoeff := last coefficients(matrix{apply(cycleImageGroup, z -> last z)},Monomials=>bBasis);
			      -- the following commands fix the degrees since function application does not seem to maintain them.
			      cycleTargetCoeff = map(target thisDiff, R^(apply(degrees source cycleTargetCoeff, d -> drop(d,1))), BToR(cycleTargetCoeff));
			      cycleImageGroupCoeff = map(target thisDiff, R^(apply(degrees source cycleImageGroupCoeff, d->drop(d,1))), BToR(cycleImageGroupCoeff));
			      hGB := gb (cycleTargetCoeff | thisDiff, ChangeMatrix => true);
			      -- the first (#cycleTargetCoeff) many rows have the coefficients that we want.  
			      imageHBCoeffs := RToH0B((cycleImageGroupCoeff // hGB)^{0..(#hbBasis)-1});
			      images := basis(cycleDegree,HB)*imageHBCoeffs;
			      pack(2, mingle {apply(cycleImageGroup, z -> first z), flatten entries images})
			   )
		        ); 
   -- now imageHash is a hashTable with key/value pair (source variable, image)
   map(HB,HA,apply(gens HA, x -> imageHash#x))
)

-- This function lifts a ring map in degree zero to a map between DGAlgebras
-- (Conditions on RingMap based on differentials?)
liftToDGMap = method(TypicalValue => DGAlgebraMap, Options=>{EndDegree=>-1})
liftToDGMap (DGAlgebra,DGAlgebra,RingMap) := opts -> (B,A,f) -> (
   -- We assume below that f is an R-algebra map, where R = A.ring, S is a finite R-algebra,
   -- and that f(\del_1(A)) \subseteq \del_1(B).
   R := A.ring;
   S := B.ring;
   -- phi0 := map(S^1,R^1,f,{{1_R}});
   -- not ok yet, only works for quotients.
   phi0 := map(pushForward(f,S^1),R^1,matrix{{1_R}});
   numgensA := numgens A.natural;
   maxLiftDegree := max((degrees A.natural) / first);
   -- if EndDegree is specified, we will send all variables beyond that degree to zero; of course this
   -- may no longer be a DG algebra map.
   if opts.EndDegree != -1 then maxLiftDegree = opts.EndDegree;
   -- degree by degree, we lift.  Because A is semifree (which we will have to check at a later date
   -- should we add non-polynomial underlying algebras), we can send the variables to any lift we choose.
   previousMap := phi0;
   currentMap := 0;
   n := 1;
   imageList := {};
   AGens := select(gens A.natural, z -> first degree z <= maxLiftDegree);
   gensHash := hashTable(join,pack(2,mingle {(AGens / degree / first),AGens / (i -> {i})}));
   while n <= maxLiftDegree do (
      gensList := gensHash#n;
      -- select the relevant columns of the nth differential
      diffA := polyDifferential(n,A);
      basisA := flatten entries getBasis(n,A);
      -- select the columns corresponding to generators
      diffA = diffA_(select(#basisA, i -> (first support basisA_i == basisA_i)));
      diffB := polyDifferential(n,B);
      -- make diffB into a map of R-modules via pushForward
      -- must use pushForward so that all modules are over the same ring so we can lift.
      diffB = map(pushForward(f,target diffB), pushForward(f,source diffB), substitute(diffB,R));
      -- factor previousMap*diffA through the image of diffB
      -- note: previousMap must be a map of R-modules so that // will work.
      factorMap := previousMap*diffA // diffB;
      images := flatten entries (getBasis(n,B)*(f ** factorMap));
      -- images has the images of the generators under the lift of f.
      imageList = imageList | images;
      padVarList := imageList | apply(numgensA-#imageList,i -> 0) | flatten entries matrix f;
      -- make a temporary dgAlgebraMap so we can use existing code to build the dg algebra map in degree n
      tempPhi := dgAlgebraMap(B,A,matrix{padVarList});
      previousMap = toComplexMap(tempPhi,n,AssertWellDefined=>false);
      n = n + 1;
   );
   dgAlgebraMap(B,A,matrix {imageList})
)

torMap = method(TypicalValue => RingMap, Options => {GenDegreeLimit=>3})
torMap RingMap := opts -> f -> (
   R := source f;
   S := target f;
   rTorAlg := torAlgebra(R,opts);
   sTorAlg := torAlgebra(S,opts);
   A := acyclicClosure(R,EndDegree=>(opts.GenDegreeLimit-1));
   B := acyclicClosure(S,EndDegree=>(opts.GenDegreeLimit-1));
   sSub := map(sTorAlg, B.natural,matrix{gens sTorAlg});
   phi := liftToDGMap(B,A,f);
   map(sTorAlg,rTorAlg,matrix{take(flatten entries matrix phi.natural,numgens A.natural) / sSub})
)



--------------------
getVariableChunks = method()
getVariableChunks DGAlgebra := A -> (
   ns := values (tally (gens A.natural) / degree / first);
   partSumNs := {0} | apply(#ns, i -> sum take(ns, i+1));
   varsA := apply(#ns, i -> (gens A.natural)_(toList(partSumNs#i..partSumNs#(i+1)-1)));
   varsA
)

chunkDegree = method()
chunkDegree (RingElement,List) := (f,L) -> (sum apply(L, l -> degree(l,f)))*(first degree first L)

getPhiMap = method()
getPhiMap (DGAlgebra, List, List) := (A, inVec, outVec) -> (
   if (sum inVec != sum outVec + 1) then return null;
   varsA := getVariableChunks A;
   h := #varsA;
   if h != #inVec or h != #outVec then return null;
   inBasis := tensor apply(h, i -> basis(inVec#i,A.natural,Variables => varsA#i));
   outBasis := tensor apply(h, i -> basis(outVec#i,A.natural,Variables => varsA#i));
   diffs := matrix {apply(flatten entries inBasis, m -> diff(A,m))};
   tempM := sub(last coefficients(diffs, Monomials => outBasis), A.ring);
   R := A.ring;
   tempM = if not isHomogeneous A then tempM else (
      sourceM := R^(-((flatten entries inBasis) / degree / (e -> drop(e,1))));
      targetM := R^(-((flatten entries outBasis) / degree / (e -> drop(e,1))));
      map(targetM,sourceM,tempM)
   );
   tempM
)

degreeVecs = method()
degreeVecs (DGAlgebra,ZZ) := (A,d) -> (
   varsA := getVariableChunks A;
   basisA := flatten entries basis(d,A.natural);
   sort unique apply(basisA, m -> apply(varsA, l -> chunkDegree(m,l)))
)

displayBlockDiff = method()
displayBlockDiff (DGAlgebra, ZZ) := (A,d) -> (
   M := blockDiff(A,d);
   inputDegs := indices source M;
   outputDegs := indices target M;
   firstRow := {"."} | inputDegs;
   myNet := {firstRow} | apply(outputDegs, o -> {o} | apply(inputDegs, i -> M^[o]_[i]));
   netList(myNet,Alignment => Center)
)

blockDiff = method()
blockDiff (DGAlgebra, ZZ) := (A,d) -> (
   if A.cache#?"blockDiffs" and A.cache#"blockDiffs"#?d then return A.cache#"blockDiffs"#d;
   inputDegs := degreeVecs(A,d);
   outputDegs := degreeVecs(A,d-1);
   tempM := table(#outputDegs, #inputDegs, (a,b) -> getPhiMap(A,inputDegs#b,outputDegs#a));
   sourceMods := (first tempM) / source;
   targetMods := tempM / first / target;
   sourceM := directSum apply(#inputDegs, i -> inputDegs#i => sourceMods#i);
   targetM := directSum apply(#outputDegs, i -> outputDegs#i => targetMods#i);
   M := map(targetM,sourceM,matrix tempM);
   if not A.cache#?"blockDiffs" then
      A.cache#"blockDiffs" = new MutableHashTable from {};
   A.cache#"blockDiffs"#d = M;
   M
)

displayBlockDiff (DGAlgebra, Array, Array) := Matrix => (A, sour, tar) -> (
    --tar: a list of mulit-indices occurring in the (i-1)-th step of the complex associated to A
    --sour: a list of mulit-indices occurring in the (i)-th step of the complex associated to A
    i := sum sour_0;
    X := blockDiff(A,i);
    X_sour^tar
    )
displayBlockDiff (DGAlgebra, List, List) := Matrix => (A, sour, tar) -> (
    --tar: a  mulit-index occurring in the (i-1)-th step of the complex associated to A
    --sour: a index occurring in the (i)-th step of the complex associated to A
    i := sum sour;
    X := blockDiff(A,i);
    X_[sour]^[tar]
    )
displayBlockDiff (DGAlgebra,  VisibleList) := Matrix => (A, tar) -> (
    --tar: a  mulit-index occurring in the (i-1)-th step of the complex associated to A
    --let i = 1+ sum tar, and d = C.dd_i the i-th differential in the complex C = toComplex(A, i).
    --this returns all the blocks in d whose target is the summand corresponding to tar.
    t := tar;
    if class tar === Sequence then t = new Array from {toList t};
    if class tar === List then t = new Array from {t};
    if class t =!= Array or class (t_0) =!= List then 
        error "argument 1 should be a sequence, list or array of one element which is a list.";
    i := 1+sum t_0;
    X := blockDiff(A,i);
    sour := new Array from indices source X;
    X_sour^t
    )
///
restart
loadPackage("DGAlgebras", Reload => true)
R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
A = acyclicClosure(R,EndDegree => 2)
L = indices source blockDiff(A,5)
displayBlockDiff(A,  [{0,2,3}], [{1,0,3},{0,4,0}])
displayBlockDiff(A,  {0,2,3}, {0,4,0})
displayBlockDiff(A , (1,0,3))
displayBlockDiff(A,  {1,0,3})
displayBlockDiff(A,  [{1,0,3}])
///

--------------------
-- Documentation  --
--------------------

beginDocumentation()

doc ///
  Key
    DGAlgebras
  Headline
    Data types and basic functions on differential graded (DG) Algebras.
  Description
    Text
      This package is used to define and manipulate DG algebras.
  Subnodes
    "Basic operations on DG Algebras"
    "The Koszul complex as a DG Algebra"
    "Basic operations on DG Algebra Maps"
///

doc ///
  Key
    "Basic operations on DG Algebras"
  Headline
    Outlines some basic operations on DG Algebras
  Description
    Text
      There are several ways to define a DGAlgebra.  One can start by defining one 'from scratch'.  One does
      this by specifying the ring over which the DGAlgebra is defined and the degrees of the generators.  The
      name of the generators of the DGAlgebra by default is $T_i$, but one may change this by specifying the
      optional (string) argument 'Variable'.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
      A = freeDGAlgebra(R,{{1,1},{1,1},{1,1},{1,1}})
    Text
      The command freeDGAlgebra only defines the underlying algebra of A, and not the differential.  To set the differential of A,
      one uses the command setDiff.
    Example
      setDiff(A, gens R)
    Text
      Note that the above is the (graded) Koszul complex on a set of generators of R.  A much easier way to define this is to use the
      function koszulComplexDGA.
    Example
      B = koszulComplexDGA(R, Variable=>"S")
    Text
      One can compute the homology algebra of a DGAlgebra using the homology (or HH) command.
    Example
      HB = HH B
      describe HB
      degrees HB
    Text
      Note that since R is a complete intersection, its Koszul homology algebra is an exterior algebra, which is a
      free graded commutative algebra.  Note that the internal degree is preserved in the computation of the homology algebra
      of B.
    Text
      One can also adjoin variables to kill cycles in homology.  The command killCycles looks for the first positive degree
      nonzero homology (say i), and adjoins variables in homological degree i+1 that differentiate to a minimal generating set of this homology, so that the
      resulting DGAlgebra now only has homology in degree greater than i (note of course this could introduce new homology in higher degrees).
      The command adjoinVariables allows finer control over this procedure.  See @ TO adjoinVariables @ for an example.
    Example
      HB.cache.cycles
      C = adjoinVariables(B,{first HB.cache.cycles})
      homologyAlgebra(C,GenDegreeLimit=>4,RelDegreeLimit=>4)
      C = killCycles(B)
      homologyAlgebra(C,GenDegreeLimit=>4,RelDegreeLimit=>4)
    Text
      Again, note that since R is a complete intersection, once we adjoin the variables in homological degree two to kill the cycles in degree one,
      we obtain a minimal DG Algebra resolution of the residue field of R.  Also, note that since C has generators in even degree, one must specify the
      optional arguments GenDegreeLimit and RelDegreeLimit to specify the max degree of the computation.  To do this, one uses the homologyAlgebra command
      rather than the HH command.
    Text
      This computation could have also been done with the command acyclicClosure.  The command acyclicClosure performs the command killCycles sequentially to ensure that the
      result has homology in higher and higher degrees, thereby computing (part of) a minimal DG Algebra resolution of the residue field.  acyclicClosure has an optional
      argument EndDegree that allows the user to specify the maximum homological degree with which to perform this adjunction of variables.  The default value of this is 3, since if there
      are any variables of degree 3 that need to be added, then each subsequent homological degree will require some variables to be adjoined (Halperin's rigidity theorem).
    Example
      D = acyclicClosure R
      R' = ZZ/101[x,y,z]/ideal{x^2,y^2,z^2,x*y*z}
      E = acyclicClosure(R',EndDegree=>5)
      tally degrees E.natural
    Text
      As you can see, since R' is not a complete intersection, the acyclic closure of E requires infinitely many variables; we display the degrees of the first 6 here.
      The tally that is displayed gives the deviations of the ring R.  One can compute the deviations directly from any minimal free resolution of the residue field
      of R', so that using the one provided by res coker vars R is faster.  To do this, use the command @ TO deviations @.
    Example
      deviations(R,DegreeLimit=>6)
      deviations(R',DegreeLimit=>6)
    Text
      As a brief warning, the command @ TO poincareN @ which is used in @ TO deviations @ uses the symbols S and T internally, and may cause problems accessing such rings with the user interface.
///

doc ///
  Key
    "The Koszul complex as a DG Algebra"
  Headline
    an example
  Description
    Text
      The Koszul complex on a sequence of elements $f_1,\dots,f_r$ is a complex of R-modules whose underlying graded R-module
      is the exterior algebra on R^r generated in homological degree one.  This algebra structure also respects the boundary map
      of the complex in the sense that it satisfies the Liebniz rule.  That is, $d(ab) = d(a)b + (-1)^{deg a}ad(b)$.  When one
      speaks of 'the' Koszul complex of a ring, one means the Koszul complex on a minimal set of generators of the maximal ideal of R.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
      KR = koszulComplexDGA R
    Text
      One can specify the name of the variable to easily handle multiple Koszul complexes at once.
    Example
      S = ZZ/101[x,y,z]/ideal{x^3,y^3,z^3,x^2*y^2,y^2*z^2}
      KS = koszulComplexDGA(S,Variable=>"U")
    Text
      To obtain the chain complex associated to the Koszul complex, one may use toComplex.  One can also obtain this complex
      directly without using the DGAlgebras package by using the command @ TO koszul @.
    Example
      cxKR = toComplex KR
      prune HH cxKR
    Text
      Since the Koszul complex is a DG algebra, its homology is itself an algebra.  One can obtain this algebra using the command
      homology, homologyAlgebra, or HH (all commands work).  This algebra structure can detect whether or not the ring is a complete
      intersection or Gorenstein.
    Example
      HKR = HH KR
      ideal HKR
      R' = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3,a*c,a*d,b*c,b*d,a^2*b^2-c^2*d^2}
      HKR' = HH koszulComplexDGA R'
      numgens HKR'
      ann ideal gens HKR'
    Text
      Note that since the socle of HKR' is one dimensional, HKR' has Poincare duality, and hence R' is Gorenstein.
    Text
      One can also consider the Koszul complex of an ideal, or a sequence of elements.
    Example
      Q = ambient R
      I = ideal {a^3,b^3,c^3,d^3}
      KI = koszulComplexDGA I
      HKI = HH KI
      describe HKI
      use Q
      I' = I + ideal{a^2*b^2*c^2*d^2}
      KI' = koszulComplexDGA I'
      HKI' = HH KI'
      describe HKI'
      HKI'.cache.cycles
    Text
      Note that since I is a Q-regular sequence, the Koszul complex is acyclic, and that both homology algebras are algebras over the zeroth homology
      of the Koszul complex.
///

doc ///
  Key
    "Basic operations on DG Algebra Maps"
  Headline
    Outlines some basic operations on DGAlgebraMaps
  Description
    Text
      An algebra map between the underlying graded algebras that satisfies the Leibniz rule is a morphism of DG algebras.  Such objects
      are created using the DGAlgebraMap class.  As with DGAlgebras, one can define a DGAlgebraMap 'from scratch' using @ TO dgAlgebraMap @.
    Example
      R = ZZ/101[a,b,c]/ideal{a^3+b^3+c^3,a*b*c}
      K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
      K2 = koszulComplexDGA(ideal {b,c},Variable=>"T")
      f = dgAlgebraMap(K2,K1,matrix{{0,T_1,T_2}})
    Text
      Once we define the DGAlgebraMap, it is a good idea to check to see if it indeed satisfies the Leibniz rule.  This can be checked by using
      isWellDefined.
    Example
      isWellDefined f
    Text
      Oops!  Let's try that again.
    Example
      g = dgAlgebraMap(K1,K2,matrix{{Y_2,Y_3}})
      isWellDefined g
    Text
      One can lift a ring homomorphism in degree zero to a map of DGAlgebras (up to a specified degree) using liftToDGMap.  This is helpful
      in some of the internal functions of the DGAlgebras package, such as computing the map induced on Tor algebras by a RingMap.
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
      S = R/ideal{a^2*b^2*c^2}
      f = map(S,R)
      A = acyclicClosure(R,EndDegree=>3)
      B = acyclicClosure(S,EndDegree=>3)
      phi = liftToDGMap(B,A,f)
    Text
      Once one has a DGAlgebraMap, one can also obtain the underlying map of complexes via toComplexMap.
    Example
      cmPhi = toComplexMap(phi,EndDegree=>3)
    Text
      There are also some auxiliary commands associated with DGAlgebraMaps
    Example
      source phi
      target phi
    Text
      One can also obtain the map on homology induced by a DGAlgebra map.
    Example
      HHg = HH g
      matrix HHg
///

doc ///
  Key
    DGAlgebra
  Headline
    The class of all DGAlgebras
  Description
    Text
      Some common ways to create DGAlgebras include @ TO koszulComplexDGA @, @ TO freeDGAlgebra @, @ TO setDiff @, and @ TO acyclicClosure @.
  SeeAlso
    "Basic operations on DG Algebras"
///

doc ///
  Key
    freeDGAlgebra
    (freeDGAlgebra,Ring,List)
    [freeDGAlgebra,Variable]
  Headline
    Constructs a DGAlgebra
  Usage
    A = freeDGAlgebra(R,degreeList) 
  Inputs
    R:Ring 
      The ring over which the DGAlgebra is defined
    degreeList:List 
      A list of degrees of the algebra generators of R.
  Outputs
    A:DGAlgebra
  Description
    Text
      This function returns a @ TO DGAlgebra @ A whose underlying algebra is a graded commutative
      polynomial ring in a number of variables equal to the number of the degrees input.  The current version of this package
      does not handle algebras A whose underlying algebra is not a polynomial ring.
    Example
      R = ZZ/101[x,y,z]
      A = freeDGAlgebra(R,{{1},{1},{1},{3}})
      A.natural
      setDiff(A,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
    Text
      The resulting @ TO DGAlgebra @ will not be graded since the differential given does not respect the grading due to the degrees assigned in the definition.
    Example
      isHomogeneous(A)
      Add = toComplex A
      B = freeDGAlgebra(R,{{1,1},{1,1},{1,1},{3,3}})
      B.natural
      setDiff(B,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
    Text
      The result of the above declaration will be graded.
    Example
      isHomogeneous(B)
      Bdd = toComplex B
    Text  
      Note that the differential is not passed into the constructor.  The reason for this (at the moment)
      is that Macaulay2 does not know what ring the differentials are defined over until after the underlying
      algebra is constructed, so the differential is set later with setDiff.  Many DG algebras that one
      encounters in commutative algebra have been implemented, however, and do not need to be defined 'by hand'.
      For example, if one wants to work with the Koszul complex as a DG algebra, then one should see the command @ TO koszulComplexDGA @.
      Also, if one wishes to specify the name of the variables used, specify the Variable option; see the example in @ TO dgAlgebraMap @.
  Caveat
    There is currently a bug handling DG algebras that have no monomials in some degree, but some monomials in a later degree;
    for example if one replaces the 3 in the above example with a 5.
///

doc ///
  Key
    koszulComplexDGA
    (koszulComplexDGA,Ring)
    [koszulComplexDGA,Variable]
  Headline
    Returns the Koszul complex as a DGAlgebra
  Usage
    A = koszulComplexDGA(R)
  Inputs
    R:Ring 
      Returns the Koszul complex on ideal vars R.
  Outputs
    A:DGAlgebra
  Description
    Text
      To construct the Koszul complex of a minimal set of generators as a @ TO DGAlgebra @ one uses
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
      A = koszulComplexDGA(R)
      complexA = toComplex A
      complexA.dd
      ranks = apply(4, i -> numgens prune HH_i(complexA))
      ranks == apply(4, i -> numgens prune HH_i(koszul vars R))
    Text
      One can also compute the homology of A directly with @ TO (homology,ZZ,DGAlgebra) @.  One may also specify
      the name of the variable using the Variable option.
///

doc ///
  Key
    (koszulComplexDGA,Ideal)
  Headline
    Returns the Koszul complex as a DGAlgebra
  Usage
    A = koszulComplexDGA(I)
  Inputs
    I:Ideal 
      An ideal of a ring R
  Outputs
    A:DGAlgebra
  Description
    Text
      To construct the Koszul complex on the set of generators of I as a @ TO DGAlgebra @ one uses
    Example
      R = ZZ/101[a,b,c]
      I = ideal{a^3,b^3,c^3,a^2*b^2*c^2}
      A = koszulComplexDGA(I)
      complexA = toComplex A
      complexA.dd
      ranks = apply(4, i -> numgens prune HH_i(complexA))
      ranks == apply(4, i -> numgens prune HH_i(koszul gens I))
    Text
      One can also compute the homology of A directly with @ TO (homology,ZZ,DGAlgebra) @.
///

doc ///
  Key
    (koszulComplexDGA,List)
  Headline
    Define the Koszul complex on a list of elements as a DGAlgebra
  Usage
    A = koszulComplexDGA(diffList)
  Inputs
    diffList:List
      A List of RingElements.  The resulting DGAlgebra will be defined over the ring of these elements.
  Outputs
    A:DGAlgebra
///

doc ///
  Key
    (homology,ZZ,DGAlgebra)
  Headline
    Computes the homology of a DG algebra as a module
  Usage
    H = homology(n,A)
  Inputs
    n:ZZ
    A:DGAlgebra 
  Outputs
    H:Module
      The nth homology of A.
  Description
    Example
      R = ZZ/32003[x,y,z]
      A = koszulComplexDGA(R)
      apply(numgens R+1, i -> numgens prune homology(i,A))
///

doc ///
  Key
    setDiff
    (setDiff,DGAlgebra,List)
    InitializeComplex
    [setDiff,InitializeComplex]
    InitializeDegreeZeroHomology
    [setDiff,InitializeDegreeZeroHomology]
  Headline
    Sets the differential of a DGAlgebra manually.
  Usage
    d = setDiff(A,diffList)
  Inputs
    A:DGAlgebra
    A:List 
  Outputs
    A:DGAlgebra
      The DGAlgebra with the differential now set.
  Description
    Example
      R = ZZ/101[x,y,z]
      A = freeDGAlgebra(R,{{1},{1},{1},{3}})
      setDiff(A,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
      Add = toComplex A
      Add.dd
    Text
      There are two options that are available for this function, and both are designed to bypass certain initializations
      that take place by default.
    Text
      The option InitializeComplex specifies whether or not to compute all differentials of
      the complex(up to the sum of the degrees of the odd degree generators) before returning from setDiff.  This is useful if
      your DGAlgebra has a large number of generators in odd degrees, and you are only interested in computing the homology
      in low degrees.  The default value of this option is true.
    Text
      The option InitializeDegreeZeroHomology specifies whether or not to define the quotient ring H_0(A).  This is used when
      computing HH(A) as a DGAlgebra.  This involves computing a Grobner basis of the image of the first differential of A,
      and as such, may want to be avoided if there are a large number of DGAlgebra generators in degree 1.  The default value of
      this options is true.
///

doc ///
  Key
    (isHomogeneous, DGAlgebra)
  Headline
    Determine if the DGAlgebra respects the gradings of the ring it is defined over.
  Usage
    isHom = isHomogeneous(A)
  Inputs
    A:DGAlgebra
  Outputs
    isHom:Boolean
      Whether or not the DGA respects the grading
  Description
    Example
      R = ZZ/101[x,y,z]
      A = freeDGAlgebra(R,{{1},{1},{1},{3}})
      setDiff(A,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
      isHomogeneous A
      B = freeDGAlgebra(R,{{1,1},{1,1},{1,1},{3,3}})
      setDiff(B,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
      isHomogeneous B
///

doc ///
  Key
    natural
  Headline
    The underlying algebra of a DGAlgebra.
  Usage
    Anat = A.natural
  Description
    Example
      R = ZZ/101[a,b,c,d]
      A = koszulComplexDGA(R)
      A.natural
///

doc ///
  Key
    cycles
  Headline
    Cycles chosen when computing the homology algebra of a DGAlgebra
  Usage
    A.cycles
  Description
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^4,c^5,d^6}
      A = koszulComplexDGA(R)
      apply(maxDegree A + 1, i -> numgens prune homology(i,A))
      HA = homologyAlgebra(A)
      numgens HA
      HA.cache.cycles
    Text
///

doc ///
  Key
    getBasis
    (getBasis,ZZ,DGAlgebra)
  Headline
    Get a basis for a particular homological degree of a DG algebra.
  Usage
    M = getBasis(n,A)
  Inputs
    n:ZZ
    A:DGAlgebra
  Outputs
    M:Matrix
      The basis of the desired homological degree of the DG Algebra.
  Description
    Text
      This function is to allow for the retrieval of a basis of a particular homological degree of a @ TO DGAlgebra @
      when the underlying algebra A.natural is multigraded.  In the code, the homological grading is always the first
      integer in the degree tuple, and so this function returns a matrix consisting of all monomials in homological
      degree n.  
    Example
      R = ZZ/101[a..d, Degrees=>{1,1,1,2}]
      A =  koszulComplexDGA(R)
      getBasis(3,A)
///

doc ///
  Key
    (getBasis,ZZ,Ring)
  Headline
    Get a basis for a degree of a ring.
  Usage
    M = getBasis(n,R)
  Inputs
    n:ZZ
    R:Ring
  Outputs
    M:Matrix
      The basis of the desired degree
  Description
    Text
      This function was not meant for general use, but it fixes the first degree in the degree tuple
      of the ring R, and finds a basis of that 'slice' of the ring.  It does this by using a cached
      version of the ring that forgets all other degrees.  A Ring object in Macaulay2 will not have this
      cached ring by default, but the rings used internally in the DGAlgebras package will.
///

doc ///
  Key
    toComplex
    (toComplex,DGAlgebra)
  Headline
    Converts a DGAlgebra to a ChainComplex
  Usage
    C = toComplex A or C = toComplex(A,n)
  Inputs
    A:DGAlgebra
  Outputs
    C:ChainComplex
      The DG algebra A as a ChainComplex
  Description
    Example
      R = ZZ/101[x_1..x_10]
      A = koszulComplexDGA(R)
      C = toComplex A
    Text
      Warning:  The term order that the internal command koszul uses to order the monomials is not GRevLex, and so the differentials
      used in koszul and koszulComplexDGA will not match up exactly.  Also, this command will only execute if all of the variables
      of the @ TO DGAlgebra @ A are of odd homological degree.  Otherwise, you need to use the function @ TO (toComplex, DGAlgebra, ZZ) @.
///

doc ///
  Key
    (toComplex,DGAlgebra,ZZ)
  Headline
    Converts a DGAlgebra to a ChainComplex
  Usage
    C = toComplex A or C = toComplex(A,n)
  Inputs
    A:DGAlgebra
    n:ZZ
  Outputs
    C:ChainComplex
      The DG algebra A as a ChainComplex
  Description
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
      A = acyclicClosure(R,EndDegree=>3)
    Text
      The above will be a resolution of the residue field over R, since R is a complete intersection.
    Example
      C = toComplex(A, 10)
      apply(10, i -> prune HH_i(C))
///

doc ///
  Key
    acyclicClosure
    (acyclicClosure,DGAlgebra)
  Headline
    Compute the acyclic closure of a DGAlgebra.
  Usage
    B = acyclicClosure(A)
  Inputs
    A:DGAlgebra
  Outputs
    B:DGAlgebra
      The acyclic closure of the DG Algebra A up to homological degree 
      provided in the EndDegree option (default value is 3).
  Description
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
      A = koszulComplexDGA(R);
      B = acyclicClosure(A,EndDegree=>3)
      toComplex(B,8)
      B.diff
  SeeAlso
    (acyclicClosure,Ring)
///

doc ///
  Key
    (acyclicClosure,Ring)
  Headline
    Compute the acyclic closure of the residue field of a ring up to a certain degree
  Usage
    A = acyclicClosure(R)
  Inputs
    R:Ring
  Outputs
    A:DGAlgebra
      The acyclic closure of the ring R up to homological degree provided in the EndDegree option (default value is 3).
  Description
    Text
      This package always chooses the Koszul complex on a generating set for the maximal ideal as a starting
      point, and then computes from there, using the function @ TO (acyclicClosure,DGAlgebra) @.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^4-d^3}
      A = acyclicClosure(R,EndDegree=>3)
      A.diff
///

doc ///
  Key
    (symbol **, DGAlgebra, Ring)
  Headline
    Tensor product of a DGAlgebra and another ring.
  Usage
    B = A ** S
  Inputs
    A:DGAlgebra
    R:Ring
  Outputs
    B:DGAlgebra
  Description
    Text
      Tensor product of a DGAlgebra and another ring (typically a quotient of A.ring).
    Example
      R = ZZ/101[a,b,c,d]
      A = koszulComplexDGA(R)
      S = R/ideal{a^3,a*b*c}
      B = A ** S
      Bdd = toComplex B
      Bdd.dd
///

doc ///
  Key
    (symbol **, DGAlgebra, DGAlgebra)
  Headline
    Tensor product of a DGAlgebra and another ring.
  Usage
    C = A ** B
  Inputs
    A:DGAlgebra
    B:DGAlgebra
  Outputs
    C:DGAlgebra
  Description
    Text
      Tensor product of a pair of DGAlgebras.
    Example
      R = ZZ/101[a,b,c,d]
      A = koszulComplexDGA({a,b})
      B = koszulComplexDGA({c,d})
      C = A ** B
      Cdd = toComplex C
      Cdd.dd
  Caveat
    Currently, the tensor product function does not create a block order on the variables from A and B.
///

doc ///
  Key
    killCycles
    (killCycles,DGAlgebra)
  Headline
    Adjoins variables to make non-bounding cycles boundaries in the lowest positive degree with nontrivial homology.
  Usage
    B = killCycles(A)
  Inputs
    A:DGAlgebra
  Outputs
    B:DGAlgebra
  Description
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3-d^4}
      A = koszulComplexDGA(R)
      A.diff
      B = killCycles(A)
      B.diff
///

doc ///
  Key
    adjoinVariables
    (adjoinVariables,DGAlgebra,List)
  Headline
    Adjoins variables to make the specified cycles boundaries.
  Usage
    B = adjoinVariables(A,cycleList)
  Inputs
    A:DGAlgebra
    cycleList:List
  Outputs
    B:DGAlgebra
  Description
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3-d^4}
      A = koszulComplexDGA(R)
      A.diff
      prune homology(1,A)
      B = adjoinVariables(A,{a^2*T_1})
      B.diff
      prune homology(1,B)
///

doc ///
  Key
    homologyAlgebra
    (homologyAlgebra,DGAlgebra)
  Headline
    Compute the homology algebra of a DGAlgebra.
  Usage
    HA = homologyAlgebra(A)
  Inputs
    A:DGAlgebra
  Outputs
    HA:Ring
  Description
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
      A = koszulComplexDGA(R)
      apply(maxDegree A + 1, i -> numgens prune homology(i,A))
      HA = homologyAlgebra(A)
    Text
      Note that HA is a graded commutative polynomial ring (i.e. an exterior algebra) since R is a complete intersection.
    Example  
      R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4,a^3*b^3*c^3*d^3}
      A = koszulComplexDGA(R)
      apply(maxDegree A + 1, i -> numgens prune homology(i,A))
      HA = homologyAlgebra(A)
      numgens HA
      HA.cache.cycles
    Example
      Q = ZZ/101[x,y,z]
      I = ideal{y^3,z*x^2,y*(z^2+y*x),z^3+2*x*y*z,x*(z^2+y*x),z*y^2,x^3,z*(z^2+2*x*y)}
      R = Q/I
      A = koszulComplexDGA(R)
      apply(maxDegree A + 1, i -> numgens prune homology(i,A))
      HA = homologyAlgebra(A)
    Text
      One can check that HA has Poincare duality since R is Gorenstein.
    Text
      If your DGAlgebra has generators in even degrees, then one must specify the options GenDegreeLimit and RelDegreeLimit.
    Example
      R = ZZ/101[a,b,c,d]
      S = R/ideal{a^4,b^4,c^4,d^4}
      A = acyclicClosure(R,EndDegree=>3)
      B = A ** S
      HB = homologyAlgebra(B,GenDegreeLimit=>7,RelDegreeLimit=>14)
///

doc ///
  Key
    (homology,DGAlgebra)
  Headline
    Compute the homology algebra of a DGAlgebra.
  Usage
    HA = homology(A)
  Inputs
    A:DGAlgebra
  Outputs
    HA:Ring
  SeeAlso
    (homologyAlgebra,DGAlgebra)
///

doc ///
  Key
    torAlgebra
    (torAlgebra,Ring)
  Headline
    Computes the Tor algebra of a ring
  Usage
    torR = torAlgebra(R)
  Inputs
    R:Ring
  Outputs
    torR:Ring
  Description
    Example
      R = ZZ/101[a,b,c,d]
      TorR = torAlgebra(R)
      S = R/ideal{a^3,b^3,c^3,d^5}
      TorS = torAlgebra(S)
    Text
      The above example calculates the Tor algebra of R and S up to degree 3, by default.  One can also specify the maximum degree
      to compute generators of the Tor algebra by specifying the GenDegreeLimit option.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3,a^2*b^2*c^3*d^2}
      TorR = torAlgebra(R,GenDegreeLimit=>5)
///

doc ///
  Key
    (torAlgebra,Ring,Ring)
  Headline
    Computes Tor_R(S,k) up to a specified generating and relating degree.
  Usage
    TorRS = torAlgebra(R,S,GenDegreeLimit=>m,RelDegreeLimit=>n)
  Inputs
    R:Ring
    S:Ring
  Outputs
    TorRS:Ring
  Description
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
      M = coker matrix {{a^3*b^3*c^3*d^3}};
      S = R/ideal{a^3*b^3*c^3*d^3}
      HB = torAlgebra(R,S,GenDegreeLimit=>4,RelDegreeLimit=>8)
      numgens HB
      apply(5,i -> #(flatten entries getBasis(i,HB)))      
      Mres = res(M, LengthLimit=>8)
    Text
      Note that in this example, $Tor_*^R(S,k)$ has trivial multiplication, since the
      map from R to S is a Golod homomorphism by a theorem of Levin and Avramov.
///

doc ///
  Key
    maxDegree
    (maxDegree,DGAlgebra)
  Headline
    Computes the maximum homological degree of a DGAlgebra
  Usage
    mDegree = maxDegree(A)
  Inputs
    A:DGAlgebra
  Outputs
    mDegree:ZZ
      The maximum degree of the DGAlgebra A (this can be infinite).
  Description
    Text
      Note that if the DGAlgebra A has any generators of even degree, then maxDegree returns infinity.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
      A = koszulComplexDGA(R)
      B = acyclicClosure(A,EndDegree=>3)
      maxDegree(A)
      maxDegree(B)
///

doc ///
  Key
    isHomologyAlgebraTrivial
    (isHomologyAlgebraTrivial,DGAlgebra)
  Headline
    Determines if the homology algebra of a DGAlgebra is trivial
  Usage
    isTriv = isHomologyAlgebraTrivial(A) 
  Inputs
    A:DGAlgebra
  Outputs
    isTriv:Boolean
  Description
    Text
      This function computes the homology algebra of the DGAlgebra A and determines if the multiplication on H(A) is trivial.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
      S = R/ideal{a^3*b^3*c^3*d^3}
      A = acyclicClosure(R,EndDegree=>3)
      B = A ** S
      isHomologyAlgebraTrivial(B,GenDegreeLimit=>6)
    Text
      The command returns true since R --> S is Golod.  Notice we also used the option GenDegreeLimit here.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
      A = koszulComplexDGA(R)
      isHomologyAlgebraTrivial(A)
    Text
      The command returns false, since R is Gorenstein, and so HA has Poincare Duality, hence the multiplication
      is far from trivial.
///

doc ///
  Key
    isAcyclic
    (isAcyclic,DGAlgebra)
  Headline
    Determines if a DGAlgebra is acyclic.
  Usage
    isAcyc = isAcyclic(A)
  Inputs
    A:DGAlgebra
  Outputs
    isAcyc:Boolean
  Description
    Text
      This function determines if the DGAlgebra is acyclic.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4+b^4+c^4+d^4}
      isAcyclic(koszulComplexDGA R)
    Example
      Q = ZZ/101[a,b,c,d]
      I = ideal {a^4,b^4,c^4,d^4}
      isAcyclic(koszulComplexDGA I)
///

doc ///
  Key
    isGolod
    (isGolod,Ring)
  Headline
    Determines if a ring is Golod
  Usage
    isGol = isGolod(R)
  Inputs
    R:Ring
  Outputs
    isGol:Boolean
  Description
    Text
      This function determines if the Koszul complex of a ring R admits a trivial Massey operation.  If one exists, then R is Golod.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4+b^4+c^4+d^4}
      isGolod(R)
    Text
      Hypersurfaces are Golod, but
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
      isGolod(R)
    Text
      complete intersections of higher codimension are not.  Here is another example:
    Example
      Q = ZZ/101[a,b,c,d]
      R = Q/(ideal vars Q)^2
      isGolod(R)
    Text
      The above is a (CM) ring minimal of minimal multiplicity, hence Golod.  The next example was found
      by Lukas Katthan, and appears in his arXiv paper 1511.04883.  It is the first known example
      of an algebra that is not Golod, but whose Koszul complex has a trivial homology product.
    Example
      Q = ZZ/101[x_1,x_2,y_1,y_2,z,w]
      I = ideal {x_1*x_2^2,z^2*w,y_1*y_2^2,x_2^2*z*w,y_2^2*z^2,x_1*x_2*y_1*y_2,x_2^2*y_2^2*z,x_1*y_1*z}
      R = Q/I
      isHomologyAlgebraTrivial koszulComplexDGA R
      isGolod R
    Text
      Note that since the Koszul complex is zero in homological degree beyond the embedding dimension, there are only finitely
      many Massey products that one needs to check to verify that a ring is Golod.
///

doc ///
  Key
    isGolodHomomorphism
    (isGolodHomomorphism,QuotientRing)
  Headline
    Determines if the canonical map from the ambient ring is Golod
  Usage
    isGol = isGolodHomomorphism(R)
  Inputs
    R:QuotientRing
  Outputs
    isGol:Boolean
  Description
    Text
      This function determines if the canonical map from ambient R --> R is Golod.  It does this by computing an acyclic closure of
      ambient R (which is a @ TO DGAlgebra @), then tensors this with R, and determines if this DG Algebra has a trivial Massey operation
      up to a certain homological degree provided by the option GenDegreeLimit.
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^4+b^4+c^4+d^4}
      isGolodHomomorphism(R,GenDegreeLimit=>5)
    Text
      If R is a Golod ring, then ambient R $\rightarrow$ R is a Golod homomorphism. 
    Example
      Q = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
      R = Q/ideal (a^3*b^3*c^3*d^3)
      isGolodHomomorphism(R,GenDegreeLimit=>5,TMOLimit=>3)
    Text
      The map from Q to R is Golod by a result of Avramov and Levin; we can only find the trivial Massey operations out to a given degree.
///

doc ///
  Key
    getGenerators
    (getGenerators,DGAlgebra)
  Headline
    Returns a list of cycles whose images generate HH(A) as an algebra
  Usage
    cycleList = getGenerators(A)
  Inputs
    A:DGAlgebra
  Outputs
    cycleList:List
  Description
    Text
      This version of the function should only be used if all algebra generators of A are in odd homological degree,
      provided in the EndDegree option.
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3,a^2*b^2*c^2}
      A = koszulComplexDGA(R)
      netList getGenerators(A)
///

doc ///
  Key
    deviations
    (deviations,Ring)
    (deviations,ChainComplex)
    (deviations,RingElement,List)
  Headline
    Computes the deviations of the input ring, complex, or power series.
  Usage
    devTally = deviations(R)
  Inputs
    R:Ring
  Outputs
    devTally:Tally
  Description
    Text
      This command computes the deviations of a @ TO Ring @, a @ TO ChainComplex @, or a power series in the form of a @ TO RingElement @.
      The deviations are the same as the degrees of the generators of the acyclic closure of R, or the degrees of the generators of the
      Tor algebra of R.  This function takes an option called Limit (default value 3) that specifies the largest deviation to compute.
    Example
      R = ZZ/101[a,b,c,d]/ideal {a^3,b^3,c^3,d^3}
      deviations(R)
      deviations(R,DegreeLimit=>4)
      S = R/ideal{a^2*b^2*c^2*d^2}
      deviations(S,DegreeLimit=>4)
      T = ZZ/101[a,b]/ideal {a^2-b^3}
      deviations(T,DegreeLimit=>4)
    Text
      Note that the deviations of T are not graded, since T is not graded.  When calling deviations on a ChainComplex, the
      zeroth free module must be cyclic, and this is checked.  The same goes for the case
      of a RingElement.
    Example
      R = ZZ/101[a,b,c,d]/ideal {a^3,b^3,c^3,d^3}
      A = degreesRing R
      kRes = res coker vars R
      pSeries = poincareN kRes
      devA = deviations(R,DegreeLimit=>5)
      devB = deviations(kRes,DegreeLimit=>5)
      devC = deviations(pSeries,degrees R, DegreeLimit=>5)
      devA === devB and devB === devC
///

doc ///
  Key
    deviationsToPoincare
    (deviationsToPoincare,HashTable)
    [deviationsToPoincare,DegreeLimit]
  Headline
    Computes the power series corresponding to a set of deviations.
  Usage
    pSeries = deviationsToPoincare(devHash)
  Inputs
    devHash:HashTable
      HashTable of the same form as the output from @ TO deviations @
  Outputs
    pSeries:RingElement
  Description
    Text
      This command takes a HashTable of the same form output from @ TO deviations @ and produces the Poincare series corresponding to it.
      The (key,value) pairs must be of the form homologicalDegree=>number or (homologicalDegree,internalDegree)=>number.
      Because 
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
      RDevs = deviations(R,DegreeLimit=>6)
      devPSeries = deviationsToPoincare(RDevs,DegreeLimit=>6)
      pSeries = poincareN (res(coker vars R, LengthLimit=>6))
      substitute(devPSeries,ring pSeries) == pSeries
///

doc ///
  Key
    findTrivialMasseyOperation
    findNaryTrivialMasseyOperation
    (findTrivialMasseyOperation,DGAlgebra)
    (findNaryTrivialMasseyOperation,DGAlgebra,List,HashTable,ZZ)
  Headline
    Finds a trivial Massey operation on a set of generators of H(A)
  Usage
    tmo = findTrivialMasseyOperation(A)
  Inputs
    A:DGAlgebra
  Outputs
    seq:Sequence
      A sequence seq whose first entry reports whether a trivial Massey operation has been found, and the second
      entry is a hash table with keys given by monomials in a generating set of the positive degree homology of
      A and values the element that bounds the Massey product corresponding to that monomial.
  Description
    Text
      This function the element that bounds all potentially nonzero Massey products (before taking homology class).
      The maximum degree of a generating cycle is specified in the option GenDegreeLimit, if needed.
    Text
      Golod rings are defined by being those rings whose Koszul complex K^R has a trivial Massey operation.
      Also, the existence of a trivial Massey operation on a DG algebra A forces the multiplication on H(A)
      to be trivial.  An example of a ring R such that H(K^R) has trivial multiplication, yet K^R does not admit
      a trivial Massey operation is unknown.  Such an example cannot be monomially defined, by a result of
      Jollenbeck and Berglund. 
    Text
      This is an example of a Golod ring.  It is Golod since it is the Stanley-Reisner ideal of a flag complex
      whose 1-skeleton is chordal [Jollenbeck-Berglund].
    Example
      Q = ZZ/101[x_1..x_6]
      I = ideal (x_3*x_5,x_4*x_5,x_1*x_6,x_3*x_6,x_4*x_6)
      R = Q/I
      A = koszulComplexDGA(R)
      isHomologyAlgebraTrivial(A,GenDegreeLimit=>3)
      cycleList = getGenerators(A)
      (hasTMO, tmoSoFar) = findTrivialMasseyOperation(A)
      assert(hasTMO)
    Text
      Below is an example of a Teter ring (Artinian Gorenstein ring modulo its socle), and the computation in Avramov and Levin's
      paper shows that H(A) does not have trivial multiplication, hence no trivial Massey operation can exist.
    Example
      Q = ZZ/101[x,y,z]
      I = ideal (x^3,y^3,z^3,x^2*y^2*z^2)
      R = Q/I
      A = koszulComplexDGA(R)
      isHomologyAlgebraTrivial(A)
      cycleList = getGenerators(A)
      assert(not first findTrivialMasseyOperation(A))
    Text
      The related function @ TO findNaryTrivialMasseyOperation @ find only the nth order trivial Massey operations.
///

doc ///
  Key
    masseyTripleProduct
    (masseyTripleProduct,DGAlgebra,RingElement,RingElement,RingElement)
  Headline
    Computes the Massey triple product of a set of cycles or homology classes
  Usage
    h = masseyTripleProduct(A,h1,h2,h3)
  Inputs
    A:DGAlgebra
    h1:RingElement
    h2:RingElement
    h3:RingElement
  Outputs
    h:RingElement
      The return value is either the homology class of the Massey triple product defined
      by the inputs or a cycle representing the homology class.
  Description
    Text
       These functions compute the Massey triple product of either three homology classes
       or three cycles that represent nonzero homology classes for which the Massey triple product
       is defined.
    Text
       For an example, we return to an example due to Lukas Katthan which was discussed in @ TO isGolod @.
       First, we define the algebra:
    Example
       Q = QQ[x_1,x_2,y_1,y_2,z]
       I = ideal (x_1*x_2^2,y_1*y_2^2,z^3,x_1*x_2*y_1*y_2,y_2^2*z^2,x_2^2*z^2,x_1*y_1*z,x_2^2*y_2^2*z)
       R = Q/I
       KR = koszulComplexDGA R
    Text
       The following are cycles:
    Example
       z1 = z^2*T_5
       z2 = y_2^2*T_3
       z3 = x_2^2*T_1
    Text
       and z1*z2, z2*z3 vanish in homology:
    Example
       (lifted12,lift12) = getBoundaryPreimage(KR,z1*z2)
       (lifted23,lift23) = getBoundaryPreimage(KR,z2*z3)
    Text
       Note that the first return value of @ TO getBoundaryPreimage @ indicates that the inputs
       are indeed boundaries, and the second value is the lift of the boundary along the differential.
    Text
       Given cycles z1,z2,z3 such that z1*z2 and z2*z3 are boundaries, 
       the Massey triple product of the homology classes represented by z1,z2 and z3 
       is the homology class of lift12*z3 + z1*lift23.  To see this, we compute and check:
    Example
       z123 = masseyTripleProduct(KR,z1,z2,z3)
       z123 == lift12*z3 + z1*lift23
    Text
       One may also compute Massey triple products directly on elements of the homology
       algebra itself, as is seen with the command masseyTripleProduct:
    Example
       H = HH(KR)
       h1 = homologyClass(KR,z1)
       h2 = homologyClass(KR,z2)
       h3 = homologyClass(KR,z3)
       h123 = masseyTripleProduct(KR,h1,h2,h3)
       h123 == homologyClass(KR,z123)
///

doc ///
  Key
    (masseyTripleProduct,DGAlgebra,ZZ,ZZ,ZZ)
  Headline
    Computes the matrix representing all triple Massey operations.
  Usage
    mat = masseyTripleProduct(A,l,m,n)
  Inputs
    A:DGAlgebra
    l:ZZ
    m:ZZ
    n:ZZ
  Outputs
    mat:Matrix
  Description
    Text
      Given a triple of homology classes h1,h2,h3, such that h1h2 = h2h3 = 0,
      the Massey triple product of h1,h2 and h3 may be defined as in
      @ TO masseyTripleProduct @.  This command computes a basis of the homology
      algebra of A in degrees l,m and n respectively, and expresses the triple
      Massey operation of each triple, provided it is defined.  If a triple product
      is not defined (i.e. if either h1h2 or h2h3 is not zero) then the triple
      product is reported as zero in the matrix.
    Text
      The following example appears in "On the Hopf algebra of a Local Ring" by Avramov
      as an example of a nonvanishing Massey operation which an algebra generator:
    Example
      Q = QQ[t_1,t_2,t_3,t_4]
      I = ideal (t_1^3,t_2^3,t_3^3-t_1*t_2^2,t_1^2*t_3^2,t_1*t_2*t_3^2,t_2^2*t_4,t_4^2)
      R = Q/I
      KR = koszulComplexDGA R
      H = HH(KR)
      masseys = masseyTripleProduct(KR,1,1,1);
      rank masseys
    Text
      As you can see, this command is useful to determine the number of linearly independent
      elements that arise as triple Massey products.
    Text
      For example, the following Massey triple product is nonvanishing and is an
      algebra generator:
    Example
      masseyTripleProduct(KR,X_2,X_4,X_1)
///

doc ///
  Key
    expandGeomSeries
    (expandGeomSeries,List,ZZ)
    (expandGeomSeries,RingElement,ZZ)
  Headline
    Expand a geometric series to a specified degree.
  Usage
    pSeries = expandGeomSeries(f,n)
  Inputs
    f:RingElement
      Ratio of the geometric series to be expanded.
    n:ZZ
      Degree which to expand the geometric series.
  Outputs
    pSeries:RingElement
      Power series representation of the geometric series.
  Description
    Text
      If the user supplies a list instead of a RingElement as the first argument, the return
      value is the product of all the each of the geometric series expanded to degree n obtained
      by calling expandGeomSeries on each element of the list.
    Example
      A = ZZ[S,T_0,T_1]
      f = expandGeomSeries(S^2*T_0^8,10)
      g = expandGeomSeries(S^4*T_1^15,10)
      h = expandGeomSeries({S^2*T_0^8,S^4*T_1^15},10)
      B = A/(first gens A)^11
      substitute(f*g,B) == h
///

doc ///
  Key
    torMap
    (torMap,RingMap)
    [torMap,GenDegreeLimit]
  Headline
    Compute the map of Tor algebras associated to a RingMap.
  Usage
    torPhi = torMap(phi)
  Inputs
    phi:RingMap
  Outputs
    torPhi:RingMap
  Description
    Text
      The functor Tor_R(M,N) is also functorial in the ring argument.  Therefore, a ring map phi from A to B induces an algebra map
      from the Tor algebra of A to the Tor algebra of B.
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3,a^2*b^2*c^2}
      S = R/ideal{a*b^2*c^2,a^2*b*c^2,a^2*b^2*c}
      f = map(S,R)
      fTor = torMap(f,GenDegreeLimit=>3)
      matrix fTor
    Text
      In the following example, the map on Tor is surjective, which means that the ring homomorphism is large (Dress-Kramer).
    Example
      R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3,a*c,a*d,b*c,b*d}
      S = ZZ/101[a,b]/ideal{a^3,b^3}
      f = map(S,R,matrix{{a,b,0,0}})
      fTor = torMap(f,GenDegreeLimit=>4)
      matrix fTor      
///

doc ///
  Key
    (diff,DGAlgebra,RingElement)
  Headline
    Computes the differential of a ring element in a DGAlgebra
  Usage
    b = diff(A,a)
  Inputs
    A:DGAlgebra
    a:RingElement
  Outputs
    b:RingElement
///

doc ///
  Key
    getBoundaryPreimage
    (getBoundaryPreimage,DGAlgebra,RingElement)
    (getBoundaryPreimage,DGAlgebra,List)
  Headline
    Attempt to find a preimage of a boundary under the differential of a DGAlgebra.
  Usage
    (lifted,myLift) = getBoundaryPreimage(A,z)
  Inputs
    A:DGAlgebra
    z:RingElement
  Outputs
    seq:Sequence
  Description
    Text
      The first element in the return value is a boolean value indicating whether the
      lift was possible.  If true, the second coordinate of the return value is the lift.
      If false, then the second coordinate of the return value is the reduction of the
      input modulo the image.
    Example
       Q = QQ[x_1,x_2,y_1,y_2,z]
       I = ideal (x_1*x_2^2,y_1*y_2^2,z^3,x_1*x_2*y_1*y_2,y_2^2*z^2,x_2^2*z^2,x_1*y_1*z,x_2^2*y_2^2*z)
       R = Q/I
       KR = koszulComplexDGA R
    Text
       The following are cycles:
    Example
       z1 = z^2*T_5
       z2 = y_2^2*T_3
       z3 = x_2^2*T_1
       {diff(KR,z1),diff(KR,z1),diff(KR,z1)}
    Text
       and z1*z2, z2*z3 vanish in homology:
    Example
       (lifted12,lift12) = getBoundaryPreimage(KR,z1*z2)
       (lifted23,lift23) = getBoundaryPreimage(KR,z2*z3)
    Text
       We can check that the differential of the lift is the supposed boundary:
    Example
       diff(KR,lift23) == z2*z3
///

doc ///
  Key
    homologyClass
    (homologyClass,DGAlgebra,RingElement)
  Headline
    Computes the element of the homology algebra corresponding to a cycle in a DGAlgebra.
  Usage
    h = homologyClass(A,z)
  Inputs
    A:DGAlgebra
    z:RingElement
  Outputs
    h:RingElement
  Description
    Text
      This function computes the element in the homology algebra of a cycle in a @ TO DGAlgebra @.
      In order to do this, the @ TO homologyAlgebra @ is retrieved (or computed, if it hasn't been
      already).
    Example
      Q = QQ[x,y,z]
      I = ideal (x^3,y^3,z^3)
      R = Q/I
      KR = koszulComplexDGA R
      z1 = x^2*T_1
      z2 = y^2*T_2
      H = HH(KR)
      homologyClass(KR,z1*z2)
///

doc ///
  Key
    zerothHomology
    (zerothHomology,DGAlgebra)
  Headline
    Compute the zeroth homology of the DGAlgebra A as a ring.
  Usage
    HA0 = zerothHomology A
  Inputs
    A:DGAlgebra
  Outputs
    HA0:Ring
      HH_0(A) as a ring.
///

doc ///
  Key
    getDegNModule
    (getDegNModule,ZZ,Ring,Ring)
  Headline
    Compute a presentation of M_i as an R-module
  Usage
    M' = getDegNModule(N,R,M)
    M' = getDegNModule(N,R,A)
  Inputs
    N:ZZ
    R:Ring
    M:Module
      M is a Module over a ring A, and A must be a graded R-algebra with A_0 = R
    A:Ring
      A must be a graded R-algebra, and A_0 = R
  Outputs
    M':Module
      M_N as an R = A_0-module
///

doc ///
  Key
    DGAlgebraMap
    ringMap
  Headline
    The class of all DG Algebra maps
  Description
    Text
      A common way to create a DGAlgebraMap is via @ TO liftToDGMap @.
///

doc ///
  Key
    dgAlgebraMap
    (isWellDefined,DGAlgebraMap)
    (dgAlgebraMap,DGAlgebra,DGAlgebra,Matrix)
  Headline
    Define a DG algebra map between DG algebras.
  Usage
    phi = dgAlgebraMap(B,A,M)
  Inputs
    A:DGAlgebra
       Source
    B:DGAlgebra
       Target
    M:Matrix
       A matrix representing where the generators of A should be mapped to (akin to ringMap)
  Outputs
    phi:DGAlgebraMap
  Description
    Example
      R = ZZ/101[a,b,c]/ideal{a^3+b^3+c^3,a*b*c}
      K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
      K2 = koszulComplexDGA(ideal {b,c},Variable=>"T")
      g = dgAlgebraMap(K1,K2,matrix{{Y_2,Y_3}})
      isWellDefined g
    Text
      The function does not check that the DG algebra map is well defined, however.
    Example
      f = dgAlgebraMap(K2,K1,matrix{{0,T_1,T_2}})
      isWellDefined f
///

doc ///
  Key
    toComplexMap
    (toComplexMap,DGAlgebraMap)
    (toComplexMap,DGAlgebraMap,ZZ)
    [toComplexMap,AssertWellDefined]
    [toComplexMap,EndDegree]
  Headline
    Construct the ChainComplexMap associated to a DGAlgebraMap
  Usage
    psi = toComplexMap phi
  Inputs
    phi:DGAlgebraMap
  Outputs
    psi:ChainComplexMap
  Description
    Example
       R = ZZ/101[a,b,c]/ideal{a^3+b^3+c^3,a*b*c}
       K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
       K2 = koszulComplexDGA(ideal {b,c},Variable=>"T")
       g = dgAlgebraMap(K1,K2,matrix{{Y_2,Y_3}})
       g' = toComplexMap g
    Text
      The option @ TO EndDegree @ must be specified if the source of phi has any algebra generators of even degree.  The option @ TO AssertWellDefined @
      is used if one wishes to assert that the result of this computation is indeed a chain map.  One can construct just the nth map in the
      chain map by providing the second @ TO ZZ @ parameter.
    Text
      This function also works when working over different rings, such as the case when the @ TO DGAlgebraMap @ is produced via
      @ TO liftToDGMap @ and in the next example.  In this case, the target module is produced via @ TO pushForward @.
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
      S = R/ideal{a^2*b^2*c^2}
      f = map(S,R)
      A = acyclicClosure(R,EndDegree=>3)
      B = acyclicClosure(S,EndDegree=>3)
      phi = liftToDGMap(B,A,f)
      toComplexMap(phi,EndDegree=>3)
///

doc ///
  Key
    liftToDGMap
    (liftToDGMap,DGAlgebra,DGAlgebra,RingMap)
    [liftToDGMap,EndDegree]
  Headline
    Lift a ring homomorphism in degree zero to a DG algebra morphism
  Usage
    phiTilde = liftToDGMap(B,A,phi)
  Inputs
    B:DGAlgebra
      Target
    A:DGAlgebra
      Source
    phi:RingMap
      Map from A in degree zero to B in degree zero
  Outputs
    phiTilde:DGAlgebraMap
      DGAlgebraMap lifting phi to a map of DGAlgebras.
  Description
    Text
      In order for phiTilde to be defined, phi of the image of the differential of A in degree 1 must lie in the image of the
      differential of B in degree 1.  At present, this condition is not checked.
    Example
      R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
      S = R/ideal{a^2*b^2*c^2}
      f = map(S,R)
      A = acyclicClosure(R,EndDegree=>3)
      B = acyclicClosure(S,EndDegree=>3)
      phi = liftToDGMap(B,A,f)
      toComplexMap(phi,EndDegree=>3)
///

doc ///
  Key
    (homology,DGAlgebraMap)
  Headline
    Computes the homomorphism in homology associated to a DGAlgebraMap.
  Usage
    homologyPhi = homology(phi,n)
  Inputs
    phi:DGAlgebraMap
  Outputs
    homologyPhi:RingMap
      The map on homology defined by phi.
  Description
    Example
      R = ZZ/101[a,b,c]/ideal{a^3+b^3+c^3,a*b*c}
      K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
      K2 = koszulComplexDGA(ideal {b,c},Variable=>"T")
      f = dgAlgebraMap(K2,K1,matrix{{0,T_1,T_2}})
      g = dgAlgebraMap(K1,K2,matrix{{Y_2,Y_3}})
      toComplexMap g
      HHg = HH g
///

-- moved this out of the documentation since it is not complete
--    (homology,DGAlgebraMap,ZZ)

--    Text
--      One can also supply the second argument (a ZZ) in order to obtain the map on homology in a specified degree.
--      (This is currently not available).

doc ///
  Key
    dgAlgebraMultMap
    (dgAlgebraMultMap,DGAlgebra,RingElement)
  Headline
    Returns the chain map corresponding to multiplication by a cycle.
  Usage
    phi = dgAlgebraMultMap(A,z)
  Inputs
    A:DGAlgebra
    z:RingElement
  Outputs
    phi:ChainComplexMap
  Description
    Text
      If A is a DGAlgebra, and z is a cycle of A, then left multiplication of A by z gives
      a chain map from A to A.  This command converts A to a complex using @ TO toComplex @,
      and constructs a @ TO ChainComplexMap @ that represents left multiplication by z.
      This command is used to determine the module structure that is computed in
      @ TO homologyModule @.
    Example
      R = QQ[x,y,z]/ideal{x^3,y^3,z^3}
      KR = koszulComplexDGA R
      z1 = x^2*T_1
      phi = dgAlgebraMultMap(KR,z1)
    Text
      As you can see, the degree of phi is the homological degree of z:
    Example
      degree phi == first degree z
    Text
      Care is also taken to ensure the resulting map is homogeneous if R and z are:
    Example
      isHomogeneous phi
    Text
      One may then view the action of multiplication by the homology class of z upon
      taking the induced map in homology:
    Example
      Hphi = prune HH(phi); (Hphi#0,Hphi#1,Hphi#2)
///

doc ///
  Key
    homologyModule
    (homologyModule,DGAlgebra,Module)
  Headline
    Compute the homology of a DGModule as a module over a DGAlgebra.
  Usage
    HM = homologyModule(A,M)
  Inputs
    A:DGAlgebra
    M:Module
  Outputs
    HM:Module
  Description
    Text
      Given a DGAlgebra A over a ring R, and an R-module M, A ** M carries the structure
      of a left DG module over A.  It follows that H(A ** M) is a module over H(A).
      Although DGModules have yet to be implemented as objects in Macaulay2 in their own right,
      the current infrastructure (with a little extra work) allows us to determine the module structure
      of this type of DG module as a module over the homology algebra of A.
    Text
      Currently, this code will only work on DGAlgebras that are finite over their ring
      of definition, such as Koszul complexes.  (Truncations of) module structures in case
      of non-finite DGAlgebras may be made available in a future update.
    Text
      For an example, we will compute the module structure of the Koszul homology of
      the canonical module over the Koszul homology algebra.
    Example
      Q = QQ[x,y,z,w]
      I = ideal (w^2, y*w+z*w, x*w, y*z+z^2, y^2+z*w, x*y+x*z, x^2+z*w)
      R = Q/I
      KR = koszulComplexDGA R
      cxKR = toComplex KR
      HKR = HH(KR)
    Text
      The following is the graded canonical module of R:
    Example
      degList = first entries vars Q / degree / first
      M = Ext^4(Q^1/I,Q^{-(sum degList)}) ** R
    Text
      We obtain the Koszul homology module using the following command:
    Example
      HKM = homologyModule(KR,M);
    Text
      One may notice the duality of HKR and HKM by considering their Hilbert series:
    Example
      hsHKR = value numerator reduceHilbert hilbertSeries HKR
      hsHKM = value numerator reduceHilbert hilbertSeries HKM
      AA = ring hsHKR
      e = numgens Q
      hsHKR == T_0^e*T_1^e*sub(hsHKM, {T_0 => T_0^(-1), T_1 => T_1^(-1)})
///

doc ///
  Key
    (source,DGAlgebraMap)
  Headline
    Outputs the source of a DGAlgebraMap
  Usage
    A = source phi
  Inputs
    phi:DGAlgebraMap
  Outputs
    A: DGAlgebra
///

doc ///
  Key
    (target,DGAlgebraMap)
  Headline
    Outputs the target of a DGAlgebraMap
  Usage
    A = target phi
  Inputs
    phi:DGAlgebraMap
  Outputs
    A: DGAlgebra
///

doc ///
  Key
    AssertWellDefined
  Headline
    Option to check whether the lifted map on DGAlgebras is well defined.
  Usage
    liftToDGMap(...,AssertWellDefined=>true)
///

doc ///
  Key
    StartDegree
  Headline
    Option to specify the degree to start computing the acyclic closure and killing cycles
  Usage
    acyclicClosure(...,StartDegree=>n)
///

doc ///
  Key
    EndDegree
  Headline
    Option to specify the degree to stop computing killing cycles and acyclic closure 
  Usage
    killCycles(...,StartDegree=>n)
///

doc ///
  Key
    GenDegreeLimit
  Headline
    Option to specify the maximum degree to look for generators
  Usage
    homologyAlgebra(...,GenDegreeLimit=>n)
///

doc ///
  Key
    RelDegreeLimit
  Headline
    Option to specify the maximum degree to look for relations
  Usage
    homologyAlgebra(...,RelDegreeLimit=>n)
///

doc ///
  Key
    TMOLimit
  Headline
    Option to specify the maximum arity of the trivial Massey operation
  Usage
    findTrivialMasseyOperation(...,TMOLimit=>n)
///

doc ///
  Key
    [isAcyclic,EndDegree]
  Headline
    Option to specify the degree to finish checking acyclicity
  Usage
    isAcyclic(...,EndDegree=>n)
///

doc ///
  Key
    [acyclicClosure,StartDegree]
  Headline
    Option to specify the degree to start computing the acyclic closure. 
  Usage
    acyclicClosure(...,StartDegree=>n)
  Description 
   Text
    The default value is 1.
///

doc ///
  Key
    [acyclicClosure,EndDegree]
  Headline
    Option to specify the degree to stop computing the acyclic closure
  Usage
    A =  acyclicClosure(B,EndDegree=>n)
  Inputs
    B: DGAlgebra
    n: ZZ
  Description
   Text
    All generators of the acyclic closure will be found up to homological degree n.
    Note that B can be an ordinary ring, a factor ring of a polynomial ring, treated as
    a DGAlgebra generated in homological degree 0, as in the following example:
    
    The default value of EndDegree is 3.
   Example
    R = ZZ/101[x,y,z,w]/(ideal"x3,y3,z3,x2yz")
    acyclicClosure R
    acyclicClosure(R,EndDegree => 3)
///

doc ///
  Key
    [getGenerators,StartDegree]
  Headline
    Option to specify the degree to start finding generators of HH(DGAlgebra)
  Usage
    getGenerators(...,StartDegree=>n)
///

doc ///
  Key
    [getGenerators,DegreeLimit]
  Headline
    Option to specify the degree to stop finding generators of HH(DGAlgebra)
  Usage
    getGenerators(...,DegreeLimit=>n)
///

doc ///
  Key
    [killCycles,StartDegree]
  Headline
    Option to specify the degree to start looking for cycles
  Usage
    killCycles(...,StartDegree=>n)
///

doc ///
  Key
    [killCycles,EndDegree]
  Headline
    Option to specify the degree to stop looking for cycles
  Usage
    killCycles(...,EndDegree=>n)
///

doc ///
  Key
    [getBasis,Limit]
  Headline
    Option to specify the maximum number of basis elements to return
  Usage
    getBasis(...,Limit=>n)
///

doc ///
  Key
    [homologyAlgebra,GenDegreeLimit]
  Headline
    Option to specify the maximum degree to look for generators
  Usage
    homologyAlgebra(...,GenDegreeLimit=>n)
///

doc ///
  Key
    [homologyAlgebra,RelDegreeLimit]
  Headline
    Option to specify the maximum degree to look for relations
  Usage
    homologyAlgebra(...,RelDegreeLimit=>n)
///

doc ///
  Key
    [torAlgebra,GenDegreeLimit]
  Headline
    Option to specify the maximum degree to look for generators
  Usage
    torAlgebra(...,GenDegreeLimit=>n)
///

doc ///
  Key
    [torAlgebra,RelDegreeLimit]
  Headline
    Option to specify the maximum degree to look for relations
  Usage
    torAlgebra(...,RelDegreeLimit=>n)
///

doc ///
  Key
    [findTrivialMasseyOperation,GenDegreeLimit]
  Headline
    Option to specify the maximum degree to look for generators
  Usage
    findTrivialMasseyOperation(...,GenDegreeLimit=>n)
///

doc ///
  Key
    [findTrivialMasseyOperation,TMOLimit]
  Headline
    Option to specify the maximum arity of a trivial Massey operation, if one exists.
  Usage
    findTrivialMasseyOperation(...,TMOLimit=>n)
///

doc ///
  Key
    [isHomologyAlgebraTrivial,GenDegreeLimit]
  Headline
    Option to specify the maximum degree to look for generators
  Usage
    isHomologyAlgebraTrivial(...,GenDegreeLimit=>n)
///

doc ///
  Key
    [isGolodHomomorphism,GenDegreeLimit]
  Headline
    Option to specify the maximum degree to look for generators
  Usage
    isGolodHomomorphism(...,GenDegreeLimit=>n)
///

doc ///
  Key
    [deviations,DegreeLimit]
  Headline
    Option to specify the maximum degree to look for generators when computing the deviations
  Usage
    deviations(...,DegreeLimit=>n)
///

doc ///
  Key
    [isGolodHomomorphism,TMOLimit]
  Headline
    Option to specify the maximum degree to look for generators when computing the deviations
  Usage
    isGolodHomomorphism(...,DegreeLimit=>n)
///

doc ///
  Key
    [homologyAlgebra,Verbosity]
  Headline
    Option to specify the maximum degree to look for generators when computing the deviations
  Usage
    homologyAlgebra(...,DegreeLimit=>n)
///

doc ///
  Key
    [getGenerators,Verbosity]
  Headline
    Option to specify the maximum degree to look for generators when computing the deviations
  Usage
    getGenerators(...,Verbosity=>n)
///

doc ///
  Key
    (net,DGAlgebra)
  Headline
    Outputs the pertinent information about a DGAlgebra
  Usage
    net A
  Inputs
    A:DGAlgebra
///

doc ///
  Key
    (net,DGAlgebraMap)
  Headline
    Outputs the pertinent information about a DGAlgebraMap
  Usage
    net phi
  Inputs
    phi:DGAlgebraMap
///

doc///
 Key 
  displayBlockDiff
  (displayBlockDiff, DGAlgebra, ZZ)
  (displayBlockDiff, DGAlgebra, Array, Array)
  (displayBlockDiff, DGAlgebra, List, List)
  (displayBlockDiff, DGAlgebra, VisibleList)  
 Headline
  Shows natural decomposition of a map in the Tate resolution
 Usage
  displayBlockDiff (A, n)
  displayBlockDiff (A, LA, LA)
  displayBlockDiff (A, L, L)
  displayBlockDiff (A, V)
 Inputs
  A:DGAlgebra
   of the sort produced by @TO acyclicClosure@ or @TO killCycles@
  n:ZZ
  LA:Array
     of the form [L] where L is a list representing a multi-index in the complex
  L:List
   of indices as above
  V:VisibleList
   a list, array or sequence representing a (single) multi-index in the complex defined by A
 Description
  Text
   For example, consder the first five steps in the resolution of the residue field
   in the following example:
  Example
   R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
   G = betti res (coker vars R, LengthLimit => 5)
  Text
   It is a free graded-commutative divided power algebra on generator of each degree starting with 1.
   We can compute the beginning of the complex corresponding to the first 3 factors with as
  Example
   A = acyclicClosure(R,EndDegree => 2)
   F = toComplex(A, 5);
   betti F
   betti F
  Text
   Since we gave @TO acyclicClosure @ the EndDegree 2, the complex produced is exact up to
   step 2; that is betti F and betti G agree up to the column 3. There are 3 chunks of generators
   in A, of homological degrees 1,2,3. 
  
   From the betti table of F 
   we see the three generators of the exterior algebra K as the linear part of the resolution.
   The second strand, 4,12,12,4 is the tensor product of K with the 4 generators of A that have
   homological degree 2 and correspond to the 4 generators of I; thus they have internal degree 3.
   We also see that A has three generators in homological degree 3 and internal degree 5.
   K multiplied by these generators accounts for 3,9,9 of the 3rd strand, while the symmetric
   square of the 4 generators of homological degree 2,  account for 0,10,0 and the product of
   these with the generators of degree 1 account for the remaining 0,0,30. Finally the 12
   in the 4th row represents the product of the 4 generators of A in homological degree with
   the 3 in degree 3.
  
   The native display of the differentials of this complex does not distinguish these pieces,
   but displayBlockDiff allows one to look at them in various ways:
  Example
   F.dd_3
   displayBlockDiff(A,3)
  Text
   Here the triples of numbers represent the number of factors from the generators of each of the three chunks of
   variables: the first index gives the number from the Koszul complex, the second from the 4 variable in homological degree
   2 and the third the number from the 3 variables of homological degree 3. Thus the sum of the
   three indices is the homological degree. The sources of the blocks are listed on the top row, the targets
   are given by the columns.
   We can see the lists of indices of the source and target with
    Example
     indices source blockDiff(A,5)
     indices target blockDiff(A,5)
  Text
   We can extract one or several blocks from F.dd_3 as follows:
  Example
   R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
   A = acyclicClosure(R,EndDegree => 2)
  Text
   We can specify a lists of source and target degrees, either as lists representing an 
   index for th source and an index for the target:
  Example
   displayBlockDiff(A,  {0,2,3}, {0,4,0})
  Text
   or as a pair of arrays of such lists
  Example
   displayBlockDiff(A,  [{0,2,3}], [{1,0,3},{0,4,0}])
  Text
   or look at all the blocks with a given target summand with:
  Example
   displayBlockDiff(A,  (1,0,3) )
 SeeAlso
  blockDiff
  acyclicClosure
///

doc ///
 Key
  blockDiff
  (blockDiff, DGAlgebra, ZZ)
 Headline
  prepares a map for display
 Usage
  X = blockDiff(A,n)
 Inputs
  A: DGAlgebra
  n: ZZ
 Outputs
  X:Matrix
   map between labeled direct sums
 Description
  Text
   Use @TO displayBlockDiff@ to display the blocks in various ways.
 SeeAlso
  displayBlockDiff 
///
  
-------------------------------
--          Testing          --
-------------------------------
TEST ///
R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
A = acyclicClosure(R,EndDegree => 2)
X = blockDiff(A,3)
Y = blockDiff(A,2)
--reconstruct a composite block:
assert(0 == Y_[{2,0,0}]^[{1,0,0}] *X^[{2,0,0}]_[{1,2,0}] + Y_[{0,2,0}]^[{1,0,0}] *X^[{0,2,0}]_[{1,2,0}] )

///
TEST ///
-- test 0 : isHomogeneous, toComplex, maxDegree
R = ZZ/101[x,y,z]
A1 = freeDGAlgebra(R,{{1},{1},{1},{3}})
setDiff(A1,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
assert(not A1.isHomogeneous)
A1dd = toComplex(A1)
A1dd.dd

A2 = freeDGAlgebra(R,{{1,1},{1,1},{1,1},{3,3}})
setDiff(A2,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
assert(A2.isHomogeneous)
A2dd = toComplex(A2)
A2dd.dd

B1 = koszulComplexDGA(R)
assert(B1.isHomogeneous)
B1dd = toComplex(B1)
B1dd.dd

R = ZZ/101[x,y,z]
R2 = R/ideal {x^2-z^3}
B2 = koszulComplexDGA(R2)
assert(not B2.isHomogeneous)
B2dd = toComplex(B2)
B2dd.dd

R = QQ[x,y,z]
B = koszulComplexDGA(R)
toComplex(B)
degrees B.natural
A = freeDGAlgebra(R,{{1},{1},{1},{3}})
setDiff(A,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
Add = toComplex(A)
assert(apply(maxDegree(A)+1, i -> prune HH_i(Add)) == {coker vars R,0,0,coker vars R,0,0,0})
///

TEST ///
-- test 1 : differential tests
R = ZZ/101[x,y,z, Degrees => {2,2,3}]
kRes = res coker vars R
kRes.dd_3
A = koszulComplexDGA(R)
d3 = A.dd_3
d2 = A.dd_2
d1 = A.dd_1
assert(source d1 == target d2)
assert(source d2 == target d3)
assert(d1*d2 == 0)
assert(d2*d3 == 0)
S1 = R/ideal (x^3-z^2)
B1 = koszulComplexDGA(S1)
d3 = B1.dd_3
d2 = B1.dd_2
d1 = B1.dd_1
assert(source d1 == target d2)
assert(source d2 == target d3)
assert(d1*d2 == 0)
assert(d2*d3 == 0)
use R
S2 = R/ideal (x^4-z^2)
B2 = koszulComplexDGA(S2)
d3 = B2.dd_3
d2 = B2.dd_2
d1 = B2.dd_1
assert(source d1 == target d2)
assert(source d2 == target d3)
assert(d1*d2 == 0)
assert(d2*d3 == 0)
///

TEST ///
--- test 2 : homology, homologyAlgebra, HH_ZZ, HH
R = ZZ/32003[a,b,x,y]/ideal{a^3,b^3,x^3,y^3,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^2}
koszulR = koszul vars R
time apply(5,i -> numgens prune HH_i(koszulR))
A = koszulComplexDGA(R)
HH_2(A)
HH(A)
hh2 = prune HH_2(koszulR)
hh2' = prune HH_2(A)
assert(hh2 == hh2')
///

TEST ///
-- test 3 : torAlgebra, deviations
R1 = QQ[x,y,z]/ideal{x^3,y^4,z^5}
TorR1 = torAlgebra(R1,GenDegreeLimit=>4)
devR1 = deviations(R1,DegreeLimit=>4)
use R1
M = coker matrix {{x^2*y^3*z^4}}
Mres = res(M, LengthLimit => 7)
R2 = QQ[x,y,z]/ideal{x^3,y^4,z^5,x^2*y^3*z^4}
time TorR1R2 = torAlgebra(R1,R2,GenDegreeLimit=>5,RelDegreeLimit=>10)
-- the multiplication is trivial, since the map R3 --> R4 is Golod
numgens TorR1R2
numgens ideal TorR1R2
apply(21, i -> #(flatten entries getBasis(i,TorR1R2)))
assert(sum oo - 1 == numgens TorR1R2)
///

TEST ///
-- test 4 : findEasyRelations
debug DGAlgebras
R1 = ZZ/32003[a,b,x,y]/ideal{a^3,b^3,x^3,y^3,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^2}
R2 = ZZ/32003[a,b,x,y,Degrees=>{1,1,2,2}]/ideal{a^3,b^3,x^3,y^3,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^2}
A1 = koszulComplexDGA(R1)
A2 = koszulComplexDGA(R2)
cycleList1 = getGenerators(A1,DegreeLimit=>4)
cycleList2 = getGenerators(A2,DegreeLimit=>4)
HAEasy1 = findEasyRelations(A1,cycleList1)
HAEasy2 = findEasyRelations(A2,cycleList2)
tally ((flatten entries basis HAEasy1) / degree)
pairs (tally ((flatten entries basis HAEasy1) / degree))
myList1 = {({4,8},1),({3,4},1),({3,5},6),({3,6},6),({3,7},4),({2,3},4),({2,4},11),({2,5},8),({2,6},4),({1,2},4),({1,3},4),({1,4},1),({0,0},1)}
myList2 = {({0},1),({1},9),({2},27),({3},17),({4},1)}
tally ((flatten entries basis HAEasy1) / degree)
tally myList1
assert(pairs tally((flatten entries basis HAEasy1) / degree) == myList1)
assert(pairs tally((flatten entries basis HAEasy2) / degree) == myList2)
///

TEST ///
-- test 5 : homology of a DGA whose H_0 is not a field
R = ZZ/32003[a,b]
I = ideal{a^6,b^6}
A = koszulComplexDGA(I)
HA = HH A
describe HA
use R
J = I + ideal {a^4*b^5,a^5*b^4}
B = koszulComplexDGA(J)
getGenerators(B)
apply(5, i -> numgens prune homology(i,B))
apply(5, i -> prune homology(i,B))
HB = HH B
HB2 = zerothHomology B
HB.cache.cycles
ideal HB
-- looks right...
getDegNModule(0,HB2,HB)
getDegNModule(1,HB2,HB)
getDegNModule(2,HB2,HB)
getDegNModule(3,HB2,HB)
getDegNModule(4,HB2,HB)

R = ZZ/32003[a,b,c]
I = (ideal vars R)^2
A = koszulComplexDGA(I)
apply(10, i -> prune homology(i,A))
time HA = HH A
HA2 = zerothHomology A
tally ((ideal HA)_* / degree / first)
select ((ideal HA)_*, f -> first degree f == 2)
-- looks right...
getDegNModule(0,HA2,HA)
getDegNModule(1,HA2,HA)
getDegNModule(2,HA2,HA)
getDegNModule(3,HA2,HA)
-- need to add asserts
///

TEST ///
-- test 6 : homologyAlgebra
R = ZZ/32003[a,b,x,y]/ideal{a^3,b^3,x^3,y^3,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^2}
koszulR = koszul vars R
time apply(5,i -> numgens prune HH_i(koszulR))
A = koszulComplexDGA(R)
time apply(5,i -> numgens prune homology(i,A))
-- ~2.15 seconds on mbp, with graded differentials
time HA = HH A
assert(numgens HA == 34)
assert(numgens ideal HA == 576)
assert(#(first degrees HA) == 2)

-- same example, but not graded because of the degree change.  The homologyAlgebra function
-- will then only return a graded algebra
R2 = ZZ/32003[a,b,x,y,Degrees=>{1,1,2,2}]/ideal{a^3,b^3,x^3,y^3,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^2}
koszulR2 = koszul vars R2
time apply(5,i -> numgens prune HH_i(koszulR2))
A2 = koszulComplexDGA(R2)
time apply(5,i -> numgens prune homology(i,A2))
-- ~2.85 seconds on mbp, with ungraded differentials
time HA2 = homologyAlgebra A2
assert(numgens HA2 == 34)
assert(numgens ideal HA2 == 576)
-- should only be singly graded
assert(#(first degrees HA2) == 1)
///

TEST ///
-- test 7 : acyclicClosure, isHomologyAlgebraTrivial, isGolod, isGolodHomomorphism
R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
M = coker matrix {{a^3*b^3*c^3*d^3}};
S = R/ideal{a^3*b^3*c^3*d^3}
time A = acyclicClosure(R,EndDegree=>6)
B = A ** S
assert(isHomologyAlgebraTrivial(B,GenDegreeLimit=>6))
assert(isGolodHomomorphism(S,GenDegreeLimit=>6,TMOLimit=>3))
-- returns true since R --> S is Golod
R = ZZ/101[a,b,c,d]/ideal{a^4,b^4,c^4,d^4}
A = koszulComplexDGA(R)
assert(not isHomologyAlgebraTrivial(A))
assert(not isGolod R)
-- false, since R is Gorenstein, and so HA has Poincare Duality
///

TEST ///
-- test 8 : DGAlgebra ** DGAlgebra - need to add in an assert
R = ZZ/101[a,b,c,d]
I = ideal(a,b)
J = ideal(c,d)
A = koszulComplexDGA(I)
B = koszulComplexDGA(J)
Cdd = toComplex(A ** B)
Cdd.dd
///

TEST ///
-- test 9 : isHomologyAlgebraTrivial, getGenerators, findTrivialMasseyOperation
Q = ZZ/101[x_1..x_6]
I = ideal (x_3*x_5,x_4*x_5,x_1*x_6,x_3*x_6,x_4*x_6)
R = Q/I
A = koszulComplexDGA(R)
isHomologyAlgebraTrivial(A,GenDegreeLimit=>3)
cycleList = getGenerators(A)
assert(first findTrivialMasseyOperation(A))

-- this is a Teter ring, and the computation in Avramov and Levin's paper shows
-- H(A) does not have trivial multiplication.
Q = ZZ/101[x,y,z]
I = ideal (x^3,y^3,z^3,x^2*y^2*z^2)
R = Q/I
A = koszulComplexDGA(R)
assert(not isHomologyAlgebraTrivial(A,GenDegreeLimit=>3))
cycleList = getGenerators(A)
prodList = apply(subsets(cycleList,2), l -> (first degree l#0 + first degree l#1,l#0*l#1));
assert(not first findTrivialMasseyOperation(A))
///

TEST ///
-- test 10 : isAcyclic
R = ZZ/101[a,b,c,d]
A = koszulComplexDGA(R)
B = koszulComplexDGA({a^4,b^4,c^4,d^4})
C = koszulComplexDGA((ideal vars R)^2)
assert(isAcyclic A)
assert(isAcyclic B)
assert(not isAcyclic C)
///

TEST ///
-- test 11 : isGolod and isHomologyAlgebraTrivial example
-- Interesting case due to Katthan.
Q = ZZ/101[x_1,x_2,y_1,y_2,z,w]
I = ideal {x_1*x_2^2,z^2*w,y_1*y_2^2,x_2^2*z*w,y_2^2*z^2,x_1*x_2*y_1*y_2,x_2^2*y_2^2*z,x_1*y_1*z}
R = Q/I
assert(isHomologyAlgebraTrivial koszulComplexDGA R == true)
assert(isGolod R == false)
///

TEST ///
--- test 12: isGolod and isHomologyAlgebraTrivial example again
--- This example is due to Roos
S = QQ[x,y,z,u]
I = ideal(u^3, x*y^2, (x+y)*z^2, x^2*u+z*u^2, y^2*u+x*z*u, y^2*z+y*z^2)
 -- you can see that the mult on the koszul homology will be trivial
betti (A = res I)
R = S/I
assert(isHomologyAlgebraTrivial koszulComplexDGA R == true)
assert(isGolod R == false)
///

end--

-- How to install the package
restart
loadPackage("DGAlgebras", Reload =>true)
restart
uninstallPackage "DGAlgebras"
restart
installPackage "DGAlgebras"


check "DGAlgebras"
viewHelp DGAlgebras



-- Below, we provide some of the examples used in development, unsupported
-- and undocumented for the user.

--- homologyModule code
restart
debug needsPackage "DGAlgebras"
Q = QQ[x,y,z,w]
I = ideal (w^2, y*w+z*w, x*w, y*z+z^2, y^2+z*w, x*y+x*z, x^2+z*w)
R = Q/I
KR = koszulComplexDGA R
cxKR = toComplex KR
HKR = HH(KR)
degList = first entries vars Q / degree / first
-- This is the (graded) canonical module of M
M = Ext^4(Q^1/I,Q^{-(sum degList)}) ** R
KM = cxKR ** M
HKM = homologyModule(KR,M)
reduceHilbert hilbertSeries HKR
reduceHilbert hilbertSeries HKM

--- massey operations code
restart
needsPackage "DGAlgebras"
Q = QQ[x_1,x_2,y_1,y_2,z]
I = ideal (x_1*x_2^2,y_1*y_2^2,z^3,x_1*x_2*y_1*y_2,y_2^2*z^2,x_2^2*z^2,x_1*y_1*z,x_2^2*y_2^2*z)
R = Q/I
KR = koszulComplexDGA R
z1 = z^2*T_5
z2 = y_2^2*T_3
z3 = x_2^2*T_1
getBoundaryPreimage(KR,z1*z2)
getBoundaryPreimage(KR,z2*z3)
z123 = masseyTripleProduct(KR,z1,z2,z3)
H = HH(KR)
h1 = homologyClass(KR,z1)
h2 = homologyClass(KR,z2)
h3 = homologyClass(KR,z3)
--- BUG - need to check the h's are in the homology algebra
h123 = masseyTripleProduct(KR,h1,h2,h3)
h123 == homologyClass z123

-- Bug from the Macaulay2 Google Group
restart
needsPackage "DGAlgebras"
R = ZZ/32003[t, Inverses=>true, MonomialOrder=>RevLex]
I = ideal t^2
A = koszulComplexDGA(I)
skewList = select(toList(0..(#degList-1)), i -> odd first degList#i)
(A.ring)[varsList, Degrees=>{{1}}, Join => false, SkewCommutative => skewList]
(A.ring)[varsList]
-- so the DGAlgebras package does not work over local rings?

-- Demo
-- 'Finite' DGAlgebras: the Koszul Complex
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
KR = koszulComplexDGA(R)
toComplex KR
KR.dd
HKR = HH KR
describe HKR
peek HKR.cache

S = R/ideal{a^2*b^2*c^2*d^2}
KS = koszulComplexDGA(S)
HKS = HH KS;
numgens HKS
numgens ideal HKS
(ideal HKS)_*
peek HKS.cache

-- (potentially) infinite partial DGAlgebras : acyclic closures
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
XR = acyclicClosure(R)
apply(gens XR.natural, x -> XR.diff(x))

S = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3,a^2*b^2*c^2*d^2}
XS = acyclicClosure(S)
netList apply(gens XS.natural, x -> XS.diff(x))

-- homotopy fiber of projection R --> S
homotopyFiber = XR ** S
homologyHomotopyFiber = HH homotopyFiber
homologyHomotopyFiber = homologyAlgebra( homotopyFiber, GenDegreeLimit=>4, RelDegreeLimit=>8)
numgens homologyHomotopyFiber
numgens ideal homologyHomotopyFiber
peek homologyHomotopyFiber.cache

-- maps on Tor algebras of rings
phi = map(S,R)
torPhi = torMap phi
matrix torPhi
-- Note: need to add ker for ring maps from skew rings :)
ker torPhi

-- lifting ring maps to DG maps
phiTilde = liftToDGMap(XS,XR,phi)
apply(gens XR.natural, x -> phiTilde.natural x)

-- deviations
restart
loadPackage "DGAlgebras"
R1 = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3}
R2 = ZZ/101[a,b,c,d]/ideal{a^3,b^3,c^3,d^3,a^2*b^2*c^2*d^2}
devR1 = deviations R1
devR2 = deviations R2
poincR2 = deviationsToPoincare devR2
coefficients(poincR2, Variables=>{first gens ring poincR2})
res coker vars R2
R3 = ZZ/101[a,b,c,d,Degrees=>entries id_(ZZ^4)]/ideal{a^3,b^3,c^3,d^3,a^2*b^2*c^2*d^2}
degrees R3
devR3 = deviations R3
-- bug here!
deviationsToPoincare devR3

-- Golod DG Algebras and trivial Massey operations
restart
loadPackage "DGAlgebras"
Q = ZZ/101[x_1..x_6]
I = ideal (x_3*x_5,x_4*x_5,x_1*x_6,x_3*x_6,x_4*x_6)
R = Q/I
A = koszulComplexDGA(R)
isHomologyAlgebraTrivial A
cycleList = getGenerators(A)
tmo = findTrivialMasseyOperation(A)

-- stub function documentation node
doc ///
  Key
    myStub
    (myStub,DGAlgebra)
  Headline
    Stub headline
  Usage
    tmo = myStub(A)
  Inputs
    A:DGAlgebra
  Outputs
    tmo:List
      Stub Output
  Description
    Text
      This is a stub.
    Example
      Q = ZZ/101[x_1..x_6]
      I = ideal (x_3*x_5,x_4*x_5,x_1*x_6,x_3*x_6,x_4*x_6)
    Text
      More stub.
///

--stub option node

doc ///
  Key
    StubOption
  Headline
    Stub Option Headline
  Usage
    homologyAlgebra(...,StubOption=>n)
///

-- JSAG examples
restart
needsPackage "DGAlgebras";
R = ZZ/101[a,b,c]/ideal{a^3,b^3,a^2*b^2};
KR = koszulComplexDGA R
HKR = HH KR;
gens HKR
ideal HKR

-- trying to make pushForward functorial
restart
loadPackage "DGAlgebras"
R = ZZ/101[x,y,z]
S = R[w]/ideal{w^3,x*w^2}
f = map(S,R)
M = matrix {{x + x*w + w^2,w},{0,w^2}}
A = source M
B = target M
Mpush = pushForward(f,M)
-- test functoriality of pushForward
kSRes = res(coker matrix {{x,y,z,w}}, LengthLimit=>5)
kSRes1push = pushForward(f,kSRes.dd_1)
kSRes2push = pushForward(f,kSRes.dd_2)
kSRes3push = pushForward(f,kSRes.dd_3)
kSRes1push*kSRes2push
kSRes2push*kSRes3push
prune homology(kSRes1push,kSRes2push)
prune homology(kSRes2push,kSRes3push)
-- pushforward the ChainComplex
kSResPush = pushForward(f,kSRes)
prune HH kSResPush

-- lifting functions
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c]/ideal{a^3,b^3,c^3}
S = R/ideal{a^2*b^2*c^2}
f = map(S,R)
A = acyclicClosure(R,EndDegree=>3)
time(B = acyclicClosure(S,EndDegree=>3))
phi = liftToDGMap(B,A,f)
-- can't do the following yet, since M2 can't handle maps between chain complexes
-- over different rings. (Should pushforward the target and then try?)
toComplexMap(phi,EndDegree=>3)

-- torMap
restart
loadPackage "DGAlgebras"
printWidth=74
R = ZZ/101[a,b]/ideal{a^3,b^3,a^2*b^2}
S = R/ideal{a*b^2,a^2*b}
f = map(S,R)
Torf = torMap(f,GenDegreeLimit=>3);
TorR = source Torf
TorS = target Torf
Torf

-- below is a good test, since the target is less complicated than the source
-- which is backwards than the usual behavior
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c]/ideal{a^2,b^2,c^2,d^2,a*c,a*d,b*c,b*d}
S = ZZ/101[a,b]/ideal{a^2,b^2}
f = map(S,R,matrix{{a,b,0,0}})
Torf = torMap(f,GenDegreeLimit=>4);
TorR = source Torf;
TorS = target Torf;
-- note the homomorphism is large
matrix Torf

-- DGAlgebraMap Testing
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c]/ideal{a^3+b^3+c^3,a*b*c}
K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
K2 = koszulComplexDGA(ideal {b,c},Variable=>"T")
f = dgAlgebraMap(K2,K1,matrix{{0,T_1,T_2}})
isWellDefined f
g = dgAlgebraMap(K1,K2,matrix{{Y_2,Y_3}})
isWellDefined g
source g
target g
toComplexMap g
HHg = HH g

restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c]/ideal{a^3+b^3+c^3,a*b*c}
K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
K2 = koszulComplexDGA(ideal vars R,Variable=>"Z")
g = dgAlgebraMap(K2,K1,matrix{{Z_1,Z_2,Z_3}})
isWellDefined g
HH g

-- change of rings DGAlgebraMap currently does not work yet, since M2 expects
-- matrices to be defined between free modules over the same ring.  Need to use
-- pushForward (for ChainComplex; not yet written) for this to work.
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b,c]/ideal{a^2+b^2+c^2}
K1 = koszulComplexDGA(ideal vars R,Variable=>"Y")
S = ZZ/101[a,b]/ideal{a^2+b^2}
K2 = koszulComplexDGA(ideal vars S,Variable=>"T")
f = dgAlgebraMap(K2,K1,matrix{{T_1,T_2,0}})
isWellDefined f
toComplexMap f

-- Some examples and things that will eventually make it into the program and documentation.
------------------
--- Taylor's resolution code
restart
loadPackage "DGAlgebras"
R = ZZ/101[a,b]
I = monomialIdeal (a^2,a*b,b^2)
degList = reverse {{1,2,0},{1,1,1},{1,0,2},{2,2,1},{2,2,2},{2,1,2},{3,2,2}}
skewList = toList select(0..#degList-1, i -> odd first degList#i)
A = R[t123,t23,t13,t12,t3,t2,t1,MonomialOrder=>{4,3},SkewCommutative=>skewList, Degrees=>degList]/ideal(a*t12-t1*t2, t13-t1*t3, b*t23-t2*t3, a*t123-t1*t23, a*b*t123+t2*t13, b*t123-t12*t3,t12^2,t23^2,t12*t1,t12*t2,t23*t2,t23*t3,t123*t1,t123*t2,t123*t3,t123*t12,t123*t13,t123*t23,t12*t23)
-- above is how to represent the algebra in M2; not really a better way to do it.
basis(A)
I = sub(ideal (a^4,b^4),A)
B = A/I
basis(B)
-- note that the command basis(A) does not return the desired answer.  There are two problems.
-- first of all, it thinks that the module is not finite over the base (R), even though it is.
-- secondly, if we add in a^n and b^n to make it finite over ZZ/101, the answer given is not a basis -
--   the basis should be 1,t1,t2,t3,t12,t13,t23,t123 (should not have t1*t2, t1*t3, etc)
-- Note that A is a free R-module, with basis t1,t2,t3,t12,t13,t23,t123.
-- How can we get this basis in general, at least in the case that A is a free R-module?
--------------

--Tutorial (Include in a separate file?)
-- Koszul Complex and homology algebras
restart
loadPackage "DGAlgebras"
debug DGAlgebras
R1 = ZZ/32003[x,y,z]
A1 = koszulComplexDGA(R1)
A1Complex = toComplex A1
A1Complex.dd
HA1 = homologyAlgebra(A1)
HA1 = HH A
describe HA1
R2 = R1/ideal{x^3,y^4,z^5}
A2 = koszulComplexDGA(R2)
time HA2 = homologyAlgebra(A2)
describe HA2
reduceHilbert hilbertSeries HA2
use R1
R3 = R1/ideal{x^3,y^4,z^5,x^2*y^3*z^4}
A3 = koszulComplexDGA(R3)
time HA3 = homologyAlgebra(A3)
describe HA3
reduceHilbert hilbertSeries HA3

restart
loadPackage "DGAlgebras"
Q = QQ[x,y,z]
I = ideal{y^3,z*x^2,y*(z^2+y*x),z^3+2*x*y*z,x*(z^2+y*x),z*y^2,x^3,z*(z^2+2*x*y)}
R = Q/I
ann ideal vars R
A = koszulComplexDGA(R)
time HA = homologyAlgebra(A)
reduceHilbert hilbertSeries HA
ann ideal vars HA

-- the following example baffles me.  The 'same' ideal is Gorenstein in characteristic 2, and Golod in characteristic 32003 (probably)
Q2 = ZZ/2[x,y,z]
f_1 = x^3*y + x^3*z + x*z^3+y*z^3
f_2 = x*y^3+y^3*z+x*z^3+y*z^3
f_3 = x*y^2*z+x*y*z^2+x*y^3+x^3*y+x*z^3+x^3*z
f_4 = x^2*y*z+x*y^2*z+x^3*z+x*z^3+y^3*z+y*z^3
f_5 = x^4+y^4+z^4+x^2*y^2+x^2*z^2+y^2*z^2+x^2*y*z+x*y^2*z+x*y*z^2+x^3*y+x^3*z
I2 = ideal{f_1,f_2,f_3,f_4,f_5}
R2 = Q2/I2
ann ideal vars R2
A2 = koszulComplexDGA(R2)
time HA2 = homologyAlgebra(A2)
reduceHilbert hilbertSeries HA2
ann ideal vars HA2

Q = ZZ/32003[x,y,z]
f_1 = x^3*y + x^3*z + x*z^3+y*z^3
f_2 = x*y^3+y^3*z+x*z^3+y*z^3
f_3 = x*y^2*z+x*y*z^2+x*y^3+x^3*y+x*z^3+x^3*z
f_4 = x^2*y*z+x*y^2*z+x^3*z+x*z^3+y^3*z+y*z^3
f_5 = x^4+y^4+z^4+x^2*y^2+x^2*z^2+y^2*z^2+x^2*y*z+x*y^2*z+x*y*z^2+x^3*y+x^3*z
I = ideal{f_1,f_2,f_3,f_4,f_5}
R = Q/I
ann ideal vars R
A = koszulComplexDGA(R)
time HA = homologyAlgebra(A)
-- should check HA by hand since the homology algebra is still monomial.
reduceHilbert hilbertSeries HA
isHomologyAlgebraTrivial(A)
ann ideal vars HA

-- fiber product example
restart
loadPackage "DGAlgebras"
R = ZZ/32003[a,b,x,y]/ideal{a^3,b^3,x^3,y^4,a*x,a*y,b*x,b*y}
apply((numgens R) + 1, i -> numgens prune HH_i(koszul vars R))
A = koszulComplexDGA(R)
-- 1.17 seconds on mbp
time HA = homologyAlgebra(A)
HA.cache.cycles
socHAgens = (ann ideal vars HA)_*
-- kill all elements of the socle of the 'wrong degree'
-- the generators we are killing are elements in W from the theorem,
-- and are zero b/c they are part of a trivial extension.  The
-- others are actual problem elements that are actually zero in the
-- connected sum.
HB = HA / ideal (select(socHAgens, i -> first degree i < 4))
-- identify the generators of the right degree
HB = HB / ideal (X_7*X_25-X_5*X_24)
-- now have a PD algebra.
mingens ann ideal vars HB
-- now we trivially extend by a graded vector space, as well as its dual to get a new PD algebra, the
-- Koszul homology algebra of a connected sum (computed below)
reduceHilbert hilbertSeries HA
reduceHilbert hilbertSeries HB
peek HA.cache

-- ungraded connected sum example
restart
loadPackage "DGAlgebras"
R = ZZ/32003[a,b,x,y]/ideal{a^3,b^3,x^3,y^4,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^3}
koszulR = koszul vars R
time apply(5,i -> numgens prune HH_i(koszulR))
A = koszulComplexDGA(R)
-- 3.8 seconds on mbp 
-- error here now - not sure why
time HA = homologyAlgebra(A)
socHA = ideal getBasis(4,HA)
HA.cache.cycles
reduceHilbert hilbertSeries HA
socHA = ideal getBasis(4,HA)
ann ideal vars HA
peek HA.cache

-- connected sum example
restart
loadPackage "DGAlgebras"
R = ZZ/32003[a,b,x,y]/ideal{a^3,b^3,x^3,y^3,a*x,a*y,b*x,b*y,a^2*b^2-x^2*y^2}
koszulR = koszul vars R
time apply(5,i -> numgens prune HH_i(koszulR))
A = koszulComplexDGA(R)
-- 2.7 seconds on mbp, with graded differentials
time HA = homologyAlgebra(A)
reduceHilbert hilbertSeries HA

-- connected sum example
-- goal: get this example to run quickly
restart
loadPackage "DGAlgebras"
R2 = ZZ/32003[a,b,x,y,z]/ideal{a^4,b^4,x^3,y^3,z^3,a*x,a*y,a*z,b*x,b*y,b*z,a^3*b^3-x^2*y^2*z^2}
A2 = koszulComplexDGA(R2)
time apply(6, i -> numgens prune homology(i,A2))
koszulR2 = koszul vars R2
time apply(6,i -> numgens prune HH_i(koszulR2))
-- 56 seconds on mbp
time HA2 = homologyAlgebra(A2)
numgens HA2
numgens ideal HA2
tally ((flatten entries basis HA2) / degree)
tally (((flatten entries basis HA2) / degree) / first)

-- This toric algebra is CM and not Koszul or Golod.  Is its homology algebra trivial?
restart
loadPackage "DGAlgebras"
R = QQ[x_1..x_6]/ideal(x_2^2-x_1*x_4,x_3^2-x_2*x_5,x_3*x_4-x_1*x_6,x_4^2-x_3*x_5,x_5^2-x_2*x_6)
A = koszulComplexDGA(R)
HA = HH A
isHomologyAlgebraTrivial(A)
-- no.

-- This algebra is not Golod, since its Poincare series is irrational.  But is its homology algebra trivial?
restart
loadPackage "DGAlgebras"
Q = QQ[a,b,c,d,e,f,g,h,i]
I = ideal (h^2-a*i,g^2-c*h,f^2-e*g,e*f-b*h,e^2-d*g,d*e-a*h,d^2-c*e,c*g-a*h,c*d-b*f,c^2-a*g,b*d-a*f,b^2-a*c)
res coker gens I
oo.dd
R = Q/I
A = koszulComplexDGA(R)
isHomologyAlgebraTrivial(A)
-- no.

-- connected sum example
-- goal: get this example to run quicker?
restart
loadPackage "DGAlgebras"
gbTrace = 2
R2 = ZZ/32003[a,b,c,x,y,z]/ideal{a^3,b^3,c^3,x^3,y^3,z^3,a*x,a*y,a*z,b*x,b*y,b*z,c*x,c*y,c*z,a^2*b^2*c^2-x^2*y^2*z^2}
A2 = koszulComplexDGA(R2)
time apply(7, i -> numgens prune homology(i,A2))
koszulR2 = koszul vars R2
time apply(7,i -> numgens prune HH_i(koszulR2))
time HA2 = homologyAlgebra(A2)
tally ((flatten entries basis HA2) / degree)
tally (((flatten entries basis HA2) / degree) / first)
-- 146 generators and 10662 relations (at least; didn't forceGB properly when I ran it before)

-- Tate resolution, toComplex
restart
loadPackage "DGAlgebras"
debug DGAlgebras
R = QQ[x,y,z,w]/ideal{x^3,y^4,z^5}
A = acyclicClosure(R,EndDegree=>1)
time Add = toComplex(A,20);
time kRes = res(coker vars R, LengthLimit => 20)

-- Homology
restart
loadPackage "DGAlgebras"
R3 = QQ[x,y,z]/ideal{x^3,y^4,z^5}
A3 = acyclicClosure(R3,EndDegree=>1)
time apply(7, i -> time numgens prune homology(i,A3))
time kRes = res(coker vars R3, LengthLimit=> 18)
time apply(17, i -> time HH_i(kRes));

-- Tor algebras
restart
loadPackage "DGAlgebras"
R3 = QQ[x,y,z]/ideal{x^3,y^4,z^5}
time TorR3 = torAlgebra(R3)
apply(16, i -> hilbertFunction(i,TorR3))
time res(coker vars R3, LengthLimit => 15)
R4 = QQ[x,y,z]/ideal{x^3,y^4,z^5,x^2*y^3*z^4}
TorR4 = torAlgebra(R4,GenDegreeLimit=>8)
apply(10, i -> hilbertFunction(i,TorR4))
res(coker vars R4, LengthLimit => 9)
TorR3R4 = torAlgebra(R3,R4,GenDegreeLimit=>4,RelDegreeLimit=>10)
reduceHilbert hilbertSeries TorR3R4
use R3
R4mod = coker matrix {{x^2*y^3*z^4}}
res(R4mod, LengthLimit => 6)

-- Acyclic closures
restart
loadPackage "DGAlgebras"
R3 = ZZ/32003[x,y]/ideal{x^3,y^4,x^2*y^3}
time A3 = acyclicClosure(R3,EndDegree=>5)
time HA3 = homologyAlgebra(A3,GenDegreeLimit=>6,RelDegreeLimit=>12)
time apply(12, i -> #(flatten entries getBasis(i,HA3)))
-- need to check the mult structure from Lucho's book.

-- The examples below are related to work in progress by Berest-Khatchatryan-Ramadoss on derived representation varieties
-- CC[x,y]; n = 2
restart
loadPackage "DGAlgebras"
debug DGAlgebras
R = QQ[x_11,x_12,x_21,x_22,y_11,y_12,y_21,y_22]
A = freeDGAlgebra(R,{{1,2},{1,2},{1,2}})
setDiff(A,{x_12*y_21 - x_21*y_12,
	   x_21*y_11+x_22*y_21-x_11*y_21-x_21*y_22,
	   x_11*y_12+x_12*y_22-x_12*y_11-x_22*y_12})
homList = apply(5,i -> numgens prune homology(i,A))
HA = homologyAlgebra(A)
HA2 = zerothHomology A
describe HA
describe HA2
degrees HA
degrees HA2
M1 = getDegNModule(0,HA2,HA)
reduceHilbert hilbertSeries M1
dim M1
M2 = getDegNModule(1,HA2,HA)
reduceHilbert hilbertSeries M2
dim M2
K = koszul vars HA2
-- is HA2 CM?
prune HH(K)
-- is M CM?
prune HH(K ** M)
getDegNModule(2,HA2,HA)

-- U(sl_2) example
restart
loadPackage "DGAlgebras"
debug DGAlgebras
x = symbol x; y = symbol y; z = symbol z; T = symbol T;
n = 2
pairsList = toList (set(1..n)**set(1..n))
symbolList = var -> apply(pairsList, i -> var_i)
R = QQ[a,b,symbolList x, symbolList y, symbolList z, Degrees=>{0,0}|toList (3*n^2:1)]
A = freeDGAlgebra(R,toList (((n^2*3):{1,2}) | (n^2:{2,3})))
tDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> x_(p#0,i)*T_(p#1+2*(i-1)) - T_(2*(p#0-1)+i)*x_(i,p#1) +
	                                                        y_(p#0,i)*T_(n^2+p#1+2*(i-1)) - T_(n^2+2*(p#0-1)+i)*y_(i,p#1) +
	                                                        z_(p#0,i)*T_(2*n^2+p#1+2*(i-1)) - T_(2*n^2+2*(p#0-1)+i)*z_(i,p#1)))
netList tDiffList
xDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> a*y_(p#0,i)*z_(i,p#1) + b*z_(p#0,i)*y_(i,p#1) + (1-a-b)*x_(p#0,i)*x_(i,p#1)))
netList xDiffList
yDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> a*z_(p#0,i)*x_(i,p#1) + b*x_(p#0,i)*z_(i,p#1) + (1-a-b)*y_(p#0,i)*y_(i,p#1)))
netList yDiffList
zDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> a*x_(p#0,i)*y_(i,p#1) + b*y_(p#0,i)*x_(i,p#1) + (1-a-b)*z_(p#0,i)*z_(i,p#1)))
netList zDiffList
allDiffs = xDiffList | yDiffList | zDiffList | tDiffList
netList allDiffs
#allDiffs
setDiff(A, allDiffs, InitializeDegreeZeroHomology => false)
complexA = toComplex(A,5)
-- will not finish
numgens HH_0(complexA)
numgens HH_1(complexA)
numgens HH_2(complexA)
numgens HH_3(complexA)
numgens HH_4(complexA)

-- CC[x,y,z] n=2 example
restart
loadPackage "DGAlgebras"
debug DGAlgebras
x = symbol x; y = symbol y; z = symbol z; T = symbol T;
n = 2
pairsList = toList (set(1..n)**set(1..n))
symbolList = var -> apply(pairsList, i -> var_i)
R = QQ[symbolList x, symbolList y, symbolList z, Degrees=>toList (3*n^2:1)]
A = freeDGAlgebra(R,toList (((n^2*3):{1,2}) | (n^2:{2,3})))
tDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> x_(p#0,i)*T_(p#1+2*(i-1)) - T_(2*(p#0-1)+i)*x_(i,p#1) +
	                                                        y_(p#0,i)*T_(n^2+p#1+2*(i-1)) - T_(n^2+2*(p#0-1)+i)*y_(i,p#1) +
	                                                        z_(p#0,i)*T_(2*n^2+p#1+2*(i-1)) - T_(2*n^2+2*(p#0-1)+i)*z_(i,p#1)))
netList tDiffList
xDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> y_(p#0,i)*z_(i,p#1) - z_(p#0,i)*y_(i,p#1)))
netList xDiffList
yDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> z_(p#0,i)*x_(i,p#1) - x_(p#0,i)*z_(i,p#1)))
netList yDiffList
zDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> x_(p#0,i)*y_(i,p#1) - y_(p#0,i)*x_(i,p#1)))
netList zDiffList
allDiffs = xDiffList | yDiffList | zDiffList | tDiffList
netList allDiffs
-- I checked carefully that the code above generates the proper differentials.
setDiff(A, allDiffs, InitializeComplex => false)
H0Ring = zerothHomology A
dim H0Ring
K = koszul vars H0Ring
-- is H0Ring CM?
prune HH(K)
-- is M CM?
H1 = prune HH_1(A); numgens H1
H2 = prune HH_2(A); numgens H2
H3 = prune HH_3(A); numgens H3
H4 = prune HH_4(A); numgens H4
H5 = prune HH_5(A); numgens H5
-- compute the homology algebra?
-- degree zero is not a field, so the GB trick no longer works.  Think about a better way of doing this?
HA = homologyAlgebra(A, GenDegreeLimit=>2, RelDegreeLimit=>4);
netList select(flatten entries gens ideal HA, f -> first degree f == 2)
tally apply(degrees ideal HA, i -> first i)
tally apply(degrees HA, i -> first i)
M = getDegNModule(1,H0Ring,HA)
prune HH(K ** M)
genList = getGenerators(A,DegreeLimit=>5);

-- CC[x,y] n = 3 example
restart
loadPackage "DGAlgebras"
debug DGAlgebras
x = symbol x; y = symbol y; T = symbol T;
n = 3
pairsList = toList (set(1..n)**set(1..n))
symbolList = var -> apply(pairsList, i -> var_i)
R = QQ[symbolList x, symbolList y, Degrees=>toList (2*n^2:1)]
A = freeDGAlgebra(R,toList ((n^2):{1,2}))
tDiffList = apply(pairsList, p -> sum apply(toList (1..n), i -> x_(p#0,i)*y_(i,p#1) - y_(p#0,i)*x_(i,p#1)))
netList tDiffList
setDiff(A, tDiffList, InitializeComplex => false)
homologyList = apply(5,i -> numgens prune homology(i,A))
H0 = HH_0(A); numgens H0
H1 = prune HH_1(A); numgens H1
H2 = prune HH_2(A); numgens H2
H3 = prune HH_3(A); numgens H3
H4 = prune HH_4(A); numgens H4
H5 = prune HH_5(A); numgens H5
H6 = prune HH_6(A); numgens H6
H7 = prune HH_7(A); numgens H7
H8 = prune HH_8(A); numgens H8
H9 = prune HH_9(A); numgens H9
genList = getGenerators(A, DegreeLimit=>3)
HA = homologyAlgebra(A)

-- CC[x]/x^2 n=2
restart
loadPackage "DGAlgebras"
debug DGAlgebras
x = symbol x; T = symbol T;
n = 2
pairsList = toList (set(1..n)**set(1..n))
symbolList = var -> apply(pairsList, i -> var_i)
R = QQ[symbolList x, Degrees=>toList (n^2:1)]
A = freeDGAlgebra(R,toList ((n^2):{1,2}))
tDiffList = apply(pairsList, p -> x_(p#0,1)*x_(1,p#1) + x_(p#0,2)*x_(2,p#1))
netList tDiffList
setDiff(A, tDiffList, InitializeComplex => false)
HA = HH(A)
homologyList = apply(5,i -> numgens prune homology(i,A))
H0 = HH_0(A); numgens H0
H1 = prune HH_1(A); numgens H1
H2 = prune HH_2(A); numgens H2
H3 = prune HH_3(A); numgens H3
H4 = prune HH_4(A); numgens H4
H5 = prune HH_5(A); numgens H5
H6 = prune HH_6(A); numgens H6
H7 = prune HH_7(A); numgens H7

-- CC[x]/x^2 n=3
restart
loadPackage "DGAlgebras"
debug DGAlgebras
x = symbol x; T = symbol T;
n = 3
pairsList = toList (set(1..n)**set(1..n))
symbolList = var -> apply(pairsList, i -> var_i)
R = QQ[symbolList x, Degrees=>toList (n^2:1)]
A = freeDGAlgebra(R,toList ((n^2):{1,2}))
tDiffList = apply(pairsList, p -> x_(p#0,1)*x_(1,p#1) + x_(p#0,2)*x_(2,p#1)+ x_(p#0,3)*x_(3,p#1))
netList tDiffList
setDiff(A, tDiffList, InitializeComplex => false)
HA = HH(A)
homologyList = apply(5,i -> numgens prune homology(i,A))
H0 = HH_0(A); numgens H0
H1 = prune HH_1(A); numgens H1
H2 = prune HH_2(A); numgens H2
H3 = prune HH_3(A); numgens H3
H4 = prune HH_4(A); numgens H4
H5 = prune HH_5(A); numgens H5
H6 = prune HH_6(A); numgens H6
H7 = prune HH_7(A); numgens H7


------
--- From Google Group
restart
needsPackage "DGAlgebras"
R = ZZ/101[x,y,z]
A = freeDGAlgebra(R,{{1},{1},{1},{3}})
A.natural
setDiff(A,{x,y,z,x*T_2*T_3-y*T_1*T_3+z*T_1*T_2})
isHomogeneous(A)



restart
uninstallPackage "DGAlgebras"
restart
installPackage "DGAlgebras"
check "DGAlgebras"
debug needsPackage "DGAlgebras"
loadPackage ("DGAlgebras", Reload => true)
--load "blockDiff.m2"


R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
A = acyclicClosure(R,EndDegree => 2)
displayBlockDiff(A,2)
X = blockDiff(A,2)
X^[{1,0,0}]_[{2,0,0}]
X^[{1,0,0}]_[{0,2,0}]
X = blockDiff(A,5)

-- this is the differential returned by DGAlgebras by default
polyDifferential(7,A)




restart
debug needsPackage "DGAlgebras"
load "blockDiff.m2"

R = QQ[x,y,z]/(ideal(x^3,y^3,z^3))
A = acyclicClosure(R)

-- another example
R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
A = acyclicClosure(R,EndDegree => 3)

-- this displays the differential with headings corresponding to the (i,j) and (p,q)
displayBlockDiff(A,7)
-- this creates the matrix as a "matrix of matrices"
elapsedTime X = blockDiff(A,9);
-- which one may then select blocks using X^[...]_[...]
X^[0]
X^[0]_[0]
-- this is the differential returned by DGAlgebras by default
polyDifferential(7,A)


--- most recent example:
restart
needsPackage "DGAlgebras"
R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
A = acyclicClosure(R,EndDegree => 2)
displayBlockDiff(A,5)
X = blockDiff(A,5)
indices source X
indices target X
Y = X^[{1,0,3}]_[{0,2,3}]  -- works now!
X^[{1,0,3},{0,4,0}]_[{0,2,3}]
tar = [{1,0,3},{0,4,0}]
sour = [{0,2,3}] 
X_tar^sour
isHomogeneous Y
degrees source Y
degrees target Y


///
R = QQ[x,y,z]/(ideal(x^3,y^3,z^3,x*y*z))
A = acyclicClosure(R,EndDegree => 2)
L = indices source blockDiff(A,5)
displayBlockDiff(A,  [{0,2,3}], [{1,0,3},{0,4,0}])
displayBlockDiff(A,  {0,2,3}, {0,4,0})
displayBlockDiff(A,  {1,0,3})
displayBlockDiff(A,  [{1,0,3}])

///
displayBlockDiff(DGAlgebra, BasicList) := (A,L) -> displayBlockDiff(A,L)

new Array from [{1}]
