Path: blob/master/sage/geometry/triangulation/element.py
4048 views
""""1A triangulation23In Sage, the4:class:`~sage.geometry.triangulation.point_configuration.PointConfiguration`5and :class:`Triangulation` satisfy a parent/element relationship. In6particular, each triangulation refers back to its point7configuration. If you want to triangulate a point configuration, you8should construct a point configuration first and then use one of its9methods to triangulate it according to your requirements. You should10never have to construct a :class:`Triangulation` object directly.1112EXAMPLES:1314First, we select the internal implementation for enumerating15triangulations::1617sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM1819Here is a simple example of how to triangulate a point configuration::2021sage: p = [[0,-1,-1],[0,0,1],[0,1,0], [1,-1,-1],[1,0,1],[1,1,0]]22sage: points = PointConfiguration(p)23sage: triang = points.triangulate(); triang24(<0,1,2,5>, <0,1,3,5>, <1,3,4,5>)25sage: triang.plot(axes=False)2627See :mod:`sage.geometry.triangulation.point_configuration` for more details.28"""293031########################################################################32# Copyright (C) 2010 Volker Braun <[email protected]>33#34# Distributed under the terms of the GNU General Public License (GPL)35#36# http://www.gnu.org/licenses/37########################################################################3839from sage.structure.element import Element40from sage.rings.all import QQ, ZZ41from sage.modules.all import vector42from sage.misc.cachefunc import cached_method434445########################################################################46def triangulation_render_2d(triangulation, **kwds):47r"""48Return a graphical representation of a 2-d triangulation.4950INPUT:5152- ``triangulation`` -- a :class:`Triangulation`.5354- ``**kwds`` -- keywords that are passed on to the graphics primitives.5556OUTPUT:5758A 2-d graphics object.5960EXAMPLES::6162sage: points = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])63sage: triang = points.triangulate()64sage: triang.plot(axes=False, aspect_ratio=1) # indirect doctest65"""66from sage.plot.all import point2d, line2d, arrow, polygon2d67points = [ point.reduced_affine() for point in triangulation.point_configuration() ]68coord = [ [p[0], p[1]] for p in points ]69plot_points = sum([ point2d(p,70zorder=2, pointsize=10, **kwds)71for p in coord ])7273tmp_lines = []74for t in triangulation:75if len(t)>=2:76tmp_lines.append([t[0], t[1]])77if len(t)>=3:78tmp_lines.append([t[0], t[2]])79tmp_lines.append([t[1], t[2]])80all_lines = []81interior_lines = []82for l in tmp_lines:83if l not in all_lines:84all_lines.append(l)85else:86interior_lines.append(l)87exterior_lines = [ l for l in all_lines if not l in interior_lines ]8889plot_interior_lines = sum([ line2d([ coord[l[0]], coord[l[1]] ],90zorder=1, rgbcolor=(0,1,0), **kwds)91for l in interior_lines ])92plot_exterior_lines = sum([ line2d([ coord[l[0]], coord[l[1]] ],93zorder=1, rgbcolor=(0,0,1), **kwds)94for l in exterior_lines ])9596plot_triangs = sum([ polygon2d([coord[t[0]], coord[t[1]], coord[t[2]]],97zorder=0, rgbcolor=(0.8, 1, 0.8), **kwds)98for t in triangulation if len(t)>=3 ])99100return \101plot_points + \102plot_interior_lines + plot_exterior_lines + \103plot_triangs104105106107108def triangulation_render_3d(triangulation, **kwds):109r"""110Return a graphical representation of a 3-d triangulation.111112INPUT:113114- ``triangulation`` -- a :class:`Triangulation`.115116- ``**kwds`` -- keywords that are passed on to the graphics primitives.117118OUTPUT:119120A 3-d graphics object.121122EXAMPLES::123124sage: p = [[0,-1,-1],[0,0,1],[0,1,0], [1,-1,-1],[1,0,1],[1,1,0]]125sage: points = PointConfiguration(p)126sage: triang = points.triangulate()127sage: triang.plot(axes=False) # indirect doctest128"""129from sage.plot.plot3d.all import point3d, line3d, arrow3d, polygon3d130points = [ point.reduced_affine() for point in triangulation.point_configuration() ]131coord = [ [p[0], p[1], p[2] ] for p in points ]132plot_points = sum([ point3d(p, size=15,133**kwds)134for p in coord ])135136tmp_lines = []137for t in triangulation:138if len(t)>=2:139tmp_lines.append([t[0], t[1]])140if len(t)>=3:141tmp_lines.append([t[0], t[2]])142tmp_lines.append([t[1], t[2]])143if len(t)>=4:144tmp_lines.append([t[0], t[3]])145tmp_lines.append([t[1], t[3]])146tmp_lines.append([t[2], t[3]])147all_lines = []148interior_lines = []149for l in tmp_lines:150if l not in all_lines:151all_lines.append(l)152else:153interior_lines.append(l)154exterior_lines = [ l for l in all_lines if not l in interior_lines ]155156from sage.plot.plot3d.texture import Texture157line_int = Texture(color='darkblue', ambient=1, diffuse=0)158line_ext = Texture(color='green', ambient=1, diffuse=0)159triang_int = Texture(opacity=0.3, specular=0, shininess=0, diffuse=0, ambient=1, color='yellow')160triang_ext = Texture(opacity=0.6, specular=0, shininess=0, diffuse=0, ambient=1, color='green')161162plot_interior_lines = sum([ line3d([ coord[l[0]], coord[l[1]] ],163thickness=2, texture=line_int, **kwds)164for l in interior_lines ])165plot_exterior_lines = sum([ line3d([ coord[l[0]], coord[l[1]] ],166thickness=3, texture=line_ext, **kwds)167for l in exterior_lines ])168169tmp_triangs = []170for t in triangulation:171if len(t)>=3:172tmp_triangs.append([t[0], t[1], t[2]])173if len(t)>=4:174tmp_triangs.append([t[0], t[1], t[3]])175tmp_triangs.append([t[0], t[2], t[3]])176tmp_triangs.append([t[1], t[2], t[3]])177all_triangs = []178interior_triangs = []179for l in tmp_triangs:180if l not in all_triangs:181all_triangs.append(l)182else:183interior_triangs.append(l)184exterior_triangs = [ l for l in all_triangs if not l in interior_triangs ]185186plot_interior_triangs = \187sum([ polygon3d([coord[t[0]], coord[t[1]], coord[t[2]]],188texture = triang_int, **kwds)189for t in interior_triangs ])190plot_exterior_triangs = \191sum([ polygon3d([coord[t[0]], coord[t[1]], coord[t[2]]],192texture = triang_ext, **kwds)193for t in exterior_triangs ])194195return \196plot_points + \197plot_interior_lines + plot_exterior_lines + \198plot_interior_triangs + plot_exterior_triangs199200201202203204########################################################################205class Triangulation(Element):206"""207A triangulation of a208:class:`~sage.geometry.triangulation.point_configuration.PointConfiguration`.209210.. WARNING::211212You should never create :class:`Triangulation` objects213manually. See214:meth:`~sage.geometry.triangulation.point_configuration.PointConfiguration.triangulate`215and216:meth:`~sage.geometry.triangulation.point_configuration.PointConfiguration.triangulations`217to triangulate point configurations.218"""219220def __init__(self, triangulation, parent, check=True):221"""222The constructor of a ``Triangulation`` object. Note that an223internal reference to the underlying ``PointConfiguration`` is224kept.225226INPUT:227228- ``parent`` -- a229:class:`~sage.geometry.triangulation.point_configuration.PointConfiguration`230231- ``triangulation`` -- an iterable of integers or iterable of232iterables (e.g. a list of lists). In the first case, the233integers specify simplices via234:meth:`PointConfiguration.simplex_to_int`. In the second235case, the point indices of the maximal simplices of the236triangulation.237238- ``check`` -- boolean. Whether to perform checks that the239triangulation is, indeed, a triangulation of the point240configuration.241242NOTE:243244Passing ``check=False`` allows you to create triangulations of245subsets of the points of the configuration, see246:meth:`~sage.geometry.triangulation.point_configuration.PointConfiguration.bistellar_flips`.247248EXAMPLES::249250sage: p = [[0,1],[0,0],[1,0]]251sage: points = PointConfiguration(p)252sage: from sage.geometry.triangulation.point_configuration import Triangulation253sage: Triangulation([(0,1,2)], points)254(<0,1,2>)255sage: Triangulation([1], points)256(<0,1,2>)257"""258Element.__init__(self, parent=parent)259self._point_configuration = parent260261try:262triangulation = tuple(sorted( tuple(sorted(t)) for t in triangulation))263except TypeError:264triangulation = tuple( self.point_configuration().int_to_simplex(i)265for i in triangulation )266assert not check or all( len(t)==self.point_configuration().dim()+1267for t in triangulation)268self._triangulation = triangulation269270271def point_configuration(self):272"""273Returns the point configuration underlying the triangulation.274275EXAMPLES::276277sage: pconfig = PointConfiguration([[0,0],[0,1],[1,0]])278sage: pconfig279A point configuration in QQ^2 consisting of 3 points. The280triangulations of this point configuration are assumed to281be connected, not necessarily fine, not necessarily regular.282sage: triangulation = pconfig.triangulate()283sage: triangulation284(<0,1,2>)285sage: triangulation.point_configuration()286A point configuration in QQ^2 consisting of 3 points. The287triangulations of this point configuration are assumed to288be connected, not necessarily fine, not necessarily regular.289sage: pconfig == triangulation.point_configuration()290True291"""292return self._point_configuration293294295def __cmp__(self, right):296r"""297Compare ``self`` and ``right``.298299INPUT:300301- ``right`` -- anything.302303OUTPUT:304305- 0 if ``right`` is the same triangulation as ``self``, 1 or306-1 otherwise.307308TESTS::309310sage: pc = PointConfiguration([[0,0],[0,1],[1,0]])311sage: t1 = pc.triangulate()312sage: from sage.geometry.triangulation.point_configuration import Triangulation313sage: t2 = Triangulation([[2,1,0]], pc)314sage: t1 is t2315False316sage: cmp(t1, t2)3170318sage: t1 == t2 # indirect doctest319True320sage: abs( cmp(t1, Triangulation(((0,1),(1,2)), pc, check=False) ))3211322sage: abs( cmp(t2, "not a triangulation") )3231324"""325left = self326c = cmp(isinstance(left,Triangulation), isinstance(right,Triangulation))327if c: return c328c = cmp(left.point_configuration(), right.point_configuration())329if c: return c330return cmp(left._triangulation, right._triangulation)331332333def __iter__(self):334"""335Iterate through the simplices of the triangulation.336337EXAMPLES::338339sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM340sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])341sage: triangulation = pc.triangulate()342sage: iter = triangulation.__iter__()343sage: iter.next()344(1, 3, 4)345sage: iter.next()346(2, 3, 4)347sage: iter.next()348Traceback (most recent call last):349...350StopIteration351"""352for p in self._triangulation:353yield p354355356def __getitem__(self, i):357"""358Access the point indices of the i-th simplex of the triangulation.359360INPUT:361362- ``i`` -- integer. The index of a simplex.363364OUTPUT:365366A tuple of integers. The vertex indices of the i-th simplex.367368EXAMPLES::369370sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM371sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])372sage: triangulation = pc.triangulate()373sage: triangulation[1]374(2, 3, 4)375"""376return self._triangulation[i]377378379def __len__(self):380"""381Returns the length of the triangulation.382383TESTS::384385sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM386sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])387sage: triangulation = pc.triangulations().next()388sage: triangulation.__len__()3892390sage: len(triangulation) # equivalent3912392"""393return len(self._triangulation)394395396def _repr_(self):397r"""398Return a string representation.399400TESTS::401402sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM403sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1],[2,2]])404sage: t = pc.triangulations()405sage: t.next()._repr_()406'(<1,4,5>, <2,4,5>)'407"""408#s = 'A triangulation'409#s += ' in QQ^'+str(self.point_configuration().ambient_dim())410#s += ' consisting of '+str(len(self))+' simplices.'411s = '('412s += ', '.join([ '<'+','.join(map(str,t))+'>' for t in self._triangulation])413s += ')'414return s415416417def plot(self, **kwds):418r"""419Produce a graphical representation of the triangulation.420421EXAMPLES::422423sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])424sage: triangulation = p.triangulate()425sage: triangulation426(<1,3,4>, <2,3,4>)427sage: triangulation.plot(axes=False)428"""429dim = self.point_configuration().dim()430431if dim == 2:432return triangulation_render_2d(self, **kwds)433434if dim == 3:435return triangulation_render_3d(self, **kwds)436437raise NotImplementedError, \438'Plotting '+str(dim)+'-dimensional triangulations not implemented!'439440441def gkz_phi(self):442r"""443Calculate the GKZ phi vector of the triangulation.444445The phi vector is a vector of length equals to the number of446points in the point configuration. For a fixed triangulation447`T`, the entry corresponding to the `i`-th point `p_i` is448449.. math::450451\phi_T(p_i) = \sum_{t\in T, t\owns p_i} Vol(t)452453that is, the total volume of all simplices containing `p_i`.454See also [GKZ]_ page 220 equation 1.4.455456OUTPUT:457458The phi vector of self.459460EXAMPLES::461462sage: p = PointConfiguration([[0,0],[1,0],[2,1],[1,2],[0,1]])463sage: p.triangulate().gkz_phi()464(3, 1, 5, 2, 4)465sage: p.lexicographic_triangulation().gkz_phi()466(1, 3, 4, 2, 5)467"""468vec = vector(ZZ, self.point_configuration().n_points())469for simplex in self:470vol = self.point_configuration().volume(simplex)471for i in simplex:472vec[i] = vec[i] + vol473return vec474475476def enumerate_simplices(self):477r"""478Return the enumerated simplices.479480OUTPUT:481482A tuple of integers that uniquely specifies the triangulation.483484EXAMPLES::485486sage: pc = PointConfiguration(matrix([487... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],488... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],489... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],490... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],491... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]492... ]).columns())493sage: triangulation = pc.lexicographic_triangulation()494sage: triangulation.enumerate_simplices()495(1678, 1688, 1769, 1779, 1895, 1905, 2112, 2143, 2234, 2360, 2555, 2580,4962610, 2626, 2650, 2652, 2654, 2661, 2663, 2667, 2685, 2755, 2757, 2759,4972766, 2768, 2772, 2811, 2881, 2883, 2885, 2892, 2894, 2898)498499You can recreate the triangulation from this list by passing500it to the constructor::501502sage: from sage.geometry.triangulation.point_configuration import Triangulation503sage: Triangulation([1678, 1688, 1769, 1779, 1895, 1905, 2112, 2143,504... 2234, 2360, 2555, 2580, 2610, 2626, 2650, 2652, 2654, 2661, 2663,505... 2667, 2685, 2755, 2757, 2759, 2766, 2768, 2772, 2811, 2881, 2883,506... 2885, 2892, 2894, 2898], pc)507(<1,3,4,7,10,13>, <1,3,4,8,10,13>, <1,3,6,7,10,13>, <1,3,6,8,10,13>,508<1,4,6,7,10,13>, <1,4,6,8,10,13>, <2,3,4,6,7,12>, <2,3,4,7,12,13>,509<2,3,6,7,12,13>, <2,4,6,7,12,13>, <3,4,5,6,9,12>, <3,4,5,8,9,12>,510<3,4,6,7,11,12>, <3,4,6,9,11,12>, <3,4,7,10,11,13>, <3,4,7,11,12,13>,511<3,4,8,9,10,12>, <3,4,8,10,12,13>, <3,4,9,10,11,12>, <3,4,10,11,12,13>,512<3,5,6,8,9,12>, <3,6,7,10,11,13>, <3,6,7,11,12,13>, <3,6,8,9,10,12>,513<3,6,8,10,12,13>, <3,6,9,10,11,12>, <3,6,10,11,12,13>, <4,5,6,8,9,12>,514<4,6,7,10,11,13>, <4,6,7,11,12,13>, <4,6,8,9,10,12>, <4,6,8,10,12,13>,515<4,6,9,10,11,12>, <4,6,10,11,12,13>)516"""517pc = self._point_configuration518return tuple( pc.simplex_to_int(t) for t in self )519520521def fan(self, origin=None):522r"""523Construct the fan of cones over the simplices of the triangulation.524525INPUT:526527- ``origin`` -- ``None`` (default) or coordinates of a528point. The common apex of all cones of the fan. If ``None``,529the triangulation must be a star triangulation and the530distinguished central point is used as the origin.531532OUTPUT:533534A :class:`~sage.geometry.fan.RationalPolyhedralFan`. The535coordinates of the points are shifted so that the apex of the536fan is the origin of the coordinate system.537538.. note:: If the set of cones over the simplices is not a fan, a539suitable exception is raised.540541EXAMPLES::542543sage: pc = PointConfiguration([(0,0), (1,0), (0,1), (-1,-1)], star=0, fine=True)544sage: triangulation = pc.triangulate()545sage: fan = triangulation.fan(); fan546Rational polyhedral fan in 2-d lattice N547sage: fan.is_equivalent( toric_varieties.P2().fan() )548True549550Toric diagrams (the `\ZZ_5` hyperconifold)::551552sage: vertices=[(0, 1, 0), (0, 3, 1), (0, 2, 3), (0, 0, 2)]553sage: interior=[(0, 1, 1), (0, 1, 2), (0, 2, 1), (0, 2, 2)]554sage: points = vertices+interior555sage: pc = PointConfiguration(points, fine=True)556sage: triangulation = pc.triangulate()557sage: fan = triangulation.fan( (-1,0,0) )558sage: fan559Rational polyhedral fan in 3-d lattice N560sage: fan.rays()561(N(1, 1, 0), N(1, 3, 1), N(1, 2, 3), N(1, 0, 2),562N(1, 1, 1), N(1, 1, 2), N(1, 2, 1), N(1, 2, 2))563"""564from sage.geometry.fan import Fan565if origin is None:566origin = self.point_configuration().star_center()567R = self.base_ring()568origin = vector(R, origin)569points = self.point_configuration().points()570return Fan(self, (vector(R, p) - origin for p in points))571572573@cached_method574def simplicial_complex(self):575r"""576Return a simplicial complex from a triangulation of the point577configuration.578579OUTPUT:580581A :class:`~sage.homology.simplicial_complex.SimplicialComplex`.582583EXAMPLES::584585sage: p = polytopes.cuboctahedron()586sage: sc = p.triangulate(engine='internal').simplicial_complex()587sage: sc588Simplicial complex with 12 vertices and 16 facets589590Any convex set is contractable, so its reduced homology groups vanish::591592sage: sc.homology()593{0: 0, 1: 0, 2: 0, 3: 0}594"""595from sage.homology.simplicial_complex import SimplicialComplex596from sage.misc.all import flatten597vertex_set = set(flatten(self))598return SimplicialComplex(vertex_set = vertex_set,599maximal_faces = self)600601602@cached_method603def _boundary_simplex_dictionary(self):604"""605Return facets and the simplices they bound606607TESTS::608609sage: triangulation = polytopes.n_cube(2).triangulate(engine='internal')610sage: triangulation._boundary_simplex_dictionary()611{(0, 1): ((0, 1, 3),),612(0, 3): ((0, 1, 3), (0, 2, 3)),613(1, 3): ((0, 1, 3),),614(2, 3): ((0, 2, 3),),615(0, 2): ((0, 2, 3),)}616617sage: triangulation = polytopes.n_cube(3).triangulate(engine='internal')618sage: triangulation._boundary_simplex_dictionary()619{(1, 4, 7): ((0, 1, 4, 7), (1, 4, 5, 7)),620(1, 3, 7): ((1, 2, 3, 7),),621(0, 1, 7): ((0, 1, 2, 7), (0, 1, 4, 7)),622(0, 2, 7): ((0, 1, 2, 7), (0, 2, 4, 7)),623(0, 1, 4): ((0, 1, 4, 7),),624(2, 4, 6): ((2, 4, 6, 7),),625(0, 1, 2): ((0, 1, 2, 7),),626(1, 2, 7): ((0, 1, 2, 7), (1, 2, 3, 7)),627(2, 6, 7): ((2, 4, 6, 7),),628(2, 3, 7): ((1, 2, 3, 7),),629(1, 4, 5): ((1, 4, 5, 7),),630(1, 5, 7): ((1, 4, 5, 7),),631(4, 5, 7): ((1, 4, 5, 7),),632(0, 4, 7): ((0, 1, 4, 7), (0, 2, 4, 7)),633(2, 4, 7): ((0, 2, 4, 7), (2, 4, 6, 7)),634(1, 2, 3): ((1, 2, 3, 7),),635(4, 6, 7): ((2, 4, 6, 7),),636(0, 2, 4): ((0, 2, 4, 7),)}637"""638result = dict()639for simplex in self:640for i in range(len(simplex)):641facet = simplex[:i] + simplex[i+1:]642result[facet] = result.get(facet, tuple()) + (simplex,)643return result644645646@cached_method647def boundary(self):648"""649Return the boundary of the triangulation.650651OUTPUT:652653The outward-facing boundary simplices (of dimension `d-1`) of654the `d`-dimensional triangulation as a set. Each boundary is655returned by a tuple of point indices.656657EXAMPLES::658659sage: triangulation = polytopes.n_cube(3).triangulate(engine='internal')660sage: triangulation661(<0,1,2,7>, <0,1,4,7>, <0,2,4,7>, <1,2,3,7>, <1,4,5,7>, <2,4,6,7>)662sage: triangulation.boundary()663frozenset([(1, 3, 7), (4, 5, 7), (1, 2, 3), (0, 1, 2), (2, 4, 6), (2, 6, 7),664(2, 3, 7), (1, 5, 7), (0, 1, 4), (1, 4, 5), (4, 6, 7), (0, 2, 4)])665sage: triangulation.interior_facets()666frozenset([(1, 4, 7), (1, 2, 7), (2, 4, 7), (0, 1, 7), (0, 4, 7), (0, 2, 7)])667"""668return frozenset(facet for facet, bounded_simplices669in self._boundary_simplex_dictionary().iteritems()670if len(bounded_simplices)==1)671672@cached_method673def interior_facets(self):674"""675Return the interior facets of the triangulation.676677OUTPUT:678679The inward-facing boundary simplices (of dimension `d-1`) of680the `d`-dimensional triangulation as a set. Each boundary is681returned by a tuple of point indices.682683EXAMPLES::684685sage: triangulation = polytopes.n_cube(3).triangulate(engine='internal')686sage: triangulation687(<0,1,2,7>, <0,1,4,7>, <0,2,4,7>, <1,2,3,7>, <1,4,5,7>, <2,4,6,7>)688sage: triangulation.boundary()689frozenset([(1, 3, 7), (4, 5, 7), (1, 2, 3), (0, 1, 2), (2, 4, 6), (2, 6, 7),690(2, 3, 7), (1, 5, 7), (0, 1, 4), (1, 4, 5), (4, 6, 7), (0, 2, 4)])691sage: triangulation.interior_facets()692frozenset([(1, 4, 7), (1, 2, 7), (2, 4, 7), (0, 1, 7), (0, 4, 7), (0, 2, 7)])693"""694return frozenset(facet for facet, bounded_simplices695in self._boundary_simplex_dictionary().iteritems()696if len(bounded_simplices)==2)697698@cached_method699def normal_cone(self):700r"""701Return the (closure of the) normal cone of the triangulation.702703Recall that a regular triangulation is one that equals the704"crease lines" of a convex piecewise-linear function. This705support function is not unique, for example, you can scale it706by a positive constant. The set of all piecewise-linear707functions with fixed creases forms an open cone. This cone can708be interpreted as the cone of normal vectors at a point of the709secondary polytope, which is why we call it normal cone. See710[GKZ]_ Section 7.1 for details.711712OUTPUT:713714The closure of the normal cone. The `i`-th entry equals the715value of the piecewise-linear function at the `i`-th point of716the configuration.717718For an irregular triangulation, the normal cone is empty. In719this case, a single point (the origin) is returned.720721EXAMPLES::722723sage: triangulation = polytopes.n_cube(2).triangulate(engine='internal')724sage: triangulation725(<0,1,3>, <0,2,3>)726sage: N = triangulation.normal_cone(); N7274-d cone in 4-d lattice728sage: N.rays()729((-1, 0, 0, 0), (1, 0, 1, 0), (-1, 0, -1, 0), (1, 0, 0, -1),730(-1, 0, 0, 1), (1, 1, 0, 0), (-1, -1, 0, 0))731sage: N.dual().rays()732((-1, 1, 1, -1),)733734TESTS::735736sage: polytopes.n_simplex(2).triangulate().normal_cone()7373-d cone in 3-d lattice738sage: _.dual().is_trivial()739True740"""741if not self.point_configuration().base_ring().is_subring(QQ):742raise NotImplementedError('Only base rings ZZ and QQ are supported')743from sage.libs.ppl import Variable, Constraint, Constraint_System, Linear_Expression, C_Polyhedron744from sage.matrix.constructor import matrix745from sage.misc.misc import uniq746from sage.rings.arith import lcm747pc = self.point_configuration()748cs = Constraint_System()749for facet in self.interior_facets():750s0, s1 = self._boundary_simplex_dictionary()[facet]751p = set(s0).difference(facet).pop()752q = set(s1).difference(facet).pop()753origin = pc.point(p).reduced_affine_vector()754base_indices = [ i for i in s0 if i!=p ]755base = matrix([ pc.point(i).reduced_affine_vector()-origin for i in base_indices ])756sol = base.solve_left( pc.point(q).reduced_affine_vector()-origin )757relation = [0]*pc.n_points()758relation[p] = sum(sol)-1759relation[q] = 1760for i, base_i in enumerate(base_indices):761relation[base_i] = -sol[i]762rel_denom = lcm([QQ(r).denominator() for r in relation])763relation = [ ZZ(r*rel_denom) for r in relation ]764ex = Linear_Expression(relation,0)765cs.insert(ex >= 0)766from sage.modules.free_module import FreeModule767ambient = FreeModule(ZZ, self.point_configuration().n_points())768if cs.empty():769cone = C_Polyhedron(ambient.dimension(), 'universe')770else:771cone = C_Polyhedron(cs)772from sage.geometry.cone import _Cone_from_PPL773return _Cone_from_PPL(cone, lattice=ambient)774775776777778779