def ker_im(self): T0 = walltime() S = diagonal_matrix([self.pq, self.pq, 1]) Si = ~S AUT = [S * M * Si for M in self.Delta_symmetry] pqDelta = self.pq * self.Delta pqDelta_fundam = self.pq * self.Delta_fundam fundam = fundamental_points(pqDelta_fundam, AUT) self.bidegrees = [] self.numsymm = {} for o in orbits(self.tensor_points(pqDelta), AUT): bideg = unique_intersection(o, fundam) if self.fd[bideg]: # Do not handle bidegrees where the domain is # 0-dimensional. These do not contribute to the kernel # or image. self.bidegrees.append(bideg) self.numsymm[bideg] = len(o) self.TM = 0 self.TR = 0 self.done = 0 self.results = {} # Bidegrees to handle in the computation, sorted according to # dimension of codomain (=> roughly by increasing difficulty) self._bidegrees_todo = sorted(self.bidegrees, key=lambda bideg: self.fc[bideg]) self.lock = Lock() T1 = walltime() if not self.threads: self._run() else: T = [Thread(target=self._run, args=(i,)) for i in range(self.threads)] for t in T: t.start() for t in T: t.join() TT = walltime() - T0 dimdomain = ZZ(sum(self.fd[bideg] * self.numsymm[bideg] for bideg in self.bidegrees)) rank = ZZ(sum(self.results[bideg][1] * self.numsymm[bideg] for bideg in self.bidegrees)) ker = dimdomain - rank if self.verbose: sys.stderr.flush() print("%-8s[%5i]:%10s (t = %.2fs + %.2fs + %.2fs = %.2fs)" % (str(self), len(self.bidegrees), ker, T1 - T0, self.TM, self.TR, TT)) sys.stdout.flush() return ker, rank
def _compute_echelon(self): A = Matrix(self.parent(), self.A.rows()) # we create a copy of the matrix U = identity_matrix(self.parent(), A.nrows()) if (self.have_ideal): # we do simplifications A = self.simplify(A) ## Step 1: initialize r = 0 c = 0 # we look from the position (r,c) while (r < A.nrows() and c < A.ncols()): ir = self.__find_pivot(A, r, c) A = self.simplify(A) U = self.simplify(U) # we simplify in case relations pop up if (ir != None): # we found a pivot # We do the swapping (if needed) if (ir != r): A.swap_rows(r, ir) U.swap_rows(r, ir) # We do the bareiss step Arc = A[r][c] Arows = A.rows() Urows = U.rows() for i in range(r): # we create zeros on top of the pivot Aic = A[i][c] A.set_row(i, Arc * Arows[i] - Aic * Arows[r]) U.set_row(i, Arc * Urows[i] - Aic * Urows[r]) # We then leave the row r without change for i in range(r + 1, A.nrows()): # we create zeros below the pivot Aic = A[i][c] A.set_row(i, Aic * Arows[r] - Arc * Arows[i]) U.set_row(i, Aic * Urows[r] - Arc * Urows[i]) r += 1 c += 1 else: # no pivot then only advance in column c += 1 # We finish simplifying the gcds in each row gcds = [gcd(row) for row in A] T = diagonal_matrix([1 / el if el != 0 else 1 for el in gcds]) A = (T * A).change_ring(self.parent()) U = T * U return A, U
def __contains__(self, x): """ Test whether ``x`` is an element of this subgroup. EXAMPLES:: sage: G.<a,b> = AbelianGroup(2) sage: A = G.subgroup([a]) sage: a in G True sage: a in A True TESTS: Check that :trac:`32910` is fixed:: sage: G.<a,b> = AbelianGroup(2, [4, 576]) sage: Hgens = [a^2, a*b^2] sage: H = G.subgroup(Hgens) sage: [g in H for g in (a^3, b^2, b^3, a^3*b^2, "junk")] [False, False, False, True, False] Check that :trac:`31507` is fixed:: sage: G = AbelianGroup(2, gens_orders=[16, 16]) sage: f0, f1 = G.gens() sage: H = G.subgroup([f0*f1^3]) sage: [g in H for g in (f0, f0*f1^2, f0*f1^3, f0*f1^4)] [False, False, True, False] sage: G.<a,b> = AbelianGroup(2) sage: Hgens = [a*b, a*b^-1] sage: H = G.subgroup(Hgens) sage: b^2 in H True """ if not isinstance(x, AbelianGroupElement): return False if x.parent() is self: return True elif x in self.ambient_group(): amb_inv = self.ambient_group().gens_orders() inv_basis = diagonal_matrix(ZZ, amb_inv) gens_basis = matrix(ZZ, len(self._gens), len(amb_inv), [g.list() for g in self._gens]) return vector(ZZ, x.list()) in inv_basis.stack(gens_basis).row_module() return False
def conf_model(n, X, ring=ZZ, output_file_name=None, verbose=False, parallelize=True, display_degree=7): # Set up our graphs vertices = range(1, n + 1) edges = [(i, j) for i, j in Combinations(vertices, 2)] graphs = list(Subsets(edges)) # Define the poset G(n) as a category def G_one(x): return '*' def G_hom(x, y): if x.issubset(y): return ['*'] return [] def G_comp(x, f, y, g, z): return '*' G = FiniteCategory(graphs, G_one, G_hom, G_comp) Gop = G.op() # Print out all the homsets if verbose: for x in G.objects: for y in G.objects: print 'Hom(' + str(x) + ', ' + str(y) + ') = ' + str(G.hom(x, y)) # Build the vertices of X^n # Given a tuple of dimensions, this function builds all integer matrices with first column zero, # last column dim_tuple, all columns distinct, and where consecutive entries in a row have difference 0 or 1. gen_prod_mat = {} def generic_prod_mat(dim_tuple): if dim_tuple in gen_prod_mat: return gen_prod_mat[dim_tuple] k = len(dim_tuple) first_column = zero_matrix(ZZ, k, 1) last_column = matrix(ZZ, k, 1, [d for d in dim_tuple]) # If dim_list is all zeros, then return a single matrix of zeros. if first_column == last_column: return [first_column] current_batch = [first_column] next_batch = [] gpms = [] while len(current_batch) != 0: for m in current_batch: l = m.ncols() next_column_options = [range(m[r, l - 1], min(m[r, l - 1] + 2, dim_tuple[r] + 1)) for r in range(k)] new_column_iterator = itertools.product(*next_column_options) # we don't want the same column again. drop = next(new_column_iterator) for next_column_tuple in new_column_iterator: next_column = matrix(ZZ, k, 1, next_column_tuple) mm = block_matrix([[m, matrix(ZZ, k, 1, next_column)]], subdivide=False) if next_column == last_column: gpms += [mm] else: next_batch += [mm] current_batch = next_batch next_batch = [] gen_prod_mat[dim_tuple] = gpms return gpms # This set will contain a list of all the Gamma-conf matrices # where Gamma is the empty graph on n nodes. prod_mats = set() nonempty_faces = X.face_iterator(increasing=True) # This line pops off the empty face next(nonempty_faces) for simplex_tuple in itertools.product(nonempty_faces, repeat=n): dim_tuple = tuple(s.dimension() for s in simplex_tuple) for gpm in generic_prod_mat(dim_tuple): l = gpm.ncols() m = matrix(ZZ, n, l, [simplex_tuple[r][gpm[r, c]] for r in range(n) for c in range(l)]) m.set_immutable() prod_mats.add(m) # Each prod matrix gets assigned a number. Build the translation dicts both ways. # While we're looping, we also compute the row-distinctness graph for each prod matrix pm_to_nn = {} nn_to_pm = {} gammas = {} nns_by_gamma = {graph: [] for graph in graphs} for nn, pm in enumerate(prod_mats): pm_to_nn[pm] = nn nn_to_pm[nn] = pm graph = Set((i, j) for i, j in edges if pm.row(vertices.index(i)) != pm.row(vertices.index(j))) gammas[nn] = graph nns_by_gamma[graph] += [nn] # The matrix cmb is the combination table. The (i, j)-entry gives the index of the smallest prod mat that # contains the columns of the ith and jth prod mat. If no such prod mat exists, the table gives -1. pmt = len(prod_mats) if verbose: print 'Preparing combination table' print 'Total number of rows: ' + str(pmt) # def prep_cmb_row(i): # row = zero_matrix(ZZ, 1, pmt) # p_cols = nn_to_pm[i].columns() # for j in range(i + 1, pmt): # q = nn_to_pm[j] # both_cols = list(set(p_cols + q.columns())) # both_cols.sort() # combined = block_matrix([[matrix(ZZ, n, 1, list(v)) for v in both_cols]], subdivide=False) # combined.set_immutable() # if combined in prod_mats: # row[0, j] = pm_to_nn[combined] # else: # row[0, j] = -1 # if verbose: # if i % 100 == 0: # print 'Finished row ' + str(i) # return [row] if parallelize: pool = Pool() def arg_gen(zed): num = 0 while num < zed: yield (num, n, pmt, nn_to_pm, prod_mats, pm_to_nn) num += 1 cmb_row_list = pool.map(prep_cmb_row, arg_gen(pmt)) pool.close() pool.join() else: cmb_row_list = [prep_cmb_row(i) for i in range(pmt)] cmb = block_matrix(cmb_row_list) cmb = cmb + cmb.transpose() + diagonal_matrix(range(pmt)) # Check if a list of prod matrices can have their columns assembled into a single prod matrix def index_compatible(list_of_indices): if len(list_of_indices) <= 1: return True cur = list_of_indices[0] for i in list_of_indices[1:]: cur = cmb[cur, i] if cur == -1: return False return True # For each graph Gamma, compute the minimal prod matrices of type Gamma. min_prod_mat_indices = [] min_prod_mat_indices_by_gamma = {} for gamma in graphs: def leq(i, j): return cmb[i, j] == j poset = Poset((nns_by_gamma[gamma], leq)) min_prod_mat_indices_by_gamma[gamma] = poset.minimal_elements() min_prod_mat_indices += min_prod_mat_indices_by_gamma[gamma] # Build the resulting simplicial complex model for the product if verbose: print print 'Building the souped-up model for the product' print # My copy of sagemath has a verbose flag for SimplicialComplex, but this is not standard yet prod_model = SimplicialComplex(from_characteristic_function=(index_compatible, min_prod_mat_indices)) dim = prod_model.dimension() # for graph in graphs: # Y = SimplicialComplex(from_characteristic_function=(index_compatible, # [i for gamma in graphs if graph.issubset(gamma) for i in min_prod_mat_indices_by_gamma[gamma]])) # print 'Graph ' + str(graph) # for d in range(Y.dimension() + 1): # print 'Faces of dimension ' + str(d) + ': ' + str(len(Y.n_faces(d))) # print Y.homology() # print # # # sys.exit(0) if verbose: print 'Computing generator degrees' z = 1 zz = sum(len(prod_model.n_faces(d)) for d in range(dim + 1)) sorted_basis = {} for d in range(dim + 1): sorted_basis[d] = {graph: [] for graph in graphs} for f in prod_model.n_faces(d): if verbose: print '\r' + str(z) + '/' + str(zz), sys.stdout.flush() z += 1 gamma = Set(set(edges).intersection(*[set(gammas[m]) for m in f])) sorted_basis[d][gamma] += [f] print # In degree d, this dict holds a list of faces of prod_model basis = {} # labels[d] has the same length as basis[d] and tells you which graph labels = {} for d in range(-1, dim + 2): basis[d] = [] labels[d] = [] if d in range(dim + 1): for graph in graphs: batch = sorted_basis[d][graph] basis[d] += batch labels[d] += [graph] * len(batch) def f_law((d,), x, f, y): if d in labels: return CatMat.identity_matrix(ring, Gop, labels[d]) else: return CatMat.identity_matrix(ring, Gop, [])
def bidegree_tables(self, short=False, flip=False): """ Return a 5-tuple of matrices representing, for each bidegree: - [0] whether or not it is in the support - [1] the dimension of the domain - [2] the dimension of the codomain - [3] the dimension of the kernel - [4] the dimension of the image If ``short=True``, return only a 3-tuple with the first 3 entries from the above list. If ``flip=True``, return the tables for the dual. """ S = diagonal_matrix([self.pq, self.pq, 1]) Si = ~S AUT = [S * M * Si for M in self.Delta_symmetry] pqDelta = self.pq * self.Delta if flip: dx, dy = sum(self.Delta.integral_points()) xmax = dx ymax = dy else: xmax = max(pt[0] for pt in pqDelta.vertices()) ymax = max(pt[1] for pt in pqDelta.vertices()) Msupp = Matrix(ZZ, ymax+1, xmax+1) Md = Matrix(ZZ, ymax+1, xmax+1) Mc = Matrix(ZZ, ymax+1, xmax+1) for bideg in self.tensor_points(pqDelta): x, y = bideg if flip: x = dx - x y = dy - y if x < 0 or y < 0: continue Md[y,x] = self.fd[bideg] Mc[y,x] = self.fc[bideg] Msupp[y,x] = 1 if short: return Msupp, Md, Mc self.ker_im() # Compute results Mker = Matrix(ZZ, ymax+1, xmax+1) Mim = Matrix(ZZ, ymax+1, xmax+1) for bideg in self.bidegrees: v = vector([bideg[0], bideg[1], 1]) for g in AUT: x, y, _ = g * v if flip: x = dx - x y = dy - y if x < 0 or y < 0: continue Mker[y,x] = self.results[bideg][0] Mim[y,x] = self.results[bideg][1] return Msupp, Md, Mc, Mker, Mim
def _splitting_classes_gens_(K, m, d): r""" Given a number field `K` of conductor `m` and degree `d`, this returns a set of multiplicative generators of the subgroup of `(\mathbb{Z}/m\mathbb{Z})^{\times}/(\mathbb{Z}/m\mathbb{Z})^{\times d}` containing exactly the classes that contain the primes splitting completely in `K`. EXAMPLES:: sage: from sage.rings.number_field.number_field import _splitting_classes_gens_ sage: K = CyclotomicField(101) sage: L = K.subfields(20)[0][0] sage: L.conductor() 101 sage: _splitting_classes_gens_(L,101,20) [95] sage: K = CyclotomicField(44) sage: L = K.subfields(4)[0][0] sage: _splitting_classes_gens_(L,44,4) [37] sage: K = CyclotomicField(44) sage: L = K.subfields(5)[0][0] sage: K.degree() 20 sage: L Number Field in zeta44_0 with defining polynomial x^5 - 2*x^4 - 16*x^3 + 24*x^2 + 48*x - 32 with zeta44_0 = 3.837971894457990? sage: L.conductor() 11 sage: _splitting_classes_gens_(L,11,5) [10] """ R = K.ring_of_integers() Zm = IntegerModRing(m) unit_gens = Zm.unit_gens() ZZunits = ZZ**len(unit_gens) unit_relations = [gcd(d, x.multiplicative_order()) for x in unit_gens] # sparse=False can be removed if the code below doesn't raise the following # AttributeError: 'Matrix_integer_sparse' object has no attribute '_clear_denom' D = diagonal_matrix(unit_relations, sparse=False) Zmstar = ZZunits / D.row_module() def map_Zmstar_to_Zm(h): li = h.lift().list() return prod(unit_gens[i]**li[i] for i in range(len(unit_gens))) Hgens = [] H = Zmstar.submodule([]) Horder = Zmstar.cardinality() / d for g in Zmstar: if H.cardinality() == Horder: break if g not in H: u = map_Zmstar_to_Zm(g) p = u.lift() while not p.is_prime(): p += m f = R.ideal(p).prime_factors()[0].residue_class_degree() h = g * f if h not in H: Hgens += [h] H = Zmstar.submodule(Hgens) return [map_Zmstar_to_Zm(h) for h in Hgens]
def test_pullback_4_1(self): self.assert_pullback_m_1(8, diagonal_matrix([1, 1, 1, 1]), prec=10) self.assert_pullback_m_1(8, diagonal_matrix([2, 1, 1, 1]), prec=8)
def test_pullback_3_1(self): self.assert_pullback_m_1(6, diagonal_matrix([1, 1, 1])) self.assert_pullback_m_1(6, matrix([[1, 1, 0], [1, 1, 0], [0, 0, 2]]))
def assert_pullback_m_1(self, k, A1, prec=10): f0 = eisenstein_pullback_coeff(k, A1, diagonal_matrix([0])) f = eisenstein_series_qexp(k, prec=prec, normalization='constant') * f0 for a in range(prec): self.assertEqual(f[a], eisenstein_pullback_coeff(k, A1, diagonal_matrix([a])))
def __call__(self, hmult, vmult): r""" INPUT: - ``hmult`` -- multiplicities of the horizontal twists - ``vmult`` -- multiplicities of the vertical twists """ if len(hmult) != self._num_hcyls or len(vmult) != self._num_vcyls: raise ValueError("invalid input lengths") E = self._E H = E * diagonal_matrix(vmult) V = E.transpose() * diagonal_matrix(hmult) if self._num_hcyls < self._num_vcyls: F = H * V else: F = V * H p = F.charpoly() assert F.nrows() == F.ncols() == min( [self._num_hcyls, self._num_vcyls]) pf = max(p.roots(AA, False)) mp = pf.minpoly() if mp.degree() == 1: K = QQ pf = QQ(pf) else: fwd, bck, q = do_polred(pf.minpoly()) im_gen = fwd(pf) K = NumberField(q, 'a', embedding=im_gen) pf = bck(K.gen()) # Compute widths of the cylinders via Perron-Frobenius if self._num_hcyls < self._num_vcyls: hcirc = (F - pf).right_kernel_matrix() assert hcirc.nrows() == 1 assert hcirc.ncols() == self._num_hcyls hcirc = hcirc[0] assert all(x > 0 for x in hcirc) vcirc = V * hcirc c = 1 d = pf else: vcirc = (F - pf).right_kernel_matrix() assert vcirc.nrows() == 1 assert vcirc.ncols() == self._num_vcyls vcirc = vcirc[0] assert all(x > 0 for x in vcirc) hcirc = H * vcirc d = 1 c = pf # Solve linear systems to get heights h = [hcirc[i] * hmult[i] / c for i in range(self._num_hcyls)] v = [vcirc[i] * vmult[i] / d for i in range(self._num_vcyls)] C = ConvexPolygons(K) P = [] for i in range(self._o.nb_squares()): hi = h[self._hcycles[i]] vi = v[self._vcycles[i]] P.append(C(edges=[(vi, 0), (0, hi), (-vi, 0), (0, -hi)])) surface = Surface_list(base_ring=K) for p in P: surface.add_polygon(p) r = self._o.r_tuple() u = self._o.u_tuple() for i in range(self._o.nb_squares()): surface.set_edge_pairing(i, 1, r[i], 3) surface.set_edge_pairing(i, 0, u[i], 2) surface.set_immutable() return TranslationSurface(surface)