def process_generators_simplicial(gen_string, dim, complex): r""" Process CHomP generator information for simplicial complexes. :param gen_string: generator output from CHomP :type gen_string: string :param dim: dimension in which to find generators :type dim: integer :param complex: simplicial complex under consideration :return: list of generators in each dimension, as described below ``gen_string`` has the form :: The 2 generators of H_1 follow: generator 1 -1 * (1,6) 1 * (1,4) ... generator 2 -1 * (1,6) 1 * (1,4) ... where each line contains a coefficient and a simplex. Each line is converted, using regular expressions, from a string to a pair ``(coefficient, Simplex)``, like :: (-1, (1,6)) representing an element in the free abelian group with basis given by simplices. Each generator is a list of such pairs, representing the sum of such elements. These are reassembled in :meth:`CHomP.__call__` to actual elements in the free module generated by the simplices of the simplicial complex in the appropriate dimension. Therefore the return value is a list of lists of pairs, one list of pairs for each generator. EXAMPLES:: sage: from sage.interfaces.chomp import process_generators_simplicial sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * (1,6)\n1 * (1,4)" sage: process_generators_simplicial(s, 1, simplicial_complexes.Torus()) [[(-1, (1, 6)), (1, (1, 4))]] """ from sage.homology.all import Simplex # each dim in gen_string starts with "The generator for H_3 # follows:". So search for "T" to find the end of the current # list of generators. g_srch = re.compile(r'H_%s\sfollow[s]?:\n([^T]*)(?:T|$)' % dim) g = g_srch.search(gen_string) output = [] simplex_list = [] if g: g = g.group(1) if g: lines = g.splitlines() for l in lines: simplex = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?(\([0-9,]*\))', l) if simplex: if simplex.group(1) and re.search("-", simplex.group(1)): sign = -1 else: sign = 1 if simplex.group(2) and len(simplex.group(2)) > 0: coeff = sign * int(simplex.group(2)) else: coeff = sign * 1 simplex = Simplex( [int(a) for a in simplex.group(3).strip('()').split(',')]) simplex_list.append((coeff, simplex)) else: if simplex_list: output.append(simplex_list) simplex_list = [] if simplex_list: output.append(simplex_list) return output else: return None
def process_generators_simplicial(gen_string, dim, complex): r""" Process CHomP generator information for simplicial complexes. :param gen_string: generator output from CHomP :type gen_string: string :param dim: dimension in which to find generators :type dim: integer :param complex: simplicial complex under consideration :return: list of generators in each dimension, as described below ``gen_string`` has the form :: The 2 generators of H_1 follow: generator 1 -1 * (1,6) 1 * (1,4) ... generator 2 -1 * (1,6) 1 * (1,4) ... where each line contains a coefficient and a simplex. Each line is converted, using regular expressions, from a string to a pair ``(coefficient, Simplex)``, like :: (-1, (1,6)) representing an element in the free abelian group with basis given by simplices. Each generator is a list of such pairs, representing the sum of such elements. These are reassembled in :meth:`CHomP.__call__` to actual elements in the free module generated by the simplices of the simplicial complex in the appropriate dimension. Therefore the return value is a list of lists of pairs, one list of pairs for each generator. EXAMPLES:: sage: from sage.interfaces.chomp import process_generators_simplicial sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * (1,6)\n1 * (1,4)" sage: process_generators_simplicial(s, 1, simplicial_complexes.Torus()) [[(-1, (1, 6)), (1, (1, 4))]] """ from sage.homology.all import Simplex # each dim in gen_string starts with "The generator for H_3 # follows:". So search for "T" to find the end of the current # list of generators. g_srch = re.compile(r'H_%s\sfollow[s]?:\n([^T]*)(?:T|$)' % dim) g = g_srch.search(gen_string) output = [] simplex_list = [] if g: g = g.group(1) if g: lines = g.splitlines() for l in lines: simplex = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?(\([0-9,]*\))', l) if simplex: if simplex.group(1) and re.search("-", simplex.group(1)): sign = -1 else: sign = 1 if simplex.group(2) and len(simplex.group(2)) > 0: coeff = sign * int(simplex.group(2)) else: coeff = sign * 1 simplex = Simplex([int(a) for a in simplex.group(3).strip('()').split(',')]) simplex_list.append((coeff, simplex)) else: if simplex_list: output.append(simplex_list) simplex_list = [] if simplex_list: output.append(simplex_list) return output else: return None
def __call__(self, program, complex, subcomplex=None, **kwds): """ Call a CHomP program to compute the homology of a chain complex, simplicial complex, or cubical complex. See :class:`CHomP` for full documentation. EXAMPLES:: sage: from sage.interfaces.chomp import CHomP sage: T = cubical_complexes.Torus() sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename from sage.homology.all import CubicalComplex, cubical_complexes from sage.homology.all import SimplicialComplex, Simplex from sage.homology.chain_complex import HomologyGroup from subprocess import Popen, PIPE from sage.rings.all import QQ, ZZ from sage.modules.all import VectorSpace, vector from sage.combinat.free_module import CombinatorialFreeModule if not have_chomp(program): raise OSError("Program %s not found" % program) verbose = kwds.get('verbose', False) generators = kwds.get('generators', False) extra_opts = kwds.get('extra_opts', '') base_ring = kwds.get('base_ring', ZZ) if extra_opts: extra_opts = extra_opts.split() else: extra_opts = [] # type of complex: cubical = False simplicial = False chain = False # CHomP seems to have problems with cubical complexes if the # first interval in the first cube defining the complex is # degenerate. So replace the complex X with [0,1] x X. if isinstance(complex, CubicalComplex): cubical = True edge = cubical_complexes.Cube(1) original_complex = complex complex = edge.product(complex) if verbose: print("Cubical complex") elif isinstance(complex, SimplicialComplex): simplicial = True if verbose: print("Simplicial complex") else: chain = True base_ring = kwds.get('base_ring', complex.base_ring()) if verbose: print("Chain complex over %s" % base_ring) if base_ring == QQ: raise ValueError( "CHomP doesn't compute over the rationals, only over Z or F_p." ) if base_ring.is_prime_field(): p = base_ring.characteristic() extra_opts.append('-p%s' % p) mod_p = True else: mod_p = False # # complex # try: data = complex._chomp_repr_() except AttributeError: raise AttributeError( "Complex cannot be converted to use with CHomP.") datafile = tmp_filename() with open(datafile, 'w') as f: f.write(data) # # subcomplex # if subcomplex is None: if cubical: subcomplex = CubicalComplex([complex.n_cells(0)[0]]) elif simplicial: m = re.search(r'\(([^,]*),', data) v = int(m.group(1)) subcomplex = SimplicialComplex([[v]]) else: # replace subcomplex with [0,1] x subcomplex. if cubical: subcomplex = edge.product(subcomplex) # # generators # if generators: genfile = tmp_filename() extra_opts.append('-g%s' % genfile) # # call program # if subcomplex is not None: try: sub = subcomplex._chomp_repr_() except AttributeError: raise AttributeError( "Subcomplex cannot be converted to use with CHomP.") subfile = tmp_filename() with open(subfile, 'w') as f: f.write(sub) else: subfile = '' if verbose: print("Popen called with arguments", end="") print([program, datafile, subfile] + extra_opts) print("") print("CHomP output:") print("") # output = Popen([program, datafile, subfile, extra_opts], cmd = [program, datafile] if subfile: cmd.append(subfile) if extra_opts: cmd.extend(extra_opts) output = Popen(cmd, stdout=PIPE).communicate()[0] if verbose: print(output) print("End of CHomP output") print("") if generators: with open(genfile, 'r') as f: gens = f.read() if verbose: print("Generators:") print(gens) # # process output # if output.find('ERROR') != -1: raise RuntimeError('error inside CHomP') # output contains substrings of one of the forms # "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2", # "H_1 = Z + Z_2 + Z" if output.find('trivial') != -1: if mod_p: return {0: VectorSpace(base_ring, 0)} else: return {0: HomologyGroup(0, ZZ)} d = {} h = re.compile("^H_([0-9]*) = (.*)$", re.M) tors = re.compile("Z_([0-9]*)") # # homology groups # for m in h.finditer(output): if verbose: print(m.groups()) # dim is the dimension of the homology group dim = int(m.group(1)) # hom_str is the right side of the equation "H_n = Z^r + Z_k + ..." hom_str = m.group(2) # need to read off number of summands and their invariants if hom_str.find("0") == 0: if mod_p: hom = VectorSpace(base_ring, 0) else: hom = HomologyGroup(0, ZZ) else: rk = 0 if hom_str.find("^") != -1: rk_srch = re.search(r'\^([0-9]*)\s?', hom_str) rk = int(rk_srch.group(1)) rk += len(re.findall(r"(Z$)|(Z\s)", hom_str)) if mod_p: rk = rk if rk != 0 else 1 if verbose: print("dimension = %s, rank of homology = %s" % (dim, rk)) hom = VectorSpace(base_ring, rk) else: n = rk invts = [] for t in tors.finditer(hom_str): n += 1 invts.append(int(t.group(1))) for i in range(rk): invts.append(0) if verbose: print( "dimension = %s, number of factors = %s, invariants = %s" % (dim, n, invts)) hom = HomologyGroup(n, ZZ, invts) # # generators # if generators: if cubical: g = process_generators_cubical(gens, dim) if verbose: print("raw generators: %s" % g) if g: module = CombinatorialFreeModule( base_ring, original_complex.n_cells(dim), prefix="", bracket=True) basis = module.basis() output = [] for x in g: v = module(0) for term in x: v += term[0] * basis[term[1]] output.append(v) g = output elif simplicial: g = process_generators_simplicial(gens, dim, complex) if verbose: print("raw generators: %s" % gens) if g: module = CombinatorialFreeModule(base_ring, complex.n_cells(dim), prefix="", bracket=False) basis = module.basis() output = [] for x in g: v = module(0) for term in x: if complex._is_numeric(): v += term[0] * basis[term[1]] else: translate = complex._translation_from_numeric( ) simplex = Simplex( [translate[a] for a in term[1]]) v += term[0] * basis[simplex] output.append(v) g = output elif chain: g = process_generators_chain(gens, dim, base_ring) if verbose: print("raw generators: %s" % gens) if g: if not mod_p: # sort generators to match up with corresponding invariant g = [ _[1] for _ in sorted(zip(invts, g), key=lambda x: x[0]) ] d[dim] = (hom, g) else: d[dim] = hom else: d[dim] = hom if chain: new_d = {} diff = complex.differential() if len(diff) == 0: return {} bottom = min(diff) top = max(diff) for dim in d: if complex._degree_of_differential == -1: # chain complex new_dim = bottom + dim else: # cochain complex new_dim = top - dim if isinstance(d[dim], tuple): # generators included. group = d[dim][0] gens = d[dim][1] new_gens = [] dimension = complex.differential(new_dim).ncols() # make sure that each vector is embedded in the # correct ambient space: pad with a zero if # necessary. for v in gens: v_dict = v.dict() if dimension - 1 not in v.dict(): v_dict[dimension - 1] = 0 new_gens.append(vector(base_ring, v_dict)) else: new_gens.append(v) new_d[new_dim] = (group, new_gens) else: new_d[new_dim] = d[dim] d = new_d return d