From a1011d30918f4de235e9616cf9e17c916664346a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 24 Apr 2024 19:59:51 -0700 Subject: [PATCH 1/3] src/sage/topology: Remove chomp interface deprecated in #33777 (2022) --- src/sage/topology/cubical_complex.py | 24 ----------------- src/sage/topology/simplicial_complex.py | 36 ------------------------- 2 files changed, 60 deletions(-) diff --git a/src/sage/topology/cubical_complex.py b/src/sage/topology/cubical_complex.py index 3d53f3b1a4b..5c255a9973f 100644 --- a/src/sage/topology/cubical_complex.py +++ b/src/sage/topology/cubical_complex.py @@ -1690,30 +1690,6 @@ def algebraic_topological_model(self, base_ring=None): base_ring = QQ return algebraic_topological_model(self, base_ring) - def _chomp_repr_(self): - r""" - String representation of self suitable for use by the CHomP - program. This lists each maximal cube on its own line. - - This function is deprecated. - - EXAMPLES:: - - sage: C = cubical_complexes.Cube(0).product(cubical_complexes.Cube(2)) - sage: C.maximal_cells() - {[0,0] x [0,1] x [0,1]} - sage: C._chomp_repr_() - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - '[0,0] x [0,1] x [0,1]\n' - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - s = "" - for c in self.maximal_cells(): - s += str(c) - s += "\n" - return s - def _simplicial_(self): r""" Simplicial complex constructed from self. diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 625e270436d..62122c1aec7 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -4498,42 +4498,6 @@ def _translation_from_numeric(self): d = self._vertex_to_index return {idx: v for v, idx in d.items()} - def _chomp_repr_(self): - r""" - String representation of ``self`` suitable for use by the CHomP - program. This lists each facet on its own line, and makes - sure vertices are listed as numbers. - - This function is deprecated. - - EXAMPLES:: - - sage: S = SimplicialComplex([(0,1,2), (2,3,5)]) - sage: print(S._chomp_repr_()) - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - (2, 3, 5) - (0, 1, 2) - - A simplicial complex whose vertices are tuples, not integers:: - - sage: S = SimplicialComplex([[(0,1), (1,2), (3,4)]]) - sage: S._chomp_repr_() - '(0, 1, 2)\n' - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - s = "" - numeric = self._is_numeric() - if not numeric: - d = self._translation_to_numeric() - for f in self.facets(): - if numeric: - s += str(f) - else: - s += '(' + ', '.join(str(d[a]) for a in f) + ')' - s += '\n' - return s - # this function overrides the standard one for GenericCellComplex, # because it lists the maximal faces, not the total number of faces. def _repr_(self): From 2384c2e7a02a481187ba4f9883a07de5840abaf5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 24 Apr 2024 20:01:06 -0700 Subject: [PATCH 2/3] src/sage/interfaces/chomp.py: Remove, deprecated in #33777 (2022) --- src/doc/en/reference/homology/index.rst | 1 - src/sage/interfaces/chomp.py | 923 ------------------------ 2 files changed, 924 deletions(-) delete mode 100644 src/sage/interfaces/chomp.py diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index 8188233a52b..e1f2adabb5e 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -19,6 +19,5 @@ computing homology groups. sage/homology/algebraic_topological_model sage/homology/homology_morphism sage/homology/matrix_utils - sage/interfaces/chomp .. include:: ../footer.txt diff --git a/src/sage/interfaces/chomp.py b/src/sage/interfaces/chomp.py deleted file mode 100644 index ad3bf9c734e..00000000000 --- a/src/sage/interfaces/chomp.py +++ /dev/null @@ -1,923 +0,0 @@ -r""" -Interface to CHomP - -This module is deprecated: see :issue:`33777`. - -CHomP stands for "Computation Homology Program", and is good at -computing homology of simplicial complexes, cubical complexes, and -chain complexes. It can also compute homomorphisms induced on -homology by maps. See the CHomP web page http://chomp.rutgers.edu/ -for more information. - -AUTHOR: - -- John H. Palmieri -""" - -import re - -from sage.misc.superseded import deprecation - -_have_chomp = {} - - -def have_chomp(program='homsimpl'): - """ - Return True if this computer has ``program`` installed. - - The first time it is run, this function caches its result in the - variable ``_have_chomp`` -- a dictionary indexed by program name - -- and any subsequent time, it just checks the value of the - variable. - - This program is used in the routine CHomP.__call__. - - If this computer doesn't have CHomP installed, you may obtain it - from http://chomp.rutgers.edu/. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import have_chomp - sage: have_chomp() # random -- depends on whether CHomP is installed - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - True - sage: 'homsimpl' in sage.interfaces.chomp._have_chomp - True - sage: sage.interfaces.chomp._have_chomp['homsimpl'] == have_chomp() - True - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - global _have_chomp - if program not in _have_chomp: - from sage.misc.sage_ostools import have_program - _have_chomp[program] = have_program(program) - return _have_chomp[program] - - -class CHomP: - r""" - Interface to the CHomP package. - - :param program: which CHomP program to use - :type program: string - :param complex: a simplicial or cubical complex - :param subcomplex: a subcomplex of ``complex`` or None (the default) - :param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`. - :type base_ring: ring; optional, default `\ZZ` - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation - progresses - :type verbose: boolean; optional, default False - :param extra_opts: options passed directly to ``program`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - The programs ``homsimpl``, ``homcubes``, and ``homchain`` are - available through this interface. ``homsimpl`` computes the - relative or absolute homology groups of simplicial complexes. - ``homcubes`` computes the relative or absolute homology groups of - cubical complexes. ``homchain`` computes the homology groups of - chain complexes. For consistency with Sage's other homology - computations, the answers produced by ``homsimpl`` and - ``homcubes`` in the absolute case are converted to reduced - homology. - - Note also that CHomP can only compute over the integers or - `\GF{p}`. CHomP is fast enough, though, that if you want - rational information, you should consider using CHomP with integer - coefficients, or with mod `p` coefficients for a sufficiently - large `p`, rather than using Sage's built-in homology algorithms. - - See also the documentation for the functions :func:`homchain`, - :func:`homcubes`, and :func:`homsimpl` for more examples, - including illustrations of some of the optional parameters. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: T = cubical_complexes.Torus() - sage: CHomP()('homcubes', T) # optional - CHomP - {0: 0, 1: Z x Z, 2: Z} - - Relative homology of a segment relative to its endpoints:: - - sage: edge = simplicial_complexes.Simplex(1) - sage: ends = edge.n_skeleton(0) - sage: CHomP()('homsimpl', edge) # optional - CHomP - {0: 0} - sage: CHomP()('homsimpl', edge, ends) # optional - CHomP - {0: 0, 1: Z} - - Homology of a chain complex:: - - sage: C = ChainComplex({3: 2 * identity_matrix(ZZ, 2)}, degree=-1) - sage: CHomP()('homchain', C) # optional - CHomP - {2: C2 x C2} - """ - def __repr__(self): - """ - String representation. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: CHomP() # indirect doctest - CHomP interface - """ - return "CHomP interface" - - 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. - - Deprecated: see :issue:`33777`. - - 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 - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - {0: 0, 1: Z x Z, 2: Z} - """ - from sage.misc.temporary_file import tmp_filename - from sage.topology.cubical_complex import CubicalComplex, cubical_complexes - from sage.topology.simplicial_complex import SimplicialComplex, Simplex - from sage.homology.chain_complex import HomologyGroup - from subprocess import Popen, PIPE - from sage.rings.integer_ring import ZZ - from sage.rings.rational_field import QQ - from sage.modules.free_module import VectorSpace - from sage.modules.free_module_element import free_module_element as vector - from sage.combinat.free_module import CombinatorialFreeModule - - deprecation(33777, "the CHomP interface is deprecated") - - 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 - - def help(self, program): - """ - Print a help message for ``program``, a program from the CHomP suite. - - :param program: which CHomP program to use - :type program: string - :return: nothing -- just print a message - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: CHomP().help('homcubes') # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - HOMCUBES, ver. ... Copyright (C) ... by Pawel Pilarczyk... - """ - deprecation(33777, "the CHomP interface is deprecated") - from subprocess import Popen, PIPE - print(Popen([program, '-h'], stdout=PIPE).communicate()[0]) - - -def homsimpl(complex=None, subcomplex=None, **kwds): - r""" - Compute the homology of a simplicial complex using the CHomP - program ``homsimpl``. If the argument ``subcomplex`` is present, - compute homology of ``complex`` relative to ``subcomplex``. - - This function is deprecated: see :issue:`33777`. - - :param complex: a simplicial complex - :param subcomplex: a subcomplex of ``complex`` or None (the default) - :param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`. - :type base_ring: ring; optional, default `\ZZ` - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation - progresses - :type verbose: boolean; optional, default False - :param help: if True, just print a help message and exit - :type help: boolean; optional, default False - :param extra_opts: options passed directly to ``program`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - EXAMPLES:: - - sage: from sage.interfaces.chomp import homsimpl - sage: T = simplicial_complexes.Torus() - sage: M8 = simplicial_complexes.MooreSpace(8) - sage: M4 = simplicial_complexes.MooreSpace(4) - sage: X = T.disjoint_union(T).disjoint_union(T).disjoint_union(M8).disjoint_union(M4) - sage: homsimpl(X)[1] # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - Z^6 x C4 x C8 - - Relative homology:: - - sage: S = simplicial_complexes.Simplex(3) - sage: bdry = S.n_skeleton(2) - sage: homsimpl(S, bdry)[3] # optional - CHomP - Z - - Generators: these are given as a list after the homology group. - Each generator is specified as a linear combination of simplices:: - - sage: homsimpl(S, bdry, generators=True)[3] # optional - CHomP - (Z, [(0, 1, 2, 3)]) - - sage: homsimpl(simplicial_complexes.Sphere(1), generators=True) # optional - CHomP - {0: 0, 1: (Z, [(0, 1) - (0, 2) + (1, 2)])} - - TESTS: - - Generators for a simplicial complex whose vertices are not integers:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: homsimpl(S1.join(S1), generators=True, base_ring=GF(2))[3][1] # optional - CHomP - [('L0', 'L1', 'R0', 'R1') + ('L0', 'L1', 'R0', 'R2') + ('L0', 'L1', 'R1', 'R2') + ('L0', 'L2', 'R0', 'R1') + ('L0', 'L2', 'R0', 'R2') + ('L0', 'L2', 'R1', 'R2') + ('L1', 'L2', 'R0', 'R1') + ('L1', 'L2', 'R0', 'R2') + ('L1', 'L2', 'R1', 'R2')] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.simplicial_complex import SimplicialComplex - help = kwds.get('help', False) - if help: - return CHomP().help('homsimpl') - # Check types here, because if you pass homsimpl a cubical - # complex, it tries to compute its homology as if it were a - # simplicial complex and gets terribly wrong answers. - if (isinstance(complex, SimplicialComplex) - and (subcomplex is None or isinstance(subcomplex, SimplicialComplex))): - return CHomP()('homsimpl', complex, subcomplex=subcomplex, **kwds) - else: - raise TypeError("Complex and/or subcomplex are not simplicial complexes.") - - -def homcubes(complex=None, subcomplex=None, **kwds): - r""" - Compute the homology of a cubical complex using the CHomP program - ``homcubes``. If the argument ``subcomplex`` is present, compute - homology of ``complex`` relative to ``subcomplex``. - - This function is deprecated: see :issue:`33777`. - - :param complex: a cubical complex - :param subcomplex: a subcomplex of ``complex`` or None (the default) - :param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`. - :type base_ring: ring; optional, default `\ZZ` - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation progresses - :type verbose: boolean; optional, default False - :param help: if True, just print a help message and exit - :type help: boolean; optional, default False - :param extra_opts: options passed directly to ``homcubes`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - EXAMPLES:: - - sage: from sage.interfaces.chomp import homcubes - sage: S = cubical_complexes.Sphere(3) - sage: homcubes(S)[3] # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - Z - - Relative homology:: - - sage: C3 = cubical_complexes.Cube(3) - sage: bdry = C3.n_skeleton(2) - sage: homcubes(C3, bdry) # optional - CHomP - {0: 0, 1: 0, 2: 0, 3: Z} - - Generators: these are given as a list after the homology group. - Each generator is specified as a linear combination of cubes:: - - sage: homcubes(cubical_complexes.Sphere(1), generators=True, base_ring=GF(2))[1][1] # optional - CHomP - [[[1,1] x [0,1]] + [[0,1] x [1,1]] + [[0,1] x [0,0]] + [[0,0] x [0,1]]] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.cubical_complex import CubicalComplex - help = kwds.get('help', False) - if help: - return CHomP().help('homcubes') - # Type-checking is here for the same reason as in homsimpl above. - if (isinstance(complex, CubicalComplex) - and (subcomplex is None or isinstance(subcomplex, CubicalComplex))): - return CHomP()('homcubes', complex, subcomplex=subcomplex, **kwds) - else: - raise TypeError("Complex and/or subcomplex are not cubical complexes.") - - -def homchain(complex=None, **kwds): - r""" - Compute the homology of a chain complex using the CHomP program - ``homchain``. - - This function is deprecated: see :issue:`33777`. - - :param complex: a chain complex - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation progresses - :type verbose: boolean; optional, default False - :param help: if True, just print a help message and exit - :type help: boolean; optional, default False - :param extra_opts: options passed directly to ``homchain`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - EXAMPLES:: - - sage: from sage.interfaces.chomp import homchain - sage: C = cubical_complexes.Sphere(3).chain_complex() - sage: homchain(C)[3] # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - Z - - Generators: these are given as a list after the homology group. - Each generator is specified as a cycle, an element in the - appropriate free module over the base ring:: - - sage: C2 = delta_complexes.Sphere(2).chain_complex() - sage: homchain(C2, generators=True)[2] # optional - CHomP - (Z, [(1, -1)]) - sage: homchain(C2, generators=True, base_ring=GF(2))[2] # optional - CHomP - (Vector space of dimension 1 over Finite Field of size 2, [(1, 1)]) - - TESTS: - - Chain complexes concentrated in negative dimensions, cochain complexes, etc.:: - - sage: C = ChainComplex({-5: 4 * identity_matrix(ZZ, 2)}, degree=-1) - sage: homchain(C) # optional - CHomP - {-6: C4 x C4} - sage: C = ChainComplex({-5: 4 * identity_matrix(ZZ, 2)}, degree=1) - sage: homchain(C, generators=True) # optional - CHomP - {-4: (C4 x C4, [(1, 0), (0, 1)])} - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.homology.chain_complex import ChainComplex_class - help = kwds.get('help', False) - if help: - return CHomP().help('homchain') - # Type-checking just in case. - if isinstance(complex, ChainComplex_class): - return CHomP()('homchain', complex, **kwds) - else: - raise TypeError("Complex is not a chain complex.") - - -def process_generators_cubical(gen_string, dim): - r""" - Process CHomP generator information for cubical complexes. - - This function is deprecated: see :issue:`33777`. - - :param gen_string: generator output from CHomP - :type gen_string: string - :param dim: dimension in which to find generators - :type dim: integer - :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 * [(0,0,0,0,0)(0,0,0,0,1)] - 1 * [(0,0,0,0,0)(0,0,1,0,0)] - ... - generator 2 - -1 * [(0,1,0,1,1)(1,1,0,1,1)] - -1 * [(0,1,0,0,1)(0,1,0,1,1)] - ... - - Each line consists of a coefficient multiplied by a cube; the cube - is specified by its "bottom left" and "upper right" corners. - - For technical reasons, we remove the first coordinate of each - tuple, and using regular expressions, the remaining parts get - converted from a string to a pair ``(coefficient, Cube)``, with - the cube represented as a product of tuples. For example, the - first line in "generator 1" gets turned into :: - - (-1, [0,0] x [0,0] x [0,0] x [0,1]) - - representing an element in the free abelian group with basis given - by cubes. 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 cubes of the cubical 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_cubical - sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * [(0,0,0,0,0)(0,0,0,0,1)]\n1 * [(0,0,0,0,0)(0,0,1,0,0)]" - sage: process_generators_cubical(s, 1) - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - [[(-1, [0,0] x [0,0] x [0,0] x [0,1]), (1, [0,0] x [0,1] x [0,0] x [0,0])]] - sage: len(process_generators_cubical(s, 1)) # only one generator - 1 - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.cubical_complex import Cube - # 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 = [] - cubes_list = [] - if g: - g = g.group(1) - if g: - # project g to one end of the cylinder [0,1] x complex: - # - # drop the first coordinate and eliminate duplicates, at least - # in positive dimensions, drop any line containing a - # degenerate cube - g = re.sub(r'\([01],', '(', g) - if dim > 0: - lines = g.splitlines() - newlines = [] - for l in lines: - x = re.findall(r'(\([0-9,]*\))', l) - if x: - left, right = x - left = [int(a) for a in left.strip('()').split(',')] - right = [int(a) for a in right.strip('()').split(',')] - if sum([xx - yy for (xx, yy) in zip(right, left)]) == dim: - newlines.append(l) - else: # line like "generator 2" - newlines.append(l) - g = newlines - cubes_list = [] - for l in g: - cubes = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?\[\(([-0-9,]+)\)\(([-0-9,]+)\)\]', l) - if cubes: - if cubes.group(1) and re.search("-", cubes.group(1)): - sign = -1 - else: - sign = 1 - if cubes.group(2) and len(cubes.group(2)) > 0: - coeff = sign * int(cubes.group(2)) - else: - coeff = sign * 1 - cube1 = [int(a) for a in cubes.group(3).split(',')] - cube2 = [int(a) for a in cubes.group(4).split(',')] - cube = Cube(zip(cube1, cube2)) - cubes_list.append((coeff, cube)) - else: - if cubes_list: - output.append(cubes_list) - cubes_list = [] - if cubes_list: - output.append(cubes_list) - return output - else: - return None - - -def process_generators_simplicial(gen_string, dim, complex): - r""" - Process CHomP generator information for simplicial complexes. - - This function is deprecated: see :issue:`33777` - - :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()) - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - [[(-1, (1, 6)), (1, (1, 4))]] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.simplicial_complex 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_chain(gen_string, dim, base_ring=None): - r""" - Process CHomP generator information for simplicial complexes. - - This function is deprecated: see :issue:`33777`. - - :param gen_string: generator output from CHomP - :type gen_string: string - :param dim: dimension in which to find generators - :type dim: integer - :param base_ring: base ring over which to do the computations - :type base_ring: optional, default ZZ - :return: list of generators in each dimension, as described below - - ``gen_string`` has the form :: - - [H_0] - a1 - - [H_1] - a2 - a3 - - [H_2] - a1 - a2 - - For each homology group, each line lists a homology generator as a - linear combination of generators ``ai`` of the group of chains in - the appropriate dimension. The elements ``ai`` are indexed - starting with `i=1`. Each generator is converted, using regular - expressions, from a string to a vector (an element in the free - module over ``base_ring``), with ``ai`` representing the unit - vector in coordinate `i-1`. For example, the string ``a1 - a2`` - gets converted to the vector ``(1, -1)``. - - Therefore the return value is a list of vectors. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import process_generators_chain - sage: s = "[H_0]\na1\n\n[H_1]\na2\na3\n" - sage: process_generators_chain(s, 1) - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - [(0, 1), (0, 0, 1)] - sage: s = "[H_0]\na1\n\n[H_1]\n5 * a2 - a1\na3\n" - sage: process_generators_chain(s, 1, base_ring=ZZ) - [(-1, 5), (0, 0, 1)] - sage: process_generators_chain(s, 1, base_ring=GF(2)) - [(1, 1), (0, 0, 1)] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.modules.free_module_element import vector - from sage.rings.integer_ring import ZZ - if base_ring is None: - base_ring = ZZ - # each dim in gens starts with a string like - # "[H_3]". So search for "[" to find the end of - # the current list of generators. - g_srch = re.compile(r'\[H_%s\]\n([^]]*)(?:\[|$)' % dim) - g = g_srch.search(gen_string) - if g: - g = g.group(1) - if g: - # each line in the string g is a linear - # combination of things like "a2", "a31", etc. - # indexing on the a's starts at 1. - lines = g.splitlines() - new_gens = [] - for l in lines: - gen = re.compile(r"([+-]?)\s?([0-9]+)?\s?[*]?\s?a([0-9]*)(?:\s|$)") - v = {} - for term in gen.finditer(l): - if term.group(1) and re.search("-", term.group(1)): - sign = -1 - else: - sign = 1 - if term.group(2) and len(term.group(2)) > 0: - coeff = sign * int(term.group(2)) - else: - coeff = sign * 1 - idx = int(term.group(3)) - v[idx-1] = coeff - if v: - new_gens.append(vector(base_ring, v)) - g = new_gens - return g From 3993ba7b6efb108018fc62f776a868e8339b62ce Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 24 Apr 2024 20:07:29 -0700 Subject: [PATCH 3/3] src/sage/homology: Remove chomp interface deprecated in #33777 (2022) --- src/doc/en/developer/coding_basics.rst | 6 +- src/sage/homology/chain_complex.py | 133 +------------------------ src/sage/homology/tests.py | 99 +----------------- 3 files changed, 5 insertions(+), 233 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index b87654b8306..589052ba827 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1303,11 +1303,11 @@ framework. Here is a comprehensive list: that is, commas, hyphens, semicolons, ..., after the first word ends the list of packages. Hyphens or colons between the word ``optional`` and the first package name are allowed. Therefore, - you should not write ``# optional - depends on package CHomP`` but simply - ``# optional - CHomP``. + you should not write ``# optional - depends on package bliss`` but simply + ``# optional - bliss``. - Optional tags are case-insensitive, so you could also write ``# optional - - chOMP``. + Bliss``. If ``# optional`` or ``# needs`` is placed right after the ``sage:`` prompt, it is a block-scoped tag, which applies to all doctest lines until diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index c01151ee7ed..83a725fdb77 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -1122,58 +1122,6 @@ def __ne__(self, other): """ return not self == other - def _homology_chomp(self, deg, base_ring, verbose, generators): - """ - Helper function for :meth:`homology`. - - This function is deprecated. - - INPUT: - - - ``deg`` -- integer (one specific homology group) or ``None`` - (all of those that can be non-zero) - - - ``base_ring`` -- the base ring (must be the integers - or a prime field) - - - ``verbose`` -- boolean, whether to print some messages - - - ``generators`` -- boolean, whether to also return generators - for homology - - EXAMPLES:: - - sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}, base_ring=GF(2)) - sage: C._homology_chomp(None, GF(2), False, False) # optional - chomp, needs sage.rings.finite_rings - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - {0: Vector space of dimension 2 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2} - - sage: D = ChainComplex({0: matrix(ZZ,1,0,[]), 1: matrix(ZZ,1,1,[0]), - ....: 2: matrix(ZZ,0,1,[])}) - sage: D._homology_chomp(None, GF(2), False, False) # optional - chomp, needs sage.rings.finite_rings - {1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - from sage.interfaces.chomp import homchain - H = homchain(self, base_ring=base_ring, verbose=verbose, - generators=generators) - if H is None: - raise RuntimeError('ran CHomP, but no output') - if deg is None: - # all the homology groups that could be non-zero - # one has to complete the answer of chomp - result = H - for idx in self.nonzero_degrees(): - if idx not in H: - result[idx] = HomologyGroup(0, base_ring) - return result - if deg in H: - return H[deg] - else: - return HomologyGroup(0, base_ring) - def homology(self, deg=None, base_ring=None, generators=False, verbose=False, algorithm='pari'): r""" @@ -1204,7 +1152,6 @@ def homology(self, deg=None, base_ring=None, generators=False, * ``'auto'`` * ``'dhsw'`` * ``'pari'`` - * ``'chomp'`` (this option is deprecated) See below for descriptions. @@ -1235,12 +1182,6 @@ def homology(self, deg=None, base_ring=None, generators=False, forces the named algorithm to be used regardless of the size of the matrices. - Finally, if ``algorithm`` is set to ``'chomp'``, then use - CHomP. CHomP is available at the web page - http://chomp.rutgers.edu/, although the software has not been - tested recently in Sage. The use of this option is deprecated; - see :issue:`33777`. - As of this writing, ``'pari'`` is the fastest standard option. .. WARNING:: @@ -1300,10 +1241,8 @@ def homology(self, deg=None, base_ring=None, generators=False, if not (base_ring.is_field() or base_ring is ZZ): raise NotImplementedError('can only compute homology if the base ring is the integers or a field') - if algorithm not in ['dhsw', 'pari', 'auto', 'no_chomp', 'chomp']: + if algorithm not in ['dhsw', 'pari', 'auto', 'no_chomp']: raise NotImplementedError('algorithm not recognized') - if algorithm == 'chomp': - return self._homology_chomp(deg, base_ring, verbose, generators) if deg is None: deg = self.nonzero_degrees() @@ -1667,76 +1606,6 @@ def shift(self, n=1): return ChainComplex({k-shift: sgn * self._diff[k] for k in self._diff}, degree_of_differential=deg) - def _chomp_repr_(self): - r""" - String representation of ``self`` suitable for use by the CHomP - program. - - This function is deprecated. - - Since CHomP can only handle chain complexes, not cochain - complexes, and since it likes its complexes to start in degree - 0, flip the complex over if necessary, and shift it to start - in degree 0. Note also that CHomP only works over the - integers or a finite prime field. - - EXAMPLES:: - - sage: C = ChainComplex({-2: matrix(ZZ, 1, 3, [3, 0, 0])}, degree=-1) - sage: C._chomp_repr_() - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - 'chain complex\n\nmax dimension = 1\n\ndimension 0\n boundary a1 = 0\n\ndimension 1\n boundary a1 = + 3 * a1 \n boundary a2 = 0\n boundary a3 = 0\n\n' - sage: C = ChainComplex({-2: matrix(ZZ, 1, 3, [3, 0, 0])}, degree=1) - sage: C._chomp_repr_() - 'chain complex\n\nmax dimension = 1\n\ndimension 0\n boundary a1 = 0\n\ndimension 1\n boundary a1 = + 3 * a1 \n boundary a2 = 0\n boundary a3 = 0\n\n' - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - deg = self.degree_of_differential() - if (self.grading_group() != ZZ or - (deg != 1 and deg != -1)): - raise ValueError('CHomP only works on Z-graded chain complexes with ' - 'differential of degree 1 or -1') - base_ring = self.base_ring() - if (base_ring == QQ) or (base_ring != ZZ and not (base_ring.is_prime_field())): - raise ValueError('CHomP doesn\'t compute over the rationals, only over Z or F_p') - if deg == -1: - diffs = self.differential() - else: - diffs = self._flip_().differential() - - if len(diffs) == 0: - diffs = {0: matrix(ZZ, 0, 0)} - - maxdim = max(diffs) - mindim = min(diffs) - # will shift chain complex by subtracting mindim from - # dimensions, so its bottom dimension is zero. - s = "chain complex\n\nmax dimension = %s\n\n" % (maxdim - mindim - 1,) - - for i in range(0, maxdim - mindim): - s += "dimension %s\n" % i - mat = diffs.get(i + mindim, matrix(base_ring, 0, 0)) - for idx in range(mat.ncols()): - s += " boundary a%s = " % (idx + 1) - # construct list of bdries - col = mat.column(idx) - nonzero_pos = col.nonzero_positions() - if nonzero_pos: - for j in nonzero_pos: - entry = col[j] - if entry > 0: - sgn = "+" - else: - sgn = "-" - entry = -entry - s += "%s %s * a%s " % (sgn, entry, j+1) - else: - s += "0" - s += "\n" - s += "\n" - return s - def _repr_(self): """ Print representation. diff --git a/src/sage/homology/tests.py b/src/sage/homology/tests.py index 1fc391376a8..206617843a9 100644 --- a/src/sage/homology/tests.py +++ b/src/sage/homology/tests.py @@ -1,33 +1,10 @@ # sage.doctest: needs sage.modules """ Tests for chain complexes, simplicial complexes, etc. - -These test whether CHomP gives the same answers as Sage's built-in -homology calculator. - -Since the CHomP interface has been deprecated --- see :issue:`33777` ---- so are many of the functions in is this module. - -TESTS:: - - sage: from sage.homology.tests import test_random_chain_complex - sage: test_random_chain_complex(trials=20) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - sage: test_random_chain_complex(level=2, trials=20) # optional - CHomP - sage: test_random_chain_complex(level=3, trials=20) # long time # optional - CHomP - - sage: from sage.homology.tests import test_random_simplicial_complex - sage: test_random_simplicial_complex(level=1, trials=20) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - sage: test_random_simplicial_complex(level=2, trials=20) # optional - CHomP - sage: test_random_simplicial_complex(level=5/2, trials=10) # long time # optional - CHomP """ from sage.misc.random_testing import random_testing from sage.misc.prandom import randint -from sage.misc.superseded import deprecation from sage.matrix.constructor import random_matrix from sage.homology.chain_complex import ChainComplex from sage.rings.integer_ring import ZZ @@ -66,43 +43,6 @@ def random_chain_complex(level=1): return ChainComplex({dim: mat}, degree=deg) -@random_testing -def test_random_chain_complex(level=1, trials=1, verbose=False): - """ - Compute the homology of a random chain complex with and without - CHomP, and compare the results. If they are not the same, raise - an error. - - This function is deprecated: see :issue:`33777`. - - :param level: measure of complexity of the chain complex -- see - :func:`random_chain_complex` - :type level: positive integer; optional, default 1 - :param trials: number of trials to conduct - :type trials: positive integer; optional, default 1 - :param verbose: if ``True``, print verbose messages - :type verbose: boolean; optional, default ``False`` - - EXAMPLES:: - - sage: from sage.homology.tests import test_random_chain_complex - sage: test_random_chain_complex(trials=2) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - """ - deprecation(33777, 'the CHomP interface is deprecated; hence so is this function') - for i in range(trials): - C = random_chain_complex(level=level) - for d in C.differential(): - chomp = C.homology(d, verbose=verbose) - no_chomp = C.homology(d, algorithm='no_chomp', verbose=verbose) - if chomp != no_chomp: - print("Homology in dimension %s according to CHomP: %s" % (d, chomp)) - print("Homology in dimension %s according to Sage: %s" % (d, no_chomp)) - print("Chain complex: %s" % C.differential()) - raise ValueError - - def random_simplicial_complex(level=1, p=0.5): """ Return a random simplicial complex. @@ -118,7 +58,7 @@ def random_simplicial_complex(level=1, p=0.5): sage: from sage.homology.tests import random_simplicial_complex sage: X = random_simplicial_complex() - sage: X # random + sage: X # random Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 31 facets sage: X.dimension() < 11 True @@ -126,40 +66,3 @@ def random_simplicial_complex(level=1, p=0.5): n = randint(2, 4 * level) dim = randint(1, n) return RandomComplex(n, dim, p) - - -@random_testing -def test_random_simplicial_complex(level=1, trials=1, verbose=False): - """ - Compute the homology of a random simplicial complex with and - without CHomP, and compare the results. If they are not the same, - raise an error. - - :param level: measure of complexity of the simplicial complex -- - see :func:`random_simplicial_complex` - :type level: positive integer; optional, default 1 - :param trials: number of trials to conduct - :type trials: positive integer; optional, default 1 - :param verbose: if ``True``, print verbose messages - :type verbose: boolean; optional, default ``False`` - - This gets pretty slow if ``level`` is more than 3. - - EXAMPLES:: - - sage: from sage.homology.tests import test_random_simplicial_complex - sage: test_random_simplicial_complex(trials=2) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - """ - deprecation(33777, 'the CHomP interface is deprecated; hence so is this function') - for i in range(trials): - X = random_simplicial_complex(level=level) - chomp = X.homology(verbose=verbose) - no_chomp = X.homology(algorithm='no_chomp', verbose=verbose) - if chomp != no_chomp: - print("Homology according to CHomP: %s" % chomp) - print("Homology according to Sage: %s" % no_chomp) - print("Simplicial complex: %s" % X) - print("Its chain complex: %s" % X.chain_complex()) - raise ValueError