def in_degree(self, n): """ The matrix representing this morphism in degree n INPUT: - ``n`` -- degree EXAMPLES:: sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) sage: D = ChainComplex({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) sage: f.in_degree(0) [1] Note that if the matrix is not specified in the definition of the map, it is assumed to be zero:: sage: f.in_degree(2) [] sage: f.in_degree(2).nrows(), f.in_degree(2).ncols() (1, 0) sage: C.free_module(2) Ambient free module of rank 0 over the principal ideal domain Integer Ring sage: D.free_module(2) Ambient free module of rank 1 over the principal ideal domain Integer Ring """ try: return self._matrix_dictionary[n] except KeyError: rows = self.codomain().free_module_rank(n) cols = self.domain().free_module_rank(n) return zero_matrix(self.domain().base_ring(), rows, cols)
def in_degree(self, n): """ The matrix representing this chain homotopy in degree ``n``. INPUT: - ``n`` -- degree EXAMPLES:: sage: from sage.homology.chain_homotopy import ChainHomotopy sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 sage: f = Hom(C, D)({}) sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) sage: H.in_degree(1) [3 1] This returns an appropriately sized zero matrix if the chain homotopy is not defined in degree n:: sage: H.in_degree(-3) [] """ try: return self._matrix_dictionary[n] except KeyError: from sage.matrix.constructor import zero_matrix deg = self.domain().degree_of_differential() rows = self.codomain().free_module_rank(n - deg) cols = self.domain().free_module_rank(n) return zero_matrix(self.domain().base_ring(), rows, cols)
def iter_positive_forms(self): if self.is_reduced(): sub4 = self._calc_iter_reduced_sub4() for (a0, a1, b01, sub4s) in sub4: t = zero_matrix(ZZ, 4) t[0, 0] = a0 t[1, 1] = a1 t[0, 1] = b01 t[1, 0] = b01 for (a2, b02, b12, sub4ss) in sub4s: ts = copy(t) ts[2, 2] = a2 ts[0, 2] = b02 ts[2, 0] = b02 ts[1, 2] = b12 ts[2, 1] = b12 for (a3, b03, b13, b23) in sub4ss: tss = copy(ts) tss[3, 3] = a3 tss[0, 1] = b01 tss[1, 0] = b01 tss[0, 2] = b02 tss[2, 0] = b02 tss[0, 3] = b03 tss[3, 0] = b03 tss.set_immutable() yield tss else: raise NotImplementedError
def invariant_form(self): """ Return the quadratic form preserved by the symplectic group. OUTPUT: A matrix. EXAMPLES:: sage: Sp(4, QQ).invariant_form() [ 0 0 0 1] [ 0 0 1 0] [ 0 -1 0 0] [-1 0 0 0] """ if self._invariant_form is not None: return self._invariant_form R = self.base_ring() d = self.degree() from sage.matrix.constructor import zero_matrix m = zero_matrix(R, d) for i in range(d): m[i, d - i - 1] = 1 if i < d / 2 else -1 m.set_immutable() return m
def __compute_operator_matrix(self,T): r""" Compute the matrix of the operator ``T``. EXAMPLES:: """ R = self._R A = self.basis_matrix().transpose() basis = self.basis() B = zero_matrix(R,len(self._E) * (self._k-1),self.dimension()) for rr in range(len(basis)): g = T(basis[rr]) B.set_block(0,rr,Matrix(R,len(self._E) * (self._k-1),1,[g._F[e]._val[ii,0] for e in range(len(self._E)) for ii in range(self._k-1) ])) try: if not R.is_exact(): smin = min([a.valuation() for a in A.list()+B.list()]) A = R.prime()**(-smin)*A B = R.prime()**(-smin)*B prec = min([a.precision_absolute() for a in A.list()+B.list()]) A = A.parent()([R(x,absprec = prec) for x in A.list()]) B = B.parent()([R(x,absprec = prec) for x in B.list()]) res = (A.solve_right(B)).transpose() res.set_immutable() except ValueError: # save([A,B],'error.log.sobj') # print A.nrows(),A.ncols() # print B.nrows(),B.ncols() raise ValueError,'The hecke operator action is wrong.' return res
def invariant_form(self): """ Return the quadratic form preserved by the symplectic group. OUTPUT: A matrix. EXAMPLES:: sage: Sp(4, QQ).invariant_form() [ 0 0 0 1] [ 0 0 1 0] [ 0 -1 0 0] [-1 0 0 0] """ if self._invariant_form is not None: return self._invariant_form R = self.base_ring() d = self.degree() from sage.matrix.constructor import zero_matrix m = zero_matrix(R, d) for i in range(d): m[i, d-i-1] = 1 if i < d/2 else -1 m.set_immutable() return m
def in_degree(self, n): """ The matrix representing this chain homotopy in degree ``n``. INPUT: - ``n`` -- degree EXAMPLES:: sage: from sage.homology.chain_homotopy import ChainHomotopy sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 sage: f = Hom(C, D)({}) sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) sage: H.in_degree(1) [3 1] This returns an appropriately sized zero matrix if the chain homotopy is not defined in degree n:: sage: H.in_degree(-3) [] """ try: return self._matrix_dictionary[n] except KeyError: from sage.matrix.constructor import zero_matrix deg = self.domain().degree_of_differential() rows = self.codomain().free_module_rank(n-deg) cols = self.domain().free_module_rank(n) return zero_matrix(self.domain().base_ring(), rows, cols)
def iter_positive_forms(self) : if self.is_reduced() : sub4 = self._calc_iter_reduced_sub4() for (a0, a1, b01, sub4s) in sub4 : t = zero_matrix(ZZ, 4) t[0,0] = a0 t[1,1] = a1 t[0,1] = b01 t[1,0] = b01 for (a2, b02, b12, sub4ss) in sub4s : ts = copy(t) ts[2,2] = a2 ts[0,2] = b02 ts[2,0] = b02 ts[1,2] = b12 ts[2,1] = b12 for (a3, b03, b13, b23) in sub4ss : tss = copy(ts) tss[3,3] = a3 tss[0,1] = b01 tss[1,0] = b01 tss[0,2] = b02 tss[2,0] = b02 tss[0,3] = b03 tss[3,0] = b03 tss.set_immutable() yield tss else : raise NotImplementedError
def __apply_atkin_lehner(self,q,f): r""" This function applies an Atkin-Lehner involution to a harmonic cocycle INPUT: - ``q`` - an integer dividing the full level p*Nminus*Nplus - ``f`` - a harmonic cocycle OUTPUT: The harmonic cocycle obtained by hitting f with the Atkin-Lehner at q EXAMPLES: :: """ R=self._R Data=self._X._get_atkin_lehner_data(q) p=self._X._p tmp=[self._U.element_class(self._U,zero_matrix(self._R,self._k-1,1),quick=True) for jj in range(len(self._E))] d1=Data[1] mga=self.embed_quaternion(Data[0]) for jj in range(len(self._E)): t=d1[jj] tmp[jj]+=(t.sign()*f._F[t.label]).l_act_by(p**(-t.power)*mga*t.igamma(self.embed_quaternion)) return HarmonicCocycleElement(self,tmp,from_values=True)
def _denominator(): R = PolynomialRing(ZZ, "T") T = R.gen() denom = R(1) lc = self._f.list()[-1] if lc == 1: # MONIC for i in range(2, self._delta + 1): if self._delta % i == 0: phi = euler_phi(i) G = IntegerModRing(i) ki = G(self._q).multiplicative_order() denom = denom * (T**ki - 1)**(phi // ki) return denom else: # Non-monic x = PolynomialRing(self._Fq, "x").gen() f = x**self._delta - lc L = f.splitting_field("a") roots = [r for r, _ in f.change_ring(L).roots()] roots_dict = dict([(r, i) for i, r in enumerate(roots)]) rootsfrob = [ L.frobenius_endomorphism(self._Fq.degree())(r) for r in roots ] m = zero_matrix(len(roots)) for i, r in enumerate(roots): m[i, roots_dict[rootsfrob[i]]] = 1 return R(R(m.characteristic_polynomial()) // (T - 1))
def __apply_hecke_operator(self,l,f): r""" This function applies a Hecke operator to a harmonic cocycle. INPUT: - ``l`` - an integer - ``f`` - a harmonic cocycle OUTPUT: A harmonic cocycle which is the result of applying the lth Hecke operator to f EXAMPLES: :: """ R=self._R HeckeData,alpha=self._X._get_hecke_data(l) if(self.level()%l==0): factor=QQ(l**(Integer((self._k-2)/2))/(l+1)) else: factor=QQ(l**(Integer((self._k-2)/2))) p=self._X._p alphamat=self.embed_quaternion(alpha) tmp=[self._U.element_class(self._U,zero_matrix(self._R,self._k-1,1),quick=True) for jj in range(len(self._E))] for ii in range(len(HeckeData)): d1=HeckeData[ii][1] mga=self.embed_quaternion(HeckeData[ii][0])*alphamat for jj in range(len(self._E)): t=d1[jj] tmp[jj]+=(t.sign()*f._F[t.label]).l_act_by(p**(-t.power)*mga*t.igamma(self.embed_quaternion)) return HarmonicCocycleElement(self,[factor*x for x in tmp],from_values=True)
def _rank(self, K) : if K is QQ or K in NumberFields() : return len(_jacobi_forms_by_taylor_expansion_coords(self.__index, self.__weight, 0)) ## This is the formula used by Poor and Yuen in Paramodular cusp forms if self.__weight == 2 : delta = len(self.__index.divisors()) // 2 - 1 else : delta = 0 return sum( ModularForms(1, self.__weight + 2 * j).dimension() + j**2 // (4 * self.__index) for j in xrange(self.__index + 1) ) \ + delta ## This is the formula given by Skoruppa in ## Jacobi forms of critical weight and Weil representations ##FIXME: There is some mistake here if self.__weight % 2 != 0 : ## Otherwise the space X(i**(n - 2 k)) is different ## See: Skoruppa, Jacobi forms of critical weight and Weil representations raise NotImplementedError m = self.__index K = CyclotomicField(24 * m, 'zeta') zeta = K.gen(0) quadform = lambda x : 6 * x**2 bilinform = lambda x,y : quadform(x + y) - quadform(x) - quadform(y) T = diagonal_matrix([zeta**quadform(i) for i in xrange(2*m)]) S = sum(zeta**(-quadform(x)) for x in xrange(2 * m)) / (2 * m) \ * matrix([[zeta**(-bilinform(j,i)) for j in xrange(2*m)] for i in xrange(2*m)]) subspace_matrix_1 = matrix( [ [1 if j == i or j == 2*m - i else 0 for j in xrange(m + 1) ] for i in xrange(2*m)] ) subspace_matrix_2 = zero_matrix(ZZ, m + 1, 2*m) subspace_matrix_2.set_block(0,0,identity_matrix(m+1)) T = subspace_matrix_2 * T * subspace_matrix_1 S = subspace_matrix_2 * S * subspace_matrix_1 sqrt3 = (zeta**(4*m) - zeta**(-4*m)) * zeta**(-6*m) rank = (self.__weight - 1/2 - 1) / 2 * (m + 1) \ + 1/8 * ( zeta**(3*m * (2*self.__weight - 1)) * S.trace() + zeta**(3*m * (1 - 2*self.__weight)) * S.trace().conjugate() ) \ + 2/(3*sqrt3) * ( zeta**(4 * m * self.__weight) * (S*T).trace() + zeta**(-4 * m * self.__weight) * (S*T).trace().conjugate() ) \ - sum((j**2 % (m+1))/(m+1) -1/2 for j in range(0,m+1)) if self.__weight > 5 / 2 : return rank else : raise NotImplementedError raise NotImplementedError
def gen(self, i = 0) : if i < self.__n : t = diagonal_matrix(ZZ, i * [0] + [2] + (self.__n - i - 1) * [0]) t.set_immutable() return t elif i >= self.__n and i < (self.__n * (self.__n + 1)) // 2 : i = i - self.__n for r in xrange(self.__n) : if i >= self.__n - r - 1 : i = i - (self.__n - r - 1) continue c = i + r + 1 break t = zero_matrix(ZZ, self.__n) t[r,c] = 1 t[c,r] = 1 t.set_immutable() return t elif not self.__reduced and i >= (self.__n * (self.__n + 1)) // 2 \ and i < self.__n**2 : i = i - (self.__n * (self.__n + 1)) // 2 for r in xrange(self.__n) : if i >= self.__n - r - 1 : i = i - (self.__n - r - 1) continue c = i + r + 1 break t = zero_matrix(ZZ, self.__n) t[r,c] = -1 t[c,r] = -1 t.set_immutable() return t raise ValueError, "Generator not defined"
def matrix(self): """ Return the standard matrix representation of ``self``. .. SEEALSO:: - :meth:`AffineGroup.linear_space()` EXAMPLES:: sage: G = AffineGroup(3, GF(7)) sage: g = G([1,2,3,4,5,6,7,8,0], [10,11,12]) sage: g [1 2 3] [3] x |-> [4 5 6] x + [4] [0 1 0] [5] sage: g.matrix() [1 2 3|3] [4 5 6|4] [0 1 0|5] [-----+-] [0 0 0|1] sage: parent(g.matrix()) Full MatrixSpace of 4 by 4 dense matrices over Finite Field of size 7 sage: g.matrix() == matrix(g) True Composition of affine group elements equals multiplication of the matrices:: sage: g1 = G.random_element() sage: g2 = G.random_element() sage: g1.matrix() * g2.matrix() == (g1*g2).matrix() True """ A = self._A b = self._b parent = self.parent() d = parent.degree() from sage.matrix.constructor import matrix, zero_matrix, block_matrix zero = zero_matrix(parent.base_ring(), 1, d) one = matrix(parent.base_ring(), [[1]]) m = block_matrix(2, 2, [A, b.column(), zero, one]) m.set_immutable() return m
def _an_element_(self): """ Construct a sample morphism. OUTPUT: An element of the homset. EXAMPLES:: sage: P2 = toric_varieties.P2() sage: homset = P2.Hom(P2) sage: homset.an_element() # indirect doctest Scheme endomorphism of 2-d CPR-Fano toric variety covered by 3 affine patches Defn: Defined by sending Rational polyhedral fan in 2-d lattice N to Rational polyhedral fan in 2-d lattice N. """ from sage.matrix.constructor import zero_matrix zero = zero_matrix(self.domain().dimension_relative(), self.codomain().dimension_relative()) return self(zero)
def invariant_form(self): """ Return the quadratic form preserved by the orthogonal group. OUTPUT: A matrix. EXAMPLES:: sage: Sp(4, QQ).invariant_form() [0 0 0 1] [0 0 1 0] [0 1 0 0] [1 0 0 0] """ from sage.matrix.constructor import zero_matrix m = zero_matrix(self.base_ring(), self.degree()) for i in range(self.degree()): m[i, self.degree() - i - 1] = 1 m.set_immutable() return m
def invariant_form(self): """ Return the quadratic form preserved by the orthogonal group. OUTPUT: A matrix. EXAMPLES:: sage: Sp(4, QQ).invariant_form() [0 0 0 1] [0 0 1 0] [0 1 0 0] [1 0 0 0] """ from sage.matrix.constructor import zero_matrix m = zero_matrix(self.base_ring(), self.degree()) for i in range(self.degree()): m[i, self.degree()-i-1] = 1 m.set_immutable() return m
def __compute_operator_matrix(self,T): r""" Compute the matrix of the operator ``T``. EXAMPLES: :: """ R=self._R A=self.basis_matrix().transpose() basis=self.basis() B=zero_matrix(R,len(self._E)*(self._k-1),self.dimension()) for rr in range(len(basis)): g=T(basis[rr]) B.set_block(0,rr,Matrix(R,len(self._E)*(self._k-1),1,[g._F[e]._val[ii,0] for e in range(len(self._E)) for ii in range(self._k-1) ])) try: res=(A.solve_right(B)).transpose() res.set_immutable() return res except ValueError: print A print B raise ValueError
def algebraic_topological_model_delta_complex(K, base_ring=None): r""" Algebraic topological model for cell complex ``K`` with coefficients in the field ``base_ring``. This has the same basic functionality as :func:`algebraic_topological_model`, but it also works for `\Delta`-complexes. For simplicial and cubical complexes it is somewhat slower, though. INPUT: - ``K`` -- a simplicial complex, a cubical complex, or a `\Delta`-complex - ``base_ring`` -- coefficient ring; must be a field OUTPUT: a pair ``(phi, M)`` consisting of - chain contraction ``phi`` - chain complex `M` See :func:`algebraic_topological_model` for the main documentation. The difference in implementation between the two: this uses matrix and vector algebra. The other function does more of the computations "by hand" and uses cells (given as simplices or cubes) to index various dictionaries. Since the cells in `\Delta`-complexes are not as nice, the other function does not work for them, while this function relies almost entirely on the structure of the associated chain complex. EXAMPLES:: sage: from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex as AT_model sage: RP2 = simplicial_complexes.RealProjectivePlane() sage: phi, M = AT_model(RP2, GF(2)) sage: M.homology() {0: Vector space of dimension 1 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2, 2: Vector space of dimension 1 over Finite Field of size 2} sage: T = delta_complexes.Torus() sage: phi, M = AT_model(T, QQ) sage: M.homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} If you want to work with cohomology rather than homology, just dualize the outputs of this function:: sage: M.dual().homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} sage: M.dual().degree_of_differential() 1 sage: phi.dual() Chain homotopy between: Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field and Chain complex morphism: From: Chain complex with at most 3 nonzero terms over Rational Field To: Chain complex with at most 3 nonzero terms over Rational Field In degree 0, the inclusion of the homology `M` into the chain complex `C` sends the homology generator to a single vertex:: sage: K = delta_complexes.Simplex(2) sage: phi, M = AT_model(K, QQ) sage: phi.iota().in_degree(0) [0] [0] [1] In cohomology, though, one needs the dual of every degree 0 cell to detect the degree 0 cohomology generator:: sage: phi.dual().iota().in_degree(0) [1] [1] [1] TESTS:: sage: T = cubical_complexes.Torus() sage: C = T.chain_complex() sage: H, M = AT_model(T, QQ) sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 True sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 True sage: coC = T.chain_complex(cochain=True) sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 True sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 True """ def conditionally_sparse(m): """ Return a sparse matrix if the characteristic is zero. Multiplication of matrices with low density seems to be quicker if the matrices are sparse, when over the rationals. Over finite fields, dense matrices are faster regardless of density. """ if base_ring == QQ: return m.sparse_matrix() else: return m if not base_ring.is_field(): raise ValueError('the coefficient ring must be a field') # The following are all dictionaries indexed by dimension. # For each n, gens[n] is an ordered list of the n-cells generating the complex M. gens = {} pi_data = {} phi_data = {} iota_data = {} for n in range(-1, K.dimension()+1): gens[n] = [] C = K.chain_complex(base_ring=base_ring) n_cells = [] pi_cols = [] iota_cols = {} for dim in range(K.dimension()+1): # old_cells: cells one dimension lower. old_cells = n_cells # n_cells: the standard basis for the vector space C.free_module(dim). n_cells = C.free_module(dim).gens() diff = C.differential(dim) # diff is sparse and low density. Dense matrices are faster # over finite fields, but for low density matrices, sparse # matrices are faster over the rationals. if base_ring != QQ: diff = diff.dense_matrix() rank = len(n_cells) old_rank = len(old_cells) # Create some matrix spaces to try to speed up matrix creation. MS_pi_t = MatrixSpace(base_ring, old_rank, len(gens[dim-1])) pi_old = MS_pi_t.matrix(pi_cols).transpose() iota_cols_old = iota_cols iota_cols = {} pi_cols_old = pi_cols pi_cols = [] phi_old = MatrixSpace(base_ring, rank, old_rank, sparse=(base_ring==QQ)).zero() phi_old_cols = phi_old.columns() phi_old = conditionally_sparse(phi_old) to_be_deleted = [] zero_vector = vector(base_ring, rank) pi_nrows = pi_old.nrows() for c_idx, c in enumerate(n_cells): # c_bar = c - phi(bdry(c)): # Avoid a bug in matrix-vector multiplication (trac 19378): if not diff: c_bar = c pi_bdry_c_bar = False else: if base_ring == QQ: c_bar = c - phi_old * (diff * c) pi_bdry_c_bar = conditionally_sparse(pi_old) * (diff * c_bar) else: c_bar = c - phi_old * diff * c pi_bdry_c_bar = conditionally_sparse(pi_old) * diff * c_bar # One small typo in the published algorithm: it says # "if bdry(c_bar) == 0", but should say # "if pi(bdry(c_bar)) == 0". if not pi_bdry_c_bar: # Append c to list of gens. gens[dim].append(c_idx) # iota(c) = c_bar iota_cols[c_idx] = c_bar # pi(c) = c pi_cols.append(c) else: # Take any u in gens so that lambda_i = <u, pi(bdry(c_bar))> != 0. # u_idx will be the index of the corresponding cell. (u_idx, lambda_i) = pi_bdry_c_bar.leading_item() for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): if u_idx not in to_be_deleted: break # This element/column needs to be deleted from gens and # iota_old. Do that later. to_be_deleted.append(u_idx) # pi(c) = 0. pi_cols.append(zero_vector) for c_j_idx, c_j in enumerate(old_cells): # eta_ij = <u, pi(c_j)>. # That is, eta_ij is the u_idx entry in the vector pi_old * c_j: eta_ij = c_j.dot_product(pi_old.row(u_idx)) if eta_ij: # Adjust phi(c_j). phi_old_cols[c_j_idx] += eta_ij * lambda_i**(-1) * c_bar # Adjust pi(c_j). pi_cols_old[c_j_idx] -= eta_ij * lambda_i**(-1) * pi_bdry_c_bar # The matrices involved have many zero entries. For # such matrices, using sparse matrices is faster over # the rationals, slower over finite fields. phi_old = matrix(base_ring, phi_old_cols, sparse=(base_ring==QQ)).transpose() keep = vector(base_ring, pi_nrows, {i:1 for i in range(pi_nrows) if i not in to_be_deleted}) cols = [v.pairwise_product(keep) for v in pi_cols_old] pi_old = MS_pi_t.matrix(cols).transpose() # Here cols is a temporary storage for the columns of iota. cols = [iota_cols_old[i] for i in sorted(iota_cols_old.keys())] for r in sorted(to_be_deleted, reverse=True): del cols[r] del gens[dim-1][r] iota_data[dim-1] = matrix(base_ring, len(gens[dim-1]), old_rank, cols).transpose() # keep: rows to keep in pi_cols_old. Start with all # columns, then delete those in to_be_deleted. keep = sorted(set(range(pi_nrows)).difference(to_be_deleted)) # Now cols is a temporary storage for columns of pi. cols = [v.list_from_positions(keep) for v in pi_cols_old] pi_data[dim-1] = matrix(base_ring, old_rank, len(gens[dim-1]), cols).transpose() phi_data[dim-1] = phi_old V_gens = VectorSpace(base_ring, len(gens[dim])) if pi_cols: cols = [] for v in pi_cols: cols.append(V_gens(v.list_from_positions(gens[dim]))) pi_cols = cols pi_data[dim] = matrix(base_ring, rank, len(gens[dim]), pi_cols).transpose() cols = [iota_cols[i] for i in sorted(iota_cols.keys())] iota_data[dim] = matrix(base_ring, len(gens[dim]), rank, cols).transpose() # M_data will contain (trivial) matrices defining the differential # on M. Keep track of the sizes using "M_rows" and "M_cols", which are # just the ranks of consecutive graded pieces of M. M_data = {} M_rows = 0 for n in range(K.dimension()+1): M_cols = len(gens[n]) M_data[n] = zero_matrix(base_ring, M_rows, M_cols) M_rows = M_cols M = ChainComplex(M_data, base_ring=base_ring, degree=-1) pi = ChainComplexMorphism(pi_data, C, M) iota = ChainComplexMorphism(iota_data, M, C) phi = ChainContraction(phi_data, pi, iota) return phi, M
def SymplecticPolarGraph(d, q, algorithm=None): r""" Returns the Symplectic Polar Graph `Sp(d,q)`. The Symplectic Polar Graph `Sp(d,q)` is built from a projective space of dimension `d-1` over a field `F_q`, and a symplectic form `f`. Two vertices `u,v` are made adjacent if `f(u,v)=0`. See the page `on symplectic graphs on Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/Sp.html>`_. INPUT: - ``d,q`` (integers) -- note that only even values of `d` are accepted by the function. - ``algorithm`` -- if set to 'gap' then the computation is carried via GAP library interface, computing totally singular subspaces, which is faster for `q>3`. Otherwise it is done directly. EXAMPLES: Computation of the spectrum of `Sp(6,2)`:: sage: g = graphs.SymplecticGraph(6,2) doctest:...: DeprecationWarning: SymplecticGraph is deprecated. Please use sage.graphs.generators.classical_geometries.SymplecticPolarGraph instead. See http://trac.sagemath.org/19136 for details. sage: g.is_strongly_regular(parameters=True) (63, 30, 13, 15) sage: set(g.spectrum()) == {-5, 3, 30} True The parameters of `Sp(4,q)` are the same as of `O(5,q)`, but they are not isomorphic if `q` is odd:: sage: G = graphs.SymplecticPolarGraph(4,3) sage: G.is_strongly_regular(parameters=True) (40, 12, 2, 4) sage: O=graphs.OrthogonalPolarGraph(5,3) sage: O.is_strongly_regular(parameters=True) (40, 12, 2, 4) sage: O.is_isomorphic(G) False sage: graphs.SymplecticPolarGraph(6,4,algorithm="gap").is_strongly_regular(parameters=True) # not tested (long time) (1365, 340, 83, 85) TESTS:: sage: graphs.SymplecticPolarGraph(4,4,algorithm="gap").is_strongly_regular(parameters=True) (85, 20, 3, 5) sage: graphs.SymplecticPolarGraph(4,4).is_strongly_regular(parameters=True) (85, 20, 3, 5) sage: graphs.SymplecticPolarGraph(4,4,algorithm="blah") Traceback (most recent call last): ... ValueError: unknown algorithm! """ if d < 1 or d%2 != 0: raise ValueError("d must be even and greater than 2") if algorithm == "gap": # faster for larger (q>3) fields from sage.libs.gap.libgap import libgap G = _polar_graph(d, q, libgap.SymplecticGroup(d, q)) elif algorithm == None: # faster for small (q<4) fields from sage.modules.free_module import VectorSpace from sage.schemes.projective.projective_space import ProjectiveSpace from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix F = FiniteField(q,"x") M = block_matrix(F, 2, 2, [zero_matrix(F,d/2), identity_matrix(F,d/2), -identity_matrix(F,d/2), zero_matrix(F,d/2)]) V = VectorSpace(F,d) PV = list(ProjectiveSpace(d-1,F)) G = Graph([[tuple(_) for _ in PV], lambda x,y:V(x)*(M*V(y)) == 0], loops = False) else: raise ValueError("unknown algorithm!") G.name("Symplectic Polar Graph Sp("+str(d)+","+str(q)+")") G.relabel() return G
def _find_isomorphism_degenerate(self, polytope): """ Helper to pick an isomorphism of degenerate polygons INPUT: - ``polytope`` -- a :class:`LatticePolytope_PPL_class`. The polytope to compare with. EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL, C_Polyhedron sage: L1 = LatticePolytope_PPL(C_Polyhedron(2, 'empty')) sage: L2 = LatticePolytope_PPL(C_Polyhedron(3, 'empty')) sage: iso = L1.find_isomorphism(L2) # indirect doctest sage: iso(L1) == L2 True sage: iso = L1._find_isomorphism_degenerate(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,4)) sage: L2 = LatticePolytope_PPL((2,1,5)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,), (3,)) sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,-1), (3,-1)) sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,2), (3,1)) sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,2), (3,2)) sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4)) sage: L1.find_isomorphism(L2) Traceback (most recent call last): ... LatticePolytopesNotIsomorphicError: different number of integral points sage: L1 = LatticePolytope_PPL((-1,2), (3,1)) sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,5)) sage: L1.find_isomorphism(L2) Traceback (most recent call last): ... LatticePolytopesNotIsomorphicError: different number of integral points """ from sage.geometry.polyhedron.lattice_euclidean_group_element import \ LatticePolytopesNotIsomorphicError polytope_vertices = polytope.vertices() self_vertices = self.ordered_vertices() # handle degenerate cases if self.n_vertices() == 0: A = zero_matrix(ZZ, polytope.space_dimension(), self.space_dimension()) b = zero_vector(ZZ, polytope.space_dimension()) return LatticeEuclideanGroupElement(A, b) if self.n_vertices() == 1: A = zero_matrix(ZZ, polytope.space_dimension(), self.space_dimension()) b = polytope_vertices[0] return LatticeEuclideanGroupElement(A, b) if self.n_vertices() == 2: self_origin = self_vertices[0] self_ray = self_vertices[1] - self_origin polytope_origin = polytope_vertices[0] polytope_ray = polytope_vertices[1] - polytope_origin Ds, Us, Vs = self_ray.column().smith_form() Dp, Up, Vp = polytope_ray.column().smith_form() assert Vs.nrows() == Vs.ncols() == Vp.nrows() == Vp.ncols() == 1 assert abs(Vs[0, 0]) == abs(Vp[0, 0]) == 1 A = zero_matrix(ZZ, Dp.nrows(), Ds.nrows()) A[0, 0] = 1 A = Up.inverse() * A * Us * (Vs[0, 0] * Vp[0, 0]) b = polytope_origin - A * self_origin try: A = matrix(ZZ, A) b = vector(ZZ, b) except TypeError: raise LatticePolytopesNotIsomorphicError('different lattice') hom = LatticeEuclideanGroupElement(A, b) if hom(self) == polytope: return hom raise LatticePolytopesNotIsomorphicError('different polygons')
def zero_element(self) : t = zero_matrix(ZZ, self.__n) t.set_immutable() return t
def __iter__(self) : if self.index() is infinity : raise ValueError, "infinity is not a true filter index" if self.is_reduced() : ## We only iterate positive definite matrices ## and later build the semidefinite ones ## We first find possible upper left matrices sub2 = self._calc_iter_reduced_sub2() sub3 = self._calc_iter_reduced_sub3() sub4 = self._calc_iter_reduced_sub4() t = zero_matrix(ZZ, 4) t.set_immutable() yield t for a0 in xrange(2, 2 * self.index(), 2) : t = zero_matrix(ZZ, 4) t[3,3] = a0 t.set_immutable() yield t for (a0, a1, b01) in sub2 : t = zero_matrix(ZZ, 4) t[2,2] = a0 t[3,3] = a1 t[2,3] = b01 t[3,2] = b01 t.set_immutable() yield t for (a0, a1, b01, sub3s) in sub3 : t = zero_matrix(ZZ, 4) t[1,1] = a0 t[2,2] = a1 t[1,2] = b01 t[2,1] = b01 for (a2, b02, b12) in sub3s : ts = copy(t) ts[3,3] = a2 ts[1,3] = b02 ts[3,1] = b02 ts[2,3] = b12 ts[3,2] = b12 ts.set_immutable() yield ts for (a0, a1, b01, sub4s) in sub4 : t = zero_matrix(ZZ, 4) t[0,0] = a0 t[1,1] = a1 t[0,1] = b01 t[1,0] = b01 for (a2, b02, b12, sub4ss) in sub4s : ts = copy(t) ts[2,2] = a2 ts[0,2] = b02 ts[2,0] = b02 ts[1,2] = b12 ts[2,1] = b12 for (a3, b03, b13, b23) in sub4ss : tss = copy(ts) tss[3,3] = a3 tss[0,1] = b01 tss[1,0] = b01 tss[0,2] = b02 tss[2,0] = b02 tss[0,3] = b03 tss[3,0] = b03 tss.set_immutable() yield tss #! if self.is_reduced() else : ## We first find possible upper left matrices sub2 = list() for a0 in xrange(2 * self.index(), 2) : for a1 in xrange(2 * self.index(), 2) : # obstruction for t[0,1] B1 = isqrt(a0 * a1) for b01 in xrange(-B1, B1 + 1) : sub2.append((a0,a1,b01)) sub3 = list() for (a0, a1, b01) in sub2 : sub3s = list() for a2 in xrange(2 * self.index(), 2) : # obstruction for t[0,2] B1 = isqrt(a0 * a2) for b02 in xrange(-B1, B1 + 1) : # obstruction for t[1,2] B3 = isqrt(a1 * a2) for b12 in xrange(-B3, B3 + 1) : # obstruction for the minor [0,1,2] of t if a0*a1*a2 - a0*b12**2 + 2*b01*b12*b02 - b01**2*a2 - a1*b02**2 < 0 : continue sub3s.append((a2, b02, b12)) sub3.append((a0, a1, b01, sub3s)) for (a0,a1,b01, sub3s) in sub3 : for (a2, b02, b12) in sub3s : for a3 in xrange(2 * self.index(), 2) : # obstruction for t[0,3] B1 = isqrt(a0 * a3) for b03 in xrange(-B1, B1 + 1) : # obstruction for t[1,3] B3 = isqrt(a1 * a3) for b13 in xrange(-B3, B3 + 1) : # obstruction for the minor [0,1,3] of t if a0*a1*a3 - a0*b13**2 + 2*b01*b13*b03 - b01**2*a3 - a1*b03**2 < 0 : continue # obstruction for t[2,3] B3 = isqrt(a2 * a3) for b23 in xrange(-B3, B3 + 1) : # obstruction for the minor [0,2,3] of t if a0*a2*a3 - a0*b23**2 + 2*b02*b23*b03 - b02**2*a3 - a2*b03**2 < 0 : continue # obstruction for the minor [1,2,3] of t if a1*a2*a3 - a1*b23**2 + 2*b12*b23*b13 - b12**2*a3 - a2*b13**2 < 0 : continue t = matrix(ZZ, 4, [a0, b01, b02, b03, b01, a1, b12, b13, b02, b12, a2, b23, b03, b13, b23, a3], check = False) if t.det() < 0 : continue t.set_immutable() yield t raise StopIteration
def cohomology_complex(self, m): r""" Return the "cohomology complex" `C^*(m)` See [Klyachko]_, equation 4.2. INPUT: - ``m`` -- tuple of integers or `M`-lattice point. A point in the dual lattice of the fan. Must be immutable. OUTPUT: The "cohomology complex" as a chain complex over the :meth:`base_ring`. EXAMPLES:: sage: P3 = toric_varieties.P(3) sage: rays = [(1,0,0), (0,1,0), (0,0,1)] sage: F1 = FilteredVectorSpace(rays, {0:[0], 1:[2], 2:[1]}) sage: F2 = FilteredVectorSpace(rays, {0:[1,2], 1:[0]}) sage: r = P3.fan().rays() sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) sage: tau = Cone([(1,0,0), (0,1,0)]) sage: sigma = Cone([(1, 0, 0)]) sage: M = P3.fan().dual_lattice() sage: m = M(1, 1, 0); m.set_immutable() sage: V.cohomology_complex(m) Chain complex with at most 2 nonzero terms over Rational Field sage: F = CyclotomicField(3) sage: P3 = toric_varieties.P(3).change_ring(F) sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) sage: V.cohomology_complex(m) Chain complex with at most 2 nonzero terms over Cyclotomic Field of order 3 and degree 2 """ fan = self._variety.fan() C = fan.complex() CV = [] F = self.base_ring() for dim in range(1,fan.dim()+1): codim = fan.dim() - dim d_C = C.differential(codim) d_V = [] for j in range(0, d_C.ncols()): tau = fan(dim)[j] d_V_row = [] for i in range(0, d_C.nrows()): sigma = fan(dim-1)[i] if sigma.is_face_of(tau): pr = self.E_quotient_projection(sigma, tau, m) d = d_C[i,j] * pr.matrix().transpose() else: E_sigma = self.E_quotient(sigma, m) E_tau = self.E_quotient(tau, m) d = zero_matrix(F, E_tau.dimension(), E_sigma.dimension()) d_V_row.append(d) d_V.append(d_V_row) d_V = block_matrix(d_V, ring=F) CV.append(d_V) from sage.homology.chain_complex import ChainComplex return ChainComplex(CV, base_ring=self.base_ring())
def SymplecticPolarGraph(d, q, algorithm=None): r""" Returns the Symplectic Polar Graph `Sp(d,q)`. The Symplectic Polar Graph `Sp(d,q)` is built from a projective space of dimension `d-1` over a field `F_q`, and a symplectic form `f`. Two vertices `u,v` are made adjacent if `f(u,v)=0`. See the page `on symplectic graphs on Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/Sp.html>`_. INPUT: - ``d,q`` (integers) -- note that only even values of `d` are accepted by the function. - ``algorithm`` -- if set to 'gap' then the computation is carried via GAP library interface, computing totally singular subspaces, which is faster for `q>3`. Otherwise it is done directly. EXAMPLES: Computation of the spectrum of `Sp(6,2)`:: sage: g = graphs.SymplecticGraph(6,2) doctest:...: DeprecationWarning: SymplecticGraph is deprecated. Please use sage.graphs.generators.classical_geometries.SymplecticPolarGraph instead. See http://trac.sagemath.org/19136 for details. sage: g.is_strongly_regular(parameters=True) (63, 30, 13, 15) sage: set(g.spectrum()) == {-5, 3, 30} True The parameters of `Sp(4,q)` are the same as of `O(5,q)`, but they are not isomorphic if `q` is odd:: sage: G = graphs.SymplecticPolarGraph(4,3) sage: G.is_strongly_regular(parameters=True) (40, 12, 2, 4) sage: O=graphs.OrthogonalPolarGraph(5,3) sage: O.is_strongly_regular(parameters=True) (40, 12, 2, 4) sage: O.is_isomorphic(G) False sage: graphs.SymplecticPolarGraph(6,4,algorithm="gap").is_strongly_regular(parameters=True) # not tested (long time) (1365, 340, 83, 85) TESTS:: sage: graphs.SymplecticPolarGraph(4,4,algorithm="gap").is_strongly_regular(parameters=True) (85, 20, 3, 5) sage: graphs.SymplecticPolarGraph(4,4).is_strongly_regular(parameters=True) (85, 20, 3, 5) sage: graphs.SymplecticPolarGraph(4,4,algorithm="blah") Traceback (most recent call last): ... ValueError: unknown algorithm! """ if d < 1 or d % 2 != 0: raise ValueError("d must be even and greater than 2") if algorithm == "gap": # faster for larger (q>3) fields from sage.libs.gap.libgap import libgap G = _polar_graph(d, q, libgap.SymplecticGroup(d, q)) elif algorithm == None: # faster for small (q<4) fields from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.schemes.projective.projective_space import ProjectiveSpace from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix F = FiniteField(q, "x") M = block_matrix(F, 2, 2, [ zero_matrix(F, d / 2), identity_matrix(F, d / 2), -identity_matrix(F, d / 2), zero_matrix(F, d / 2) ]) V = VectorSpace(F, d) PV = list(ProjectiveSpace(d - 1, F)) G = Graph([[tuple(_) for _ in PV], lambda x, y: V(x) * (M * V(y)) == 0], loops=False) else: raise ValueError("unknown algorithm!") G.name("Symplectic Polar Graph Sp(" + str(d) + "," + str(q) + ")") G.relabel() return G
def __init__(self, matrices, C, D, check=True): """ Create a morphism from a dictionary of matrices. EXAMPLES:: sage: S = simplicial_complexes.Sphere(1) sage: S Minimal triangulation of the 1-sphere sage: C = S.chain_complex() sage: C.differential() {0: [], 1: [ 1 1 0] [ 0 -1 -1] [-1 0 1], 2: []} sage: f = {0:zero_matrix(ZZ,3,3),1:zero_matrix(ZZ,3,3)} sage: G = Hom(C,C) sage: x = G(f) sage: x Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] [0 0 0], 1: [0 0 0] [0 0 0] [0 0 0]} Check that the bug in :trac:`13220` has been fixed:: sage: X = simplicial_complexes.Simplex(1) sage: Y = simplicial_complexes.Simplex(0) sage: g = Hom(X,Y)({0:0, 1:0}) sage: g.associated_chain_complex_morphism() Chain complex morphism: From: Chain complex with at most 2 nonzero terms over Integer Ring To: Chain complex with at most 1 nonzero terms over Integer Ring Check that an error is raised if the matrices are the wrong size:: sage: C = ChainComplex({0: zero_matrix(ZZ, 0, 1)}) sage: D = ChainComplex({0: zero_matrix(ZZ, 0, 2)}) sage: Hom(C,D)({0: matrix(1, 2, [1, 1])}) # 1x2 is the wrong size. Traceback (most recent call last): ... ValueError: matrix in degree 0 is not the right size sage: Hom(C,D)({0: matrix(2, 1, [1, 1])}) # 2x1 is right. Chain complex morphism: From: Chain complex with at most 1 nonzero terms over Integer Ring To: Chain complex with at most 1 nonzero terms over Integer Ring """ if not C.base_ring() == D.base_ring(): raise NotImplementedError( 'morphisms between chain complexes of different' ' base rings are not implemented') d = C.degree_of_differential() if d != D.degree_of_differential(): raise ValueError('degree of differential does not match') from sage.misc.misc import uniq degrees = uniq(C.differential().keys() + D.differential().keys()) initial_matrices = dict(matrices) matrices = dict() for i in degrees: if i - d not in degrees: if not (C.free_module_rank(i) == D.free_module_rank(i) == 0): raise ValueError( '{} and {} are not rank 0 in degree {}'.format( C, D, i)) continue try: matrices[i] = initial_matrices.pop(i) except KeyError: matrices[i] = zero_matrix(C.base_ring(), D.differential(i).ncols(), C.differential(i).ncols(), sparse=True) if check: # All remaining matrices given must be 0x0. if not all(m.ncols() == m.nrows() == 0 for m in initial_matrices.values()): raise ValueError('the remaining matrices are not empty') # Check sizes of matrices. for i in matrices: if (matrices[i].nrows() != D.free_module_rank(i) or matrices[i].ncols() != C.free_module_rank(i)): raise ValueError( 'matrix in degree {} is not the right size'.format(i)) # Check commutativity. for i in degrees: if i - d not in degrees: if not (C.free_module_rank(i) == D.free_module_rank(i) == 0): raise ValueError( '{} and {} are not rank 0 in degree {}'.format( C, D, i)) continue if i + d not in degrees: if not (C.free_module_rank(i + d) == D.free_module_rank(i + d) == 0): raise ValueError( '{} and {} are not rank 0 in degree {}'.format( C, D, i + d)) continue Dm = D.differential(i) * matrices[i] mC = matrices[i + d] * C.differential(i) if mC != Dm: raise ValueError( 'matrices must define a chain complex morphism') self._matrix_dictionary = {} for i in matrices: m = matrices[i] # Use immutable matrices because they're hashable. m.set_immutable() self._matrix_dictionary[i] = m Morphism.__init__(self, Hom(C, D, ChainComplexes(C.base_ring())))
def _find_isomorphism_degenerate(self, polytope): """ Helper to pick an isomorphism of degenerate polygons INPUT: - ``polytope`` -- a :class:`LatticePolytope_PPL_class`. The polytope to compare with. EXAMPLES:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL, C_Polyhedron sage: L1 = LatticePolytope_PPL(C_Polyhedron(2, 'empty')) sage: L2 = LatticePolytope_PPL(C_Polyhedron(3, 'empty')) sage: iso = L1.find_isomorphism(L2) # indirect doctest sage: iso(L1) == L2 True sage: iso = L1._find_isomorphism_degenerate(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,4)) sage: L2 = LatticePolytope_PPL((2,1,5)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,), (3,)) sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,-1), (3,-1)) sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,2), (3,1)) sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4)) sage: iso = L1.find_isomorphism(L2) sage: iso(L1) == L2 True sage: L1 = LatticePolytope_PPL((-1,2), (3,2)) sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4)) sage: L1.find_isomorphism(L2) Traceback (most recent call last): ... LatticePolytopesNotIsomorphicError: different number of integral points sage: L1 = LatticePolytope_PPL((-1,2), (3,1)) sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,5)) sage: L1.find_isomorphism(L2) Traceback (most recent call last): ... LatticePolytopesNotIsomorphicError: different number of integral points """ from sage.geometry.polyhedron.lattice_euclidean_group_element import \ LatticePolytopesNotIsomorphicError polytope_vertices = polytope.vertices() self_vertices = self.ordered_vertices() # handle degenerate cases if self.n_vertices() == 0: A = zero_matrix(ZZ, polytope.space_dimension(), self.space_dimension()) b = zero_vector(ZZ, polytope.space_dimension()) return LatticeEuclideanGroupElement(A, b) if self.n_vertices() == 1: A = zero_matrix(ZZ, polytope.space_dimension(), self.space_dimension()) b = polytope_vertices[0] return LatticeEuclideanGroupElement(A, b) if self.n_vertices() == 2: self_origin = self_vertices[0] self_ray = self_vertices[1] - self_origin polytope_origin = polytope_vertices[0] polytope_ray = polytope_vertices[1] - polytope_origin Ds, Us, Vs = self_ray.column().smith_form() Dp, Up, Vp = polytope_ray.column().smith_form() assert Vs.nrows() == Vs.ncols() == Vp.nrows() == Vp.ncols() == 1 assert abs(Vs[0, 0]) == abs(Vp[0, 0]) == 1 A = zero_matrix(ZZ, Dp.nrows(), Ds.nrows()) A[0, 0] = 1 A = Up.inverse() * A * Us * (Vs[0, 0] * Vp[0, 0]) b = polytope_origin - A*self_origin try: A = matrix(ZZ, A) b = vector(ZZ, b) except TypeError: raise LatticePolytopesNotIsomorphicError('different lattice') hom = LatticeEuclideanGroupElement(A, b) if hom(self) == polytope: return hom raise LatticePolytopesNotIsomorphicError('different polygons')
def cohomology_complex(self, m): r""" Return the "cohomology complex" `C^*(m)` See [Klyachko]_, equation 4.2. INPUT: - ``m`` -- tuple of integers or `M`-lattice point. A point in the dual lattice of the fan. Must be immutable. OUTPUT: The "cohomology complex" as a chain complex over the :meth:`base_ring`. EXAMPLES:: sage: P3 = toric_varieties.P(3) sage: rays = [(1,0,0), (0,1,0), (0,0,1)] sage: F1 = FilteredVectorSpace(rays, {0:[0], 1:[2], 2:[1]}) sage: F2 = FilteredVectorSpace(rays, {0:[1,2], 1:[0]}) sage: r = P3.fan().rays() sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) sage: tau = Cone([(1,0,0), (0,1,0)]) sage: sigma = Cone([(1, 0, 0)]) sage: M = P3.fan().dual_lattice() sage: m = M(1, 1, 0); m.set_immutable() sage: V.cohomology_complex(m) Chain complex with at most 2 nonzero terms over Rational Field sage: F = CyclotomicField(3) sage: P3 = toric_varieties.P(3).change_ring(F) sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) sage: V.cohomology_complex(m) Chain complex with at most 2 nonzero terms over Cyclotomic Field of order 3 and degree 2 """ fan = self._variety.fan() C = fan.complex() CV = [] F = self.base_ring() for dim in range(1, fan.dim() + 1): codim = fan.dim() - dim d_C = C.differential(codim) d_V = [] for j in range(d_C.ncols()): tau = fan(dim)[j] d_V_row = [] for i in range(d_C.nrows()): sigma = fan(dim - 1)[i] if sigma.is_face_of(tau): pr = self.E_quotient_projection(sigma, tau, m) d = d_C[i, j] * pr.matrix().transpose() else: E_sigma = self.E_quotient(sigma, m) E_tau = self.E_quotient(tau, m) d = zero_matrix(F, E_tau.dimension(), E_sigma.dimension()) d_V_row.append(d) d_V.append(d_V_row) d_V = block_matrix(d_V, ring=F) CV.append(d_V) from sage.homology.chain_complex import ChainComplex return ChainComplex(CV, base_ring=self.base_ring())
def _global_restriction_matrix(precision, S, weight_parity, find_relations = False) : r""" A matrix that maps the Fourier expansion of a Jacobi form of given precision to their restrictions with respect to the elements of S. INPUT: - ``precision`` -- An instance of JacobiFormD1Filter. - `S` -- A list of vectors. - ``weight_parity`` -- The parity of the weight of the considered Jacobi forms. - ``find_relation`` -- A boolean. If ``True``, then the restrictions to nonreduced indices will also be computed. TESTS:: sage: from psage.modform.jacobiforms.jacobiformd1_fourierexpansion import * sage: from psage.modform.jacobiforms.jacobiformd1_fegenerators import _global_restriction_matrix sage: precision = JacobiFormD1Filter(5, QuadraticForm(matrix(2, [2,1,1,2]))) sage: (global_restriction_matrix, row_groups, row_labels, column_labels) = _global_restriction_matrix(precision, [vector((1,0))], 12) sage: global_restriction_matrix [1 0 0 0 0 0 0 0 0] [0 1 2 0 0 0 0 0 0] [2 0 2 0 0 0 0 0 0] [0 0 2 1 2 0 0 0 0] [0 2 0 0 2 0 0 0 0] [2 0 0 0 2 1 2 0 0] [0 0 2 2 0 0 2 0 0] [0 2 0 0 0 0 2 1 2] [0 0 0 0 2 2 0 0 2] sage: (row_groups, row_labels, column_labels) ([((1, 0), 1, 0, 9)], {1: {(0, 0): 0, (3, 0): 5, (3, 1): 6, (2, 1): 4, (2, 0): 3, (1, 0): 1, (4, 1): 8, (1, 1): 2, (4, 0): 7}}, [(0, (0, 0)), (1, (0, 0)), (1, (1, 1)), (2, (0, 0)), (2, (1, 1)), (3, (0, 0)), (3, (1, 1)), (4, (0, 0)), (4, (1, 1))]) """ L = precision.jacobi_index() weight_parity = weight_parity % 2 jacobi_indices = [ L(s) for s in S ] index_filters = dict( (m, list(JacobiFormD1NNFilter(precision.index(), m, reduced = not find_relations))) for m in Set(jacobi_indices) ) column_labels = list(precision) reductions = dict( (l, list()) for l in column_labels ) for l in precision.monoid_filter() : (lred, sign) = precision.monoid().reduce(l) reductions[lred].append((l, sign)) row_groups = [ len(index_filters[m]) for m in jacobi_indices ] row_groups = [ (s, m, sum(row_groups[:i]), row_groups[i]) for ((i, s), m) in zip(enumerate(S), jacobi_indices) ] row_labels = dict( (m, dict( (l, i) for (i, l) in enumerate(index_filters[m]) )) for m in Set(jacobi_indices) ) dot_products = [ cython_lambda( ' , '.join([ 'int x{0}'.format(i) for i in range(len(s)) ]), ' + '.join([ '{0} * x{1}'.format(s[i], i) for i in range(len(s)) ]) ) for (s, _, _, _) in row_groups ] restriction_matrix = zero_matrix(ZZ, row_groups[-1][2] + row_groups[-1][3], len(column_labels)) for (cind, l) in enumerate(column_labels) : for ((n, r), sign) in reductions[l] : for ((s, m, start, length), dot_product) in zip(row_groups, dot_products) : row_labels_dict = row_labels[m] try : restriction_matrix[start + row_labels_dict[(n, dot_product(*r))], cind] \ += 1 if weight_parity == 0 else sign except KeyError : pass return (restriction_matrix, row_groups, row_labels, column_labels)
def algebraic_topological_model(K, base_ring=None): r""" Algebraic topological model for cell complex ``K`` with coefficients in the field ``base_ring``. INPUT: - ``K`` -- either a simplicial complex or a cubical complex - ``base_ring`` -- coefficient ring; must be a field OUTPUT: a pair ``(phi, M)`` consisting of - chain contraction ``phi`` - chain complex `M` This construction appears in a paper by Pilarczyk and Réal [PR]_. Given a cell complex `K` and a field `F`, there is a chain complex `C` associated to `K` with coefficients in `F`. The *algebraic topological model* for `K` is a chain complex `M` with trivial differential, along with chain maps `\pi: C \to M` and `\iota: M \to C` such that - `\pi \iota = 1_M`, and - there is a chain homotopy `\phi` between `1_C` and `\iota \pi`. In particular, `\pi` and `\iota` induce isomorphisms on homology, and since `M` has trivial differential, it is its own homology, and thus also the homology of `C`. Thus `\iota` lifts homology classes to their cycle representatives. The chain homotopy `\phi` satisfies some additional properties, making it a *chain contraction*: - `\phi \phi = 0`, - `\pi \phi = 0`, - `\phi \iota = 0`. Given an algebraic topological model for `K`, it is then easy to compute cup products and cohomology operations on the cohomology of `K`, as described in [G-DR03]_ and [PR]_. Implementation details: the cell complex `K` must have an :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells` method from which we can extract a list of cells in each dimension. Combining the lists in increasing order of dimension then defines a filtration of the complex: a list of cells in which the boundary of each cell consists of cells earlier in the list. This is required by Pilarczyk and Réal's algorithm. There must also be a :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex` method, to construct the chain complex `C` associated to this chain complex. In particular, this works for simplicial complexes and cubical complexes. It doesn't work for `\Delta`-complexes, though: the list of their `n`-cells has the wrong format. Note that from the chain contraction ``phi``, one can recover the chain maps `\pi` and `\iota` via ``phi.pi()`` and ``phi.iota()``. Then one can recover `C` and `M` from, for example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, respectively. EXAMPLES:: sage: from sage.homology.algebraic_topological_model import algebraic_topological_model sage: RP2 = simplicial_complexes.RealProjectivePlane() sage: phi, M = algebraic_topological_model(RP2, GF(2)) sage: M.homology() {0: Vector space of dimension 1 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2, 2: Vector space of dimension 1 over Finite Field of size 2} sage: T = cubical_complexes.Torus() sage: phi, M = algebraic_topological_model(T, QQ) sage: M.homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} If you want to work with cohomology rather than homology, just dualize the outputs of this function:: sage: M.dual().homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} sage: M.dual().degree_of_differential() 1 sage: phi.dual() Chain homotopy between: Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field and Chain complex morphism: From: Chain complex with at most 3 nonzero terms over Rational Field To: Chain complex with at most 3 nonzero terms over Rational Field In degree 0, the inclusion of the homology `M` into the chain complex `C` sends the homology generator to a single vertex:: sage: K = simplicial_complexes.Simplex(2) sage: phi, M = algebraic_topological_model(K, QQ) sage: phi.iota().in_degree(0) [0] [0] [1] In cohomology, though, one needs the dual of every degree 0 cell to detect the degree 0 cohomology generator:: sage: phi.dual().iota().in_degree(0) [1] [1] [1] TESTS:: sage: T = cubical_complexes.Torus() sage: C = T.chain_complex() sage: H, M = T.algebraic_topological_model() sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 True sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 True sage: coC = T.chain_complex(cochain=True) sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 True sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 True """ if not base_ring.is_field(): raise ValueError('the coefficient ring must be a field') # The following are all dictionaries indexed by dimension. # For each n, gens[n] is an ordered list of the n-cells generating the complex M. gens = {} # For each n, phi_dict[n] is a dictionary of the form {idx: # vector}, where idx is the index of an n-cell in the list of # n-cells in K, and vector is the image of that n-cell, as an # element in the free module of (n+1)-chains for K. phi_dict = {} # For each n, pi_dict[n] is a dictionary of the same form, except # that the target vectors should be elements of the chain complex M. pi_dict = {} # For each n, iota_dict[n] is a dictionary of the form {cell: # vector}, where cell is one of the generators for M and vector is # its image in C, as an element in the free module of n-chains. iota_dict = {} for n in range(K.dimension()+1): gens[n] = [] phi_dict[n] = {} pi_dict[n] = {} iota_dict[n] = {} C = K.chain_complex(base_ring=base_ring) # old_cells: cells one dimension lower. old_cells = [] for dim in range(K.dimension()+1): n_cells = K.n_cells(dim) diff = C.differential(dim) # diff is sparse and low density. Dense matrices are faster # over finite fields, but for low density matrices, sparse # matrices are faster over the rationals. if base_ring != QQ: diff = diff.dense_matrix() rank = len(n_cells) old_rank = len(old_cells) V_old = VectorSpace(base_ring, old_rank) zero = V_old.zero_vector() for c_idx, c in enumerate(zip(n_cells, VectorSpace(base_ring, rank).gens())): # c is the pair (cell, the corresponding standard basis # vector in the free module of chains). Separate its # components, calling them c and c_vec: c_vec = c[1] c = c[0] # No need to set zero values for any of the maps: we will # assume any unset values are zero. # From the paper: phi_dict[c] = 0. # c_bar = c - phi(bdry(c)) c_bar = c_vec bdry_c = diff * c_vec # Apply phi to bdry_c and subtract from c_bar. for (idx, coord) in bdry_c.iteritems(): try: c_bar -= coord * phi_dict[dim-1][idx] except KeyError: pass bdry_c_bar = diff * c_bar # Evaluate pi(bdry(c_bar)). pi_bdry_c_bar = zero for (idx, coeff) in bdry_c_bar.iteritems(): try: pi_bdry_c_bar += coeff * pi_dict[dim-1][idx] except KeyError: pass # One small typo in the published algorithm: it says # "if bdry(c_bar) == 0", but should say # "if pi(bdry(c_bar)) == 0". if not pi_bdry_c_bar: # Append c to list of gens. gens[dim].append(c) # iota(c) = c_bar iota_dict[dim][c] = c_bar # pi(c) = c pi_dict[dim][c_idx] = c_vec else: # Take any u in gens so that lambda_i = <u, pi(bdry(c_bar))> != 0. # u_idx will be the index of the corresponding cell. for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): # Now find the actual cell. u = old_cells[u_idx] if u in gens[dim-1]: break # pi(c) = 0: no need to do anything about this. for c_j_idx in range(old_rank): # eta_ij = <u, pi(c_j)>. try: eta_ij = pi_dict[dim-1][c_j_idx][u_idx] except (KeyError, IndexError): eta_ij = 0 if eta_ij: # Adjust phi(c_j). try: phi_dict[dim-1][c_j_idx] += eta_ij * lambda_i**(-1) * c_bar except KeyError: phi_dict[dim-1][c_j_idx] = eta_ij * lambda_i**(-1) * c_bar # Adjust pi(c_j). try: pi_dict[dim-1][c_j_idx] += -eta_ij * lambda_i**(-1) * pi_bdry_c_bar except KeyError: pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar gens[dim-1].remove(u) del iota_dict[dim-1][u] old_cells = n_cells # Now we have constructed the raw data for M, pi, iota, phi, so we # have to convert that to data which can be used to construct chain # complexes, chain maps, and chain contractions. # M_data will contain (trivial) matrices defining the differential # on M. Keep track of the sizes using "M_rows" and "M_cols", which are # just the ranks of consecutive graded pieces of M. M_data = {} M_rows = 0 # pi_data: the matrices defining pi. Similar for iota_data and phi_data. pi_data = {} iota_data = {} phi_data = {} for n in range(K.dimension()+1): n_cells = K.n_cells(n) # Remove zero entries from pi_dict and phi_dict. pi_dict[n] = {i: pi_dict[n][i] for i in pi_dict[n] if pi_dict[n][i]} phi_dict[n] = {i: phi_dict[n][i] for i in phi_dict[n] if phi_dict[n][i]} # Convert gens to data defining the chain complex M with # trivial differential. M_cols = len(gens[n]) M_data[n] = zero_matrix(base_ring, M_rows, M_cols) M_rows = M_cols # Convert the dictionaries for pi, iota, phi to matrices which # will define chain maps and chain homotopies. pi_cols = [] phi_cols = [] for (idx, c) in enumerate(n_cells): # First pi: if idx in pi_dict[n]: column = vector(base_ring, M_rows) for (entry, coeff) in pi_dict[n][idx].iteritems(): # Translate from cells in n_cells to cells in gens[n]. column[gens[n].index(n_cells[entry])] = coeff else: column = vector(base_ring, M_rows) pi_cols.append(column) # Now phi: try: column = phi_dict[n][idx] except KeyError: column = vector(base_ring, len(K.n_cells(n+1))) phi_cols.append(column) # Now iota: iota_cols = [iota_dict[n][c] for c in gens[n]] pi_data[n] = matrix(base_ring, pi_cols).transpose() iota_data[n] = matrix(base_ring, len(gens[n]), len(n_cells), iota_cols).transpose() phi_data[n] = matrix(base_ring, phi_cols).transpose() M = ChainComplex(M_data, base_ring=base_ring, degree=-1) pi = ChainComplexMorphism(pi_data, C, M) iota = ChainComplexMorphism(iota_data, M, C) phi = ChainContraction(phi_data, pi, iota) return phi, M
def algebraic_topological_model_delta_complex(K, base_ring=None): r""" Algebraic topological model for cell complex ``K`` with coefficients in the field ``base_ring``. This has the same basic functionality as :func:`algebraic_topological_model`, but it also works for `\Delta`-complexes. For simplicial and cubical complexes it is somewhat slower, though. INPUT: - ``K`` -- a simplicial complex, a cubical complex, or a `\Delta`-complex - ``base_ring`` -- coefficient ring; must be a field OUTPUT: a pair ``(phi, M)`` consisting of - chain contraction ``phi`` - chain complex `M` See :func:`algebraic_topological_model` for the main documentation. The difference in implementation between the two: this uses matrix and vector algebra. The other function does more of the computations "by hand" and uses cells (given as simplices or cubes) to index various dictionaries. Since the cells in `\Delta`-complexes are not as nice, the other function does not work for them, while this function relies almost entirely on the structure of the associated chain complex. EXAMPLES:: sage: from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex as AT_model sage: RP2 = simplicial_complexes.RealProjectivePlane() sage: phi, M = AT_model(RP2, GF(2)) sage: M.homology() {0: Vector space of dimension 1 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2, 2: Vector space of dimension 1 over Finite Field of size 2} sage: T = delta_complexes.Torus() sage: phi, M = AT_model(T, QQ) sage: M.homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} If you want to work with cohomology rather than homology, just dualize the outputs of this function:: sage: M.dual().homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} sage: M.dual().degree_of_differential() 1 sage: phi.dual() Chain homotopy between: Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field and Chain complex morphism: From: Chain complex with at most 3 nonzero terms over Rational Field To: Chain complex with at most 3 nonzero terms over Rational Field In degree 0, the inclusion of the homology `M` into the chain complex `C` sends the homology generator to a single vertex:: sage: K = delta_complexes.Simplex(2) sage: phi, M = AT_model(K, QQ) sage: phi.iota().in_degree(0) [0] [0] [1] In cohomology, though, one needs the dual of every degree 0 cell to detect the degree 0 cohomology generator:: sage: phi.dual().iota().in_degree(0) [1] [1] [1] TESTS:: sage: T = cubical_complexes.Torus() sage: C = T.chain_complex() sage: H, M = AT_model(T, QQ) sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 True sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 True sage: coC = T.chain_complex(cochain=True) sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 True sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 True """ def conditionally_sparse(m): """ Return a sparse matrix if the characteristic is zero. Multiplication of matrices with low density seems to be quicker if the matrices are sparse, when over the rationals. Over finite fields, dense matrices are faster regardless of density. """ if base_ring == QQ: return m.sparse_matrix() else: return m if not base_ring.is_field(): raise ValueError('the coefficient ring must be a field') # The following are all dictionaries indexed by dimension. # For each n, gens[n] is an ordered list of the n-cells generating the complex M. gens = {} pi_data = {} phi_data = {} iota_data = {} for n in range(-1, K.dimension()+1): gens[n] = [] C = K.chain_complex(base_ring=base_ring) n_cells = [] pi_cols = [] iota_cols = {} for dim in range(K.dimension()+1): # old_cells: cells one dimension lower. old_cells = n_cells # n_cells: the standard basis for the vector space C.free_module(dim). n_cells = C.free_module(dim).gens() diff = C.differential(dim) # diff is sparse and low density. Dense matrices are faster # over finite fields, but for low density matrices, sparse # matrices are faster over the rationals. if base_ring != QQ: diff = diff.dense_matrix() rank = len(n_cells) old_rank = len(old_cells) # Create some matrix spaces to try to speed up matrix creation. MS_pi_t = MatrixSpace(base_ring, old_rank, len(gens[dim-1])) pi_old = MS_pi_t.matrix(pi_cols).transpose() iota_cols_old = iota_cols iota_cols = {} pi_cols_old = pi_cols pi_cols = [] phi_old = MatrixSpace(base_ring, rank, old_rank, sparse=(base_ring==QQ)).zero() phi_old_cols = phi_old.columns() phi_old = conditionally_sparse(phi_old) to_be_deleted = [] zero_vector = vector(base_ring, rank) pi_nrows = pi_old.nrows() for c_idx, c in enumerate(n_cells): # c_bar = c - phi(bdry(c)): # Avoid a bug in matrix-vector multiplication (trac 19378): if not diff: c_bar = c pi_bdry_c_bar = False else: if base_ring == QQ: c_bar = c - phi_old * (diff * c) pi_bdry_c_bar = conditionally_sparse(pi_old) * (diff * c_bar) else: c_bar = c - phi_old * diff * c pi_bdry_c_bar = conditionally_sparse(pi_old) * diff * c_bar # One small typo in the published algorithm: it says # "if bdry(c_bar) == 0", but should say # "if pi(bdry(c_bar)) == 0". if not pi_bdry_c_bar: # Append c to list of gens. gens[dim].append(c_idx) # iota(c) = c_bar iota_cols[c_idx] = c_bar # pi(c) = c pi_cols.append(c) else: # Take any u in gens so that lambda_i = <u, pi(bdry(c_bar))> != 0. # u_idx will be the index of the corresponding cell. (u_idx, lambda_i) = pi_bdry_c_bar.leading_item() for (u_idx, lambda_i) in iteritems(pi_bdry_c_bar): if u_idx not in to_be_deleted: break # This element/column needs to be deleted from gens and # iota_old. Do that later. to_be_deleted.append(u_idx) # pi(c) = 0. pi_cols.append(zero_vector) for c_j_idx, c_j in enumerate(old_cells): # eta_ij = <u, pi(c_j)>. # That is, eta_ij is the u_idx entry in the vector pi_old * c_j: eta_ij = c_j.dot_product(pi_old.row(u_idx)) if eta_ij: # Adjust phi(c_j). phi_old_cols[c_j_idx] += eta_ij * lambda_i**(-1) * c_bar # Adjust pi(c_j). pi_cols_old[c_j_idx] -= eta_ij * lambda_i**(-1) * pi_bdry_c_bar # The matrices involved have many zero entries. For # such matrices, using sparse matrices is faster over # the rationals, slower over finite fields. phi_old = matrix(base_ring, phi_old_cols, sparse=(base_ring==QQ)).transpose() keep = vector(base_ring, pi_nrows, {i:1 for i in range(pi_nrows) if i not in to_be_deleted}) cols = [v.pairwise_product(keep) for v in pi_cols_old] pi_old = MS_pi_t.matrix(cols).transpose() # Here cols is a temporary storage for the columns of iota. cols = [iota_cols_old[i] for i in sorted(iota_cols_old.keys())] for r in sorted(to_be_deleted, reverse=True): del cols[r] del gens[dim-1][r] iota_data[dim-1] = matrix(base_ring, len(gens[dim-1]), old_rank, cols).transpose() # keep: rows to keep in pi_cols_old. Start with all # columns, then delete those in to_be_deleted. keep = sorted(set(range(pi_nrows)).difference(to_be_deleted)) # Now cols is a temporary storage for columns of pi. cols = [v.list_from_positions(keep) for v in pi_cols_old] pi_data[dim-1] = matrix(base_ring, old_rank, len(gens[dim-1]), cols).transpose() phi_data[dim-1] = phi_old V_gens = VectorSpace(base_ring, len(gens[dim])) if pi_cols: cols = [] for v in pi_cols: cols.append(V_gens(v.list_from_positions(gens[dim]))) pi_cols = cols pi_data[dim] = matrix(base_ring, rank, len(gens[dim]), pi_cols).transpose() cols = [iota_cols[i] for i in sorted(iota_cols.keys())] iota_data[dim] = matrix(base_ring, len(gens[dim]), rank, cols).transpose() # M_data will contain (trivial) matrices defining the differential # on M. Keep track of the sizes using "M_rows" and "M_cols", which are # just the ranks of consecutive graded pieces of M. M_data = {} M_rows = 0 for n in range(K.dimension()+1): M_cols = len(gens[n]) M_data[n] = zero_matrix(base_ring, M_rows, M_cols) M_rows = M_cols M = ChainComplex(M_data, base_ring=base_ring, degree=-1) pi = ChainComplexMorphism(pi_data, C, M) iota = ChainComplexMorphism(iota_data, M, C) phi = ChainContraction(phi_data, pi, iota) return phi, M
def algebraic_topological_model(K, base_ring=None): r""" Algebraic topological model for cell complex ``K`` with coefficients in the field ``base_ring``. INPUT: - ``K`` -- either a simplicial complex or a cubical complex - ``base_ring`` -- coefficient ring; must be a field OUTPUT: a pair ``(phi, M)`` consisting of - chain contraction ``phi`` - chain complex `M` This construction appears in a paper by Pilarczyk and Réal [PR2015]_. Given a cell complex `K` and a field `F`, there is a chain complex `C` associated to `K` with coefficients in `F`. The *algebraic topological model* for `K` is a chain complex `M` with trivial differential, along with chain maps `\pi: C \to M` and `\iota: M \to C` such that - `\pi \iota = 1_M`, and - there is a chain homotopy `\phi` between `1_C` and `\iota \pi`. In particular, `\pi` and `\iota` induce isomorphisms on homology, and since `M` has trivial differential, it is its own homology, and thus also the homology of `C`. Thus `\iota` lifts homology classes to their cycle representatives. The chain homotopy `\phi` satisfies some additional properties, making it a *chain contraction*: - `\phi \phi = 0`, - `\pi \phi = 0`, - `\phi \iota = 0`. Given an algebraic topological model for `K`, it is then easy to compute cup products and cohomology operations on the cohomology of `K`, as described in [GDR2003]_ and [PR2015]_. Implementation details: the cell complex `K` must have an :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells` method from which we can extract a list of cells in each dimension. Combining the lists in increasing order of dimension then defines a filtration of the complex: a list of cells in which the boundary of each cell consists of cells earlier in the list. This is required by Pilarczyk and Réal's algorithm. There must also be a :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex` method, to construct the chain complex `C` associated to this chain complex. In particular, this works for simplicial complexes and cubical complexes. It doesn't work for `\Delta`-complexes, though: the list of their `n`-cells has the wrong format. Note that from the chain contraction ``phi``, one can recover the chain maps `\pi` and `\iota` via ``phi.pi()`` and ``phi.iota()``. Then one can recover `C` and `M` from, for example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, respectively. EXAMPLES:: sage: from sage.homology.algebraic_topological_model import algebraic_topological_model sage: RP2 = simplicial_complexes.RealProjectivePlane() sage: phi, M = algebraic_topological_model(RP2, GF(2)) sage: M.homology() {0: Vector space of dimension 1 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2, 2: Vector space of dimension 1 over Finite Field of size 2} sage: T = cubical_complexes.Torus() sage: phi, M = algebraic_topological_model(T, QQ) sage: M.homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} If you want to work with cohomology rather than homology, just dualize the outputs of this function:: sage: M.dual().homology() {0: Vector space of dimension 1 over Rational Field, 1: Vector space of dimension 2 over Rational Field, 2: Vector space of dimension 1 over Rational Field} sage: M.dual().degree_of_differential() 1 sage: phi.dual() Chain homotopy between: Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field and Chain complex morphism: From: Chain complex with at most 3 nonzero terms over Rational Field To: Chain complex with at most 3 nonzero terms over Rational Field In degree 0, the inclusion of the homology `M` into the chain complex `C` sends the homology generator to a single vertex:: sage: K = simplicial_complexes.Simplex(2) sage: phi, M = algebraic_topological_model(K, QQ) sage: phi.iota().in_degree(0) [0] [0] [1] In cohomology, though, one needs the dual of every degree 0 cell to detect the degree 0 cohomology generator:: sage: phi.dual().iota().in_degree(0) [1] [1] [1] TESTS:: sage: T = cubical_complexes.Torus() sage: C = T.chain_complex() sage: H, M = T.algebraic_topological_model() sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 True sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 True sage: coC = T.chain_complex(cochain=True) sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 True sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 True """ if not base_ring.is_field(): raise ValueError('the coefficient ring must be a field') # The following are all dictionaries indexed by dimension. # For each n, gens[n] is an ordered list of the n-cells generating the complex M. gens = {} # For each n, phi_dict[n] is a dictionary of the form {idx: # vector}, where idx is the index of an n-cell in the list of # n-cells in K, and vector is the image of that n-cell, as an # element in the free module of (n+1)-chains for K. phi_dict = {} # For each n, pi_dict[n] is a dictionary of the same form, except # that the target vectors should be elements of the chain complex M. pi_dict = {} # For each n, iota_dict[n] is a dictionary of the form {cell: # vector}, where cell is one of the generators for M and vector is # its image in C, as an element in the free module of n-chains. iota_dict = {} for n in range(K.dimension()+1): gens[n] = [] phi_dict[n] = {} pi_dict[n] = {} iota_dict[n] = {} C = K.chain_complex(base_ring=base_ring) # old_cells: cells one dimension lower. old_cells = [] for dim in range(K.dimension()+1): n_cells = K._n_cells_sorted(dim) diff = C.differential(dim) # diff is sparse and low density. Dense matrices are faster # over finite fields, but for low density matrices, sparse # matrices are faster over the rationals. if base_ring != QQ: diff = diff.dense_matrix() rank = len(n_cells) old_rank = len(old_cells) V_old = VectorSpace(base_ring, old_rank) zero = V_old.zero_vector() for c_idx, c in enumerate(zip(n_cells, VectorSpace(base_ring, rank).gens())): # c is the pair (cell, the corresponding standard basis # vector in the free module of chains). Separate its # components, calling them c and c_vec: c_vec = c[1] c = c[0] # No need to set zero values for any of the maps: we will # assume any unset values are zero. # From the paper: phi_dict[c] = 0. # c_bar = c - phi(bdry(c)) c_bar = c_vec bdry_c = diff * c_vec # Apply phi to bdry_c and subtract from c_bar. for (idx, coord) in iteritems(bdry_c): try: c_bar -= coord * phi_dict[dim-1][idx] except KeyError: pass bdry_c_bar = diff * c_bar # Evaluate pi(bdry(c_bar)). pi_bdry_c_bar = zero for (idx, coeff) in iteritems(bdry_c_bar): try: pi_bdry_c_bar += coeff * pi_dict[dim-1][idx] except KeyError: pass # One small typo in the published algorithm: it says # "if bdry(c_bar) == 0", but should say # "if pi(bdry(c_bar)) == 0". if not pi_bdry_c_bar: # Append c to list of gens. gens[dim].append(c) # iota(c) = c_bar iota_dict[dim][c] = c_bar # pi(c) = c pi_dict[dim][c_idx] = c_vec else: # Take any u in gens so that lambda_i = <u, pi(bdry(c_bar))> != 0. # u_idx will be the index of the corresponding cell. for u_idx in pi_bdry_c_bar.nonzero_positions(): lambda_i = pi_bdry_c_bar[u_idx] # Now find the actual cell. u = old_cells[u_idx] if u in gens[dim-1]: break # pi(c) = 0: no need to do anything about this. for c_j_idx in range(old_rank): # eta_ij = <u, pi(c_j)>. try: eta_ij = pi_dict[dim-1][c_j_idx][u_idx] except (KeyError, IndexError): eta_ij = 0 if eta_ij: # Adjust phi(c_j). try: phi_dict[dim-1][c_j_idx] += eta_ij * lambda_i**(-1) * c_bar except KeyError: phi_dict[dim-1][c_j_idx] = eta_ij * lambda_i**(-1) * c_bar # Adjust pi(c_j). try: pi_dict[dim-1][c_j_idx] += -eta_ij * lambda_i**(-1) * pi_bdry_c_bar except KeyError: pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar gens[dim-1].remove(u) del iota_dict[dim-1][u] old_cells = n_cells # Now we have constructed the raw data for M, pi, iota, phi, so we # have to convert that to data which can be used to construct chain # complexes, chain maps, and chain contractions. # M_data will contain (trivial) matrices defining the differential # on M. Keep track of the sizes using "M_rows" and "M_cols", which are # just the ranks of consecutive graded pieces of M. M_data = {} M_rows = 0 # pi_data: the matrices defining pi. Similar for iota_data and phi_data. pi_data = {} iota_data = {} phi_data = {} for n in range(K.dimension()+1): n_cells = K._n_cells_sorted(n) # Remove zero entries from pi_dict and phi_dict. pi_dict[n] = {i: pi_dict[n][i] for i in pi_dict[n] if pi_dict[n][i]} phi_dict[n] = {i: phi_dict[n][i] for i in phi_dict[n] if phi_dict[n][i]} # Convert gens to data defining the chain complex M with # trivial differential. M_cols = len(gens[n]) M_data[n] = zero_matrix(base_ring, M_rows, M_cols) M_rows = M_cols # Convert the dictionaries for pi, iota, phi to matrices which # will define chain maps and chain homotopies. pi_cols = [] phi_cols = [] for (idx, c) in enumerate(n_cells): # First pi: if idx in pi_dict[n]: column = vector(base_ring, M_rows) for (entry, coeff) in iteritems(pi_dict[n][idx]): # Translate from cells in n_cells to cells in gens[n]. column[gens[n].index(n_cells[entry])] = coeff else: column = vector(base_ring, M_rows) pi_cols.append(column) # Now phi: try: column = phi_dict[n][idx] except KeyError: column = vector(base_ring, len(K.n_cells(n+1))) phi_cols.append(column) # Now iota: iota_cols = [iota_dict[n][c] for c in gens[n]] pi_data[n] = matrix(base_ring, pi_cols).transpose() iota_data[n] = matrix(base_ring, len(gens[n]), len(n_cells), iota_cols).transpose() phi_data[n] = matrix(base_ring, phi_cols).transpose() M = ChainComplex(M_data, base_ring=base_ring, degree=-1) pi = ChainComplexMorphism(pi_data, C, M) iota = ChainComplexMorphism(iota_data, M, C) phi = ChainContraction(phi_data, pi, iota) return phi, M
def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain=False): """ Returns the associated chain complex morphism of ``self``. EXAMPLES:: sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) sage: f = {0:0,1:1,2:2} sage: x = H(f) sage: x Simplicial complex morphism: From: Minimal triangulation of the 1-sphere To: Minimal triangulation of the 2-sphere Defn: 0 |--> 0 1 |--> 1 2 |--> 2 sage: a = x.associated_chain_complex_morphism() sage: a Chain complex morphism: From: Chain complex with at most 2 nonzero terms over Integer Ring To: Chain complex with at most 3 nonzero terms over Integer Ring sage: a._matrix_dictionary {0: [1 0 0] [0 1 0] [0 0 1] [0 0 0], 1: [1 0 0] [0 1 0] [0 0 0] [0 0 1] [0 0 0] [0 0 0], 2: []} sage: x.associated_chain_complex_morphism(augmented=True) Chain complex morphism: From: Chain complex with at most 3 nonzero terms over Integer Ring To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(cochain=True) Chain complex morphism: From: Chain complex with at most 3 nonzero terms over Integer Ring To: Chain complex with at most 2 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(augmented=True,cochain=True) Chain complex morphism: From: Chain complex with at most 4 nonzero terms over Integer Ring To: Chain complex with at most 3 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(base_ring=GF(11)) Chain complex morphism: From: Chain complex with at most 2 nonzero terms over Finite Field of size 11 To: Chain complex with at most 3 nonzero terms over Finite Field of size 11 Some simplicial maps which reverse the orientation of a few simplices:: sage: g = {0:1, 1:2, 2:0} sage: H(g).associated_chain_complex_morphism()._matrix_dictionary {0: [0 0 1] [1 0 0] [0 1 0] [0 0 0], 1: [ 0 -1 0] [ 0 0 -1] [ 0 0 0] [ 1 0 0] [ 0 0 0] [ 0 0 0], 2: []} sage: X = SimplicialComplex([[0, 1]], is_mutable=False) sage: Hom(X,X)({0:1, 1:0}).associated_chain_complex_morphism()._matrix_dictionary {0: [0 1] [1 0], 1: [-1]} """ max_dim = max(self.domain().dimension(),self.codomain().dimension()) min_dim = min(self.domain().dimension(),self.codomain().dimension()) matrices = {} if augmented is True: m = matrix(base_ring,1,1,1) if not cochain: matrices[-1] = m else: matrices[-1] = m.transpose() for dim in range(min_dim+1): X_faces = list(self.domain().n_cells(dim)) Y_faces = list(self.codomain().n_cells(dim)) num_faces_X = len(X_faces) num_faces_Y = len(Y_faces) mval = [0 for i in range(num_faces_X*num_faces_Y)] for i in X_faces: y, oriented = self(i, orientation=True) if y.dimension() < dim: pass else: mval[X_faces.index(i)+(Y_faces.index(y)*num_faces_X)] = oriented m = matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() for dim in range(min_dim+1,max_dim+1): try: l1 = len(self.codomain().n_cells(dim)) except KeyError: l1 = 0 try: l2 = len(self.domain().n_cells(dim)) except KeyError: l2 = 0 m = zero_matrix(base_ring,l1,l2,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() if not cochain: return ChainComplexMorphism(matrices,\ self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) else: return ChainComplexMorphism(matrices,\ self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain))
def __init__(self, matrices, C, D, check=True): """ Create a morphism from a dictionary of matrices. EXAMPLES:: sage: S = simplicial_complexes.Sphere(1) sage: S Minimal triangulation of the 1-sphere sage: C = S.chain_complex() sage: C.differential() {0: [], 1: [-1 -1 0] [ 1 0 -1] [ 0 1 1], 2: []} sage: f = {0:zero_matrix(ZZ,3,3),1:zero_matrix(ZZ,3,3)} sage: G = Hom(C,C) sage: x = G(f) sage: x Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] [0 0 0], 1: [0 0 0] [0 0 0] [0 0 0]} Check that the bug in :trac:`13220` has been fixed:: sage: X = simplicial_complexes.Simplex(1) sage: Y = simplicial_complexes.Simplex(0) sage: g = Hom(X,Y)({0:0, 1:0}) sage: g.associated_chain_complex_morphism() Chain complex morphism: From: Chain complex with at most 2 nonzero terms over Integer Ring To: Chain complex with at most 1 nonzero terms over Integer Ring Check that an error is raised if the matrices are the wrong size:: sage: C = ChainComplex({0: zero_matrix(ZZ, 0, 1)}) sage: D = ChainComplex({0: zero_matrix(ZZ, 0, 2)}) sage: Hom(C,D)({0: matrix(1, 2, [1, 1])}) # 1x2 is the wrong size. Traceback (most recent call last): ... ValueError: matrix in degree 0 is not the right size sage: Hom(C,D)({0: matrix(2, 1, [1, 1])}) # 2x1 is right. Chain complex morphism: From: Chain complex with at most 1 nonzero terms over Integer Ring To: Chain complex with at most 1 nonzero terms over Integer Ring """ if not C.base_ring() == D.base_ring(): raise NotImplementedError('morphisms between chain complexes of different' ' base rings are not implemented') d = C.degree_of_differential() if d != D.degree_of_differential(): raise ValueError('degree of differential does not match') from sage.misc.misc import uniq degrees = uniq(list(C.differential()) + list(D.differential())) initial_matrices = dict(matrices) matrices = dict() for i in degrees: if i - d not in degrees: if not (C.free_module_rank(i) == D.free_module_rank(i) == 0): raise ValueError('{} and {} are not rank 0 in degree {}'.format(C, D, i)) continue try: matrices[i] = initial_matrices.pop(i) except KeyError: matrices[i] = zero_matrix(C.base_ring(), D.differential(i).ncols(), C.differential(i).ncols(), sparse=True) if check: # All remaining matrices given must be 0x0. if not all(m.ncols() == m.nrows() == 0 for m in initial_matrices.values()): raise ValueError('the remaining matrices are not empty') # Check sizes of matrices. for i in matrices: if (matrices[i].nrows() != D.free_module_rank(i) or matrices[i].ncols() != C.free_module_rank(i)): raise ValueError('matrix in degree {} is not the right size'.format(i)) # Check commutativity. for i in degrees: if i - d not in degrees: if not (C.free_module_rank(i) == D.free_module_rank(i) == 0): raise ValueError('{} and {} are not rank 0 in degree {}'.format(C, D, i)) continue if i + d not in degrees: if not (C.free_module_rank(i+d) == D.free_module_rank(i+d) == 0): raise ValueError('{} and {} are not rank 0 in degree {}'.format(C, D, i+d)) continue Dm = D.differential(i) * matrices[i] mC = matrices[i+d] * C.differential(i) if mC != Dm: raise ValueError('matrices must define a chain complex morphism') self._matrix_dictionary = {} for i in matrices: m = matrices[i] # Use immutable matrices because they're hashable. m.set_immutable() self._matrix_dictionary[i] = m Morphism.__init__(self, Hom(C,D, ChainComplexes(C.base_ring())))
def __iter__(self): if self.index() is infinity: raise ValueError("infinity is not a true filter index") if self.is_reduced(): ## We only iterate positive definite matrices ## and later build the semidefinite ones ## We first find possible upper left matrices sub2 = self._calc_iter_reduced_sub2() sub3 = self._calc_iter_reduced_sub3() sub4 = self._calc_iter_reduced_sub4() t = zero_matrix(ZZ, 4) t.set_immutable() yield t for a0 in range(2, 2 * self.index(), 2): t = zero_matrix(ZZ, 4) t[3, 3] = a0 t.set_immutable() yield t for (a0, a1, b01) in sub2: t = zero_matrix(ZZ, 4) t[2, 2] = a0 t[3, 3] = a1 t[2, 3] = b01 t[3, 2] = b01 t.set_immutable() yield t for (a0, a1, b01, sub3s) in sub3: t = zero_matrix(ZZ, 4) t[1, 1] = a0 t[2, 2] = a1 t[1, 2] = b01 t[2, 1] = b01 for (a2, b02, b12) in sub3s: ts = copy(t) ts[3, 3] = a2 ts[1, 3] = b02 ts[3, 1] = b02 ts[2, 3] = b12 ts[3, 2] = b12 ts.set_immutable() yield ts for (a0, a1, b01, sub4s) in sub4: t = zero_matrix(ZZ, 4) t[0, 0] = a0 t[1, 1] = a1 t[0, 1] = b01 t[1, 0] = b01 for (a2, b02, b12, sub4ss) in sub4s: ts = copy(t) ts[2, 2] = a2 ts[0, 2] = b02 ts[2, 0] = b02 ts[1, 2] = b12 ts[2, 1] = b12 for (a3, b03, b13, b23) in sub4ss: tss = copy(ts) tss[3, 3] = a3 tss[0, 1] = b01 tss[1, 0] = b01 tss[0, 2] = b02 tss[2, 0] = b02 tss[0, 3] = b03 tss[3, 0] = b03 tss.set_immutable() yield tss #! if self.is_reduced() else: ## We first find possible upper left matrices sub2 = list() for a0 in range(2 * self.index(), 2): for a1 in range(2 * self.index(), 2): # obstruction for t[0,1] B1 = isqrt(a0 * a1) for b01 in range(-B1, B1 + 1): sub2.append((a0, a1, b01)) sub3 = list() for (a0, a1, b01) in sub2: sub3s = list() for a2 in range(2 * self.index(), 2): # obstruction for t[0,2] B1 = isqrt(a0 * a2) for b02 in range(-B1, B1 + 1): # obstruction for t[1,2] B3 = isqrt(a1 * a2) for b12 in range(-B3, B3 + 1): # obstruction for the minor [0,1,2] of t if a0 * a1 * a2 - a0 * b12**2 + 2 * b01 * b12 * b02 - b01**2 * a2 - a1 * b02**2 < 0: continue sub3s.append((a2, b02, b12)) sub3.append((a0, a1, b01, sub3s)) for (a0, a1, b01, sub3s) in sub3: for (a2, b02, b12) in sub3s: for a3 in range(2 * self.index(), 2): # obstruction for t[0,3] B1 = isqrt(a0 * a3) for b03 in range(-B1, B1 + 1): # obstruction for t[1,3] B3 = isqrt(a1 * a3) for b13 in range(-B3, B3 + 1): # obstruction for the minor [0,1,3] of t if a0 * a1 * a3 - a0 * b13**2 + 2 * b01 * b13 * b03 - b01**2 * a3 - a1 * b03**2 < 0: continue # obstruction for t[2,3] B3 = isqrt(a2 * a3) for b23 in range(-B3, B3 + 1): # obstruction for the minor [0,2,3] of t if a0 * a2 * a3 - a0 * b23**2 + 2 * b02 * b23 * b03 - b02**2 * a3 - a2 * b03**2 < 0: continue # obstruction for the minor [1,2,3] of t if a1 * a2 * a3 - a1 * b23**2 + 2 * b12 * b23 * b13 - b12**2 * a3 - a2 * b13**2 < 0: continue t = matrix(ZZ, 4, [ a0, b01, b02, b03, b01, a1, b12, b13, b02, b12, a2, b23, b03, b13, b23, a3 ], check=False) if t.det() < 0: continue t.set_immutable() yield t raise StopIteration
def _global_restriction_matrix(precision, S, weight_parity, find_relations=False): r""" A matrix that maps the Fourier expansion of a Jacobi form of given precision to their restrictions with respect to the elements of S. INPUT: - ``precision`` -- An instance of JacobiFormD1Filter. - `S` -- A list of vectors. - ``weight_parity`` -- The parity of the weight of the considered Jacobi forms. - ``find_relation`` -- A boolean. If ``True``, then the restrictions to nonreduced indices will also be computed. TESTS:: sage: from psage.modform.jacobiforms.jacobiformd1_fourierexpansion import * sage: from psage.modform.jacobiforms.jacobiformd1_fegenerators import _global_restriction_matrix sage: precision = JacobiFormD1Filter(5, QuadraticForm(matrix(2, [2,1,1,2]))) sage: (global_restriction_matrix, row_groups, row_labels, column_labels) = _global_restriction_matrix(precision, [vector((1,0))], 12) sage: global_restriction_matrix [1 0 0 0 0 0 0 0 0] [0 1 2 0 0 0 0 0 0] [2 0 2 0 0 0 0 0 0] [0 0 2 1 2 0 0 0 0] [0 2 0 0 2 0 0 0 0] [2 0 0 0 2 1 2 0 0] [0 0 2 2 0 0 2 0 0] [0 2 0 0 0 0 2 1 2] [0 0 0 0 2 2 0 0 2] sage: (row_groups, row_labels, column_labels) ([((1, 0), 1, 0, 9)], {1: {(0, 0): 0, (3, 0): 5, (3, 1): 6, (2, 1): 4, (2, 0): 3, (1, 0): 1, (4, 1): 8, (1, 1): 2, (4, 0): 7}}, [(0, (0, 0)), (1, (0, 0)), (1, (1, 1)), (2, (0, 0)), (2, (1, 1)), (3, (0, 0)), (3, (1, 1)), (4, (0, 0)), (4, (1, 1))]) """ L = precision.jacobi_index() weight_parity = weight_parity % 2 jacobi_indices = [L(s) for s in S] index_filters = dict( (m, list( JacobiFormD1NNFilter( precision.index(), m, reduced=not find_relations))) for m in Set(jacobi_indices)) column_labels = list(precision) reductions = dict((l, list()) for l in column_labels) for l in precision.monoid_filter(): (lred, sign) = precision.monoid().reduce(l) reductions[lred].append((l, sign)) row_groups = [len(index_filters[m]) for m in jacobi_indices] row_groups = [(s, m, sum(row_groups[:i]), row_groups[i]) for ((i, s), m) in zip(enumerate(S), jacobi_indices)] row_labels = dict((m, dict((l, i) for (i, l) in enumerate(index_filters[m]))) for m in Set(jacobi_indices)) dot_products = [ cython_lambda( ' , '.join(['int x{0}'.format(i) for i in range(len(s))]), ' + '.join(['{0} * x{1}'.format(s[i], i) for i in range(len(s))])) for (s, _, _, _) in row_groups ] restriction_matrix = zero_matrix(ZZ, row_groups[-1][2] + row_groups[-1][3], len(column_labels)) for (cind, l) in enumerate(column_labels): for ((n, r), sign) in reductions[l]: for ((s, m, start, length), dot_product) in zip(row_groups, dot_products): row_labels_dict = row_labels[m] try: restriction_matrix[start + row_labels_dict[(n, dot_product(*r))], cind] \ += 1 if weight_parity == 0 else sign except KeyError: pass return (restriction_matrix, row_groups, row_labels, column_labels)