Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*- Algebraic topological model for a cell complex
This file contains two functions, :func:`algebraic_topological_model` and :func:`algebraic_topological_model_delta_complex`. The second works more generally: for all simplicial, cubical, and `\Delta`-complexes. The first only works for simplicial and cubical complexes, but it is faster in those case.
AUTHORS:
- John H. Palmieri (2015-09) """
######################################################################## # Copyright (C) 2015 John H. Palmieri <palmieri@math.washington.edu> # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # # http://www.gnu.org/licenses/ ########################################################################
# TODO: cythonize this.
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 """ 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. # 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. # 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. # 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.
# old_cells: cells one dimension lower.
# 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.
# 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: # 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)) # Apply phi to bdry_c and subtract from c_bar.
# Evaluate pi(bdry(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". # Append c to list of gens. # iota(c) = c_bar # pi(c) = 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. # Now find the actual cell.
# pi(c) = 0: no need to do anything about this. # eta_ij = <u, pi(c_j)>. # Adjust phi(c_j). # Adjust pi(c_j). except KeyError: pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar
# 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. # pi_data: the matrices defining pi. Similar for iota_data and phi_data. # Remove zero entries from pi_dict and phi_dict. # Convert gens to data defining the chain complex M with # trivial differential. # Convert the dictionaries for pi, iota, phi to matrices which # will define chain maps and chain homotopies. # First pi: # Translate from cells in n_cells to cells in gens[n]. else:
# Now phi: # Now iota:
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 """ """ 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. """ else:
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.
# old_cells: cells one dimension lower. # n_cells: the standard basis for the vector space C.free_module(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.
# Create some matrix spaces to try to speed up matrix creation.
# c_bar = c - phi(bdry(c)): # Avoid a bug in matrix-vector multiplication (trac 19378): else: else:
# One small typo in the published algorithm: it says # "if bdry(c_bar) == 0", but should say # "if pi(bdry(c_bar)) == 0". # Append c to list of gens. # iota(c) = c_bar # pi(c) = 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. # This element/column needs to be deleted from gens and # iota_old. Do that later. # pi(c) = 0. # eta_ij = <u, pi(c_j)>. # That is, eta_ij is the u_idx entry in the vector pi_old * c_j: # Adjust phi(c_j). # Adjust pi(c_j).
# The matrices involved have many zero entries. For # such matrices, using sparse matrices is faster over # the rationals, slower over finite fields. if i not in to_be_deleted})
# Here cols is a temporary storage for the columns of iota. # keep: rows to keep in pi_cols_old. Start with all # columns, then delete those in to_be_deleted. # Now cols is a temporary storage for columns of pi.
# 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.
|