from ..free_module import FreeModuleElement
from ..symmetric import SymmetricRingElement
from ..utils import pairwise
from itertools import chain, product, combinations, combinations_with_replacement
[docs]class Simplex(tuple):
r"""A simplex :math:`(v_0, \dots, v_n)`.
A simplex is a finite non-decreasing tuple of non-negative integers.
We identity these with faces of the infinite simplex :math:`\Delta^\infty`.
"""
[docs] def __init__(self, iterable):
"""Initializes *self*.
PARAMETERS
----------
interable : :class:'iterable'
Used to create a :class:`tuple` of :class:`int`.
EXAMPLE
-------
>>> print(Simplex((1,2,4)))
(1,2,4)
"""
tuple.__init__(iterable)
def __str__(self):
return super.__str__(self).replace(', ', ',')
@property
def dimension(self):
"""The dimension of *self*.
Defined as the length of the tuple minus one.
RETURNS
-------
:class:`int`
The dimension of *self*.
EXAMPLE
-------
>>> Simplex((1,3,4,5)).dimension
3
"""
return len(self) - 1
[docs] def face(self, i):
"""The i-th face of *self*.
Obtained by removing the i-th entry of *self*.
RETURNS
-------
:class:`comch.simplicial.Simplex`
The i-th face of *self*.
EXAMPLE
-------
>>> Simplex((1,3,4,5)).face(2)
(1, 3, 5)
"""
return Simplex(self[:i] + self[i + 1:])
[docs] def degeneracy(self, i):
"""The i-th degeneracy of *self*.
Obtained by repeating the i-th entry of the tuple.
RETURNS
-------
:class:`comch.simplicial.Simplex`
The i-th face of *self*.
EXAMPLE
-------
>>> Simplex((1,3,4,5)).degeneracy(2)
(1, 3, 4, 4, 5)
"""
return Simplex(self[:i + 1] + self[i:])
[docs] def coface(self, i):
"""The i-th coface of *self*.
Obtained by adding 1 to each j-th entries with j
greater or equal to i.
RETURNS
-------
:class:`comch.simplicial.Simplex`
The i-th coface of *self*.
EXAMPLE
-------
>>> Simplex((1,3,4,5)).coface(2)
(1, 4, 5, 6)
"""
def d_i(i, j): return j + 1 if j >= i else j
return tuple(d_i(i, j) for j in self)
[docs] def codegeneracy(self, i):
"""The i-th codegeneracy of *self*.
Obtained by subtracting 1 from each j-th entries with j
greater than i.
RETURNS
-------
:class:`comch.simplicial.Simplex`
The i-th codegeneracy of *self*.
EXAMPLE
-------
>>> Simplex((1,3,4,5)).codegeneracy(2)
(1, 2, 3, 4)
"""
def s_i(i, j): return j - 1 if j > i else j
return tuple(s_i(i, j) for j in self)
[docs] def is_degenerate(self):
"""Returns ``True`` if *self* is degenerate and ``False`` if not.
A simplex is degenerate if it is empty or if contains equal consecutive
values.
RETURNS
-------
:class:`bool`
``True`` if *self* is degenerate and ``False`` if not.
EXAMPLE
-------
>>> Simplex(()).is_degenerate()
True
>>> Simplex((1,1,2)).is_degenerate()
True
"""
consecutive_values = any([i == j for i, j in pairwise(self)])
empty_simplex = (self.dimension == -1)
return empty_simplex or consecutive_values
[docs] def is_nondegenerate(self):
"""Returns ``True`` if *self* is nondegenerate and ``False`` if not.
A simplex is nondegenerate if it is not empty and contains no equal
consecutive values.
RETURNS
-------
:class:`bool`
``True`` if *self* is nondegenerate and ``False`` if not.
EXAMPLE
-------
>>> Simplex((1,2,5)).is_nondegenerate()
True
"""
return not self.is_degenerate()
[docs]class SimplicialElement(FreeModuleElement):
r"""Elements in an iterated tensor product of the chains on the
infinite simplex.
The chains on the infinite simplex :math:`C = C_\bullet(\Delta^\infty; R)`
is the differential graded module :math:`C` with degree-:math:`n` part
:math:`C_n` freely generated as an :math:`R`-module by simplices of
dimension :math:`n`, and differential on these given by the sum of its
faces with alternating signs. Explicitly,
.. math::
\partial (v_0, \dots, v_n) =
\sum_{i=0}^n (v_0, \dots, \widehat{v}_i, \dots, v_d).
The degree-:math:`n` part of the tensor product :math:`C^{\otimes r}`
and its differential are recursively defined by
.. math::
(C^{\otimes r})_n = \bigoplus_{i+j=n} C_i \otimes (C^{\otimes r})_n
and
.. math::
\partial(c_1 \otimes c_2 \otimes \cdots \otimes c_r) =
(\partial c_1) \otimes c_2 \otimes \cdots \otimes c_r +
(-1)^{|c_1|} c_1 \otimes \partial (c_2 \otimes \cdots \otimes c_r).
ATTRIBUTES
----------
torsion : :class:`int`
The non-neg int :math:`n` of the ring :math:`\mathbb Z/n \mathbb Z`.
dimension : :class:`int` non-negative
NOT SURE IF NEEDED.
"""
[docs] def __init__(self, data=None, dimension=None, torsion=None):
"""Initializes *self*.
PARAMETERS
----------
data : :class:`int` or ``None``, default: ``None``
Dictionary representing a linear cobination of basis elements.
Items in the dictionary correspond to `basis_element: coefficient`
pairs. Each basis_element must create a :class:`tuple` of
:class:`comch.simplicial.Simplex` and `coefficient` must be an
:class:`int`.
dimension : :class:`int`
NOT SURE IF NEEDED.
torsion : :class:`int`
The non-neg int :math:`n` of the ring :math:`\mathbb Z/n \mathbb Z`.
EXAMPLE
-------
>>> x = SimplicialElement({((0,), (0, 1, 2)): 1,\
((0, 1), (1, 2)): -1,\
((0, 1, 2), (2,)): 1})
>>> print(x)
((0,),(0,1,2)) - ((0,1),(1,2)) + ((0,1,2),(2,))
"""
if data:
if not dimension:
dims = (i for i in chain.from_iterable(
chain.from_iterable(data.keys())))
try:
dimensions = max(dims)
except ValueError:
dimensions = -1
new_data = {}
for k, v in data.items():
new_k = tuple(Simplex(spx) for spx in k)
new_data[new_k] = v
data = new_data
self.dimension = dimension
super(SimplicialElement, self).__init__(data=data,
torsion=torsion)
def __str__(self):
string = super().__str__()
return string.replace(', ', ',')
def _latex_(self):
r"""Representation in LaTex.
RETURNS
-------
:class:`string`
A LaTex friendly representation.
EXAMPLE
-------
>>> x = SimplicialElement({((0,), (0, 1, 2)): 1})
>>> print(x._latex_())
[0] \otimes [0,1,2]
"""
string = str(self)
string = string.replace(',),(', r'] \otimes [')
string = string.replace('),(', r'] \otimes [')
string = string.replace('((', '[')
string = string.replace(',))', ']').replace('),)', ']')
string = string.replace('))', ']')
return string
@property
def arity(self):
"""Arity of *self*.
Defined as ``None`` if *self* is not homogeneous. The arity of a basis
element is defined as the number of tensor factors making it.
RETURNS
-------
:class:`int` positive or ``None``.
The length of the keys of *self* or ``None`` if not well defined.
EXAMPLE
-------
>>> x = SimplicialElement({((0,), (0, 1, 2)): 1})
>>> x.arity
2
"""
arities = set(len(multispx) for multispx in self.keys())
if len(arities) != 1:
return None
return arities.pop()
@property
def degree(self):
"""Degree of *self*.
Defined as ``None`` if self is not homogeneous. The degree of a basis
element agrees with the sum of the dimension of the simplices making it.
RETURNS
-------
:class:`int` positive or ``None``.
The sum of the dimensions of the simplices of every key of *self* or
``None`` if not well defined.
EXAMPLE
-------
>>> x = SimplicialElement({((0,), (0, 1, 2)): 1})
>>> x.degree
2
"""
degs = {sum(spx.dimension for spx in k) for k in self.keys()}
if len(degs) != 1:
return None
return degs.pop()
[docs] def boundary(self):
"""Boundary of *self*.
As defined in the class's docstring.
RETURNS
_______
:class:`comch.simplicical.SimplicialElement`
The boundary of *self* as an element in a tensor product of
differential graded modules.
EXAMPLE
-------
>>> x = SimplicialElement({((0, 1), (1, 2)): 1})
>>> print(x.boundary())
((1,),(1,2)) - ((0,),(1,2)) - ((0,1),(2,)) + ((0,1),(1,))
"""
answer = self.zero()
for k, v in self.items():
for idx, spx in enumerate(k):
acc_dim = sum((spx_left.dimension for spx_left in k[:idx]))
for i in range(spx.dimension + 1):
new_spx = spx.face(i)
new_k = k[:idx] + (new_spx,) + k[idx + 1:]
sign_exp = (acc_dim + i) % 2
answer += answer.create({new_k: v * (-1) ** sign_exp})
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 permuting the tensor factor.
PARAMETERS
----------
other : :class:`int` or :class:`comch.symmetric.SymmetricElement`.
The symmetric ring element left acting on *self*.
RETURNS
_______
:class:`comch.simplicial.SimplicialElement`
The product: *other* ``*`` *self* with Koszul's sign convention.
EXAMPLE
-------
>>> x = SimplicialElement({((0, 1), (1, 2)): 1})
>>> t = SymmetricRingElement({(2, 1): 1})
>>> print(t * x)
- ((1,2),(0,1))
>>> print(3 * x)
3((0,1),(1,2))
"""
def check_input(self, other):
"""Symmetric ring element with same attributes."""
if not isinstance(other, SymmetricRingElement):
raise TypeError(f'__rmul__ by 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, multispx):
weights = [spx.dimension % 2 for spx in multispx]
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 = [None] * len(k2)
for idx, i in enumerate(k2):
new_key[i - 1] = k1[idx]
new_sign = sign(k2, k1)
answer += self.create({tuple(new_key): new_sign * v1 * v2})
return answer
[docs] def iterated_diagonal(self, times=1, coord=1):
r"""Iterated Alexander-Whitney diagonal applied at a specific tensor factor.
The AW diagonal is the chain map :math:`\Delta \colon C \to C \otimes C`
defined on the chains of the infinite simplex by the formula
.. math::
\Delta((v_0, dots, v_n)) =
\sum_{i=0}^n (v_0, dots, v_i) \otimes (v_i, dots, v_n).
It is coassociative, :math:`(\Delta \otimes \mathrm{id}) \Delta =
(\mathrm{id} \otimes \Delta) \Delta`, so it has a well defined iteration
:math:`\Delta^k`, and for every :math:`i \in \{1, \dots, r\}`, there is map
:math:`C^{\otimes r} \to C^{\otimes k+r}` sending
:math:`(x_1 \otimes \cdots \otimes x_n)` to
:math:`(x_1 \otimes \cdots \otimes \Delta^k(k_i) \cdots \otimes x_n)`.
PARAMETERS
----------
times : :class:`int`
The number of times the AW diagonal is composed with itself.
coord : :class:`int`
The tensor position on which the iterated diagonal acts.
RETURNS
_______
:class:`comch.simplicial.SimplicialElement`
The action of the iterated AW diagonal on *self*.
EXAMPLE
-------
>>> x = SimplicialElement({((0, 1, 2), ): 1})
>>> print(x.iterated_diagonal())
((0,),(0,1,2)) + ((0,1),(1,2)) + ((0,1,2),(2,))
"""
if self.degree is None:
raise TypeError(f'only for homogeneous elements')
if self.arity < coord:
raise TypeError(f'arity = {self.arity} < coord = {coord}')
answer = self.zero()
for k, v in self.items():
left, spx, right = k[:coord - 1], k[coord - 1], k[coord:]
for p in combinations_with_replacement(range(self.degree + 1), times):
p = (0,) + p + (self.degree,)
new_k = []
for i, j in pairwise(p):
new_k.append(Simplex(spx[i:j + 1]))
answer += self.create({left + tuple(new_k) + right: v})
return answer
[docs] def one_reduced(self):
"""Returns the 1-reduction of *self*.
The 1-reduction map is the map induced by the collapse of the
1-skeleton of the infinite simplex.
RETURNS
_______
:class:`comch.simplicial.SimplicialElement`
The preferred representative of *self*.
EXAMPLE
-------
>>> x = SimplicialElement({((1,2), (2,3,4)): 1})
>>> print(x.one_reduced())
0
"""
answer = self.zero()
for k, v in self.items():
if all(spx.dimension != 1 for spx in k):
answer += self.create({k: v})
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 a degenerate tensor factor, or 2) the
coefficient is 0.
RETURNS
_______
:class:`comch.simplicial.SimplicialElement`
The preferred representative of *self*.
EXAMPLE
-------
>>> print(SimplicialElement({((1,3), (1,1)): 1}))
0
"""
for k, v in self.items():
if any([spx.is_degenerate() for spx in k]):
self[k] = 0
super().preferred_rep()
[docs]class Simplicial:
"""Produces simplicial elements of interest."""
[docs] @staticmethod
def standard_element(n, times=1, torsion=None):
r"""The chain represented by the simplex :math:`(0, \dots, n)`.
PARAMETERS
----------
n : :class:`int`
The dimension of the standard simplex considered.
times : :class:`int`
The number of tensor copies.
torsion : :class:`int`
The non-neg int :math:`n` of the ring :math:`\mathbb Z/n \mathbb Z`.
EXAMPLES
--------
>>> print(Simplicial.standard_element(3, 2))
((0,1,2,3),(0,1,2,3))
"""
key = (tuple(range(n + 1)),) * times
return SimplicialElement({key: 1}, torsion=torsion)
[docs] @staticmethod
def basis(n, torsion=None):
r"""Iterator of all basis elements in the chain complex of a n-simplex.
PARAMETERS
----------
n : :class:`int`
The dimension of the standard simplex considered.
torsion : :class:`int`
The non-neg int :math:`n` of the ring :math:`\mathbb Z/n \mathbb Z`.
EXAMPLES
--------
>>> print([str(b) for b in Simplicial.basis(1)])
['((0,),)', '((1,),)', '((0,1),)']
"""
for m in range(1, n+2):
for b in combinations(range(n+1), m):
yield SimplicialElement({(b,): 1}, torsion=torsion)