Path: blob/master/src/sage/geometry/triangulation/point_configuration.py
8817 views
r"""1Triangulations of a point configuration23A point configuration is a finite set of points in Euclidean space or,4more generally, in projective space. A triangulation is a simplicial5decomposition of the convex hull of a given point configuration such6that all vertices of the simplices end up lying on points of the7configuration. That is, there are no new vertices apart from the8initial points.910Note that points that are not vertices of the convex hull need not be11used in the triangulation. A triangulation that does make use of all12points of the configuration is called fine, and you can restrict13yourself to such triangulations if you want. See14:class:`PointConfiguration` and15:meth:`~PointConfiguration.restrict_to_fine_triangulations` for16more details.1718Finding a single triangulation and listing all connected19triangulations is implemented natively in this package. However, for20more advanced options [TOPCOM]_ needs to be installed. It is available21as an optional package for Sage, and you can install it with the22command::2324sage: install_package('TOPCOM') # not tested2526.. note::2728TOPCOM and the internal algorithms tend to enumerate29triangulations in a different order. This is why we always30explicitly specify the engine as ``engine='TOPCOM'`` or31``engine='internal'`` in the doctests. In your own applications,32you do not need to specify the engine. By default, TOPCOM is used33if it is available and the internal algorithms are used otherwise.3435EXAMPLES:3637First, we select the internal implementation for enumerating38triangulations::3940sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM4142A 2-dimensional point configuration::4344sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])45sage: p46A point configuration in QQ^2 consisting of 5 points. The47triangulations of this point configuration are assumed to48be connected, not necessarily fine, not necessarily regular.49sage: t = p.triangulate() # a single triangulation50sage: t51(<1,3,4>, <2,3,4>)52sage: len(t)53254sage: t[0]55(1, 3, 4)56sage: t[1]57(2, 3, 4)58sage: list(t)59[(1, 3, 4), (2, 3, 4)]60sage: t.plot(axes=False)61sage: list( p.triangulations() )62[(<1,3,4>, <2,3,4>),63(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),64(<1,2,3>, <1,2,4>),65(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]66sage: p_fine = p.restrict_to_fine_triangulations()67sage: p_fine68A point configuration in QQ^2 consisting of 5 points. The69triangulations of this point configuration are assumed to70be connected, fine, not necessarily regular.71sage: list( p_fine.triangulations() )72[(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),73(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]7475A 3-dimensional point configuration::7677sage: p = [[0,-1,-1],[0,0,1],[0,1,0], [1,-1,-1],[1,0,1],[1,1,0]]78sage: points = PointConfiguration(p)79sage: triang = points.triangulate()80sage: triang.plot(axes=False)8182The standard example of a non-regular triangulation::8384sage: p = PointConfiguration([[-1,-5/9],[0,10/9],[1,-5/9],[-2,-10/9],[0,20/9],[2,-10/9]])85sage: regular = p.restrict_to_regular_triangulations(True).triangulations_list() # optional - TOPCOM86sage: nonregular = p.restrict_to_regular_triangulations(False).triangulations_list() # optional - TOPCOM87sage: len(regular) # optional - TOPCOM881689sage: len(nonregular) # optional - TOPCOM90291sage: nonregular[0].plot(aspect_ratio=1, axes=False) # optional - TOPCOM9293Note that the points need not be in general position. That is, the94points may lie in a hyperplane and the linear dependencies will be95removed before passing the data to TOPCOM which cannot handle it::9697sage: points = [[0,0,0,1],[0,3,0,1],[3,0,0,1],[0,0,1,1],[0,3,1,1],[3,0,1,1],[1,1,2,1]]98sage: points = [ p+[1,2,3] for p in points ]99sage: pc = PointConfiguration(points)100sage: pc.ambient_dim()1017102sage: pc.dim()1033104sage: pc.triangulate()105(<0,1,2,6>, <0,1,3,6>, <0,2,3,6>, <1,2,4,6>, <1,3,4,6>, <2,3,5,6>, <2,4,5,6>)106sage: _ in pc.triangulations()107True108sage: len( pc.triangulations_list() )10926110111REFERENCES:112113.. [TOPCOM]114J. Rambau,115TOPCOM <http://www.rambau.wm.uni-bayreuth.de/TOPCOM/>.116117.. [GKZ]118Gel'fand, I. M.; Kapranov, M. M.; and Zelevinsky, A. V.119"Discriminants, Resultants and Multidimensional Determinants" Birkhauser 1994.120121.. [PUNTOS]122Jesus A. De Loera123http://www.math.ucdavis.edu/~deloera/RECENT_WORK/puntos2000124125AUTHORS:126127- Volker Braun: initial version, 2010128129- Josh Whitney: added functionality for computing130volumes and secondary polytopes of PointConfigurations131132- Marshall Hampton: improved documentation and doctest coverage133134- Volker Braun: rewrite using Parent/Element and catgories. Added135a Point class. More doctests. Less zombies.136137- Volker Braun: Cythonized parts of it, added a C++ implementation138of the bistellar flip algorithm to enumerate all connected139triangulations.140141- Volker Braun 2011: switched the triangulate() method to the142placing triangulation (faster).143"""144145########################################################################146# Note: The doctests that make use of TOPCOM are147# marked # optional - TOPCOM148# If you have it installed, run doctests as149#150# sage -tp 4 --long --optional=sage,topcom sage/geometry/triangulation/151########################################################################152153154########################################################################155# Copyright (C) 2010 Volker Braun <[email protected]>156# Copyright (C) 2010 Josh Whitney <[email protected]>157# Copyright (C) 2010 Marshall Hampton <[email protected]>158#159# Distributed under the terms of the GNU General Public License (GPL)160#161# http://www.gnu.org/licenses/162########################################################################163164from sage.structure.unique_representation import UniqueRepresentation165from sage.structure.element import Element166from sage.misc.cachefunc import cached_method167168from sage.combinat.combination import Combinations169from sage.rings.all import QQ, ZZ170from sage.matrix.constructor import matrix171from sage.modules.all import vector172from sage.groups.perm_gps.permgroup import PermutationGroup173174from copy import copy175import sys176import pexpect177178179from sage.geometry.triangulation.base import \180PointConfiguration_base, Point, ConnectedTriangulationsIterator181182from sage.geometry.triangulation.element import Triangulation183184185########################################################################186class PointConfiguration(UniqueRepresentation, PointConfiguration_base):187"""188A collection of points in Euclidean (or projective) space.189190This is the parent class for the triangulations of the point191configuration. There are a few options to specifically select what192kind of triangulations are admissible.193194INPUT:195196The constructor accepts the following arguments:197198- ``points`` -- the points. Technically, any iterable of iterables199will do. In particular, a :class:`PointConfiguration` can be passed.200201- ``projective`` -- boolean (default: ``False``). Whether the202point coordinates should be interpreted as projective (``True``)203or affine (``False``) coordinates. If necessary, points are204projectivized by setting the last homogeneous coordinate to one205and/or affine patches are chosen internally.206207- ``connected`` -- boolean (default: ``True``). Whether the208triangulations should be connected to the regular triangulations209via bistellar flips. These are much easier to compute than all210triangulations.211212- ``fine`` -- boolean (default: ``False``). Whether the213triangulations must be fine, that is, make use of all points of214the configuration.215216- ``regular`` -- boolean or ``None`` (default: ``None``). Whether217the triangulations must be regular. A regular triangulation is218one that is induced by a piecewise-linear convex support219function. In other words, the shadows of the faces of a220polyhedron in one higher dimension.221222* ``True``: Only regular triangulations.223224* ``False``: Only non-regular triangulations.225226* ``None`` (default): Both kinds of triangulation.227228- ``star`` -- either ``None`` or a point. Whether the229triangulations must be star. A triangulation is star if all230maximal simplices contain a common point. The central point can231be specified by its index (an integer) in the given points or by232its coordinates (anything iterable.)233234EXAMPLES::235236sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])237sage: p238A point configuration in QQ^2 consisting of 5 points. The239triangulations of this point configuration are assumed to240be connected, not necessarily fine, not necessarily regular.241sage: p.triangulate() # a single triangulation242(<1,3,4>, <2,3,4>)243"""244245246# we cache the output of _have_TOPCOM() in this class variable247_have_TOPCOM_cached = None248249# whether to use TOPCOM. Will be set to True or False during250# initialization. All implementations should check this boolean251# variable to decide whether to call TOPCOM or not252_use_TOPCOM = None253254255@classmethod256def _have_TOPCOM(cls):257r"""258Return whether TOPCOM is installed.259260EXAMPLES::261262sage: PointConfiguration._have_TOPCOM() # optional - TOPCOM263True264"""265if PointConfiguration._have_TOPCOM_cached != None:266return PointConfiguration._have_TOPCOM_cached267268try:269out = PointConfiguration._TOPCOM_exec('points2placingtriang',270'[[0,1],[1,1]]', verbose=False).next()271PointConfiguration._have_TOPCOM_cached = True272assert out=='{{0,1}}',\273'TOPCOM ran but did not produce the correct output!'274except pexpect.ExceptionPexpect:275PointConfiguration._have_TOPCOM_cached = False276277PointConfiguration.set_engine('auto')278return PointConfiguration._have_TOPCOM_cached279280281@staticmethod282def __classcall__(cls, points, projective=False, connected=True, fine=False, regular=None, star=None):283r"""284Normalize the constructor arguments to be unique keys.285286EXAMPLES::287288sage: pc1 = PointConfiguration([[1,2],[2,3],[3,4]], connected=True)289sage: pc2 = PointConfiguration(((1,2),(2,3),(3,4)), regular=None)290sage: pc1 is pc2 # indirect doctest291True292"""293if isinstance(points, PointConfiguration_base):294pc = points295points = tuple( p.projective() for p in points )296projective = True297defined_affine = pc.is_affine()298elif projective:299points = tuple( tuple(p) for p in points )300defined_affine = False301else:302points = tuple( tuple(p)+(1,) for p in points )303defined_affine = True304if star!=None and star not in ZZ:305star_point = tuple(star)306if len(star_point)<len(points[0]):307star_point = tuple(star)+(1,)308star = points.index(star_point)309return super(PointConfiguration, cls)\310.__classcall__(cls, points, connected, fine, regular, star, defined_affine)311312313def __init__(self, points, connected, fine, regular, star, defined_affine):314"""315Initialize a :class:`PointConfiguration` object.316317EXAMPLES::318319sage: p = PointConfiguration([[0,4],[2,3],[3,2],[4,0],[3,-2],[2,-3],[0,-4],[-2,-3],[-3,-2],[-4,0],[-3,2],[-2,3]])320sage: len(p.triangulations_list()) # long time (26s on sage.math, 2012)32116796322323TESTS::324325sage: TestSuite(p).run()326"""327# first, test if we have TOPCOM and set up class variables accordingly328PointConfiguration._have_TOPCOM()329330assert connected in [True, False], 'Unknown value: connected='+str(connected)331self._connected = connected332if connected!=True and not PointConfiguration._have_TOPCOM():333raise ValueError, 'You must install TOPCOM to find non-connected triangulations.'334335assert fine in [True, False], 'Unknown value: fine='+str(fine)336self._fine = fine337338assert regular in [True, False, None], 'Unknown value: regular='+str(regular)339self._regular = regular340if regular!=None and not PointConfiguration._have_TOPCOM():341raise ValueError, 'You must install TOPCOM to test for regularity.'342343assert star==None or star in ZZ, 'Unknown value: fine='+str(star)344self._star = star345346PointConfiguration_base.__init__(self, points, defined_affine)347348349@classmethod350def set_engine(cls, engine='auto'):351r"""352Set the engine used to compute triangulations.353354INPUT:355356- ``engine`` -- either 'auto' (default), 'internal', or357'TOPCOM'. The latter two instruct this package to always use358its own triangulation algorithms or TOPCOM's algorithms,359respectively. By default ('auto'), TOPCOM is used if it is360available and internal routines otherwise.361362EXAMPLES::363364sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])365sage: p.set_engine('internal') # to make doctests independent of TOPCOM366sage: p.triangulate()367(<1,3,4>, <2,3,4>)368sage: p.set_engine('TOPCOM') # optional - TOPCOM369sage: p.triangulate() # optional - TOPCOM370(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)371sage: p.set_engine('internal') # optional - TOPCOM372"""373if engine not in ['auto', 'TOPCOM', 'internal']:374raise ValueError, 'Unknown value for "engine": '+str(engine)375376have_TOPCOM = PointConfiguration._have_TOPCOM()377PointConfiguration._use_TOPCOM = \378(engine=='TOPCOM') or (engine=='auto' and have_TOPCOM)379380381def star_center(self):382r"""383Return the center used for star triangulations.384385.. seealso:: :meth:`restrict_to_star_triangulations`.386387OUTPUT:388389A :class:`~sage.geometry.triangulation.base.Point` if a390distinguished star central point has been fixed.391``ValueError`` exception is raised otherwise.392393EXAMPLES::394395sage: pc = PointConfiguration([(1,0),(-1,0),(0,1),(0,2)], star=(0,1)); pc396A point configuration in QQ^2 consisting of 4 points. The397triangulations of this point configuration are assumed to be398connected, not necessarily fine, not necessarily regular, and399star with center P(0, 1).400sage: pc.star_center()401P(0, 1)402403sage: pc_nostar = pc.restrict_to_star_triangulations(None)404sage: pc_nostar405A point configuration in QQ^2 consisting of 4 points. The406triangulations of this point configuration are assumed to be407connected, not necessarily fine, not necessarily regular.408sage: pc_nostar.star_center()409Traceback (most recent call last):410...411ValueError: The point configuration has no star center defined.412"""413if self._star is None:414raise ValueError, 'The point configuration has no star center defined.'415else:416return self[self._star]417418419def __reduce__(self):420r"""421Override __reduce__ to correctly pickle/unpickle.422423TESTS::424425sage: p = PointConfiguration([[0, 1], [0, 0], [1, 0], [1,1]])426sage: loads(p.dumps()) is p427True428429sage: p = PointConfiguration([[0, 1, 1], [0, 0, 1], [1, 0, 1], [1,1, 1]], projective=True)430sage: loads(p.dumps()) is p431True432"""433if self.is_affine():434points = tuple( p.affine() for p in self )435return (PointConfiguration, (points, False,436self._connected, self._fine, self._regular, self._star))437else:438points = tuple( p.projective() for p in self )439return (PointConfiguration, (points, True,440self._connected, self._fine, self._regular, self._star))441442443def an_element(self):444"""445Synonymous for :meth:`triangulate`.446447TESTS::448449sage: p = PointConfiguration([[0, 1], [0, 0], [1, 0], [1,1]])450sage: p.an_element()451(<0,1,3>, <1,2,3>)452"""453return self.triangulate()454455456def _element_constructor_(self, e):457"""458Construct a triangulation.459460TESTS::461462sage: p = PointConfiguration([[0, 1], [0, 0], [1, 0], [1,1]])463sage: p._element_constructor_([ (0,1,2), (2,3,0) ])464(<0,1,2>, <0,2,3>)465"""466return self.element_class(e, parent=self)467468469Element = Triangulation470471472def __iter__(self):473"""474Iterate through the points of the point configuration.475476OUTPUT:477478Returns projective coordinates of the points. See also the479``PointConfiguration.points()`` method, which returns affine480coordinates.481482EXAMPLES::483484sage: p = PointConfiguration([[1,1], [2,2], [3,3]]);485sage: list(p) # indirect doctest486[P(1, 1), P(2, 2), P(3, 3)]487sage: [ p[i] for i in range(0,p.n_points()) ]488[P(1, 1), P(2, 2), P(3, 3)]489sage: list(p.points())490[P(1, 1), P(2, 2), P(3, 3)]491sage: [ p.point(i) for i in range(0,p.n_points()) ]492[P(1, 1), P(2, 2), P(3, 3)]493"""494for p in self.points():495yield p496497498def _repr_(self):499r"""500Return a string representation.501502TESTS::503504sage: p = PointConfiguration([[1,1,1],[-1,1,1],[1,-1,1],[-1,-1,1],[1,1,-1],505... [-1,1,-1],[1,-1,-1],[-1,-1,-1],[0,0,0]])506sage: p._repr_()507'A point configuration in QQ^3 consisting of 9 points. The triangulations508of this point configuration are assumed to be connected, not necessarily509fine, not necessarily regular.'510511sage: PointConfiguration([[1, 1, 1], [-1, 1, 1], [1, -1, 1], [-1, -1, 1]], projective=True)512A point configuration in P(QQ^3) consisting of 4 points. The triangulations513of this point configuration are assumed to be connected, not necessarily514fine, not necessarily regular.515"""516s = 'A point configuration'517if self.is_affine():518s += ' in QQ^'+str(self.ambient_dim())519else:520s += ' in P(QQ^'+str(self.ambient_dim()+1)+')'521if len(self)==1:522s += ' consisting of '+str(len(self))+' point. '523else:524s += ' consisting of '+str(len(self))+' points. '525526s += 'The triangulations of this point configuration are assumed to be'527528if self._connected:529s += ' connected,'530else:531s += ' not necessarily connected,'532533if self._fine:534s += ' fine,'535else:536s += ' not necessarily fine,'537538if self._regular==True:539s += ' regular'540elif self._regular==False:541s += ' irregular'542else:543s += ' not necessarily regular'544545if self._star==None:546s += '.'547else:548s += ', and star with center '+str(self.star_center())+'.'549return s550551552def _TOPCOM_points(self):553r"""554Convert the list of input points to a string that can be fed555to TOPCOM.556557TESTS::558559sage: p = PointConfiguration([[1,1,1], [-1,1,1], [1,-1,1], [-1,-1,1], [1,1,-1]])560sage: p._TOPCOM_points()561'[[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]'562"""563s = '['564s += ','.join([565'[' + ','.join(map(str,p.reduced_projective())) + ']'566for p in self ])567s += ']'568return s569570571@classmethod572def _TOPCOM_exec(cls, executable, input_string, verbose=True):573r"""574Run TOPCOM.575576INPUT:577578- ``executable`` -- string. The name of the executable.579580- ``input_string`` -- string. Will be piped into the running581executable's stdin.582583- ``verbose`` -- boolean. Whether to print out the TOPCOM584interaction.585586TESTS::587588sage: p = PointConfiguration([[1,1,1], [-1,1,1], [1,-1,1], [-1,-1,1], [1,1,-1]])589sage: out = p._TOPCOM_exec('points2placingtriang', '[[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]', verbose=True)590sage: list(out) # optional - TOPCOM591#### TOPCOM input ####592# points2placingtriang593# [[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]594#### TOPCOM output ####595# {{0,1,2,4},{1,2,3,4}}596#######################597['{{0,1,2,4},{1,2,3,4}}']598"""599timeout = 600600proc = pexpect.spawn(executable, timeout=timeout)601proc.expect('Evaluating Commandline Options \.\.\.')602proc.expect('\.\.\. done\.')603proc.setecho(0)604assert proc.readline().strip() == ''605606if verbose:607print "#### TOPCOM input ####"608print "# " + executable609print "# " + input_string610sys.stdout.flush()611612proc.send(input_string)613proc.send('X\nX\n')614615if verbose:616print "#### TOPCOM output ####"617sys.stdout.flush()618619while True:620try:621line = proc.readline().strip()622except pexpect.TIMEOUT:623if verbose:624print '# Still runnnig '+str(executable)625continue626if len(line)==0: # EOF627break;628if verbose:629print "# " + line630sys.stdout.flush()631632try:633yield line.strip()634except GeneratorExit:635proc.close(force=True)636raise StopIteration637638if verbose:639print "#######################"640sys.stdout.flush()641642643def _TOPCOM_communicate(self, executable, verbose=True):644r"""645Execute TOPCOM and parse the output into a646:class:`~sage.geometry.triangulation.element.Triangulation`.647648TESTS::649650sage: p = PointConfiguration([[1,1,1], [-1,1,1], [1,-1,1], [-1,-1,1], [1,1,-1]])651sage: out = p._TOPCOM_communicate('points2placingtriang', verbose=True)652sage: list(out) # optional - TOPCOM653#### TOPCOM input ####654# points2placingtriang655# [[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]656#### TOPCOM output ####657# {{0,1,2,4},{1,2,3,4}}658#######################659[(<0,1,2,4>, <1,2,3,4>)]660"""661for line in self._TOPCOM_exec(executable,662self._TOPCOM_points(), verbose):663triangulation = line[ line.find('{{')+2 : line.rfind('}}') ]664triangulation = triangulation.split('},{')665triangulation = [ [ QQ(t) for t in triangle.split(',') ]666for triangle in triangulation ]667668if self._star!=None:669o = self._star670if not all( t.count(o)>0 for t in triangulation):671continue672673yield self(triangulation)674675676def _TOPCOM_triangulations(self, verbose=True):677r"""678Returns all triangulations satisfying the restrictions imposed.679680EXAMPLES::681682sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])683sage: iter = p._TOPCOM_triangulations(verbose=True)684sage: iter.next() # optional - TOPCOM685#### TOPCOM input ####686# points2triangs687# [[0,0,1],[0,1,1],[1,0,1],[1,1,1],[-1,-1,1]]688#### TOPCOM output ####689# T[1]:=[5,3:{{0,1,2},{1,2,3},{0,2,4},{0,1,4}}];690(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)691"""692command = 'points2'693694if not self._connected:695command += 'all'696697if self._fine:698command += 'fine'699700command += 'triangs'701702if self._regular==True:703command += ' --regular'704if self._regular==False:705command += ' --nonregular'706707for t in self._TOPCOM_communicate(command, verbose):708yield t709710711def _TOPCOM_triangulate(self, verbose=True):712r"""713Return one (in no particular order) triangulation subject714to all restrictions imposed previously.715716INPUT:717718- ``verbose`` -- boolean. Whether to print out the TOPCOM719interaction.720721OUTPUT:722723A :class:`~sage.geometry.triangulation.element.Triangulation`724satisfying all restrictions imposed. Raises a ``ValueError``725if no such triangulation exists.726727EXAMPLES::728729sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])730sage: p.set_engine('TOPCOM') # optional - TOPCOM731sage: p._TOPCOM_triangulate(verbose=False) # optional - TOPCOM732(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)733sage: list( p.triangulate() ) # optional - TOPCOM734[(0, 1, 2), (0, 1, 4), (0, 2, 4), (1, 2, 3)]735sage: p.set_engine('internal') # optional - TOPCOM736"""737assert self._regular!=False, \738'When asked for a single triangulation TOPCOM ' + \739'always returns a regular triangulation.'740741command = "points2"742if self._fine:743command += "finetriang"744else:745command += "placingtriang"746747return self._TOPCOM_communicate(command, verbose).next()748749750def restrict_to_regular_triangulations(self, regular=True):751"""752Restrict to regular triangulations.753754NOTE:755756Regularity testing requires the optional TOPCOM package.757758INPUT:759760- ``regular`` -- ``True``, ``False``, or ``None``. Whether to761restrict to regular triangulations, irregular762triangulations, or lift any restrictions on regularity.763764OUTPUT:765766A new :class:`PointConfiguration` with the same points, but767whose triangulations will all be regular as specified. See768:class:`PointConfiguration` for details.769770EXAMPLES::771772sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])773sage: p774A point configuration in QQ^2 consisting of 5 points. The775triangulations of this point configuration are assumed to776be connected, not necessarily fine, not necessarily regular.777sage: len(p.triangulations_list())7784779sage: p_regular = p.restrict_to_regular_triangulations() # optional - TOPCOM780sage: len(p_regular.triangulations_list()) # optional - TOPCOM7814782sage: p == p_regular.restrict_to_regular_triangulations(regular=None) # optional - TOPCOM783True784"""785return PointConfiguration(self,786connected=self._connected,787fine=self._fine,788regular=regular,789star=self._star)790791792def restrict_to_connected_triangulations(self, connected=True):793"""794Restrict to connected triangulations.795796NOTE:797798Finding non-connected triangulations requires the optional799TOPCOM package.800801INPUT:802803- ``connected`` -- boolean. Whether to restrict to804triangulations that are connected by bistellar flips to the805regular triangulations.806807OUTPUT:808809A new :class:`PointConfiguration` with the same points, but810whose triangulations will all be in the connected811component. See :class:`PointConfiguration` for details.812813EXAMPLES::814815sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])816sage: p817A point configuration in QQ^2 consisting of 5 points. The818triangulations of this point configuration are assumed to819be connected, not necessarily fine, not necessarily regular.820sage: len(p.triangulations_list())8214822sage: p_all = p.restrict_to_connected_triangulations(connected=False) # optional - TOPCOM823sage: len(p_all.triangulations_list()) # optional - TOPCOM8244825sage: p == p_all.restrict_to_connected_triangulations(connected=True) # optional - TOPCOM826True827"""828return PointConfiguration(self,829connected=connected,830fine=self._fine,831regular=self._regular,832star=self._star)833834835def restrict_to_fine_triangulations(self, fine=True):836"""837Restrict to fine triangulations.838839INPUT:840841- ``fine`` -- boolean. Whether to restrict to fine triangulations.842843OUTPUT:844845A new :class:`PointConfiguration` with the same points, but846whose triangulations will all be fine. See847:class:`PointConfiguration` for details.848849EXAMPLES::850851sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])852sage: p853A point configuration in QQ^2 consisting of 5 points. The854triangulations of this point configuration are assumed to855be connected, not necessarily fine, not necessarily regular.856sage: len(p.triangulations_list())8574858sage: p_fine = p.restrict_to_fine_triangulations()859sage: len(p.triangulations_list())8604861sage: p == p_fine.restrict_to_fine_triangulations(fine=False)862True863"""864return PointConfiguration(self,865connected=self._connected,866fine=fine,867regular=self._regular,868star=self._star)869870871def restrict_to_star_triangulations(self, star):872"""873Restrict to star triangulations with the given point as the874center.875876INPUT:877878- ``origin`` -- ``None`` or an integer or the coordinates of a879point. An integer denotes the index of the central point. If880``None`` is passed, any restriction on the starshape will be881removed.882883OUTPUT:884885A new :class:`PointConfiguration` with the same points, but886whose triangulations will all be star. See887:class:`PointConfiguration` for details.888889EXAMPLES::890891sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])892sage: len(list( p.triangulations() ))8934894sage: p_star = p.restrict_to_star_triangulations(0)895sage: p_star is p.restrict_to_star_triangulations((0,0))896True897sage: p_star.triangulations_list()898[(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)]899sage: p_newstar = p_star.restrict_to_star_triangulations(1) # pick different origin900sage: p_newstar.triangulations_list()901[(<1,2,3>, <1,2,4>)]902sage: p == p_star.restrict_to_star_triangulations(star=None)903True904"""905return PointConfiguration(self,906connected=self._connected,907fine=self._fine,908regular=self._regular,909star=star)910911912def triangulations(self, verbose=False):913r"""914Returns all triangulations.915916- ``verbose`` -- boolean (default: ``False``). Whether to917print out the TOPCOM interaction, if any.918919OUTPUT:920921A generator for the triangulations satisfying all the922restrictions imposed. Each triangulation is returned as a923:class:`~sage.geometry.triangulation.element.Triangulation` object.924925EXAMPLES::926927sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])928sage: iter = p.triangulations()929sage: iter.next()930(<1,3,4>, <2,3,4>)931sage: iter.next()932(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)933sage: iter.next()934(<1,2,3>, <1,2,4>)935sage: iter.next()936(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)937sage: p.triangulations_list()938[(<1,3,4>, <2,3,4>),939(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),940(<1,2,3>, <1,2,4>),941(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]942sage: p_fine = p.restrict_to_fine_triangulations()943sage: p_fine.triangulations_list()944[(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),945(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]946947Note that we explicitly asked the internal algorithm to948compute the triangulations. Using TOPCOM, we obtain the same949triangulations but in a different order::950951sage: p.set_engine('TOPCOM') # optional - TOPCOM952sage: iter = p.triangulations() # optional - TOPCOM953sage: iter.next() # optional - TOPCOM954(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)955sage: iter.next() # optional - TOPCOM956(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)957sage: iter.next() # optional - TOPCOM958(<1,2,3>, <1,2,4>)959sage: iter.next() # optional - TOPCOM960(<1,3,4>, <2,3,4>)961sage: p.triangulations_list() # optional - TOPCOM962[(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>),963(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),964(<1,2,3>, <1,2,4>),965(<1,3,4>, <2,3,4>)]966sage: p_fine = p.restrict_to_fine_triangulations() # optional - TOPCOM967sage: p_fine.set_engine('TOPCOM') # optional - TOPCOM968sage: p_fine.triangulations_list() # optional - TOPCOM969[(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>),970(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)]971sage: p.set_engine('internal') # optional - TOPCOM972"""973if self._use_TOPCOM:974for triangulation in self._TOPCOM_triangulations(verbose):975yield triangulation976else:977if (self._connected!=True):978raise ValueError, 'Need TOPCOM to find disconnected triangulations.'979if (self._regular!=None):980raise ValueError, 'Need TOPCOM to test for regularity.'981ci = ConnectedTriangulationsIterator(self, star=self._star, fine=self._fine)982for encoded_triangulation in ci:983yield self(encoded_triangulation)984985986def triangulations_list(self, verbose=False):987r"""988Return all triangulations.989990INPUT:991992- ``verbose`` -- boolean. Whether to print out the TOPCOM993interaction, if any.994995OUTPUT:996997A list of triangulations (see998:class:`~sage.geometry.triangulation.element.Triangulation`)999satisfying all restrictions imposed previously.10001001EXAMPLES::10021003sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1]])1004sage: p.triangulations_list()1005[(<0,1,2>, <1,2,3>), (<0,1,3>, <0,2,3>)]1006sage: map(list, p.triangulations_list() )1007[[(0, 1, 2), (1, 2, 3)], [(0, 1, 3), (0, 2, 3)]]1008sage: p.set_engine('TOPCOM') # optional - TOPCOM1009sage: p.triangulations_list() # optional - TOPCOM1010[(<0,1,2>, <1,2,3>), (<0,1,3>, <0,2,3>)]1011sage: p.set_engine('internal') # optional - TOPCOM1012"""1013return list(self.triangulations(verbose))101410151016def triangulate(self, verbose=False):1017r"""1018Return one (in no particular order) triangulation.10191020INPUT:10211022- ``verbose`` -- boolean. Whether to print out the TOPCOM1023interaction, if any.10241025OUTPUT:10261027A :class:`~sage.geometry.triangulation.element.Triangulation`1028satisfying all restrictions imposed. Raises a ``ValueError``1029if no such triangulation exists.10301031EXAMPLES::10321033sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])1034sage: p.triangulate()1035(<1,3,4>, <2,3,4>)1036sage: list( p.triangulate() )1037[(1, 3, 4), (2, 3, 4)]10381039Using TOPCOM yields a different, but equally good, triangulation::10401041sage: p.set_engine('TOPCOM') # optional - TOPCOM1042sage: p.triangulate() # optional - TOPCOM1043(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)1044sage: list( p.triangulate() ) # optional - TOPCOM1045[(0, 1, 2), (0, 1, 4), (0, 2, 4), (1, 2, 3)]1046sage: p.set_engine('internal') # optional - TOPCOM1047"""1048if self._use_TOPCOM and self._regular!=False:1049try:1050return self._TOPCOM_triangulate(verbose)1051except StopIteration:1052# either topcom did not return a triangulation or we filtered it out1053pass10541055if self._connected and not self._fine and self._regular!=False and self._star==None:1056return self.placing_triangulation()10571058try:1059return self.triangulations(verbose).next()1060except StopIteration:1061# there is no triangulation1062pass1063raise ValueError, 'No triangulation with the required properties.'106410651066def convex_hull(self):1067"""1068Return the convex hull of the point configuration.10691070EXAMPLES::10711072sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])1073sage: p.convex_hull()1074A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices1075"""1076try:1077return self._polyhedron1078except AttributeError:1079pass10801081from sage.geometry.polyhedron.constructor import Polyhedron1082pts = [ p.reduced_affine() for p in self.points() ];1083self._polyhedron = Polyhedron(vertices=pts);1084return self._polyhedron108510861087def restricted_automorphism_group(self):1088r"""1089Return the restricted automorphism group.10901091First, let the linear automorphism group be the subgroup of1092the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d`1093preserving the `d`-dimensional point configuration. The1094Euclidean group acts in the usual way `\vec{x}\mapsto1095A\vec{x}+b` on the ambient space.10961097The restricted automorphism group is the subgroup of the1098linear automorphism group generated by permutations of1099points. See [BSS]_ for more details and a description of the1100algorithm.11011102OUTPUT:11031104A1105:class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>`1106that is isomorphic to the restricted automorphism group is1107returned.11081109Note that in Sage, permutation groups always act on positive1110integers while lists etc. are indexed by nonnegative1111integers. The indexing of the permutation group is chosen to1112be shifted by ``+1``. That is, the transposition ``(i,j)`` in1113the permutation group corresponds to exchange of ``self[i-1]``1114and ``self[j-1]``.11151116EXAMPLES::11171118sage: pyramid = PointConfiguration([[1,0,0],[0,1,1],[0,1,-1],[0,-1,-1],[0,-1,1]])1119sage: pyramid.restricted_automorphism_group()1120Permutation Group with generators [(3,5), (2,3)(4,5), (2,4)]1121sage: DihedralGroup(4).is_isomorphic(_)1122True11231124The square with an off-center point in the middle. Note thath1125the middle point breaks the restricted automorphism group1126`D_4` of the convex hull::11271128sage: square = PointConfiguration([(3/4,3/4),(1,1),(1,-1),(-1,-1),(-1,1)])1129sage: square.restricted_automorphism_group()1130Permutation Group with generators [(3,5)]1131sage: DihedralGroup(1).is_isomorphic(_)1132True1133"""1134if '_restricted_automorphism_group' in self.__dict__:1135return self._restricted_automorphism_group11361137v_list = [ vector(p.projective()) for p in self ]1138Qinv = sum( v.column() * v.row() for v in v_list ).inverse()11391140# construct the graph1141from sage.graphs.graph import Graph11421143# Was set to sparse = False, but there is a problem with Graph1144# backends. It should probably be set back to sparse = False as soon as1145# the backends are fixed.1146G = Graph(sparse=True)1147for i in range(0,len(v_list)):1148for j in range(i+1,len(v_list)):1149v_i = v_list[i]1150v_j = v_list[j]1151G.add_edge(i+1,j+1, v_i * Qinv * v_j)11521153group = G.automorphism_group(edge_labels=True)1154self._restricted_automorphism_group = group1155return group115611571158def face_codimension(self, point):1159r"""1160Return the smallest `d\in\mathbb{Z}` such that ``point`` is1161contained in the interior of a codimension-`d` face.11621163EXAMPLES::11641165sage: triangle = PointConfiguration([[0,0], [1,-1], [1,0], [1,1]]);1166sage: triangle.point(2)1167P(1, 0)1168sage: triangle.face_codimension(2)116911170sage: triangle.face_codimension( [1,0] )1171111721173This also works for degenerate cases like the tip of the1174pyramid over a square (which saturates four inequalities)::11751176sage: pyramid = PointConfiguration([[1,0,0],[0,1,1],[0,1,-1],[0,-1,-1],[0,-1,1]])1177sage: pyramid.face_codimension(0)117831179"""1180try:1181p = vector(self.point(point).reduced_affine())1182except TypeError:1183p = vector(point);11841185inequalities = []1186for ieq in self.convex_hull().inequality_generator():1187if (ieq.A()*p + ieq.b() == 0):1188inequalities += [ ieq.vector() ];1189return matrix(inequalities).rank();119011911192def face_interior(self, dim=None, codim=None):1193"""1194Return points by the codimension of the containing face in the convex hull.11951196EXAMPLES::11971198sage: triangle = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);1199sage: triangle.face_interior()1200((1,), (3,), (0, 2, 4))1201sage: triangle.face_interior(dim=0) # the vertices of the convex hull1202(0, 2, 4)1203sage: triangle.face_interior(codim=1) # interior of facets1204(3,)1205"""1206assert not (dim!=None and codim!=None), "You cannot specify both dim and codim."12071208if (dim!=None):1209return self.face_interior()[self.convex_hull().dim()-dim]1210if (codim!=None):1211return self.face_interior()[codim]12121213try:1214return self._face_interior1215except AttributeError:1216pass12171218d = [ self.face_codimension(i) for i in range(0,self.n_points()) ]12191220return tuple( tuple(filter( lambda i: d[i]==codim, range(0,self.n_points())) )1221for codim in range(0,self.dim()+1) )122212231224def exclude_points(self, point_idx_list):1225"""1226Return a new point configuration with the given points1227removed.12281229INPUT:12301231- ``point_idx_list`` -- a list of integers. The indices of1232points to exclude.12331234OUTPUT:12351236A new :class:`PointConfiguration` with the given points1237removed.12381239EXAMPLES::12401241sage: p = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);1242sage: list(p)1243[P(-1, 0), P(0, 0), P(1, -1), P(1, 0), P(1, 1)]1244sage: q = p.exclude_points([3])1245sage: list(q)1246[P(-1, 0), P(0, 0), P(1, -1), P(1, 1)]1247sage: p.exclude_points( p.face_interior(codim=1) ).points()1248(P(-1, 0), P(0, 0), P(1, -1), P(1, 1))1249"""1250points = [ self.point(i) for i in range(0,self.n_points())1251if not i in point_idx_list ]1252return PointConfiguration(points,1253projective=False,1254connected=self._connected,1255fine=self._fine,1256regular=self._regular,1257star=self._star)125812591260def volume(self, simplex=None):1261"""1262Find n! times the n-volume of a simplex of dimension n.12631264INPUT:12651266- ``simplex`` (optional argument) -- a simplex from a1267triangulation T specified as a list of point indices.12681269OUTPUT:12701271* If a simplex was passed as an argument: n!*(volume of ``simplex``).12721273* Without argument: n!*(the total volume of the convex hull).12741275EXAMPLES:12761277The volume of the standard simplex should always be 1::12781279sage: p = PointConfiguration([[0,0],[1,0],[0,1],[1,1]])1280sage: p.volume( [0,1,2] )128111282sage: simplex = p.triangulate()[0] # first simplex of triangulation1283sage: p.volume(simplex)1284112851286The square can be triangulated into two minimal simplices, so1287in the "integral" normalization its volume equals two::12881289sage: p.volume()1290212911292.. note::12931294We return n!*(metric volume of the simplex) to ensure that1295the volume is an integer. Essentially, this normalizes1296things so that the volume of the standard n-simplex is 1.1297See [GKZ]_ page 182.1298"""1299if (simplex==None):1300return sum([ self.volume(s) for s in self.triangulate() ])13011302#Form a matrix whose columns are the points of simplex1303#with the first point of simplex shifted to the origin.1304v = [ self.point(i).reduced_affine_vector() for i in simplex ]1305m = matrix([ v_i - v[0] for v_i in v[1:] ])1306return abs(m.det())130713081309def secondary_polytope(self):1310r"""1311Calculate the secondary polytope of the point configuration.13121313For a definition of the secondary polytope, see [GKZ]_ page 2201314Definition 1.6.13151316Note that if you restricted the admissible triangulations of1317the point configuration then the output will be the1318corresponding face of the whole secondary polytope.13191320OUTPUT:13211322The secondary polytope of the point configuration as an1323instance of1324:class:`~sage.geometry.polyhedron.base.Polyhedron_base`.13251326EXAMPLES::13271328sage: p = PointConfiguration([[0,0],[1,0],[2,1],[1,2],[0,1]])1329sage: poly = p.secondary_polytope()1330sage: poly.vertices_matrix()1331[1 1 3 3 5]1332[3 5 1 4 1]1333[4 2 5 2 4]1334[2 4 2 5 4]1335[5 3 4 1 1]1336sage: poly.Vrepresentation()1337(A vertex at (1, 3, 4, 2, 5),1338A vertex at (1, 5, 2, 4, 3),1339A vertex at (3, 1, 5, 2, 4),1340A vertex at (3, 4, 2, 5, 1),1341A vertex at (5, 1, 4, 4, 1))1342sage: poly.Hrepresentation()1343(An equation (0, 0, 1, 2, 1) x - 13 == 0,1344An equation (1, 0, 0, 2, 2) x - 15 == 0,1345An equation (0, 1, 0, -3, -2) x + 13 == 0,1346An inequality (0, 0, 0, -1, -1) x + 7 >= 0,1347An inequality (0, 0, 0, 1, 0) x - 2 >= 0,1348An inequality (0, 0, 0, -2, -1) x + 11 >= 0,1349An inequality (0, 0, 0, 0, 1) x - 1 >= 0,1350An inequality (0, 0, 0, 3, 2) x - 14 >= 0)1351"""1352from sage.geometry.polyhedron.constructor import Polyhedron1353#TODO: once restriction to regular triangulations is fixed,1354#change the next line to only take the regular triangulations,1355#since they are the vertices of the secondary polytope anyway.1356l = self.triangulations_list()1357return Polyhedron(vertices = [x.gkz_phi() for x in l])135813591360def circuits_support(self):1361r"""1362A generator for the supports of the circuits of the point configuration.13631364See :meth:`circuits` for details.13651366OUTPUT:13671368A generator for the supports `C_-\cup C_+` (returned as a1369Python tuple) for all circuits of the point configuration.13701371EXAMPLES::13721373sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])1374sage: list( p.circuits_support() )1375[(0, 3, 4), (0, 1, 2), (1, 2, 3, 4)]1376"""1377n = len(self)1378U = [ self[i].reduced_projective() for i in range(0,n) ]13791380# the index set of U1381I = set(range(0,n))1382# The (indices of) known independent elements of U1383independent_k = [ (i,) for i in range(0,n) ]1384supports_k = []13851386supports = () # supports of circuits1387for k in range(2, self.dim()+3):13881389# possibly linear dependent subsets1390supports_knext = set()1391possible_dependency = set()1392for indep in independent_k:1393indep_plus_one = [ tuple(sorted(indep+(i,))) for i in (I-set(indep)) ]1394possible_dependency.update(indep_plus_one)1395for supp in supports_k:1396supp_plus_one = [ tuple(sorted(supp+(i,))) for i in (I-set(supp)) ]1397possible_dependency.difference_update(supp_plus_one)1398supports_knext.update(supp_plus_one)13991400# remember supports and independents for the next k-iteration1401supports_k = list(supports_knext)1402independent_k = []1403for idx in possible_dependency:1404rk = matrix([ U[i] for i in idx ]).rank()1405if rk==k:1406independent_k.append(idx)1407else:1408supports_k.append(idx)1409yield idx1410assert independent_k==[] # there are no independent (self.dim()+3)-tuples141114121413def circuits(self):1414r"""1415Return the circuits of the point configuration.14161417Roughly, a circuit is a minimal linearly dependent subset of1418the points. That is, a circuit is a partition14191420.. MATH::14211422\{ 0, 1, \dots, n-1 \} = C_+ \cup C_0 \cup C_-14231424such that there is an (unique up to an overall normalization) affine1425relation14261427.. MATH::14281429\sum_{i\in C_+} \alpha_i \vec{p}_i =1430\sum_{j\in C_-} \alpha_j \vec{p}_j14311432with all positive (or all negative) coefficients, where1433`\vec{p}_i=(p_1,\dots,p_k,1)` are the projective coordinates1434of the `i`-th point.14351436OUTPUT:14371438The list of (unsigned) circuits as triples `(C_+, C_0,1439C_-)`. The swapped circuit `(C_-, C_0, C_+)` is not returned1440separately.14411442EXAMPLES::14431444sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])1445sage: p.circuits()1446(((0,), (1, 2), (3, 4)), ((0,), (3, 4), (1, 2)), ((1, 2), (0,), (3, 4)))144714481449TESTS::14501451sage: U=matrix([1452... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],1453... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],1454... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],1455... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],1456... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]1457... ])1458sage: p = PointConfiguration(U.columns())1459sage: len( p.circuits() ) # long time14602181461"""1462try:1463return self._circuits1464except AttributeError:1465pass14661467n = len(self)1468U = [ self[i].reduced_projective() for i in range(0,n) ]14691470Circuits = ()1471for support in self.circuits_support():1472m = matrix([ U[i] for i in support ]).transpose()1473ker = m.right_kernel().basis()[0]1474assert len(ker)==len(support)1475Cplus = [ support[i] for i in range(0,len(support)) if ker[i]>0 ]1476Cminus = [ support[i] for i in range(0,len(support)) if ker[i]<0 ]1477Czero = set( range(0,n) ).difference(support)1478Circuits += ( (tuple(Cplus), tuple(Czero), tuple(Cminus)), )1479self._circuits = Circuits1480return Circuits148114821483def positive_circuits(self, *negative):1484r"""1485Returns the positive part of circuits with fixed negative part.14861487A circuit is a pair `(C_+, C_-)`, each consisting of a subset1488(actually, an ordered tuple) of point indices.14891490INPUT:14911492- ``*negative`` -- integer. The indices of points.14931494OUTPUT:14951496A tuple of all circuits with `C_-` = ``negative``.14971498EXAMPLE::14991500sage: p = PointConfiguration([(1,0,0),(0,1,0),(0,0,1),(-2,0,-1),(-2,-1,0),(-3,-1,-1),(1,1,1),(-1,0,0),(0,0,0)])1501sage: p.positive_circuits(8)1502((0, 7), (0, 1, 4), (0, 2, 3), (0, 5, 6), (0, 1, 2, 5), (0, 3, 4, 6))1503sage: p.positive_circuits(0,5,6)1504((8,),)1505"""1506pos = ()1507negative = tuple(sorted(negative))1508for circuit in self.circuits():1509Cpos = circuit[0]1510Cneg = circuit[2]1511if Cpos == negative:1512pos += ( Cneg, )1513elif Cneg == negative:1514pos += ( Cpos, )1515return pos151615171518def bistellar_flips(self):1519r"""1520Return the bistellar flips.15211522OUTPUT:15231524The bistellar flips as a tuple. Each flip is a pair1525`(T_+,T_-)` where `T_+` and `T_-` are partial triangulations1526of the point configuration.15271528EXAMPLES::15291530sage: pc = PointConfiguration([(0,0),(1,0),(0,1),(1,1)])1531sage: pc.bistellar_flips()1532(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)1533sage: Tpos, Tneg = pc.bistellar_flips()[0]1534sage: Tpos.plot(axes=False)1535sage: Tneg.plot(axes=False)15361537The 3d analog::15381539sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(-1,0,0),(1,1,1)])1540sage: pc.bistellar_flips()1541(((<0,1,2,3>, <0,1,2,4>), (<0,1,3,4>, <0,2,3,4>, <1,2,3,4>)),)15421543A 2d flip on the base of the pyramid over a square::15441545sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(0,2,2),(1,1,1)])1546sage: pc.bistellar_flips()1547(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)1548sage: Tpos, Tneg = pc.bistellar_flips()[0]1549sage: Tpos.plot(axes=False)1550"""1551flips = []1552for C in self.circuits():1553Cpos = list(C[0])1554Cneg = list(C[2])1555support = sorted(Cpos+Cneg)1556Tpos = [ Cpos+Cneg[0:i]+Cneg[i+1:len(Cneg)] for i in range(0,len(Cneg)) ]1557Tneg = [ Cneg+Cpos[0:i]+Cpos[i+1:len(Cpos)] for i in range(0,len(Cpos)) ]1558flips.append( (self.element_class(Tpos, parent=self, check=False),1559self.element_class(Tneg, parent=self, check=False)) )1560return tuple(flips)156115621563def lexicographic_triangulation(self):1564r"""1565Return the lexicographic triangulation.15661567The algorithm was taken from [PUNTOS]_.15681569EXAMPLES::15701571sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])1572sage: p.lexicographic_triangulation()1573(<1,3,4>, <2,3,4>)15741575TESTS::15761577sage: U=matrix([1578... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],1579... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],1580... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],1581... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],1582... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]1583... ])1584sage: pc = PointConfiguration(U.columns())1585sage: pc.lexicographic_triangulation()1586(<1,3,4,7,10,13>, <1,3,4,8,10,13>, <1,3,6,7,10,13>, <1,3,6,8,10,13>,1587<1,4,6,7,10,13>, <1,4,6,8,10,13>, <2,3,4,6,7,12>, <2,3,4,7,12,13>,1588<2,3,6,7,12,13>, <2,4,6,7,12,13>, <3,4,5,6,9,12>, <3,4,5,8,9,12>,1589<3,4,6,7,11,12>, <3,4,6,9,11,12>, <3,4,7,10,11,13>, <3,4,7,11,12,13>,1590<3,4,8,9,10,12>, <3,4,8,10,12,13>, <3,4,9,10,11,12>, <3,4,10,11,12,13>,1591<3,5,6,8,9,12>, <3,6,7,10,11,13>, <3,6,7,11,12,13>, <3,6,8,9,10,12>,1592<3,6,8,10,12,13>, <3,6,9,10,11,12>, <3,6,10,11,12,13>, <4,5,6,8,9,12>,1593<4,6,7,10,11,13>, <4,6,7,11,12,13>, <4,6,8,9,10,12>, <4,6,8,10,12,13>,1594<4,6,9,10,11,12>, <4,6,10,11,12,13>)1595sage: len(_)1596341597"""1598lex_supp = set()1599for circuit in self.circuits():1600Cplus = circuit[0]1601Cminus = circuit[2]1602s0 = min(Cplus + Cminus)1603if s0 in Cplus:1604lex_supp.add(Cplus)1605else:1606lex_supp.add(Cminus)16071608lex_supp = sorted(lex_supp, key=lambda x:-len(x))1609basepts = copy(lex_supp)1610for i in range(0,len(lex_supp)-1):1611for j in range(i+1,len(lex_supp)):1612if set(lex_supp[j]).issubset(set(lex_supp[i])):1613try:1614basepts.remove(lex_supp[i])1615except ValueError:1616pass16171618basepts = [ (len(b),)+b for b in basepts ] # decorate1619basepts = sorted(basepts) # sort1620basepts = [ b[1:] for b in basepts ] # undecorate16211622def make_cotriang(basepts):1623if len(basepts)==0:1624return [frozenset()]1625triangulation = set()1626for tail in make_cotriang(basepts[1:]):1627for head in basepts[0]:1628triangulation.update([ frozenset([head]).union(tail) ])16291630nonminimal = set()1631for rel in Combinations(triangulation,2):1632if rel[0].issubset(rel[1]): nonminimal.update([rel[1]])1633if rel[1].issubset(rel[0]): nonminimal.update([rel[0]])1634triangulation.difference_update(nonminimal)16351636triangulation = [ [len(t)]+sorted(t) for t in triangulation ] # decorate1637triangulation = sorted(triangulation) # sort1638triangulation = [ frozenset(t[1:]) for t in triangulation ] # undecorate16391640return triangulation16411642triangulation = make_cotriang(basepts)1643I = frozenset(range(0,self.n_points()))1644triangulation = [ tuple(I.difference(t)) for t in triangulation ]16451646return self(triangulation)164716481649@cached_method1650def distance_affine(self, x, y):1651r"""1652Returns the distance between two points.16531654The distance function used in this method is `d_{aff}(x,y)^2`,1655the square of the usual affine distance function16561657.. math::16581659d_{aff}(x,y) = |x-y|16601661INPUT:16621663- ``x``, ``y`` -- two points of the point configuration.16641665OUTPUT:16661667The metric distance-square `d_{aff}(x,y)^2`. Note that this1668distance lies in the same field as the entries of ``x``,1669``y``. That is, the distance of rational points will be1670rational and so on.16711672EXAMPLES::16731674sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1675sage: [ pc.distance_affine(pc.point(0), p) for p in pc.points() ]1676[0, 1, 5, 5, 1]1677"""1678self._assert_is_affine()1679d = 01680for xi, yi in zip(x.projective(), y.projective()):1681d += (xi-yi)**21682return d168316841685@cached_method1686def distance_FS(self, x, y):1687r"""1688Returns the distance between two points.16891690The distance function used in this method is `1-\cos1691d_{FS}(x,y)^2`, where `d_{FS}` is the Fubini-Study distance of1692projective points. Recall the Fubini-Studi distance function16931694.. math::16951696d_{FS}(x,y) = \arccos \sqrt{ \frac{(x\cdot y)^2}{|x|^2 |y|^2} }16971698INPUT:16991700- ``x``, ``y`` -- two points of the point configuration.17011702OUTPUT:17031704The distance `1-\cos d_{FS}(x,y)^2`. Note that this distance1705lies in the same field as the entries of ``x``, ``y``. That1706is, the distance of rational points will be rational and so1707on.17081709EXAMPLES::17101711sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1712sage: [ pc.distance_FS(pc.point(0), p) for p in pc.points() ]1713[0, 1/2, 5/6, 5/6, 1/2]1714"""1715x2 = y2 = xy = 01716for xi, yi in zip(x.projective(), y.projective()):1717x2 += xi*xi1718y2 += yi*yi1719xy += xi*yi1720return 1-xy*xy/(x2*y2)172117221723@cached_method1724def distance(self, x, y):1725"""1726Returns the distance between two points.17271728INPUT:17291730- ``x``, ``y`` -- two points of the point configuration.17311732OUTPUT:17331734The distance between ``x`` and ``y``, measured either with1735:meth:`distance_affine` or :meth:`distance_FS` depending on1736whether the point configuration is defined by affine or1737projective points. These are related, but not equal to the1738usual flat and Fubini-Study distance.17391740EXAMPLES::17411742sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1743sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]1744[0, 1, 5, 5, 1]17451746sage: pc = PointConfiguration([(0,0,1),(1,0,1),(2,1,1),(1,2,1),(0,1,1)], projective=True)1747sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]1748[0, 1/2, 5/6, 5/6, 1/2]1749"""1750if self.is_affine():1751return self.distance_affine(x,y)1752else:1753return self.distance_FS(x,y)175417551756def farthest_point(self, points, among=None):1757"""1758Return the point with the most distance from ``points``.17591760INPUT:17611762- ``points`` -- a list of points.17631764- ``among`` -- a list of points or ``None`` (default). The set1765of points from which to pick the farthest one. By default,1766all points of the configuration are considered.17671768OUTPUT:17691770A :class:`~sage.geometry.triangulation.base.Point` with1771largest minimal distance from all given ``points``.17721773EXAMPLES::17741775sage: pc = PointConfiguration([(0,0),(1,0),(1,1),(0,1)])1776sage: pc.farthest_point([ pc.point(0) ])1777P(1, 1)1778"""1779if len(points)==0:1780return self.point(0)1781if among is None:1782among = self.points()1783p_max = None1784for p in among:1785if p in points:1786continue1787if p_max is None:1788p_max = p1789d_max = min(self.distance(p,q) for q in points)1790continue1791d = min(self.distance(p,q) for q in points)1792if d>d_max:1793p_max = p1794return p_max179517961797def contained_simplex(self, large=True, initial_point=None):1798"""1799Return a simplex contained in the point configuration.18001801INPUT:18021803- ``large`` -- boolean. Whether to attempt to return a large1804simplex.18051806- ``initial_point`` -- a1807:class:`~sage.geometry.triangulation.base.Point` or ``None``1808(default). A specific point to start with when picking the1809simplex vertices.18101811OUTPUT:18121813A tuple of points that span a simplex of dimension1814:meth:`dim`. If ``large==True``, the simplex is constructed by1815sucessively picking the farthest point. This will ensure that1816the simplex is not unneccessarily small, but will in general1817not return a maximal simplex.18181819EXAMPLES::18201821sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])1822sage: pc.contained_simplex()1823(P(0, 1), P(2, 1), P(1, 0))1824sage: pc.contained_simplex(large=False)1825(P(0, 1), P(1, 1), P(1, 0))1826sage: pc.contained_simplex(initial_point=pc.point(0))1827(P(0, 0), P(1, 1), P(1, 0))18281829sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])1830sage: pc.contained_simplex()1831(P(-1, -1), P(1, 1), P(0, 1))18321833TESTS::18341835sage: pc = PointConfiguration([[0,0],[0,1],[1,0]])1836sage: pc.contained_simplex()1837(P(1, 0), P(0, 1), P(0, 0))1838sage: pc = PointConfiguration([[0,0],[0,1]])1839sage: pc.contained_simplex()1840(P(0, 1), P(0, 0))1841sage: pc = PointConfiguration([[0,0]])1842sage: pc.contained_simplex()1843(P(0, 0),)1844sage: pc = PointConfiguration([])1845sage: pc.contained_simplex()1846()1847"""1848self._assert_is_affine()1849if self.n_points()==0:1850return tuple()1851points = list(self.points())1852if initial_point is None:1853origin = points.pop()1854else:1855origin = initial_point1856points.remove(origin)1857vertices = [origin]1858edges = []1859while len(vertices) <= self.dim():1860if large:1861p = self.farthest_point(vertices, points)1862points.remove(p)1863else:1864p = points.pop()1865edge = p.reduced_affine_vector()-origin.reduced_affine_vector()1866if len(edges)>0 and (ker * edge).is_zero():1867continue1868vertices.append(p)1869edges.append(edge)1870ker = matrix(edges).right_kernel().matrix()1871return tuple(vertices)187218731874def placing_triangulation(self, point_order=None):1875r"""1876Construct the placing (pushing) triangulation.18771878INPUT:18791880- ``point_order`` -- list of points or integers. The order in1881which the points are to be placed.18821883OUTPUT:18841885A :class:`~sage.geometry.triangulation.triangulation.Triangulation`.18861887EXAMPLES::18881889sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1890sage: pc.placing_triangulation()1891(<0,1,2>, <0,2,4>, <2,3,4>)18921893sage: U=matrix([1894... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],1895... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],1896... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],1897... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],1898... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]1899... ])1900sage: p = PointConfiguration(U.columns())1901sage: triangulation = p.placing_triangulation(); triangulation1902(<0,2,3,4,6,7>, <0,2,3,4,6,12>, <0,2,3,4,7,13>, <0,2,3,4,12,13>,1903<0,2,3,6,7,13>, <0,2,3,6,12,13>, <0,2,4,6,7,13>, <0,2,4,6,12,13>,1904<0,3,4,6,7,12>, <0,3,4,7,12,13>, <0,3,6,7,12,13>, <0,4,6,7,12,13>,1905<1,3,4,5,6,12>, <1,3,4,6,11,12>, <1,3,4,7,11,13>, <1,3,4,11,12,13>,1906<1,3,6,7,11,13>, <1,3,6,11,12,13>, <1,4,6,7,11,13>, <1,4,6,11,12,13>,1907<3,4,6,7,11,12>, <3,4,7,11,12,13>, <3,6,7,11,12,13>, <4,6,7,11,12,13>)1908sage: sum(p.volume(t) for t in triangulation)1909421910"""1911facet_normals = dict()1912def facets_of_simplex(simplex):1913"""1914Return the facets of the simplex and store the normals in facet_normals1915"""1916simplex = list(simplex)1917origin = simplex[0]1918rest = simplex[1:]1919span = matrix([ origin.reduced_affine_vector()-p.reduced_affine_vector()1920for p in rest ])1921# span.inverse() linearly transforms the simplex into the unit simplex1922normals = span.inverse().columns()1923facets = []1924# The facets incident to the chosen vertex "origin"1925for opposing_vertex, normal in zip(rest, normals):1926facet = frozenset([origin] + [ p for p in rest if p is not opposing_vertex ])1927facets.append(facet)1928normal.set_immutable()1929facet_normals[facet] = normal1930# The remaining facet that is not incident to "origin"1931facet = frozenset(rest)1932normal = -sum(normals)1933normal.set_immutable()1934facet_normals[facet] = normal1935facets.append(facet)1936return set(facets)19371938# input verification1939self._assert_is_affine()1940if point_order is None:1941point_order = list(self.points())1942elif isinstance(point_order[0], Point):1943point_order = list(point_order)1944assert all(p.point_configuration()==self for p in point_order)1945else:1946point_order = [ self.point(i) for i in point_order ]1947assert all(p in self.points() for p in point_order)19481949# construct the initial simplex1950simplices = [ frozenset(self.contained_simplex()) ]1951for s in simplices[0]:1952try:1953point_order.remove(s)1954except ValueError:1955pass1956facets = facets_of_simplex(simplices[0])19571958# successively place the remaining points1959for point in point_order:1960# identify visible facets1961visible_facets = []1962for facet in facets:1963origin = iter(facet).next()1964normal = facet_normals[facet]1965v = point.reduced_affine_vector() - origin.reduced_affine_vector()1966if v*normal>0:1967visible_facets.append(facet)19681969# construct simplices over each visible facet1970new_facets = set()1971for facet in visible_facets:1972simplex = frozenset(list(facet) + [point])1973# print 'simplex', simplex1974simplices.append(simplex)1975for facet in facets_of_simplex(simplex):1976if facet in visible_facets: continue1977if facet in new_facets:1978new_facets.remove(facet)1979continue1980new_facets.add(facet)1981facets.difference_update(visible_facets)1982facets.update(new_facets)19831984# construct the triangulation1985triangulation = [ [p.index() for p in simplex] for simplex in simplices ]1986return self(triangulation)19871988pushing_triangulation = placing_triangulation19891990@cached_method1991def Gale_transform(self, points=None):1992r"""1993Return the Gale transform of ``self``.19941995INPUT:19961997- ``points`` -- a tuple of points or point indices or ``None``1998(default). A subset of points for which to compute the Gale1999transform. By default, all points are used.20002001OUTPUT:20022003A matrix over :meth:`base_ring`.20042005EXAMPLES::20062007sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])2008sage: pc.Gale_transform()2009[ 1 -1 0 1 -1]2010[ 0 0 1 -2 1]20112012sage: pc.Gale_transform((0,1,3,4))2013[ 1 -1 1 -1]20142015sage: points = (pc.point(0), pc.point(1), pc.point(3), pc.point(4))2016sage: pc.Gale_transform(points)2017[ 1 -1 1 -1]2018"""2019self._assert_is_affine()2020if points is None:2021points = self.points()2022else:2023try:2024points = [ self.point(ZZ(i)) for i in points ]2025except TypeError:2026pass2027m = matrix([ (1,) + p.affine() for p in points])2028return m.left_kernel().matrix()202920302031