From 042d29a5bf258d65d2b80f47c4fddf04cd414b27 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Thu, 26 Oct 2023 15:58:06 +0200 Subject: [PATCH 01/11] First implementation of twisted chain complex --- src/sage/topology/simplicial_set.py | 165 ++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py index 3ee14ae2da8..646522510e7 100644 --- a/src/sage/topology/simplicial_set.py +++ b/src/sage/topology/simplicial_set.py @@ -3772,6 +3772,171 @@ def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, return ChainComplex(differentials, degree_of_differential=-1, check=check) + + def twisted_chain_complex(self, twisting_operator, dimensions=None, augmented=False, + cochain=False, verbose=False, subcomplex=None, + check=False): + r""" + Return the normalized chain complex twisted by some operator. + + A twisting operator is a map from the set of simplices to some algebra. + The differentials are then twisted by this operator. + + INPUT: + + - ``twisting_operator`` -- a dictionary, associating the twist of each + simplex. + + - ``dimensions`` -- if ``None``, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. + + - ``augmented`` (optional, default ``False``) -- if ``True``, + return the augmented chain complex (that is, include a class + in dimension `-1` corresponding to the empty cell). + + - ``cochain`` (optional, default ``False``) -- if ``True``, + return the cochain complex (that is, the dual of the chain + complex). + + - ``verbose`` (optional, default ``False``) -- ignored. + + - ``subcomplex`` (optional, default ``None``) -- if present, + compute the chain complex relative to this subcomplex. + + - ``check`` (optional, default ``False``) -- If ``True``, make + sure that the chain complex is actually a chain complex: + the differentials are composable and their product is zero. + + The normalized chain complex of a simplicial set is isomorphic + to the chain complex obtained by modding out by degenerate + simplices, and the latter is what is actually constructed + here. + + EXAMPLES:: + + sage: W = simplicial_sets.Sphere(1).wedge(simplicial_sets.Sphere(2)) + sage: W.nondegenerate_simplices() + [*, sigma_1, sigma_2] + sage: s1 = W.nondegenerate_simplices()[1] + sage: L. = LaurentPolynomialRing(QQ) + sage: tw = {s1:t} + sage: ChC = W.twisted_chain_complex(tw) + sage: ChC.differential(1) + [-1 + t] + sage: ChC.differential(2) + [0] + + """ + from sage.homology.chain_complex import ChainComplex + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + + def twist(s): + if s in twisting_operator: + return twisting_operator[s] + if s.dimension() > 1: + return twist(self.face_data()[s][-1]) + return 1 + base_ring = cm.common_parent(*twisting_operator.values()) + + if dimensions is None: + if not self.cells(): # Empty + if cochain: + return ChainComplex({-1: matrix(base_ring, 0, 0)}, + degree_of_differential=1) + return ChainComplex({0: matrix(base_ring, 0, 0)}, + degree_of_differential=-1) + dimensions = list(range(self.dimension() + 1)) + else: + if not isinstance(dimensions, (list, tuple, range)): + dimensions = list(range(dimensions - 1, dimensions + 2)) + else: + dimensions = [n for n in dimensions if n >= 0] + if not dimensions: + # Return the empty chain complex. + if cochain: + return ChainComplex(base_ring=base_ring, degree=1) + else: + return ChainComplex(base_ring=base_ring, degree=-1) + + differentials = {} + # Convert the tuple self._data to a dictionary indexed by the + # non-degenerate simplices. + if subcomplex: + X = self.quotient(subcomplex) + face_data = X.face_data() + nondegens = X.nondegenerate_simplices() + else: + face_data = self.face_data() + nondegens = self.nondegenerate_simplices() + # simplices: dictionary indexed by dimension, values the list + # of non-degenerate simplices in that dimension. + simplices = {} + for sigma in nondegens: + if sigma.dimension() in simplices: + simplices[sigma.dimension()].append(sigma) + else: + simplices[sigma.dimension()] = [sigma] + first = dimensions.pop(0) + if first in simplices: + rank = len(simplices[first]) + current = sorted(simplices[first]) + else: + rank = 0 + current = [] + if augmented and first == 0: + differentials[first-1] = matrix(base_ring, 0, 1) + differentials[first] = matrix(base_ring, 1, rank, + [1] * rank) + else: + differentials[first] = matrix(base_ring, 0, rank) + + for d in dimensions: + old_rank = rank + faces = {_[1]: _[0] for _ in enumerate(current)} + if d in simplices: + current = sorted(simplices[d]) + rank = len(current) + # old_rank: number of simplices in dimension d-1. + # faces: list of simplices in dimension d-1. + # rank: number of simplices in dimension d. + # current: list of simplices in dimension d. + if not faces: + differentials[d] = matrix(base_ring, old_rank, rank) + else: + matrix_data = {} + for col, sigma in enumerate(current): + sign = 1 + twists = len(face_data[sigma]) * [1] + twists[0] = twist(sigma) + for (ch,tau) in zip(twists,face_data[sigma]): + if tau.is_nondegenerate(): + row = faces[tau] + if (row, col) in matrix_data: + matrix_data[(row, col)] += sign*ch + else: + matrix_data[(row, col)] = sign*ch + sign *= -1 + + differentials[d] = matrix(base_ring, old_rank, + rank, matrix_data) + + else: + rank = 0 + current = [] + differentials[d] = matrix(base_ring, old_rank, rank) + + if cochain: + new_diffs = {} + for d in differentials: + new_diffs[d-1] = differentials[d].transpose() + return ChainComplex(new_diffs, degree_of_differential=1, + check=check) + return ChainComplex(differentials, degree_of_differential=-1, + check=check) + @cached_method def algebraic_topological_model(self, base_ring=None): r""" From 00827742c4b9cd264868843a73e34a2e2515cfef Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Thu, 9 Nov 2023 14:38:28 +0100 Subject: [PATCH 02/11] first prototype --- src/sage/topology/simplicial_set.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py index 646522510e7..f707c9407fa 100644 --- a/src/sage/topology/simplicial_set.py +++ b/src/sage/topology/simplicial_set.py @@ -258,8 +258,10 @@ import copy +from sage.matrix.special import identity_matrix from sage.misc.cachefunc import cached_method from sage.misc.fast_methods import WithEqualityById +from sage.misc.flatten import flatten from sage.misc.lazy_import import lazy_import from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ @@ -3937,6 +3939,97 @@ def twist(s): return ChainComplex(differentials, degree_of_differential=-1, check=check) + def twisted_homology(self, n): + r""" + The `n`-th twisted homology module of the simplicial set with respect to + the abelianization of the fundamental_group. + + It is a module over a Laurent + polynomial ring, that, if the fundamental group is abelian, coincides + with the homology of the universal cover. + + INPUT: + + - ``n`` - a positive integer. + """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + G, d = self._universal_cover_dict() + abelG = G.abelianization_map().codomain() + abelinv = abelG.abelian_invariants() + RP = PolynomialRing(ZZ, 'x', 2 * len(abelinv)) + I = RP.ideal([RP.gen(i)**j-1 for i,j in enumerate(abelinv)]) + J = RP.ideal([RP.gen(2*a)*RP.gen(2*a+1) - 1 for a in range(RP.ngens()//2)]) + GB = (I+J).groebner_basis() + GBI = RP.ideal(GB) + def reduce_laurent(a): + return a._singular_().reduce(GBI)._sage_() + def group_to_polynomial(el, RP): + res = RP.one() + for a in el.Tietze(): + if a > 0: + res *= RP.gen(2*a-2) + else: + res*= RP.gen(-2*a-1) + return res + nd = {a:group_to_polynomial(G.abelianization_map()(b), RP) for a,b in d.items()} + CC = self.twisted_chain_complex(nd) + def mkernel(M): + if M.nrows() == 0 or M.ncols() == 0: + return M + res = M + n = res.ncols() + for g in (I+J).gens(): + res = res.stack(g*identity_matrix(n)) + syz = res.T._singular_().syz()._sage_() + trimmed = syz.T.submatrix(0,0,syz.ncols(),M.nrows()) + trimmed = trimmed.apply_map(reduce_laurent) + to_delete = [i for (i,r) in enumerate(trimmed.rows()) if not r] + return trimmed.delete_rows(to_delete) + def lift_to_submodule(S, M): + if S.nrows() == 0 or S.ncols() == 0: + return S + res = M + for g in GB: + res = res.stack(g*identity_matrix(M.ncols())) + singres = res.T._singular_().lift(S.T._singular_())._sage_() + return singres.submatrix(0,0,M.nrows(),S.nrows()) + def mgb(M): + if M.nrows() == 0 or M.ncols() == 0: + return M + res = M + for g in GB: + res = res.stack(g*identity_matrix(M.ncols())) + sres = res.T._singular_().std()._sage_().T + to_delete = [i for i, r in enumerate(sres.apply_map(reduce_laurent)) if not r] + return sres.delete_rows(to_delete) + M2 = border_matrix(n+1) + M1 = CC.differential(n).T + M2 = CC.differential(n + 1).T + if M1.nrows() == 0: + return (RP**0).quotient_module([]) + K = mkernel(M1) + DK = mkernel(K) + if M2.nrows() > 0: + S = lift_to_submodule(M2, K) + if S.nrows() > 0 and S.ncols() > 0: + res = mgb(DK.stack(S.T)) + else: + res = DK + else: + res = mgb(DK.T) + resmat = mgb(res.apply_map(reduce_laurent)) + L = LaurentPolynomialRing(ZZ, 'x', len(abelinv)) + h = RP.hom(flatten([[g, g**-1] for g in L.gens()]),codomain = L) + laumat = resmat.apply_map(h) + laumat = laumat.change_ring(L) + if laumat.ncols() > 0: + for g in I.gens(): + laumat = laumat.stack(h(g)*identity_matrix(L, laumat.ncols())) + AM = L ** K.nrows() + SM = AM.submodule(laumat) + return AM.quotient_module(SM) + @cached_method def algebraic_topological_model(self, base_ring=None): r""" From 50c9a3886f2ffe5e4d1e7231a3e316369ef5f6b7 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Fri, 10 Nov 2023 09:55:31 +0100 Subject: [PATCH 03/11] start integration with twisted chain complex --- src/sage/topology/simplicial_set.py | 117 ++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 15 deletions(-) diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py index f707c9407fa..667a60bb6b9 100644 --- a/src/sage/topology/simplicial_set.py +++ b/src/sage/topology/simplicial_set.py @@ -258,11 +258,13 @@ import copy +from sage.functions.generalized import sign from sage.matrix.special import identity_matrix from sage.misc.cachefunc import cached_method from sage.misc.fast_methods import WithEqualityById from sage.misc.flatten import flatten from sage.misc.lazy_import import lazy_import +from sage.misc.misc_c import prod from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ @@ -296,6 +298,7 @@ class AbstractSimplex_class(SageObject): Users should not call this directly, but instead use :func:`AbstractSimplex`. See that function for more documentation. """ + def __init__(self, dim, degeneracies=(), underlying=None, name=None, latex_name=None): """ @@ -3204,6 +3207,7 @@ class SimplicialSet_finite(SimplicialSet_arbitrary, GenericCellComplex): sage: X Y """ + def __init__(self, data, base_point=None, name=None, check=True, category=None, latex_name=None): r""" @@ -3774,10 +3778,44 @@ def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, return ChainComplex(differentials, degree_of_differential=-1, check=check) + def _canonical_twisting_operator(self): + r""" + The canonical twisting operator corresponds to the abelianization of the fundamental + group. It assigns each edge to the corresponding element in the algebra of the + abelianization of the fundamental group, which is a quotient of a Laurent polynomial + ring. - def twisted_chain_complex(self, twisting_operator, dimensions=None, augmented=False, - cochain=False, verbose=False, subcomplex=None, - check=False): + EXAMPLES:: + + sage: X = simplicial_sets.Torus() + sage: d = X._canonical_twisting_operator() + sage: d + {(sigma_1, sigma_1): f2, (sigma_1, s_0 v_0): f3, (s_0 v_0, sigma_1): f2*f3^-1} + sage: list(d.values())[0].parent() + Multivariate Laurent Polynomial Ring in f2, f3 over Rational Field + sage: Y = simplicial_sets.RealProjectiveSpace(2) + sage: Y._canonical_twisting_operator() + {f: F1bar} + sage: d2 = Y._canonical_twisting_operator() + sage: d2 + {f: F1bar} + sage: list(d2.values())[0].parent() + Quotient of Univariate Laurent Polynomial Ring in F1 over Rational Field by the ideal (-1 + F1^2) + + """ + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + G, d = self._universal_cover_dict() + phi = G.abelianization_map() + abelG, R, I, images = G.abelianization_to_algebra(ZZ) + QRP = R.quotient_ring(I) + res = dict() + for s, el in d.items(): + res[s] = QRP(prod(images[abs(a)-1]**sign(a) for a in phi(el).Tietze())) + return res + + def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augmented=False, + cochain=False, verbose=False, subcomplex=None, + check=False): r""" Return the normalized chain complex twisted by some operator. @@ -3787,7 +3825,9 @@ def twisted_chain_complex(self, twisting_operator, dimensions=None, augmented=Fa INPUT: - ``twisting_operator`` -- a dictionary, associating the twist of each - simplex. + simplex. If it is not given, the canonical one (associated to the + laurent polynomial ring abelianization of the fundamental group, ignoring + torsion) is used. - ``dimensions`` -- if ``None``, compute the chain complex in all dimensions. If a list or tuple of integers, compute the @@ -3830,18 +3870,51 @@ def twisted_chain_complex(self, twisting_operator, dimensions=None, augmented=Fa sage: ChC.differential(2) [0] + :: + + sage: X = simplicial_sets.Torus() + sage: C = X.twisted_chain_complex() + sage: C.differential(1) + [f2*f3^-1 - 1 f3 - 1 f2 - 1] + sage: C.differential(2) + [ 1 f3] + [f2*f3^-1 1] + [ -1 -1] + sage: C.differential(3) + [] + + :: + + sage: Y = simplicial_sets.RealProjectiveSpace(2) + sage: C = Y.twisted_chain_complex() + sage: C.differential(1) + [-1 + F1] + sage: C.differential(2) + [1 + F1] + sage: C.differential(3) + [] + """ from sage.homology.chain_complex import ChainComplex from sage.structure.element import get_coercion_model cm = get_coercion_model() + if twisting_operator: + twop = twisting_operator + else: + di = self._canonical_twisting_operator() + if hasattr(list(di.values())[0], "lift"): + twop = {a: b.lift() for a, b in di.items()} + else: + twop = di + def twist(s): - if s in twisting_operator: - return twisting_operator[s] + if s in twop: + return twop[s] if s.dimension() > 1: return twist(self.face_data()[s][-1]) return 1 - base_ring = cm.common_parent(*twisting_operator.values()) + base_ring = cm.common_parent(*twop.values()) if dimensions is None: if not self.cells(): # Empty @@ -3913,7 +3986,7 @@ def twist(s): sign = 1 twists = len(face_data[sigma]) * [1] twists[0] = twist(sigma) - for (ch,tau) in zip(twists,face_data[sigma]): + for (ch, tau) in zip(twists, face_data[sigma]): if tau.is_nondegenerate(): row = faces[tau] if (row, col) in matrix_data: @@ -3952,28 +4025,40 @@ def twisted_homology(self, n): - ``n`` - a positive integer. """ + CC = self.twisted_chain_complex(ZZ) + M1 = CC.differential(n).T + M2 = CC.differential(n + 1).T + RL = CC.base_ring() + RP = PolynomialRing(ZZ, 'x', 2 * RL.ngens()) + def convert_to_polynomial(p): + + D = self._canonical_twisting_operator() + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing G, d = self._universal_cover_dict() abelG = G.abelianization_map().codomain() abelinv = abelG.abelian_invariants() RP = PolynomialRing(ZZ, 'x', 2 * len(abelinv)) - I = RP.ideal([RP.gen(i)**j-1 for i,j in enumerate(abelinv)]) + I = RP.ideal([RP.gen(i)**j-1 for i, j in enumerate(abelinv)]) J = RP.ideal([RP.gen(2*a)*RP.gen(2*a+1) - 1 for a in range(RP.ngens()//2)]) GB = (I+J).groebner_basis() GBI = RP.ideal(GB) + def reduce_laurent(a): return a._singular_().reduce(GBI)._sage_() + def group_to_polynomial(el, RP): res = RP.one() for a in el.Tietze(): if a > 0: res *= RP.gen(2*a-2) else: - res*= RP.gen(-2*a-1) + res *= RP.gen(-2*a-1) return res - nd = {a:group_to_polynomial(G.abelianization_map()(b), RP) for a,b in d.items()} + nd = {a: group_to_polynomial(G.abelianization_map()(b), RP) for a, b in d.items()} CC = self.twisted_chain_complex(nd) + def mkernel(M): if M.nrows() == 0 or M.ncols() == 0: return M @@ -3982,10 +4067,11 @@ def mkernel(M): for g in (I+J).gens(): res = res.stack(g*identity_matrix(n)) syz = res.T._singular_().syz()._sage_() - trimmed = syz.T.submatrix(0,0,syz.ncols(),M.nrows()) + trimmed = syz.T.submatrix(0, 0, syz.ncols(), M.nrows()) trimmed = trimmed.apply_map(reduce_laurent) - to_delete = [i for (i,r) in enumerate(trimmed.rows()) if not r] + to_delete = [i for (i, r) in enumerate(trimmed.rows()) if not r] return trimmed.delete_rows(to_delete) + def lift_to_submodule(S, M): if S.nrows() == 0 or S.ncols() == 0: return S @@ -3993,7 +4079,8 @@ def lift_to_submodule(S, M): for g in GB: res = res.stack(g*identity_matrix(M.ncols())) singres = res.T._singular_().lift(S.T._singular_())._sage_() - return singres.submatrix(0,0,M.nrows(),S.nrows()) + return singres.submatrix(0, 0, M.nrows(), S.nrows()) + def mgb(M): if M.nrows() == 0 or M.ncols() == 0: return M @@ -4020,7 +4107,7 @@ def mgb(M): res = mgb(DK.T) resmat = mgb(res.apply_map(reduce_laurent)) L = LaurentPolynomialRing(ZZ, 'x', len(abelinv)) - h = RP.hom(flatten([[g, g**-1] for g in L.gens()]),codomain = L) + h = RP.hom(flatten([[g, g**-1] for g in L.gens()]), codomain=L) laumat = resmat.apply_map(h) laumat = laumat.change_ring(L) if laumat.ncols() > 0: From 13098aa85e4234a5e33df6bd1c80c0986cad72da Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Fri, 10 Nov 2023 13:00:19 +0100 Subject: [PATCH 04/11] First fully working implementation of twisted_homology --- .../rings/polynomial/laurent_polynomial.pyx | 23 ++++ .../polynomial/laurent_polynomial_mpair.pyx | 28 +++++ .../laurent_polynomial_ring_base.py | 8 ++ src/sage/topology/simplicial_set.py | 110 ++++++++++++------ 4 files changed, 136 insertions(+), 33 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index cf92b0e43d1..b61fb1024c3 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -1988,3 +1988,26 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): 0 """ return self.__u[-self.__n] + + def _as_extended_polynomial(self): + """ + This Laurent polynomial seen as a polynomial in twice as many variables, + where half of the variables are the inverses of the other half. + + EXAMPLES:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = t-t^-2 + sage: f._as_extended_polynomial() + -tinv^2 + t + sage: _.parent() + Multivariate Polynomial Ring in t, tinv over Rational Field + + """ + dres = {} + for (e, c) in self.dict().items(): + if e > 0 : + dres[(e,0)] = c + else: + dres[(0,-e)] = c + return self.parent()._extended_ring(dres) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index e14f1b36ce5..b428560c1ae 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -555,6 +555,34 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): for exp, coeff in self._poly.iterator_exp_coeff(): yield (coeff, P.element_class(P, one, exp.eadd(self._mon))) + def _as_extended_polynomial(self): + """ + This Laurent polynomial seen as a polynomial in twice as many variables, + where half of the variables are the inverses of the other half. + + EXAMPLES:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = t1-t2^-2 + sage: f + t1 - t2^-2 + sage: f._as_extended_polynomial() + -t2inv^2 + t1 + sage: _.parent() + Multivariate Polynomial Ring in t1, t1inv, t2, t2inv over Rational Field + + """ + dres = {} + for (e, c) in self.dict().items(): + exps = [] + for t in e: + if t > 0 : + exps += [t, 0] + else: + exps += [0, -t] + dres [tuple(exps)] = c + return self.parent()._extended_ring(dres) + def iterator_exp_coeff(self): """ Iterate over ``self`` as pairs of (ETuple, coefficient). diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 0c9022c492c..302476381a5 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -25,6 +25,7 @@ from sage.rings.infinity import infinity +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent @@ -61,6 +62,13 @@ def __init__(self, R): self._one_element = self.element_class(self, R.one()) CommutativeRing.__init__(self, R.base_ring(), names=names, category=R.category()) + ernames = [] + for n in names: + ernames.append(n) + ernames.append(n + "inv") + ER = PolynomialRing(R.base_ring(), ernames) + self._extended_ring = ER + self._extended_ring_ideal = ER.ideal([ER.gen(2*i)*ER.gen(2*i+1)-1 for i in range(self._n)]) def ngens(self): """ diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py index 667a60bb6b9..0dc1a64fcb0 100644 --- a/src/sage/topology/simplicial_set.py +++ b/src/sage/topology/simplicial_set.py @@ -4012,37 +4012,85 @@ def twist(s): return ChainComplex(differentials, degree_of_differential=-1, check=check) - def twisted_homology(self, n): + def twisted_homology(self, n, reduced=False): r""" The `n`-th twisted homology module of the simplicial set with respect to the abelianization of the fundamental_group. - It is a module over a Laurent - polynomial ring, that, if the fundamental group is abelian, coincides - with the homology of the universal cover. + It is a module over a polynomial ring, including relations to make some + variables the multiplicative inverses of others. INPUT: - ``n`` - a positive integer. + + - ``reduced`` - (default: False) if set to True, the presentation matrix + will be reduced. + + EXAMPLES:: + + sage: X = simplicial_sets.Sphere(1).wedge(simplicial_sets.Sphere(2)) + sage: X.twisted_homology(1) + Quotient module by Submodule of Ambient free module of rank 1 over the integral domain Multivariate Polynomial Ring in f1, f1inv over Integer Ring + Generated by the rows of the matrix: + [f1*f1inv - 1] + sage: X.twisted_homology(2) + Quotient module by Submodule of Ambient free module of rank 1 over the integral domain Multivariate Polynomial Ring in f1, f1inv over Integer Ring + Generated by the rows of the matrix: + [] + + :: + + sage: Y = simplicial_sets.Torus() + sage: Y.twisted_homology(1) + Quotient module by Submodule of Ambient free module of rank 5 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring + Generated by the rows of the matrix: + [ 1 0 0 0 0] + [ 0 1 0 0 0] + [ 0 0 1 0 0] + [ 0 0 0 1 0] + [ 0 0 0 0 1] + [f2*f2inv - 1 0 0 0 0] + [ 0 f2*f2inv - 1 0 0 0] + [ 0 0 f2*f2inv - 1 0 0] + [ 0 0 0 f2*f2inv - 1 0] + [ 0 0 0 0 f2*f2inv - 1] + [f3*f3inv - 1 0 0 0 0] + [ 0 f3*f3inv - 1 0 0 0] + [ 0 0 f3*f3inv - 1 0 0] + [ 0 0 0 f3*f3inv - 1 0] + [ 0 0 0 0 f3*f3inv - 1] + sage: Y.twisted_homology(2) + Quotient module by Submodule of Ambient free module of rank 0 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring + Generated by the rows of the matrix: + [] + sage: Y.twisted_homology(1, reduced=True) + Quotient module by Submodule of Ambient free module of rank 5 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring + Generated by the rows of the matrix: + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] + """ - CC = self.twisted_chain_complex(ZZ) + G, d = self._universal_cover_dict() + phi = G.abelianization_map() + abelG, R, I, images = G.abelianization_to_algebra(ZZ) + CC = self.twisted_chain_complex() M1 = CC.differential(n).T M2 = CC.differential(n + 1).T - RL = CC.base_ring() - RP = PolynomialRing(ZZ, 'x', 2 * RL.ngens()) def convert_to_polynomial(p): - - D = self._canonical_twisting_operator() - - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing - G, d = self._universal_cover_dict() - abelG = G.abelianization_map().codomain() - abelinv = abelG.abelian_invariants() - RP = PolynomialRing(ZZ, 'x', 2 * len(abelinv)) - I = RP.ideal([RP.gen(i)**j-1 for i, j in enumerate(abelinv)]) - J = RP.ideal([RP.gen(2*a)*RP.gen(2*a+1) - 1 for a in range(RP.ngens()//2)]) - GB = (I+J).groebner_basis() + if hasattr(p, "lift"): + return p.lift()._as_extended_polynomial() + else: + return p._as_extended_polynomial() + M1 = M1.apply_map(convert_to_polynomial) + M2 = M2.apply_map(convert_to_polynomial) + RP = R._extended_ring + IP = RP.ideal([convert_to_polynomial(g) for g in I]) + JP = R._extended_ring_ideal + GB = (IP+JP).groebner_basis() GBI = RP.ideal(GB) def reduce_laurent(a): @@ -4056,15 +4104,13 @@ def group_to_polynomial(el, RP): else: res *= RP.gen(-2*a-1) return res - nd = {a: group_to_polynomial(G.abelianization_map()(b), RP) for a, b in d.items()} - CC = self.twisted_chain_complex(nd) def mkernel(M): if M.nrows() == 0 or M.ncols() == 0: return M res = M n = res.ncols() - for g in (I+J).gens(): + for g in (IP+JP).gens(): res = res.stack(g*identity_matrix(n)) syz = res.T._singular_().syz()._sage_() trimmed = syz.T.submatrix(0, 0, syz.ncols(), M.nrows()) @@ -4091,8 +4137,6 @@ def mgb(M): to_delete = [i for i, r in enumerate(sres.apply_map(reduce_laurent)) if not r] return sres.delete_rows(to_delete) M2 = border_matrix(n+1) - M1 = CC.differential(n).T - M2 = CC.differential(n + 1).T if M1.nrows() == 0: return (RP**0).quotient_module([]) K = mkernel(M1) @@ -4106,15 +4150,15 @@ def mgb(M): else: res = mgb(DK.T) resmat = mgb(res.apply_map(reduce_laurent)) - L = LaurentPolynomialRing(ZZ, 'x', len(abelinv)) - h = RP.hom(flatten([[g, g**-1] for g in L.gens()]), codomain=L) - laumat = resmat.apply_map(h) - laumat = laumat.change_ring(L) - if laumat.ncols() > 0: - for g in I.gens(): - laumat = laumat.stack(h(g)*identity_matrix(L, laumat.ncols())) - AM = L ** K.nrows() - SM = AM.submodule(laumat) + AM = RP ** K.nrows() + if resmat.ncols() == 0: + SM = AM.submodule([]) + return AM.quotient_module(SM) + for g in (IP+JP).gens(): + resmat = resmat.stack(g * identity_matrix(resmat.ncols())) + if reduced: + resmat = resmat.T._singular_().std()._sage_().T + SM = AM.submodule(resmat) return AM.quotient_module(SM) @cached_method From 7d5ad2b2a16d26c3decb0378b53bb56788917971 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Fri, 10 Nov 2023 15:21:44 +0100 Subject: [PATCH 05/11] moved to pointed category and fixed issue with universal_cover_dict --- src/sage/categories/simplicial_sets.py | 405 ++++++++++++++++++++++++- src/sage/topology/simplicial_set.py | 387 ----------------------- 2 files changed, 399 insertions(+), 393 deletions(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index c07871402fd..72dd3b6220c 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -8,13 +8,22 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** +from sage.functions.generalized import sign +from sage.matrix.special import identity_matrix from sage.misc.cachefunc import cached_method +from sage.misc.lazy_import import lazy_import from sage.categories.category_singleton import Category_singleton from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.sets_cat import Sets from sage.categories.homsets import HomsetsCategory +from sage.matrix.constructor import matrix +from sage.misc.flatten import flatten +from sage.misc.misc_c import prod from sage.rings.infinity import Infinity from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ + +lazy_import('sage.matrix.constructor', 'matrix') class SimplicialSets(Category_singleton): @@ -360,10 +369,9 @@ def _universal_cover_dict(self): [1, f, f * f] """ from sage.groups.free_group import FreeGroup - skel = self.n_skeleton(2) - graph = skel.graph() - if not skel.is_connected(): - graph = graph.subgraph(skel.base_point()) + graph = self.graph() + if not self.is_connected(): + graph = graph.subgraph(self.base_point()) edges = [e[2] for e in graph.edges(sort=False)] spanning_tree = [e[2] for e in graph.min_spanning_tree()] gens = [e for e in edges if e not in spanning_tree] @@ -371,9 +379,9 @@ def _universal_cover_dict(self): FG = FreeGroup(len(gens), 'e') rels = [] - for f in skel.n_cells(2): + for f in self.n_cells(2): z = dict() - for i, sigma in enumerate(skel.faces(f)): + for i, sigma in enumerate(self.faces(f)): if sigma in spanning_tree: z[i] = FG.one() elif sigma.is_degenerate(): @@ -574,6 +582,391 @@ def universal_cover(self): """ return self.universal_cover_map().domain() + def _canonical_twisting_operator(self): + r""" + The canonical twisting operator corresponds to the abelianization of the fundamental + group. It assigns each edge to the corresponding element in the algebra of the + abelianization of the fundamental group, which is a quotient of a Laurent polynomial + ring. + + EXAMPLES:: + + sage: X = simplicial_sets.Torus() + sage: d = X._canonical_twisting_operator() + sage: d + {(s_0 v_0, sigma_1): f2*f3^-1, (sigma_1, s_0 v_0): f3, (sigma_1, sigma_1): f2} + sage: list(d.values())[0].parent() + Multivariate Laurent Polynomial Ring in f2, f3 over Integer Ring + sage: Y = simplicial_sets.RealProjectiveSpace(2) + sage: Y._canonical_twisting_operator() + {f: F1bar} + sage: d2 = Y._canonical_twisting_operator() + sage: d2 + {f: F1bar} + sage: list(d2.values())[0].parent() + Quotient of Univariate Laurent Polynomial Ring in F1 over Integer Ring by the ideal (-1 + F1^2) + + """ + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + G, d = self._universal_cover_dict() + phi = G.abelianization_map() + abelG, R, I, images = G.abelianization_to_algebra(ZZ) + QRP = R.quotient_ring(I) + res = dict() + for s, el in d.items(): + res[s] = QRP(prod(images[abs(a)-1]**sign(a) for a in phi(el).Tietze())) + return res + + def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augmented=False, + cochain=False, verbose=False, subcomplex=None, + check=False): + r""" + Return the normalized chain complex twisted by some operator. + + A twisting operator is a map from the set of simplices to some algebra. + The differentials are then twisted by this operator. + + INPUT: + + - ``twisting_operator`` -- a dictionary, associating the twist of each + simplex. If it is not given, the canonical one (associated to the + laurent polynomial ring abelianization of the fundamental group, ignoring + torsion) is used. + + - ``dimensions`` -- if ``None``, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. + + - ``augmented`` (optional, default ``False``) -- if ``True``, + return the augmented chain complex (that is, include a class + in dimension `-1` corresponding to the empty cell). + + - ``cochain`` (optional, default ``False``) -- if ``True``, + return the cochain complex (that is, the dual of the chain + complex). + + - ``verbose`` (optional, default ``False``) -- ignored. + + - ``subcomplex`` (optional, default ``None``) -- if present, + compute the chain complex relative to this subcomplex. + + - ``check`` (optional, default ``False``) -- If ``True``, make + sure that the chain complex is actually a chain complex: + the differentials are composable and their product is zero. + + The normalized chain complex of a simplicial set is isomorphic + to the chain complex obtained by modding out by degenerate + simplices, and the latter is what is actually constructed + here. + + EXAMPLES:: + + sage: W = simplicial_sets.Sphere(1).wedge(simplicial_sets.Sphere(2)) + sage: W.nondegenerate_simplices() + [*, sigma_1, sigma_2] + sage: s1 = W.nondegenerate_simplices()[1] + sage: L. = LaurentPolynomialRing(QQ) + sage: tw = {s1:t} + sage: ChC = W.twisted_chain_complex(tw) + sage: ChC.differential(1) + [-1 + t] + sage: ChC.differential(2) + [0] + + :: + + sage: X = simplicial_sets.Torus() + sage: C = X.twisted_chain_complex() + sage: C.differential(1) + [f2*f3^-1 - 1 f3 - 1 f2 - 1] + sage: C.differential(2) + [ 1 f3] + [f2*f3^-1 1] + [ -1 -1] + sage: C.differential(3) + [] + + :: + + sage: Y = simplicial_sets.RealProjectiveSpace(2) + sage: C = Y.twisted_chain_complex() + sage: C.differential(1) + [-1 + F1] + sage: C.differential(2) + [1 + F1] + sage: C.differential(3) + [] + + """ + from sage.homology.chain_complex import ChainComplex + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + + if twisting_operator: + twop = twisting_operator + else: + di = self._canonical_twisting_operator() + if hasattr(list(di.values())[0], "lift"): + twop = {a: b.lift() for a, b in di.items()} + else: + twop = di + + def twist(s): + if s in twop: + return twop[s] + if s.dimension() > 1: + return twist(self.face_data()[s][-1]) + return 1 + base_ring = cm.common_parent(*twop.values()) + + if dimensions is None: + if not self.cells(): # Empty + if cochain: + return ChainComplex({-1: matrix(base_ring, 0, 0)}, + degree_of_differential=1) + return ChainComplex({0: matrix(base_ring, 0, 0)}, + degree_of_differential=-1) + dimensions = list(range(self.dimension() + 1)) + else: + if not isinstance(dimensions, (list, tuple, range)): + dimensions = list(range(dimensions - 1, dimensions + 2)) + else: + dimensions = [n for n in dimensions if n >= 0] + if not dimensions: + # Return the empty chain complex. + if cochain: + return ChainComplex(base_ring=base_ring, degree=1) + else: + return ChainComplex(base_ring=base_ring, degree=-1) + + differentials = {} + # Convert the tuple self._data to a dictionary indexed by the + # non-degenerate simplices. + if subcomplex: + X = self.quotient(subcomplex) + face_data = X.face_data() + nondegens = X.nondegenerate_simplices() + else: + face_data = self.face_data() + nondegens = self.nondegenerate_simplices() + # simplices: dictionary indexed by dimension, values the list + # of non-degenerate simplices in that dimension. + simplices = {} + for sigma in nondegens: + if sigma.dimension() in simplices: + simplices[sigma.dimension()].append(sigma) + else: + simplices[sigma.dimension()] = [sigma] + first = dimensions.pop(0) + if first in simplices: + rank = len(simplices[first]) + current = sorted(simplices[first]) + else: + rank = 0 + current = [] + if augmented and first == 0: + differentials[first-1] = matrix(base_ring, 0, 1) + differentials[first] = matrix(base_ring, 1, rank, + [1] * rank) + else: + differentials[first] = matrix(base_ring, 0, rank) + + for d in dimensions: + old_rank = rank + faces = {_[1]: _[0] for _ in enumerate(current)} + if d in simplices: + current = sorted(simplices[d]) + rank = len(current) + # old_rank: number of simplices in dimension d-1. + # faces: list of simplices in dimension d-1. + # rank: number of simplices in dimension d. + # current: list of simplices in dimension d. + if not faces: + differentials[d] = matrix(base_ring, old_rank, rank) + else: + matrix_data = {} + for col, sigma in enumerate(current): + sign = 1 + twists = len(face_data[sigma]) * [1] + twists[0] = twist(sigma) + for (ch, tau) in zip(twists, face_data[sigma]): + if tau.is_nondegenerate(): + row = faces[tau] + if (row, col) in matrix_data: + matrix_data[(row, col)] += sign*ch + else: + matrix_data[(row, col)] = sign*ch + sign *= -1 + + differentials[d] = matrix(base_ring, old_rank, + rank, matrix_data) + + else: + rank = 0 + current = [] + differentials[d] = matrix(base_ring, old_rank, rank) + + if cochain: + new_diffs = {} + for d in differentials: + new_diffs[d-1] = differentials[d].transpose() + return ChainComplex(new_diffs, degree_of_differential=1, + check=check) + return ChainComplex(differentials, degree_of_differential=-1, + check=check) + + def twisted_homology(self, n, reduced=False): + r""" + The `n`-th twisted homology module of the simplicial set with respect to + the abelianization of the fundamental_group. + + It is a module over a polynomial ring, including relations to make some + variables the multiplicative inverses of others. + + INPUT: + + - ``n`` - a positive integer. + + - ``reduced`` - (default: False) if set to True, the presentation matrix + will be reduced. + + EXAMPLES:: + + sage: X = simplicial_sets.Sphere(1).wedge(simplicial_sets.Sphere(2)) + sage: X.twisted_homology(1) + Quotient module by Submodule of Ambient free module of rank 0 over the integral domain Multivariate Polynomial Ring in f1, f1inv over Integer Ring + Generated by the rows of the matrix: + [] + sage: X.twisted_homology(2) + Quotient module by Submodule of Ambient free module of rank 1 over the integral domain Multivariate Polynomial Ring in f1, f1inv over Integer Ring + Generated by the rows of the matrix: + [f1*f1inv - 1] + + :: + + sage: Y = simplicial_sets.Torus() + sage: Y.twisted_homology(1) + Quotient module by Submodule of Ambient free module of rank 5 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring + Generated by the rows of the matrix: + [ 1 0 0 0 0] + [ 0 1 0 0 0] + [ 0 0 1 0 0] + [ 0 0 0 1 0] + [ 0 0 0 0 1] + [f2*f2inv - 1 0 0 0 0] + [ 0 f2*f2inv - 1 0 0 0] + [ 0 0 f2*f2inv - 1 0 0] + [ 0 0 0 f2*f2inv - 1 0] + [ 0 0 0 0 f2*f2inv - 1] + [f3*f3inv - 1 0 0 0 0] + [ 0 f3*f3inv - 1 0 0 0] + [ 0 0 f3*f3inv - 1 0 0] + [ 0 0 0 f3*f3inv - 1 0] + [ 0 0 0 0 f3*f3inv - 1] + sage: Y.twisted_homology(2) + Quotient module by Submodule of Ambient free module of rank 0 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring + Generated by the rows of the matrix: + [] + sage: Y.twisted_homology(1, reduced=True) + Quotient module by Submodule of Ambient free module of rank 5 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring + Generated by the rows of the matrix: + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] + + """ + G, d = self._universal_cover_dict() + phi = G.abelianization_map() + abelG, R, I, images = G.abelianization_to_algebra(ZZ) + CC = self.twisted_chain_complex() + M1 = CC.differential(n).T + M2 = CC.differential(n + 1).T + def convert_to_polynomial(p): + if hasattr(p, "lift"): + return p.lift()._as_extended_polynomial() + else: + return p._as_extended_polynomial() + M1 = M1.apply_map(convert_to_polynomial) + M2 = M2.apply_map(convert_to_polynomial) + RP = R._extended_ring + IP = RP.ideal([convert_to_polynomial(g) for g in I]) + JP = R._extended_ring_ideal + GB = (IP+JP).groebner_basis() + GBI = RP.ideal(GB) + + def reduce_laurent(a): + return a._singular_().reduce(GBI)._sage_() + + def group_to_polynomial(el, RP): + res = RP.one() + for a in el.Tietze(): + if a > 0: + res *= RP.gen(2*a-2) + else: + res *= RP.gen(-2*a-1) + return res + + def mkernel(M): + if M.nrows() == 0: + return matrix(M.base_ring(), 0, 0) + if M.ncols() == 0: + return M.T + res = M + n = res.ncols() + for g in (IP+JP).gens(): + res = res.stack(g*identity_matrix(n)) + syz = res.T._singular_().syz()._sage_() + trimmed = syz.T.submatrix(0, 0, syz.ncols(), M.nrows()) + trimmed = trimmed.apply_map(reduce_laurent) + to_delete = [i for (i, r) in enumerate(trimmed.rows()) if not r] + return trimmed.delete_rows(to_delete) + + def lift_to_submodule(S, M): + if S.nrows() == 0 or S.ncols() == 0: + return S + res = M + for g in GB: + res = res.stack(g*identity_matrix(M.ncols())) + singres = res.T._singular_().lift(S.T._singular_())._sage_() + return singres.submatrix(0, 0, M.nrows(), S.nrows()) + + def mgb(M): + if M.nrows() == 0 or M.ncols() == 0: + return M + res = M + for g in GB: + res = res.stack(g*identity_matrix(M.ncols())) + sres = res.T._singular_().std()._sage_().T + to_delete = [i for i, r in enumerate(sres.apply_map(reduce_laurent)) if not r] + return sres.delete_rows(to_delete) + M2 = border_matrix(n+1) + if M1.nrows() == 0: + return (RP**0).quotient_module([]) + K = mkernel(M1) + DK = mkernel(K) + if M2.nrows() > 0: + S = lift_to_submodule(M2, K) + if S.nrows() > 0 and S.ncols() > 0: + res = mgb(DK.stack(S.T)) + else: + res = DK + else: + res = mgb(DK) + resmat = mgb(res.apply_map(reduce_laurent)) + AM = RP ** K.nrows() + if resmat.ncols() == 0: + SM = AM.submodule([]) + return AM.quotient_module(SM) + for g in (IP+JP).gens(): + resmat = resmat.stack(g * identity_matrix(resmat.ncols())) + if reduced: + resmat = resmat.T._singular_().std()._sage_().T + SM = AM.submodule(resmat) + return AM.quotient_module(SM) + def is_simply_connected(self): """ Return ``True`` if this pointed simplicial set is simply connected. diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py index 0dc1a64fcb0..e33465f1153 100644 --- a/src/sage/topology/simplicial_set.py +++ b/src/sage/topology/simplicial_set.py @@ -258,13 +258,9 @@ import copy -from sage.functions.generalized import sign -from sage.matrix.special import identity_matrix from sage.misc.cachefunc import cached_method from sage.misc.fast_methods import WithEqualityById -from sage.misc.flatten import flatten from sage.misc.lazy_import import lazy_import -from sage.misc.misc_c import prod from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ @@ -3778,389 +3774,6 @@ def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, return ChainComplex(differentials, degree_of_differential=-1, check=check) - def _canonical_twisting_operator(self): - r""" - The canonical twisting operator corresponds to the abelianization of the fundamental - group. It assigns each edge to the corresponding element in the algebra of the - abelianization of the fundamental group, which is a quotient of a Laurent polynomial - ring. - - EXAMPLES:: - - sage: X = simplicial_sets.Torus() - sage: d = X._canonical_twisting_operator() - sage: d - {(sigma_1, sigma_1): f2, (sigma_1, s_0 v_0): f3, (s_0 v_0, sigma_1): f2*f3^-1} - sage: list(d.values())[0].parent() - Multivariate Laurent Polynomial Ring in f2, f3 over Rational Field - sage: Y = simplicial_sets.RealProjectiveSpace(2) - sage: Y._canonical_twisting_operator() - {f: F1bar} - sage: d2 = Y._canonical_twisting_operator() - sage: d2 - {f: F1bar} - sage: list(d2.values())[0].parent() - Quotient of Univariate Laurent Polynomial Ring in F1 over Rational Field by the ideal (-1 + F1^2) - - """ - from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing - G, d = self._universal_cover_dict() - phi = G.abelianization_map() - abelG, R, I, images = G.abelianization_to_algebra(ZZ) - QRP = R.quotient_ring(I) - res = dict() - for s, el in d.items(): - res[s] = QRP(prod(images[abs(a)-1]**sign(a) for a in phi(el).Tietze())) - return res - - def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augmented=False, - cochain=False, verbose=False, subcomplex=None, - check=False): - r""" - Return the normalized chain complex twisted by some operator. - - A twisting operator is a map from the set of simplices to some algebra. - The differentials are then twisted by this operator. - - INPUT: - - - ``twisting_operator`` -- a dictionary, associating the twist of each - simplex. If it is not given, the canonical one (associated to the - laurent polynomial ring abelianization of the fundamental group, ignoring - torsion) is used. - - - ``dimensions`` -- if ``None``, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. - - - ``augmented`` (optional, default ``False``) -- if ``True``, - return the augmented chain complex (that is, include a class - in dimension `-1` corresponding to the empty cell). - - - ``cochain`` (optional, default ``False``) -- if ``True``, - return the cochain complex (that is, the dual of the chain - complex). - - - ``verbose`` (optional, default ``False``) -- ignored. - - - ``subcomplex`` (optional, default ``None``) -- if present, - compute the chain complex relative to this subcomplex. - - - ``check`` (optional, default ``False``) -- If ``True``, make - sure that the chain complex is actually a chain complex: - the differentials are composable and their product is zero. - - The normalized chain complex of a simplicial set is isomorphic - to the chain complex obtained by modding out by degenerate - simplices, and the latter is what is actually constructed - here. - - EXAMPLES:: - - sage: W = simplicial_sets.Sphere(1).wedge(simplicial_sets.Sphere(2)) - sage: W.nondegenerate_simplices() - [*, sigma_1, sigma_2] - sage: s1 = W.nondegenerate_simplices()[1] - sage: L. = LaurentPolynomialRing(QQ) - sage: tw = {s1:t} - sage: ChC = W.twisted_chain_complex(tw) - sage: ChC.differential(1) - [-1 + t] - sage: ChC.differential(2) - [0] - - :: - - sage: X = simplicial_sets.Torus() - sage: C = X.twisted_chain_complex() - sage: C.differential(1) - [f2*f3^-1 - 1 f3 - 1 f2 - 1] - sage: C.differential(2) - [ 1 f3] - [f2*f3^-1 1] - [ -1 -1] - sage: C.differential(3) - [] - - :: - - sage: Y = simplicial_sets.RealProjectiveSpace(2) - sage: C = Y.twisted_chain_complex() - sage: C.differential(1) - [-1 + F1] - sage: C.differential(2) - [1 + F1] - sage: C.differential(3) - [] - - """ - from sage.homology.chain_complex import ChainComplex - from sage.structure.element import get_coercion_model - cm = get_coercion_model() - - if twisting_operator: - twop = twisting_operator - else: - di = self._canonical_twisting_operator() - if hasattr(list(di.values())[0], "lift"): - twop = {a: b.lift() for a, b in di.items()} - else: - twop = di - - def twist(s): - if s in twop: - return twop[s] - if s.dimension() > 1: - return twist(self.face_data()[s][-1]) - return 1 - base_ring = cm.common_parent(*twop.values()) - - if dimensions is None: - if not self.cells(): # Empty - if cochain: - return ChainComplex({-1: matrix(base_ring, 0, 0)}, - degree_of_differential=1) - return ChainComplex({0: matrix(base_ring, 0, 0)}, - degree_of_differential=-1) - dimensions = list(range(self.dimension() + 1)) - else: - if not isinstance(dimensions, (list, tuple, range)): - dimensions = list(range(dimensions - 1, dimensions + 2)) - else: - dimensions = [n for n in dimensions if n >= 0] - if not dimensions: - # Return the empty chain complex. - if cochain: - return ChainComplex(base_ring=base_ring, degree=1) - else: - return ChainComplex(base_ring=base_ring, degree=-1) - - differentials = {} - # Convert the tuple self._data to a dictionary indexed by the - # non-degenerate simplices. - if subcomplex: - X = self.quotient(subcomplex) - face_data = X.face_data() - nondegens = X.nondegenerate_simplices() - else: - face_data = self.face_data() - nondegens = self.nondegenerate_simplices() - # simplices: dictionary indexed by dimension, values the list - # of non-degenerate simplices in that dimension. - simplices = {} - for sigma in nondegens: - if sigma.dimension() in simplices: - simplices[sigma.dimension()].append(sigma) - else: - simplices[sigma.dimension()] = [sigma] - first = dimensions.pop(0) - if first in simplices: - rank = len(simplices[first]) - current = sorted(simplices[first]) - else: - rank = 0 - current = [] - if augmented and first == 0: - differentials[first-1] = matrix(base_ring, 0, 1) - differentials[first] = matrix(base_ring, 1, rank, - [1] * rank) - else: - differentials[first] = matrix(base_ring, 0, rank) - - for d in dimensions: - old_rank = rank - faces = {_[1]: _[0] for _ in enumerate(current)} - if d in simplices: - current = sorted(simplices[d]) - rank = len(current) - # old_rank: number of simplices in dimension d-1. - # faces: list of simplices in dimension d-1. - # rank: number of simplices in dimension d. - # current: list of simplices in dimension d. - if not faces: - differentials[d] = matrix(base_ring, old_rank, rank) - else: - matrix_data = {} - for col, sigma in enumerate(current): - sign = 1 - twists = len(face_data[sigma]) * [1] - twists[0] = twist(sigma) - for (ch, tau) in zip(twists, face_data[sigma]): - if tau.is_nondegenerate(): - row = faces[tau] - if (row, col) in matrix_data: - matrix_data[(row, col)] += sign*ch - else: - matrix_data[(row, col)] = sign*ch - sign *= -1 - - differentials[d] = matrix(base_ring, old_rank, - rank, matrix_data) - - else: - rank = 0 - current = [] - differentials[d] = matrix(base_ring, old_rank, rank) - - if cochain: - new_diffs = {} - for d in differentials: - new_diffs[d-1] = differentials[d].transpose() - return ChainComplex(new_diffs, degree_of_differential=1, - check=check) - return ChainComplex(differentials, degree_of_differential=-1, - check=check) - - def twisted_homology(self, n, reduced=False): - r""" - The `n`-th twisted homology module of the simplicial set with respect to - the abelianization of the fundamental_group. - - It is a module over a polynomial ring, including relations to make some - variables the multiplicative inverses of others. - - INPUT: - - - ``n`` - a positive integer. - - - ``reduced`` - (default: False) if set to True, the presentation matrix - will be reduced. - - EXAMPLES:: - - sage: X = simplicial_sets.Sphere(1).wedge(simplicial_sets.Sphere(2)) - sage: X.twisted_homology(1) - Quotient module by Submodule of Ambient free module of rank 1 over the integral domain Multivariate Polynomial Ring in f1, f1inv over Integer Ring - Generated by the rows of the matrix: - [f1*f1inv - 1] - sage: X.twisted_homology(2) - Quotient module by Submodule of Ambient free module of rank 1 over the integral domain Multivariate Polynomial Ring in f1, f1inv over Integer Ring - Generated by the rows of the matrix: - [] - - :: - - sage: Y = simplicial_sets.Torus() - sage: Y.twisted_homology(1) - Quotient module by Submodule of Ambient free module of rank 5 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring - Generated by the rows of the matrix: - [ 1 0 0 0 0] - [ 0 1 0 0 0] - [ 0 0 1 0 0] - [ 0 0 0 1 0] - [ 0 0 0 0 1] - [f2*f2inv - 1 0 0 0 0] - [ 0 f2*f2inv - 1 0 0 0] - [ 0 0 f2*f2inv - 1 0 0] - [ 0 0 0 f2*f2inv - 1 0] - [ 0 0 0 0 f2*f2inv - 1] - [f3*f3inv - 1 0 0 0 0] - [ 0 f3*f3inv - 1 0 0 0] - [ 0 0 f3*f3inv - 1 0 0] - [ 0 0 0 f3*f3inv - 1 0] - [ 0 0 0 0 f3*f3inv - 1] - sage: Y.twisted_homology(2) - Quotient module by Submodule of Ambient free module of rank 0 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring - Generated by the rows of the matrix: - [] - sage: Y.twisted_homology(1, reduced=True) - Quotient module by Submodule of Ambient free module of rank 5 over the integral domain Multivariate Polynomial Ring in f2, f2inv, f3, f3inv over Integer Ring - Generated by the rows of the matrix: - [1 0 0 0 0] - [0 1 0 0 0] - [0 0 1 0 0] - [0 0 0 1 0] - [0 0 0 0 1] - - """ - G, d = self._universal_cover_dict() - phi = G.abelianization_map() - abelG, R, I, images = G.abelianization_to_algebra(ZZ) - CC = self.twisted_chain_complex() - M1 = CC.differential(n).T - M2 = CC.differential(n + 1).T - def convert_to_polynomial(p): - if hasattr(p, "lift"): - return p.lift()._as_extended_polynomial() - else: - return p._as_extended_polynomial() - M1 = M1.apply_map(convert_to_polynomial) - M2 = M2.apply_map(convert_to_polynomial) - RP = R._extended_ring - IP = RP.ideal([convert_to_polynomial(g) for g in I]) - JP = R._extended_ring_ideal - GB = (IP+JP).groebner_basis() - GBI = RP.ideal(GB) - - def reduce_laurent(a): - return a._singular_().reduce(GBI)._sage_() - - def group_to_polynomial(el, RP): - res = RP.one() - for a in el.Tietze(): - if a > 0: - res *= RP.gen(2*a-2) - else: - res *= RP.gen(-2*a-1) - return res - - def mkernel(M): - if M.nrows() == 0 or M.ncols() == 0: - return M - res = M - n = res.ncols() - for g in (IP+JP).gens(): - res = res.stack(g*identity_matrix(n)) - syz = res.T._singular_().syz()._sage_() - trimmed = syz.T.submatrix(0, 0, syz.ncols(), M.nrows()) - trimmed = trimmed.apply_map(reduce_laurent) - to_delete = [i for (i, r) in enumerate(trimmed.rows()) if not r] - return trimmed.delete_rows(to_delete) - - def lift_to_submodule(S, M): - if S.nrows() == 0 or S.ncols() == 0: - return S - res = M - for g in GB: - res = res.stack(g*identity_matrix(M.ncols())) - singres = res.T._singular_().lift(S.T._singular_())._sage_() - return singres.submatrix(0, 0, M.nrows(), S.nrows()) - - def mgb(M): - if M.nrows() == 0 or M.ncols() == 0: - return M - res = M - for g in GB: - res = res.stack(g*identity_matrix(M.ncols())) - sres = res.T._singular_().std()._sage_().T - to_delete = [i for i, r in enumerate(sres.apply_map(reduce_laurent)) if not r] - return sres.delete_rows(to_delete) - M2 = border_matrix(n+1) - if M1.nrows() == 0: - return (RP**0).quotient_module([]) - K = mkernel(M1) - DK = mkernel(K) - if M2.nrows() > 0: - S = lift_to_submodule(M2, K) - if S.nrows() > 0 and S.ncols() > 0: - res = mgb(DK.stack(S.T)) - else: - res = DK - else: - res = mgb(DK.T) - resmat = mgb(res.apply_map(reduce_laurent)) - AM = RP ** K.nrows() - if resmat.ncols() == 0: - SM = AM.submodule([]) - return AM.quotient_module(SM) - for g in (IP+JP).gens(): - resmat = resmat.stack(g * identity_matrix(resmat.ncols())) - if reduced: - resmat = resmat.T._singular_().std()._sage_().T - SM = AM.submodule(resmat) - return AM.quotient_module(SM) - @cached_method def algebraic_topological_model(self, base_ring=None): r""" From 2579692b04ebbbd425ca1fe98f35015270f5e85a Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Mon, 13 Nov 2023 12:07:50 +0100 Subject: [PATCH 06/11] Address reviewer's comments --- src/sage/categories/simplicial_sets.py | 41 +++--- .../rings/polynomial/laurent_polynomial.pyx | 138 +++++++++--------- .../polynomial/laurent_polynomial_mpair.pyx | 71 ++++----- 3 files changed, 125 insertions(+), 125 deletions(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index 72dd3b6220c..3b71c4b9dd4 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -17,7 +17,6 @@ from sage.categories.sets_cat import Sets from sage.categories.homsets import HomsetsCategory from sage.matrix.constructor import matrix -from sage.misc.flatten import flatten from sage.misc.misc_c import prod from sage.rings.infinity import Infinity from sage.rings.integer import Integer @@ -393,7 +392,7 @@ def _universal_cover_dict(self): z[i] = FG.one() rels.append(z[0]*z[1].inverse()*z[2]) G = FG.quotient(rels) - char = {g : G.gen(i) for i,g in enumerate(gens)} + char = {g: G.gen(i) for i, g in enumerate(gens)} for e in edges: if e not in gens: char[e] = G.one() @@ -514,7 +513,7 @@ def covering_map(self, character): grelems.append(cells_dict[(f.nondegenerate(), g)].apply_degeneracies(*f.degeneracies())) faces_dict[cell] = grelems cover = SimplicialSet(faces_dict, base_point=cells_dict[(self.base_point(), G.one())]) - cover_map_data = {c : s[0] for (s,c) in cells_dict.items()} + cover_map_data = {c: s[0] for (s, c) in cells_dict.items()} return SimplicialSetMorphism(data=cover_map_data, domain=cover, codomain=self) def cover(self, character): @@ -607,7 +606,6 @@ def _canonical_twisting_operator(self): Quotient of Univariate Laurent Polynomial Ring in F1 over Integer Ring by the ideal (-1 + F1^2) """ - from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing G, d = self._universal_cover_dict() phi = G.abelianization_map() abelG, R, I, images = G.abelianization_to_algebra(ZZ) @@ -618,8 +616,8 @@ def _canonical_twisting_operator(self): return res def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augmented=False, - cochain=False, verbose=False, subcomplex=None, - check=False): + cochain=False, verbose=False, subcomplex=None, + check=False): r""" Return the normalized chain complex twisted by some operator. @@ -629,31 +627,31 @@ def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augment INPUT: - ``twisting_operator`` -- a dictionary, associating the twist of each - simplex. If it is not given, the canonical one (associated to the - laurent polynomial ring abelianization of the fundamental group, ignoring - torsion) is used. + simplex. If it is not given, the canonical one (associated to the + laurent polynomial ring abelianization of the fundamental group, ignoring + torsion) is used. - ``dimensions`` -- if ``None``, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. - ``augmented`` (optional, default ``False``) -- if ``True``, - return the augmented chain complex (that is, include a class - in dimension `-1` corresponding to the empty cell). + return the augmented chain complex (that is, include a class + in dimension `-1` corresponding to the empty cell). - ``cochain`` (optional, default ``False``) -- if ``True``, - return the cochain complex (that is, the dual of the chain - complex). + return the cochain complex (that is, the dual of the chain + complex). - ``verbose`` (optional, default ``False``) -- ignored. - ``subcomplex`` (optional, default ``None``) -- if present, - compute the chain complex relative to this subcomplex. + compute the chain complex relative to this subcomplex. - ``check`` (optional, default ``False``) -- If ``True``, make - sure that the chain complex is actually a chain complex: - the differentials are composable and their product is zero. + sure that the chain complex is actually a chain complex: + the differentials are composable and their product is zero. The normalized chain complex of a simplicial set is isomorphic to the chain complex obtained by modding out by degenerate @@ -768,7 +766,7 @@ def twist(s): if augmented and first == 0: differentials[first-1] = matrix(base_ring, 0, 1) differentials[first] = matrix(base_ring, 1, rank, - [1] * rank) + [1] * rank) else: differentials[first] = matrix(base_ring, 0, rank) @@ -800,7 +798,7 @@ def twist(s): sign *= -1 differentials[d] = matrix(base_ring, old_rank, - rank, matrix_data) + rank, matrix_data) else: rank = 0 @@ -884,6 +882,7 @@ def twisted_homology(self, n, reduced=False): CC = self.twisted_chain_complex() M1 = CC.differential(n).T M2 = CC.differential(n + 1).T + def convert_to_polynomial(p): if hasattr(p, "lift"): return p.lift()._as_extended_polynomial() diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index b61fb1024c3..9dfdc174a36 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -320,6 +320,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): William Stein, David Joyner, and Robert Bradshaw - Travis Scrimshaw (09-2013): Cleaned-up and added a few extra methods """ + def __init__(self, parent, f, n=0): r""" Create the Laurent polynomial `t^n \cdot f`. @@ -351,15 +352,15 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): CommutativeAlgebraElement.__init__(self, parent) if isinstance(f, LaurentPolynomial_univariate): - n += (f).__n - if (f).__u._parent is parent._R: - f = (f).__u + n += ( < LaurentPolynomial_univariate > f).__n + if ( < LaurentPolynomial_univariate > f).__u._parent is parent._R: + f = ( < LaurentPolynomial_univariate > f).__u else: - f = parent._R((f).__u) + f = parent._R(( < LaurentPolynomial_univariate > f).__u) elif (not isinstance(f, Polynomial)) or (parent is not f.parent()): if isinstance(f, dict): v = min(f) if f else 0 - f = {i-v: c for i,c in f.items()} + f = {i-v: c for i, c in f.items()} n += v f = parent._R(f) @@ -555,7 +556,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): self.__n = 0 return # we already caught the infinity and zero cases - cdef long v = self.__u.valuation() + cdef long v = self.__u.valuation() self.__n += v self.__u = self.__u >> v @@ -592,11 +593,11 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): elif e == 0: var = "" else: - var = "*{}^{}".format(X,e) - s += "{}{}".format(x,var) + var = "*{}^{}".format(X, e) + s += "{}{}".format(x, var) first = False s = s.replace(" + -", " - ") - s = s.replace(" 1*"," ") + s = s.replace(" 1*", " ") s = s.replace(" -1*", " -") return s[1:] @@ -653,19 +654,19 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): elif e == 0: var = "" elif e > 0: - var = "|{}^{{{}}}".format(X,e) + var = "|{}^{{{}}}".format(X, e) if e >= 0: - s += "{}{}".format(x,var) - else: # negative e + s += "{}{}".format(x, var) + else: # negative e if e == -1: s += "\\frac{{{}}}{{{}}}".format(x, X) else: - s += "\\frac{{{}}}{{{}^{{{}}}}}".format(x, X,-e) + s += "\\frac{{{}}}{{{}^{{{}}}}}".format(x, X, -e) first = False s = s.replace(" + -", " - ") - s = s.replace(" 1|"," ") + s = s.replace(" 1|", " ") s = s.replace(" -1|", " -") - s = s.replace("|","") + s = s.replace("|", "") return s[1:] @@ -701,7 +702,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): # degrees cdef long result = 0 cdef long result_mon - cdef int i,j + cdef int i, j cdef long var_hash_name = hash(self.__u._parent._names[0]) for i in range(self.__u.degree()+1): result_mon = hash(self.__u[i]) @@ -760,7 +761,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): self.__u[start:stop:step] # error out, see issue #18940 stop = stop - self.__n if stop is not None else self.__u.degree() + 1 f = self.__u[:stop] - ret = self._new_c() + ret = self._new_c() ret.__u = f ret.__n = self.__n ret._normalize() @@ -913,11 +914,11 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): j = i - self.__n if j >= 0: self.__u._unsafe_mutate(j, value) - else: # off to the left + else: # off to the left if value != 0: self.__n = self.__n + j R = self._parent.base_ring() - coeffs = [value] + [R.zero() for _ in range(1,-j)] + self.__u.list() + coeffs = [value] + [R.zero() for _ in range(1, -j)] + self.__u.list() self.__u = self.__u._parent(coeffs) self._normalize() @@ -945,7 +946,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): ALGORITHM: Shift the unit parts to align them, then add. """ - cdef LaurentPolynomial_univariate right = right_m + cdef LaurentPolynomial_univariate right = right_m cdef long m cdef LaurentPolynomial_univariate ret @@ -969,8 +970,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): f1 = self.__u f2 = right.__u # 3. Add - ret = self._new_c() - ret.__u = (f1 + f2) + ret = self._new_c() + ret.__u = (f1 + f2) ret.__n = m ret._normalize() return ret @@ -989,7 +990,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): ALGORITHM: Shift the unit parts to align them, then subtract. """ - cdef LaurentPolynomial_univariate right = right_m + cdef LaurentPolynomial_univariate right = right_m cdef long m cdef LaurentPolynomial_univariate ret @@ -1009,8 +1010,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): f1 = self.__u << self.__n - m f2 = right.__u # 3. Subtract - ret = self._new_c() - ret.__u = (f1 - f2) + ret = self._new_c() + ret.__u = (f1 - f2) ret.__n = m ret._normalize() return ret @@ -1042,8 +1043,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): -1 - t^5 """ cdef LaurentPolynomial_univariate ret - ret = self._new_c() - ret.__u = -self.__u + ret = self._new_c() + ret.__u = -self.__u ret.__n = self.__n # No need to normalize return ret @@ -1058,10 +1059,10 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: f*g x^-3 + x^-2 + x^-1 + x^8 """ - cdef LaurentPolynomial_univariate right = right_r + cdef LaurentPolynomial_univariate right = right_r cdef LaurentPolynomial_univariate ret - ret = self._new_c() - ret.__u = (self.__u * right.__u) + ret = self._new_c() + ret.__u = (self.__u * right.__u) ret.__n = self.__n + right.__n ret._normalize() return ret @@ -1076,8 +1077,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): 3*x^-3 + 3*x + 3*x^2 + 9*x^4 """ cdef LaurentPolynomial_univariate ret - ret = self._new_c() - ret.__u = self.__u._rmul_(c) + ret = self._new_c() + ret.__u = self.__u._rmul_(c) ret.__n = self.__n ret._normalize() return ret @@ -1092,8 +1093,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): 3*x^-3 + 3*x + 3*x^2 + 9*x^4 """ cdef LaurentPolynomial_univariate ret - ret = self._new_c() - ret.__u = self.__u._lmul_(c) + ret = self._new_c() + ret.__u = self.__u._lmul_(c) ret.__n = self.__n ret._normalize() return ret @@ -1170,10 +1171,10 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: 1 // f 0 """ - cdef LaurentPolynomial_univariate right = rhs + cdef LaurentPolynomial_univariate right = rhs cdef LaurentPolynomial_univariate ret - ret = self._new_c() - ret.__u = (self.__u // right.__u) + ret = self._new_c() + ret.__u = (self.__u // right.__u) ret.__n = self.__n - right.__n ret._normalize() return ret @@ -1196,7 +1197,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): 1 + 4*t^2 + 6*t^4 + 4*t^6 + t^8 """ cdef LaurentPolynomial_univariate ret - ret = self._new_c() + ret = self._new_c() ret.__u = self.__u ret.__n = self.__n + k # No need to normalize @@ -1215,7 +1216,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): 1 + 4*t^2 + 6*t^4 + 4*t^6 + t^8 """ cdef LaurentPolynomial_univariate ret - ret = self._new_c() + ret = self._new_c() ret.__u = self.__u ret.__n = self.__n + k # No need to normalize @@ -1234,7 +1235,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): t^-14 + 4*t^-12 + 6*t^-10 + 4*t^-8 + t^-6 """ cdef LaurentPolynomial_univariate ret - ret = self._new_c() + ret = self._new_c() ret.__u = self.__u ret.__n = self.__n - k # No need to normalize @@ -1256,7 +1257,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: (x^-2 + x)*(x^-2 + 1) / ((x^-5 + x^-8)*(x + 2)) (x^6 + x^4)/(x + 2) """ - cdef LaurentPolynomial_univariate right = rhs + cdef LaurentPolynomial_univariate right = rhs if right.__u.is_zero(): raise ZeroDivisionError return self * ~right @@ -1282,8 +1283,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): Fraction Field of Univariate Polynomial Ring in t over Rational Field """ cdef LaurentPolynomial_univariate ret - if self.__u.is_constant(): # this has a single term c*x^n - ret = self._new_c() + if self.__u.is_constant(): # this has a single term c*x^n + ret = self._new_c() if self.__u.is_unit(): ret.__u = self.__u.inverse_of_unit() ret.__n = -self.__n @@ -1357,9 +1358,9 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: gcd((t^-2 + t)*(t + t^-1), (t^5 + t^8)*(1 + t^-2)) t^-3 + t^-1 + 1 + t^2 """ - b = self._parent(right) + b = self._parent(right) cdef LaurentPolynomial_univariate ret - ret = self._new_c() + ret = self._new_c() ret.__u = self.__u.gcd(b.__u) ret.__n = min(self.__n, b.__n) ret._normalize() @@ -1402,15 +1403,15 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: num == q * den + r True """ - cdef LaurentPolynomial_univariate right = other + cdef LaurentPolynomial_univariate right = other q, r = self.__u.quo_rem(right.__u) cdef LaurentPolynomial_univariate ql, qr - ql = self._new_c() - ql.__u = q + ql = self._new_c() + ql.__u = q ql.__n = self.__n - right.__n ql._normalize() - qr = self._new_c() - qr.__u = r + qr = self._new_c() + qr.__u = r qr.__n = self.__n qr._normalize() return ql, qr @@ -1455,7 +1456,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: f > g True """ - cdef LaurentPolynomial_univariate right = right_r + cdef LaurentPolynomial_univariate right = right_r zero = self._parent.base_ring().zero() @@ -1516,8 +1517,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): if n <= self.valuation(): return self._parent.zero() cdef LaurentPolynomial_univariate ret - ret = self._new_c() - ret.__u = self.__u.truncate(n - self.__n) + ret = self._new_c() + ret.__u = self.__u.truncate(n - self.__n) ret.__n = self.__n ret._normalize() return ret @@ -1618,7 +1619,6 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): """ return self.__n == 0 and self.__u.is_constant() - def is_square(self, root=False): r""" Return whether this Laurent polynomial is a square. @@ -1700,7 +1700,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): """ from copy import copy cdef LaurentPolynomial_univariate ret - ret = self._new_c() + ret = self._new_c() ret.__u = copy(self.__u) ret.__n = self.__n # No need to normalize @@ -1793,8 +1793,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): try: # call _derivative() recursively on coefficients u = [coeff._derivative(var) for coeff in self.__u.list(copy=False)] - ret = self._new_c() - ret.__u = self._parent._R(u) + ret = self._new_c() + ret.__u = self._parent._R(u) ret.__n = self.__n ret._normalize() return ret @@ -1808,8 +1808,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): cdef list a = self.__u.list(copy=True) for m in range(len(a)): a[m] *= n + m - ret = self._new_c() - ret.__u = self._parent._R(a) + ret = self._new_c() + ret.__u = self._parent._R(a) ret.__n = self.__n - 1 ret._normalize() return ret @@ -1865,16 +1865,16 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): cdef list a = self.__u.list(copy=False) if n < 0: - v = [a[i]/(n+i+1) for i in range(min(-1-n,len(a)))] + [0] + v = [a[i]/(n+i+1) for i in range(min(-1-n, len(a)))] + [0] else: v = [] - v += [a[i]/(n+i+1) for i in range(max(-n,0), len(a))] + v += [a[i]/(n+i+1) for i in range(max(-n, 0), len(a))] try: u = self._parent._R(v) except TypeError: raise ArithmeticError("coefficients of integral cannot be coerced into the base ring") - ret = self._new_c() - ret.__u = u + ret = self._new_c() + ret.__u = u ret.__n = n + 1 ret._normalize() return ret @@ -1907,7 +1907,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): """ if kwds: f = self.subs(**kwds) - if x: # If there are non-keyword arguments + if x: # If there are non-keyword arguments return f(*x) else: return f @@ -1935,14 +1935,14 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): """ cdef LaurentPolynomial_univariate u, d pf = self.__u.factor() - u = self._new_c() + u = self._new_c() u.__u = pf.unit() u.__n = self.__n u._normalize() f = [] for t in pf: - d = self._new_c() + d = self._new_c() d.__u = t[0] d.__n = 0 d._normalize() @@ -2006,8 +2006,8 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): """ dres = {} for (e, c) in self.dict().items(): - if e > 0 : - dres[(e,0)] = c + if e > 0: + dres[(e, 0)] = c else: - dres[(0,-e)] = c + dres[(0, -e)] = c return self.parent()._extended_ring(dres) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index b428560c1ae..5d3628cb30a 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -22,6 +22,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ Multivariate Laurent polynomials. """ + def __init__(self, parent, x, mon=None, reduce=True): """ Currently, one can only create LaurentPolynomials out of dictionaries @@ -120,8 +121,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): x = {k.esub(self._mon): x_k for k, x_k in x.iteritems()} elif (isinstance(x, LaurentPolynomial_mpair) and parent.variable_names() == x.parent().variable_names()): - self._mon = (x)._mon - x = (x)._poly + self._mon = ( < LaurentPolynomial_mpair > x)._mon + x = ( < LaurentPolynomial_mpair > x)._poly else: # since x should coerce into parent, _mon should be (0,...,0) self._mon = ETuple({}, int(parent.ngens())) self._poly = parent._R(x) @@ -279,26 +280,26 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): # self._parent.variable_names(), # self._parent.base_ring() # ) - cdef dict D = self._poly.dict() + cdef dict D = self._poly.dict() cdef ETuple e if i is None: e = None for k in D: if e is None: - e = k + e = k else: e = e.emin(k) if not e.is_constant(): - self._poly = (self._poly // self._poly._parent({e: 1})) + self._poly = (self._poly // self._poly._parent({e: 1})) self._mon = self._mon.eadd(e) else: e = None for k in D: if e is None or k[i] < e: - e = k[i] + e = k[i] if e > 0: - self._poly = (self._poly // self._poly._parent.gen(i)) + self._poly = (self._poly // self._poly._parent.gen(i)) self._mon = self._mon.eadd_p(e, i) cdef _compute_polydict(self): @@ -312,7 +313,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ # cdef dict D = self._poly._mpoly_dict_recursive(self._parent.variable_names(), # self._parent.base_ring()) - cdef dict D = self._poly.dict() + cdef dict D = self._poly.dict() cdef dict DD if self._mon.is_constant(): self._prod = PolyDict(D) @@ -576,11 +577,11 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): for (e, c) in self.dict().items(): exps = [] for t in e: - if t > 0 : + if t > 0: exps += [t, 0] else: exps += [0, -t] - dres [tuple(exps)] = c + dres[tuple(exps)] = c return self.parent()._extended_ring(dres) def iterator_exp_coeff(self): @@ -649,7 +650,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ if parent(mon) != self._parent: raise TypeError("input must have the same parent") - cdef LaurentPolynomial_mpair m = mon + cdef LaurentPolynomial_mpair m = mon if m._prod is None: m._compute_polydict() if self._prod is None: @@ -738,7 +739,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ if mon.parent() is not self._parent: mon = self._parent(mon) - cdef LaurentPolynomial_mpair m = mon + cdef LaurentPolynomial_mpair m = mon if self._prod is None: self._compute_polydict() if m._prod is None: @@ -808,7 +809,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ if self._prod is None: self._compute_polydict() - return self._prod.dict() + return < dict > self._prod.dict() def _fraction_pair(self): """ @@ -850,7 +851,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): x + y + z + y^-1 """ cdef LaurentPolynomial_mpair ans = self._new_c() - cdef LaurentPolynomial_mpair right = _right + cdef LaurentPolynomial_mpair right = _right ans._mon, a, b = self._mon.combine_to_positives(right._mon) if not a.is_constant(): ans._poly = self._poly * self._poly._parent({a: 1}) @@ -875,7 +876,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): -y - z + y^-1 """ cdef LaurentPolynomial_mpair ans = self._new_c() - cdef LaurentPolynomial_mpair right = _right + cdef LaurentPolynomial_mpair right = _right cdef ETuple a, b ans._mon, a, b = self._mon.combine_to_positives(right._mon) if not a.is_constant(): @@ -911,7 +912,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): sage: (s+q)/(q^2*t^(-2)) s*q^-2*t^2 + q^-1*t^2 """ - cdef LaurentPolynomial_mpair right = rhs + cdef LaurentPolynomial_mpair right = rhs if right.is_zero(): raise ZeroDivisionError if right._poly.is_term(): @@ -1000,8 +1001,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): x*y + x*z + 1 + y^-1*z """ cdef LaurentPolynomial_mpair ans = self._new_c() - ans._mon = self._mon.eadd((right)._mon) - ans._poly = self._poly * (right)._poly + ans._mon = self._mon.eadd(( < LaurentPolynomial_mpair > right)._mon) + ans._poly = self._poly * ( < LaurentPolynomial_mpair > right)._poly return ans cpdef _floordiv_(self, right): @@ -1040,7 +1041,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): b + 1 """ cdef LaurentPolynomial_mpair ans = self._new_c() - cdef LaurentPolynomial_mpair rightl = right + cdef LaurentPolynomial_mpair rightl = right self._normalize() rightl._normalize() ans._mon = self._mon.esub(rightl._mon) @@ -1092,8 +1093,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): selfl._poly = self._poly selfl._mon = self._mon cdef LaurentPolynomial_mpair rightl = self._new_c() - rightl._poly = ( right)._poly - rightl._mon = ( right)._mon + rightl._poly = (< LaurentPolynomial_mpair > right)._poly + rightl._mon = (< LaurentPolynomial_mpair > right)._mon selfl._normalize() rightl._normalize() @@ -1126,15 +1127,15 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ if self._prod is None: self._compute_polydict() - if ( right)._prod is None: - ( right)._compute_polydict() + if (< LaurentPolynomial_mpair > right)._prod is None: + (< LaurentPolynomial_mpair > right)._compute_polydict() try: sortkey = self._parent.term_order().sortkey except AttributeError: sortkey = None - return self._prod.rich_compare((right)._prod, + return self._prod.rich_compare(( < LaurentPolynomial_mpair > right)._prod, op, sortkey) def exponents(self): @@ -1171,7 +1172,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): if not x: return self._poly.total_degree() + sum(self._mon) - cdef tuple g = self._parent.gens() + cdef tuple g = self._parent.gens() cdef Py_ssize_t i cdef bint no_generator_found = True for i in range(len(g)): @@ -1609,14 +1610,14 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): u = self.parent(pf.unit()) - cdef tuple g = self._parent.gens() + cdef tuple g = self._parent.gens() for i in self._mon.nonzero_positions(): u *= g[i] ** self._mon[i] cdef list f = [] cdef dict d for t in pf: - d = (t[0].dict()) + d = (t[0].dict()) if len(d) == 1: # monomials are units u *= self.parent(d) ** t[1] else: @@ -1728,14 +1729,14 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): val *= d[i]**v[i] df[v] = R(h(val)) - ans = self._new_c() + ans = self._new_c() ans._prod = PolyDict(df) ans._mon = self._mon if new_ring is None: S = self._poly._parent else: S = self._poly._parent.change_ring(R) - ans._poly = S({v.esub(ans._mon): df[v] for v in df}) + ans._poly = S({v.esub(ans._mon): df[v] for v in df}) if new_ring is not None: return new_ring(ans) return ans @@ -1791,8 +1792,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): x = 0 for i in range(n): if not mat.get_is_zero_unsafe(j, i): - x += ( v[i]) * int(mat.get_unsafe(j, i)) - if x < ( mon[j]): + x += (< int > v[i]) * int(mat.get_unsafe(j, i)) + if x < (< int > mon[j]): mon[j] = x exp[j] = x dr[ETuple(exp)] = d[v] @@ -1801,10 +1802,10 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): for v in dr: dr[v] = self._parent._base(h(dr[v])) - ans = self._new_c() + ans = self._new_c() ans._prod = PolyDict(dr) ans._mon = ETuple(mon) - ans._poly = self._poly._parent({v.esub(ans._mon): dr[v] for v in dr}) + ans._poly = self._poly._parent({v.esub(ans._mon): dr[v] for v in dr}) if new_ring is not None: return new_ring(ans) return ans @@ -1875,10 +1876,10 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): S = self._poly._parent else: S = self._poly._parent.change_ring(new_ring._base) - ans = self._new_c() + ans = self._new_c() ans._prod = PolyDict(dr) ans._mon = mon - ans._poly = S({v.esub(ans._mon): dr[v] for v in dr}) + ans._poly = S({v.esub(ans._mon): dr[v] for v in dr}) if new_ring is not None: return new_ring(ans) return ans From e4c6c4a3574aa7b409a15b3d44a923abc844bf56 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Tue, 14 Nov 2023 12:55:10 +0100 Subject: [PATCH 07/11] Update src/sage/categories/simplicial_sets.py Co-authored-by: John H. Palmieri --- src/sage/categories/simplicial_sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index 3b71c4b9dd4..f9b8e2ab63f 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -827,7 +827,7 @@ def twisted_homology(self, n, reduced=False): - ``n`` - a positive integer. - ``reduced`` - (default: False) if set to True, the presentation matrix - will be reduced. + will be reduced. EXAMPLES:: From 11cbf0cfa16568db7383a66ed52ad7d991b5ef97 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Wed, 15 Nov 2023 20:22:57 +0100 Subject: [PATCH 08/11] Remove unncessary line in doctest. --- src/sage/categories/simplicial_sets.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index f9b8e2ab63f..f568c4cef23 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -597,8 +597,6 @@ def _canonical_twisting_operator(self): sage: list(d.values())[0].parent() Multivariate Laurent Polynomial Ring in f2, f3 over Integer Ring sage: Y = simplicial_sets.RealProjectiveSpace(2) - sage: Y._canonical_twisting_operator() - {f: F1bar} sage: d2 = Y._canonical_twisting_operator() sage: d2 {f: F1bar} From 7df77de0a032fc813942816d91c34bec6af5da9e Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Mon, 20 Nov 2023 13:50:24 +0100 Subject: [PATCH 09/11] make sure we get twisting of arbitrary simplices --- src/sage/categories/simplicial_sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index f568c4cef23..99b06e1628e 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -712,7 +712,7 @@ def twist(s): if s in twop: return twop[s] if s.dimension() > 1: - return twist(self.face_data()[s][-1]) + return twist(self.face(s,s.dimension())) return 1 base_ring = cm.common_parent(*twop.values()) From f50d62f8b27a642bfcab647d9704cb5770ae6af5 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Tue, 21 Nov 2023 15:13:36 +0100 Subject: [PATCH 10/11] Switch to libsingular backend. --- src/sage/categories/simplicial_sets.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index 99b06e1628e..f2668656555 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -796,12 +796,12 @@ def twist(s): sign *= -1 differentials[d] = matrix(base_ring, old_rank, - rank, matrix_data) + rank, matrix_data, sparse=False) else: rank = 0 current = [] - differentials[d] = matrix(base_ring, old_rank, rank) + differentials[d] = matrix(base_ring, old_rank, rank, sparse=False) if cochain: new_diffs = {} @@ -874,6 +874,13 @@ def twisted_homology(self, n, reduced=False): [0 0 0 0 1] """ + from sage.libs.singular.function import singular_function + from sage.libs.singular.option import opt_verb + opt_verb['not_warn_sb'] = True + singstd = singular_function("std") + singsyz = singular_function("syz") + singred = singular_function("reduce") + singlift = singular_function("lift") G, d = self._universal_cover_dict() phi = G.abelianization_map() abelG, R, I, images = G.abelianization_to_algebra(ZZ) @@ -895,7 +902,7 @@ def convert_to_polynomial(p): GBI = RP.ideal(GB) def reduce_laurent(a): - return a._singular_().reduce(GBI)._sage_() + return singred(a, GBI, ring=RP) def group_to_polynomial(el, RP): res = RP.one() @@ -915,7 +922,7 @@ def mkernel(M): n = res.ncols() for g in (IP+JP).gens(): res = res.stack(g*identity_matrix(n)) - syz = res.T._singular_().syz()._sage_() + syz = matrix(singsyz(res.T, ring=res.base_ring())).T trimmed = syz.T.submatrix(0, 0, syz.ncols(), M.nrows()) trimmed = trimmed.apply_map(reduce_laurent) to_delete = [i for (i, r) in enumerate(trimmed.rows()) if not r] @@ -927,7 +934,7 @@ def lift_to_submodule(S, M): res = M for g in GB: res = res.stack(g*identity_matrix(M.ncols())) - singres = res.T._singular_().lift(S.T._singular_())._sage_() + singres = matrix(singlift(res.T, S.T,ring=res.base_ring())) return singres.submatrix(0, 0, M.nrows(), S.nrows()) def mgb(M): @@ -936,7 +943,7 @@ def mgb(M): res = M for g in GB: res = res.stack(g*identity_matrix(M.ncols())) - sres = res.T._singular_().std()._sage_().T + sres = matrix(singstd(res.T, ring=RP)) to_delete = [i for i, r in enumerate(sres.apply_map(reduce_laurent)) if not r] return sres.delete_rows(to_delete) M2 = border_matrix(n+1) @@ -960,8 +967,9 @@ def mgb(M): for g in (IP+JP).gens(): resmat = resmat.stack(g * identity_matrix(resmat.ncols())) if reduced: - resmat = resmat.T._singular_().std()._sage_().T + resmat = matrix(singstd(resmat.T, ring=RP)) SM = AM.submodule(resmat) + opt_verb.reset_default() return AM.quotient_module(SM) def is_simply_connected(self): From fb77773bd3dfa8c6985d8080b6a0069a1c5f3eb8 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Tue, 21 Nov 2023 16:33:03 +0100 Subject: [PATCH 11/11] Fix bug consisting in applying twice the abelianization map. --- src/sage/categories/simplicial_sets.py | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index f2668656555..19ec792d9ab 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -593,7 +593,7 @@ def _canonical_twisting_operator(self): sage: X = simplicial_sets.Torus() sage: d = X._canonical_twisting_operator() sage: d - {(s_0 v_0, sigma_1): f2*f3^-1, (sigma_1, s_0 v_0): f3, (sigma_1, sigma_1): f2} + {(s_0 v_0, sigma_1): f3, (sigma_1, s_0 v_0): f2*f3^-1, (sigma_1, sigma_1): f2} sage: list(d.values())[0].parent() Multivariate Laurent Polynomial Ring in f2, f3 over Integer Ring sage: Y = simplicial_sets.RealProjectiveSpace(2) @@ -610,7 +610,7 @@ def _canonical_twisting_operator(self): QRP = R.quotient_ring(I) res = dict() for s, el in d.items(): - res[s] = QRP(prod(images[abs(a)-1]**sign(a) for a in phi(el).Tietze())) + res[s] = QRP(prod(images[abs(a)-1]**sign(a) for a in el.Tietze())) return res def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augmented=False, @@ -675,10 +675,10 @@ def twisted_chain_complex(self, twisting_operator=None, dimensions=None, augment sage: X = simplicial_sets.Torus() sage: C = X.twisted_chain_complex() sage: C.differential(1) - [f2*f3^-1 - 1 f3 - 1 f2 - 1] + [ f3 - 1 f2*f3^-1 - 1 f2 - 1] sage: C.differential(2) - [ 1 f3] - [f2*f3^-1 1] + [ 1 f2*f3^-1] + [ f3 1] [ -1 -1] sage: C.differential(3) [] @@ -873,6 +873,21 @@ def twisted_homology(self, n, reduced=False): [0 0 0 1 0] [0 0 0 0 1] + TESTS:: + + sage: X = simplicial_sets.PresentationComplex(groups.presentation.FGAbelian((3,2))) + sage: TW2 = X.twisted_homology(2, reduced=True) + sage: M = TW2.relations_matrix() + sage: from sage.libs.singular.function import singular_function + sage: vdim = singular_function("vdim") + sage: vdim(M.T, ring=M.base_ring()) + // ** considering the image in Q[...] + // ** _ is no standard basis + 5 + sage: X.universal_cover().homology(2) + Z^5 + sage: from sage.libs.singular.function import singular_function + """ from sage.libs.singular.function import singular_function from sage.libs.singular.option import opt_verb @@ -948,6 +963,7 @@ def mgb(M): return sres.delete_rows(to_delete) M2 = border_matrix(n+1) if M1.nrows() == 0: + opt_verb.reset_default() return (RP**0).quotient_module([]) K = mkernel(M1) DK = mkernel(K) @@ -963,6 +979,7 @@ def mgb(M): AM = RP ** K.nrows() if resmat.ncols() == 0: SM = AM.submodule([]) + opt_verb.reset_default() return AM.quotient_module(SM) for g in (IP+JP).gens(): resmat = resmat.stack(g * identity_matrix(resmat.ncols()))