Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/geometry/fan.py
4059 views
1
r"""
2
Rational polyhedral fans
3
4
This module was designed as a part of the framework for toric varieties
5
(:mod:`~sage.schemes.toric.variety`,
6
:mod:`~sage.schemes.toric.fano_variety`). While the emphasis is on
7
complete full-dimensional fans, arbitrary fans are supported. Work
8
with distinct lattices. The default lattice is :class:`ToricLattice
9
<sage.geometry.toric_lattice.ToricLatticeFactory>` `N` of the appropriate
10
dimension. The only case when you must specify lattice explicitly is creation
11
of a 0-dimensional fan, where dimension of the ambient space cannot be
12
guessed.
13
14
A **rational polyhedral fan** is a *finite* collection of *strictly* convex
15
rational polyhedral cones, such that the intersection of any two cones of the
16
fan is a face of each of them and each face of each cone is also a cone of the
17
fan.
18
19
AUTHORS:
20
21
- Andrey Novoseltsev (2010-05-15): initial version.
22
23
- Andrey Novoseltsev (2010-06-17): substantial improvement during review by
24
Volker Braun.
25
26
EXAMPLES:
27
28
Use :func:`Fan` to construct fans "explicitly"::
29
30
sage: fan = Fan(cones=[(0,1), (1,2)],
31
... rays=[(1,0), (0,1), (-1,0)])
32
sage: fan
33
Rational polyhedral fan in 2-d lattice N
34
35
In addition to giving such lists of cones and rays you can also create cones
36
first using :func:`~sage.geometry.cone.Cone` and then combine them into a fan.
37
See the documentation of :func:`Fan` for details.
38
39
Instead of building a fan from scratch, for this tutorial we will use an easy
40
way to get two fans assosiated to :class:`lattice polytopes
41
<sage.geometry.lattice_polytope.LatticePolytopeClass>`: :func:`FaceFan` and
42
:func:`NormalFan`::
43
44
sage: fan1 = FaceFan(lattice_polytope.octahedron(3))
45
sage: fan2 = NormalFan(lattice_polytope.octahedron(3))
46
47
Given such "automatic" fans, you may wonder what are their rays and cones::
48
49
sage: fan1.rays()
50
(N(1, 0, 0), N(0, 1, 0), N(0, 0, 1),
51
N(-1, 0, 0), N(0, -1, 0), N(0, 0, -1))
52
sage: fan1.ray_matrix()
53
[ 1 0 0 -1 0 0]
54
[ 0 1 0 0 -1 0]
55
[ 0 0 1 0 0 -1]
56
sage: fan1.generating_cones()
57
(3-d cone of Rational polyhedral fan in 3-d lattice N,
58
3-d cone of Rational polyhedral fan in 3-d lattice N,
59
3-d cone of Rational polyhedral fan in 3-d lattice N,
60
3-d cone of Rational polyhedral fan in 3-d lattice N,
61
3-d cone of Rational polyhedral fan in 3-d lattice N,
62
3-d cone of Rational polyhedral fan in 3-d lattice N,
63
3-d cone of Rational polyhedral fan in 3-d lattice N,
64
3-d cone of Rational polyhedral fan in 3-d lattice N)
65
66
The last output is not very illuminating. Let's try to improve it::
67
68
sage: for cone in fan1: print cone.rays()
69
(N(1, 0, 0), N(0, 1, 0), N(0, 0, -1))
70
(N(0, 1, 0), N(-1, 0, 0), N(0, 0, -1))
71
(N(1, 0, 0), N(0, -1, 0), N(0, 0, -1))
72
(N(-1, 0, 0), N(0, -1, 0), N(0, 0, -1))
73
(N(1, 0, 0), N(0, 1, 0), N(0, 0, 1))
74
(N(0, 1, 0), N(0, 0, 1), N(-1, 0, 0))
75
(N(1, 0, 0), N(0, 0, 1), N(0, -1, 0))
76
(N(0, 0, 1), N(-1, 0, 0), N(0, -1, 0))
77
78
You can also do ::
79
80
sage: for cone in fan1: print cone.ambient_ray_indices()
81
(0, 1, 5)
82
(1, 3, 5)
83
(0, 4, 5)
84
(3, 4, 5)
85
(0, 1, 2)
86
(1, 2, 3)
87
(0, 2, 4)
88
(2, 3, 4)
89
90
to see indices of rays of the fan corresponding to each cone.
91
92
While the above cycles were over "cones in fan", it is obvious that we did not
93
get ALL the cones: every face of every cone in a fan must also be in the fan,
94
but all of the above cones were of dimension three. The reason for this
95
behaviour is that in many cases it is enough to work with generating cones of
96
the fan, i.e. cones which are not faces of bigger cones. When you do need to
97
work with lower dimensional cones, you can easily get access to them using
98
:meth:`~sage.geometry.fan.RationalPolyhedralFan.cones`::
99
100
sage: [cone.ambient_ray_indices() for cone in fan1.cones(2)]
101
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (0, 4),
102
(2, 4), (3, 4), (1, 5), (3, 5), (4, 5), (0, 5)]
103
104
In fact, you don't have to type ``.cones``::
105
106
sage: [cone.ambient_ray_indices() for cone in fan1(2)]
107
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (0, 4),
108
(2, 4), (3, 4), (1, 5), (3, 5), (4, 5), (0, 5)]
109
110
You may also need to know the inclusion relations between all of the cones of
111
the fan. In this case check out
112
:meth:`~sage.geometry.fan.RationalPolyhedralFan.cone_lattice`::
113
114
sage: L = fan1.cone_lattice()
115
sage: L
116
Finite poset containing 28 elements
117
sage: L.bottom()
118
0-d cone of Rational polyhedral fan in 3-d lattice N
119
sage: L.top()
120
Rational polyhedral fan in 3-d lattice N
121
sage: cone = L.level_sets()[2][0]
122
sage: cone
123
2-d cone of Rational polyhedral fan in 3-d lattice N
124
sage: L.hasse_diagram().neighbors(cone)
125
[3-d cone of Rational polyhedral fan in 3-d lattice N,
126
3-d cone of Rational polyhedral fan in 3-d lattice N,
127
1-d cone of Rational polyhedral fan in 3-d lattice N,
128
1-d cone of Rational polyhedral fan in 3-d lattice N]
129
130
Note, that while ``cone`` above seems to be a "cone", it is not::
131
132
sage: cone.rays()
133
Traceback (most recent call last):
134
...
135
AttributeError: 'FinitePoset_with_category.element_class' object has no attribute 'rays'
136
137
To get your hands on the "real" cone, you need to do one more step::
138
139
sage: cone = cone.element
140
sage: cone.rays()
141
(N(1, 0, 0), N(0, 1, 0))
142
143
You can check how "good" a fan is::
144
145
sage: fan1.is_complete()
146
True
147
sage: fan1.is_simplicial()
148
True
149
sage: fan1.is_smooth()
150
True
151
152
The face fan of the octahedron is really good! Time to remember that we have
153
also constructed its normal fan::
154
155
sage: fan2.is_complete()
156
True
157
sage: fan2.is_simplicial()
158
False
159
sage: fan2.is_smooth()
160
False
161
162
This one does have some "problems," but we can fix them::
163
164
sage: fan3 = fan2.make_simplicial()
165
sage: fan3.is_simplicial()
166
True
167
sage: fan3.is_smooth()
168
False
169
170
Note that we had to save the result of
171
:meth:`~sage.geometry.fan.RationalPolyhedralFan.make_simplicial` in a new fan.
172
Fans in Sage are immutable, so any operation that does change them constructs
173
a new fan.
174
175
We can also make ``fan3`` smooth, but it will take a bit more work::
176
177
sage: cube = lattice_polytope.octahedron(3).polar()
178
sage: sk = cube.skeleton_points(2)
179
sage: rays = [cube.point(p) for p in sk]
180
sage: fan4 = fan3.subdivide(new_rays=rays)
181
sage: fan4.is_smooth()
182
True
183
184
Let's see how "different" are ``fan2`` and ``fan4``::
185
186
sage: fan2.ngenerating_cones()
187
6
188
sage: fan2.nrays()
189
8
190
sage: fan4.ngenerating_cones()
191
48
192
sage: fan4.nrays()
193
26
194
195
Smoothness does not come for free!
196
197
Please take a look at the rest of the available functions below and their
198
complete descriptions. If you need any features that are missing, feel free to
199
suggest them. (Or implement them on your own and submit a patch to Sage for
200
inclusion!)
201
"""
202
203
204
#*****************************************************************************
205
# Copyright (C) 2010 Andrey Novoseltsev <[email protected]>
206
# Copyright (C) 2010 William Stein <[email protected]>
207
#
208
# Distributed under the terms of the GNU General Public License (GPL)
209
#
210
# http://www.gnu.org/licenses/
211
#*****************************************************************************
212
213
214
import collections
215
import warnings
216
import copy
217
218
from sage.combinat.combination import Combinations
219
from sage.combinat.posets.posets import FinitePoset
220
from sage.geometry.cone import (Cone,
221
ConvexRationalPolyhedralCone,
222
IntegralRayCollection,
223
is_Cone,
224
normalize_rays)
225
from sage.geometry.hasse_diagram import Hasse_diagram_from_incidences
226
from sage.geometry.lattice_polytope import (LatticePolytope,
227
all_faces,
228
all_facet_equations)
229
from sage.geometry.toric_lattice import is_ToricLattice
230
from sage.geometry.toric_plotter import ToricPlotter
231
from sage.graphs.all import DiGraph
232
from sage.matrix.all import matrix
233
from sage.misc.all import cached_method, flatten, walltime, prod
234
from sage.misc.misc import deprecation
235
from sage.modules.all import vector
236
from sage.rings.all import QQ, RR, ZZ
237
from sage.structure.all import Sequence
238
from sage.structure.coerce import parent
239
240
241
def is_Fan(x):
242
r"""
243
Check if ``x`` is a Fan.
244
245
INPUT:
246
247
- ``x`` -- anything.
248
249
OUTPUT:
250
251
- ``True`` if ``x`` is a fan and ``False`` otherwise.
252
253
EXAMPLES::
254
255
sage: from sage.geometry.fan import is_Fan
256
sage: is_Fan(1)
257
False
258
sage: fan = FaceFan(lattice_polytope.octahedron(2))
259
sage: fan
260
Rational polyhedral fan in 2-d lattice N
261
sage: is_Fan(fan)
262
True
263
"""
264
return isinstance(x, RationalPolyhedralFan)
265
266
267
def Fan(cones, rays=None, lattice=None, check=True, normalize=True,
268
is_complete=None, discard_faces=False, **kwds):
269
r"""
270
Construct a rational polyhedral fan.
271
272
.. NOTE::
273
274
Approximate time to construct a fan consisting of `n` cones is `n^2/5`
275
seconds. That is half an hour for 100 cones. This time can be
276
significantly reduced in the future, but it is still likely to be
277
`\sim n^2` (with, say, `/500` instead of `/5`). If you know that your
278
input does form a valid fan, use ``check=False`` option to skip
279
consistency checks.
280
281
INPUT:
282
283
- ``cones`` -- list of either
284
:class:`Cone<sage.geometry.cone.ConvexRationalPolyhedralCone>` objects
285
or lists of integers interpreted as indices of generating rays in
286
``rays``. These must be only **maximal** cones of the fan, unless
287
``discard_faces=True`` option is specified;
288
289
- ``rays`` -- list of rays given as list or vectors convertible to the
290
rational extension of ``lattice``. If ``cones`` are given by
291
:class:`Cone<sage.geometry.cone.ConvexRationalPolyhedralCone>` objects
292
``rays`` may be determined automatically. You still may give them
293
explicitly to ensure a particular order of rays in the fan. In this case
294
you must list all rays that appear in ``cones``. You can give "extra"
295
ones if it is convenient (e.g. if you have a big list of rays for
296
several fans), but all "extra" rays will be discarded;
297
298
- ``lattice`` -- :class:`ToricLattice
299
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
300
other object that behaves like these. If not specified, an attempt will
301
be made to determine an appropriate toric lattice automatically;
302
303
- ``check`` -- by default the input data will be checked for correctness
304
(e.g. that intersection of any two given cones is a face of each). If you
305
know for sure that the input is correct, you may significantly decrease
306
construction time using ``check=False`` option;
307
308
- ``normalize`` -- you can further speed up construction using
309
``normalize=False`` option. In this case ``cones`` must be a list of
310
**sorted** :class:`tuples` and ``rays`` must be immutable primitive
311
vectors in ``lattice``. In general, you should not use this option, it
312
is designed for code optimization and does not give as drastic
313
improvement in speed as the previous one;
314
315
- ``is_complete`` -- every fan can determine on its own if it is complete
316
or not, however it can take quite a bit of time for "big" fans with many
317
generating cones. On the other hand, in some situations it is known in
318
advance that a certain fan is complete. In this case you can pass
319
``is_complete=True`` option to speed up some computations. You may also
320
pass ``is_complete=False`` option, although it is less likely to be
321
beneficial. Of course, passing a wrong value can compromise the
322
integrity of data structures of the fan and lead to wrong results, so
323
you should be very careful if you decide to use this option;
324
325
- ``discard_faces`` -- by default, the fan constructor expects the list of
326
**maximal** cones. If you provide "extra" ones and leave ``check=True``
327
(default), an exception will be raised. If you provide "extra" cones and
328
set ``check=False``, you may get wrong results as assumptions on internal
329
data structures will be invalid. If you want the fan constructor to
330
select the maximal cones from the given input, you may provide
331
``discard_faces=True`` option (it works both for ``check=True`` and
332
``check=False``).
333
334
OUTPUT:
335
336
- a :class:`fan <RationalPolyhedralFan>`.
337
338
EXAMPLES:
339
340
Let's construct a fan corresponding to the projective plane in several
341
ways::
342
343
sage: cone1 = Cone([(1,0), (0,1)])
344
sage: cone2 = Cone([(0,1), (-1,-1)])
345
sage: cone3 = Cone([(-1,-1), (1,0)])
346
sage: P2 = Fan([cone1, cone2, cone2])
347
Traceback (most recent call last):
348
...
349
ValueError: you have provided 3 cones, but only 2 of them are maximal!
350
Use discard_faces=True if you indeed need to construct a fan from
351
these cones.
352
353
Oops! There was a typo and ``cone2`` was listed twice as a generating cone
354
of the fan. If it was intentional (e.g. the list of cones was generated
355
automatically and it is possible that it contains repetitions or faces of
356
other cones), use ``discard_faces=True`` option::
357
358
sage: P2 = Fan([cone1, cone2, cone2], discard_faces=True)
359
sage: P2.ngenerating_cones()
360
2
361
362
However, in this case it was definitely a typo, since the fan of
363
`\mathbb{P}^2` has 3 maximal cones::
364
365
sage: P2 = Fan([cone1, cone2, cone3])
366
sage: P2.ngenerating_cones()
367
3
368
369
Looks better. An alternative way is ::
370
371
sage: rays = [(1,0), (0,1), (-1,-1)]
372
sage: cones = [(0,1), (1,2), (2,0)]
373
sage: P2a = Fan(cones, rays)
374
sage: P2a.ngenerating_cones()
375
3
376
sage: P2 == P2a
377
False
378
379
That may seem wrong, but it is not::
380
381
sage: P2.is_equivalent(P2a)
382
True
383
384
See :meth:`~RationalPolyhedralFan.is_equivalent` for details.
385
386
Yet another way to construct this fan is ::
387
388
sage: P2b = Fan(cones, rays, check=False)
389
sage: P2b.ngenerating_cones()
390
3
391
sage: P2a == P2b
392
True
393
394
If you try the above examples, you are likely to notice the difference in
395
speed, so when you are sure that everything is correct, it is a good idea
396
to use ``check=False`` option. On the other hand, it is usually **NOT** a
397
good idea to use ``normalize=False`` option::
398
399
sage: P2c = Fan(cones, rays, check=False, normalize=False)
400
Traceback (most recent call last):
401
...
402
AttributeError: 'tuple' object has no attribute 'parent'
403
404
Yet another way is to use functions :func:`FaceFan` and :func:`NormalFan`
405
to construct fans from :class:`lattice polytopes
406
<sage.geometry.lattice_polytope.LatticePolytopeClass>`.
407
408
We have not yet used ``lattice`` argument, since if was determined
409
automatically::
410
411
sage: P2.lattice()
412
2-d lattice N
413
sage: P2b.lattice()
414
2-d lattice N
415
416
However, it is necessary to specify it explicitly if you want to construct
417
a fan without rays or cones::
418
419
sage: Fan([], [])
420
Traceback (most recent call last):
421
...
422
ValueError: you must specify the lattice
423
when you construct a fan without rays and cones!
424
sage: F = Fan([], [], lattice=ToricLattice(2, "L"))
425
sage: F
426
Rational polyhedral fan in 2-d lattice L
427
sage: F.lattice_dim()
428
2
429
sage: F.dim()
430
0
431
"""
432
if "discard_warning" in kwds:
433
deprecation("discard_warning is deprecated, use discard_faces instead.",
434
"Sage Version 4.7.2")
435
discard_faces = not discard_warning
436
kwds.pop(discard_warning)
437
if kwds:
438
raise ValueError("unrecognized keywords: %s" % kwds)
439
440
if not check and not normalize and not discard_faces:
441
return RationalPolyhedralFan(cones, rays, lattice, is_complete)
442
if not isinstance(cones, list):
443
try:
444
cones = list(cones)
445
except TypeError:
446
raise TypeError(
447
"cones must be given as an iterable!"
448
"\nGot: %s" % cones)
449
if not cones:
450
if lattice is None:
451
if rays:
452
lattice = normalize_rays(rays, lattice)[0].parent()
453
else:
454
raise ValueError("you must specify the lattice when you "
455
"construct a fan without rays and cones!")
456
return RationalPolyhedralFan(cones=((), ), rays=(), lattice=lattice)
457
if is_Cone(cones[0]):
458
# Construct the fan from Cone objects
459
if lattice is None:
460
lattice = cones[0].lattice()
461
# If we determine the lattice automatically, we don't want to force
462
# any conversion. TODO: take into account coercions?
463
if check:
464
for cone in cones:
465
if cone.lattice() != lattice:
466
raise ValueError("cones belong to different lattices "
467
"(%s and %s), cannot determine the lattice of the "
468
"fan!" % (lattice, cone.lattice()))
469
for i, cone in enumerate(cones):
470
if cone.lattice() != lattice:
471
cones[i] = Cone(cone.rays(), lattice, check=False)
472
if check:
473
for cone in cones:
474
if not cone.is_strictly_convex():
475
raise ValueError(
476
"cones of a fan must be strictly convex!")
477
# Optimization for fans generated by a single cone
478
if len(cones) == 1:
479
cone = cones[0]
480
return RationalPolyhedralFan((tuple(range(cone.nrays())), ),
481
cone.rays(), lattice,
482
is_complete=lattice.dimension() == 0)
483
ray_set = set([])
484
for cone in cones:
485
ray_set.update(cone.rays())
486
if rays: # Preserve the initial order of rays, if they were given
487
rays = normalize_rays(rays, lattice)
488
new_rays = []
489
for ray in rays:
490
if ray in ray_set and ray not in new_rays:
491
new_rays.append(ray)
492
if len(new_rays) != len(ray_set):
493
raise ValueError(
494
"if rays are given, they must include all rays of the fan!")
495
rays = new_rays
496
else:
497
rays = tuple(ray_set)
498
if check:
499
# Maybe we should compute all faces of all cones and save them for
500
# later if we are doing this check?
501
generating_cones = []
502
for cone in sorted(cones, key=lambda cone: cone.dim(),
503
reverse=True):
504
is_generating = True
505
for g_cone in generating_cones:
506
i_cone = cone.intersection(g_cone)
507
if i_cone.is_face_of(cone) and i_cone.is_face_of(g_cone):
508
if i_cone.dim() == cone.dim():
509
is_generating = False # cone is a face of g_cone
510
break
511
else:
512
raise ValueError(
513
"these cones cannot belong to the same fan!"
514
"\nCone 1 rays: %s\nCone 2 rays: %s"
515
% (g_cone.rays(), cone.rays()))
516
if is_generating:
517
generating_cones.append(cone)
518
if len(cones) > len(generating_cones):
519
if discard_faces:
520
cones = generating_cones
521
else:
522
raise ValueError("you have provided %d cones, but only %d "
523
"of them are maximal! Use discard_faces=True if you "
524
"indeed need to construct a fan from these cones." %
525
(len(cones), len(generating_cones)))
526
elif discard_faces:
527
cones = _discard_faces(cones)
528
return RationalPolyhedralFan(
529
(tuple(sorted(rays.index(ray) for ray in cone.rays()))
530
for cone in cones), rays, lattice, is_complete)
531
# Construct the fan from rays and "tuple cones"
532
rays = normalize_rays(rays, lattice)
533
for n, cone in enumerate(cones):
534
try:
535
cones[n] = sorted(cone)
536
except TypeError:
537
raise TypeError("cannot interpret %s as a cone!" % cone)
538
if not check and not discard_faces:
539
return RationalPolyhedralFan(cones, rays, lattice, is_complete)
540
# If we do need to make all the check, build explicit cone objects first
541
return Fan((Cone([rays[n] for n in cone], lattice) for cone in cones),
542
rays, lattice, is_complete=is_complete,
543
discard_faces=discard_faces)
544
545
546
def FaceFan(polytope, lattice=None):
547
r"""
548
Construct the face fan of the given lattice ``polytope``.
549
550
INPUT:
551
552
- ``polytope`` -- :class:`lattice polytope
553
<sage.geometry.lattice_polytope.LatticePolytopeClass>`;
554
555
- ``lattice`` -- :class:`ToricLattice
556
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
557
other object that behaves like these. If not specified, an attempt will
558
be made to determine an appropriate toric lattice automatically.
559
560
OUTPUT:
561
562
- :class:`rational polyhedral fan <RationalPolyhedralFan>`.
563
564
See also :func:`NormalFan`.
565
566
EXAMPLES:
567
568
Let's construct the fan corresponding to the product of two projective
569
lines::
570
571
sage: diamond = lattice_polytope.octahedron(2)
572
sage: P1xP1 = FaceFan(diamond)
573
sage: P1xP1.ray_matrix()
574
[ 1 0 -1 0]
575
[ 0 1 0 -1]
576
sage: for cone in P1xP1: print cone.rays()
577
(N(1, 0), N(0, -1))
578
(N(-1, 0), N(0, -1))
579
(N(1, 0), N(0, 1))
580
(N(0, 1), N(-1, 0))
581
"""
582
if any(d <= 0 for d in polytope.distances([0]*polytope.dim())):
583
raise ValueError("face fans are defined only for polytopes containing"
584
"the origin as an interior point!")
585
cones = (facet.vertices() for facet in polytope.facets())
586
rays = polytope.vertices().columns(copy=False)
587
fan = Fan(cones, rays, lattice=lattice, check=False)
588
fan._is_complete = polytope.dim() == polytope.ambient_dim()
589
return fan
590
591
592
def NormalFan(polytope, lattice=None):
593
r"""
594
Construct the normal fan of the given lattice ``polytope``.
595
596
INPUT:
597
598
- ``polytope`` -- :class:`lattice polytope
599
<sage.geometry.lattice_polytope.LatticePolytopeClass>`;
600
601
- ``lattice`` -- :class:`ToricLattice
602
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
603
other object that behaves like these. If not specified, an attempt will
604
be made to determine an appropriate toric lattice automatically.
605
606
OUTPUT:
607
608
- :class:`rational polyhedral fan <RationalPolyhedralFan>`.
609
610
See also :func:`FaceFan`.
611
612
EXAMPLES:
613
614
Let's construct the fan corresponding to the product of two projective
615
lines::
616
617
sage: square = lattice_polytope.octahedron(2).polar()
618
sage: P1xP1 = NormalFan(square)
619
sage: P1xP1.ray_matrix()
620
[ 1 0 -1 0]
621
[ 0 1 0 -1]
622
sage: for cone in P1xP1: print cone.rays()
623
(N(1, 0), N(0, -1))
624
(N(-1, 0), N(0, -1))
625
(N(1, 0), N(0, 1))
626
(N(0, 1), N(-1, 0))
627
"""
628
rays = (polytope.facet_normal(i) for i in range(polytope.nfacets()))
629
cones = (vertex.facets() for vertex in polytope.faces(dim=0))
630
fan = Fan(cones, rays, lattice=lattice, check=False)
631
fan._is_complete = polytope.dim() == polytope.ambient_dim()
632
return fan
633
634
635
class Cone_of_fan(ConvexRationalPolyhedralCone):
636
r"""
637
Construct a cone belonging to a fan.
638
639
.. WARNING::
640
641
This class does not check that the input defines a valid cone of a
642
fan. You must not construct objects of this class directly.
643
644
In addition to all of the properties of "regular" :class:`cones
645
<sage.geometry.cone.ConvexRationalPolyhedralCone>`, such cones know their
646
relation to the fan.
647
648
INPUT:
649
650
- ``ambient`` -- fan whose cone is constructed;
651
652
- ``ambient_ray_indices`` -- increasing list or tuple of integers, indices
653
of rays of ``ambient`` generating this cone.
654
655
OUTPUT:
656
657
- cone of ``ambient``.
658
659
EXAMPLES:
660
661
The intended way to get objects of this class is the following::
662
663
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
664
sage: cone = P1xP1.generating_cone(0)
665
sage: cone
666
2-d cone of Rational polyhedral fan in 2-d lattice N
667
sage: cone.ambient_ray_indices()
668
(0, 3)
669
sage: cone.star_generator_indices()
670
(0,)
671
"""
672
673
def __init__(self, ambient, ambient_ray_indices):
674
r"""
675
See :class:`Cone_of_Fan` for documentation.
676
677
TESTS:
678
679
The following code is likely to construct an invalid object, we just
680
test that creation of cones of fans is working::
681
682
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
683
sage: cone = sage.geometry.fan.Cone_of_fan(P1xP1, (0,))
684
sage: cone
685
1-d cone of Rational polyhedral fan in 2-d lattice N
686
sage: TestSuite(cone).run()
687
"""
688
super(Cone_of_fan, self).__init__(
689
ambient=ambient, ambient_ray_indices=ambient_ray_indices)
690
self._is_strictly_convex = True
691
# Because if not, this cone should not have been constructed
692
693
def _repr_(self):
694
r"""
695
Return a string representation of ``self``.
696
697
OUTPUT:
698
699
- string.
700
701
TESTS::
702
703
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
704
sage: cone = P1xP1.generating_cone(0)
705
sage: cone._repr_()
706
'2-d cone of Rational polyhedral fan in 2-d lattice N'
707
sage: cone.facets()[0]._repr_()
708
'1-d cone of Rational polyhedral fan in 2-d lattice N'
709
"""
710
# The base class would print "face of" instead of "cone of"
711
return "%d-d cone of %s" % (self.dim(), self.ambient())
712
713
def star_generator_indices(self):
714
r"""
715
Return indices of generating cones of the "ambient fan" containing
716
``self``.
717
718
OUTPUT:
719
720
- increasing :class:`tuple` of integers.
721
722
EXAMPLES::
723
724
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
725
sage: cone = P1xP1.generating_cone(0)
726
sage: cone.star_generator_indices()
727
(0,)
728
729
TESTS:
730
731
A mistake in this function used to cause the problem reported in
732
Trac 9782. We check that now everything is working smoothly::
733
734
sage: f = Fan([(0, 2, 4),
735
... (0, 4, 5),
736
... (0, 3, 5),
737
... (0, 1, 3),
738
... (0, 1, 2),
739
... (2, 4, 6),
740
... (4, 5, 6),
741
... (3, 5, 6),
742
... (1, 3, 6),
743
... (1, 2, 6)],
744
... [(-1, 0, 0),
745
... (0, -1, 0),
746
... (0, 0, -1),
747
... (0, 0, 1),
748
... (0, 1, 2),
749
... (0, 1, 3),
750
... (1, 0, 4)])
751
sage: f.is_complete()
752
True
753
sage: X = ToricVariety(f)
754
sage: X.fan().is_complete()
755
True
756
"""
757
if "_star_generator_indices" not in self.__dict__:
758
fan = self.ambient()
759
sgi = set(range(fan.ngenerating_cones()))
760
for ray in self.ambient_ray_indices():
761
sgi.intersection_update(fan._ray_to_cones(ray))
762
self._star_generator_indices = tuple(sorted(sgi))
763
return self._star_generator_indices
764
765
def star_generators(self):
766
r"""
767
Return indices of generating cones of the "ambient fan" containing
768
``self``.
769
770
OUTPUT:
771
772
- increasing :class:`tuple` of integers.
773
774
EXAMPLES::
775
776
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
777
sage: cone = P1xP1.generating_cone(0)
778
sage: cone.star_generators()
779
(2-d cone of Rational polyhedral fan in 2-d lattice N,)
780
"""
781
if "_star_generators" not in self.__dict__:
782
self._star_generators = tuple(self.ambient().generating_cone(i)
783
for i in self.star_generator_indices())
784
return self._star_generators
785
786
787
class RationalPolyhedralFan(IntegralRayCollection,
788
collections.Callable,
789
collections.Container):
790
r"""
791
Create a rational polyhedral fan.
792
793
.. WARNING::
794
795
This class does not perform any checks of correctness of input nor
796
does it convert input into the standard representation. Use
797
:func:`Fan` to construct fans from "raw data" or :func:`FaceFan` and
798
:func:`NormalFan` to get fans associated to polytopes.
799
800
Fans are immutable, but they cache most of the returned values.
801
802
INPUT:
803
804
- ``cones`` -- list of generating cones of the fan, each cone given as a
805
list of indices of its generating rays in ``rays``;
806
807
- ``rays`` -- list of immutable primitive vectors in ``lattice``
808
consisting of exactly the rays of the fan (i.e. no "extra" ones);
809
810
- ``lattice`` -- :class:`ToricLattice
811
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
812
other object that behaves like these. If ``None``, it will be determined
813
as :func:`parent` of the first ray. Of course, this cannot be done if
814
there are no rays, so in this case you must give an appropriate
815
``lattice`` directly.
816
817
- ``is_complete`` -- if given, must be ``True`` or ``False`` depending on
818
whether this fan is complete or not. By default, it will be determined
819
automatically if necessary.
820
821
OUTPUT:
822
823
- rational polyhedral fan.
824
"""
825
826
def __init__(self, cones, rays, lattice, is_complete=None):
827
r"""
828
See :class:`RationalPolyhedralFan` for documentation.
829
830
TESTS::
831
832
sage: v = vector([0,1])
833
sage: v.set_immutable()
834
sage: f = sage.geometry.fan.RationalPolyhedralFan(
835
... [(0,)], [v], None)
836
sage: f.rays()
837
((0, 1),)
838
sage: TestSuite(f).run()
839
sage: f = Fan([(0,)], [(0,1)])
840
sage: TestSuite(f).run()
841
"""
842
super(RationalPolyhedralFan, self).__init__(rays, lattice)
843
self._generating_cones = tuple(Cone_of_fan(self, c) for c in cones)
844
for i, cone in enumerate(self._generating_cones):
845
cone._star_generator_indices = (i,)
846
# Knowing completeness drastically affects the speed of cone lattice
847
# computation and containment check, so we have a special way to
848
# optimize it.
849
if is_complete is not None:
850
self._is_complete = is_complete
851
852
def __call__(self, dim=None, codim=None):
853
r"""
854
Return the specified cones of ``self``.
855
856
.. NOTE::
857
858
"Direct call" syntax is a synonym for :meth:`cones` method except
859
that in the case of no input parameters this function returns
860
just ``self``.
861
862
INPUT:
863
864
- ``dim`` -- dimension of the requested cones;
865
866
- ``codim`` -- codimension of the requested cones.
867
868
OUTPUT:
869
870
- cones of ``self`` of the specified (co)dimension if it was given,
871
otherwise ``self``.
872
873
TESTS::
874
875
sage: cone1 = Cone([(1,0), (0,1)])
876
sage: cone2 = Cone([(-1,0)])
877
sage: fan = Fan([cone1, cone2])
878
sage: fan(1)
879
(1-d cone of Rational polyhedral fan in 2-d lattice N,
880
1-d cone of Rational polyhedral fan in 2-d lattice N,
881
1-d cone of Rational polyhedral fan in 2-d lattice N)
882
sage: fan(2)
883
(2-d cone of Rational polyhedral fan in 2-d lattice N,)
884
sage: fan(dim=2)
885
(2-d cone of Rational polyhedral fan in 2-d lattice N,)
886
sage: fan(codim=2)
887
(0-d cone of Rational polyhedral fan in 2-d lattice N,)
888
sage: fan(dim=1, codim=1)
889
Traceback (most recent call last):
890
...
891
ValueError: dimension and codimension
892
cannot be specified together!
893
sage: fan() is fan
894
True
895
"""
896
if dim is None and codim is None:
897
# "self.cones()" returns all cones, but for the call syntax
898
# "self()" we return just "self", which seems to be more natural
899
# and convenient for ToricVariety.fan() method.
900
return self
901
else:
902
return self.cones(dim, codim)
903
904
def __cmp__(self, right):
905
r"""
906
Compare ``self`` and ``right``.
907
908
INPUT:
909
910
- ``right`` -- anything.
911
912
OUTPUT:
913
914
- 0 if ``right`` is also a fan, their rays are the same and stored in
915
the same order, and their generating cones are the same and stored
916
in the same order. 1 or -1 otherwise.
917
918
TESTS::
919
920
sage: f1 = Fan(cones=[(0,1), (1,2)],
921
... rays=[(1,0), (0,1), (-1, 0)],
922
... check=False)
923
sage: f2 = Fan(cones=[(1,2), (0,1)],
924
... rays=[(1,0), (0,1), (-1, 0)],
925
... check=False)
926
sage: f3 = Fan(cones=[(1,2), (0,1)],
927
... rays=[(1,0), (0,1), (-1, 0)],
928
... check=False)
929
sage: cmp(f1, f2)
930
1
931
sage: cmp(f2, f1)
932
-1
933
sage: cmp(f2, f3)
934
0
935
sage: f2 is f3
936
False
937
sage: cmp(f1, 1) * cmp(1, f1)
938
-1
939
"""
940
if is_Fan(right):
941
return cmp([self.rays(), self.generating_cones()],
942
[right.rays(), right.generating_cones()])
943
else:
944
return cmp(type(self), type(right))
945
946
def __contains__(self, cone):
947
r"""
948
Check if ``cone`` is equivalent to a cone of the fan.
949
950
See :meth:`_contains` (which is called by this function) for
951
documentation.
952
953
TESTS::
954
955
sage: cone1 = Cone([(0,-1), (1,0)])
956
sage: cone2 = Cone([(1,0), (0,1)])
957
sage: f = Fan([cone1, cone2])
958
sage: f.generating_cone(0) in f
959
True
960
sage: cone1 in f
961
True
962
sage: (1,1) in f # not a cone
963
False
964
sage: "Ceci n'est pas un cone" in f
965
False
966
"""
967
return self._contains(cone)
968
969
def __iter__(self):
970
r"""
971
Return an iterator over generating cones of ``self``.
972
973
OUTPUT:
974
975
- iterator.
976
977
TESTS::
978
979
sage: f = Fan(cones=[(0,1), (1,2)],
980
... rays=[(1,0), (0,1), (-1, 0)],
981
... check=False)
982
sage: for cone in f: print cone.rays()
983
(N(1, 0), N(0, 1))
984
(N(0, 1), N(-1, 0))
985
"""
986
return iter(self.generating_cones())
987
988
def _compute_cone_lattice(self):
989
r"""
990
Compute the cone lattice of ``self``.
991
992
See :meth:`cone_lattice` for documentation.
993
994
TESTS:
995
996
We use different algorithms depending on available information. One of
997
the common cases is a fan which is KNOWN to be complete, i.e. we do
998
not even need to check if it is complete.
999
1000
sage: fan = FaceFan(lattice_polytope.octahedron(2))
1001
sage: fan.cone_lattice() # indirect doctest
1002
Finite poset containing 10 elements
1003
1004
These 10 elements are: 1 origin, 4 rays, 4 generating cones, 1 fan.
1005
1006
Another common case is the fan of faces of a single cone::
1007
1008
sage: quadrant = Cone([(1,0), (0,1)])
1009
sage: fan = Fan([quadrant])
1010
sage: fan.cone_lattice() # indirect doctest
1011
Finite poset containing 5 elements
1012
1013
These 5 elements are: 1 origin, 2 rays, 1 generating cone, 1 fan.
1014
1015
Finally, we have "intermediate" fans which are incomplete but are
1016
generated by more than one cone::
1017
1018
sage: cone1 = Cone([(1,0), (0,1)])
1019
sage: cone2 = Cone([(-1,0)])
1020
sage: fan = Fan([cone1, cone2])
1021
sage: fan.ray_matrix()
1022
[ 0 1 -1]
1023
[ 1 0 0]
1024
sage: for cone in fan: print cone.ambient_ray_indices()
1025
(0, 1)
1026
(2,)
1027
sage: L = fan.cone_lattice() # indirect doctest
1028
sage: L
1029
Finite poset containing 6 elements
1030
1031
Here we got 1 origin, 3 rays (one is a generating cone),
1032
1 2-dimensional cone (a generating one), and 1 fan.
1033
"""
1034
# Define a face constructor
1035
def FanFace(rays, cones):
1036
if not cones: # The top face, fan itself
1037
return self
1038
if len(cones) == 1: # MAY be a generating cone or NOT!!!
1039
g_cone = self.generating_cone(cones[0])
1040
if g_cone.ambient_ray_indices() == rays:
1041
return g_cone
1042
face = Cone_of_fan(ambient=self, ambient_ray_indices=rays)
1043
face._star_generator_indices=cones
1044
return face
1045
# Check directly if we know completeness already, since *determining*
1046
# completeness relies on this function
1047
if "_is_complete" in self.__dict__ and self._is_complete:
1048
# We can use a fast way for complete fans
1049
self._cone_lattice = Hasse_diagram_from_incidences(
1050
self._ray_to_cones(),
1051
(cone.ambient_ray_indices() for cone in self),
1052
FanFace, key = id(self))
1053
else:
1054
# For general fans we will "merge" face lattices of generating
1055
# cones.
1056
L = DiGraph()
1057
face_to_rays = dict() # face |---> (indices of fan rays)
1058
rays_to_index = dict() # (indices of fan rays) |---> face index
1059
# face index |---> (indices of containing generating cones)
1060
index_to_cones = []
1061
# During construction index 0 will correspond to the fan
1062
# We think of the fan not being in the cone even when there is
1063
# only one cone
1064
index_to_cones.append(())
1065
next_index = 1
1066
for i, cone in enumerate(self):
1067
# Set up translation of faces of cone to rays and indices
1068
# We make a standalone cone to compute its standalone face
1069
# lattice, since cones of fans get their lattices from fans
1070
L_cone = Cone(cone.rays(), lattice=self.lattice(),
1071
check=False, normalize=False).face_lattice()
1072
for f in L_cone:
1073
f = f.element
1074
f_rays = tuple(cone.ambient_ray_indices()[ray]
1075
for ray in f.ambient_ray_indices())
1076
face_to_rays[f] = f_rays
1077
try:
1078
f_index = rays_to_index[f_rays]
1079
index_to_cones[f_index].append(i)
1080
except KeyError: # Did not see f before
1081
f_index = next_index
1082
next_index += 1
1083
rays_to_index[f_rays] = f_index
1084
index_to_cones.append([i])
1085
# Add all relations between faces of cone to L
1086
for f,g in L_cone.cover_relations_iterator():
1087
L.add_edge(rays_to_index[face_to_rays[f.element]],
1088
rays_to_index[face_to_rays[g.element]])
1089
# Add the inclusion of cone into the fan itself
1090
L.add_edge(
1091
rays_to_index[face_to_rays[L_cone.top().element]], 0)
1092
1093
# Enumeration of graph vertices must be a linear extension of the
1094
# poset
1095
new_order = L.topological_sort()
1096
# Make sure that generating cones are in the end in proper order
1097
tail = [rays_to_index[gc.ambient_ray_indices()] for gc in self]
1098
tail.append(0) # We know that the fan itself has index 0
1099
new_order = [n for n in new_order if n not in tail] + tail
1100
# Make sure that rays are in the beginning in proper order
1101
head = [rays_to_index[()]] # Empty face
1102
head.extend(rays_to_index[(n,)] for n in range(self.nrays()))
1103
new_order = head + [n for n in new_order if n not in head]
1104
# "Invert" this list to a dictionary
1105
labels = dict()
1106
for new, old in enumerate(new_order):
1107
labels[old] = new
1108
L.relabel(labels)
1109
1110
elements = [None] * next_index
1111
for rays, index in rays_to_index.items():
1112
elements[labels[index]] = FanFace(
1113
rays, tuple(index_to_cones[index]))
1114
# We need "special treatment" for the whole fan. If we added its
1115
# ray incidence information to the total list, it would be
1116
# confused with the generating cone in the case of a single cone.
1117
elements[labels[0]] = FanFace(tuple(range(self.nrays())), ())
1118
self._cone_lattice = FinitePoset(L, elements, key = id(self))
1119
1120
def _contains(self, cone):
1121
r"""
1122
Check if ``cone`` is equivalent to a cone of the fan.
1123
1124
This function is called by :meth:`__contains__` and :meth:`contains`
1125
to ensure the same call depth for warning messages.
1126
1127
INPUT:
1128
1129
- ``cone`` -- anything.
1130
1131
OUTPUT:
1132
1133
- ``False`` if ``cone`` is not a cone or if ``cone`` is not
1134
equivalent to a cone of the fan. ``True`` otherwise.
1135
1136
TESTS::
1137
1138
sage: cone1 = Cone([(0,-1), (1,0)])
1139
sage: cone2 = Cone([(1,0), (0,1)])
1140
sage: f = Fan([cone1, cone2])
1141
sage: f._contains(cone1)
1142
True
1143
sage: f._contains((1,1)) # this is not a cone!
1144
False
1145
1146
Note that the ambient fan of the cone does not matter::
1147
1148
sage: cone1_f = f.generating_cone(0)
1149
sage: cone1_f is cone1
1150
False
1151
sage: cone1_f.is_equivalent(cone1)
1152
True
1153
sage: cone1 in Fan([cone1, cone2]) # not a cone of any particular fan
1154
True
1155
sage: cone1_f in Fan([cone1, cone2]) # belongs to different fan, but equivalent cone
1156
True
1157
"""
1158
try:
1159
self.embed(cone) # Fails if cone is not in self.
1160
return True
1161
except TypeError: # cone is not a cone
1162
return False
1163
except ValueError: # cone is a cone, but wrong
1164
if not cone.lattice().is_submodule(self.lattice()):
1165
warnings.warn("you have checked if a fan contains a cone "
1166
"from another lattice, this is always False!",
1167
stacklevel=3)
1168
return False
1169
1170
def support_contains(self, *args):
1171
r"""
1172
Check if a point is contained in the support of the fan.
1173
1174
The support of a fan is the union of all cones of the fan. If
1175
you want to know whether the fan contains a given cone, you
1176
should use :meth:`contains` instead.
1177
1178
INPUT:
1179
1180
- ``*args`` -- an element of ``self.lattice()`` or something
1181
that can be converted to it (for example, a list of
1182
coordinates).
1183
1184
OUTPUT:
1185
1186
- ``True`` if ``point`` is contained in the support of the
1187
fan, ``False`` otherwise.
1188
1189
TESTS::
1190
1191
sage: cone1 = Cone([(0,-1), (1,0)])
1192
sage: cone2 = Cone([(1,0), (0,1)])
1193
sage: f = Fan([cone1, cone2])
1194
1195
We check if some points are in this fan::
1196
1197
sage: f.support_contains(f.lattice()(1,0))
1198
True
1199
sage: f.support_contains(cone1) # a cone is not a point of the lattice
1200
False
1201
sage: f.support_contains((1,0))
1202
True
1203
sage: f.support_contains(1,1)
1204
True
1205
sage: f.support_contains((-1,0))
1206
False
1207
sage: f.support_contains(f.lattice().dual()(1,0)) #random output (warning)
1208
False
1209
sage: f.support_contains(f.lattice().dual()(1,0))
1210
False
1211
sage: f.support_contains(1)
1212
False
1213
sage: f.support_contains(0) # 0 converts to the origin in the lattice
1214
True
1215
sage: f.support_contains(1/2, sqrt(3))
1216
True
1217
sage: f.support_contains(-1/2, sqrt(3))
1218
False
1219
"""
1220
if len(args)==1:
1221
point = args[0]
1222
else:
1223
point = args
1224
1225
try:
1226
point = self._ambient_space_point(point)
1227
except TypeError, ex:
1228
if str(ex).endswith("have incompatible lattices!"):
1229
warnings.warn("you have checked if a fan contains a point "
1230
"from an incompatible lattice, this is False!",
1231
stacklevel=3)
1232
return False
1233
if self.is_complete():
1234
return True
1235
return any(point in cone for cone in self)
1236
1237
def cartesian_product(self, other, lattice=None):
1238
r"""
1239
Return the Cartesian product of ``self`` with ``other``.
1240
1241
INPUT:
1242
1243
- ``other`` -- a :class:`rational polyhedral fan
1244
<sage.geometry.fan.RationalPolyhedralFan>`;
1245
1246
- ``lattice`` -- (optional) the ambient lattice for the
1247
Cartesian product fan. By default, the direct sum of the
1248
ambient lattices of ``self`` and ``other`` is constructed.
1249
1250
OUTPUT:
1251
1252
- a :class:`fan <RationalPolyhedralFan>` whose cones are all pairwise
1253
Cartesian products of the cones of ``self`` and ``other``.
1254
1255
EXAMPLES::
1256
1257
sage: K = ToricLattice(1, 'K')
1258
sage: fan1 = Fan([[0],[1]],[(1,),(-1,)], lattice=K)
1259
sage: L = ToricLattice(2, 'L')
1260
sage: fan2 = Fan(rays=[(1,0),(0,1),(-1,-1)],
1261
... cones=[[0,1],[1,2],[2,0]], lattice=L)
1262
sage: fan1.cartesian_product(fan2)
1263
Rational polyhedral fan in 3-d lattice K+L
1264
sage: _.ngenerating_cones()
1265
6
1266
"""
1267
assert is_Fan(other)
1268
rc = super(RationalPolyhedralFan, self).cartesian_product(
1269
other, lattice)
1270
self_cones = [cone.ambient_ray_indices() for cone in self]
1271
n = self.nrays()
1272
other_cones = [tuple(n + i for i in cone.ambient_ray_indices())
1273
for cone in other]
1274
new_cones = [c1 + c2 for c1 in self_cones for c2 in other_cones]
1275
try: # Is completeness of the result obvious?
1276
return RationalPolyhedralFan(new_cones, rc.rays(), rc.lattice(),
1277
self._is_complete and other._is_complete)
1278
except AttributeError: # The result is either incomplete or unknown.
1279
return RationalPolyhedralFan(new_cones, rc.rays(), rc.lattice())
1280
1281
def _latex_(self):
1282
r"""
1283
Return a LaTeX representation of ``self``.
1284
1285
OUTPUT:
1286
1287
- string.
1288
1289
TESTS::
1290
1291
sage: f = Fan(cones=[(0,1), (1,2)],
1292
... rays=[(1,0), (0,1), (-1, 0)],
1293
... check=False)
1294
sage: f._latex_()
1295
'\\Sigma^{2}'
1296
"""
1297
return r"\Sigma^{%s}" % self.lattice_dim()
1298
1299
def _ray_to_cones(self, i=None):
1300
r"""
1301
Return the set of generating cones containing the ``i``-th ray.
1302
1303
INPUT:
1304
1305
- ``i`` -- integer, index of a ray of ``self``.
1306
1307
OUTPUT:
1308
1309
- :class:`frozenset` of indices of generating cones of ``self``
1310
containing the ``i``-th ray if ``i`` was given, :class:`tuple` of
1311
these sets for all rays otherwise.
1312
1313
EXAMPLES::
1314
1315
sage: fan = FaceFan(lattice_polytope.octahedron(2))
1316
sage: fan._ray_to_cones(0)
1317
frozenset([0, 2])
1318
sage: fan._ray_to_cones()
1319
(frozenset([0, 2]), frozenset([2, 3]),
1320
frozenset([1, 3]), frozenset([0, 1]))
1321
"""
1322
# This function is close to self(1)[i].star_generator_indices(), but
1323
# it does not require computation of the cone lattice and is
1324
# convenient for internal purposes.
1325
if "_ray_to_cones_tuple" not in self.__dict__:
1326
ray_to_cones = []
1327
for ray in self.rays():
1328
ray_to_cones.append([])
1329
for k, cone in enumerate(self):
1330
for j in cone.ambient_ray_indices():
1331
ray_to_cones[j].append(k)
1332
self._ray_to_cones_tuple = tuple(frozenset(rtc)
1333
for rtc in ray_to_cones)
1334
if i is None:
1335
return self._ray_to_cones_tuple
1336
else:
1337
return self._ray_to_cones_tuple[i]
1338
1339
def _repr_(self):
1340
r"""
1341
Return a string representation of ``self``.
1342
1343
OUTPUT:
1344
1345
- string.
1346
1347
TESTS::
1348
1349
sage: f = Fan(cones=[(0,1), (1,2)],
1350
... rays=[(1,0), (0,1), (-1, 0)],
1351
... check=False)
1352
sage: f._repr_()
1353
'Rational polyhedral fan in 2-d lattice N'
1354
sage: f = Fan(cones=[(0,1), (1,2)],
1355
... rays=[(1,0), (0,1), (-1, 0)],
1356
... lattice=ZZ^2,
1357
... check=False)
1358
sage: f._repr_()
1359
'Rational polyhedral fan in 2-d lattice'
1360
"""
1361
result = "Rational polyhedral fan in"
1362
if is_ToricLattice(self.lattice()):
1363
result += " %s" % self.lattice()
1364
else:
1365
result += " %d-d lattice" % self.lattice_dim()
1366
return result
1367
1368
def _subdivide_palp(self, new_rays, verbose):
1369
r"""
1370
Subdivide ``self`` adding ``new_rays`` one by one.
1371
1372
INPUT:
1373
1374
- ``new_rays`` -- immutable primitive vectors in the lattice of
1375
``self``;
1376
1377
- ``verbose`` -- if ``True``, some timing information will be printed.
1378
1379
OUTPUT:
1380
1381
- rational polyhedral fan.
1382
1383
.. NOTE::
1384
1385
All generating cones of ``self`` must be full-dimensional.
1386
1387
TESTS::
1388
1389
sage: cone1 = Cone([(1,0), (0,1)])
1390
sage: cone2 = Cone([(-1,0)])
1391
sage: new_rays = sage.geometry.cone.normalize_rays([(1,1)], None)
1392
sage: fan = Fan([cone1, cone2])
1393
sage: fan._subdivide_palp(new_rays, True)
1394
Traceback (most recent call last):
1395
...
1396
ValueError: PALP-subdividing can be used only for
1397
fans whose generating cones are full-dimensional!
1398
sage: fan = Fan([cone1])
1399
sage: # Timing information will depend on your machine
1400
sage: new_fan = fan._subdivide_palp(new_rays, True)
1401
R:1/1 C:2 T:...(ms) T/new:...(ms) T/all:...(ms)
1402
sage: new_fan.ray_matrix()
1403
[1 0 1]
1404
[0 1 1]
1405
sage: for cone in new_fan: print cone.ambient_ray_indices()
1406
(1, 2)
1407
(0, 2)
1408
1409
We make sure that this function constructs cones with ordered ambient
1410
ray indices (see Trac 9812)::
1411
1412
sage: C = Cone([(1,0,0), (0,1,0), (1,0,1), (0,1,1)])
1413
sage: F = Fan([C]).make_simplicial()
1414
sage: [cone.ambient_ray_indices() for cone in F]
1415
[(0, 2, 3), (0, 1, 3)]
1416
"""
1417
dim = self.lattice_dim()
1418
for cone in self:
1419
if cone.dim() != dim:
1420
raise ValueError("PALP-subdividing can be used only for fans "
1421
"whose generating cones are full-dimensional!")
1422
# Convert cones to lattice polytopes
1423
cone_polytopes = [cone.lattice_polytope() for cone in self]
1424
for cone_polytope in cone_polytopes:
1425
cone_polytope._dim = dim
1426
all_faces(cone_polytopes)
1427
all_facet_equations(cone_polytopes)
1428
# Iterative subdivision
1429
for n, ray in enumerate(new_rays):
1430
start = walltime()
1431
old_polytopes = []
1432
new_polytopes = []
1433
for cone_polytope in cone_polytopes:
1434
if (cone_polytope.nvertices() == dim + 1 #simplex
1435
and ray in cone_polytope.vertices().columns(copy=False)):
1436
old_polytopes.append(cone_polytope)
1437
continue # Subdivision would not give any new polytopes
1438
distances = cone_polytope.distances(ray)
1439
# The origin is the last 0-dimensional face
1440
cone_facets = cone_polytope.faces(dim=0)[-1].facets()
1441
if all(distances[fn] >= 0 for fn in cone_facets):
1442
# Ray is inside the cone, even if not inside cone_polytope
1443
# Do subdivision with cones over each non-containing facet
1444
vertices = cone_polytope.vertices().columns(copy=False)
1445
for fn in cone_facets:
1446
if distances[fn] > 0:
1447
new_v = [vertices[v] for v in
1448
cone_polytope.facets()[fn].vertices()]
1449
# Add ray keeping the origin last
1450
new_v.insert(-1, ray)
1451
new_v = matrix(ZZ, new_v).transpose()
1452
new_polytope = LatticePolytope(new_v,
1453
copy_vertices=False, compute_vertices=False)
1454
new_polytope._dim = dim
1455
new_polytopes.append(new_polytope)
1456
else:
1457
old_polytopes.append(cone_polytope)
1458
# Precompute data for new polytopes using single calls to PALP
1459
all_faces(new_polytopes)
1460
all_facet_equations(new_polytopes)
1461
cone_polytopes = old_polytopes + new_polytopes
1462
if verbose:
1463
t = walltime(start)
1464
# Avoid division by zero
1465
T_new = ("%d" % (t / len(new_polytopes) * 1000)
1466
if new_polytopes else "-")
1467
print("R:%d/%d C:%d T:%d(ms) T/new:%s(ms) T/all:%d(ms)"
1468
% (n + 1, len(new_rays), len(cone_polytopes), t * 1000,
1469
T_new, t / len(cone_polytopes) * 1000))
1470
# Convert lattice polytopes to cones
1471
new_fan_rays = list(self.rays())
1472
new_fan_rays.extend(ray for ray in new_rays
1473
if ray not in self.ray_set())
1474
cones = tuple(tuple(sorted(new_fan_rays.index(cone_polytope.vertex(v))
1475
for v in range(cone_polytope.nvertices() - 1)))
1476
for cone_polytope in cone_polytopes)
1477
fan = Fan(cones, new_fan_rays, check=False, normalize=False)
1478
return fan
1479
1480
def cone_containing(self, *points):
1481
r"""
1482
Return the smallest cone of ``self`` containing all given points.
1483
1484
INPUT:
1485
1486
- either one or more indices of rays of ``self``, or one or more
1487
objects representing points of the ambient space of ``self``, or a
1488
list of such objects (you CANNOT give a list of indices).
1489
1490
OUTPUT:
1491
1492
- A :class:`cone of fan <Cone_of_fan>` whose ambient fan is
1493
``self``.
1494
1495
.. NOTE::
1496
1497
We think of the origin as of the smallest cone containing no rays
1498
at all. If there is no ray in ``self`` that contains all ``rays``,
1499
a ``ValueError`` exception will be raised.
1500
1501
EXAMPLES::
1502
1503
sage: cone1 = Cone([(0,-1), (1,0)])
1504
sage: cone2 = Cone([(1,0), (0,1)])
1505
sage: f = Fan([cone1, cone2])
1506
sage: f.rays()
1507
(N(0, 1), N(0, -1), N(1, 0))
1508
sage: f.cone_containing(0) # ray index
1509
1-d cone of Rational polyhedral fan in 2-d lattice N
1510
sage: f.cone_containing(0, 1) # ray indices
1511
Traceback (most recent call last):
1512
...
1513
ValueError: there is no cone in
1514
Rational polyhedral fan in 2-d lattice N
1515
containing all of the given rays! Rays: (N(0, 1), N(0, -1))
1516
sage: f.cone_containing(0, 2) # ray indices
1517
2-d cone of Rational polyhedral fan in 2-d lattice N
1518
sage: f.cone_containing((0,1)) # point
1519
1-d cone of Rational polyhedral fan in 2-d lattice N
1520
sage: f.cone_containing([(0,1)]) # point
1521
1-d cone of Rational polyhedral fan in 2-d lattice N
1522
sage: f.cone_containing((1,1))
1523
2-d cone of Rational polyhedral fan in 2-d lattice N
1524
sage: f.cone_containing((1,1), (1,0))
1525
2-d cone of Rational polyhedral fan in 2-d lattice N
1526
sage: f.cone_containing()
1527
0-d cone of Rational polyhedral fan in 2-d lattice N
1528
sage: f.cone_containing((0,0))
1529
0-d cone of Rational polyhedral fan in 2-d lattice N
1530
sage: f.cone_containing((-1,1))
1531
Traceback (most recent call last):
1532
...
1533
ValueError: there is no cone in
1534
Rational polyhedral fan in 2-d lattice N
1535
containing all of the given points! Points: [N(-1, 1)]
1536
1537
TESTS::
1538
1539
sage: fan = Fan(cones=[(0,1,2,3), (0,1,4)],
1540
... rays=[(1,1,1), (1,-1,1), (1,-1,-1), (1,1,-1), (0,0,1)])
1541
sage: fan.cone_containing(0).rays()
1542
(N(1, 1, 1),)
1543
"""
1544
if not points:
1545
return self.cones(dim=0)[0]
1546
try:
1547
rays = map(int, points)
1548
# Got ray indices
1549
generating_cones = set(range(self.ngenerating_cones()))
1550
for ray in rays:
1551
generating_cones.intersection_update(self._ray_to_cones(ray))
1552
if not generating_cones:
1553
raise ValueError("there is no cone in %s containing all of "
1554
"the given rays! Rays: %s" % (self, self.rays(rays)))
1555
containing_cone = self.generating_cone(generating_cones.pop())
1556
for cone in generating_cones:
1557
containing_cone = containing_cone.intersection(
1558
self.generating_cone(cone))
1559
if not self.is_complete():
1560
# This cone may be too big in the case of incomplete fans
1561
rays = frozenset(rays)
1562
facets = containing_cone.facets()
1563
for facet in facets:
1564
if rays.issubset(facet._ambient_ray_indices):
1565
containing_cone = containing_cone.intersection(facet)
1566
return containing_cone
1567
except TypeError:
1568
# Got points (hopefully)
1569
try:
1570
points = map(self._ambient_space_point, points)
1571
except TypeError:
1572
if len(points) == 1:
1573
points = map(self._ambient_space_point, points[0])
1574
else:
1575
raise
1576
# If we are still here, points are good
1577
# First we try to find a generating cone containing all points
1578
containing_cone = None
1579
for cone in self:
1580
contains_all = True
1581
for point in points:
1582
if point not in cone:
1583
contains_all = False
1584
break
1585
if contains_all:
1586
containing_cone = cone
1587
break
1588
if containing_cone is None:
1589
raise ValueError("there is no cone in %s containing all of "
1590
"the given points! Points: %s" % (self, points))
1591
# Now we take the intersection of facets that contain all points
1592
facets = containing_cone.facets()
1593
for facet in facets:
1594
contains_all = True
1595
for point in points:
1596
if point not in facet:
1597
contains_all = False
1598
break
1599
if contains_all:
1600
containing_cone = containing_cone.intersection(facet)
1601
return containing_cone
1602
1603
def cone_lattice(self):
1604
r"""
1605
Return the cone lattice of ``self``.
1606
1607
This lattice will have the origin as the bottom (we do not include the
1608
empty set as a cone) and the fan itself as the top.
1609
1610
OUTPUT:
1611
1612
- :class:`finite poset <sage.combinat.posets.posets.FinitePoset` of
1613
:class:`cones of fan<Cone_of_fan>`, behaving like "regular" cones,
1614
but also containing the information about their relation to this
1615
fan, namely, the contained rays and containing generating cones. The
1616
top of the lattice will be this fan itself (*which is not a*
1617
:class:`cone of fan<Cone_of_fan>`).
1618
1619
See also :meth:`cones`.
1620
1621
EXAMPLES:
1622
1623
Cone lattices can be computed for arbitrary fans::
1624
1625
sage: cone1 = Cone([(1,0), (0,1)])
1626
sage: cone2 = Cone([(-1,0)])
1627
sage: fan = Fan([cone1, cone2])
1628
sage: fan.ray_matrix()
1629
[ 0 1 -1]
1630
[ 1 0 0]
1631
sage: for cone in fan: print cone.ambient_ray_indices()
1632
(0, 1)
1633
(2,)
1634
sage: L = fan.cone_lattice()
1635
sage: L
1636
Finite poset containing 6 elements
1637
1638
These 6 elements are the origin, three rays, one two-dimensional
1639
cone, and the fan itself\ . Since we do add the fan itself as the
1640
largest face, you should be a little bit careful with this last
1641
element::
1642
1643
sage: for face in L: print face.element.ambient_ray_indices()
1644
Traceback (most recent call last):
1645
...
1646
AttributeError: 'RationalPolyhedralFan'
1647
object has no attribute 'ambient_ray_indices'
1648
sage: L.top()
1649
Rational polyhedral fan in 2-d lattice N
1650
1651
For example, you can do ::
1652
1653
sage: for l in L.level_sets()[:-1]:
1654
... print [f.element.ambient_ray_indices() for f in l]
1655
[()]
1656
[(0,), (1,), (2,)]
1657
[(0, 1)]
1658
1659
If the fan is complete, its cone lattice is atomic and coatomic and
1660
can (and will!) be computed in a much more efficient way, but the
1661
interface is exactly the same::
1662
1663
sage: fan = FaceFan(lattice_polytope.octahedron(2))
1664
sage: L = fan.cone_lattice()
1665
sage: for l in L.level_sets()[:-1]:
1666
... print [f.element.ambient_ray_indices() for f in l]
1667
[()]
1668
[(0,), (1,), (2,), (3,)]
1669
[(0, 1), (1, 2), (0, 3), (2, 3)]
1670
1671
Let's also consider the cone lattice of a fan generated by a single
1672
cone::
1673
1674
sage: fan = Fan([cone1])
1675
sage: L = fan.cone_lattice()
1676
sage: L
1677
Finite poset containing 5 elements
1678
1679
Here these 5 elements correspond to the origin, two rays, one
1680
generating cone of dimension two, and the whole fan. While this single
1681
cone "is" the whole fan, it is consistent and convenient to
1682
distinguish them in the cone lattice.
1683
"""
1684
if "_cone_lattice" not in self.__dict__:
1685
self._compute_cone_lattice()
1686
return self._cone_lattice
1687
1688
# Internally we use this name for a uniform behaviour of cones and fans.
1689
_face_lattice_function = cone_lattice
1690
1691
def __getstate__(self):
1692
r"""
1693
Return the dictionary that should be pickled.
1694
1695
OUTPUT:
1696
1697
- :class:`dict`.
1698
1699
TESTS::
1700
1701
sage: cone1 = Cone([(1,0), (0,1)])
1702
sage: cone2 = Cone([(-1,0)])
1703
sage: fan = Fan([cone1, cone2])
1704
sage: fan.cone_lattice()
1705
Finite poset containing 6 elements
1706
sage: fan._test_pickling()
1707
"""
1708
state = copy.copy(self.__dict__)
1709
# TODO: do we want to keep the cone lattice in the pickle?
1710
# Currently there is an unpickling loop if do.
1711
# See Cone.__getstate__ for a similar problem and discussion.
1712
state.pop("_cone_lattice", None)
1713
return state
1714
1715
1716
1717
def cones(self, dim=None, codim=None):
1718
r"""
1719
Return the specified cones of ``self``.
1720
1721
INPUT:
1722
1723
- ``dim`` -- dimension of the requested cones;
1724
1725
- ``codim`` -- codimension of the requested cones.
1726
1727
.. NOTE::
1728
1729
You can specify at most one input parameter.
1730
1731
OUTPUT:
1732
1733
- :class:`tuple` of cones of ``self`` of the specified (co)dimension,
1734
if either ``dim`` or ``codim`` is given. Otherwise :class:`tuple` of
1735
such tuples for all existing dimensions.
1736
1737
EXAMPLES::
1738
1739
sage: cone1 = Cone([(1,0), (0,1)])
1740
sage: cone2 = Cone([(-1,0)])
1741
sage: fan = Fan([cone1, cone2])
1742
sage: fan(dim=0)
1743
(0-d cone of Rational polyhedral fan in 2-d lattice N,)
1744
sage: fan(codim=2)
1745
(0-d cone of Rational polyhedral fan in 2-d lattice N,)
1746
sage: for cone in fan.cones(1): cone.ray(0)
1747
N(0, 1)
1748
N(1, 0)
1749
N(-1, 0)
1750
sage: fan.cones(2)
1751
(2-d cone of Rational polyhedral fan in 2-d lattice N,)
1752
1753
You cannot specify both dimension and codimension, even if they
1754
"agree"::
1755
1756
sage: fan(dim=1, codim=1)
1757
Traceback (most recent call last):
1758
...
1759
ValueError: dimension and codimension
1760
cannot be specified together!
1761
1762
But it is OK to ask for cones of too high or low (co)dimension::
1763
1764
sage: fan(-1)
1765
()
1766
sage: fan(3)
1767
()
1768
sage: fan(codim=4)
1769
()
1770
"""
1771
if "_cones" not in self.__dict__:
1772
levels = [(e.element for e in level) # Generators
1773
for level in self.cone_lattice().level_sets()]
1774
levels.pop() # The very last level is this FAN, not cone.
1775
# It seems that there is no reason to believe that the order of
1776
# faces in level sets has anything to do with the order of
1777
# vertices in the Hasse diagram of FinitePoset. So, while
1778
# Hasse_diagram_from_incidences tried to ensure a "good order,"
1779
# we will sort faces corresponding to rays, as well as faces
1780
# corresponding to generating cones, if they are all of the same
1781
# dimension (otherwise it is not very useful).
1782
if len(levels) >= 3: # There are cones of dimension higher than 1
1783
top_cones = list(levels[-1])
1784
if len(top_cones) == self.ngenerating_cones():
1785
top_cones.sort(key=lambda cone:
1786
cone.star_generator_indices()[0])
1787
levels[-1] = top_cones
1788
if len(levels) >= 2: # We have rays
1789
rays = list(levels[1])
1790
rays.sort(key=lambda cone: cone.ambient_ray_indices()[0])
1791
levels[1] = rays
1792
self._cones = tuple(tuple(level) for level in levels)
1793
if dim is None:
1794
if codim is None:
1795
return self._cones
1796
dim = self.dim() - codim
1797
elif codim is not None:
1798
raise ValueError(
1799
"dimension and codimension cannot be specified together!")
1800
return self._cones[dim] if 0 <= dim < len(self._cones) else ()
1801
1802
def contains(self, cone):
1803
r"""
1804
Check if a given ``cone`` is equivalent to a cone of the fan.
1805
1806
INPUT:
1807
1808
- ``cone`` -- anything.
1809
1810
OUTPUT:
1811
1812
- ``False`` if ``cone`` is not a cone or if ``cone`` is not
1813
equivalent to a cone of the fan. ``True`` otherwise.
1814
1815
.. NOTE::
1816
1817
Recall that a fan is a (finite) collection of cones. A
1818
cone is contained in a fan if it is equivalent to one of
1819
the cones of the fan. In particular, it is possible that
1820
all rays of the cone are in the fan, but the cone itself
1821
is not.
1822
1823
If you want to know whether a point is in the support of
1824
the fan, you should use :meth:`support_contains`.
1825
1826
EXAMPLES:
1827
1828
We first construct a simple fan::
1829
1830
sage: cone1 = Cone([(0,-1), (1,0)])
1831
sage: cone2 = Cone([(1,0), (0,1)])
1832
sage: f = Fan([cone1, cone2])
1833
1834
Now we check if some cones are in this fan. First, we make sure that
1835
the order of rays of the input cone does not matter (``check=False``
1836
option ensures that rays of these cones will be listed exactly as they
1837
are given)::
1838
1839
sage: f.contains(Cone([(1,0), (0,1)], check=False))
1840
True
1841
sage: f.contains(Cone([(0,1), (1,0)], check=False))
1842
True
1843
1844
Now we check that a non-generating cone is in our fan::
1845
1846
sage: f.contains(Cone([(1,0)]))
1847
True
1848
sage: Cone([(1,0)]) in f # equivalent to the previous command
1849
True
1850
1851
Finally, we test some cones which are not in this fan::
1852
1853
sage: f.contains(Cone([(1,1)]))
1854
False
1855
sage: f.contains(Cone([(1,0), (-0,1)]))
1856
True
1857
1858
A point is not a cone::
1859
1860
sage: n = f.lattice()(1,1); n
1861
N(1, 1)
1862
sage: f.contains(n)
1863
False
1864
"""
1865
return self._contains(cone)
1866
1867
def embed(self, cone):
1868
r"""
1869
Return the cone equivalent to the given one, but sitting in ``self``.
1870
1871
You may need to use this method before calling methods of ``cone`` that
1872
depend on the ambient structure, such as
1873
:meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.ambient_ray_indices`
1874
or
1875
:meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.facet_of`. The
1876
cone returned by this method will have ``self`` as ambient. If ``cone``
1877
does not represent a valid cone of ``self``, ``ValueError`` exception
1878
is raised.
1879
1880
.. NOTE::
1881
1882
This method is very quick if ``self`` is already the ambient
1883
structure of ``cone``, so you can use without extra checks and
1884
performance hit even if ``cone`` is likely to sit in ``self`` but
1885
in principle may not.
1886
1887
INPUT:
1888
1889
- ``cone`` -- a :class:`cone
1890
<sage.geometry.cone.ConvexRationalPolyhedralCone>`.
1891
1892
OUTPUT:
1893
1894
- a :class:`cone of fan <Cone_of_fan>`, equivalent to ``cone`` but
1895
sitting inside ``self``.
1896
1897
EXAMPLES:
1898
1899
Let's take a 3-d fan generated by a cone on 4 rays::
1900
1901
sage: f = Fan([Cone([(1,0,1), (0,1,1), (-1,0,1), (0,-1,1)])])
1902
1903
Then any ray generates a 1-d cone of this fan, but if you construct
1904
such a cone directly, it will not "sit" inside the fan::
1905
1906
sage: ray = Cone([(0,-1,1)])
1907
sage: ray
1908
1-d cone in 3-d lattice N
1909
sage: ray.ambient_ray_indices()
1910
(0,)
1911
sage: ray.adjacent()
1912
()
1913
sage: ray.ambient()
1914
1-d cone in 3-d lattice N
1915
1916
If we want to operate with this ray as a part of the fan, we need to
1917
embed it first::
1918
1919
sage: e_ray = f.embed(ray)
1920
sage: e_ray
1921
1-d cone of Rational polyhedral fan in 3-d lattice N
1922
sage: e_ray.rays()
1923
(N(0, -1, 1),)
1924
sage: e_ray is ray
1925
False
1926
sage: e_ray.is_equivalent(ray)
1927
True
1928
sage: e_ray.ambient_ray_indices()
1929
(3,)
1930
sage: e_ray.adjacent()
1931
(1-d cone of Rational polyhedral fan in 3-d lattice N,
1932
1-d cone of Rational polyhedral fan in 3-d lattice N)
1933
sage: e_ray.ambient()
1934
Rational polyhedral fan in 3-d lattice N
1935
1936
Not every cone can be embedded into a fixed fan::
1937
1938
sage: f.embed(Cone([(0,0,1)]))
1939
Traceback (most recent call last):
1940
...
1941
ValueError: 1-d cone in 3-d lattice N does not belong
1942
to Rational polyhedral fan in 3-d lattice N!
1943
sage: f.embed(Cone([(1,0,1), (-1,0,1)]))
1944
Traceback (most recent call last):
1945
...
1946
ValueError: 2-d cone in 3-d lattice N does not belong
1947
to Rational polyhedral fan in 3-d lattice N!
1948
"""
1949
if not is_Cone(cone):
1950
raise TypeError("%s is not a cone!" % cone)
1951
if cone.ambient() is self:
1952
return cone
1953
rays = self.rays()
1954
try:
1955
# Compute ray indices.
1956
ray_indices = [rays.index(ray) for ray in cone.rays()]
1957
# Get the smallest cone containing them
1958
result = self.cone_containing(*ray_indices)
1959
# If there is a cone containing all of the rays of the given cone,
1960
# they must be among its generating rays and we only need to worry
1961
# if there are any extra ones.
1962
if cone.nrays() != result.nrays():
1963
raise ValueError
1964
except ValueError:
1965
raise ValueError("%s does not belong to %s!" % (cone, self))
1966
return result
1967
1968
def Gale_transform(self):
1969
r"""
1970
Return the Gale transform of ``self``.
1971
1972
OUTPUT:
1973
1974
A matrix over `ZZ`.
1975
1976
EXAMPLES::
1977
1978
sage: fan = FaceFan(lattice_polytope.octahedron(2))
1979
sage: fan.Gale_transform()
1980
[ 1 0 1 0 -2]
1981
[ 0 1 0 1 -2]
1982
sage: _.base_ring()
1983
Integer Ring
1984
"""
1985
if "_Gale_transform" not in self.__dict__:
1986
m = self.ray_matrix().augment(matrix(self.lattice_dim(), 1))
1987
m = m.stack(matrix([1]*m.ncols()))
1988
self._Gale_transform = matrix(ZZ, m.transpose().integer_kernel().matrix())
1989
return self._Gale_transform
1990
1991
def generating_cone(self, n):
1992
r"""
1993
Return the ``n``-th generating cone of ``self``.
1994
1995
INPUT:
1996
1997
- ``n`` -- integer, the index of a generating cone.
1998
1999
OUTPUT:
2000
2001
- :class:`cone of fan<Cone_of_fan>`.
2002
2003
EXAMPLES::
2004
2005
sage: fan = FaceFan(lattice_polytope.octahedron(2))
2006
sage: fan.generating_cone(0)
2007
2-d cone of Rational polyhedral fan in 2-d lattice N
2008
"""
2009
return self._generating_cones[n]
2010
2011
def generating_cones(self):
2012
r"""
2013
Return generating cones of ``self``.
2014
2015
OUTPUT:
2016
2017
- :class:`tuple` of :class:`cones of fan<Cone_of_fan>`.
2018
2019
EXAMPLES::
2020
2021
sage: fan = FaceFan(lattice_polytope.octahedron(2))
2022
sage: fan.generating_cones()
2023
(2-d cone of Rational polyhedral fan in 2-d lattice N,
2024
2-d cone of Rational polyhedral fan in 2-d lattice N,
2025
2-d cone of Rational polyhedral fan in 2-d lattice N,
2026
2-d cone of Rational polyhedral fan in 2-d lattice N)
2027
sage: cone1 = Cone([(1,0), (0,1)])
2028
sage: cone2 = Cone([(-1,0)])
2029
sage: fan = Fan([cone1, cone2])
2030
sage: fan.generating_cones()
2031
(2-d cone of Rational polyhedral fan in 2-d lattice N,
2032
1-d cone of Rational polyhedral fan in 2-d lattice N)
2033
"""
2034
return self._generating_cones
2035
2036
def is_complete(self):
2037
r"""
2038
Check if ``self`` is complete.
2039
2040
A rational polyhedral fan is *complete* if its cones fill the whole
2041
space.
2042
2043
OUTPUT:
2044
2045
- ``True`` if ``self`` is complete and ``False`` otherwise.
2046
2047
EXAMPLES::
2048
2049
sage: fan = FaceFan(lattice_polytope.octahedron(2))
2050
sage: fan.is_complete()
2051
True
2052
sage: cone1 = Cone([(1,0), (0,1)])
2053
sage: cone2 = Cone([(-1,0)])
2054
sage: fan = Fan([cone1, cone2])
2055
sage: fan.is_complete()
2056
False
2057
"""
2058
if "_is_complete" in self.__dict__:
2059
return self._is_complete
2060
d = self.lattice_dim()
2061
if self.dim() != d:
2062
self._is_complete = False
2063
return False
2064
for cone in self:
2065
if cone.dim() != d:
2066
self._is_complete = False
2067
return False
2068
# Now we know that all generating cones are full-dimensional.
2069
# Then boundary cones are (d-1)-dimensional.
2070
for cone in self(codim=1):
2071
if len(cone.star_generator_indices()) != 2:
2072
self._is_complete = False
2073
return False
2074
self._is_complete = True
2075
return True
2076
2077
def is_equivalent(self, other):
2078
r"""
2079
Check if ``self`` is "mathematically" the same as ``other``.
2080
2081
INPUT:
2082
2083
- ``other`` - fan.
2084
2085
OUTPUT:
2086
2087
- ``True`` if ``self`` and ``other`` define the same fans as
2088
collections of equivalent cones in the same lattice, ``False``
2089
otherwise.
2090
2091
There are three different equivalences between fans `F_1` and `F_2`
2092
in the same lattice:
2093
2094
#. They have the same rays in the same order and the same generating
2095
cones in the same order.
2096
This is tested by ``F1 == F2``.
2097
#. They have the same rays and the same generating cones without
2098
taking into account any order.
2099
This is tested by ``F1.is_equivalent(F2)``.
2100
#. They are in the same orbit of `GL(n,\ZZ)` (and, therefore,
2101
correspond to isomorphic toric varieties).
2102
This is tested by ``F1.is_isomorphic(F2)``.
2103
2104
EXAMPLES::
2105
2106
sage: fan1 = Fan(cones=[(0,1), (1,2)],
2107
... rays=[(1,0), (0,1), (-1,-1)],
2108
... check=False)
2109
sage: fan2 = Fan(cones=[(2,1), (0,2)],
2110
... rays=[(1,0), (-1,-1), (0,1)],
2111
... check=False)
2112
sage: fan3 = Fan(cones=[(0,1), (1,2)],
2113
... rays=[(1,0), (0,1), (-1,1)],
2114
... check=False)
2115
sage: fan1 == fan2
2116
False
2117
sage: fan1.is_equivalent(fan2)
2118
True
2119
sage: fan1 == fan3
2120
False
2121
sage: fan1.is_equivalent(fan3)
2122
False
2123
"""
2124
if (self.lattice() != other.lattice()
2125
or self.dim() != other.dim()
2126
or self.ngenerating_cones() != other.ngenerating_cones()
2127
or self.ray_set() != other.ray_set()):
2128
return False
2129
# Now we need to really compare cones, which can take a while
2130
return sorted(sorted(cone.rays()) for cone in self) \
2131
== sorted(sorted(cone.rays()) for cone in other)
2132
2133
def is_isomorphic(self, other):
2134
r"""
2135
Check if ``self`` is in the same `GL(n, \ZZ)`-orbit as ``other``.
2136
2137
INPUT:
2138
2139
- ``other`` - fan.
2140
2141
OUTPUT:
2142
2143
- ``True`` if ``self`` and ``other`` are in the same
2144
`GL(n, \ZZ)`-orbit, ``False`` otherwise.
2145
2146
There are three different equivalences between fans `F_1` and `F_2`
2147
in the same lattice:
2148
2149
#. They have the same rays in the same order and the same generating
2150
cones in the same order.
2151
This is tested by ``F1 == F2``.
2152
#. They have the same rays and the same generating cones without
2153
taking into account any order.
2154
This is tested by ``F1.is_equivalent(F2)``.
2155
#. They are in the same orbit of `GL(n,\ZZ)` (and, therefore,
2156
correspond to isomorphic toric varieties).
2157
This is tested by ``F1.is_isomorphic(F2)``.
2158
2159
EXAMPLES:
2160
2161
These fans are "mirrors" of each other::
2162
2163
sage: fan1 = Fan(cones=[(0,1), (1,2)],
2164
... rays=[(1,0), (0,1), (-1,-1)],
2165
... check=False)
2166
sage: fan2 = Fan(cones=[(0,1), (1,2)],
2167
... rays=[(1,0), (0,-1), (-1,1)],
2168
... check=False)
2169
sage: fan1 == fan2
2170
False
2171
sage: fan1.is_equivalent(fan2)
2172
False
2173
sage: fan1.is_isomorphic(fan2)
2174
Traceback (most recent call last):
2175
...
2176
NotImplementedError: fan isomorphism is not implemented yet!
2177
"""
2178
if self.lattice() != other.lattice():
2179
return False
2180
raise NotImplementedError("fan isomorphism is not implemented yet!")
2181
2182
def is_simplicial(self):
2183
r"""
2184
Check if ``self`` is simplicial.
2185
2186
A rational polyhedral fan is **simplicial** if all of its cones are,
2187
i.e. primitive vectors along generating rays of every cone form a part
2188
of a *rational* basis of the ambient space.
2189
2190
OUTPUT:
2191
2192
- ``True`` if ``self`` is simplicial and ``False`` otherwise.
2193
2194
EXAMPLES::
2195
2196
sage: fan = FaceFan(lattice_polytope.octahedron(2))
2197
sage: fan.is_simplicial()
2198
True
2199
sage: cone1 = Cone([(1,0), (0,1)])
2200
sage: cone2 = Cone([(-1,0)])
2201
sage: fan = Fan([cone1, cone2])
2202
sage: fan.is_simplicial()
2203
True
2204
2205
In fact, any fan in a two-dimensional ambient space is simplicial.
2206
This is no longer the case in dimension three::
2207
2208
sage: fan = NormalFan(lattice_polytope.octahedron(3))
2209
sage: fan.is_simplicial()
2210
False
2211
sage: fan.generating_cone(0).nrays()
2212
4
2213
"""
2214
if "is_simplicial" not in self.__dict__:
2215
self._is_simplicial = all(cone.is_simplicial() for cone in self)
2216
return self._is_simplicial
2217
2218
@cached_method
2219
def is_smooth(self, codim=None):
2220
r"""
2221
Check if ``self`` is smooth.
2222
2223
A rational polyhedral fan is **smooth** if all of its cones
2224
are, i.e. primitive vectors along generating rays of every
2225
cone form a part of an *integral* basis of the ambient
2226
space. In this case the corresponding toric variety is smooth.
2227
2228
A fan in an `n`-dimensional lattice is smooth up to codimension `c`
2229
if all cones of codimension greater than or equal to `c` are smooth,
2230
i.e. if all cones of dimension less than or equal to `n-c` are smooth.
2231
In this case the singular set of the corresponding toric variety is of
2232
dimension less than `c`.
2233
2234
INPUT:
2235
2236
- ``codim`` -- codimension in which smoothness has to be checked, by
2237
default complete smoothness will be checked.
2238
2239
OUTPUT:
2240
2241
- ``True`` if ``self`` is smooth (in codimension ``codim``, if it was
2242
given) and ``False`` otherwise.
2243
2244
EXAMPLES::
2245
2246
sage: fan = FaceFan(lattice_polytope.octahedron(2))
2247
sage: fan.is_smooth()
2248
True
2249
sage: cone1 = Cone([(1,0), (0,1)])
2250
sage: cone2 = Cone([(-1,0)])
2251
sage: fan = Fan([cone1, cone2])
2252
sage: fan.is_smooth()
2253
True
2254
sage: fan = NormalFan(lattice_polytope.octahedron(2))
2255
sage: fan.is_smooth()
2256
False
2257
sage: fan.is_smooth(codim=1)
2258
True
2259
sage: fan.generating_cone(0).rays()
2260
(N(-1, 1), N(-1, -1))
2261
sage: fan.generating_cone(0).ray_matrix().det()
2262
2
2263
"""
2264
if codim is None or codim < 0:
2265
codim = 0
2266
if codim > self.lattice_dim() - 2:
2267
return True
2268
return all(cone.is_smooth() for cone in self(codim=codim)) and \
2269
self.is_smooth(codim + 1)
2270
2271
def make_simplicial(self, **kwds):
2272
r"""
2273
Construct a simplicial fan subdividing ``self``.
2274
2275
It is a synonym for :meth:`subdivide` with ``make_simplicial=True``
2276
option.
2277
2278
INPUT:
2279
2280
- this functions accepts only keyword arguments. See :meth:`subdivide`
2281
for documentation.
2282
2283
OUTPUT:
2284
2285
- :class:`rational polyhedral fan
2286
<sage.geometry.fan.RationalPolyhedralFan>`.
2287
2288
EXAMPLES::
2289
2290
sage: fan = NormalFan(lattice_polytope.octahedron(3))
2291
sage: fan.is_simplicial()
2292
False
2293
sage: fan.ngenerating_cones()
2294
6
2295
sage: new_fan = fan.make_simplicial()
2296
sage: new_fan.is_simplicial()
2297
True
2298
sage: new_fan.ngenerating_cones()
2299
12
2300
"""
2301
return self.subdivide(make_simplicial=True, **kwds)
2302
2303
def ngenerating_cones(self):
2304
r"""
2305
Return the number of generating cones of ``self``.
2306
2307
OUTPUT:
2308
2309
- integer.
2310
2311
EXAMPLES::
2312
2313
sage: fan = FaceFan(lattice_polytope.octahedron(2))
2314
sage: fan.ngenerating_cones()
2315
4
2316
sage: cone1 = Cone([(1,0), (0,1)])
2317
sage: cone2 = Cone([(-1,0)])
2318
sage: fan = Fan([cone1, cone2])
2319
sage: fan.ngenerating_cones()
2320
2
2321
"""
2322
return len(self.generating_cones())
2323
2324
def plot(self, **options):
2325
r"""
2326
Plot ``self``.
2327
2328
INPUT:
2329
2330
- any options for toric plots (see :func:`toric_plotter.options
2331
<sage.geometry.toric_plotter.options>`), none are mandatory.
2332
2333
OUTPUT:
2334
2335
- a plot.
2336
2337
EXAMPLES::
2338
2339
sage: fan = toric_varieties.dP6().fan()
2340
sage: fan.plot()
2341
"""
2342
tp = ToricPlotter(options, self.lattice().degree(), self.rays())
2343
result = tp.plot_lattice() + tp.plot_rays() + tp.plot_generators()
2344
if self.dim() >= 2:
2345
result += tp.plot_walls(self(2))
2346
return result
2347
2348
def subdivide(self, new_rays=None, make_simplicial=False,
2349
algorithm="default", verbose=False):
2350
r"""
2351
Construct a new fan subdividing ``self``.
2352
2353
INPUT:
2354
2355
- ``new_rays`` - list of new rays to be added during subdivision, each
2356
ray must be a list or a vector. May be empty or ``None`` (default);
2357
2358
- ``make_simplicial`` - if ``True``, the returned fan is guaranteed to
2359
be simplicial, default is ``False``;
2360
2361
- ``algorithm`` - string with the name of the algorithm used for
2362
subdivision. Currently there is only one available algorithm called
2363
"default";
2364
2365
- ``verbose`` - if ``True``, some timing information may be printed
2366
during the process of subdivision.
2367
2368
OUTPUT:
2369
2370
- :class:`rational polyhedral fan
2371
<sage.geometry.fan.RationalPolyhedralFan>`.
2372
2373
Currently the "default" algorithm corresponds to iterative stellar
2374
subdivision for each ray in ``new_rays``.
2375
2376
EXAMPLES::
2377
2378
sage: fan = NormalFan(lattice_polytope.octahedron(3))
2379
sage: fan.is_simplicial()
2380
False
2381
sage: fan.ngenerating_cones()
2382
6
2383
sage: fan.nrays()
2384
8
2385
sage: new_fan = fan.subdivide(new_rays=[(1,0,0)])
2386
sage: new_fan.is_simplicial()
2387
False
2388
sage: new_fan.ngenerating_cones()
2389
9
2390
sage: new_fan.nrays()
2391
9
2392
2393
TESTS:
2394
2395
We check that Trac #11902 is fixed::
2396
2397
sage: fan = toric_varieties.P2().fan()
2398
sage: fan.subdivide(new_rays=[(0,0)])
2399
Traceback (most recent call last):
2400
...
2401
ValueError: the origin cannot be used for fan subdivision!
2402
"""
2403
# Maybe these decisions should be done inside the algorithms
2404
# We can figure it out once we have at least two of them.
2405
if make_simplicial and not self.is_simplicial():
2406
rays = list(self.rays())
2407
else:
2408
rays = []
2409
rays.extend(ray for ray in normalize_rays(new_rays, self.lattice())
2410
if ray not in self.ray_set())
2411
if not rays:
2412
return self # Nothing has to be done
2413
if self.lattice().zero() in rays:
2414
raise ValueError("the origin cannot be used for fan subdivision!")
2415
if algorithm == "default":
2416
algorithm = "palp"
2417
method_name = "_subdivide_" + algorithm
2418
if not hasattr(self, method_name):
2419
raise ValueError('"%s" is an unknown subdivision algorithm!'
2420
% algorithm)
2421
return getattr(self, method_name)(rays, verbose)
2422
2423
def primitive_collections(self):
2424
ur"""
2425
Return the primitive collections.
2426
2427
OUTPUT:
2428
2429
Returns the subsets `\{i_1,\dots,i_k\} \subset \{ 1,\dots,n\}`
2430
such that
2431
2432
* The points `\{p_{i_1},\dots,p_{i_k}\}` do not span a cone of
2433
the fan.
2434
2435
* If you remove any one `p_{i_j}` from the set, then they do
2436
span a cone of the fan.
2437
2438
.. NOTE::
2439
2440
By replacing the multiindices `\{i_1,\dots,i_k\}` of each
2441
primitive collection with the monomials `x_{i_1}\cdots
2442
x_{i_k}` one generates the Stanley-Reisner ideal in
2443
`\ZZ[x_1,\dots]`.
2444
2445
REFERENCES:
2446
2447
..
2448
2449
V.V. Batyrev, On the classification of smooth projective
2450
toric varieties, Tohoku Math.J. 43 (1991), 569-585
2451
2452
EXAMPLES::
2453
2454
sage: fan = Fan([[0,1,3],[3,4],[2,0],[1,2,4]], [(-3, -2, 1), (0, 0, 1), (3, -2, 1), (-1, -1, 1), (1, -1, 1)])
2455
sage: fan.primitive_collections()
2456
[frozenset([0, 4]), frozenset([2, 3]), frozenset([0, 1, 2]), frozenset([1, 3, 4])]
2457
"""
2458
try:
2459
return self._primitive_collections
2460
except AttributeError:
2461
pass
2462
2463
def is_not_facet(I):
2464
return all( not(I<=f) for f in facets )
2465
2466
def is_in_SR(I):
2467
return all( not(I>=sr) for sr in SR)
2468
2469
# Generators of SR are index sets I = {i1, ..., ik}
2470
# called "primitve collections" such that
2471
# 1) I is not contained in a face
2472
# 2) if you remove any one entry j, then I-{j} is contained in a facet
2473
facets = map(frozenset, [ c.ambient_ray_indices() for c in self.generating_cones() ])
2474
# print "facets = " + str(facets)
2475
all_points = frozenset( range(0,self.nrays()) )
2476
d_max = max(map(len,facets))+1
2477
SR = []
2478
for d in range(1,d_max):
2479
checked = set([])
2480
for facet in facets:
2481
for I_minus_j_list in Combinations(facet, d):
2482
I_minus_j = frozenset(I_minus_j_list)
2483
for j in all_points - I_minus_j:
2484
I = I_minus_j.union( frozenset([j]) )
2485
2486
if I in checked:
2487
continue
2488
else:
2489
checked.add(I)
2490
# print "Trying " + str(I)
2491
2492
if is_not_facet(I) and is_in_SR(I):
2493
SR.append(I)
2494
2495
self._primitive_collections = SR
2496
return self._primitive_collections
2497
2498
def Stanley_Reisner_ideal(self, ring):
2499
"""
2500
Return the Stanley-Reisner ideal.
2501
2502
INPUT:
2503
2504
- A polynomial ring in ``self.nrays()`` variables.
2505
2506
OUTPUT:
2507
2508
- The Stanley-Reisner ideal in the given polynomial ring.
2509
2510
EXAMPLES::
2511
2512
sage: fan = Fan([[0,1,3],[3,4],[2,0],[1,2,4]], [(-3, -2, 1), (0, 0, 1), (3, -2, 1), (-1, -1, 1), (1, -1, 1)])
2513
sage: fan.Stanley_Reisner_ideal( PolynomialRing(QQ,5,'A, B, C, D, E') )
2514
Ideal (A*E, C*D, A*B*C, B*D*E) of Multivariate Polynomial Ring in A, B, C, D, E over Rational Field
2515
"""
2516
generators_indices = self.primitive_collections()
2517
SR = ring.ideal([ prod([ ring.gen(i) for i in sr]) for sr in generators_indices ])
2518
return SR
2519
2520
def linear_equivalence_ideal(self, ring):
2521
"""
2522
Return the ideal generated by linear relations
2523
2524
INPUT:
2525
2526
- A polynomial ring in ``self.nrays()`` variables.
2527
2528
OUTPUT:
2529
2530
Returns the ideal, in the given ``ring``, generated by the
2531
linear relations of the rays. In toric geometry, this
2532
corresponds to rational equivalence of divisors.
2533
2534
EXAMPLES::
2535
2536
sage: fan = Fan([[0,1,3],[3,4],[2,0],[1,2,4]], [(-3, -2, 1), (0, 0, 1), (3, -2, 1), (-1, -1, 1), (1, -1, 1)])
2537
sage: fan.linear_equivalence_ideal( PolynomialRing(QQ,5,'A, B, C, D, E') )
2538
Ideal (-3*A + 3*C - D + E, -2*A - 2*C - D - E, A + B + C + D + E) of Multivariate Polynomial Ring in A, B, C, D, E over Rational Field
2539
"""
2540
gens = []
2541
for d in range(0,self.dim()):
2542
gens.append( sum([ self.ray(i)[d] * ring.gen(i)
2543
for i in range(0, self.nrays()) ]) )
2544
return ring.ideal(gens)
2545
2546
def oriented_boundary(self, cone):
2547
r"""
2548
Return the facets bounding ``cone`` with their induced
2549
orientation.
2550
2551
INPUT:
2552
2553
- ``cone`` -- a cone of the fan or the whole fan.
2554
2555
OUTPUT:
2556
2557
The boundary cones of ``cone`` as a formal linear combination
2558
of cones with coefficients `\pm 1`. Each summand is a facet of
2559
``cone`` and the coefficient indicates whether their (chosen)
2560
orientation argrees or disagrees with the "outward normal
2561
first" boundary orientation. Note that the orientation of any
2562
individial cone is arbitrary. This method once and for all
2563
picks orientations for all cones and then computes the
2564
boundaries relative to that chosen orientation.
2565
2566
If ``cone`` is the fan itself, the generating cones with their
2567
orientation relative to the ambient space are returned.
2568
2569
See :meth:`complex` for the associated chain complex. If you
2570
do not require the orientation, use :meth:`cone.facets()
2571
<sage.geometry.cone.ConvexRationalPolyhedralCone.facets>`
2572
instead.
2573
2574
EXAMPLES::
2575
2576
sage: fan = toric_varieties.P(3).fan()
2577
sage: cone = fan(2)[0]
2578
sage: bdry = fan.oriented_boundary(cone); bdry
2579
1-d cone of Rational polyhedral fan in 3-d lattice N
2580
- 1-d cone of Rational polyhedral fan in 3-d lattice N
2581
sage: bdry[0]
2582
(1, 1-d cone of Rational polyhedral fan in 3-d lattice N)
2583
sage: bdry[1]
2584
(-1, 1-d cone of Rational polyhedral fan in 3-d lattice N)
2585
sage: fan.oriented_boundary(bdry[0][1])
2586
-0-d cone of Rational polyhedral fan in 3-d lattice N
2587
sage: fan.oriented_boundary(bdry[1][1])
2588
-0-d cone of Rational polyhedral fan in 3-d lattice N
2589
2590
If you pass the fan itself, this method returns the
2591
orientation of the generating cones which is determined by the
2592
order of the rays in :meth:`cone.ray_basis()
2593
<sage.geometry.cone.IntegralRayCollection.ray_basis>` ::
2594
2595
sage: fan.oriented_boundary(fan)
2596
-3-d cone of Rational polyhedral fan in 3-d lattice N
2597
+ 3-d cone of Rational polyhedral fan in 3-d lattice N
2598
- 3-d cone of Rational polyhedral fan in 3-d lattice N
2599
+ 3-d cone of Rational polyhedral fan in 3-d lattice N
2600
sage: [ matrix(cone.ray_basis()).det() for cone in fan.generating_cones() ]
2601
[-1, 1, -1, 1]
2602
2603
A non-full dimensional fan::
2604
2605
sage: cone = Cone([(4,5)])
2606
sage: fan = Fan([cone])
2607
sage: fan.oriented_boundary(cone)
2608
0-d cone of Rational polyhedral fan in 2-d lattice N
2609
sage: fan.oriented_boundary(fan)
2610
1-d cone of Rational polyhedral fan in 2-d lattice N
2611
2612
TESTS::
2613
2614
sage: fan = toric_varieties.P2().fan()
2615
sage: trivial_cone = fan(0)[0]
2616
sage: fan.oriented_boundary(trivial_cone)
2617
0
2618
"""
2619
if not cone is self:
2620
cone = self.embed(cone)
2621
if '_oriented_boundary' in self.__dict__:
2622
return self._oriented_boundary[cone]
2623
2624
# Fix (arbitrary) orientations of the generating cones. Induced
2625
# by ambient space orientation for full-dimensional cones
2626
from sage.structure.formal_sum import FormalSum
2627
def sign(x):
2628
assert x != 0
2629
if x>0: return +1
2630
else: return -1
2631
N_QQ = self.lattice().base_extend(QQ)
2632
dim = self.lattice_dim()
2633
outward_vectors = dict()
2634
generating_cones = []
2635
for c in self.generating_cones():
2636
if c.dim()==dim:
2637
outward_v = []
2638
else:
2639
Q = N_QQ.quotient(c.rays())
2640
outward_v = [ Q.lift(q) for q in Q.gens() ]
2641
2642
outward_vectors[c] = outward_v
2643
orientation = sign(matrix(outward_v + list(c.ray_basis())).det())
2644
generating_cones.append(tuple([orientation, c]))
2645
boundaries = {self:FormalSum(generating_cones)}
2646
2647
# The orientation of each facet is arbitrary, but the
2648
# partititon of the boundary in positively and negatively
2649
# oriented facets is not.
2650
for d in range(dim, -1, -1):
2651
for c in self(d):
2652
c_boundary = []
2653
c_matrix = matrix(outward_vectors[c] + list(c.ray_basis()))
2654
c_matrix_inv = c_matrix.inverse()
2655
for facet in c.facets():
2656
outward_ray_indices = set(c.ambient_ray_indices()) \
2657
.difference(set(facet.ambient_ray_indices()))
2658
outward_vector = - sum(self.ray(i) for i in outward_ray_indices)
2659
outward_vectors[facet] = [outward_vector] + outward_vectors[c]
2660
facet_matrix = matrix(outward_vectors[facet] + list(facet.ray_basis()))
2661
orientation = sign((c_matrix_inv * facet_matrix).det())
2662
c_boundary.append(tuple([orientation, facet]))
2663
boundaries[c] = FormalSum(c_boundary)
2664
2665
self._oriented_boundary = boundaries
2666
return boundaries[cone]
2667
2668
def complex(self, base_ring=ZZ, extended=False):
2669
r"""
2670
Return the chain complex of the fan.
2671
2672
To a `d`-dimensional fan `\Sigma`, one can canonically
2673
associate a chain complex `K^\bullet`
2674
2675
.. math::
2676
2677
0 \longrightarrow
2678
\ZZ^{\Sigma(d)} \longrightarrow
2679
\ZZ^{\Sigma(d-1)} \longrightarrow
2680
\cdots \longrightarrow
2681
\ZZ^{\Sigma(0)} \longrightarrow
2682
0
2683
2684
where the leftmost non-zero entry is in degree `0` and the
2685
rightmost entry in degree `d`. See [Klyachko], eq. (3.2). This
2686
complex computes the homology of `|\Sigma|\subset N_\RR` with
2687
arbitrary support,
2688
2689
.. math::
2690
2691
H_i(K) = H_{d-i}(|\Sigma|, \ZZ)_{\text{non-cpct}}
2692
2693
For a complete fan, this is just the non-compactly supported
2694
homology of `\RR^d`. In this case, `H_0(K)=\ZZ` and `0` in all
2695
non-zero degrees.
2696
2697
For a complete fan, there is an extended chain complex
2698
2699
.. math::
2700
2701
0 \longrightarrow
2702
\ZZ \longrightarrow
2703
\ZZ^{\Sigma(d)} \longrightarrow
2704
\ZZ^{\Sigma(d-1)} \longrightarrow
2705
\cdots \longrightarrow
2706
\ZZ^{\Sigma(0)} \longrightarrow
2707
0
2708
2709
where we take the first `\ZZ` term to be in degree -1. This
2710
complex is an exact sequence, that is, all homology groups
2711
vanish.
2712
2713
The orientation of each cone is chosen as in
2714
:meth:`oriented_boundary`.
2715
2716
INPUT:
2717
2718
- ``extended`` -- Boolean (default:False). Whether to
2719
construct the extended complex, that is, including the
2720
`\ZZ`-term at degree -1 or not.
2721
2722
- ``base_ring`` -- A ring (default: ``ZZ``). The ring to use
2723
instead of `\ZZ`.
2724
2725
OUTPUT:
2726
2727
The complex associated to the fan as a :class:`ChainComplex
2728
<sage.homology.chain_complex.ChainComplex>`. Raises a
2729
``ValueError`` if the extended complex is requested for a
2730
non-complete fan.
2731
2732
EXAMPLES::
2733
2734
sage: fan = toric_varieties.P(3).fan()
2735
sage: K_normal = fan.complex(); K_normal
2736
Chain complex with at most 4 nonzero terms over Integer Ring
2737
sage: K_normal.homology()
2738
{0: Z, 1: 0, 2: 0, 3: 0}
2739
sage: K_extended = fan.complex(extended=True); K_extended
2740
Chain complex with at most 5 nonzero terms over Integer Ring
2741
sage: K_extended.homology()
2742
{0: 0, 1: 0, 2: 0, 3: 0, -1: 0}
2743
2744
Homology computations are much faster over `\QQ` if you don't
2745
care about the torsion coefficients::
2746
2747
sage: toric_varieties.P2_123().fan().complex(extended=True, base_ring=QQ)
2748
Chain complex with at most 4 nonzero terms over Rational Field
2749
sage: _.homology()
2750
{0: Vector space of dimension 0 over Rational Field,
2751
1: Vector space of dimension 0 over Rational Field,
2752
2: Vector space of dimension 0 over Rational Field,
2753
-1: Vector space of dimension 0 over Rational Field}
2754
2755
The extended complex is only defined for complete fans::
2756
2757
sage: fan = Fan([ Cone([(1,0)]) ])
2758
sage: fan.is_complete()
2759
False
2760
sage: fan.complex(extended=True)
2761
Traceback (most recent call last):
2762
...
2763
ValueError: The extended complex is only defined for complete fans!
2764
2765
The definition of the complex does not refer to the ambient
2766
space of the fan, so it does not distinguish a fan from the
2767
same fan embedded in a subspace::
2768
2769
sage: K1 = Fan([Cone([(-1,)]), Cone([(1,)])]).complex()
2770
sage: K2 = Fan([Cone([(-1,0,0)]), Cone([(1,0,0)])]).complex()
2771
sage: K1 == K2
2772
True
2773
2774
Things get more complicated for non-complete fans::
2775
2776
sage: fan = Fan([Cone([(1,1,1)]),
2777
... Cone([(1,0,0),(0,1,0)]),
2778
... Cone([(-1,0,0),(0,-1,0),(0,0,-1)])])
2779
sage: fan.complex().homology()
2780
{0: 0, 1: 0, 2: Z x Z, 3: 0}
2781
sage: fan = Fan([Cone([(1,0,0),(0,1,0)]),
2782
... Cone([(-1,0,0),(0,-1,0),(0,0,-1)])])
2783
sage: fan.complex().homology()
2784
{0: 0, 1: 0, 2: Z, 3: 0}
2785
sage: fan = Fan([Cone([(-1,0,0),(0,-1,0),(0,0,-1)])])
2786
sage: fan.complex().homology()
2787
{0: 0, 1: 0, 2: 0, 3: 0}
2788
2789
REFERENCES:
2790
2791
.. [Klyachko]
2792
A. A. Klyachko,
2793
Equivariant Bundles on Toral Varieties.
2794
Mathematics of the USSR - Izvestiya 35 (1990), 337-375.
2795
"""
2796
dim = self.dim()
2797
delta = dict()
2798
for degree in range(1, dim+1):
2799
m = matrix(base_ring, len(self(degree-1)), len(self(degree)), base_ring.zero())
2800
for i, cone in enumerate(self(degree)):
2801
boundary = self.oriented_boundary(cone)
2802
for orientation, d_cone in boundary:
2803
m[self(degree-1).index(d_cone), i] = orientation
2804
delta[dim-degree] = m
2805
2806
from sage.homology.chain_complex import ChainComplex
2807
if not extended:
2808
return ChainComplex(delta, base_ring=base_ring)
2809
2810
# add the extra entry for the extended complex
2811
if not self.is_complete():
2812
raise ValueError('The extended complex is only defined for complete fans!')
2813
extension = matrix(base_ring, len(self(dim)), 1, base_ring.zero())
2814
generating_cones = self.oriented_boundary(self)
2815
for orientation, d_cone in generating_cones:
2816
extension[self(dim).index(d_cone), 0] = orientation
2817
delta[-1] = extension
2818
return ChainComplex(delta, base_ring=base_ring)
2819
2820
2821
def discard_faces(cones):
2822
r"""
2823
Return the cones of the given list which are not faces of each other.
2824
2825
INPUT:
2826
2827
- ``cones`` -- a list of
2828
:class:`cones <sage.geometry.cone.ConvexRationalPolyhedralCone>`.
2829
2830
OUTPUT:
2831
2832
- a list of
2833
:class:`cones <sage.geometry.cone.ConvexRationalPolyhedralCone>`,
2834
sorted by dimension in decreasing order.
2835
2836
EXAMPLES:
2837
2838
Consider all cones of a fan::
2839
2840
sage: Sigma = toric_varieties.P2().fan()
2841
sage: cones = flatten(Sigma.cones())
2842
sage: len(cones)
2843
7
2844
2845
Most of them are not necessary to generate this fan::
2846
2847
sage: from sage.geometry.fan import discard_faces
2848
sage: len(discard_faces(cones))
2849
3
2850
sage: Sigma.ngenerating_cones()
2851
3
2852
"""
2853
# Convert to a list or make a copy, so that the input is unchanged.
2854
cones = list(cones)
2855
cones.sort(key=lambda cone: cone.dim(), reverse=True)
2856
generators = []
2857
for cone in cones:
2858
if not any(cone.is_face_of(other) for other in generators):
2859
generators.append(cone)
2860
return generators
2861
2862
_discard_faces = discard_faces # Due to a name conflict in Fan constructor
2863
2864