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