Source code for comch.surjection.surjection

from ..free_module import FreeModuleElement
from ..symmetric import SymmetricGroupElement
from ..symmetric import SymmetricRingElement, SymmetricRing

from ..simplicial import Simplex, SimplicialElement, Simplicial
from ..cubical import CubicalElement, Cubical
from ..utils import pairwise

from itertools import combinations, product, combinations_with_replacement
from operator import itemgetter
from functools import reduce
from math import floor, factorial


[docs]class SurjectionElement(FreeModuleElement): r"""Element in the surjection operad. For a positive integer :math:`r` let :math:`\mathcal X(r)_d` be the free :math:`R`-module generated by all functions :math:`s : \{1, \dots, d+r\} \to \{1, \dots, r\}` modulo the :math:`R`-submodule generated by degenerate functions, i.e., those which are either non-surjective or have a pair of equal consecutive values. There is a left action of :math:`\mathrm S_r` on :math:`\mathcal X(r)` which is up to signs defined on basis elements by :math:`\pi \cdot s = \pi \circ s`. We represent a surjection :math:`s` as the sequence of its values :math:`\big( s(1), \dots, s(n+r) \big)`. The boundary map in this complex is defined up to signs by .. math:: \partial s = \sum_{i=1}^{r+d} \pm \big(s(1),\dots,\widehat{s(i)},\dots,s(n+r)\big). We refer to [McS] and [BF], where this operad was introduced, for their corresponding sign conventions, and to the corresponding methods below for the operadic composition and complexity filtration. REFERENCES ---------- [McS]: J. McClure, and J. Smith. "Multivariable cochain operations and little n-cubes." Journal of the American Mathematical Society 16.3 (2003): 681-704. [BF]: C. Berger, and B. Fresse. "Combinatorial operad actions on cochains." Mathematical Proceedings of the Cambridge Philosophical Society. Vol. 137. No. 1. Cambridge University Press, 2004. ATTRIBUTES ---------- convention : :class:`string` 'Berger-Fresse' or 'McClure-Smith'. The sign convention used. """ default_convention = 'Berger-Fresse' """Class attribute: Used if :attr:`convention` is ``None`` during initialization."""
[docs] def __init__(self, data=None, torsion=None, convention=None): """ PARAMETERS ---------- data : :class:`dict` or ``None``, default: ``None`` Dictionary representing a linear combination of basis elements. Items in the dictionary correspond to `basis_element: coefficient` pairs. Each basis_element must create a :class:`tuple` of :class:`int` and `coefficient` must be an :class:`int`. torsion : :class:`int` The non-neg int :math:`n` of the ring :math:`\mathbb Z/n \mathbb Z`. EXAMPLES -------- >>> s = SurjectionElement() >>> print(s) 0 >>> s = SurjectionElement({(1,2,1,3,1,3): 1}) >>> print(s) (1,2,1,3,1,3) """ if convention is None: convention = SurjectionElement.default_convention self.convention = convention super(SurjectionElement, self).__init__(data=data, torsion=torsion)
def __str__(self): string = super().__str__() return string.replace(', ', ',') @property def arity(self): """Arity of *self*. Defined as ``None`` if *self* is not homogeneous. The arity of a basis surjection element agrees with the maximum value it attains. RETURNS _______ :class:`int` The arity of *self*. EXAMPLE ------- >>> SurjectionElement({(1,2,1,3,1): 1}).arity 3 """ if not self: return None arities = set(max(surj) for surj in self.keys()) if len(arities) == 1: return arities.pop() return None @property def degree(self): """Degree of *self*. Defined as ``None`` if *self* is not homogeneous. The degree of a basis surjection agrees with the cardinality of its domain minus its arity. RETURNS _______ :class:`int` The degree of *self*. EXAMPLE ------- >>> SurjectionElement({(1,2,1,3,1): 1}).arity 3 """ if not self: return None degrees = set(len(surj) - max(surj) for surj in self.keys()) if len(degrees) == 1: return degrees.pop() return None @property def complexity(self): r"""Complexity of *self*. Defined as ``None`` if *self* is not homogeneous. The complexity of a finite binary sequence (i.e. a sequence of two distinct values) is defined as the number of consecutive distinct elements in it. For example, (1,2,2,1) and (1,1,1,2) have complexities 2 and 1 respectively. The complexity of a basis surjection element is defined as the maximum value of the complexities of its binary subsequences. Notice that for arity 2, the complexity of an element is equal to its degree plus 1. It is proven in [McCS] that the subcomplex generated by basis surjection elements of complexity at most :math:`n` define a suboperad of :math:`\mathcal X` modeling an :math:`E_{n+1}`-operad. RETURNS _______ :class:`int` The complexity of *self*. EXAMPLE ------- >>> SurjectionElement({(1,2,1,3,1): 1}).complexity 2 """ complexity = 0 for k in self.keys(): for i, j in combinations(range(1, max(k) + 1), 2): seq = filter(lambda x: x == i or x == j, k) cpxty = len([p for p, q in pairwise(seq) if p != q]) complexity = max(cpxty, complexity) return complexity @property def filtration(self): """Filtration by sum of all pairwise complexities. RETURNS _______ :class:`int` The filtration level of *self*. EXAMPLE ------- >>> SurjectionElement({(1,2,1,3,2): 1}).filtration 6 """ filtration = 0 for key in self.keys(): binary_complexities = [] for i, j in combinations(range(1, max(key) + 1), 2): r = tuple(k for k in key if k == i or k == j) cpxty = len([p for p, q in pairwise(r) if p != q]) binary_complexities.append(cpxty) filtration = max(filtration, sum(binary_complexities)) return filtration
[docs] def boundary(self): r"""Boundary of *self*. Up to signs, it is defined by taking the sum of all elements obtained by removing one entry at the time. Explicitly, for basis surjection elements we have .. math:: \partial s = \sum_{i=1}^{r+d} \pm s \circ \delta_i = \sum_{i=1}^{r+d} \pm \big(s(1),\dots,\widehat{s(i)},\dots,s(n+r)\big). RETURNS _______ :class:`comch.surjection.SurjectionElement` The boundary of *self* with the corresponiding sign convention. EXAMPLE ------- >>> s = SurjectionElement({(3,2,1,3,1,3): 1}) >>> s.convention = 'Berger-Fresse' >>> print(s.boundary()) (2,1,3,1,3) - (3,2,3,1,3) - (3,2,1,3,1) >>> s.convention = 'McClure-Smith' >>> print(s.boundary()) (3,2,3,1,3) - (2,1,3,1,3) - (3,2,1,3,1) """ answer = self.zero() if self.torsion == 2: for k in self.keys(): for idx in range(0, len(k)): bdry_summand = k[:idx] + k[idx + 1:] if k[idx] in bdry_summand: answer += self.create({bdry_summand: 1}) return answer if self.convention == 'Berger-Fresse': for k, v in self.items(): # determining the signs of the summands signs = {} alternating_sign = 1 for idx, i in enumerate(k): if i in k[idx + 1:]: signs[idx] = alternating_sign alternating_sign *= (-1) elif i in k[:idx]: occurs = (pos for pos, j in enumerate(k[:idx]) if i == j) signs[idx] = signs[max(occurs)] * (-1) else: signs[idx] = 0 # computing the summands for idx in range(0, len(k)): bdry_summand = k[:idx] + k[idx + 1:] if k[idx] in bdry_summand: answer += self.create({bdry_summand: signs[idx] * v}) if self.convention == 'McClure-Smith': for k, v in self.items(): sign = 1 for i in range(1, max(k) + 1): for idx in (idx for idx, j in enumerate(k) if j == i): new_k = k[:idx] + k[idx + 1:] if k[idx] in new_k: answer += answer.create({new_k: v * sign}) sign *= -1 sign *= -1 return answer
[docs] def __rmul__(self, other): """Left action: *other* ``*`` *self* Left multiplication by a symmetric group element or an integer. Defined up to signs on basis elements by applying the permutation to the values of the surjection. PARAMETERS ---------- other : :class:`int` or :class:`comch.symmetric.SymmetricRingElement`. The element to left act on *self* with. RETURNS _______ :class:`comch.surjection.SurjectionElement` The product: *other* ``*`` *self*, with the given sign convention. EXAMPLE ------- >>> surj = SurjectionElement({(1,2,3,1,2): 1}) >>> print(- surj) - (1,2,3,1,2) >>> rho = SymmetricRingElement({(2,3,1): 1}) >>> print(rho * surj) (2,3,1,2,3) """ def check_input(self, other): if not isinstance(other, SymmetricRingElement): raise TypeError( f'Type int or SymmetricRingElement not {type(other)}') if self.torsion != other.torsion: raise TypeError('only defined for equal attribute torsion') if self.arity != other.arity: raise TypeError('Unequal arity attribute') def sign(perm, surj, convention): if convention == 'Berger-Fresse': return 1 assert convention == 'McClure-Smith' weights = [surj.count(i) - 1 for i in range(1, max(surj) + 1)] sign_exp = 0 for idx, i in enumerate(perm): right = [weights[perm.index(j)] for j in perm[idx + 1:] if i > j] sign_exp += sum(right) * weights[idx] return (-1) ** (sign_exp % 2) if isinstance(other, int): return super().__rmul__(other) check_input(self, other) answer = self.zero() for (k1, v1), (k2, v2) in product(self.items(), other.items()): new_key = tuple(k2[i - 1] for i in k1) new_sign = sign(k2, k1, self.convention) answer += self.create({new_key: new_sign * v1 * v2}) return answer
[docs] def orbit(self, representation='trivial'): """The preferred representative of the symmetric orbit of *self*. The preferred representative in the orbit of basis surjections element is the one satisfying that the first occurence of each integer appear in increasing order. The representation used can be either 'trivial' or 'sign'. RETURNS _______ :class:`comch.surjection.SurjectionElement` The preferred element in the symmetric orbit of *self*. EXAMPLE ------- >>> s = SurjectionElement({(1,3,2): 1}) >>> print(s.orbit(representation='trivial')) (1,2,3) >>> print(s.orbit(representation='sign')) - (1,2,3) """ def sign(permutation, representation): if representation == 'trivial': return 1 if representation == 'sign': return permutation.sign answer = self.zero() for k, v in self.items(): seen = [] for i in k: if i not in seen: seen.append(i) permutation = SymmetricGroupElement(seen).inverse() new_v = sign(permutation, representation) * v answer += permutation * self.create({k: new_v}) return answer
[docs] def __call__(self, other, coord=1): """The action: *self*(*other*). The action of *self* on the tensor factor specified by *coord* on an element in the tensor product of normalized chains of a standard simplex or of a standard cube. PARAMETERS ---------- other : :class:`comch.simplicial.SimplicialElement` or\ :class:`comch.cubical.CubicalElement`. The element to which apply *self* to coord : :class:`int`, default 1. The tensor factor of *other* to apply *self* to. RETURNS _______ :class:`comch.simplicial.SimplicialElement` or\ :class:`comch.cubical.CubicalElement` The action of *self* on *other* at *coord*. """ def check_input(self, other, coord=1): if self.degree is None or self.arity is None: raise TypeError('defined for homogeneous surjections') if other.arity < coord: raise TypeError(f'arity = {other.arity} < coord = {coord}') if self.torsion != other.torsion: raise TypeError('only defined for equal attribute torsion') def compute_sign(k1, k2): """Returns the sign associated to a pair.""" def ordering_sign(permu, weights): """Returns the exponent of the Koszul sign of the given permutation acting on the elements of degrees given by the list of weights """ sign_exp = 0 for idx, j in enumerate(permu): to_add = [weights[permu.index(i)] for i in permu[idx + 1:] if i < j] sign_exp += weights[idx] * sum(to_add) return sign_exp % 2 def action_sign(ordered_k1, ordered_weights): """Given a ordered tuple [1,..,1, 2,...,2, ..., r,...,r] and weights [w_1, w_2, ..., w_{r+d}] of the same length, gives the koszul sign obtained by inserting from the left a weight 1 operator between equal consecutive elements. """ sign_exp = 0 for idx, (i, j) in enumerate(pairwise(ordered_k1)): if i == j: sign_exp += sum(ordered_weights[:idx + 1]) return sign_exp % 2 sign_exp = 0 weights = [e.dimension % 2 for e in k2] inv_ordering_permu = [pair[0] for pair in sorted(enumerate(k1), key=itemgetter(1))] ordering_permu = tuple(inv_ordering_permu.index(i) for i in range(len(inv_ordering_permu))) sign_exp += ordering_sign(ordering_permu, weights) ordered_k1 = list(sorted(k1)) ordered_weights = [weights[i] for i in inv_ordering_permu] sign_exp += action_sign(ordered_k1, ordered_weights) return (-1) ** sign_exp def simplicial(self, other, coord): """Action on Eilenberg-Zilber elements.""" answer = other.zero() times = self.arity + self.degree - 1 pre_join = other.iterated_diagonal(times, coord) for (k1, v1), (k2, v2) in product(self.items(), pre_join.items()): i, j = coord - 1, coord + len(k1) - 1 left, k2, right = k2[:i], k2[i:j], k2[j:] new_k = [] zero_summand = False for i in range(1, max(k1) + 1): to_join = (spx for idx, spx in enumerate(k2) if k1[idx] == i) joined = Simplex(reduce(lambda x, y: x + y, to_join)) if joined.is_degenerate(): zero_summand = True break new_k.append(joined) if not zero_summand: if self.torsion == 2: sign = 1 else: sign = compute_sign(k1, k2) deg_left = sum(len(spx) - 1 for spx in left) % 2 sign *= (-1) ** (deg_left * self.degree) new_k = left + tuple(new_k) + right answer += answer.create({new_k: sign * v1 * v2}) return answer def cubical(self, other): """Action on cubical Eilenberg-Zilber elements.""" answer = other.zero() pre_join = other.iterated_diagonal(self.arity + self.degree - 1) for (k1, v1), (k2, v2) in product(self.items(), pre_join.items()): to_dist = [] zero_summand = False for i in range(1, max(k1) + 1): key_to_join = tuple(cube for idx, cube in enumerate(k2) if k1[idx] == i) joined = other.create({key_to_join: 1}).join() if not joined: zero_summand = True break to_dist.append(joined) if not zero_summand: if self.torsion == 2: sign = 1 else: sign = compute_sign(k1, k2) items_to_dist = [summand.items() for summand in to_dist] for pairs in product(*items_to_dist): new_k = reduce(lambda x, y: x + y, (pair[0] for pair in pairs)) new_v = reduce(lambda x, y: x * y, (pair[1] for pair in pairs)) to_add = answer.create({tuple(new_k): sign * new_v * v1 * v2}) answer += to_add return answer if not self or not other: return other.zero() check_input(self, other, coord=1) if isinstance(other, SimplicialElement): if self.convention != 'McClure-Smith': raise NotImplementedError return simplicial(self, other, coord) elif isinstance(other, CubicalElement): return cubical(self, other) else: raise NotImplementedError
[docs] def compose(self, other, position): r"""Operadic compositions: *self* :math:`\circ_{position}` *other*. The :math:`i`-th composition :math:`x \circ_i y` of :math:`x \in \mathcal X(r)` and :math:`y \in \mathcal X(s)` is defined by the following procedure: let :math:`w` be the cardinality of :math:`x^{-1}(i)`, for every collection of ordered indices .. math:: 1 = j_0 \leq j_1 \leq j_2 \leq \cdots \leq j_{w-1} \leq j_w = s we construct an associated splitting of :math:`y` .. math:: (y(j_0), \dots, y(j_1));\ (y(j_1), \dots, y(j_2));\ \cdots \ ;\ (y(j_{w-1}), \dots, y(j_w)). The element :math:`x \circ_i y \in \mathcal(r+s-1)` is represented, up to signs, as the sum over all possible collections of order indices of the sequence obtained in the following two steps: 1) replace each occurrence of :math:`i` in :math:`x` by the corresponding sequence in the associated splitting having its values shifted up by :math:`i-1`, and 2) shift up by :math:`s-1` the values of :math:`x` greater than :math:`i`. PARAMETERS ---------- other : :class:`comch.surjection.SurjectionElement` The element to operad compose *self* with. position : :class:`int` The value at which the composition occurs. RETURNS _______ :class:`comch.surjection.SurjectionElement` The operadic composition of *self* and *other*. EXAMPLE ------- >>> x = SurjectionElement({(1,2,1,3): 1}, convention='Berger-Fresse') >>> y = SurjectionElement({(1,2,1): 1}, convention='Berger-Fresse') >>> print(x.compose(y, 1)) (1,3,1,2,1,4) - (1,2,3,2,1,4) - (1,2,1,3,1,4) """ def bf_sign(p1, k1, p2, k2): """Sign associated to the Berger-Fresse composition.""" def caesuras(k): """Returns the caesuras of a basis element.""" caesuras = [] for idx, i in enumerate(k): if i in k[idx + 1:]: caesuras.append(idx) return caesuras def weights(cae, p): """Returns the weights of the splitting knowing the caesuras.""" weights = [] for i, j in pairwise(p): closed_open = len([e for e in cae if i <= e < j]) weights.append(closed_open) return [value % 2 for value in weights] p1 = [0] + p1 + [len(k1) - 1] cae1 = caesuras(k1) w1 = weights(cae1, p1) cae2 = caesuras(k2) w2 = weights(cae2, p2) sign_exp = 0 for idx, w in enumerate(w2): if w: sign_exp += sum(w1[idx + 1:]) % 2 return (-1) ** sign_exp def ms_sign(positions, k1, p, k2): raise NotImplementedError answer = self.zero() for (k1, v1), (k2, v2) in product(self.items(), other.items()): positions = [idx for idx, j in enumerate(k1) if j == position] for p in combinations_with_replacement( range(len(k2)), len(positions) - 1): p = (0,) + p + (len(k2) - 1,) split = [] for a, b in pairwise(p): split.append(tuple(k2[a:b + 1])) to_insert = (tuple(j + position - 1 for j in part) for part in split) new_k = list() for j in k1: if j < position: new_k.append(j) elif j == position: new_k += next(to_insert) else: new_k.append(j + other.arity - 1) if self.torsion == 2: sign = 1 elif self.convention == 'Berger-Fresse': sign = bf_sign(positions, k1, p, k2) elif self.convention == 'McClure-Smith': sign = ms_sign() answer += answer.create({tuple(new_k): v1 * v2 * sign}) return answer
[docs] def suspension(self): r"""Image of *self* in the suspension of the surjection operad. Given a basis element :math:`u` in arity :math:`r` and degree :math:`d` the suspension is in degree :math:`d-r+1` and is :math:`0` if :math:`(u(1),\dots,u(r))` is not a permutation and :math:`sgn(u(1),\dots,u(r)) (u(r),\dots,u(r+d))` otherwise. RETURNS _______ :class:`comch.surjection.SurjectionElement` The image of *self* in the suspension of the operad. EXAMPLE ------- >>> x = SurjectionElement({(1,3,2,1,2):1}, convention='Berger-Fresse') >>> print(x.suspension()) - (2,1,2) """ if not self: return self if self.arity is None or self.degree is None: raise TypeError('defined for homogeneous elements only') if self.convention != 'Berger-Fresse': raise NotImplementedError answer = self.zero() for k, v in self.items(): nonzero = False try: p = SymmetricGroupElement(k[:self.arity]) sign = p.sign nonzero = True except TypeError: pass if nonzero: answer += self.create({k[self.arity - 1:]: v * sign}) return answer
[docs] def preferred_rep(self): """Preferred representative of *self*. Removes pairs `basis_element: coefficient` which satisfy either of: 1) the basis element has equal consecutive values, 2) the basis element does not represent a surjection, or 3) the coefficient is 0. RETURNS _______ :class:`comch.surjection.SurjectionElement` The preferred representative of *self*. EXAMPLE ------- >>> print(SurjectionElement({(1,1,2):1, (1,3):1, (1,2):0})) 0 """ zeros = list() for k in self.keys(): if set(k) != set(range(1, max(k) + 1)): zeros.append(k) for k in zeros: del self[k] for k, v in self.items(): for i in range(len(k) - 1): if k[i] == k[i + 1]: self[k] = 0 super().preferred_rep()
[docs]class Surjection: """Produces surjection elements of interest."""
[docs] @staticmethod def may_steenrod_structure(arity, degree, torsion=None, convention=None): r"""Representative of the requested Steenrod product. Let :math:`\mathrm{C}_n` be the cyclic group of order :math:`n` thought of as the subgroup of :math:`\mathrm{S}_n` generated by an element :math:`\rho`. We denote this inclusion by :math:`\iota : \mathrm C_r \to \mathrm S_r`. The elements .. math:: T = \rho-1 \quad \text{ and } \quad N = 1+\rho+\cdots+\rho^{r-1} in :math:`R[C_r]` define a minimal resolution :math:`W(r)` .. math:: R[C_r] \stackrel{T}{\longleftarrow} R[C_r] \stackrel{N}{\longleftarrow} R[C_r] \stackrel{T}{\longleftarrow} \cdots of :math:`R` by a free differential graded :math:`R[C_r]`-module. We denote a preferred basis element of :math:`W(r)_i` by :math:`e_i`. A May-Steenrod structure on an operad :math:`\mathcal O` is a morphism of :math:`\mathrm{C}`-modules :math:`\mathcal W \stackrel{\psi}{\longrightarrow} \mathcal O` for which there exists a factorization through an :math:`E_\infty`-operad .. math:: \mathcal W \stackrel{\iota}{\longrightarrow} \mathcal R \stackrel{\phi}{\longrightarrow} \mathcal O such that :math:`\iota` is a weak equivalence and :math:`\phi` a morphism of operads. This method returns the image under the May-Steenrod structure constructed in [KMM] of the preferred basis element of :math:`W(r)_i`. PARAMETERS ---------- arity : :class:`int` The arity considered. degree : :class:`int` The degree considered. torsion : :class:`int` or ``None``, default ``None`` The non-neg int :math:`n` of the ring :math:`\mathbb Z/n \mathbb Z`. convention : :class:`string` or ``None``, default ``None`` The sign convention of the surjection operad. RETURNS _______ :class:`comch.surjection.SurjectionElement` The image of the basis element under the May-Steenrod structure. REFERENCES ---------- [KMM]: Kaufmann, R. M., & Medina-Mardones, A. M. (2020). Chain level Steenrod operations. arXiv preprint arXiv:2010.02571. """ def i(surj, iterate=1): """Inclusion of Surj(r) into Surj(r+1) Defined by appending 1 at the start of basis elements and raising the value of all other entries by 1.""" if iterate == 1: answer = surj.zero() for k, v in surj.items(): answer += answer.create( {(1,) + tuple(j + 1 for j in k): v}) return answer if iterate > 1: return i(i(surj, iterate=iterate - 1)) def p(surj, iterate=1): """Projection of Surj(r) to Surj(r-1) Defined by removing 1 from a basis element with only one occurrences of value 1 and subtracting 1 from all other entries. """ if iterate == 1: answer = surj.zero() for k, v in surj.items(): if k.count(1) == 1: idx = k.index(1) new_k = (tuple(j - 1 for j in k[:idx]) + tuple(j - 1 for j in k[idx + 1:])) answer += answer.create({new_k: v}) return answer if iterate > 1: return p(p(surj, iterate=iterate - 1)) def s(surj): """Chain homotopy from the identity to the composition pi. Explicitly, id - ip = ds + sd.""" answer = surj.zero() for k, v in surj.items(): answer += answer.create({(1,) + tuple(j for j in k): v}) return answer def h(surj): """Chain homotopy from the identity to i...i p..p. In Surj(r), realizing its contractibility to Surj(1).""" answer = s(surj) for r in range(1, arity - 1): answer += i(s(p(surj, r)), r) return answer operators = { 0: SymmetricRing.norm_element(arity), 1: SymmetricRing.transposition_element(arity) } def psi(arity, degree, convention=convention): """Recursive definition of steenrod product over the integers.""" if degree == 0: return SurjectionElement({tuple(range(1, arity + 1)): 1}, convention=convention) else: previous = psi(arity, degree - 1, convention=convention) acted_on = operators[degree % 2] * previous answer = h(acted_on) return answer if convention is None: convention = SurjectionElement.default_convention integral_answer = psi(arity, degree, convention=convention) if torsion: integral_answer.set_torsion(torsion) return integral_answer
[docs] @staticmethod def steenrod_operation(p, s, q, bockstein=False, convention='McClure-Smith'): r"""Chain level representative of :math:`P_s` or :math:`\beta P_s`. Let :math:`A` be such that :math:`\mathrm{End}_A` is equipped with a May-Steenrod structure .. math:: \psi : W \to \mathrm{End}_A. For any prime :math:`p`, define the linear map :math:`D_d : A \otimes \mathbb F_p \to A \otimes \mathbb F_p` by .. math:: D_d(a) = \begin{cases} \psi(e_d)(a^{\otimes p})& d \geq 0 \\ 0 & d < 0. \end{cases} For any integer :math:`s`, the Steenrod operations .. math:: P_s : H_\bullet(A; \mathbb F_2) \to H_{\bullet + s}(A; \mathbb F_2) and, for :math:`p > 2`, .. math:: P_s : H_\bullet(A; \mathbb F_p) \to H_{\bullet + 2s(p-1)}(A; \mathbb F_p) \qquad \beta P_s : H_\bullet(A; \mathbb F_p) \to H_{\bullet+2s(p-1)-1}(A; \mathbb F_p) are defined for a class :math:`[a]` of degree :math:`q` respectively by .. math:: P_s\big([a]\big) = \big[D_{s-q}(a)\big] \qquad and .. math:: P_s\big([a]\big) = \big[(-1)^s \nu(q) D_{(2s-q)(p-1)}(a)\big] \qquad \beta P_s\big([a]\big) = \big[(-1)^s \nu(q)D_{(2s-q)(p-1)-1}(a)\big] where :math:`\nu(q) = (-1)^{q(q-1)m/2}(m!)^q` and :math:`m = (p-1)/2`. PARAMETERS ---------- p : :class:`int` The prime considered. s : :class:`int` The subscript of the Steenrod operation. q : :class:`int` The degree of the class acted on. bockstein : :class:`bool`, default ``False`` Determines the use of the Bockstein homomorphism. convention : :class:`string` default 'McClure-Smith' The sign convention used. RETURNS _______ :class:`comch.surjection.SurjectionElement` The surjection element representing the given Steenrod operation. REFERENCES ---------- [May]: May, J. Peter. "A general algebraic approach to Steenrod operations." The Steenrod Algebra and its Applications: a conference to celebrate NE Steenrod's sixtieth birthday. Springer, Berlin, Heidelberg, 1970. """ # input check if not all(isinstance(i, int) for i in {p, s, q}): raise TypeError('initialize with three int p,s,n') if not isinstance(bockstein, bool): raise TypeError('bockstein must be a boolean') if p == 2 and bockstein: raise TypeError('bP only defined for odd primes') if p == 2: coeff = 1 d = s - q if d < 0: return SurjectionElement(torsion=2) else: b = int(bockstein) # Serre convention: v(2j)=(-1)^j & v(2j+1)=v(2j)*m! w/ m=(p-1)/2 coeff = (-1) ** (floor(q / 2) + s) if q / 2 - floor(q / 2): coeff *= factorial((p - 1) / 2) # degree of the element: (2s-q)(p-1)-b d = (2 * s - q) * (p - 1) - b if d < 0: return SurjectionElement(torsion=p) return int(coeff) * Surjection.may_steenrod_structure( p, d, torsion=p, convention=convention)
[docs] @staticmethod def steenrod_chain(p, s, q, bockstein=False, shape='simplex'): """Chain representative of a Steenrod operation. Given the parameters of a Steenrod operation: prime p, subindex s, and cochain degree q, and bockstein, it returns the chain in the tensor product of a standard simplex on which the iterated tensor product of the cochain acts defining a cochain representative of its image under the operation. PARAMETERS ---------- p : :class:`int` The prime considered. s : :class:`int` The subscript of the Steenrod operation. q : :class:`int` The degree of the class acted on. bockstein : :class:`bool`, default ``False`` Determines the use of the bockstein homomorphism. shape: :class:`string`, 'simplex' or 'cube' Action on the standard simplex or cube RETURNS _______ :class:`comch.simplicial.SimplicialElement`\ or :class:`comch.cubical.CubicalElement` The tensor product elment obtained by applying the representative of the specified Steenrod operation. EXAMPLES -------- >>> print(Surjection.steenrod_chain(2, -1, -2)) ((0,2,3),(0,1,2)) + ((0,1,3),(1,2,3)) """ def filter_homogeneous(element): homogeneous = {} for k, v in element.items(): if len(set(elmt.dimension for elmt in k)) == 1: homogeneous[k] = v return element.create(homogeneous) surj = Surjection.steenrod_operation(p, s, q, bockstein=bockstein) b = int(bockstein) if p == 2: d = q + s else: d = q + 2 * s * (p - 1) - b if shape == 'simplex': element = Simplicial.standard_element(-d, torsion=p) elif shape == 'cube': element = Cubical.standard_element(-d, torsion=p) return filter_homogeneous(surj(element))
[docs] @staticmethod def basis(arity, degree, complexity=None): r"""Basis of the chain complex. Basis of :math:`\mathcal X` In the given arity, degree and complexity. PARAMETERS ---------- arity : :class:`int` The arity considered. degree : :class:`int` The degree considered. complexity : :class:`int`, default ``None`` The complexity considered. RETURNS _______ :class:`list` of :class:`tuple` of :class:`int` The basis for the complex of surjection in the given arity, degree and complexity. EXAMPLES -------- >>> Surjection.basis(2,1) [(1, 2, 1), (2, 1, 2)] """ if complexity is None: complexity = degree + 1 a, d, c = arity, degree, complexity basis = [] for s in product(range(1, a + 1), repeat=a + d): surj = SurjectionElement({s: 1}) if surj and surj.complexity <= c and surj.arity == a: basis.append(s) return basis