Path: blob/master/src/sage/geometry/point_collection.pyx
8815 views
r"""1Point collections23This module was designed as a part of framework for toric varieties4(:mod:`~sage.schemes.toric.variety`,5:mod:`~sage.schemes.toric.fano_variety`).67AUTHORS:89- Andrey Novoseltsev (2011-04-25): initial version, based on cone module.1011- Andrey Novoseltsev (2012-03-06): additions and doctest changes while12switching cones to use point collections.1314EXAMPLES:1516The idea behind :class:`point collections <PointCollection>` is to have a17container for points of the same space that1819* behaves like a tuple *without significant performance penalty*::2021sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()22sage: c[1]23N(1, 0, 1)24sage: for point in c: point25N(0, 0, 1)26N(1, 0, 1)27N(0, 1, 1)28N(1, 1, 1)2930* prints in a convenient way and with clear indication of the ambient space::3132sage: c33N(0, 0, 1),34N(1, 0, 1),35N(0, 1, 1),36N(1, 1, 1)37in 3-d lattice N3839* allows (cached) access to alternative representations::4041sage: c.set()42frozenset([N(0, 1, 1), N(1, 1, 1), N(0, 0, 1), N(1, 0, 1)])4344* allows introduction of additional methods::4546sage: c.basis()47N(0, 0, 1),48N(1, 0, 1),49N(0, 1, 1)50in 3-d lattice N5152Examples of natural point collections include ray and line generators of cones,53vertices and points of polytopes, normals to facets, their subcollections, etc.5455Using this class for all of the above cases allows for unified interface *and*56cache sharing. Suppose that `\Delta` is a reflexive polytope. Then the same57point collection can be linked as5859#. vertices of `\Delta`;60#. facet normals of its polar `\Delta^\circ`;61#. ray generators of the face fan of `\Delta`;62#. ray generators of the normal fan of `\Delta`.6364If all these objects are in use and, say, a matrix representation was computed65for one of them, it becomes available to all others as well, eliminating the66need to spend time and memory four times.67"""6869#*****************************************************************************70# Copyright (C) 2012 Andrey Novoseltsev <[email protected]>71#72# Distributed under the terms of the GNU General Public License (GPL)73# as published by the Free Software Foundation; either version 2 of74# the License, or (at your option) any later version.75# http://www.gnu.org/licenses/76#*****************************************************************************7778from sage.structure.sage_object cimport SageObject7980from sage.matrix.all import matrix81from sage.misc.all import latex828384def is_PointCollection(x):85r"""86Check if ``x`` is a :class:`point collection <PointCollection>`.8788INPUT:8990- ``x`` -- anything.9192OUTPUT:9394- ``True`` if ``x`` is a point collection and ``False`` otherwise.9596EXAMPLES::9798sage: from sage.geometry.point_collection import is_PointCollection99sage: is_PointCollection(1)100False101sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)])102sage: is_PointCollection(c.rays())103True104"""105return isinstance(x, PointCollection)106107108_output_format = "default"109110111cdef class PointCollection(SageObject):112r"""113Create a point collection.114115.. WARNING::116117No correctness check or normalization is performed on the input data.118This class is designed for internal operations and you probably should119not use it directly.120121Point collections are immutable, but cache most of the returned values.122123INPUT:124125- ``points`` -- an iterable structure of immutable elements of ``module``,126if ``points`` are already accessible to you as a :class:`tuple`, it is127preferable to use it for speed and memory consumption reasons;128129- ``module`` -- an ambient module for ``points``. If ``None``, it will be130determined as :func:`parent` of the first point. Of course, this cannot131be done if there are no points, so in this case you must give an132appropriate ``module`` directly. Note that ``None`` is *not* the default133value - you always *must* give this argument explicitly, even if it is134``None``.135136OUTPUT:137138- a point collection.139"""140141cdef:142tuple _points143object _module144# cache attributes145PointCollection _basis146object _matrix147frozenset _set148149def __init__(self, points, module=None):150r"""151See :class:`PointCollection` for documentation.152153TESTS::154155sage: from sage.geometry.point_collection import PointCollection156sage: v = vector([1,0])157sage: v.set_immutable()158sage: c = PointCollection([v], ZZ^2)159sage: c.module()160Ambient free module of rank 2161over the principal ideal domain Integer Ring162sage: c = PointCollection([v], None)163sage: c.module() # Determined automatically164Ambient free module of rank 2165over the principal ideal domain Integer Ring166sage: TestSuite(c).run()167"""168super(PointCollection, self).__init__()169self._points = tuple(points)170self._module = self._points[0].parent() if module is None else module171172def __add__(left, right):173r"""174Return the joint point collection.175176INPUT:177178- ``left`` -- a :class:`PointCollection`;179180- ``right`` -- a :class:`PointCollection`.181182OUTPUT:183184- a :class:`PointCollection`.185186TESTS::187188sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()189sage: c + c190N(0, 0, 1),191N(1, 0, 1),192N(0, 1, 1),193N(1, 1, 1),194N(0, 0, 1),195N(1, 0, 1),196N(0, 1, 1),197N(1, 1, 1)198in 3-d lattice N199"""200if not (isinstance(left, PointCollection) and201isinstance(right, PointCollection)):202raise NotImplementedError203cdef PointCollection left_pc = left204cdef PointCollection right_pc = right205if not left_pc._module is right_pc._module:206raise NotImplementedError207return PointCollection(left_pc._points + right_pc._points,208left_pc._module)209210def __call__(self, *args):211r"""212Return a subcollection of ``self``.213214INPUT:215216- a list of integers (as a single or many arguments).217218OUTPUT:219220- a :class:`point collection <PointCollection>`.221222TESTS::223224sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()225sage: c()226Empty collection227in 3-d lattice N228sage: c(2,1)229N(0, 1, 1),230N(1, 0, 1)231in 3-d lattice N232sage: c(range(4)) == c233True234"""235if len(args) == 1:236try:237args = tuple(args[0])238except TypeError:239pass240# Avoid creating a copy of self241if len(args) == len(self) and args == tuple(range(len(self))):242return self243else:244return PointCollection([self[i] for i in args], self._module)245246def __cmp__(self, right):247r"""248Compare ``self`` and ``right``.249250INPUT:251252- ``right`` -- anything.253254OUTPUT:255256- 0 if ``right`` is of the same type as ``self`` (i.e. it is another257:class:`point collection <PointCollection>`), they have the same258:meth:`module`, and their points are the same and listed in the same259order. 1 or -1 otherwise.260261TESTS::262263sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()264sage: cmp(c, c)2650266sage: cmp(c, 1) * cmp(1, c)267-1268"""269c = cmp(type(self), type(right))270if c != 0:271return c272cdef PointCollection pc = right273c = cmp(self._module, pc._module)274if c != 0:275return c276return cmp(self._points, pc._points)277278def __getitem__(self, n):279r"""280Return the ``n``-th point of ``self``.281282INPUT:283284- ``n`` -- an integer.285286OUTPUT:287288- a point, an element of the ambient :meth:`module` of ``self``.289290EXAMPLES::291292sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()293sage: c[0]294N(0, 0, 1)295"""296return self._points[n]297298def __hash__(self):299r"""300Return the hash of ``self``.301302OUTPUT:303304- an integer.305306TESTS::307308sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()309sage: hash(c) == hash(c)310True311"""312return hash(self._points)313314def __iter__(self):315r"""316Return an iterator over points of ``self``.317318OUTPUT:319320- an iterator.321322TESTS::323324sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()325sage: for point in c: print point326N(0, 0, 1)327N(1, 0, 1)328N(0, 1, 1)329N(1, 1, 1)330"""331return iter(self._points)332333def __len__(self):334r"""335Return the number of points in ``self``.336337OUTPUT:338339- an integer.340341EXAMPLES::342343sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()344sage: len(c)3454346"""347return len(self._points)348349def __list__(self):350r"""351Return a list of points of ``self``.352353OUTPUT:354355- a list.356357TESTS::358359sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()360sage: list(c)361[N(0, 0, 1), N(1, 0, 1), N(0, 1, 1), N(1, 1, 1)]362"""363return list(self._points)364365def __reduce__(self):366r"""367Prepare ``self`` for pickling.368369OUTPUT:370371- a tuple, currently the class name and a tuple consisting of points372and the ambient module.373374TESTS::375376sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()377sage: loads(dumps(c))378N(0, 0, 1),379N(1, 0, 1),380N(0, 1, 1),381N(1, 1, 1)382in 3-d lattice N383sage: loads(dumps(c)) == c384True385"""386return (PointCollection, (self._points, self._module))387388def __tuple__(self):389r"""390Return the tuple of points of ``self``.391392OUTPUT:393394- a tuple.395396TESTS::397398sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()399sage: tuple(c)400(N(0, 0, 1), N(1, 0, 1), N(0, 1, 1), N(1, 1, 1))401"""402return self._points403404def _latex_(self):405r"""406Return a LaTeX representation of ``self``.407408OUTPUT:409410- a string.411412TESTS::413414sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()415sage: print c._latex_()416\left(\left(0,\,0,\,1\right)_{N}, \left(1,\,0,\,1\right)_{N},417\left(0,\,1,\,1\right)_{N}, \left(1,\,1,\,1\right)_{N}\right)_{N}418"""419global _output_format420if _output_format in ["default", "tuple"]:421r = latex(tuple(self))422elif _output_format == "matrix":423r = latex(self.matrix())424elif _output_format == "column matrix":425r = latex(self.column_matrix())426elif _output_format == "separated column matrix":427r = latex(self.column_matrix())428r = r.replace("r" * len(self), "|".join("r" * len(self)))429return r"%s_{%s}" % (r, latex(self.module()))430431def _matrix_(self, ring=None):432r"""433Return a matrix whose rows are points of ``self``.434435INPUT:436437- ``ring`` -- a base ring for the returned matrix (default: base ring of438:meth:`module` of ``self``).439440OUTPUT:441442- a :class:`matrix <Matrix>`.443444EXAMPLES::445446sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()447sage: matrix(c)448[0 0 1]449[1 0 1]450[0 1 1]451[1 1 1]452"""453if ring is None:454return self.matrix()455else:456return self.matrix().change_ring(ring)457458def _repr_(self):459r"""460Return a string representation of ``self``.461462OUTPUT:463464- a string.465466TESTS::467468sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()469sage: print c._repr_()470N(0, 0, 1),471N(1, 0, 1),472N(0, 1, 1),473N(1, 1, 1)474in 3-d lattice N475"""476global _output_format477if _output_format == "default":478r = map(repr, self)479r = [point.split(",") for point in r]480if not r:481r = "Empty collection"482else:483if "(" in r[0][0]:484delimiter = "("485elif "[" in r[0][0]:486delimiter = "["487else:488raise ValueError("cannot parse point representation!")489heads = []490for point in r:491head, point[0] = point[0].rsplit(delimiter, 1)492heads.append(head + delimiter)493format = "{{:<{}}}".format(max(map(len, heads)))494widths = [0] * len(r[0])495for point in r:496for i, coordinate in enumerate(point):497widths[i] = max(widths[i], len(coordinate))498format += ",".join("{{:>{}}}".format(width) for width in widths)499r = ",\n".join([format.format(head, *point)500for head, point in zip(heads, r)])501elif _output_format == "tuple":502r = tuple(self)503elif _output_format == "matrix":504r = self.matrix()505else:506r = self.column_matrix()507return "{}\nin {}".format(r, self.module())508509def basis(self):510r"""511Return a linearly independent subset of points of ``self``.512513OUTPUT:514515- a :class:`point collection <PointCollection>` giving a random (but516fixed) choice of an `\RR`-basis for the vector space spanned by the517points of ``self``.518519EXAMPLES::520521sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()522sage: c.basis()523N(0, 0, 1),524N(1, 0, 1),525N(0, 1, 1)526in 3-d lattice N527528Calling this method twice will always return *exactly the same* point529collection::530531sage: c.basis().basis() is c.basis()532True533"""534if self._basis is None:535self._basis = self(self.matrix().pivot_rows())536return self._basis537538def cardinality(self):539r"""540Return the number of points in ``self``.541542OUTPUT:543544- an integer.545546EXAMPLES::547548sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()549sage: c.cardinality()5504551"""552return len(self._points)553554def cartesian_product(self, other, module=None):555r"""556Return the Cartesian product of ``self`` with ``other``.557558INPUT:559560- ``other`` -- a :class:`point collection <PointCollection>`;561562- ``module`` -- (optional) the ambient module for the result. By563default, the direct sum of the ambient modules of ``self`` and564``other`` is constructed.565566OUTPUT:567568- a :class:`point collection <PointCollection>`.569570EXAMPLES::571572sage: c = Cone([(0,0,1), (1,1,1)]).rays()573sage: c.cartesian_product(c)574N+N(0, 0, 1, 0, 0, 1),575N+N(1, 1, 1, 0, 0, 1),576N+N(0, 0, 1, 1, 1, 1),577N+N(1, 1, 1, 1, 1, 1)578in 6-d lattice N+N579"""580assert is_PointCollection(other)581if module is None:582module = self._module.direct_sum(other.module())583P = [list(p) for p in self]584Q = [list(q) for q in other]585PQ = [module(p + q) for q in Q for p in P]586for pq in PQ:587pq.set_immutable()588return PointCollection(PQ, module)589590def column_matrix(self):591r"""592Return a matrix whose columns are points of ``self``.593594OUTPUT:595596- a :class:`matrix <Matrix>`.597598EXAMPLES::599600sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()601sage: c.column_matrix()602[0 1 0 1]603[0 0 1 1]604[1 1 1 1]605"""606return self.matrix().transpose()607608def dimension(self):609r"""610Return the dimension of the space spanned by points of ``self``.611612.. note:: You can use either :meth:`dim` or :meth:`dimension`.613614OUTPUT:615616- an integer.617618EXAMPLES::619620sage: c = Cone([(0,0,1), (1,1,1)]).rays()621sage: c.dimension()6222623sage: c.dim()6242625"""626return self.matrix().rank()627628dim = dimension629630def dual_module(self):631r"""632Return the dual of the ambient module of ``self``.633634OUTPUT:635636- a :class:`module <FreeModule_generic>`. If possible (that is, if the637ambient :meth:`module` `M` of ``self`` has a ``dual()`` method), the638dual module is returned. Otherwise, `R^n` is returned, where `n` is639the dimension of `M` and `R` is its base ring.640641EXAMPLES::642643sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()644sage: c.dual_module()6453-d lattice M646"""647M = self._module648try:649return M.dual()650except AttributeError:651# TODO: add support for torsion modules as well?652return M.base_ring() ** M.dimension()653654def index(self, *args):655r"""656Return the index of the first occurrence of ``point`` in ``self``.657658INPUT:659660- ``point`` -- a point of ``self``;661662- ``start`` -- (optional) an integer, if given, the search will start663at this position;664665- ``stop`` -- (optional) an integer, if given, the search will stop666at this position.667668OUTPUT:669670- an integer if ``point`` is in ``self[start:stop]``, otherwise a671``ValueError`` exception is raised.672673EXAMPLES::674675sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()676sage: c.index((0,1,1))677Traceback (most recent call last):678...679ValueError: tuple.index(x): x not in tuple680681Note that this was not a mistake: the *tuple* ``(0,1,1)`` is *not* a682point of ``c``! We need to pass actual element of the ambient module of683``c`` to get their indices::684685sage: N = c.module()686sage: c.index(N(0,1,1))6872688sage: c[2]689N(0, 1, 1)690"""691return self._points.index(*args)692693def matrix(self):694r"""695Return a matrix whose rows are points of ``self``.696697OUTPUT:698699- a :class:`matrix <Matrix>`.700701EXAMPLES::702703sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()704sage: c.matrix()705[0 0 1]706[1 0 1]707[0 1 1]708[1 1 1]709"""710if self._matrix is None:711M = matrix(self._module.base_ring(), len(self._points),712self._module.degree(), self._points)713M.set_immutable()714self._matrix = M715return self._matrix716717def module(self):718r"""719Return the ambient module of ``self``.720721OUTPUT:722723- a :class:`module <FreeModule_generic>`.724725EXAMPLES::726727sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()728sage: c.module()7293-d lattice N730"""731return self._module732733@classmethod # @staticmethod does not work in Cython so far734def output_format(cls, format=None):735r"""736Return or set the output format for **ALL** point collections.737738INPUT:739740- ``format`` -- (optional) if given, must be one of the strings741* "default" -- output one point per line with vertical alignment of742coordinates in text mode, same as "tuple" for LaTeX;743* "tuple" -- output ``tuple(self)`` with lattice information;744* "matrix" -- output :meth:`matrix` with lattice information;745* "column matrix" -- output :meth:`column_matrix` with lattice746information;747* "separated column matrix" -- same as "column matrix" for text748mode, for LaTeX separate columns by lines (not shown by jsMath).749750OUTPUT:751752- a string with the current format (only if ``format`` was omitted).753754This function affects both regular and LaTeX output.755756EXAMPLES::757758sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()759sage: c760N(0, 0, 1),761N(1, 0, 1),762N(0, 1, 1),763N(1, 1, 1)764in 3-d lattice N765sage: c.output_format()766'default'767sage: c.output_format("tuple")768sage: c769(N(0, 0, 1), N(1, 0, 1), N(0, 1, 1), N(1, 1, 1))770in 3-d lattice N771sage: c.output_format("matrix")772sage: c773[0 0 1]774[1 0 1]775[0 1 1]776[1 1 1]777in 3-d lattice N778sage: c.output_format("column matrix")779sage: c780[0 1 0 1]781[0 0 1 1]782[1 1 1 1]783in 3-d lattice N784sage: c.output_format("separated column matrix")785sage: c786[0 1 0 1]787[0 0 1 1]788[1 1 1 1]789in 3-d lattice N790791Note that the last two outpus are identical, separators are only792inserted in the LaTeX mode::793794sage: latex(c)795\left(\begin{array}{r|r|r|r}7960 & 1 & 0 & 1 \\7970 & 0 & 1 & 1 \\7981 & 1 & 1 & 1799\end{array}\right)_{N}800801Since this is a static method, you can call it for the class directly::802803sage: from sage.geometry.point_collection import PointCollection804sage: PointCollection.output_format("default")805sage: c806N(0, 0, 1),807N(1, 0, 1),808N(0, 1, 1),809N(1, 1, 1)810in 3-d lattice N811"""812global _output_format813if format is None:814return _output_format815assert format in ["default", "tuple", "matrix", "column matrix",816"separated column matrix"]817_output_format = format818819def set(self):820r"""821Return points of ``self`` as a :class:`frozenset`.822823OUTPUT:824825- a :class:`frozenset`.826827EXAMPLES::828829sage: c = Cone([(0,0,1), (1,0,1), (0,1,1), (1,1,1)]).rays()830sage: c.set()831frozenset([N(0, 1, 1), N(1, 1, 1), N(0, 0, 1), N(1, 0, 1)])832"""833if self._set is None:834self._set = frozenset(self._points)835return self._set836837838