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