Path: blob/master/sage/geometry/triangulation/point_configuration.py
4058 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 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 (30s 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 QQ^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,j, v_i * Qinv * v_j)11521153group, node_dict = G.automorphism_group(edge_labels=True, translation=True)11541155# Relabel the permutation group1156perm_to_vertex = dict( (i,v+1) for v,i in node_dict.items() )1157group = PermutationGroup([ [ tuple([ perm_to_vertex[i] for i in cycle ])1158for cycle in generator.cycle_tuples() ]1159for generator in group.gens() ])11601161self._restricted_automorphism_group = group1162return group116311641165def face_codimension(self, point):1166r"""1167Return the smallest `d\in\mathbb{Z}` such that ``point`` is1168contained in the interior of a codimension-`d` face.11691170EXAMPLES::11711172sage: triangle = PointConfiguration([[0,0], [1,-1], [1,0], [1,1]]);1173sage: triangle.point(2)1174P(1, 0)1175sage: triangle.face_codimension(2)117611177sage: triangle.face_codimension( [1,0] )1178111791180This also works for degenerate cases like the tip of the1181pyramid over a square (which saturates four inequalities)::11821183sage: pyramid = PointConfiguration([[1,0,0],[0,1,1],[0,1,-1],[0,-1,-1],[0,-1,1]])1184sage: pyramid.face_codimension(0)118531186"""1187try:1188p = vector(self.point(point).reduced_affine())1189except TypeError:1190p = vector(point);11911192inequalities = []1193for ieq in self.convex_hull().inequality_generator():1194if (ieq.A()*p + ieq.b() == 0):1195inequalities += [ ieq.vector() ];1196return matrix(inequalities).rank();119711981199def face_interior(self, dim=None, codim=None):1200"""1201Return points by the codimension of the containing face in the convex hull.12021203EXAMPLES::12041205sage: triangle = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);1206sage: triangle.face_interior()1207((1,), (3,), (0, 2, 4))1208sage: triangle.face_interior(dim=0) # the vertices of the convex hull1209(0, 2, 4)1210sage: triangle.face_interior(codim=1) # interior of facets1211(3,)1212"""1213assert not (dim!=None and codim!=None), "You cannot specify both dim and codim."12141215if (dim!=None):1216return self.face_interior()[self.convex_hull().dim()-dim]1217if (codim!=None):1218return self.face_interior()[codim]12191220try:1221return self._face_interior1222except AttributeError:1223pass12241225d = [ self.face_codimension(i) for i in range(0,self.n_points()) ]12261227return tuple( tuple(filter( lambda i: d[i]==codim, range(0,self.n_points())) )1228for codim in range(0,self.dim()+1) )122912301231def exclude_points(self, point_idx_list):1232"""1233Return a new point configuration with the given points1234removed.12351236INPUT:12371238- ``point_idx_list`` -- a list of integers. The indices of1239points to exclude.12401241OUTPUT:12421243A new :class:`PointConfiguration` with the given points1244removed.12451246EXAMPLES::12471248sage: p = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);1249sage: list(p)1250[P(-1, 0), P(0, 0), P(1, -1), P(1, 0), P(1, 1)]1251sage: q = p.exclude_points([3])1252sage: list(q)1253[P(-1, 0), P(0, 0), P(1, -1), P(1, 1)]1254sage: p.exclude_points( p.face_interior(codim=1) ).points()1255(P(-1, 0), P(0, 0), P(1, -1), P(1, 1))1256"""1257points = [ self.point(i) for i in range(0,self.n_points())1258if not i in point_idx_list ]1259return PointConfiguration(points,1260projective=False,1261connected=self._connected,1262fine=self._fine,1263regular=self._regular,1264star=self._star)126512661267def volume(self, simplex=None):1268"""1269Find n! times the n-volume of a simplex of dimension n.12701271INPUT:12721273- ``simplex`` (optional argument) -- a simplex from a1274triangulation T specified as a list of point indices.12751276OUTPUT:12771278* If a simplex was passed as an argument: n!*(volume of ``simplex``).12791280* Without argument: n!*(the total volume of the convex hull).12811282EXAMPLES:12831284The volume of the standard simplex should always be 1::12851286sage: p = PointConfiguration([[0,0],[1,0],[0,1],[1,1]])1287sage: p.volume( [0,1,2] )128811289sage: simplex = p.triangulate()[0] # first simplex of triangulation1290sage: p.volume(simplex)1291112921293The square can be triangulated into two minimal simplices, so1294in the "integral" normalization its volume equals two::12951296sage: p.volume()1297212981299.. note::13001301We return n!*(metric volume of the simplex) to ensure that1302the volume is an integer. Essentially, this normalizes1303things so that the volume of the standard n-simplex is 1.1304See [GKZ]_ page 182.1305"""1306if (simplex==None):1307return sum([ self.volume(s) for s in self.triangulate() ])13081309#Form a matrix whose columns are the points of simplex1310#with the first point of simplex shifted to the origin.1311v = [ self.point(i).reduced_affine_vector() for i in simplex ]1312m = matrix([ v_i - v[0] for v_i in v[1:] ])1313return abs(m.det())131413151316def secondary_polytope(self):1317r"""1318Calculate the secondary polytope of the point configuration.13191320For a definition of the secondary polytope, see [GKZ]_ page 2201321Definition 1.6.13221323Note that if you restricted the admissible triangulations of1324the point configuration then the output will be the1325corresponding face of the whole secondary polytope.13261327OUTPUT:13281329The secondary polytope of the point configuration as an1330instance of1331:class:`~sage.geometry.polyhedron.base.Polyhedron_base`.13321333EXAMPLES::13341335sage: p = PointConfiguration([[0,0],[1,0],[2,1],[1,2],[0,1]])1336sage: poly = p.secondary_polytope()1337sage: matrix(poly.vertices()).transpose()1338[1 1 3 3 5]1339[3 5 1 4 1]1340[4 2 5 2 4]1341[2 4 2 5 4]1342[5 3 4 1 1]1343sage: poly.Vrepresentation()1344(A vertex at (1, 3, 4, 2, 5),1345A vertex at (1, 5, 2, 4, 3),1346A vertex at (3, 1, 5, 2, 4),1347A vertex at (3, 4, 2, 5, 1),1348A vertex at (5, 1, 4, 4, 1))1349sage: poly.Hrepresentation()1350(An equation (0, 0, 1, 2, 1) x - 13 == 0,1351An equation (1, 0, 0, 2, 2) x - 15 == 0,1352An equation (0, 1, 0, -3, -2) x + 13 == 0,1353An inequality (0, 0, 0, -1, -1) x + 7 >= 0,1354An inequality (0, 0, 0, 1, 0) x - 2 >= 0,1355An inequality (0, 0, 0, -2, -1) x + 11 >= 0,1356An inequality (0, 0, 0, 0, 1) x - 1 >= 0,1357An inequality (0, 0, 0, 3, 2) x - 14 >= 0)1358"""1359from sage.geometry.polyhedron.constructor import Polyhedron1360#TODO: once restriction to regular triangulations is fixed,1361#change the next line to only take the regular triangulations,1362#since they are the vertices of the secondary polytope anyway.1363l = self.triangulations_list()1364return Polyhedron(vertices = [x.gkz_phi() for x in l])136513661367def circuits_support(self):1368r"""1369A generator for the supports of the circuits of the point configuration.13701371See :meth:`circuits` for details.13721373OUTPUT:13741375A generator for the supports `C_-\cup C_+` (returned as a1376Python tuple) for all circuits of the point configuration.13771378EXAMPLES::13791380sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])1381sage: list( p.circuits_support() )1382[(0, 3, 4), (0, 1, 2), (1, 2, 3, 4)]1383"""1384n = len(self)1385U = [ self[i].reduced_projective() for i in range(0,n) ]13861387# the index set of U1388I = set(range(0,n))1389# The (indices of) known independent elements of U1390independent_k = [ (i,) for i in range(0,n) ]1391supports_k = []13921393supports = () # supports of circuits1394for k in range(2, self.dim()+3):13951396# possibly linear dependent subsets1397supports_knext = set()1398possible_dependency = set()1399for indep in independent_k:1400indep_plus_one = [ tuple(sorted(indep+(i,))) for i in (I-set(indep)) ]1401possible_dependency.update(indep_plus_one)1402for supp in supports_k:1403supp_plus_one = [ tuple(sorted(supp+(i,))) for i in (I-set(supp)) ]1404possible_dependency.difference_update(supp_plus_one)1405supports_knext.update(supp_plus_one)14061407# remember supports and independents for the next k-iteration1408supports_k = list(supports_knext)1409independent_k = []1410for idx in possible_dependency:1411rk = matrix([ U[i] for i in idx ]).rank()1412if rk==k:1413independent_k.append(idx)1414else:1415supports_k.append(idx)1416yield idx1417assert independent_k==[] # there are no independent (self.dim()+3)-tuples141814191420def circuits(self):1421r"""1422Return the circuits of the point configuration.14231424Roughly, a circuit is a minimal linearly dependent subset of1425the points. That is, a circuit is a partition14261427.. MATH::14281429\{ 0, 1, \dots, n-1 \} = C_+ \cup C_0 \cup C_-14301431such that there is an (unique up to an overall normalization) affine1432relation14331434.. MATH::14351436\sum_{i\in C_+} \alpha_i \vec{p}_i =1437\sum_{j\in C_-} \alpha_j \vec{p}_j14381439with all positive (or all negative) coefficients, where1440`\vec{p}_i=(p_1,\dots,p_k,1)` are the projective coordinates1441of the `i`-th point.14421443OUTPUT:14441445The list of (unsigned) circuits as triples `(C_+, C_0,1446C_-)`. The swapped circuit `(C_-, C_0, C_+)` is not returned1447separately.14481449EXAMPLES::14501451sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])1452sage: p.circuits()1453(((0,), (1, 2), (3, 4)), ((0,), (3, 4), (1, 2)), ((1, 2), (0,), (3, 4)))145414551456TESTS::14571458sage: U=matrix([1459... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],1460... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],1461... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],1462... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],1463... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]1464... ])1465sage: p = PointConfiguration(U.columns())1466sage: len( p.circuits() ) # long time14672181468"""1469try:1470return self._circuits1471except AttributeError:1472pass14731474n = len(self)1475U = [ self[i].reduced_projective() for i in range(0,n) ]14761477Circuits = ()1478for support in self.circuits_support():1479m = matrix([ U[i] for i in support ]).transpose()1480ker = m.right_kernel().basis()[0]1481assert len(ker)==len(support)1482Cplus = [ support[i] for i in range(0,len(support)) if ker[i]>0 ]1483Cminus = [ support[i] for i in range(0,len(support)) if ker[i]<0 ]1484Czero = set( range(0,n) ).difference(support)1485Circuits += ( (tuple(Cplus), tuple(Czero), tuple(Cminus)), )1486self._circuits = Circuits1487return Circuits148814891490def positive_circuits(self, *negative):1491r"""1492Returns the positive part of circuits with fixed negative part.14931494A circuit is a pair `(C_+, C_-)`, each consisting of a subset1495(actually, an ordered tuple) of point indices.14961497INPUT:14981499- ``*negative`` -- integer. The indices of points.15001501OUTPUT:15021503A tuple of all circuits with `C_-` = ``negative``.15041505EXAMPLE::15061507sage: 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)])1508sage: p.positive_circuits(8)1509((0, 7), (0, 1, 4), (0, 2, 3), (0, 5, 6), (0, 1, 2, 5), (0, 3, 4, 6))1510sage: p.positive_circuits(0,5,6)1511((8,),)1512"""1513pos = ()1514negative = tuple(sorted(negative))1515for circuit in self.circuits():1516Cpos = circuit[0]1517Cneg = circuit[2]1518if Cpos == negative:1519pos += ( Cneg, )1520elif Cneg == negative:1521pos += ( Cpos, )1522return pos152315241525def bistellar_flips(self):1526r"""1527Return the bistellar flips.15281529OUTPUT:15301531The bistellar flips as a tuple. Each flip is a pair1532`(T_+,T_-)` where `T_+` and `T_-` are partial triangulations1533of the point configuration.15341535EXAMPLES::15361537sage: pc = PointConfiguration([(0,0),(1,0),(0,1),(1,1)])1538sage: pc.bistellar_flips()1539(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)1540sage: Tpos, Tneg = pc.bistellar_flips()[0]1541sage: Tpos.plot(axes=False)1542sage: Tneg.plot(axes=False)15431544The 3d analog::15451546sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(-1,0,0),(1,1,1)])1547sage: pc.bistellar_flips()1548(((<0,1,2,3>, <0,1,2,4>), (<0,1,3,4>, <0,2,3,4>, <1,2,3,4>)),)15491550A 2d flip on the base of the pyramid over a square::15511552sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(0,2,2),(1,1,1)])1553sage: pc.bistellar_flips()1554(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)1555sage: Tpos, Tneg = pc.bistellar_flips()[0]1556sage: Tpos.plot(axes=False)1557"""1558flips = []1559for C in self.circuits():1560Cpos = list(C[0])1561Cneg = list(C[2])1562support = sorted(Cpos+Cneg)1563Tpos = [ Cpos+Cneg[0:i]+Cneg[i+1:len(Cneg)] for i in range(0,len(Cneg)) ]1564Tneg = [ Cneg+Cpos[0:i]+Cpos[i+1:len(Cpos)] for i in range(0,len(Cpos)) ]1565flips.append( (self.element_class(Tpos, parent=self, check=False),1566self.element_class(Tneg, parent=self, check=False)) )1567return tuple(flips)156815691570def lexicographic_triangulation(self):1571r"""1572Return the lexicographic triangulation.15731574The algorithm was taken from [PUNTOS]_.15751576EXAMPLES::15771578sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])1579sage: p.lexicographic_triangulation()1580(<1,3,4>, <2,3,4>)15811582TESTS::15831584sage: U=matrix([1585... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],1586... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],1587... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],1588... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],1589... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]1590... ])1591sage: pc = PointConfiguration(U.columns())1592sage: pc.lexicographic_triangulation()1593(<1,3,4,7,10,13>, <1,3,4,8,10,13>, <1,3,6,7,10,13>, <1,3,6,8,10,13>,1594<1,4,6,7,10,13>, <1,4,6,8,10,13>, <2,3,4,6,7,12>, <2,3,4,7,12,13>,1595<2,3,6,7,12,13>, <2,4,6,7,12,13>, <3,4,5,6,9,12>, <3,4,5,8,9,12>,1596<3,4,6,7,11,12>, <3,4,6,9,11,12>, <3,4,7,10,11,13>, <3,4,7,11,12,13>,1597<3,4,8,9,10,12>, <3,4,8,10,12,13>, <3,4,9,10,11,12>, <3,4,10,11,12,13>,1598<3,5,6,8,9,12>, <3,6,7,10,11,13>, <3,6,7,11,12,13>, <3,6,8,9,10,12>,1599<3,6,8,10,12,13>, <3,6,9,10,11,12>, <3,6,10,11,12,13>, <4,5,6,8,9,12>,1600<4,6,7,10,11,13>, <4,6,7,11,12,13>, <4,6,8,9,10,12>, <4,6,8,10,12,13>,1601<4,6,9,10,11,12>, <4,6,10,11,12,13>)1602sage: len(_)1603341604"""1605lex_supp = set()1606for circuit in self.circuits():1607Cplus = circuit[0]1608Cminus = circuit[2]1609s0 = min(Cplus + Cminus)1610if s0 in Cplus:1611lex_supp.add(Cplus)1612else:1613lex_supp.add(Cminus)16141615lex_supp = sorted(lex_supp, key=lambda x:-len(x))1616basepts = copy(lex_supp)1617for i in range(0,len(lex_supp)-1):1618for j in range(i+1,len(lex_supp)):1619if set(lex_supp[j]).issubset(set(lex_supp[i])):1620try:1621basepts.remove(lex_supp[i])1622except ValueError:1623pass16241625basepts = [ (len(b),)+b for b in basepts ] # decorate1626basepts = sorted(basepts) # sort1627basepts = [ b[1:] for b in basepts ] # undecorate16281629def make_cotriang(basepts):1630if len(basepts)==0:1631return [frozenset()]1632triangulation = set()1633for tail in make_cotriang(basepts[1:]):1634for head in basepts[0]:1635triangulation.update([ frozenset([head]).union(tail) ])16361637nonminimal = set()1638for rel in Combinations(triangulation,2):1639if rel[0].issubset(rel[1]): nonminimal.update([rel[1]])1640if rel[1].issubset(rel[0]): nonminimal.update([rel[0]])1641triangulation.difference_update(nonminimal)16421643triangulation = [ [len(t)]+sorted(t) for t in triangulation ] # decorate1644triangulation = sorted(triangulation) # sort1645triangulation = [ frozenset(t[1:]) for t in triangulation ] # undecorate16461647return triangulation16481649triangulation = make_cotriang(basepts)1650I = frozenset(range(0,self.n_points()))1651triangulation = [ tuple(I.difference(t)) for t in triangulation ]16521653return self(triangulation)165416551656@cached_method1657def distance_affine(self, x, y):1658r"""1659Returns the distance between two points.16601661The distance function used in this method is `d_{aff}(x,y)^2`,1662the square of the usual affine distance function16631664.. math::16651666d_{aff}(x,y) = |x-y|16671668INPUT:16691670- ``x``, ``y`` -- two points of the point configuration.16711672OUTPUT:16731674The metric distance-square `d_{aff}(x,y)^2`. Note that this1675distance lies in the same field as the entries of ``x``,1676``y``. That is, the distance of rational points will be1677rational and so on.16781679EXAMPLES::16801681sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1682sage: [ pc.distance_affine(pc.point(0), p) for p in pc.points() ]1683[0, 1, 5, 5, 1]1684"""1685self._assert_is_affine()1686d = 01687for xi, yi in zip(x.projective(), y.projective()):1688d += (xi-yi)**21689return d169016911692@cached_method1693def distance_FS(self, x, y):1694r"""1695Returns the distance between two points.16961697The distance function used in this method is `1-\cos1698d_{FS}(x,y)^2`, where `d_{FS}` is the Fubini-Study distance of1699projective points. Recall the Fubini-Studi distance function17001701.. math::17021703d_{FS}(x,y) = \arccos \sqrt{ \frac{(x\cdot y)^2}{|x|^2 |y|^2} }17041705INPUT:17061707- ``x``, ``y`` -- two points of the point configuration.17081709OUTPUT:17101711The distance `1-\cos d_{FS}(x,y)^2`. Note that this distance1712lies in the same field as the entries of ``x``, ``y``. That1713is, the distance of rational points will be rational and so1714on.17151716EXAMPLES::17171718sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1719sage: [ pc.distance_FS(pc.point(0), p) for p in pc.points() ]1720[0, 1/2, 5/6, 5/6, 1/2]1721"""1722x2 = y2 = xy = 01723for xi, yi in zip(x.projective(), y.projective()):1724x2 += xi*xi1725y2 += yi*yi1726xy += xi*yi1727return 1-xy*xy/(x2*y2)172817291730@cached_method1731def distance(self, x, y):1732"""1733Returns the distance between two points.17341735INPUT:17361737- ``x``, ``y`` -- two points of the point configuration.17381739OUTPUT:17401741The distance between ``x`` and ``y``, measured either with1742:meth:`distance_affine` or :meth:`distance_FS` depending on1743whether the point configuration is defined by affine or1744projective points. These are related, but not equal to the1745usual flat and Fubini-Study distance.17461747EXAMPLES::17481749sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1750sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]1751[0, 1, 5, 5, 1]17521753sage: pc = PointConfiguration([(0,0,1),(1,0,1),(2,1,1),(1,2,1),(0,1,1)], projective=True)1754sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]1755[0, 1/2, 5/6, 5/6, 1/2]1756"""1757if self.is_affine():1758return self.distance_affine(x,y)1759else:1760return self.distance_FS(x,y)176117621763def farthest_point(self, points, among=None):1764"""1765Return the point with the most distance from ``points``.17661767INPUT:17681769- ``points`` -- a list of points.17701771- ``among`` -- a list of points or ``None`` (default). The set1772of points from which to pick the farthest one. By default,1773all points of the configuration are considered.17741775OUTPUT:17761777A :class:`~sage.geometry.triangulation.base.Point` with1778largest minimal distance from all given ``points``.17791780EXAMPLES::17811782sage: pc = PointConfiguration([(0,0),(1,0),(1,1),(0,1)])1783sage: pc.farthest_point([ pc.point(0) ])1784P(1, 1)1785"""1786if len(points)==0:1787return self.point(0)1788if among is None:1789among = self.points()1790p_max = None1791for p in among:1792if p in points:1793continue1794if p_max is None:1795p_max = p1796d_max = min(self.distance(p,q) for q in points)1797continue1798d = min(self.distance(p,q) for q in points)1799if d>d_max:1800p_max = p1801return p_max180218031804def contained_simplex(self, large=True, initial_point=None):1805"""1806Return a simplex contained in the point configuration.18071808INPUT:18091810- ``large`` -- boolean. Whether to attempt to return a large1811simplex.18121813- ``initial_point`` -- a1814:class:`~sage.geometry.triangulation.base.Point` or ``None``1815(default). A specific point to start with when picking the1816simplex vertices.18171818OUTPUT:18191820A tuple of points that span a simplex of dimension1821:meth:`dim`. If ``large==True``, the simplex is constructed by1822sucessively picking the farthest point. This will ensure that1823the simplex is not unneccessarily small, but will in general1824not return a maximal simplex.18251826EXAMPLES::18271828sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])1829sage: pc.contained_simplex()1830(P(0, 1), P(2, 1), P(1, 0))1831sage: pc.contained_simplex(large=False)1832(P(0, 1), P(1, 1), P(1, 0))1833sage: pc.contained_simplex(initial_point=pc.point(0))1834(P(0, 0), P(1, 1), P(1, 0))18351836sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])1837sage: pc.contained_simplex()1838(P(-1, -1), P(1, 1), P(0, 1))18391840TESTS::18411842sage: pc = PointConfiguration([[0,0],[0,1],[1,0]])1843sage: pc.contained_simplex()1844(P(1, 0), P(0, 1), P(0, 0))1845sage: pc = PointConfiguration([[0,0],[0,1]])1846sage: pc.contained_simplex()1847(P(0, 1), P(0, 0))1848sage: pc = PointConfiguration([[0,0]])1849sage: pc.contained_simplex()1850(P(0, 0),)1851sage: pc = PointConfiguration([])1852sage: pc.contained_simplex()1853()1854"""1855self._assert_is_affine()1856if self.n_points()==0:1857return tuple()1858points = list(self.points())1859if initial_point is None:1860origin = points.pop()1861else:1862origin = initial_point1863points.remove(origin)1864vertices = [origin]1865edges = []1866while len(vertices) <= self.dim():1867if large:1868p = self.farthest_point(vertices, points)1869points.remove(p)1870else:1871p = points.pop()1872edge = p.reduced_affine_vector()-origin.reduced_affine_vector()1873if len(edges)>0 and (ker * edge).is_zero():1874continue1875vertices.append(p)1876edges.append(edge)1877ker = matrix(edges).right_kernel().matrix()1878return tuple(vertices)187918801881def placing_triangulation(self, point_order=None):1882r"""1883Construct the placing (pushing) triangulation.18841885INPUT:18861887- ``point_order`` -- list of points or integers. The order in1888which the points are to be placed.18891890OUTPUT:18911892A :class:`~sage.geometry.triangulation.triangulation.Triangulation`.18931894EXAMPLES::18951896sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])1897sage: pc.placing_triangulation()1898(<0,1,2>, <0,2,4>, <2,3,4>)18991900sage: U=matrix([1901... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],1902... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],1903... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],1904... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],1905... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]1906... ])1907sage: p = PointConfiguration(U.columns())1908sage: triangulation = p.placing_triangulation(); triangulation1909(<0,2,3,4,6,7>, <0,2,3,4,6,12>, <0,2,3,4,7,13>, <0,2,3,4,12,13>,1910<0,2,3,6,7,13>, <0,2,3,6,12,13>, <0,2,4,6,7,13>, <0,2,4,6,12,13>,1911<0,3,4,6,7,12>, <0,3,4,7,12,13>, <0,3,6,7,12,13>, <0,4,6,7,12,13>,1912<1,3,4,5,6,12>, <1,3,4,6,11,12>, <1,3,4,7,11,13>, <1,3,4,11,12,13>,1913<1,3,6,7,11,13>, <1,3,6,11,12,13>, <1,4,6,7,11,13>, <1,4,6,11,12,13>,1914<3,4,6,7,11,12>, <3,4,7,11,12,13>, <3,6,7,11,12,13>, <4,6,7,11,12,13>)1915sage: sum(p.volume(t) for t in triangulation)1916421917"""1918facet_normals = dict()1919def facets_of_simplex(simplex):1920"""1921Return the facets of the simplex and store the normals in facet_normals1922"""1923simplex = list(simplex)1924origin = simplex[0]1925rest = simplex[1:]1926span = matrix([ origin.reduced_affine_vector()-p.reduced_affine_vector()1927for p in rest ])1928# span.inverse() linearly transforms the simplex into the unit simplex1929normals = span.inverse().columns()1930facets = []1931# The facets incident to the chosen vertex "origin"1932for opposing_vertex, normal in zip(rest, normals):1933facet = frozenset([origin] + [ p for p in rest if p is not opposing_vertex ])1934facets.append(facet)1935normal.set_immutable()1936facet_normals[facet] = normal1937# The remaining facet that is not incident to "origin"1938facet = frozenset(rest)1939normal = -sum(normals)1940normal.set_immutable()1941facet_normals[facet] = normal1942facets.append(facet)1943return set(facets)19441945# input verification1946self._assert_is_affine()1947if point_order is None:1948point_order = list(self.points())1949elif isinstance(point_order[0], Point):1950point_order = list(point_order)1951assert all(p.point_configuration()==self for p in point_order)1952else:1953point_order = [ self.point(i) for i in point_order ]1954assert all(p in self.points() for p in point_order)19551956# construct the initial simplex1957simplices = [ frozenset(self.contained_simplex()) ]1958for s in simplices[0]:1959try:1960point_order.remove(s)1961except ValueError:1962pass1963facets = facets_of_simplex(simplices[0])19641965# successively place the remaining points1966for point in point_order:1967# identify visible facets1968visible_facets = []1969for facet in facets:1970origin = iter(facet).next()1971normal = facet_normals[facet]1972v = point.reduced_affine_vector() - origin.reduced_affine_vector()1973if v*normal>0:1974visible_facets.append(facet)19751976# construct simplices over each visible facet1977new_facets = set()1978for facet in visible_facets:1979simplex = frozenset(list(facet) + [point])1980# print 'simplex', simplex1981simplices.append(simplex)1982for facet in facets_of_simplex(simplex):1983if facet in visible_facets: continue1984if facet in new_facets:1985new_facets.remove(facet)1986continue1987new_facets.add(facet)1988facets.difference_update(visible_facets)1989facets.update(new_facets)19901991# construct the triangulation1992triangulation = [ [p.index() for p in simplex] for simplex in simplices ]1993return self(triangulation)19941995pushing_triangulation = placing_triangulation19961997@cached_method1998def Gale_transform(self, points=None):1999r"""2000Return the Gale transform of ``self``.20012002INPUT:20032004- ``points`` -- a tuple of points or point indices or ``None``2005(default). A subset of points for which to compute the Gale2006transform. By default, all points are used.20072008OUTPUT:20092010A matrix over :meth:`base_ring`.20112012EXAMPLES::20132014sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])2015sage: pc.Gale_transform()2016[ 1 -1 0 1 -1]2017[ 0 0 1 -2 1]20182019sage: pc.Gale_transform((0,1,3,4))2020[ 1 -1 1 -1]20212022sage: points = (pc.point(0), pc.point(1), pc.point(3), pc.point(4))2023sage: pc.Gale_transform(points)2024[ 1 -1 1 -1]2025"""2026self._assert_is_affine()2027if points is None:2028points = self.points()2029else:2030try:2031points = [ self.point(ZZ(i)) for i in points ]2032except TypeError:2033pass2034m = matrix([ (1,) + p.affine() for p in points])2035return m.left_kernel().matrix()203620372038