Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/geometry/cone.py
4057 views
1
r"""
2
Convex rational polyhedral cones
3
4
This module was designed as a part of framework for toric varieties
5
(:mod:`~sage.schemes.toric.variety`,
6
:mod:`~sage.schemes.toric.fano_variety`). While the emphasis is on
7
strictly convex cones, non-strictly convex cones are supported as well. Work
8
with distinct lattices (in the sense of discrete subgroups spanning vector
9
spaces) is supported. The default lattice is :class:`ToricLattice
10
<sage.geometry.toric_lattice.ToricLatticeFactory>` `N` of the appropriate
11
dimension. The only case when you must specify lattice explicitly is creation
12
of a 0-dimensional cone, where dimension of the ambient space cannot be
13
guessed.
14
15
AUTHORS:
16
17
- Andrey Novoseltsev (2010-05-13): initial version.
18
19
- Andrey Novoseltsev (2010-06-17): substantial improvement during review by
20
Volker Braun.
21
22
- Volker Braun (2010-06-21): various spanned/quotient/dual lattice
23
computations added.
24
25
- Volker Braun (2010-12-28): Hilbert basis for cones.
26
27
EXAMPLES:
28
29
Use :func:`Cone` to construct cones::
30
31
sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
32
sage: halfspace = Cone([(1,0,0), (0,1,0), (-1,-1,0), (0,0,1)])
33
sage: positive_xy = Cone([(1,0,0), (0,1,0)])
34
sage: four_rays = Cone([(1,1,1), (1,-1,1), (-1,-1,1), (-1,1,1)])
35
36
For all of the cones above we have provided primitive generating rays, but in
37
fact this is not necessary - a cone can be constructed from any collection of
38
rays (from the same space, of course). If there are non-primitive (or even
39
non-integral) rays, they will be replaced with primitive ones. If there are
40
extra rays, they will be discarded. Of course, this means that :func:`Cone`
41
has to do some work before actually constructing the cone and sometimes it is
42
not desirable, if you know for sure that your input is already "good". In this
43
case you can use options ``check=False`` to force :func:`Cone` to use
44
exactly the directions that you have specified and ``normalize=False`` to
45
force it to use exactly the rays that you have specified. However, it is
46
better not to use these possibilities without necessity, since cones are
47
assumed to be represented by a minimal set of primitive generating rays.
48
See :func:`Cone` for further documentation on construction.
49
50
Once you have a cone, you can perform numerous operations on it. The most
51
important ones are, probably, ray accessing methods::
52
53
sage: halfspace.rays()
54
(N(0, 0, 1), N(0, 1, 0), N(0, -1, 0), N(1, 0, 0), N(-1, 0, 0))
55
sage: halfspace.ray(3)
56
N(1, 0, 0)
57
sage: halfspace.ray_matrix()
58
[ 0 0 0 1 -1]
59
[ 0 1 -1 0 0]
60
[ 1 0 0 0 0]
61
sage: halfspace.ray_set()
62
frozenset([N(1, 0, 0), N(-1, 0, 0), N(0, 1, 0), N(0, 0, 1), N(0, -1, 0)])
63
64
If you want to do something with each ray of a cone, you can write ::
65
66
sage: for ray in positive_xy: print ray
67
N(1, 0, 0)
68
N(0, 1, 0)
69
70
You can also get an iterator over only some selected rays::
71
72
sage: for ray in halfspace.ray_iterator([1,2,1]): print ray
73
N(0, 1, 0)
74
N(0, -1, 0)
75
N(0, 1, 0)
76
77
There are two dimensions associated to each cone - the dimension of the
78
subspace spanned by the cone and the dimension of the space where it lives::
79
80
sage: positive_xy.dim()
81
2
82
sage: positive_xy.lattice_dim()
83
3
84
85
You also may be interested in this dimension::
86
87
sage: dim(positive_xy.linear_subspace())
88
0
89
sage: dim(halfspace.linear_subspace())
90
2
91
92
Or, perhaps, all you care about is whether it is zero or not::
93
94
sage: positive_xy.is_strictly_convex()
95
True
96
sage: halfspace.is_strictly_convex()
97
False
98
99
You can also perform these checks::
100
101
sage: positive_xy.is_simplicial()
102
True
103
sage: four_rays.is_simplicial()
104
False
105
sage: positive_xy.is_smooth()
106
True
107
108
You can work with subcones that form faces of other cones::
109
110
sage: face = four_rays.faces(dim=2)[0]
111
sage: face
112
2-d face of 3-d cone in 3-d lattice N
113
sage: face.rays()
114
(N(1, 1, 1), N(1, -1, 1))
115
sage: face.ambient_ray_indices()
116
(0, 1)
117
sage: four_rays.rays(face.ambient_ray_indices())
118
(N(1, 1, 1), N(1, -1, 1))
119
120
If you need to know inclusion relations between faces, you can use ::
121
122
sage: L = four_rays.face_lattice()
123
sage: map(len, L.level_sets())
124
[1, 4, 4, 1]
125
sage: face = L.level_sets()[2][0]
126
sage: face.element.rays()
127
(N(1, 1, 1), N(1, -1, 1))
128
sage: L.hasse_diagram().neighbors_in(face)
129
[1-d face of 3-d cone in 3-d lattice N,
130
1-d face of 3-d cone in 3-d lattice N]
131
132
.. WARNING::
133
134
As you can see in the above example, the order of faces in level sets of
135
the face lattice may differ from the order of faces returned by
136
:meth:`~ConvexRationalPolyhedralCone.faces`. While the first order is
137
random, the latter one ensures that one-dimensional faces are listed in
138
the same order as generating rays.
139
140
When all the functionality provided by cones is not enough, you may want to
141
check if you can do necessary things using lattice polytopes and polyhedra
142
corresponding to cones::
143
144
sage: four_rays.lattice_polytope()
145
A lattice polytope: 3-dimensional, 5 vertices.
146
sage: four_rays.polyhedron()
147
A 3-dimensional polyhedron in QQ^3 defined as
148
the convex hull of 1 vertex and 4 rays
149
150
And of course you are always welcome to suggest new features that should be
151
added to cones!
152
153
REFERENCES:
154
155
.. [Fulton]
156
Wiliam Fulton, "Introduction to Toric Varieties", Princeton
157
University Press
158
"""
159
160
#*****************************************************************************
161
# Copyright (C) 2010 Volker Braun <[email protected]>
162
# Copyright (C) 2010 Andrey Novoseltsev <[email protected]>
163
# Copyright (C) 2010 William Stein <[email protected]>
164
#
165
#
166
# Distributed under the terms of the GNU General Public License (GPL)
167
#
168
# http://www.gnu.org/licenses/
169
#*****************************************************************************
170
171
172
import collections
173
import copy
174
import warnings
175
176
from sage.combinat.posets.posets import FinitePoset
177
from sage.geometry.lattice_polytope import LatticePolytope, integral_length
178
from sage.geometry.polyhedron.constructor import Polyhedron
179
from sage.geometry.polyhedron.base import is_Polyhedron
180
from sage.geometry.hasse_diagram import Hasse_diagram_from_incidences
181
from sage.geometry.toric_lattice import ToricLattice, is_ToricLattice, \
182
is_ToricLatticeQuotient
183
from sage.geometry.toric_plotter import ToricPlotter, label_list
184
from sage.graphs.all import DiGraph
185
from sage.matrix.all import matrix, identity_matrix
186
from sage.misc.all import flatten, latex, prod
187
from sage.modules.all import span, vector
188
from sage.rings.all import QQ, RR, ZZ, gcd
189
from sage.structure.all import SageObject
190
from sage.structure.coerce import parent
191
from sage.libs.ppl import C_Polyhedron, Generator_System, Constraint_System, \
192
Linear_Expression, ray as PPL_ray, point as PPL_point, \
193
Poly_Con_Relation
194
from sage.geometry.integral_points import parallelotope_points
195
196
197
def is_Cone(x):
198
r"""
199
Check if ``x`` is a cone.
200
201
INPUT:
202
203
- ``x`` -- anything.
204
205
OUTPUT:
206
207
- ``True`` if ``x`` is a cone and ``False`` otherwise.
208
209
EXAMPLES::
210
211
sage: from sage.geometry.cone import is_Cone
212
sage: is_Cone(1)
213
False
214
sage: quadrant = Cone([(1,0), (0,1)])
215
sage: quadrant
216
2-d cone in 2-d lattice N
217
sage: is_Cone(quadrant)
218
True
219
"""
220
return isinstance(x, ConvexRationalPolyhedralCone)
221
222
223
def Cone(rays, lattice=None, check=True, normalize=True):
224
r"""
225
Construct a (not necessarily strictly) convex rational polyhedral cone.
226
227
INPUT:
228
229
- ``rays`` -- a list of rays. Each ray should be given as a list
230
or a vector convertible to the rational extension of the given
231
``lattice``. May also be specified by a
232
:class:`~sage.geometry.polyhedron.base.Polyhedron_base` object;
233
234
- ``lattice`` -- :class:`ToricLattice
235
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
236
other object that behaves like these. If not specified, an attempt will
237
be made to determine an appropriate toric lattice automatically;
238
239
- ``check`` -- by default the input data will be checked for
240
correctness (e.g. that all rays have the same number of
241
components) and generating rays will be constructed from
242
``rays``. If you know that the input is a minimal set of
243
generators of a valid cone, you may significantly decrease
244
construction time using ``check=False`` option;
245
246
- ``normalize`` -- you can further speed up construction using
247
``normalize=False`` option. In this case ``rays`` must be a list of
248
immutable primitive rays in ``lattice``. In general, you should not use
249
this option, it is designed for code optimization and does not give as
250
drastic improvement in speed as the previous one.
251
252
OUTPUT:
253
254
- convex rational polyhedral cone determined by ``rays``.
255
256
EXAMPLES:
257
258
Let's define a cone corresponding to the first quadrant of the plane
259
(note, you can even mix objects of different types to represent rays, as
260
long as you let this function to perform all the checks and necessary
261
conversions!)::
262
263
sage: quadrant = Cone([(1,0), [0,1]])
264
sage: quadrant
265
2-d cone in 2-d lattice N
266
sage: quadrant.rays()
267
(N(1, 0), N(0, 1))
268
269
You may prefer to use :meth:`~IntegralRayCollection.ray_matrix` when you
270
want to take a look at rays, they will be given as columns::
271
272
sage: quadrant.ray_matrix()
273
[1 0]
274
[0 1]
275
276
If you give more rays than necessary, the extra ones will be discarded::
277
278
sage: Cone([(1,0), (0,1), (1,1), (0,1)]).rays()
279
(N(0, 1), N(1, 0))
280
281
However, this work is not done with ``check=False`` option, so use it
282
carefully! ::
283
284
sage: Cone([(1,0), (0,1), (1,1), (0,1)], check=False).rays()
285
(N(1, 0), N(0, 1), N(1, 1), N(0, 1))
286
287
Even worse things can happen with ``normalize=False`` option::
288
289
sage: Cone([(1,0), (0,1)], check=False, normalize=False)
290
Traceback (most recent call last):
291
...
292
AttributeError: 'tuple' object has no attribute 'parent'
293
294
You can construct different "not" cones: not full-dimensional, not
295
strictly convex, not containing any rays::
296
297
sage: one_dimensional_cone = Cone([(1,0)])
298
sage: one_dimensional_cone.dim()
299
1
300
sage: half_plane = Cone([(1,0), (0,1), (-1,0)])
301
sage: half_plane.rays()
302
(N(0, 1), N(1, 0), N(-1, 0))
303
sage: half_plane.is_strictly_convex()
304
False
305
sage: origin = Cone([(0,0)])
306
sage: origin.rays()
307
()
308
sage: origin.dim()
309
0
310
sage: origin.lattice_dim()
311
2
312
313
You may construct the cone above without giving any rays, but in this case
314
you must provide lattice explicitly::
315
316
sage: origin = Cone([])
317
Traceback (most recent call last):
318
...
319
ValueError: lattice must be given explicitly if there are no rays!
320
sage: origin = Cone([], lattice=ToricLattice(2))
321
sage: origin.dim()
322
0
323
sage: origin.lattice_dim()
324
2
325
sage: origin.lattice()
326
2-d lattice N
327
328
Of course, you can also provide lattice in other cases::
329
330
sage: L = ToricLattice(3, "L")
331
sage: c1 = Cone([(1,0,0),(1,1,1)], lattice=L)
332
sage: c1.rays()
333
(L(1, 0, 0), L(1, 1, 1))
334
335
Or you can construct cones from rays of a particular lattice::
336
337
sage: ray1 = L(1,0,0)
338
sage: ray2 = L(1,1,1)
339
sage: c2 = Cone([ray1, ray2])
340
sage: c2.rays()
341
(L(1, 0, 0), L(1, 1, 1))
342
sage: c1 == c2
343
True
344
345
When the cone in question is not strictly convex, the standard form for
346
the "generating rays" of the linear subspace is "basis vectors and their
347
negatives", as in the following example::
348
349
sage: plane = Cone([(1,0), (0,1), (-1,-1)])
350
sage: plane.ray_matrix()
351
[ 0 0 1 -1]
352
[ 1 -1 0 0]
353
354
The cone can also be specified by a
355
:class:`~sage.geometry.polyhedron.base.Polyhedron_base`::
356
357
sage: p = plane.polyhedron()
358
sage: Cone(p)
359
2-d cone in 2-d lattice N
360
sage: Cone(p) == plane
361
True
362
363
TESTS::
364
365
sage: N = ToricLattice(2)
366
sage: Nsub = N.span([ N(1,2) ])
367
sage: Cone(Nsub.basis())
368
1-d cone in Sublattice <N(1, 2)>
369
sage: Cone([N(0)])
370
0-d cone in 2-d lattice N
371
"""
372
# Cone from Polyhedron
373
if is_Polyhedron(rays):
374
polyhedron = rays
375
if lattice is None:
376
lattice = ToricLattice(polyhedron.ambient_dim())
377
if polyhedron.n_vertices() > 1:
378
raise ValueError("%s is not a cone!" % polyhedron)
379
apex = polyhedron.vertices()[0]
380
if apex.count(0) != len(apex):
381
raise ValueError("the apex of %s is not at the origin!"
382
% polyhedron)
383
rays = normalize_rays(polyhedron.rays(), lattice)
384
strict_rays = tuple(rays)
385
lines = tuple(normalize_rays(polyhedron.lines(), lattice))
386
for line in lines:
387
rays.append(line)
388
rays.append(-line)
389
rays[-1].set_immutable()
390
cone = ConvexRationalPolyhedralCone(rays, lattice)
391
# Save constructed stuff for later use
392
cone._polyhedron = polyhedron
393
cone._strict_rays = strict_rays
394
cone._lines = lines
395
return cone
396
# Cone from rays
397
if check or normalize:
398
rays = normalize_rays(rays, lattice)
399
if lattice is None:
400
if rays:
401
lattice = rays[0].parent()
402
else:
403
raise ValueError(
404
"lattice must be given explicitly if there are no rays!")
405
if not check or not rays:
406
return ConvexRationalPolyhedralCone(rays, lattice)
407
# Any set of rays forms a cone, but we want to keep only generators
408
if is_ToricLatticeQuotient(lattice):
409
gs = Generator_System(
410
PPL_point(Linear_Expression(lattice(0).vector(), 0)))
411
for r in rays:
412
if not r.is_zero():
413
gs.insert(PPL_ray(Linear_Expression(r.vector(), 0)))
414
else:
415
gs = Generator_System( PPL_point(Linear_Expression(lattice(0),0)) )
416
for r in rays:
417
if not r.is_zero():
418
gs.insert( PPL_ray(Linear_Expression(r,0)) )
419
cone = C_Polyhedron(gs)
420
return _Cone_from_PPL(cone, lattice, rays)
421
422
423
def _Cone_from_PPL(cone, lattice, original_rays=None):
424
"""
425
Construct a cone from a :class:`~sage.libs.ppl.Polyhedron`.
426
427
This is a private function and not intended to be exposed to the
428
end user. It is used internally by :func:`Cone` and in
429
:meth:`ConvexRationalPolyhedralCone.intersection`.
430
431
INPUT:
432
433
- ``cone`` -- a :class:`~sage.libs.ppl.Polyhedron` having the
434
origin as its single point.
435
436
- ``lattice`` -- :class:`ToricLattice
437
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
438
other object that behaves like these.
439
440
- ``original_rays`` -- (default: ``None``) if given, must be a minimal list
441
of normalized generating rays of ``cone``. If ``cone`` is strictly convex
442
and ``original_rays`` were given, they will be used as internal rays of
443
the constructed cone, in the given order.
444
445
OUTPUT:
446
447
A :class:`ConvexRationalPolyhedralCone`.
448
449
TESTS::
450
451
sage: Cone([(1,0), (0,1), (1,1), (0,1)]).rays() # indirect doctest
452
(N(0, 1), N(1, 0))
453
"""
454
rays = []
455
lines = []
456
for g in cone.minimized_generators():
457
ray = lattice(g.coefficients())
458
ray.set_immutable()
459
if g.is_ray():
460
rays.append(ray)
461
if g.is_line():
462
lines.append(ray)
463
negative_ray = -ray
464
negative_ray.set_immutable()
465
lines.append(negative_ray)
466
if (original_rays is not None and not lines and
467
len(rays) == len(original_rays)):
468
return ConvexRationalPolyhedralCone(original_rays, lattice, PPL=cone)
469
else:
470
return ConvexRationalPolyhedralCone(rays + lines, lattice, PPL=cone)
471
472
473
def normalize_rays(rays, lattice):
474
r"""
475
Normalize a list of rational rays: make them primitive and immutable.
476
477
INPUT:
478
479
- ``rays`` -- list of rays which can be converted to the rational
480
extension of ``lattice``;
481
482
- ``lattice`` -- :class:`ToricLattice
483
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
484
other object that behaves like these. If ``None``, an attempt will
485
be made to determine an appropriate toric lattice automatically.
486
487
OUTPUT:
488
489
- list of immutable primitive vectors of the ``lattice`` in the same
490
directions as original ``rays``.
491
492
EXAMPLES::
493
494
sage: from sage.geometry.cone import normalize_rays
495
sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], None)
496
[N(0, 1), N(0, 1), N(3, 2), N(3, 14)]
497
sage: L = ToricLattice(2, "L")
498
sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], L.dual())
499
[L*(0, 1), L*(0, 1), L*(3, 2), L*(3, 14)]
500
sage: ray_in_L = L(0,1)
501
sage: normalize_rays([ray_in_L, (0, 2), (3, 2), (5/7, 10/3)], None)
502
[L(0, 1), L(0, 1), L(3, 2), L(3, 14)]
503
sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], ZZ^2)
504
[(0, 1), (0, 1), (3, 2), (3, 14)]
505
sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], ZZ^3)
506
Traceback (most recent call last):
507
...
508
TypeError: cannot convert (0, 1) to
509
Vector space of dimension 3 over Rational Field!
510
sage: normalize_rays([], ZZ^3)
511
[]
512
"""
513
if rays is None:
514
rays = []
515
try:
516
rays = list(rays)
517
except TypeError:
518
raise TypeError(
519
"rays must be given as a list or a compatible structure!"
520
"\nGot: %s" % rays)
521
if rays:
522
if lattice is None:
523
ray_parent = parent(rays[0])
524
lattice = (ray_parent if is_ToricLattice(ray_parent)
525
else ToricLattice(len(rays[0])))
526
# Are we dealing with a quotient lattice?
527
try:
528
if not lattice.is_torsion_free():
529
raise ValueError("cannot normalize rays of torsion quotients!")
530
except AttributeError:
531
pass
532
V = lattice.base_extend(QQ)
533
for n, ray in enumerate(rays):
534
try:
535
ray = V(ray)
536
except TypeError:
537
raise TypeError("cannot convert %s to %s!" % (ray, V))
538
if ray.is_zero():
539
ray = lattice(0)
540
else:
541
ray = lattice(ray / integral_length(ray))
542
ray.set_immutable()
543
rays[n] = ray
544
return rays
545
546
547
class IntegralRayCollection(SageObject,
548
collections.Hashable,
549
collections.Iterable):
550
r"""
551
Create a collection of integral rays.
552
553
.. WARNING::
554
555
No correctness check or normalization is performed on the input data.
556
This class is designed for internal operations and you probably should
557
not use it directly.
558
559
This is a base class for :class:`convex rational polyhedral cones
560
<ConvexRationalPolyhedralCone>` and :class:`fans
561
<sage.geometry.fan.RationalPolyhedralFan>`.
562
563
Ray collections are immutable, but they cache most of the returned values.
564
565
INPUT:
566
567
- ``rays`` -- list of immutable vectors in ``lattice``;
568
569
- ``lattice`` -- :class:`ToricLattice
570
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
571
other object that behaves like these. If ``None``, it will be determined
572
as :func:`parent` of the first ray. Of course, this cannot be done if
573
there are no rays, so in this case you must give an appropriate
574
``lattice`` directly. Note that ``None`` is *not* the default value -
575
you always *must* give this argument explicitly, even if it is ``None``.
576
577
OUTPUT:
578
579
- collection of given integral rays.
580
"""
581
582
def __init__(self, rays, lattice):
583
r"""
584
See :class:`IntegralRayCollection` for documentation.
585
586
TESTS::
587
588
sage: from sage.geometry.cone import (
589
... IntegralRayCollection)
590
sage: v = vector([1,0])
591
sage: v.set_immutable()
592
sage: c = IntegralRayCollection([v], ZZ^2)
593
sage: c = IntegralRayCollection([v], None)
594
sage: c.lattice() # Determined automatically
595
Ambient free module of rank 2
596
over the principal ideal domain Integer Ring
597
sage: c.rays()
598
((1, 0),)
599
sage: TestSuite(c).run()
600
"""
601
self._rays = tuple(rays)
602
self._lattice = self._rays[0].parent() if lattice is None else lattice
603
604
def __cmp__(self, right):
605
r"""
606
Compare ``self`` and ``right``.
607
608
INPUT:
609
610
- ``right`` -- anything.
611
612
OUTPUT:
613
614
- 0 if ``right`` is of the same type as ``self``, they have the same
615
ambient lattices, and their rays are the same and listed in the same
616
order. 1 or -1 otherwise.
617
618
TESTS::
619
620
sage: c1 = Cone([(1,0), (0,1)])
621
sage: c2 = Cone([(0,1), (1,0)])
622
sage: c3 = Cone([(0,1), (1,0)])
623
sage: cmp(c1, c2)
624
1
625
sage: cmp(c2, c1)
626
-1
627
sage: cmp(c2, c3)
628
0
629
sage: c2 is c3
630
False
631
sage: cmp(c1, 1) * cmp(1, c1)
632
-1
633
"""
634
c = cmp(type(self), type(right))
635
if c:
636
return c
637
# We probably do need to have explicit comparison of lattices here
638
# since if one of the collections does not live in a toric lattice,
639
# comparison of rays may miss the difference.
640
return cmp((self.lattice(), self.rays()),
641
(right.lattice(), right.rays()))
642
643
def __hash__(self):
644
r"""
645
Return the hash of ``self`` computed from rays.
646
647
OUTPUT:
648
649
- integer.
650
651
TESTS::
652
653
sage: c = Cone([(1,0), (0,1)])
654
sage: hash(c) == hash(c)
655
True
656
"""
657
if "_hash" not in self.__dict__:
658
self._hash = hash(self._rays)
659
return self._hash
660
661
def __iter__(self):
662
r"""
663
Return an iterator over rays of ``self``.
664
665
OUTPUT:
666
667
- iterator.
668
669
TESTS::
670
671
sage: c = Cone([(1,0), (0,1)])
672
sage: for ray in c: print ray
673
N(1, 0)
674
N(0, 1)
675
"""
676
return self.ray_iterator()
677
678
def _ambient_space_point(self, data):
679
r"""
680
Try to convert ``data`` to a point of the ambient space of ``self``.
681
682
INPUT:
683
684
- ``data`` -- anything.
685
686
OUTPUT:
687
688
- integral, rational or numeric point of the ambient space of ``self``
689
if ``data`` were successfully interpreted in such a way, otherwise a
690
``TypeError`` exception is raised.
691
692
TESTS::
693
694
sage: c = Cone([(1,0), (0,1)])
695
sage: c._ambient_space_point([1,1])
696
N(1, 1)
697
sage: c._ambient_space_point(c.dual_lattice()([1,1]))
698
Traceback (most recent call last):
699
...
700
TypeError: the point M(1, 1) and
701
2-d cone in 2-d lattice N have incompatible lattices!
702
sage: c._ambient_space_point([1,1/3])
703
(1, 1/3)
704
sage: c._ambient_space_point([1/2,1/sqrt(3)])
705
(0.500000000000000, 0.577350269189626)
706
sage: c._ambient_space_point([1,1,3])
707
Traceback (most recent call last):
708
...
709
TypeError: [1, 1, 3] does not represent a valid point
710
in the ambient space of 2-d cone in 2-d lattice N!
711
"""
712
L = self.lattice()
713
try: # to make a lattice element...
714
return L(data)
715
except TypeError:
716
# Special treatment for toric lattice elements
717
if is_ToricLattice(parent(data)):
718
raise TypeError("the point %s and %s have incompatible "
719
"lattices!" % (data, self))
720
try: # ... or an exact point...
721
return L.base_extend(QQ)(data)
722
except TypeError:
723
pass
724
try: # ... or at least a numeric one
725
return L.base_extend(RR)(data)
726
except TypeError:
727
pass
728
# Raise TypeError with our own message
729
raise TypeError("%s does not represent a valid point in the ambient "
730
"space of %s!" % (data, self))
731
732
def cartesian_product(self, other, lattice=None):
733
r"""
734
Return the Cartesian product of ``self`` with ``other``.
735
736
INPUT:
737
738
- ``other`` -- an :class:`IntegralRayCollection`;
739
740
- ``lattice`` -- (optional) the ambient lattice for the result. By
741
default, the direct sum of the ambient lattices of ``self`` and
742
``other`` is constructed.
743
744
OUTPUT:
745
746
- an :class:`IntegralRayCollection`.
747
748
By the Cartesian product of ray collections `(r_0, \dots, r_{n-1})` and
749
`(s_0, \dots, s_{m-1})` we understand the ray collection of the form
750
`((r_0, 0), \dots, (r_{n-1}, 0), (0, s_0), \dots, (0, s_{m-1}))`, which
751
is suitable for Cartesian products of cones and fans. The ray order is
752
guaranteed to be as described.
753
754
EXAMPLES::
755
756
sage: c = Cone([(1,)])
757
sage: c.cartesian_product(c) # indirect doctest
758
2-d cone in 2-d lattice N+N
759
sage: _.rays()
760
(N+N(1, 0), N+N(0, 1))
761
"""
762
assert isinstance(other, IntegralRayCollection)
763
if lattice is None:
764
lattice = self.lattice().direct_sum(other.lattice())
765
suffix = [0] * other.lattice_dim()
766
rays = [lattice(list(r1) + suffix) for r1 in self.rays()]
767
prefix = [0] * self.lattice_dim()
768
rays.extend(lattice(prefix + list(r2)) for r2 in other.rays())
769
for r in rays:
770
r.set_immutable()
771
return IntegralRayCollection(rays, lattice)
772
773
def dim(self):
774
r"""
775
Return the dimension of the subspace spanned by rays of ``self``.
776
777
OUTPUT:
778
779
- integer.
780
781
EXAMPLES::
782
783
sage: c = Cone([(1,0)])
784
sage: c.lattice_dim()
785
2
786
sage: c.dim()
787
1
788
"""
789
if "_dim" not in self.__dict__:
790
self._dim = self.ray_matrix().rank()
791
return self._dim
792
793
def lattice(self):
794
r"""
795
Return the ambient lattice of ``self``.
796
797
OUTPUT:
798
799
- lattice.
800
801
EXAMPLES::
802
803
sage: c = Cone([(1,0)])
804
sage: c.lattice()
805
2-d lattice N
806
sage: Cone([], ZZ^3).lattice()
807
Ambient free module of rank 3
808
over the principal ideal domain Integer Ring
809
"""
810
return self._lattice
811
812
def dual_lattice(self):
813
r"""
814
Return the dual of the ambient lattice of ``self``.
815
816
OUTPUT:
817
818
- lattice. If possible (that is, if :meth:`lattice` has a
819
``dual()`` method), the dual lattice is returned. Otherwise,
820
`\ZZ^n` is returned, where `n` is the dimension of ``self``.
821
822
EXAMPLES::
823
824
sage: c = Cone([(1,0)])
825
sage: c.dual_lattice()
826
2-d lattice M
827
sage: Cone([], ZZ^3).dual_lattice()
828
Ambient free module of rank 3
829
over the principal ideal domain Integer Ring
830
"""
831
if '_dual_lattice' not in self.__dict__:
832
try:
833
self._dual_lattice = self.lattice().dual()
834
except AttributeError:
835
self._dual_lattice = ZZ**self.lattice_dim()
836
return self._dual_lattice
837
838
def lattice_dim(self):
839
r"""
840
Return the dimension of the ambient lattice of ``self``.
841
842
OUTPUT:
843
844
- integer.
845
846
EXAMPLES::
847
848
sage: c = Cone([(1,0)])
849
sage: c.lattice_dim()
850
2
851
sage: c.dim()
852
1
853
"""
854
return self.lattice().dimension()
855
856
def nrays(self):
857
r"""
858
Return the number of rays of ``self``.
859
860
OUTPUT:
861
862
- integer.
863
864
EXAMPLES::
865
866
sage: c = Cone([(1,0), (0,1)])
867
sage: c.nrays()
868
2
869
"""
870
return len(self._rays)
871
872
def plot(self, **options):
873
r"""
874
Plot ``self``.
875
876
INPUT:
877
878
- any options for toric plots (see :func:`toric_plotter.options
879
<sage.geometry.toric_plotter.options>`), none are mandatory.
880
881
OUTPUT:
882
883
- a plot.
884
885
EXAMPLES::
886
887
sage: quadrant = Cone([(1,0), (0,1)])
888
sage: quadrant.plot()
889
"""
890
tp = ToricPlotter(options, self.lattice().degree(), self.rays())
891
return tp.plot_lattice() + tp.plot_rays() + tp.plot_generators()
892
893
def ray(self, n):
894
r"""
895
Return the ``n``-th ray of ``self``.
896
897
INPUT:
898
899
- ``n`` -- integer, an index of a ray of ``self``. Enumeration of rays
900
starts with zero.
901
902
OUTPUT:
903
904
- ray, an element of the lattice of ``self``.
905
906
EXAMPLES::
907
908
sage: c = Cone([(1,0), (0,1)])
909
sage: c.ray(0)
910
N(1, 0)
911
"""
912
return self._rays[n]
913
914
def ray_iterator(self, ray_list=None):
915
r"""
916
Return an iterator over (some of) the rays of ``self``.
917
918
INPUT:
919
920
- ``ray_list`` -- list of integers, the indices of the requested rays.
921
If not specified, an iterator over all rays of ``self`` will be
922
returned.
923
924
OUTPUT:
925
926
- iterator.
927
928
EXAMPLES::
929
930
sage: c = Cone([(1,0), (0,1), (-1, 0)])
931
sage: for ray in c.ray_iterator(): print ray
932
N(0, 1)
933
N(1, 0)
934
N(-1, 0)
935
sage: for ray in c.ray_iterator([2,1]): print ray
936
N(-1, 0)
937
N(1, 0)
938
"""
939
if ray_list is None:
940
for ray in self._rays:
941
yield ray
942
else:
943
rays = self._rays
944
for n in ray_list:
945
yield rays[n]
946
947
def ray_matrix(self):
948
r"""
949
Return a matrix whose columns are rays of ``self``.
950
951
It can be convenient for linear algebra operations on rays, as well as
952
for easy-to-read output.
953
954
OUTPUT:
955
956
- matrix.
957
958
EXAMPLES::
959
960
sage: c = Cone([(1,0), (0,1), (-1, 0)])
961
sage: c.ray_matrix()
962
[ 0 1 -1]
963
[ 1 0 0]
964
"""
965
if "_ray_matrix" not in self.__dict__:
966
self._ray_matrix = matrix(ZZ, self.nrays(),
967
self.lattice().degree(), self._rays).transpose()
968
self._ray_matrix.set_immutable()
969
return self._ray_matrix
970
971
def ray_set(self):
972
r"""
973
Return rays of ``self`` as a :class:`frozenset`.
974
975
Use :meth:`rays` if you want to get rays in the fixed order.
976
977
OUTPUT:
978
979
- :class:`frozenset` of rays.
980
981
EXAMPLES::
982
983
sage: c = Cone([(1,0), (0,1), (-1, 0)])
984
sage: c.ray_set()
985
frozenset([N(0, 1), N(1, 0), N(-1, 0)])
986
"""
987
if "_ray_set" not in self.__dict__:
988
self._ray_set = frozenset(self._rays)
989
return self._ray_set
990
991
def rays(self, ray_list=None):
992
r"""
993
Return rays of ``self`` as a :class:`tuple`.
994
995
INPUT:
996
997
- ``ray_list`` -- list of integers, the indices of the requested rays.
998
If not specified, all rays of ``self`` will be returned. You may
999
want to use :meth:`ray_set` if you do not care about the order of
1000
rays. See also :meth:`ray_iterator`.
1001
1002
OUTPUT:
1003
1004
- :class:`tuple` of rays.
1005
1006
EXAMPLES::
1007
1008
sage: c = Cone([(1,0), (0,1), (-1, 0)])
1009
sage: c.rays()
1010
(N(0, 1), N(1, 0), N(-1, 0))
1011
sage: c.rays([0, 2])
1012
(N(0, 1), N(-1, 0))
1013
"""
1014
if ray_list is None:
1015
return self._rays
1016
else:
1017
return tuple(self.ray_iterator(ray_list))
1018
1019
def ray_basis(self):
1020
r"""
1021
Returns a linearly independent subset of the rays.
1022
1023
OUTPUT:
1024
1025
Returns a random but fixed choice of a `\QQ`-basis (of
1026
N-lattice points) for the vector space spanned by the rays.
1027
1028
.. NOTE::
1029
1030
See :meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.sublattice`
1031
if you need a `\ZZ`-basis.
1032
1033
EXAMPLES::
1034
1035
sage: c = Cone([(1,1,1,1), (1,-1,1,1), (-1,-1,1,1), (-1,1,1,1), (0,0,0,1)])
1036
sage: c.ray_basis()
1037
(N(1, 1, 1, 1), N(1, -1, 1, 1), N(-1, -1, 1, 1), N(0, 0, 0, 1))
1038
"""
1039
if "_ray_basis" not in self.__dict__:
1040
indices = self.ray_matrix().pivots()
1041
self._ray_basis = tuple(self.ray(i) for i in indices)
1042
return self._ray_basis
1043
1044
def ray_basis_matrix(self):
1045
r"""
1046
Returns a linearly independent subset of the rays as a matrix.
1047
1048
OUTPUT:
1049
1050
- Returns a random but fixed choice of a `\QQ`-basis (of
1051
N-lattice points) for the vector space spanned by the rays.
1052
1053
- The linearly independent rays are the columns of the returned matrix.
1054
1055
.. NOTE::
1056
1057
* see also :meth:`ray_basis`.
1058
1059
* See :meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.sublattice`
1060
if you need a `\ZZ`-basis.
1061
1062
EXAMPLES::
1063
1064
sage: c = Cone([(1,1,1,1), (1,-1,1,1), (-1,-1,1,1), (-1,1,1,1), (0,0,0,1)])
1065
sage: c.ray_basis_matrix()
1066
[ 1 1 -1 0]
1067
[ 1 -1 -1 0]
1068
[ 1 1 1 0]
1069
[ 1 1 1 1]
1070
"""
1071
basis = self.ray_basis()
1072
m = self._lattice.degree()
1073
n = len(basis)
1074
return matrix(ZZ, n, m, basis).transpose()
1075
1076
1077
# Derived classes MUST allow construction of their objects using ``ambient``
1078
# and ``ambient_ray_indices`` keyword parameters. See ``intersection`` method
1079
# for an example why this is needed.
1080
class ConvexRationalPolyhedralCone(IntegralRayCollection,
1081
collections.Container):
1082
r"""
1083
Create a convex rational polyhedral cone.
1084
1085
.. WARNING::
1086
1087
This class does not perform any checks of correctness of input nor
1088
does it convert input into the standard representation. Use
1089
:func:`Cone` to construct cones.
1090
1091
Cones are immutable, but they cache most of the returned values.
1092
1093
INPUT:
1094
1095
The input can be either:
1096
1097
- ``rays`` -- list of immutable primitive vectors in ``lattice``;
1098
1099
- ``lattice`` -- :class:`ToricLattice
1100
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
1101
other object that behaves like these. If ``None``, it will be determined
1102
as :func:`parent` of the first ray. Of course, this cannot be done if
1103
there are no rays, so in this case you must give an appropriate
1104
``lattice`` directly.
1105
1106
or (these parameters must be given as keywords):
1107
1108
- ``ambient`` -- ambient structure of this cone, a bigger :class:`cone
1109
<ConvexRationalPolyhedralCone>` or a :class:`fan
1110
<sage.geometry.fan.RationalPolyhedralFan>`, this cone *must be a face
1111
of* ``ambient``;
1112
1113
- ``ambient_ray_indices`` -- increasing list or tuple of integers, indices
1114
of rays of ``ambient`` generating this cone.
1115
1116
In both cases, the following keyword parameter may be specified in addition:
1117
1118
- ``PPL`` -- either ``None`` (default) or a
1119
:class:`~sage.libs.ppl.C_Polyhedron` representing the cone. This
1120
serves only to cache the polyhedral data if you know it
1121
already. The polyhedron will be set immutable.
1122
1123
OUTPUT:
1124
1125
- convex rational polyhedral cone.
1126
1127
.. NOTE::
1128
1129
Every cone has its ambient structure. If it was not specified, it is
1130
this cone itself.
1131
"""
1132
1133
def __init__(self, rays=None, lattice=None,
1134
ambient=None, ambient_ray_indices=None, PPL=None):
1135
r"""
1136
See :class:`ConvexRationalPolyhedralCone` for documentation.
1137
1138
TESTS::
1139
1140
sage: from sage.geometry.cone import (
1141
... ConvexRationalPolyhedralCone)
1142
sage: v1 = vector([1,0])
1143
sage: v2 = vector([0,1])
1144
sage: v1.set_immutable()
1145
sage: v2.set_immutable()
1146
sage: ac = ConvexRationalPolyhedralCone([v1, v2], ZZ^2)
1147
sage: ac = ConvexRationalPolyhedralCone([v1, v2], None)
1148
sage: ac.lattice() # Determined automatically
1149
Ambient free module of rank 2
1150
over the principal ideal domain Integer Ring
1151
sage: ac.rays()
1152
((1, 0), (0, 1))
1153
sage: ac.ambient() is ac
1154
True
1155
sage: TestSuite(ac).run()
1156
sage: sc = ConvexRationalPolyhedralCone(ambient=ac,
1157
... ambient_ray_indices=[1])
1158
sage: sc.rays()
1159
((0, 1),)
1160
sage: sc.ambient() is ac
1161
True
1162
sage: TestSuite(sc).run()
1163
"""
1164
superinit = super(ConvexRationalPolyhedralCone, self).__init__
1165
if ambient is None:
1166
superinit(rays, lattice)
1167
self._ambient = self
1168
self._ambient_ray_indices = tuple(range(self.nrays()))
1169
else:
1170
self._ambient = ambient
1171
self._ambient_ray_indices = tuple(ambient_ray_indices)
1172
superinit(ambient.rays(self._ambient_ray_indices),
1173
ambient.lattice())
1174
if not PPL is None:
1175
self._PPL_C_Polyhedron = PPL
1176
self._PPL_C_Polyhedron.set_immutable()
1177
1178
def _PPL_cone(self):
1179
r"""
1180
Returns the Parma Polyhedra Library (PPL) representation of the cone.
1181
1182
OUTPUT:
1183
1184
A :class:`~sage.libs.ppl.C_Polyhedron` representing the cone.
1185
1186
EXAMPLES::
1187
1188
sage: c = Cone([(1,0), (1,1), (0,1)])
1189
sage: c._PPL_cone()
1190
A 2-dimensional polyhedron in QQ^2 defined as
1191
the convex hull of 1 point, 2 rays
1192
sage: c._PPL_cone().minimized_generators()
1193
Generator_System {point(0/1, 0/1), ray(0, 1), ray(1, 0)}
1194
sage: c._PPL_cone().minimized_constraints()
1195
Constraint_System {x1>=0, x0>=0}
1196
1197
TESTS:
1198
1199
There are no empty cones, the origin always belongs to them::
1200
1201
sage: Cone([(0,0)])._PPL_cone()
1202
A 0-dimensional polyhedron in QQ^2
1203
defined as the convex hull of 1 point
1204
sage: Cone([], lattice=ToricLattice(2))._PPL_cone()
1205
A 0-dimensional polyhedron in QQ^2
1206
defined as the convex hull of 1 point
1207
"""
1208
if "_PPL_C_Polyhedron" not in self.__dict__:
1209
gs = Generator_System(
1210
PPL_point(Linear_Expression(self._lattice(0), 0)))
1211
for r in self.rays():
1212
gs.insert( PPL_ray(Linear_Expression(r,0)) )
1213
self._PPL_C_Polyhedron = C_Polyhedron(gs)
1214
self._PPL_C_Polyhedron.set_immutable()
1215
return self._PPL_C_Polyhedron
1216
1217
def __contains__(self, point):
1218
r"""
1219
Check if ``point`` is contained in ``self``.
1220
1221
See :meth:`_contains` (which is called by this function) for
1222
documentation.
1223
1224
TESTS::
1225
1226
sage: c = Cone([(1,0), (0,1)])
1227
sage: (1,1) in c
1228
True
1229
sage: [1,1] in c
1230
True
1231
sage: (-1,0) in c
1232
False
1233
"""
1234
return self._contains(point)
1235
1236
def __getstate__(self):
1237
r"""
1238
Return the dictionary that should be pickled.
1239
1240
OUTPUT:
1241
1242
- :class:`dict`.
1243
1244
TESTS::
1245
1246
sage: C = Cone([(1,0)])
1247
sage: C.face_lattice()
1248
Finite poset containing 2 elements
1249
sage: C._test_pickling()
1250
sage: C2 = loads(dumps(C)); C2
1251
1-d cone in 2-d lattice N
1252
sage: C2 == C
1253
True
1254
sage: C2 is C # Is this desirable?
1255
False
1256
"""
1257
state = copy.copy(self.__dict__)
1258
state.pop("_polyhedron", None) # Polyhedron is not picklable.
1259
state.pop("_lattice_polytope", None) # Just to save time and space.
1260
state.pop("_PPL_C_Polyhedron", None) # PPL is not picklable.
1261
1262
# TODO: do we want to keep the face lattice in the pickle?
1263
# Currently there is an unpickling loop if do:
1264
# Unpickling a cone C requires first to unpickle its face lattice.
1265
# The latter is a Poset which takes C among its arguments. Due
1266
# to UniqueRepresentation, this triggers a call to hash(C) which
1267
# itself depends on the attribute C._rays which have not yet
1268
# been unpickled. See ``explain_pickle(dumps(C))``.
1269
state.pop("_face_lattice", None)
1270
return state
1271
1272
def _contains(self, point, region='whole cone'):
1273
r"""
1274
Check if ``point`` is contained in ``self``.
1275
1276
This function is called by :meth:`__contains__` and :meth:`contains`
1277
to ensure the same call depth for warning messages.
1278
1279
INPUT:
1280
1281
- ``point`` -- anything. An attempt will be made to convert it into a
1282
single element of the ambient space of ``self``. If it fails,
1283
``False`` is returned;
1284
1285
- ``region`` -- string. Can be either 'whole cone' (default),
1286
'interior', or 'relative interior'. By default, a point on
1287
the boundary of the cone is considered part of the cone. If
1288
you want to test whether the **interior** of the cone
1289
contains the point, you need to pass the optional argument
1290
``'interior'``. If you want to test whether the **relative
1291
interior** of the cone contains the point, you need to pass
1292
the optional argument ``'relative_interior'``.
1293
1294
OUTPUT:
1295
1296
- ``True`` if ``point`` is contained in the specified ``region`` of
1297
``self``, ``False`` otherwise.
1298
1299
Raises a ``ValueError`` if ``region`` is not one of the
1300
three allowed values.
1301
1302
TESTS::
1303
1304
sage: c = Cone([(1,0), (0,1)])
1305
sage: c._contains((1,1))
1306
True
1307
"""
1308
try:
1309
point = self._ambient_space_point(point)
1310
except TypeError, ex:
1311
if str(ex).endswith("have incompatible lattices!"):
1312
warnings.warn("you have checked if a cone contains a point "
1313
"from an incompatible lattice, this is False!",
1314
stacklevel=3)
1315
return False
1316
1317
if region not in ("whole cone", "relative interior", "interior"):
1318
raise ValueError("%s is an unknown region of the cone!" % region)
1319
if region == "interior" and self.dim() < self.lattice_dim():
1320
return False
1321
need_strict = region.endswith("interior")
1322
M = self.dual_lattice()
1323
for c in self._PPL_cone().minimized_constraints():
1324
pr = M(c.coefficients()) * point
1325
if c.is_equality():
1326
if pr != 0:
1327
return False
1328
elif pr < 0 or need_strict and pr == 0:
1329
return False
1330
return True
1331
1332
def interior_contains(self, *args):
1333
r"""
1334
Check if a given point is contained in the interior of ``self``.
1335
1336
For a cone of strictly lower-dimension than the ambient space,
1337
the interior is always empty. You probably want to use
1338
:meth:`relative_interior_contains` in this case.
1339
1340
INPUT:
1341
1342
- anything. An attempt will be made to convert all arguments into a
1343
single element of the ambient space of ``self``. If it fails,
1344
``False`` will be returned.
1345
1346
OUTPUT:
1347
1348
- ``True`` if the given point is contained in the interior of
1349
``self``, ``False`` otherwise.
1350
1351
EXAMPLES::
1352
1353
sage: c = Cone([(1,0), (0,1)])
1354
sage: c.contains((1,1))
1355
True
1356
sage: c.interior_contains((1,1))
1357
True
1358
sage: c.contains((1,0))
1359
True
1360
sage: c.interior_contains((1,0))
1361
False
1362
"""
1363
point = flatten(args)
1364
if len(point) == 1:
1365
point = point[0]
1366
return self._contains(point, 'interior')
1367
1368
def relative_interior_contains(self, *args):
1369
r"""
1370
Check if a given point is contained in the relative interior of ``self``.
1371
1372
For a full-dimensional cone the relative interior is simply
1373
the interior, so this method will do the same check as
1374
:meth:`interior_contains`. For a strictly lower-dimensional cone, the
1375
relative interior is the cone without its facets.
1376
1377
INPUT:
1378
1379
- anything. An attempt will be made to convert all arguments into a
1380
single element of the ambient space of ``self``. If it fails,
1381
``False`` will be returned.
1382
1383
OUTPUT:
1384
1385
- ``True`` if the given point is contained in the relative
1386
interior of ``self``, ``False`` otherwise.
1387
1388
EXAMPLES::
1389
1390
sage: c = Cone([(1,0,0), (0,1,0)])
1391
sage: c.contains((1,1,0))
1392
True
1393
sage: c.relative_interior_contains((1,1,0))
1394
True
1395
sage: c.interior_contains((1,1,0))
1396
False
1397
sage: c.contains((1,0,0))
1398
True
1399
sage: c.relative_interior_contains((1,0,0))
1400
False
1401
sage: c.interior_contains((1,0,0))
1402
False
1403
"""
1404
point = flatten(args)
1405
if len(point) == 1:
1406
point = point[0]
1407
return self._contains(point, 'relative interior')
1408
1409
def cartesian_product(self, other, lattice=None):
1410
r"""
1411
Return the Cartesian product of ``self`` with ``other``.
1412
1413
INPUT:
1414
1415
- ``other`` -- a :class:`cone <ConvexRationalPolyhedralCone>`;
1416
1417
- ``lattice`` -- (optional) the ambient lattice for the
1418
Cartesian product cone. By default, the direct sum of the
1419
ambient lattices of ``self`` and ``other`` is constructed.
1420
1421
OUTPUT:
1422
1423
- a :class:`cone <ConvexRationalPolyhedralCone>`.
1424
1425
EXAMPLES::
1426
1427
sage: c = Cone([(1,)])
1428
sage: c.cartesian_product(c)
1429
2-d cone in 2-d lattice N+N
1430
sage: _.rays()
1431
(N+N(1, 0), N+N(0, 1))
1432
"""
1433
assert is_Cone(other)
1434
rc = super(ConvexRationalPolyhedralCone, self).cartesian_product(
1435
other, lattice)
1436
return ConvexRationalPolyhedralCone(rc.rays(), rc.lattice())
1437
1438
def __cmp__(self, right):
1439
r"""
1440
Compare ``self`` and ``right``.
1441
1442
INPUT:
1443
1444
- ``right`` -- anything.
1445
1446
OUTPUT:
1447
1448
- 0 if ``self`` and ``right`` are cones of any kind in the same
1449
lattice with the same rays listed in the same order. 1 or -1
1450
otherwise.
1451
1452
TESTS::
1453
1454
sage: c1 = Cone([(1,0), (0,1)])
1455
sage: c2 = Cone([(0,1), (1,0)])
1456
sage: c3 = Cone([(0,1), (1,0)])
1457
sage: cmp(c1, c2)
1458
1
1459
sage: cmp(c2, c1)
1460
-1
1461
sage: cmp(c2, c3)
1462
0
1463
sage: c2 is c3
1464
False
1465
sage: cmp(c1, 1) * cmp(1, c1)
1466
-1
1467
"""
1468
if is_Cone(right):
1469
# We don't care about particular type of right in this case
1470
return cmp((self.lattice(), self.rays()),
1471
(right.lattice(), right.rays()))
1472
else:
1473
return cmp(type(self), type(right))
1474
1475
def _latex_(self):
1476
r"""
1477
Return a LaTeX representation of ``self``.
1478
1479
OUTPUT:
1480
1481
- string.
1482
1483
TESTS::
1484
1485
sage: quadrant = Cone([(1,0), (0,1)])
1486
sage: quadrant._latex_()
1487
'\\sigma^{2}'
1488
sage: quadrant.facets()[0]._latex_()
1489
'\\sigma^{1} \\subset \\sigma^{2}'
1490
"""
1491
if self.ambient() is self:
1492
return r"\sigma^{%d}" % self.dim()
1493
else:
1494
return r"\sigma^{%d} \subset %s" % (self.dim(),
1495
latex(self.ambient()))
1496
1497
def _repr_(self):
1498
r"""
1499
Return a string representation of ``self``.
1500
1501
OUTPUT:
1502
1503
- string.
1504
1505
TESTS::
1506
1507
sage: quadrant = Cone([(1,0), (0,1)])
1508
sage: quadrant._repr_()
1509
'2-d cone in 2-d lattice N'
1510
sage: quadrant
1511
2-d cone in 2-d lattice N
1512
sage: quadrant.facets()[0]
1513
1-d face of 2-d cone in 2-d lattice N
1514
"""
1515
result = "%d-d" % self.dim()
1516
if self.ambient() is self:
1517
result += " cone in"
1518
if is_ToricLattice(self.lattice()):
1519
result += " %s" % self.lattice()
1520
else:
1521
result += " %d-d lattice" % self.lattice_dim()
1522
else:
1523
result += " face of %s" % self.ambient()
1524
return result
1525
1526
def _sort_faces(self, faces):
1527
r"""
1528
Return sorted (if necessary) ``faces`` as a tuple.
1529
1530
This function ensures that one-dimensional faces are listed in
1531
agreement with the order of corresponding rays.
1532
1533
INPUT:
1534
1535
- ``faces`` -- iterable of :class:`cones
1536
<ConvexRationalPolyhedralCone>`.
1537
1538
OUTPUT:
1539
1540
- :class:`tuple` of :class:`cones <ConvexRationalPolyhedralCone>`.
1541
1542
TESTS::
1543
1544
sage: octant = Cone(identity_matrix(3).columns())
1545
sage: # indirect doctest
1546
sage: for i, face in enumerate(octant.faces(1)):
1547
... if face.ray(0) != octant.ray(i):
1548
... print "Wrong order!"
1549
"""
1550
faces = tuple(faces)
1551
if len(faces) > 1: # Otherwise there is nothing to sort
1552
if faces[0].nrays() == 1:
1553
faces = tuple(sorted(faces,
1554
key=lambda f: f._ambient_ray_indices))
1555
return faces
1556
1557
def adjacent(self):
1558
r"""
1559
Return faces adjacent to ``self`` in the ambient face lattice.
1560
1561
Two *distinct* faces `F_1` and `F_2` of the same face lattice are
1562
**adjacent** if all of the following conditions hold:
1563
1564
* `F_1` and `F_2` have the same dimension `d`;
1565
1566
* `F_1` and `F_2` share a facet of dimension `d-1`;
1567
1568
* `F_1` and `F_2` are facets of some face of dimension `d+1`, unless
1569
`d` is the dimension of the ambient structure.
1570
1571
OUTPUT:
1572
1573
- :class:`tuple` of :class:`cones <ConvexRationalPolyhedralCone>`.
1574
1575
EXAMPLES::
1576
1577
sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
1578
sage: octant.adjacent()
1579
()
1580
sage: one_face = octant.faces(1)[0]
1581
sage: len(one_face.adjacent())
1582
2
1583
sage: one_face.adjacent()[1]
1584
1-d face of 3-d cone in 3-d lattice N
1585
1586
Things are a little bit subtle with fans, as we illustrate below.
1587
1588
First, we create a fan from two cones in the plane::
1589
1590
sage: fan = Fan(cones=[(0,1), (1,2)],
1591
... rays=[(1,0), (0,1), (-1,0)])
1592
sage: cone = fan.generating_cone(0)
1593
sage: len(cone.adjacent())
1594
1
1595
1596
The second generating cone is adjacent to this one. Now we create the
1597
same fan, but embedded into the 3-dimensional space::
1598
1599
sage: fan = Fan(cones=[(0,1), (1,2)],
1600
... rays=[(1,0,0), (0,1,0), (-1,0,0)])
1601
sage: cone = fan.generating_cone(0)
1602
sage: len(cone.adjacent())
1603
1
1604
1605
The result is as before, since we still have::
1606
1607
sage: fan.dim()
1608
2
1609
1610
Now we add another cone to make the fan 3-dimensional::
1611
1612
sage: fan = Fan(cones=[(0,1), (1,2), (3,)],
1613
... rays=[(1,0,0), (0,1,0), (-1,0,0), (0,0,1)])
1614
sage: cone = fan.generating_cone(0)
1615
sage: len(cone.adjacent())
1616
0
1617
1618
Since now ``cone`` has smaller dimension than ``fan``, it and its
1619
adjacent cones must be facets of a bigger one, but since ``cone``
1620
in this example is generating, it is not contained in any other.
1621
"""
1622
if "_adjacent" not in self.__dict__:
1623
L = self._ambient._face_lattice_function()
1624
adjacent = set()
1625
facets = self.facets()
1626
superfaces = self.facet_of()
1627
if superfaces:
1628
for superface in superfaces:
1629
for facet in facets:
1630
adjacent.update(L.open_interval(facet, superface))
1631
if adjacent:
1632
adjacent.remove(L(self))
1633
self._adjacent = self._sort_faces(face.element
1634
for face in adjacent)
1635
elif self.dim() == self._ambient.dim():
1636
# Special treatment relevant for fans
1637
for facet in facets:
1638
adjacent.update(facet.facet_of())
1639
if adjacent:
1640
adjacent.remove(self)
1641
self._adjacent = self._sort_faces(adjacent)
1642
else:
1643
self._adjacent = ()
1644
return self._adjacent
1645
1646
def ambient(self):
1647
r"""
1648
Return the ambient structure of ``self``.
1649
1650
OUTPUT:
1651
1652
- cone or fan containing ``self`` as a face.
1653
1654
EXAMPLES::
1655
1656
sage: cone = Cone([(1,2,3), (4,6,5), (9,8,7)])
1657
sage: cone.ambient()
1658
3-d cone in 3-d lattice N
1659
sage: cone.ambient() is cone
1660
True
1661
sage: face = cone.faces(1)[0]
1662
sage: face
1663
1-d face of 3-d cone in 3-d lattice N
1664
sage: face.ambient()
1665
3-d cone in 3-d lattice N
1666
sage: face.ambient() is cone
1667
True
1668
"""
1669
return self._ambient
1670
1671
def ambient_ray_indices(self):
1672
r"""
1673
Return indices of rays of the ambient structure generating ``self``.
1674
1675
OUTPUT:
1676
1677
- increasing :class:`tuple` of integers.
1678
1679
EXAMPLES::
1680
1681
sage: quadrant = Cone([(1,0), (0,1)])
1682
sage: quadrant.ambient_ray_indices()
1683
(0, 1)
1684
sage: quadrant.facets()[1].ambient_ray_indices()
1685
(1,)
1686
"""
1687
return self._ambient_ray_indices
1688
1689
def contains(self, *args):
1690
r"""
1691
Check if a given point is contained in ``self``.
1692
1693
INPUT:
1694
1695
- anything. An attempt will be made to convert all arguments into a
1696
single element of the ambient space of ``self``. If it fails,
1697
``False`` will be returned.
1698
1699
OUTPUT:
1700
1701
- ``True`` if the given point is contained in ``self``, ``False``
1702
otherwise.
1703
1704
EXAMPLES::
1705
1706
sage: c = Cone([(1,0), (0,1)])
1707
sage: c.contains(c.lattice()(1,0))
1708
True
1709
sage: c.contains((1,0))
1710
True
1711
sage: c.contains((1,1))
1712
True
1713
sage: c.contains(1,1)
1714
True
1715
sage: c.contains((-1,0))
1716
False
1717
sage: c.contains(c.dual_lattice()(1,0)) #random output (warning)
1718
False
1719
sage: c.contains(c.dual_lattice()(1,0))
1720
False
1721
sage: c.contains(1)
1722
False
1723
sage: c.contains(1/2, sqrt(3))
1724
True
1725
sage: c.contains(-1/2, sqrt(3))
1726
False
1727
"""
1728
point = flatten(args)
1729
if len(point) == 1:
1730
point = point[0]
1731
return self._contains(point)
1732
1733
def dual(self):
1734
r"""
1735
Return the dual cone of ``self``.
1736
1737
OUTPUT:
1738
1739
- :class:`cone <ConvexRationalPolyhedralCone>`.
1740
1741
EXAMPLES::
1742
1743
sage: cone = Cone([(1,0), (-1,3)])
1744
sage: cone.dual().rays()
1745
(M(3, 1), M(0, 1))
1746
1747
Now let's look at a more complicated case::
1748
1749
sage: cone = Cone([(-2,-1,2), (4,1,0), (-4,-1,-5), (4,1,5)])
1750
sage: cone.is_strictly_convex()
1751
False
1752
sage: cone.dim()
1753
3
1754
sage: cone.dual().rays()
1755
(M(7, -18, -2), M(1, -4, 0))
1756
sage: cone.dual().dual() is cone
1757
True
1758
1759
We correctly handle the degenerate cases::
1760
1761
sage: N = ToricLattice(2)
1762
sage: Cone([], lattice=N).dual().rays() # empty cone
1763
(M(1, 0), M(-1, 0), M(0, 1), M(0, -1))
1764
sage: Cone([(1,0)], lattice=N).dual().rays() # ray in 2d
1765
(M(1, 0), M(0, 1), M(0, -1))
1766
sage: Cone([(1,0),(-1,0)], lattice=N).dual().rays() # line in 2d
1767
(M(0, 1), M(0, -1))
1768
sage: Cone([(1,0),(0,1)], lattice=N).dual().rays() # strictly convex cone
1769
(M(1, 0), M(0, 1))
1770
sage: Cone([(1,0),(-1,0),(0,1)], lattice=N).dual().rays() # half space
1771
(M(0, 1),)
1772
sage: Cone([(1,0),(0,1),(-1,-1)], lattice=N).dual().rays() # whole space
1773
()
1774
"""
1775
if "_dual" not in self.__dict__:
1776
rays = list(self.facet_normals())
1777
for ray in self.orthogonal_sublattice().gens():
1778
rays.append(ray)
1779
rays.append(-ray)
1780
self._dual = Cone(rays, lattice=self.dual_lattice(), check=False)
1781
self._dual._dual = self
1782
return self._dual
1783
1784
def embed(self, cone):
1785
r"""
1786
Return the cone equivalent to the given one, but sitting in ``self`` as
1787
a face.
1788
1789
You may need to use this method before calling methods of ``cone`` that
1790
depend on the ambient structure, such as
1791
:meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.ambient_ray_indices`
1792
or
1793
:meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.facet_of`. The
1794
cone returned by this method will have ``self`` as ambient. If ``cone``
1795
does not represent a valid cone of ``self``, ``ValueError`` exception
1796
is raised.
1797
1798
.. NOTE::
1799
1800
This method is very quick if ``self`` is already the ambient
1801
structure of ``cone``, so you can use without extra checks and
1802
performance hit even if ``cone`` is likely to sit in ``self`` but
1803
in principle may not.
1804
1805
INPUT:
1806
1807
- ``cone`` -- a :class:`cone
1808
<sage.geometry.cone.ConvexRationalPolyhedralCone>`.
1809
1810
OUTPUT:
1811
1812
- a :class:`cone <sage.geometry.cone.ConvexRationalPolyhedralCone>`,
1813
equivalent to ``cone`` but sitting inside ``self``.
1814
1815
EXAMPLES:
1816
1817
Let's take a 3-d cone on 4 rays::
1818
1819
sage: c = Cone([(1,0,1), (0,1,1), (-1,0,1), (0,-1,1)])
1820
1821
Then any ray generates a 1-d face of this cone, but if you construct
1822
such a face directly, it will not "sit" inside the cone::
1823
1824
sage: ray = Cone([(0,-1,1)])
1825
sage: ray
1826
1-d cone in 3-d lattice N
1827
sage: ray.ambient_ray_indices()
1828
(0,)
1829
sage: ray.adjacent()
1830
()
1831
sage: ray.ambient()
1832
1-d cone in 3-d lattice N
1833
1834
If we want to operate with this ray as a face of the cone, we need to
1835
embed it first::
1836
1837
sage: e_ray = c.embed(ray)
1838
sage: e_ray
1839
1-d face of 3-d cone in 3-d lattice N
1840
sage: e_ray.rays()
1841
(N(0, -1, 1),)
1842
sage: e_ray is ray
1843
False
1844
sage: e_ray.is_equivalent(ray)
1845
True
1846
sage: e_ray.ambient_ray_indices()
1847
(3,)
1848
sage: e_ray.adjacent()
1849
(1-d face of 3-d cone in 3-d lattice N,
1850
1-d face of 3-d cone in 3-d lattice N)
1851
sage: e_ray.ambient()
1852
3-d cone in 3-d lattice N
1853
1854
Not every cone can be embedded into a fixed ambient cone::
1855
1856
sage: c.embed(Cone([(0,0,1)]))
1857
Traceback (most recent call last):
1858
...
1859
ValueError: 1-d cone in 3-d lattice N is not a face
1860
of 3-d cone in 3-d lattice N!
1861
sage: c.embed(Cone([(1,0,1), (-1,0,1)]))
1862
Traceback (most recent call last):
1863
...
1864
ValueError: 2-d cone in 3-d lattice N is not a face
1865
of 3-d cone in 3-d lattice N!
1866
"""
1867
assert is_Cone(cone)
1868
if cone.ambient() is self:
1869
return cone
1870
if self.is_strictly_convex():
1871
rays = self.rays()
1872
try:
1873
ray_indices = tuple(sorted(rays.index(ray)
1874
for ray in cone.rays()))
1875
for face in self.faces(cone.dim()):
1876
if face.ambient_ray_indices() == ray_indices:
1877
return face
1878
except ValueError:
1879
pass
1880
else:
1881
# We cannot use the trick with indices since rays are not unique.
1882
for face in self.faces(cone.dim()):
1883
if cone.is_equivalent(face):
1884
return face
1885
# If we are here, then either ValueError was raised or we went through
1886
# all faces and didn't find the matching one.
1887
raise ValueError("%s is not a face of %s!" % (cone, self))
1888
1889
def face_lattice(self):
1890
r"""
1891
Return the face lattice of ``self``.
1892
1893
This lattice will have the origin as the bottom (we do not include the
1894
empty set as a face) and this cone itself as the top.
1895
1896
OUTPUT:
1897
1898
- :class:`finite poset <sage.combinat.posets.posets.FinitePoset>` of
1899
:class:`cones <ConvexRationalPolyhedralCone>`.
1900
1901
EXAMPLES:
1902
1903
Let's take a look at the face lattice of the first quadrant::
1904
1905
sage: quadrant = Cone([(1,0), (0,1)])
1906
sage: L = quadrant.face_lattice()
1907
sage: L
1908
Finite poset containing 4 elements
1909
1910
To see all faces arranged by dimension, you can do this::
1911
1912
sage: for level in L.level_sets(): print level
1913
[0-d face of 2-d cone in 2-d lattice N]
1914
[1-d face of 2-d cone in 2-d lattice N,
1915
1-d face of 2-d cone in 2-d lattice N]
1916
[2-d cone in 2-d lattice N]
1917
1918
To work with a particular face of a particular dimension it is not
1919
enough to do just ::
1920
1921
sage: face = L.level_sets()[1][0]
1922
sage: face
1923
1-d face of 2-d cone in 2-d lattice N
1924
sage: face.rays()
1925
Traceback (most recent call last):
1926
...
1927
AttributeError: 'FinitePoset_with_category.element_class' object has no attribute 'rays'
1928
1929
To get the actual face you need one more step::
1930
1931
sage: face = face.element
1932
1933
Now you can look at the actual rays of this face... ::
1934
1935
sage: face.rays()
1936
(N(1, 0),)
1937
1938
... or you can see indices of the rays of the orginal cone that
1939
correspond to the above ray::
1940
1941
sage: face.ambient_ray_indices()
1942
(0,)
1943
sage: quadrant.ray(0)
1944
N(1, 0)
1945
1946
An alternative to extracting faces from the face lattice is to use
1947
:meth:`faces` method::
1948
1949
sage: face is quadrant.faces(dim=1)[0]
1950
True
1951
1952
The advantage of working with the face lattice directly is that you
1953
can (relatively easily) get faces that are related to the given one::
1954
1955
sage: face = L.level_sets()[1][0]
1956
sage: D = L.hasse_diagram()
1957
sage: D.neighbors(face)
1958
[0-d face of 2-d cone in 2-d lattice N,
1959
2-d cone in 2-d lattice N]
1960
1961
However, you can achieve some of this functionality using
1962
:meth:`facets`, :meth:`facet_of`, and :meth:`adjacent` methods::
1963
1964
sage: face = quadrant.faces(1)[0]
1965
sage: face
1966
1-d face of 2-d cone in 2-d lattice N
1967
sage: face.rays()
1968
(N(1, 0),)
1969
sage: face.facets()
1970
(0-d face of 2-d cone in 2-d lattice N,)
1971
sage: face.facet_of()
1972
(2-d cone in 2-d lattice N,)
1973
sage: face.adjacent()
1974
(1-d face of 2-d cone in 2-d lattice N,)
1975
sage: face.adjacent()[0].rays()
1976
(N(0, 1),)
1977
1978
Note that if ``cone`` is a face of ``supercone``, then the face
1979
lattice of ``cone`` consists of (appropriate) faces of ``supercone``::
1980
1981
sage: supercone = Cone([(1,2,3,4), (5,6,7,8),
1982
... (1,2,4,8), (1,3,9,7)])
1983
sage: supercone.face_lattice()
1984
Finite poset containing 16 elements
1985
sage: supercone.face_lattice().top()
1986
4-d cone in 4-d lattice N
1987
sage: cone = supercone.facets()[0]
1988
sage: cone
1989
3-d face of 4-d cone in 4-d lattice N
1990
sage: cone.face_lattice()
1991
Finite poset containing 8 elements
1992
sage: cone.face_lattice().bottom()
1993
0-d face of 4-d cone in 4-d lattice N
1994
sage: cone.face_lattice().top()
1995
3-d face of 4-d cone in 4-d lattice N
1996
sage: cone.face_lattice().top().element == cone
1997
True
1998
1999
TESTS::
2000
2001
sage: C1 = Cone([(0,1)])
2002
sage: C2 = Cone([(0,1)])
2003
sage: C1 == C2
2004
True
2005
sage: C1 is C2
2006
False
2007
2008
C1 and C2 are equal, but not identical. We currently want them
2009
to have non identical face lattices, even if the faces
2010
themselves are equal (see #10998)::
2011
2012
sage: C1.face_lattice() is C2.face_lattice()
2013
False
2014
2015
sage: C1.facets()[0]
2016
0-d face of 1-d cone in 2-d lattice N
2017
sage: C2.facets()[0]
2018
0-d face of 1-d cone in 2-d lattice N
2019
2020
sage: C1.facets()[0].ambient() is C1
2021
True
2022
sage: C2.facets()[0].ambient() is C1
2023
False
2024
sage: C2.facets()[0].ambient() is C2
2025
True
2026
"""
2027
if "_face_lattice" not in self.__dict__:
2028
if self._ambient is self:
2029
# We need to compute face lattice on our own. To accommodate
2030
# non-strictly convex cones we split rays (or rather their
2031
# indicies) into those in the linear subspace and others, which
2032
# we refer to as atoms.
2033
S = self.linear_subspace()
2034
subspace_rays = []
2035
atom_to_ray = []
2036
atom_to_facets = []
2037
normals = self.facet_normals()
2038
facet_to_atoms = [[] for normal in normals]
2039
for i, ray in enumerate(self):
2040
if ray in S:
2041
subspace_rays.append(i)
2042
else:
2043
facets = [j for j, normal in enumerate(normals)
2044
if ray * normal == 0]
2045
atom_to_facets.append(facets)
2046
atom = len(atom_to_ray)
2047
for j in facets:
2048
facet_to_atoms[j].append(atom)
2049
atom_to_ray.append(i)
2050
2051
def ConeFace(atoms, facets):
2052
if facets:
2053
rays = sorted([atom_to_ray[a] for a in atoms]
2054
+ subspace_rays)
2055
face = ConvexRationalPolyhedralCone(
2056
ambient=self, ambient_ray_indices=rays)
2057
# It may be nice if this functionality is exposed,
2058
# however it makes sense only for cones which are
2059
# thought of as faces of a single cone, not of a fan.
2060
face._containing_cone_facets = facets
2061
return face
2062
else:
2063
return self
2064
2065
self._face_lattice = Hasse_diagram_from_incidences(
2066
atom_to_facets, facet_to_atoms, ConeFace,
2067
key = id(self))
2068
else:
2069
# Get face lattice as a sublattice of the ambient one
2070
allowed_indices = frozenset(self._ambient_ray_indices)
2071
L = DiGraph()
2072
origin = \
2073
self._ambient._face_lattice_function().bottom().element
2074
L.add_vertex(0) # In case it is the only one
2075
dfaces = [origin]
2076
faces = [origin]
2077
face_to_index = {origin:0}
2078
next_index = 1
2079
next_d = 1 # Dimension of faces to be considered next.
2080
while next_d < self.dim():
2081
ndfaces = []
2082
for face in dfaces:
2083
face_index = face_to_index[face]
2084
for new_face in face.facet_of():
2085
if not allowed_indices.issuperset(
2086
new_face._ambient_ray_indices):
2087
continue
2088
if new_face in ndfaces:
2089
new_face_index = face_to_index[new_face]
2090
else:
2091
ndfaces.append(new_face)
2092
face_to_index[new_face] = next_index
2093
new_face_index = next_index
2094
next_index += 1
2095
L.add_edge(face_index, new_face_index)
2096
faces.extend(ndfaces)
2097
dfaces = ndfaces
2098
next_d += 1
2099
if self.dim() > 0:
2100
# Last level is very easy to build, so we do it separately
2101
# even though the above cycle could do it too.
2102
faces.append(self)
2103
for face in dfaces:
2104
L.add_edge(face_to_index[face], next_index)
2105
self._face_lattice = FinitePoset(L, faces, key = id(self))
2106
return self._face_lattice
2107
2108
# Internally we use this name for a uniform behaviour of cones and fans.
2109
_face_lattice_function = face_lattice
2110
2111
def faces(self, dim=None, codim=None):
2112
r"""
2113
Return faces of ``self`` of specified (co)dimension.
2114
2115
INPUT:
2116
2117
- ``dim`` -- integer, dimension of the requested faces;
2118
2119
- ``codim`` -- integer, codimension of the requested faces.
2120
2121
.. NOTE::
2122
2123
You can specify at most one parameter. If you don't give any, then
2124
all faces will be returned.
2125
2126
OUTPUT:
2127
2128
- if either ``dim`` or ``codim`` is given, the output will be a
2129
:class:`tuple` of :class:`cones <ConvexRationalPolyhedralCone>`;
2130
2131
- if neither ``dim`` nor ``codim`` is given, the output will be the
2132
:class:`tuple` of tuples as above, giving faces of all existing
2133
dimensions. If you care about inclusion relations between faces,
2134
consider using :meth:`face_lattice` or :meth:`adjacent`,
2135
:meth:`facet_of`, and :meth:`facets`.
2136
2137
EXAMPLES:
2138
2139
Let's take a look at the faces of the first quadrant::
2140
2141
sage: quadrant = Cone([(1,0), (0,1)])
2142
sage: quadrant.faces()
2143
((0-d face of 2-d cone in 2-d lattice N,),
2144
(1-d face of 2-d cone in 2-d lattice N,
2145
1-d face of 2-d cone in 2-d lattice N),
2146
(2-d cone in 2-d lattice N,))
2147
sage: quadrant.faces(dim=1)
2148
(1-d face of 2-d cone in 2-d lattice N,
2149
1-d face of 2-d cone in 2-d lattice N)
2150
sage: face = quadrant.faces(dim=1)[0]
2151
2152
Now you can look at the actual rays of this face... ::
2153
2154
sage: face.rays()
2155
(N(1, 0),)
2156
2157
... or you can see indices of the rays of the orginal cone that
2158
correspond to the above ray::
2159
2160
sage: face.ambient_ray_indices()
2161
(0,)
2162
sage: quadrant.ray(0)
2163
N(1, 0)
2164
2165
Note that it is OK to ask for faces of too small or high dimension::
2166
2167
sage: quadrant.faces(-1)
2168
()
2169
sage: quadrant.faces(3)
2170
()
2171
2172
In the case of non-strictly convex cones even faces of small
2173
non-negative dimension may be missing::
2174
2175
sage: halfplane = Cone([(1,0), (0,1), (-1,0)])
2176
sage: halfplane.faces(0)
2177
()
2178
sage: halfplane.faces()
2179
((1-d face of 2-d cone in 2-d lattice N,),
2180
(2-d cone in 2-d lattice N,))
2181
sage: plane = Cone([(1,0), (0,1), (-1,-1)])
2182
sage: plane.faces(1)
2183
()
2184
sage: plane.faces()
2185
((2-d cone in 2-d lattice N,),)
2186
2187
TESTS:
2188
2189
Now we check that "general" cones whose dimension is smaller than the
2190
dimension of the ambient space work as expected (see Trac #9188)::
2191
2192
sage: c = Cone([(1,1,1,3),(1,-1,1,3),(-1,-1,1,3)])
2193
sage: c.faces()
2194
((0-d face of 3-d cone in 4-d lattice N,),
2195
(1-d face of 3-d cone in 4-d lattice N,
2196
1-d face of 3-d cone in 4-d lattice N,
2197
1-d face of 3-d cone in 4-d lattice N),
2198
(2-d face of 3-d cone in 4-d lattice N,
2199
2-d face of 3-d cone in 4-d lattice N,
2200
2-d face of 3-d cone in 4-d lattice N),
2201
(3-d cone in 4-d lattice N,))
2202
2203
We also ensure that a call to this function does not break
2204
:meth:`facets` method (see Trac #9780)::
2205
2206
sage: cone = toric_varieties.dP8().fan().generating_cone(0)
2207
sage: cone
2208
2-d cone of Rational polyhedral fan in 2-d lattice N
2209
sage: [f.rays() for f in cone.facets()]
2210
[(N(1, 1),), (N(0, 1),)]
2211
sage: len(cone.faces())
2212
3
2213
sage: [f.rays() for f in cone.facets()]
2214
[(N(1, 1),), (N(0, 1),)]
2215
"""
2216
if dim is not None and codim is not None:
2217
raise ValueError(
2218
"dimension and codimension cannot be specified together!")
2219
dim = self.dim() - codim if codim is not None else dim
2220
if "_faces" not in self.__dict__:
2221
self._faces = tuple(self._sort_faces(e.element for e in level)
2222
for level in self.face_lattice().level_sets())
2223
# To avoid duplication and ensure order consistency
2224
if len(self._faces) > 1:
2225
self._facets = self._faces[-2]
2226
if dim is None:
2227
return self._faces
2228
else:
2229
lsd = self.linear_subspace().dimension()
2230
return self._faces[dim - lsd] if lsd <= dim <= self.dim() else ()
2231
2232
def facet_normals(self):
2233
r"""
2234
Return inward normals to facets of ``self``.
2235
2236
.. NOTE::
2237
2238
#. For a not full-dimensional cone facet normals will specify
2239
hyperplanes whose intersections with the space spanned by
2240
``self`` give facets of ``self``.
2241
2242
#. For a not strictly convex cone facet normals will be orthogonal
2243
to the linear subspace of ``self``, i.e. they always will be
2244
elements of the dual cone of ``self``.
2245
2246
#. The order of normals is random and may be different from the
2247
one in :meth:`facets`.
2248
2249
OUTPUT:
2250
2251
- :class:`tuple` of vectors.
2252
2253
If the ambient :meth:`~IntegralRayCollection.lattice` of ``self`` is a
2254
:class:`toric lattice
2255
<sage.geometry.toric_lattice.ToricLatticeFactory>`, the facet nomals
2256
will be elements of the dual lattice. If it is a general lattice (like
2257
``ZZ^n``) that does not have a ``dual()`` method, the facet normals
2258
will be returned as integral vectors.
2259
2260
EXAMPLES::
2261
2262
sage: cone = Cone([(1,0), (-1,3)])
2263
sage: cone.facet_normals()
2264
(M(3, 1), M(0, 1))
2265
2266
Now let's look at a more complicated case::
2267
2268
sage: cone = Cone([(-2,-1,2), (4,1,0), (-4,-1,-5), (4,1,5)])
2269
sage: cone.is_strictly_convex()
2270
False
2271
sage: cone.dim()
2272
3
2273
sage: cone.linear_subspace().dimension()
2274
1
2275
sage: lsg = (QQ^3)(cone.linear_subspace().gen(0)); lsg
2276
(1, 1/4, 5/4)
2277
sage: cone.facet_normals()
2278
(M(7, -18, -2), M(1, -4, 0))
2279
sage: [lsg*normal for normal in cone.facet_normals()]
2280
[0, 0]
2281
2282
A lattice that does not have a ``dual()`` method::
2283
2284
sage: Cone([(1,1),(0,1)], lattice=ZZ^2).facet_normals()
2285
((1, 0), (-1, 1))
2286
2287
We correctly handle the degenerate cases::
2288
2289
sage: N = ToricLattice(2)
2290
sage: Cone([], lattice=N).facet_normals() # empty cone
2291
()
2292
sage: Cone([(1,0)], lattice=N).facet_normals() # ray in 2d
2293
(M(1, 0),)
2294
sage: Cone([(1,0),(-1,0)], lattice=N).facet_normals() # line in 2d
2295
()
2296
sage: Cone([(1,0),(0,1)], lattice=N).facet_normals() # strictly convex cone
2297
(M(1, 0), M(0, 1))
2298
sage: Cone([(1,0),(-1,0),(0,1)], lattice=N).facet_normals() # half space
2299
(M(0, 1),)
2300
sage: Cone([(1,0),(0,1),(-1,-1)], lattice=N).facet_normals() # whole space
2301
()
2302
"""
2303
if "_facet_normals" not in self.__dict__:
2304
cone = self._PPL_cone()
2305
normals = []
2306
for c in cone.minimized_constraints():
2307
assert c.inhomogeneous_term() == 0
2308
if c.is_inequality():
2309
normals.append(c.coefficients())
2310
M = self.dual_lattice()
2311
normals = tuple(map(M,normals))
2312
for n in normals:
2313
n.set_immutable()
2314
self._facet_normals = normals
2315
return self._facet_normals
2316
2317
def facet_of(self):
2318
r"""
2319
Return *cones* of the ambient face lattice having ``self`` as a facet.
2320
2321
OUTPUT:
2322
2323
- :class:`tuple` of :class:`cones <ConvexRationalPolyhedralCone>`.
2324
2325
EXAMPLES::
2326
2327
sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
2328
sage: octant.facet_of()
2329
()
2330
sage: one_face = octant.faces(1)[0]
2331
sage: len(one_face.facet_of())
2332
2
2333
sage: one_face.facet_of()[1]
2334
2-d face of 3-d cone in 3-d lattice N
2335
2336
While fan is the top element of its own cone lattice, which is a
2337
variant of a face lattice, we do not refer to cones as its facets::
2338
2339
sage: fan = Fan([octant])
2340
sage: fan.generating_cone(0).facet_of()
2341
()
2342
2343
Subcones of generating cones work as before::
2344
2345
sage: one_cone = fan(1)[0]
2346
sage: len(one_cone.facet_of())
2347
2
2348
"""
2349
if "_facet_of" not in self.__dict__:
2350
L = self._ambient._face_lattice_function()
2351
H = L.hasse_diagram()
2352
self._facet_of = self._sort_faces(f.element
2353
for f in H.neighbors_out(L(self)) if is_Cone(f.element))
2354
return self._facet_of
2355
2356
def facets(self):
2357
r"""
2358
Return facets (faces of codimension 1) of ``self``.
2359
2360
OUTPUT:
2361
2362
- :class:`tuple` of :class:`cones <ConvexRationalPolyhedralCone>`.
2363
2364
EXAMPLES::
2365
2366
sage: quadrant = Cone([(1,0), (0,1)])
2367
sage: quadrant.facets()
2368
(1-d face of 2-d cone in 2-d lattice N,
2369
1-d face of 2-d cone in 2-d lattice N)
2370
"""
2371
if "_facets" not in self.__dict__:
2372
L = self._ambient._face_lattice_function()
2373
H = L.hasse_diagram()
2374
self._facets = self._sort_faces(tuple(
2375
f.element for f in H.neighbors_in(L(self))))
2376
return self._facets
2377
2378
def intersection(self, other):
2379
r"""
2380
Compute the intersection of two cones.
2381
2382
INPUT:
2383
2384
- ``other`` - :class:`cone <ConvexRationalPolyhedralCone>`.
2385
2386
OUTPUT:
2387
2388
- :class:`cone <ConvexRationalPolyhedralCone>`.
2389
2390
Raises ``ValueError`` if the ambient space dimensions are not
2391
compatible.
2392
2393
EXAMPLES::
2394
2395
sage: cone1 = Cone([(1,0), (-1, 3)])
2396
sage: cone2 = Cone([(-1,0), (2, 5)])
2397
sage: cone1.intersection(cone2).rays()
2398
(N(-1, 3), N(2, 5))
2399
2400
It is OK to intersect cones living in sublattices of the same ambient
2401
lattice::
2402
2403
sage: N = cone1.lattice()
2404
sage: Ns = N.submodule([(1,1)])
2405
sage: cone3 = Cone([(1,1)], lattice=Ns)
2406
sage: I = cone1.intersection(cone3)
2407
sage: I.rays()
2408
(N(1, 1),)
2409
sage: I.lattice()
2410
Sublattice <N(1, 1)>
2411
2412
But you cannot intersect cones from incompatible lattices without
2413
explicit conversion::
2414
2415
sage: cone1.intersection(cone1.dual())
2416
Traceback (most recent call last):
2417
...
2418
ValueError: 2-d lattice N and 2-d lattice M
2419
have different ambient lattices!
2420
sage: cone1.intersection(Cone(cone1.dual().rays(), N)).rays()
2421
(N(3, 1), N(0, 1))
2422
"""
2423
if self._ambient is other._ambient:
2424
# Cones of the same ambient cone or fan intersect nicely/quickly.
2425
# Can we maybe even return an element of the cone lattice?..
2426
# But currently it can be done only for strictly convex cones.
2427
ambient_ray_indices = tuple(r for r in self._ambient_ray_indices
2428
if r in other._ambient_ray_indices)
2429
# type(self) allows this code to work nicely for derived classes,
2430
# although it forces all of them to accept such input
2431
return type(self)(ambient=self._ambient,
2432
ambient_ray_indices=ambient_ray_indices)
2433
# Generic (slow) intersection, returning a generic cone.
2434
p = C_Polyhedron(self._PPL_cone())
2435
p.add_constraints(other._PPL_cone().constraints())
2436
return _Cone_from_PPL(p, self.lattice().intersection(other.lattice()))
2437
2438
def is_equivalent(self, other):
2439
r"""
2440
Check if ``self`` is "mathematically" the same as ``other``.
2441
2442
INPUT:
2443
2444
- ``other`` - cone.
2445
2446
OUTPUT:
2447
2448
- ``True`` if ``self`` and ``other`` define the same cones as sets of
2449
points in the same lattice, ``False`` otherwise.
2450
2451
There are three different equivalences between cones `C_1` and `C_2`
2452
in the same lattice:
2453
2454
#. They have the same generating rays in the same order.
2455
This is tested by ``C1 == C2``.
2456
#. They describe the same sets of points.
2457
This is tested by ``C1.is_equivalent(C2)``.
2458
#. They are in the same orbit of `GL(n,\ZZ)` (and, therefore,
2459
correspond to isomorphic affine toric varieties).
2460
This is tested by ``C1.is_isomorphic(C2)``.
2461
2462
EXAMPLES::
2463
2464
sage: cone1 = Cone([(1,0), (-1, 3)])
2465
sage: cone2 = Cone([(-1,3), (1, 0)])
2466
sage: cone1.rays()
2467
(N(1, 0), N(-1, 3))
2468
sage: cone2.rays()
2469
(N(-1, 3), N(1, 0))
2470
sage: cone1 == cone2
2471
False
2472
sage: cone1.is_equivalent(cone2)
2473
True
2474
"""
2475
if self is other:
2476
return True
2477
# TODO: Next check is pointless if cones and fans are made to be unique
2478
if self.ambient() is other.ambient() and self.is_strictly_convex():
2479
return self.ambient_ray_indices() == other.ambient_ray_indices()
2480
if self.lattice() != other.lattice():
2481
return False
2482
return self._PPL_cone() == other._PPL_cone()
2483
2484
def is_face_of(self, cone):
2485
r"""
2486
Check if ``self`` forms a face of another ``cone``.
2487
2488
INPUT:
2489
2490
- ``cone`` -- cone.
2491
2492
OUTPUT:
2493
2494
- ``True`` if ``self`` is a face of ``cone``, ``False`` otherwise.
2495
2496
EXAMPLES::
2497
2498
sage: quadrant = Cone([(1,0), (0,1)])
2499
sage: cone1 = Cone([(1,0)])
2500
sage: cone2 = Cone([(1,2)])
2501
sage: quadrant.is_face_of(quadrant)
2502
True
2503
sage: cone1.is_face_of(quadrant)
2504
True
2505
sage: cone2.is_face_of(quadrant)
2506
False
2507
2508
Being a face means more than just saturating a facet
2509
inequality::
2510
2511
sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
2512
sage: cone = Cone([(2,1,0),(1,2,0)])
2513
sage: cone.is_face_of(octant)
2514
False
2515
"""
2516
if self.lattice() != cone.lattice():
2517
return False
2518
if self._ambient is cone._ambient:
2519
# Cones are always faces of their ambient structure, so
2520
return self.ray_set().issubset(cone.ray_set())
2521
if self.is_equivalent(cone):
2522
return True
2523
# Obviously False case
2524
if self.dim() >= cone.dim(): # if == and face, we return True above
2525
return False
2526
2527
# It remains to test whether self is a proper face of cone:
2528
# 1) self must saturate at least one facet inequality
2529
saturates = Poly_Con_Relation.saturates()
2530
supporting_hyperplanes = Constraint_System()
2531
for c in cone._PPL_cone().minimized_constraints():
2532
rel = self._PPL_cone().relation_with(c)
2533
if c.is_equality() and not rel.implies(saturates):
2534
return False
2535
if c.is_inequality() and rel.implies(saturates):
2536
c_eq = (Linear_Expression(c.coefficients(),
2537
c.inhomogeneous_term()) == 0)
2538
supporting_hyperplanes.insert(c_eq)
2539
if supporting_hyperplanes.empty():
2540
return False
2541
# 2) self must be a whole face, and not just a part of one
2542
cone_face = C_Polyhedron(cone._PPL_cone())
2543
cone_face.add_constraints(supporting_hyperplanes)
2544
return cone_face == self._PPL_cone()
2545
2546
def is_isomorphic(self, other):
2547
r"""
2548
Check if ``self`` is in the same `GL(n, \ZZ)`-orbit as ``other``.
2549
2550
INPUT:
2551
2552
- ``other`` - cone.
2553
2554
OUTPUT:
2555
2556
- ``True`` if ``self`` and ``other`` are in the same
2557
`GL(n, \ZZ)`-orbit, ``False`` otherwise.
2558
2559
There are three different equivalences between cones `C_1` and `C_2`
2560
in the same lattice:
2561
2562
#. They have the same generating rays in the same order.
2563
This is tested by ``C1 == C2``.
2564
#. They describe the same sets of points.
2565
This is tested by ``C1.is_equivalent(C2)``.
2566
#. They are in the same orbit of `GL(n,\ZZ)` (and, therefore,
2567
correspond to isomorphic affine toric varieties).
2568
This is tested by ``C1.is_isomorphic(C2)``.
2569
2570
EXAMPLES::
2571
2572
sage: cone1 = Cone([(1,0), (0, 3)])
2573
sage: cone2 = Cone([(-1,3), (1, 0)])
2574
sage: cone1.is_isomorphic(cone2)
2575
Traceback (most recent call last):
2576
...
2577
NotImplementedError: cone isomorphism is not implemented yet!
2578
"""
2579
if self is other:
2580
return True
2581
if self.lattice() != other.lattice():
2582
return False
2583
raise NotImplementedError("cone isomorphism is not implemented yet!")
2584
2585
def is_simplicial(self):
2586
r"""
2587
Check if ``self`` is simplicial.
2588
2589
A cone is called **simplicial** if primitive vectors along its
2590
generating rays form a part of a *rational* basis of the ambient
2591
space.
2592
2593
OUTPUT:
2594
2595
- ``True`` if ``self`` is simplicial, ``False`` otherwise.
2596
2597
EXAMPLES::
2598
2599
sage: cone1 = Cone([(1,0), (0, 3)])
2600
sage: cone2 = Cone([(1,0), (0, 3), (-1,-1)])
2601
sage: cone1.is_simplicial()
2602
True
2603
sage: cone2.is_simplicial()
2604
False
2605
"""
2606
return self.nrays() == self.dim()
2607
2608
def is_smooth(self):
2609
r"""
2610
Check if ``self`` is smooth.
2611
2612
A cone is called **smooth** if primitive vectors along its
2613
generating rays form a part of an *integral* basis of the
2614
ambient space. Equivalently, they generate the whole lattice
2615
on the linear subspace spanned by the rays.
2616
2617
OUTPUT:
2618
2619
- ``True`` if ``self`` is smooth, ``False`` otherwise.
2620
2621
EXAMPLES::
2622
2623
sage: cone1 = Cone([(1,0), (0, 1)])
2624
sage: cone2 = Cone([(1,0), (-1, 3)])
2625
sage: cone1.is_smooth()
2626
True
2627
sage: cone2.is_smooth()
2628
False
2629
2630
The following cones are the same up to a `SL(2,\ZZ)`
2631
coordinate transformation::
2632
2633
sage: Cone([(1,0,0), (2,1,-1)]).is_smooth()
2634
True
2635
sage: Cone([(1,0,0), (2,1,1)]).is_smooth()
2636
True
2637
sage: Cone([(1,0,0), (2,1,2)]).is_smooth()
2638
True
2639
"""
2640
if "_is_smooth" not in self.__dict__:
2641
if not self.is_simplicial():
2642
self._is_smooth = False
2643
else:
2644
elementary_divisors = self.ray_matrix().transpose().elementary_divisors()
2645
self._is_smooth = (elementary_divisors == [1]*self.nrays())
2646
return self._is_smooth
2647
2648
def is_trivial(self):
2649
"""
2650
Checks if the cone has no rays.
2651
2652
OUTPUT:
2653
2654
- ``True`` if the cone has no rays, ``False`` otherwise.
2655
2656
EXAMPLES::
2657
2658
sage: c0 = Cone([], lattice=ToricLattice(3))
2659
sage: c0.is_trivial()
2660
True
2661
sage: c0.nrays()
2662
0
2663
"""
2664
return self.nrays() == 0
2665
2666
def is_strictly_convex(self):
2667
r"""
2668
Check if ``self`` is strictly convex.
2669
2670
A cone is called **strictly convex** if it does not contain any lines.
2671
2672
OUTPUT:
2673
2674
- ``True`` if ``self`` is strictly convex, ``False`` otherwise.
2675
2676
EXAMPLES::
2677
2678
sage: cone1 = Cone([(1,0), (0, 1)])
2679
sage: cone2 = Cone([(1,0), (-1, 0)])
2680
sage: cone1.is_strictly_convex()
2681
True
2682
sage: cone2.is_strictly_convex()
2683
False
2684
"""
2685
if "_is_strictly_convex" not in self.__dict__:
2686
convex = True
2687
for gs in self._PPL_cone().minimized_generators():
2688
if gs.is_line():
2689
convex = False
2690
break
2691
self._is_strictly_convex = convex
2692
return self._is_strictly_convex
2693
2694
def lattice_polytope(self):
2695
r"""
2696
Return the lattice polytope associated to ``self``.
2697
2698
The vertices of this polytope are primitive vectors along the
2699
generating rays of ``self`` and the origin, if ``self`` is strictly
2700
convex. In this case the origin is the last vertex, so the `i`-th ray
2701
of the cone always corresponds to the `i`-th vertex of the polytope.
2702
2703
See also :meth:`polyhedron`.
2704
2705
OUTPUT:
2706
2707
- :class:`LatticePolytope
2708
<sage.geometry.lattice_polytope.LatticePolytopeClass>`.
2709
2710
EXAMPLES::
2711
2712
sage: quadrant = Cone([(1,0), (0,1)])
2713
sage: lp = quadrant.lattice_polytope()
2714
sage: lp
2715
A lattice polytope: 2-dimensional, 3 vertices.
2716
sage: lp.vertices()
2717
[1 0 0]
2718
[0 1 0]
2719
2720
sage: line = Cone([(1,0), (-1,0)])
2721
sage: lp = line.lattice_polytope()
2722
sage: lp
2723
A lattice polytope: 1-dimensional, 2 vertices.
2724
sage: lp.vertices()
2725
[ 1 -1]
2726
[ 0 0]
2727
"""
2728
if "_lattice_polytope" not in self.__dict__:
2729
m = self.ray_matrix()
2730
if self.is_strictly_convex():
2731
m = m.augment(matrix(ZZ, self.lattice().degree(), 1)) # origin
2732
self._lattice_polytope = LatticePolytope(m,
2733
compute_vertices=False, copy_vertices=False)
2734
return self._lattice_polytope
2735
2736
def line_set(self):
2737
r"""
2738
Return a set of lines generating the linear subspace of ``self``.
2739
2740
OUTPUT:
2741
2742
- :class:`frozenset` of primitive vectors in the lattice of ``self``
2743
giving directions of lines that span the linear subspace of
2744
``self``. These lines are arbitrary, but fixed. See also
2745
:meth:`lines`.
2746
2747
EXAMPLES::
2748
2749
sage: halfplane = Cone([(1,0), (0,1), (-1,0)])
2750
sage: halfplane.line_set()
2751
frozenset([N(1, 0)])
2752
sage: fullplane = Cone([(1,0), (0,1), (-1,-1)])
2753
sage: fullplane.line_set()
2754
frozenset([N(0, 1), N(1, 0)])
2755
"""
2756
if "_line_set" not in self.__dict__:
2757
self._line_set = frozenset(self.lines())
2758
return self._line_set
2759
2760
def linear_subspace(self):
2761
r"""
2762
Return the largest linear subspace contained inside of ``self``.
2763
2764
OUTPUT:
2765
2766
- subspace of the ambient space of ``self``.
2767
2768
EXAMPLES::
2769
2770
sage: halfplane = Cone([(1,0), (0,1), (-1,0)])
2771
sage: halfplane.linear_subspace()
2772
Vector space of degree 2 and dimension 1 over Rational Field
2773
Basis matrix:
2774
[1 0]
2775
"""
2776
if "_linear_subspace" not in self.__dict__:
2777
if self.is_strictly_convex():
2778
self._linear_subspace = span(
2779
[vector(QQ, self.lattice_dim())], QQ)
2780
else:
2781
self._linear_subspace = span(self.lines(), QQ)
2782
return self._linear_subspace
2783
2784
def lines(self):
2785
r"""
2786
Return lines generating the linear subspace of ``self``.
2787
2788
OUTPUT:
2789
2790
- :class:`tuple` of primitive vectors in the lattice of ``self``
2791
giving directions of lines that span the linear subspace of
2792
``self``. These lines are arbitrary, but fixed. If you do not care
2793
about the order, see also :meth:`line_set`.
2794
2795
EXAMPLES::
2796
2797
sage: halfplane = Cone([(1,0), (0,1), (-1,0)])
2798
sage: halfplane.lines()
2799
(N(1, 0),)
2800
sage: fullplane = Cone([(1,0), (0,1), (-1,-1)])
2801
sage: fullplane.lines()
2802
(N(0, 1), N(1, 0))
2803
"""
2804
if "_lines" not in self.__dict__:
2805
lines = []
2806
for g in self._PPL_cone().minimized_generators():
2807
if g.is_line():
2808
lines.append(g.coefficients())
2809
N = self.lattice()
2810
lines = tuple(map(N,lines))
2811
for l in lines:
2812
l.set_immutable()
2813
self._lines = lines
2814
return self._lines
2815
2816
def plot(self, **options):
2817
r"""
2818
Plot ``self``.
2819
2820
INPUT:
2821
2822
- any options for toric plots (see :func:`toric_plotter.options
2823
<sage.geometry.toric_plotter.options>`), none are mandatory.
2824
2825
OUTPUT:
2826
2827
- a plot.
2828
2829
EXAMPLES::
2830
2831
sage: quadrant = Cone([(1,0), (0,1)])
2832
sage: quadrant.plot()
2833
"""
2834
# What to do with 3-d cones in 5-d? Use some projection method?
2835
deg = self.lattice().degree()
2836
tp = ToricPlotter(options, deg, self.rays())
2837
# Modify ray labels to match the ambient cone or fan.
2838
tp.ray_label = label_list(tp.ray_label, self.nrays(), deg <= 2,
2839
self.ambient_ray_indices())
2840
result = tp.plot_lattice() + tp.plot_generators()
2841
# To deal with non-strictly convex cones we separate rays and labels.
2842
result += tp.plot_ray_labels()
2843
tp.ray_label = None
2844
lsd = self.linear_subspace().dimension()
2845
if lsd == 1:
2846
# Plot only rays of the line
2847
v = self.lines()[0]
2848
tp.set_rays([v, -v])
2849
if lsd <= 1:
2850
result += tp.plot_rays()
2851
# Modify wall labels to match the ambient cone or fan too.
2852
walls = self.faces(2)
2853
try:
2854
ambient_walls = self.ambient().faces(2)
2855
except AttributeError:
2856
ambient_walls = self.ambient().cones(2)
2857
tp.wall_label = label_list(tp.wall_label, len(walls), deg <= 2,
2858
[ambient_walls.index(wall) for wall in walls])
2859
tp.set_rays(self.ambient().rays())
2860
result += tp.plot_walls(walls)
2861
return result
2862
2863
def polyhedron(self):
2864
r"""
2865
Return the polyhedron associated to ``self``.
2866
2867
Mathematically this polyhedron is the same as ``self``.
2868
2869
See also :meth:`lattice_polytope`.
2870
2871
OUTPUT:
2872
2873
- :class:`~sage.geometry.polyhedron.base.Polyhedron_base`.
2874
2875
EXAMPLES::
2876
2877
sage: quadrant = Cone([(1,0), (0,1)])
2878
sage: quadrant.polyhedron()
2879
A 2-dimensional polyhedron in QQ^2 defined as the convex hull
2880
of 1 vertex and 2 rays
2881
sage: line = Cone([(1,0), (-1,0)])
2882
sage: line.polyhedron()
2883
A 1-dimensional polyhedron in QQ^2 defined as the convex hull
2884
of 1 vertex and 1 line
2885
2886
Here is an example of a trivial cone (see Trac #10237)::
2887
2888
sage: origin = Cone([], lattice=ZZ^2)
2889
sage: origin.polyhedron()
2890
A 0-dimensional polyhedron in QQ^2 defined as the convex hull
2891
of 1 vertex
2892
"""
2893
if "_polyhedron" not in self.__dict__:
2894
self._polyhedron = Polyhedron(rays=self.rays(),
2895
vertices=[self.lattice()(0)])
2896
return self._polyhedron
2897
2898
def strict_quotient(self):
2899
r"""
2900
Return the quotient of ``self`` by the linear subspace.
2901
2902
We define the **strict quotient** of a cone to be the image of this
2903
cone in the quotient of the ambient space by the linear subspace of
2904
the cone, i.e. it is the "complementary part" to the linear subspace.
2905
2906
OUTPUT:
2907
2908
- cone.
2909
2910
EXAMPLES::
2911
2912
sage: halfplane = Cone([(1,0), (0,1), (-1,0)])
2913
sage: ssc = halfplane.strict_quotient()
2914
sage: ssc
2915
1-d cone in 1-d lattice N
2916
sage: ssc.rays()
2917
(N(1),)
2918
sage: line = Cone([(1,0), (-1,0)])
2919
sage: ssc = line.strict_quotient()
2920
sage: ssc
2921
0-d cone in 1-d lattice N
2922
sage: ssc.rays()
2923
()
2924
"""
2925
if "_strict_quotient" not in self.__dict__:
2926
if self.is_strictly_convex():
2927
self._strict_quotient = self
2928
else:
2929
L = self.lattice()
2930
Q = L.base_extend(QQ) / self.linear_subspace()
2931
# Maybe we can improve this one if we create something special
2932
# for sublattices. But it seems to be the most natural choice
2933
# for names. If many subcones land in the same lattice -
2934
# that's just how it goes.
2935
if is_ToricLattice(L):
2936
S = ToricLattice(Q.dimension(), L._name, L._dual_name,
2937
L._latex_name, L._latex_dual_name)
2938
else:
2939
S = ZZ**Q.dimension()
2940
rays = [Q(ray) for ray in self.rays() if not Q(ray).is_zero()]
2941
quotient = Cone(rays, S, check=False)
2942
quotient._is_strictly_convex = True
2943
self._strict_quotient = quotient
2944
return self._strict_quotient
2945
2946
def _split_ambient_lattice(self):
2947
r"""
2948
Compute a decomposition of the ``N``-lattice into `N_\sigma`
2949
and its complement `N(\sigma)`.
2950
2951
You should not call this function directly, but call
2952
:meth:`sublattice` and :meth:`sublattice_complement` instead.
2953
2954
EXAMPLES::
2955
2956
sage: c = Cone([ (1,2) ])
2957
sage: c._split_ambient_lattice()
2958
sage: c._sublattice
2959
Sublattice <N(1, 2)>
2960
sage: c._sublattice_complement
2961
Sublattice <N(0, 1)>
2962
sage: c._orthogonal_sublattice
2963
Sublattice <M(-2, 1)>
2964
sage: c._orthogonal_sublattice_complement
2965
Sublattice <M(1, 0)>
2966
sage: N = c.lattice()
2967
sage: n = N(27,31)
2968
sage: (N(0) + c._sublattice.gen(0) * (c._orthogonal_sublattice_complement.gen(0)*n)
2969
... + c._sublattice_complement.gen(0) * (c._orthogonal_sublattice.gen(0)*n))
2970
N(27, 31)
2971
2972
Degenerate cases::
2973
2974
sage: C2_Z2 = Cone([(1,0),(1,2)])
2975
sage: C2_Z2._split_ambient_lattice()
2976
sage: C2_Z2._sublattice
2977
Sublattice <N(1, 0), N(0, 1)>
2978
sage: C2_Z2._orthogonal_sublattice
2979
Sublattice <>
2980
2981
Trivial cone::
2982
2983
sage: trivial_cone = Cone([], lattice=ToricLattice(3))
2984
sage: trivial_cone._split_ambient_lattice()
2985
sage: trivial_cone._sublattice
2986
Sublattice <>
2987
sage: trivial_cone._sublattice_complement
2988
Sublattice <N(1, 0, 0), N(0, 1, 0), N(0, 0, 1)>
2989
sage: trivial_cone._orthogonal_sublattice
2990
Sublattice <M(1, 0, 0), M(0, 1, 0), M(0, 0, 1)>
2991
sage: trivial_cone._orthogonal_sublattice_complement
2992
Sublattice <>
2993
"""
2994
Nsigma = self.ray_basis_matrix()
2995
r = Nsigma.ncols()
2996
D, U, V = Nsigma.smith_form() # D = U*N*V <=> N = Uinv*D*Vinv
2997
Uinv = U.inverse()
2998
n = Uinv.ncols()
2999
3000
N = self.lattice()
3001
# basis for the spanned lattice
3002
basis = [ Uinv.column(i) for i in range(0,r) ]
3003
self._sublattice = N.submodule_with_basis(basis)
3004
# basis for a complement to the spanned lattice
3005
basis = [ Uinv.column(i) for i in range(r,n) ]
3006
self._sublattice_complement = N.submodule_with_basis(basis)
3007
3008
M = self.dual_lattice()
3009
# basis for the dual spanned lattice
3010
basis = [U.row(i) for i in range(r, n)]
3011
self._orthogonal_sublattice = M.submodule_with_basis(basis)
3012
# basis for a complement to the dual spanned lattice
3013
basis = [U.row(i) for i in range(r)]
3014
self._orthogonal_sublattice_complement = M.submodule_with_basis(basis)
3015
3016
def sublattice(self, *args, **kwds):
3017
r"""
3018
The sublattice spanned by the cone.
3019
3020
Let `\sigma` be the given cone and `N=` ``self.lattice()`` the
3021
ambient lattice. Then, in the notation of [Fulton]_, this
3022
method returns the sublattice
3023
3024
.. MATH::
3025
3026
N_\sigma \stackrel{\text{def}}{=} \mathop{span}( N\cap \sigma )
3027
3028
INPUT:
3029
3030
- either nothing or something that can be turned into an element of
3031
this lattice.
3032
3033
OUTPUT:
3034
3035
- if no arguments were given, a :class:`toric sublattice
3036
<sage.geometry.toric_lattice.ToricLattice_sublattice_with_basis>`,
3037
otherwise the corresponding element of it.
3038
3039
.. NOTE::
3040
3041
* The sublattice spanned by the cone is the saturation of
3042
the sublattice generated by the rays of the cone.
3043
3044
* See
3045
:meth:`sage.geometry.cone.IntegralRayCollection.ray_basis`
3046
if you only need a `\QQ`-basis.
3047
3048
* The returned lattice points are usually not rays of the
3049
cone. In fact, for a non-smooth cone the rays do not
3050
generate the sublattice `N_\sigma`, but only a finite
3051
index sublattice.
3052
3053
EXAMPLES::
3054
3055
sage: cone = Cone([(1, 1, 1), (1, -1, 1), (-1, -1, 1), (-1, 1, 1)])
3056
sage: cone.ray_basis_matrix()
3057
[ 1 1 -1]
3058
[ 1 -1 -1]
3059
[ 1 1 1]
3060
sage: cone.ray_basis_matrix().det()
3061
-4
3062
sage: cone.sublattice()
3063
Sublattice <N(1, 1, 1), N(0, 1, 0), N(1, 0, 0)>
3064
sage: matrix( cone.sublattice().gens() ).det()
3065
-1
3066
3067
Another example::
3068
3069
sage: c = Cone([(1,2,3), (4,-5,1)])
3070
sage: c
3071
2-d cone in 3-d lattice N
3072
sage: c.rays()
3073
(N(1, 2, 3), N(4, -5, 1))
3074
sage: c.sublattice()
3075
Sublattice <N(1, 2, 3), N(0, 13, 11)>
3076
sage: c.sublattice(5, -3, 4)
3077
N(5, -3, 4)
3078
sage: c.sublattice(1, 0, 0)
3079
Traceback (most recent call last):
3080
...
3081
TypeError: element (= [1, 0, 0]) is not in free module
3082
"""
3083
if "_sublattice" not in self.__dict__:
3084
self._split_ambient_lattice()
3085
if args or kwds:
3086
return self._sublattice(*args, **kwds)
3087
else:
3088
return self._sublattice
3089
3090
def sublattice_quotient(self, *args, **kwds):
3091
r"""
3092
The quotient of the ambient lattice by the sublattice spanned
3093
by the cone.
3094
3095
INPUT:
3096
3097
- either nothing or something that can be turned into an element of
3098
this lattice.
3099
3100
OUTPUT:
3101
3102
- if no arguments were given, a :class:`quotient of a toric lattice
3103
<sage.geometry.toric_lattice.ToricLattice_quotient>`,
3104
otherwise the corresponding element of it.
3105
3106
EXAMPLES::
3107
3108
sage: C2_Z2 = Cone([(1,0),(1,2)]) # C^2/Z_2
3109
sage: c1, c2 = C2_Z2.facets()
3110
sage: c2.sublattice_quotient()
3111
1-d lattice, quotient of 2-d lattice N by Sublattice <N(1, 2)>
3112
sage: N = C2_Z2.lattice()
3113
sage: n = N(1,1)
3114
sage: n_bar = c2.sublattice_quotient(n); n_bar
3115
N[1, 1]
3116
sage: n_bar.lift()
3117
N(1, 1)
3118
sage: vector(n_bar)
3119
(-1)
3120
"""
3121
if "_sublattice_quotient" not in self.__dict__:
3122
self._sublattice_quotient = self.lattice() / self.sublattice()
3123
if args or kwds:
3124
return self._sublattice_quotient(*args, **kwds)
3125
else:
3126
return self._sublattice_quotient
3127
3128
def sublattice_complement(self, *args, **kwds):
3129
r"""
3130
A complement of the sublattice spanned by the cone.
3131
3132
In other words, :meth:`sublattice` and
3133
:meth:`sublattice_complement` together form a
3134
`\ZZ`-basis for the ambient :meth:`lattice()
3135
<sage.geometry.cone.IntegralRayCollection.lattice>`.
3136
3137
In the notation of [Fulton]_, let `\sigma` be the given cone
3138
and `N=` ``self.lattice()`` the ambient lattice. Then this
3139
method returns
3140
3141
.. MATH::
3142
3143
N(\sigma) \stackrel{\text{def}}{=} N / N_\sigma
3144
3145
lifted (non-canonically) to a sublattice of `N`.
3146
3147
INPUT:
3148
3149
- either nothing or something that can be turned into an element of
3150
this lattice.
3151
3152
OUTPUT:
3153
3154
- if no arguments were given, a :class:`toric sublattice
3155
<sage.geometry.toric_lattice.ToricLattice_sublattice_with_basis>`,
3156
otherwise the corresponding element of it.
3157
3158
EXAMPLES::
3159
3160
sage: C2_Z2 = Cone([(1,0),(1,2)]) # C^2/Z_2
3161
sage: c1, c2 = C2_Z2.facets()
3162
sage: c2.sublattice()
3163
Sublattice <N(1, 2)>
3164
sage: c2.sublattice_complement()
3165
Sublattice <N(0, 1)>
3166
3167
A more complicated example::
3168
3169
sage: c = Cone([(1,2,3), (4,-5,1)])
3170
sage: c.sublattice()
3171
Sublattice <N(1, 2, 3), N(0, 13, 11)>
3172
sage: c.sublattice_complement()
3173
Sublattice <N(-7, -8, -16)>
3174
sage: m = matrix( c.sublattice().gens() + c.sublattice_complement().gens() )
3175
sage: m
3176
[ 1 2 3]
3177
[ 0 13 11]
3178
[ -7 -8 -16]
3179
sage: m.det()
3180
-1
3181
"""
3182
if "_sublattice_complement" not in self.__dict__:
3183
self._split_ambient_lattice()
3184
if args or kwds:
3185
return self._sublattice_complement(*args, **kwds)
3186
else:
3187
return self._sublattice_complement
3188
3189
def orthogonal_sublattice(self, *args, **kwds):
3190
r"""
3191
The sublattice (in the dual lattice) orthogonal to the
3192
sublattice spanned by the cone.
3193
3194
Let `M=` ``self.dual_lattice()`` be the lattice dual to the
3195
ambient lattice of the given cone `\sigma`. Then, in the
3196
notation of [Fulton]_, this method returns the sublattice
3197
3198
.. MATH::
3199
3200
M(\sigma) \stackrel{\text{def}}{=}
3201
\sigma^\perp \cap M
3202
\subset M
3203
3204
INPUT:
3205
3206
- either nothing or something that can be turned into an element of
3207
this lattice.
3208
3209
OUTPUT:
3210
3211
- if no arguments were given, a :class:`toric sublattice
3212
<sage.geometry.toric_lattice.ToricLattice_sublattice_with_basis>`,
3213
otherwise the corresponding element of it.
3214
3215
EXAMPLES::
3216
3217
sage: c = Cone([(1,1,1), (1,-1,1), (-1,-1,1), (-1,1,1)])
3218
sage: c.orthogonal_sublattice()
3219
Sublattice <>
3220
sage: c12 = Cone([(1,1,1), (1,-1,1)])
3221
sage: c12.sublattice()
3222
Sublattice <N(1, 1, 1), N(0, 1, 0)>
3223
sage: c12.orthogonal_sublattice()
3224
Sublattice <M(-1, 0, 1)>
3225
"""
3226
if "_orthogonal_sublattice" not in self.__dict__:
3227
self._split_ambient_lattice()
3228
if args or kwds:
3229
return self._orthogonal_sublattice(*args, **kwds)
3230
else:
3231
return self._orthogonal_sublattice
3232
3233
def relative_quotient(self, subcone):
3234
r"""
3235
The quotient of the spanned lattice by the lattice spanned by
3236
a subcone.
3237
3238
In the notation of [Fulton]_, let `N` be the ambient lattice
3239
and `N_\sigma` the sublattice spanned by the given cone
3240
`\sigma`. If `\rho < \sigma` is a subcone, then `N_\rho` =
3241
``rho.sublattice()`` is a saturated sublattice of `N_\sigma` =
3242
``self.sublattice()``. This method returns the quotient
3243
lattice. The lifts of the quotient generators are
3244
`\dim(\sigma)-\dim(\rho)` linearly independent primitive
3245
lattice lattice points that, together with `N_\rho`, generate
3246
`N_\sigma`.
3247
3248
OUTPUT:
3249
3250
- :class:`toric lattice quotient
3251
<sage.geometry.toric_lattice.ToricLattice_quotient>`.
3252
3253
.. NOTE::
3254
3255
* The quotient `N_\sigma / N_\rho` of spanned sublattices
3256
has no torsion since the sublattice `N_\rho` is saturated.
3257
3258
* In the codimension one case, the generator of
3259
`N_\sigma / N_\rho` is chosen to be in the same direction as the
3260
image `\sigma / N_\rho`
3261
3262
EXAMPLES::
3263
3264
sage: sigma = Cone([(1,1,1,3),(1,-1,1,3),(-1,-1,1,3),(-1,1,1,3)])
3265
sage: rho = Cone([(-1, -1, 1, 3), (-1, 1, 1, 3)])
3266
sage: sigma.sublattice()
3267
Sublattice <N(1, 1, 1, 3), N(1, 0, 0, 0), N(0, 1, 0, 0)>
3268
sage: rho.sublattice()
3269
Sublattice <N(-1, 1, 1, 3), N(0, 1, 0, 0)>
3270
sage: sigma.relative_quotient(rho)
3271
1-d lattice, quotient
3272
of Sublattice <N(1, 1, 1, 3), N(1, 0, 0, 0), N(0, 1, 0, 0)>
3273
by Sublattice <N(1, 0, -1, -3), N(0, 1, 0, 0)>
3274
sage: sigma.relative_quotient(rho).gens()
3275
(N[1, 0, 0, 0],)
3276
3277
More complicated example::
3278
3279
sage: rho = Cone([(1, 2, 3), (1, -1, 1)])
3280
sage: sigma = Cone([(1, 2, 3), (1, -1, 1), (-1, 1, 1), (-1, -1, 1)])
3281
sage: N_sigma = sigma.sublattice()
3282
sage: N_sigma
3283
Sublattice <N(3, 0, 1), N(2, 1, 0), N(1, 0, 0)>
3284
sage: N_rho = rho.sublattice()
3285
sage: N_rho
3286
Sublattice <N(1, -1, 1), N(0, 3, 2)>
3287
sage: sigma.relative_quotient(rho).gens()
3288
(N[0, -2, -1],)
3289
sage: N = rho.lattice()
3290
sage: N_sigma == N.span(N_rho.gens() + tuple(q.lift()
3291
... for q in sigma.relative_quotient(rho).gens()))
3292
True
3293
3294
Sign choice in the codimension one case::
3295
3296
sage: sigma1 = Cone([(1, 2, 3), (1, -1, 1), (-1, 1, 1), (-1, -1, 1)]) # 3d
3297
sage: sigma2 = Cone([(1, 1, -1), (1, 2, 3), (1, -1, 1), (1, -1, -1)]) # 3d
3298
sage: rho = sigma1.intersection(sigma2)
3299
sage: rho.sublattice()
3300
Sublattice <N(1, -1, 1), N(0, 3, 2)>
3301
sage: sigma1.relative_quotient(rho)
3302
1-d lattice, quotient
3303
of Sublattice <N(3, 0, 1), N(2, 1, 0), N(1, 0, 0)>
3304
by Sublattice <N(1, 2, 3), N(0, 3, 2)>
3305
sage: sigma1.relative_quotient(rho).gens()
3306
(N[0, -2, -1],)
3307
sage: sigma2.relative_quotient(rho).gens()
3308
(N[0, -1, -1],)
3309
"""
3310
try:
3311
cached_values = self._relative_quotient
3312
except AttributeError:
3313
self._relative_quotient = {}
3314
cached_values = self._relative_quotient
3315
3316
try:
3317
return cached_values[subcone]
3318
except KeyError:
3319
pass
3320
3321
Ncone = self.sublattice()
3322
Nsubcone = subcone.sublattice()
3323
3324
extra_ray = None
3325
if Ncone.dimension()-Nsubcone.dimension()==1:
3326
extra_ray = set(self.ray_set() - subcone.ray_set()).pop()
3327
3328
Q = Ncone.quotient(Nsubcone, positive_point=extra_ray)
3329
assert Q.is_torsion_free()
3330
cached_values[subcone] = Q
3331
return Q
3332
3333
def relative_orthogonal_quotient(self, supercone):
3334
r"""
3335
The quotient of the dual spanned lattice by the dual of the
3336
supercone's spanned lattice.
3337
3338
In the notation of [Fulton]_, if ``supercone`` = `\rho >
3339
\sigma` = ``self`` is a cone that contains `\sigma` as a face,
3340
then `M(\rho)` = ``supercone.orthogonal_sublattice()`` is a
3341
saturated sublattice of `M(\sigma)` =
3342
``self.orthogonal_sublattice()``. This method returns the
3343
quotient lattice. The lifts of the quotient generators are
3344
`\dim(\rho)-\dim(\sigma)` linearly independent M-lattice
3345
lattice points that, together with `M(\rho)`, generate
3346
`M(\sigma)`.
3347
3348
OUTPUT:
3349
3350
- :class:`toric lattice quotient
3351
<sage.geometry.toric_lattice.ToricLattice_quotient>`.
3352
3353
If we call the output ``Mrho``, then
3354
3355
- ``Mrho.cover() == self.orthogonal_sublattice()``, and
3356
3357
- ``Mrho.relations() == supercone.orthogonal_sublattice()``.
3358
3359
.. NOTE::
3360
3361
* `M(\sigma) / M(\rho)` has no torsion since the sublattice
3362
`M(\rho)` is saturated.
3363
3364
* In the codimension one case, (a lift of) the generator of
3365
`M(\sigma) / M(\rho)` is chosen to be positive on `\sigma`.
3366
3367
EXAMPLES::
3368
3369
sage: rho = Cone([(1,1,1,3),(1,-1,1,3),(-1,-1,1,3),(-1,1,1,3)])
3370
sage: rho.orthogonal_sublattice()
3371
Sublattice <M(0, 0, 3, -1)>
3372
sage: sigma = rho.facets()[1]
3373
sage: sigma.orthogonal_sublattice()
3374
Sublattice <M(0, 1, 1, 0), M(0, 3, 0, 1)>
3375
sage: sigma.is_face_of(rho)
3376
True
3377
sage: Q = sigma.relative_orthogonal_quotient(rho); Q
3378
1-d lattice, quotient
3379
of Sublattice <M(0, 1, 1, 0), M(0, 3, 0, 1)>
3380
by Sublattice <M(0, 0, 3, -1)>
3381
sage: Q.gens()
3382
(M[0, 1, 1, 0],)
3383
3384
Different codimension::
3385
3386
sage: rho = Cone([[1,-1,1,3],[-1,-1,1,3]])
3387
sage: sigma = rho.facets()[0]
3388
sage: sigma.orthogonal_sublattice()
3389
Sublattice <M(0, 1, 1, 0), M(1, 1, 0, 0), M(0, 3, 0, 1)>
3390
sage: rho.orthogonal_sublattice()
3391
Sublattice <M(0, 1, 1, 0), M(0, 3, 0, 1)>
3392
sage: sigma.relative_orthogonal_quotient(rho).gens()
3393
(M[-1, -1, 0, 0],)
3394
3395
Sign choice in the codimension one case::
3396
3397
sage: sigma1 = Cone([(1, 2, 3), (1, -1, 1), (-1, 1, 1), (-1, -1, 1)]) # 3d
3398
sage: sigma2 = Cone([(1, 1, -1), (1, 2, 3), (1, -1, 1), (1, -1, -1)]) # 3d
3399
sage: rho = sigma1.intersection(sigma2)
3400
sage: rho.relative_orthogonal_quotient(sigma1).gens()
3401
(M[-5, -2, 3],)
3402
sage: rho.relative_orthogonal_quotient(sigma2).gens()
3403
(M[5, 2, -3],)
3404
"""
3405
try:
3406
cached_values = self._relative_orthogonal_quotient
3407
except AttributeError:
3408
self._relative_orthogonal_quotient = {}
3409
cached_values = self._relative_orthogonal_quotient
3410
3411
try:
3412
return cached_values[supercone]
3413
except KeyError:
3414
pass
3415
3416
Mcone = self.orthogonal_sublattice()
3417
Msupercone = supercone.orthogonal_sublattice()
3418
3419
extra_ray = None
3420
if Mcone.dimension()-Msupercone.dimension()==1:
3421
extra_ray = set(supercone.ray_set() - self.ray_set()).pop()
3422
3423
Q = Mcone.quotient(Msupercone, positive_dual_point=extra_ray)
3424
assert Q.is_torsion_free()
3425
cached_values[supercone] = Q
3426
return Q
3427
3428
def semigroup_generators(self):
3429
r"""
3430
Return generators for the semigroup of lattice points of ``self``.
3431
3432
OUTPUT:
3433
3434
A tuple of lattice points generating the semigroup of lattice points
3435
contained in this cone.
3436
3437
.. note:: No attempt is made to return a minimal set of generators, see
3438
:meth:`Hilbert_basis` for that.
3439
3440
EXAMPLES:
3441
3442
The following command ensures that the output ordering in the examples
3443
below is independent of TOPCOM, you don't have to use it::
3444
3445
sage: PointConfiguration.set_engine('internal')
3446
3447
We start with a simple case of a non-smooth 2-dimensional cone::
3448
3449
sage: Cone([ (1,0), (1,2) ]).semigroup_generators()
3450
(N(1, 1), N(1, 0), N(1, 2))
3451
3452
A non-simplicial cone works, too::
3453
3454
sage: cone = Cone([(3,0,-1), (1,-1,0), (0,1,0), (0,0,1)])
3455
sage: cone.semigroup_generators()
3456
(N(1, 0, 0), N(0, 0, 1), N(0, 1, 0), N(3, 0, -1), N(1, -1, 0))
3457
3458
GAP's toric package thinks this is challenging::
3459
3460
sage: cone = Cone([[1,2,3,4],[0,1,0,7],[3,1,0,2],[0,0,1,0]]).dual()
3461
sage: len( cone.semigroup_generators() )
3462
2806
3463
3464
The cone need not be strictly convex::
3465
3466
sage: halfplane = Cone([(1,0),(2,1),(-1,0)])
3467
sage: halfplane.semigroup_generators()
3468
(N(0, 1), N(1, 0), N(-1, 0))
3469
sage: line = Cone([(1,1,1),(-1,-1,-1)])
3470
sage: line.semigroup_generators()
3471
(N(1, 1, 1), N(-1, -1, -1))
3472
sage: wedge = Cone([ (1,0,0), (1,2,0), (0,0,1), (0,0,-1) ])
3473
sage: wedge.semigroup_generators()
3474
(N(1, 0, 0), N(1, 1, 0), N(1, 2, 0), N(0, 0, 1), N(0, 0, -1))
3475
3476
Nor does it have to be full-dimensional (see
3477
http://trac.sagemath.org/sage_trac/ticket/11312)::
3478
3479
sage: Cone([(1,1,0), (-1,1,0)]).semigroup_generators()
3480
(N(0, 1, 0), N(1, 1, 0), N(-1, 1, 0))
3481
3482
Neither full-dimensional nor simplicial::
3483
3484
sage: A = matrix([(1, 3, 0), (-1, 0, 1), (1, 1, -2), (15, -2, 0)])
3485
sage: A.elementary_divisors()
3486
[1, 1, 1, 0]
3487
sage: cone3d = Cone([(3,0,-1), (1,-1,0), (0,1,0), (0,0,1)])
3488
sage: rays = [ A*vector(v) for v in cone3d.rays() ]
3489
sage: gens = Cone(rays).semigroup_generators(); gens
3490
(N(1, -1, 1, 15), N(0, 1, -2, 0), N(-2, -1, 0, 17), N(3, -4, 5, 45), N(3, 0, 1, -2))
3491
sage: set(map(tuple,gens)) == set([ tuple(A*r) for r in cone3d.semigroup_generators() ])
3492
True
3493
3494
TESTS::
3495
3496
sage: len(Cone(identity_matrix(10).rows()).semigroup_generators())
3497
10
3498
3499
sage: trivial_cone = Cone([], lattice=ToricLattice(3))
3500
sage: trivial_cone.semigroup_generators()
3501
()
3502
3503
ALGORITHM:
3504
3505
If the cone is not simplicial, it is first triangulated. Each
3506
simplicial subcone has the integral points of the spaned
3507
parallelotope as generators. This is the first step of the
3508
primal Normaliz algorithm, see [Normaliz]_. For each
3509
simplicial cone (of dimension `d`), the integral points of the
3510
open parallelotope
3511
3512
.. math::
3513
3514
par \langle x_1, \dots, x_d \rangle =
3515
\ZZ^n \cap
3516
\left\{
3517
q_1 x_1 + \cdots +q_d x_d
3518
:~
3519
0 \leq q_i < 1
3520
\right\}
3521
3522
are then computed [BrunsKoch]_.
3523
3524
Finally, the the union of the generators of all simplicial
3525
subcones is returned.
3526
3527
REFERENCES:
3528
3529
.. [BrunsKoch]
3530
W. Bruns and R. Koch,
3531
Computing the integral closure of an affine semigroup.
3532
Uni. Iaggelonicae Acta Math. 39, (2001), 59-70
3533
"""
3534
# if the cone is not simplicial, triangulate and run
3535
# recursively
3536
N = self.lattice()
3537
if not self.is_simplicial():
3538
from sage.geometry.triangulation.point_configuration import PointConfiguration
3539
origin = self.nrays() # last one in pc
3540
pc = PointConfiguration(self.rays() + (N(0),), star=origin)
3541
triangulation = pc.triangulate()
3542
subcones = [ Cone([self.ray(i) for i in simplex if i!=origin],
3543
lattice=N, check=False)
3544
for simplex in triangulation ]
3545
gens = set()
3546
for cone in subcones:
3547
gens.update(cone.semigroup_generators())
3548
return tuple(gens)
3549
3550
gens = list(parallelotope_points(self.rays(), N)) + list(self.rays())
3551
gens = filter(lambda v:gcd(v)==1, gens)
3552
return tuple(gens)
3553
3554
def Hilbert_basis(self):
3555
r"""
3556
Return the Hilbert basis of the cone.
3557
3558
Given a strictly convex cone `C\subset \RR^d`, the Hilbert
3559
basis of `C` is the set of all irreducible elements in the
3560
semigroup `C\cap \ZZ^d`. It is the unique minimal generating
3561
set over `\ZZ` for the integral points `C\cap \ZZ^d`.
3562
3563
If the cone `C` is not strictly convex, this method finds the
3564
(unique) minimial set of lattice points that need to be added
3565
to the defining rays of the cone to generate the whole
3566
semigroup `C\cap \ZZ^d`. But because the rays of the cone are
3567
not unique nor necessarily minimal in this case, neither is
3568
the returned generating set (consisting of the rays plus
3569
additional generators).
3570
3571
See also :meth:`semigroup_generators` if you are not
3572
interested in a minimal set of generators.
3573
3574
OUTPUT:
3575
3576
A tuple of lattice points. The rays of the cone are the first
3577
``self.nrays()`` entries.
3578
3579
EXAMPLES:
3580
3581
The following command ensures that the output ordering in the examples
3582
below is independent of TOPCOM, you don't have to use it::
3583
3584
sage: PointConfiguration.set_engine('internal')
3585
3586
We start with a simple case of a non-smooth 2-dimensional cone::
3587
3588
sage: Cone([ (1,0), (1,2) ]).Hilbert_basis()
3589
(N(1, 0), N(1, 2), N(1, 1))
3590
3591
Two more complicated example from GAP/toric::
3592
3593
sage: Cone([[1,0],[3,4]]).dual().Hilbert_basis()
3594
(M(0, 1), M(4, -3), M(3, -2), M(2, -1), M(1, 0))
3595
sage: cone = Cone([[1,2,3,4],[0,1,0,7],[3,1,0,2],[0,0,1,0]]).dual()
3596
sage: cone.Hilbert_basis() # long time
3597
(M(10, -7, 0, 1), M(-5, 21, 0, -3), M(0, -2, 0, 1), M(15, -63, 25, 9),
3598
M(2, -3, 0, 1), M(1, -4, 1, 1), M(-1, 3, 0, 0), M(4, -4, 0, 1),
3599
M(1, -5, 2, 1), M(3, -5, 1, 1), M(6, -5, 0, 1), M(3, -13, 5, 2),
3600
M(2, -6, 2, 1), M(5, -6, 1, 1), M(0, 1, 0, 0), M(8, -6, 0, 1),
3601
M(-2, 8, 0, -1), M(10, -42, 17, 6), M(7, -28, 11, 4), M(5, -21, 9, 3),
3602
M(6, -21, 8, 3), M(5, -14, 5, 2), M(2, -7, 3, 1), M(4, -7, 2, 1),
3603
M(7, -7, 1, 1), M(0, 0, 1, 0), M(-3, 14, 0, -2), M(-1, 7, 0, -1),
3604
M(1, 0, 0, 0))
3605
3606
Not a strictly convex cone::
3607
3608
sage: wedge = Cone([ (1,0,0), (1,2,0), (0,0,1), (0,0,-1) ])
3609
sage: wedge.semigroup_generators()
3610
(N(1, 0, 0), N(1, 1, 0), N(1, 2, 0), N(0, 0, 1), N(0, 0, -1))
3611
sage: wedge.Hilbert_basis()
3612
(N(1, 2, 0), N(1, 0, 0), N(0, 0, 1), N(0, 0, -1), N(1, 1, 0))
3613
3614
Not full-dimensional cones are ok, too (see
3615
http://trac.sagemath.org/sage_trac/ticket/11312)::
3616
3617
sage: Cone([(1,1,0), (-1,1,0)]).Hilbert_basis()
3618
(N(1, 1, 0), N(-1, 1, 0), N(0, 1, 0))
3619
3620
ALGORITHM:
3621
3622
The primal Normaliz algorithm, see [Normaliz]_.
3623
3624
REFERENCES:
3625
3626
.. [Normaliz]
3627
Winfried Bruns, Bogdan Ichim, and Christof Soeger:
3628
Normaliz.
3629
http://www.mathematik.uni-osnabrueck.de/normaliz/
3630
"""
3631
if '_Hilbert_basis' in self.__dict__:
3632
return self._Hilbert_basis
3633
3634
if self.is_strictly_convex():
3635
def not_in_linear_subspace(x): return True
3636
else:
3637
linear_subspace = self.linear_subspace()
3638
def not_in_linear_subspace(x): return not x in linear_subspace
3639
3640
irreducible = list(self.rays()) # these are irreducible for sure
3641
gens = list(self.semigroup_generators())
3642
for x in irreducible:
3643
try:
3644
gens.remove(x)
3645
except ValueError:
3646
pass
3647
3648
while gens:
3649
x = gens.pop()
3650
if any(not_in_linear_subspace(y) and x-y in self
3651
for y in irreducible+gens):
3652
continue
3653
irreducible.append(x)
3654
3655
Hilbert_basis = tuple(irreducible)
3656
self._Hilbert_basis = Hilbert_basis
3657
return Hilbert_basis
3658
3659
def Hilbert_coefficients(self, point):
3660
r"""
3661
Return the expansion coefficients of ``point`` with respect to
3662
:meth:`Hilbert_basis`.
3663
3664
INPUT:
3665
3666
- ``point`` -- a :meth:`lattice` point in the cone, or
3667
something that can be converted to a point. For example, a
3668
list or tuple of integers.
3669
3670
OUTPUT:
3671
3672
A `\ZZ`-vector of length ``len(self.Hilbert_basis())`` with nonnegative
3673
components.
3674
3675
.. note::
3676
3677
Since the Hilbert basis elements are not necessarily linearly
3678
independent, the expansion coefficients are not unique. However,
3679
this method will always return the same expansion coefficients when
3680
invoked with the same argument.
3681
3682
EXAMPLES::
3683
3684
sage: cone = Cone([(1,0),(0,1)])
3685
sage: cone.rays()
3686
(N(1, 0), N(0, 1))
3687
sage: cone.Hilbert_coefficients([3,2])
3688
(3, 2)
3689
3690
A more complicated example::
3691
3692
sage: N = ToricLattice(2)
3693
sage: cone = Cone([N(1,0),N(1,2)])
3694
sage: cone.Hilbert_basis()
3695
(N(1, 0), N(1, 2), N(1, 1))
3696
sage: cone.Hilbert_coefficients( N(1,1) )
3697
(0, 0, 1)
3698
3699
The cone need not be strictly convex::
3700
3701
sage: N = ToricLattice(3)
3702
sage: cone = Cone([N(1,0,0),N(1,2,0),N(0,0,1),N(0,0,-1)])
3703
sage: cone.Hilbert_basis()
3704
(N(1, 2, 0), N(1, 0, 0), N(0, 0, 1), N(0, 0, -1), N(1, 1, 0))
3705
sage: cone.Hilbert_coefficients( N(1,1,3) )
3706
(0, 0, 3, 0, 1)
3707
"""
3708
if self.is_strictly_convex():
3709
def in_linear_subspace(x): return False
3710
else:
3711
linear_subspace = self.linear_subspace()
3712
def in_linear_subspace(x): return x in linear_subspace
3713
3714
point = self.lattice()(point)
3715
assert point in self, 'The given point is not in the cone!'
3716
basis = self.Hilbert_basis()
3717
3718
from sage.numerical.mip import MixedIntegerLinearProgram
3719
p = MixedIntegerLinearProgram(maximization=False)
3720
p.set_objective(None)
3721
x = p.new_variable(integer=True)
3722
x = [ x[i] for i in range(0,len(basis)) ]
3723
for i in range(0,self.lattice_dim()):
3724
p.add_constraint(sum(b[i]*x[j] for j,b in enumerate(basis)) == point[i])
3725
p.solve()
3726
3727
return vector(ZZ, p.get_values(x))
3728
3729
3730
3731