-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfan.py
3695 lines (3029 loc) · 130 KB
/
fan.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
r"""
Rational polyhedral fans
This module was designed as a part of the framework for toric varieties
(:mod:`~sage.schemes.toric.variety`,
:mod:`~sage.schemes.toric.fano_variety`). While the emphasis is on
complete full-dimensional fans, arbitrary fans are supported. Work
with distinct lattices. The default lattice is :class:`ToricLattice
<sage.geometry.toric_lattice.ToricLatticeFactory>` `N` of the appropriate
dimension. The only case when you must specify lattice explicitly is creation
of a 0-dimensional fan, where dimension of the ambient space cannot be
guessed.
A **rational polyhedral fan** is a *finite* collection of *strictly* convex
rational polyhedral cones, such that the intersection of any two cones of the
fan is a face of each of them and each face of each cone is also a cone of the
fan.
AUTHORS:
- Andrey Novoseltsev (2010-05-15): initial version.
- Andrey Novoseltsev (2010-06-17): substantial improvement during review by
Volker Braun.
EXAMPLES:
Use :func:`Fan` to construct fans "explicitly"::
sage: fan = Fan(cones=[(0,1), (1,2)],
....: rays=[(1,0), (0,1), (-1,0)])
sage: fan
Rational polyhedral fan in 2-d lattice N
In addition to giving such lists of cones and rays you can also create cones
first using :func:`~sage.geometry.cone.Cone` and then combine them into a fan.
See the documentation of :func:`Fan` for details.
In 2 dimensions there is a unique maximal fan determined by rays, and
you can use :func:`Fan2d` to construct it::
sage: fan2d = Fan2d(rays=[(1,0), (0,1), (-1,0)])
sage: fan2d.is_equivalent(fan)
True
But keep in mind that in higher dimensions the cone data is essential
and cannot be omitted. Instead of building a fan from scratch, for
this tutorial we will use an easy way to get two fans associated to
:class:`lattice polytopes
<sage.geometry.lattice_polytope.LatticePolytopeClass>`:
:func:`FaceFan` and :func:`NormalFan`::
sage: fan1 = FaceFan(lattice_polytope.cross_polytope(3))
sage: fan2 = NormalFan(lattice_polytope.cross_polytope(3))
Given such "automatic" fans, you may wonder what are their rays and cones::
sage: fan1.rays()
M( 1, 0, 0),
M( 0, 1, 0),
M( 0, 0, 1),
M(-1, 0, 0),
M( 0, -1, 0),
M( 0, 0, -1)
in 3-d lattice M
sage: fan1.generating_cones()
(3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M)
The last output is not very illuminating. Let's try to improve it::
sage: for cone in fan1: print(cone.rays())
M( 0, 1, 0),
M( 0, 0, 1),
M(-1, 0, 0)
in 3-d lattice M
M( 0, 0, 1),
M(-1, 0, 0),
M( 0, -1, 0)
in 3-d lattice M
M(-1, 0, 0),
M( 0, -1, 0),
M( 0, 0, -1)
in 3-d lattice M
M( 0, 1, 0),
M(-1, 0, 0),
M( 0, 0, -1)
in 3-d lattice M
M(1, 0, 0),
M(0, 1, 0),
M(0, 0, -1)
in 3-d lattice M
M(1, 0, 0),
M(0, 1, 0),
M(0, 0, 1)
in 3-d lattice M
M(1, 0, 0),
M(0, 0, 1),
M(0, -1, 0)
in 3-d lattice M
M(1, 0, 0),
M(0, -1, 0),
M(0, 0, -1)
in 3-d lattice M
You can also do ::
sage: for cone in fan1: print(cone.ambient_ray_indices())
(1, 2, 3)
(2, 3, 4)
(3, 4, 5)
(1, 3, 5)
(0, 1, 5)
(0, 1, 2)
(0, 2, 4)
(0, 4, 5)
to see indices of rays of the fan corresponding to each cone.
While the above cycles were over "cones in fan", it is obvious that we did not
get ALL the cones: every face of every cone in a fan must also be in the fan,
but all of the above cones were of dimension three. The reason for this
behaviour is that in many cases it is enough to work with generating cones of
the fan, i.e. cones which are not faces of bigger cones. When you do need to
work with lower dimensional cones, you can easily get access to them using
:meth:`~sage.geometry.fan.RationalPolyhedralFan.cones`::
sage: [cone.ambient_ray_indices() for cone in fan1.cones(2)]
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (0, 4),
(2, 4), (3, 4), (1, 5), (3, 5), (4, 5), (0, 5)]
In fact, you do not have to type ``.cones``::
sage: [cone.ambient_ray_indices() for cone in fan1(2)]
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (0, 4),
(2, 4), (3, 4), (1, 5), (3, 5), (4, 5), (0, 5)]
You may also need to know the inclusion relations between all of the cones of
the fan. In this case check out
:meth:`~sage.geometry.fan.RationalPolyhedralFan.cone_lattice`::
sage: L = fan1.cone_lattice()
sage: L
Finite lattice containing 28 elements with distinguished linear extension
sage: L.bottom()
0-d cone of Rational polyhedral fan in 3-d lattice M
sage: L.top()
Rational polyhedral fan in 3-d lattice M
sage: cone = L.level_sets()[2][0]
sage: cone
2-d cone of Rational polyhedral fan in 3-d lattice M
sage: sorted(L.hasse_diagram().neighbors(cone))
[1-d cone of Rational polyhedral fan in 3-d lattice M,
1-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M,
3-d cone of Rational polyhedral fan in 3-d lattice M]
You can check how "good" a fan is::
sage: fan1.is_complete()
True
sage: fan1.is_simplicial()
True
sage: fan1.is_smooth()
True
The face fan of the octahedron is really good! Time to remember that we have
also constructed its normal fan::
sage: fan2.is_complete()
True
sage: fan2.is_simplicial()
False
sage: fan2.is_smooth()
False
This one does have some "problems," but we can fix them::
sage: fan3 = fan2.make_simplicial()
sage: fan3.is_simplicial()
True
sage: fan3.is_smooth()
False
Note that we had to save the result of
:meth:`~sage.geometry.fan.RationalPolyhedralFan.make_simplicial` in a new fan.
Fans in Sage are immutable, so any operation that does change them constructs
a new fan.
We can also make ``fan3`` smooth, but it will take a bit more work::
sage: cube = lattice_polytope.cross_polytope(3).polar()
sage: sk = cube.skeleton_points(2)
sage: rays = [cube.point(p) for p in sk]
sage: fan4 = fan3.subdivide(new_rays=rays)
sage: fan4.is_smooth()
True
Let's see how "different" are ``fan2`` and ``fan4``::
sage: fan2.ngenerating_cones()
6
sage: fan2.nrays()
8
sage: fan4.ngenerating_cones()
48
sage: fan4.nrays()
26
Smoothness does not come for free!
Please take a look at the rest of the available functions below and their
complete descriptions. If you need any features that are missing, feel free to
suggest them. (Or implement them on your own and submit a patch to Sage for
inclusion!)
"""
# ****************************************************************************
# Copyright (C) 2010 Andrey Novoseltsev <[email protected]>
# Copyright (C) 2010 William Stein <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# https://www.gnu.org/licenses/
# ****************************************************************************
from collections.abc import Callable, Container
from copy import copy
from warnings import warn
from sage.structure.richcmp import richcmp_method, richcmp
from sage.combinat.combination import Combinations
from sage.combinat.posets.posets import FinitePoset
from sage.geometry.cone import (_ambient_space_point,
Cone,
ConvexRationalPolyhedralCone,
IntegralRayCollection,
is_Cone,
normalize_rays)
from sage.geometry.hasse_diagram import lattice_from_incidences
from sage.geometry.point_collection import PointCollection
from sage.geometry.toric_lattice import ToricLattice, is_ToricLattice
from sage.geometry.toric_plotter import ToricPlotter
from sage.graphs.digraph import DiGraph
from sage.matrix.all import matrix
from sage.misc.cachefunc import cached_method
from sage.misc.all import walltime, prod
from sage.modules.all import vector, span
from sage.rings.all import QQ, ZZ
def is_Fan(x):
r"""
Check if ``x`` is a Fan.
INPUT:
- ``x`` -- anything.
OUTPUT:
- ``True`` if ``x`` is a fan and ``False`` otherwise.
EXAMPLES::
sage: from sage.geometry.fan import is_Fan
sage: is_Fan(1)
False
sage: fan = toric_varieties.P2().fan()
sage: fan
Rational polyhedral fan in 2-d lattice N
sage: is_Fan(fan)
True
"""
return isinstance(x, RationalPolyhedralFan)
def Fan(cones, rays=None, lattice=None, check=True, normalize=True,
is_complete=None, virtual_rays=None, discard_faces=False,
allow_arrangement=False):
r"""
Construct a rational polyhedral fan.
.. NOTE::
Approximate time to construct a fan consisting of `n` cones is `n^2/5`
seconds. That is half an hour for 100 cones. This time can be
significantly reduced in the future, but it is still likely to be
`\sim n^2` (with, say, `/500` instead of `/5`). If you know that your
input does form a valid fan, use ``check=False`` option to skip
consistency checks.
INPUT:
- ``cones`` -- list of either
:class:`Cone<sage.geometry.cone.ConvexRationalPolyhedralCone>` objects
or lists of integers interpreted as indices of generating rays in
``rays``. These must be only **maximal** cones of the fan, unless
``discard_faces=True`` or ``allow_arrangement=True`` option is specified;
- ``rays`` -- list of rays given as list or vectors convertible to the
rational extension of ``lattice``. If ``cones`` are given by
:class:`Cone<sage.geometry.cone.ConvexRationalPolyhedralCone>` objects
``rays`` may be determined automatically. You still may give them
explicitly to ensure a particular order of rays in the fan. In this case
you must list all rays that appear in ``cones``. You can give "extra"
ones if it is convenient (e.g. if you have a big list of rays for
several fans), but all "extra" rays will be discarded;
- ``lattice`` -- :class:`ToricLattice
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
other object that behaves like these. If not specified, an attempt will
be made to determine an appropriate toric lattice automatically;
- ``check`` -- by default the input data will be checked for correctness
(e.g. that intersection of any two given cones is a face of each),
unless ``allow_arrangement=True`` option is specified. If you
know for sure that the input is correct, you may significantly decrease
construction time using ``check=False`` option;
- ``normalize`` -- you can further speed up construction using
``normalize=False`` option. In this case ``cones`` must be a list of
**sorted** :class:`tuples` and ``rays`` must be immutable primitive
vectors in ``lattice``. In general, you should not use this option, it
is designed for code optimization and does not give as drastic
improvement in speed as the previous one;
- ``is_complete`` -- every fan can determine on its own if it is complete
or not, however it can take quite a bit of time for "big" fans with many
generating cones. On the other hand, in some situations it is known in
advance that a certain fan is complete. In this case you can pass
``is_complete=True`` option to speed up some computations. You may also
pass ``is_complete=False`` option, although it is less likely to be
beneficial. Of course, passing a wrong value can compromise the
integrity of data structures of the fan and lead to wrong results, so
you should be very careful if you decide to use this option;
- ``virtual_rays`` -- (optional, computed automatically if needed) a list of
ray generators to be used for :meth:`virtual_rays`;
- ``discard_faces`` -- by default, the fan constructor expects the list of
**maximal** cones, unless ``allow_arrangement=True`` option is specified.
If you provide "extra" ones and leave ``allow_arrangement=False`` (default)
and ``check=True`` (default), an exception will be raised.
If you provide "extra" cones and set ``allow_arrangement=False`` (default)
and ``check=False``, you may get wrong results as assumptions on internal
data structures will be invalid. If you want the fan constructor to
select the maximal cones from the given input, you may provide
``discard_faces=True`` option (it works both for ``check=True`` and
``check=False``).
- ``allow_arrangement`` -- by default (``allow_arrangement=False``),
the fan constructor expects that the intersection of any two given cones is
a face of each. If ``allow_arrangement=True`` option is specified, then
construct a rational polyhedralfan from the cone arrangement, so that the
union of the cones in the polyhedral fan equals to the union of the given
cones, and each given cone is the union of some cones in the polyhedral fan.
OUTPUT:
- a :class:`fan <RationalPolyhedralFan>`.
.. SEEALSO::
In 2 dimensions you can cyclically order the rays. Hence the
rays determine a unique maximal fan without having to specify
the cones, and you can use :func:`Fan2d` to construct this
fan from just the rays.
EXAMPLES:
Let's construct a fan corresponding to the projective plane in several
ways::
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(0,1), (-1,-1)])
sage: cone3 = Cone([(-1,-1), (1,0)])
sage: P2 = Fan([cone1, cone2, cone2])
Traceback (most recent call last):
...
ValueError: you have provided 3 cones, but only 2 of them are maximal!
Use discard_faces=True if you indeed need to construct a fan from
these cones.
Oops! There was a typo and ``cone2`` was listed twice as a generating cone
of the fan. If it was intentional (e.g. the list of cones was generated
automatically and it is possible that it contains repetitions or faces of
other cones), use ``discard_faces=True`` option::
sage: P2 = Fan([cone1, cone2, cone2], discard_faces=True)
sage: P2.ngenerating_cones()
2
However, in this case it was definitely a typo, since the fan of
`\mathbb{P}^2` has 3 maximal cones::
sage: P2 = Fan([cone1, cone2, cone3])
sage: P2.ngenerating_cones()
3
Looks better. An alternative way is ::
sage: rays = [(1,0), (0,1), (-1,-1)]
sage: cones = [(0,1), (1,2), (2,0)]
sage: P2a = Fan(cones, rays)
sage: P2a.ngenerating_cones()
3
sage: P2 == P2a
False
That may seem wrong, but it is not::
sage: P2.is_equivalent(P2a)
True
See :meth:`~RationalPolyhedralFan.is_equivalent` for details.
Yet another way to construct this fan is ::
sage: P2b = Fan(cones, rays, check=False)
sage: P2b.ngenerating_cones()
3
sage: P2a == P2b
True
If you try the above examples, you are likely to notice the difference in
speed, so when you are sure that everything is correct, it is a good idea
to use ``check=False`` option. On the other hand, it is usually **NOT** a
good idea to use ``normalize=False`` option::
sage: P2c = Fan(cones, rays, check=False, normalize=False)
Traceback (most recent call last):
...
AttributeError: 'tuple' object has no attribute 'parent'
Yet another way is to use functions :func:`FaceFan` and :func:`NormalFan`
to construct fans from :class:`lattice polytopes
<sage.geometry.lattice_polytope.LatticePolytopeClass>`.
We have not yet used ``lattice`` argument, since if was determined
automatically::
sage: P2.lattice()
2-d lattice N
sage: P2b.lattice()
2-d lattice N
However, it is necessary to specify it explicitly if you want to construct
a fan without rays or cones::
sage: Fan([], [])
Traceback (most recent call last):
...
ValueError: you must specify the lattice
when you construct a fan without rays and cones!
sage: F = Fan([], [], lattice=ToricLattice(2, "L"))
sage: F
Rational polyhedral fan in 2-d lattice L
sage: F.lattice_dim()
2
sage: F.dim()
0
In the following examples, we test the ``allow_arrangement=True`` option.
See :trac:`25122`.
The intersection of the two cones is not a face of each. Therefore,
they do not belong to the same rational polyhedral fan::
sage: c1 = Cone([(-2,-1,1), (-2,1,1), (2,1,1), (2,-1,1)])
sage: c2 = Cone([(-1,-2,1), (-1,2,1), (1,2,1), (1,-2,1)])
sage: c1.intersection(c2).is_face_of(c1)
False
sage: c1.intersection(c2).is_face_of(c2)
False
sage: Fan([c1, c2])
Traceback (most recent call last):
...
ValueError: these cones cannot belong to the same fan!
...
Let's construct the fan using ``allow_arrangement=True`` option::
sage: fan = Fan([c1, c2], allow_arrangement=True)
sage: fan.ngenerating_cones()
5
Another example where cone c2 is inside cone c1::
sage: c1 = Cone([(4, 0, 0), (0, 4, 0), (0, 0, 4)])
sage: c2 = Cone([(2, 1, 1), (1, 2, 1), (1, 1, 2)])
sage: fan = Fan([c1, c2], allow_arrangement=True)
sage: fan.ngenerating_cones()
7
sage: fan.plot()
Graphics3d Object
Cones of different dimension::
sage: c1 = Cone([(1,0),(0,1)])
sage: c2 = Cone([(2,1)])
sage: c3 = Cone([(-1,-2)])
sage: fan = Fan([c1, c2, c3], allow_arrangement=True)
sage: for cone in sorted(fan.generating_cones()): print(sorted(cone.rays()))
[N(-1, -2)]
[N(0, 1), N(1, 2)]
[N(1, 0), N(2, 1)]
[N(1, 2), N(2, 1)]
A 3-d cone and a 1-d cone::
sage: c3 = Cone([[0, 1, 1], [1, 0, 1], [0, -1, 1], [-1, 0, 1]])
sage: c1 = Cone([[0, 0, 1]])
sage: fan1 = Fan([c1, c3], allow_arrangement=True)
sage: fan1.plot()
Graphics3d Object
A 3-d cone and two 2-d cones::
sage: c2v = Cone([[0, 1, 1], [0, -1, 1]])
sage: c2h = Cone([[1, 0, 1], [-1, 0, 1]])
sage: fan2 = Fan([c2v, c2h, c3], allow_arrangement=True)
sage: fan2.is_simplicial()
True
sage: fan2.is_equivalent(fan1)
True
"""
def result():
# "global" does not work here...
R, V = rays, virtual_rays
if V is not None:
if normalize:
V = normalize_rays(V, lattice)
if check:
R = PointCollection(V, lattice)
V = PointCollection(V, lattice)
d = lattice.dimension()
if len(V) != d - R.dim() or (R + V).dim() != d:
raise ValueError("virtual rays must be linearly "
"independent and with other rays span the ambient space.")
return RationalPolyhedralFan(cones, R, lattice, is_complete, V)
if not check and not normalize and not discard_faces and not allow_arrangement:
return result()
if not isinstance(cones, list):
try:
cones = list(cones)
except TypeError:
raise TypeError(
"cones must be given as an iterable!"
"\nGot: %s" % cones)
if not cones:
if lattice is None:
if rays is not None and rays:
lattice = normalize_rays(rays, lattice)[0].parent()
else:
raise ValueError("you must specify the lattice when you "
"construct a fan without rays and cones!")
cones = ((), )
rays = ()
return result()
if is_Cone(cones[0]):
# Construct the fan from Cone objects
if lattice is None:
lattice = cones[0].lattice()
# If we determine the lattice automatically, we do not
# want to force any conversion. TODO: take into account
# coercions?
if check:
for cone in cones:
if cone.lattice() != lattice:
raise ValueError("cones belong to different lattices "
"(%s and %s), cannot determine the lattice of the "
"fan!" % (lattice, cone.lattice()))
for i, cone in enumerate(cones):
if cone.lattice() != lattice:
cones[i] = Cone(cone.rays(), lattice, check=False)
if check:
for cone in cones:
if not cone.is_strictly_convex():
raise ValueError(
"cones of a fan must be strictly convex!")
# Optimization for fans generated by a single cone
if len(cones) == 1 and rays is None:
cone = cones[0]
cones = (tuple(range(cone.nrays())), )
rays = cone.rays()
is_complete = lattice.dimension() == 0
return result()
if allow_arrangement:
cones = _refine_arrangement_to_fan(cones)
cones = _discard_faces(cones)
elif check:
# Maybe we should compute all faces of all cones and save them for
# later if we are doing this check?
generating_cones = []
for cone in sorted(cones, key=lambda cone: cone.dim(),
reverse=True):
is_generating = True
for g_cone in generating_cones:
i_cone = cone.intersection(g_cone)
if i_cone.is_face_of(cone) and i_cone.is_face_of(g_cone):
if i_cone.dim() == cone.dim():
is_generating = False # cone is a face of g_cone
break
else:
raise ValueError(
"these cones cannot belong to the same fan!"
"\nCone 1 rays: %s\nCone 2 rays: %s"
% (g_cone.rays(), cone.rays()))
if is_generating:
generating_cones.append(cone)
if len(cones) > len(generating_cones):
if discard_faces:
cones = generating_cones
else:
raise ValueError("you have provided %d cones, but only %d "
"of them are maximal! Use discard_faces=True if you "
"indeed need to construct a fan from these cones." %
(len(cones), len(generating_cones)))
elif discard_faces:
cones = _discard_faces(cones)
ray_set = set([])
for cone in cones:
ray_set.update(cone.rays())
if rays: # Preserve the initial order of rays, if they were given
rays = normalize_rays(rays, lattice)
new_rays = []
for ray in rays:
if ray in ray_set and ray not in new_rays:
new_rays.append(ray)
if len(new_rays) != len(ray_set):
raise ValueError(
"if rays are given, they must include all rays of the fan!")
rays = new_rays
else:
rays = tuple(sorted(ray_set))
cones = (tuple(sorted(rays.index(ray) for ray in cone.rays()))
for cone in cones)
return result()
# Construct the fan from rays and "tuple cones"
rays = normalize_rays(rays, lattice)
for n, cone in enumerate(cones):
try:
cones[n] = sorted(cone)
except TypeError:
raise TypeError("cannot interpret %s as a cone!" % cone)
if not check and not discard_faces and not allow_arrangement:
return result()
# If we do need to make all the check, build explicit cone objects first
return Fan((Cone([rays[n] for n in cone], lattice) for cone in cones),
rays, lattice, is_complete=is_complete,
virtual_rays=virtual_rays, discard_faces=discard_faces,
allow_arrangement=allow_arrangement)
def FaceFan(polytope, lattice=None):
r"""
Construct the face fan of the given rational ``polytope``.
INPUT:
- ``polytope`` -- a :func:`polytope
<sage.geometry.polyhedron.constructor.Polyhedron>` over `\QQ` or
a :class:`lattice polytope
<sage.geometry.lattice_polytope.LatticePolytopeClass>`. A (not
necessarily full-dimensional) polytope containing the origin in
its :meth:`relative interior
<sage.geometry.polyhedron.base.Polyhedron_base.relative_interior_contains>`.
- ``lattice`` -- :class:`ToricLattice
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
other object that behaves like these. If not specified, an attempt will
be made to determine an appropriate toric lattice automatically.
OUTPUT:
- :class:`rational polyhedral fan <RationalPolyhedralFan>`.
See also :func:`NormalFan`.
EXAMPLES:
Let's construct the fan corresponding to the product of two projective
lines::
sage: diamond = lattice_polytope.cross_polytope(2)
sage: P1xP1 = FaceFan(diamond)
sage: P1xP1.rays()
M( 1, 0),
M( 0, 1),
M(-1, 0),
M( 0, -1)
in 2-d lattice M
sage: for cone in P1xP1: print(cone.rays())
M(-1, 0),
M( 0, -1)
in 2-d lattice M
M( 0, 1),
M(-1, 0)
in 2-d lattice M
M(1, 0),
M(0, 1)
in 2-d lattice M
M(1, 0),
M(0, -1)
in 2-d lattice M
TESTS::
sage: cuboctahed = polytopes.cuboctahedron()
sage: FaceFan(cuboctahed)
Rational polyhedral fan in 3-d lattice M
sage: cuboctahed.is_lattice_polytope(), cuboctahed.dilation(1/2).is_lattice_polytope()
(True, False)
sage: fan1 = FaceFan(cuboctahed)
sage: fan2 = FaceFan(cuboctahed.dilation(2).lattice_polytope())
sage: fan1.is_equivalent(fan2)
True
sage: ray = Polyhedron(vertices=[(-1,-1)], rays=[(1,1)])
sage: FaceFan(ray)
Traceback (most recent call last):
...
ValueError: face fans are defined only for
polytopes containing the origin as an interior point!
sage: interval_in_QQ2 = Polyhedron([ (0,-1), (0,+1) ])
sage: FaceFan(interval_in_QQ2).generating_cones()
(1-d cone of Rational polyhedral fan in 2-d lattice M,
1-d cone of Rational polyhedral fan in 2-d lattice M)
sage: FaceFan(Polyhedron([(-1,0), (1,0), (0,1)])) # origin on facet
Traceback (most recent call last):
...
ValueError: face fans are defined only for
polytopes containing the origin as an interior point!
"""
from sage.geometry.lattice_polytope import is_LatticePolytope
interior_point_error = ValueError(
"face fans are defined only for polytopes containing "
"the origin as an interior point!")
if is_LatticePolytope(polytope):
if any(d <= 0 for d in polytope.distances([0]*polytope.dim())):
raise interior_point_error
cones = (f.ambient_vertex_indices() for f in polytope.facets())
rays = polytope.vertices()
is_complete = polytope.dim() == polytope.lattice_dim()
else:
origin = polytope.ambient_space().zero()
if not (polytope.is_compact() and
polytope.relative_interior_contains(origin)):
raise interior_point_error
cones = [ [ v.index() for v in facet.incident() ]
for facet in polytope.inequalities() ]
rays = [vector(_) for _ in polytope.vertices()]
is_complete = polytope.dim() == polytope.ambient_dim()
if lattice is None:
# Since default lattice polytopes are in the M lattice,
# treat polyhedra as being there as well.
lattice = ToricLattice(len(origin)).dual()
return Fan(cones, rays, lattice=lattice, check=False,
is_complete=is_complete)
def NormalFan(polytope, lattice=None):
r"""
Construct the normal fan of the given rational ``polytope``.
This returns the inner normal fan. For the outer normal fan, use
``NormalFan(-P)``.
INPUT:
- ``polytope`` -- a full-dimensional :func:`polytope
<sage.geometry.polyhedron.constructor.Polyhedron>` over `\QQ`
or:class:`lattice polytope
<sage.geometry.lattice_polytope.LatticePolytopeClass>`.
- ``lattice`` -- :class:`ToricLattice
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
other object that behaves like these. If not specified, an attempt will
be made to determine an appropriate toric lattice automatically.
OUTPUT:
- :class:`rational polyhedral fan <RationalPolyhedralFan>`.
See also :func:`FaceFan`.
EXAMPLES:
Let's construct the fan corresponding to the product of two projective
lines::
sage: square = LatticePolytope([(1,1), (-1,1), (-1,-1), (1,-1)])
sage: P1xP1 = NormalFan(square)
sage: P1xP1.rays()
N( 1, 0),
N( 0, 1),
N(-1, 0),
N( 0, -1)
in 2-d lattice N
sage: for cone in P1xP1: print(cone.rays())
N(-1, 0),
N( 0, -1)
in 2-d lattice N
N(1, 0),
N(0, -1)
in 2-d lattice N
N(1, 0),
N(0, 1)
in 2-d lattice N
N( 0, 1),
N(-1, 0)
in 2-d lattice N
sage: cuboctahed = polytopes.cuboctahedron()
sage: NormalFan(cuboctahed)
Rational polyhedral fan in 3-d lattice N
TESTS::
sage: cuboctahed.is_lattice_polytope(), cuboctahed.dilation(1/2).is_lattice_polytope()
(True, False)
sage: fan1 = NormalFan(cuboctahed)
sage: fan2 = NormalFan(cuboctahed.dilation(2).lattice_polytope())
sage: fan1.is_equivalent(fan2)
True
"""
dimension_error = ValueError(
'the normal fan is only defined for full-dimensional polytopes')
from sage.geometry.lattice_polytope import is_LatticePolytope
if is_LatticePolytope(polytope):
if polytope.dim() != polytope.lattice_dim():
raise dimension_error
rays = polytope.facet_normals()
cones = (v.ambient_facet_indices() for v in polytope.faces(dim=0))
else:
if polytope.dim() != polytope.ambient_dim():
raise dimension_error
if not polytope.is_compact():
raise NotImplementedError('the normal fan is only supported for polytopes (compact polyhedra).')
cones = [[ieq.index() for ieq in vertex.incident()]
for vertex in polytope.vertices()]
rays = [ieq.A() for ieq in polytope.inequalities()]
return Fan(cones, rays, lattice=lattice, check=False, is_complete=True)
def Fan2d(rays, lattice=None):
r"""
Construct the maximal 2-d fan with given ``rays``.
In two dimensions we can uniquely construct a fan from just rays,
just by cyclically ordering the rays and constructing as many
cones as possible. This is why we implement a special constructor
for this case.
INPUT:
- ``rays`` -- list of rays given as list or vectors convertible to
the rational extension of ``lattice``. Duplicate rays are
removed without changing the ordering of the remaining rays.
- ``lattice`` -- :class:`ToricLattice
<sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any
other object that behaves like these. If not specified, an attempt will
be made to determine an appropriate toric lattice automatically.
EXAMPLES::
sage: Fan2d([(0,1), (1,0)])
Rational polyhedral fan in 2-d lattice N
sage: Fan2d([], lattice=ToricLattice(2, 'myN'))
Rational polyhedral fan in 2-d lattice myN
The ray order is as specified, even if it is not the cyclic order::
sage: fan1 = Fan2d([(0,1), (1,0)])
sage: fan1.rays()
N(0, 1),
N(1, 0)
in 2-d lattice N
sage: fan2 = Fan2d([(1,0), (0,1)])
sage: fan2.rays()
N(1, 0),
N(0, 1)
in 2-d lattice N
sage: fan1 == fan2, fan1.is_equivalent(fan2)
(False, True)
sage: fan = Fan2d([(1,1), (-1,-1), (1,-1), (-1,1)])
sage: [ cone.ambient_ray_indices() for cone in fan ]
[(2, 1), (1, 3), (3, 0), (0, 2)]
sage: fan.is_complete()
True
TESTS::
sage: Fan2d([(0,1), (0,1)]).generating_cones()
(1-d cone of Rational polyhedral fan in 2-d lattice N,)
sage: Fan2d([(1,1), (-1,-1)]).generating_cones()
(1-d cone of Rational polyhedral fan in 2-d lattice N,
1-d cone of Rational polyhedral fan in 2-d lattice N)
sage: Fan2d([])
Traceback (most recent call last):
...
ValueError: you must specify a 2-dimensional lattice
when you construct a fan without rays.
sage: Fan2d([(3,4)]).rays()
N(3, 4)
in 2-d lattice N
sage: Fan2d([(0,1,0)])
Traceback (most recent call last):
...
ValueError: the lattice must be 2-dimensional.
sage: Fan2d([(0,1), (1,0), (0,0)])
Traceback (most recent call last):
...
ValueError: only non-zero vectors define rays
sage: Fan2d([(0, -2), (2, -10), (1, -3), (2, -9), (2, -12), (1, 1),
....: (2, 1), (1, -5), (0, -6), (1, -7), (0, 1), (2, -4),
....: (2, -2), (1, -9), (1, -8), (2, -6), (0, -1), (0, -3),
....: (2, -11), (2, -8), (1, 0), (0, -5), (1, -4), (2, 0),
....: (1, -6), (2, -7), (2, -5), (-1, -3), (1, -1), (1, -2),
....: (0, -4), (2, -3), (2, -1)]).cone_lattice()
Finite lattice containing 44 elements with distinguished linear extension
sage: Fan2d([(1,1)]).is_complete()
False
sage: Fan2d([(1,1), (-1,-1)]).is_complete()
False
sage: Fan2d([(1,0), (0,1)]).is_complete()
False
"""
if not rays:
if lattice is None or lattice.dimension() != 2:
raise ValueError('you must specify a 2-dimensional lattice when '
'you construct a fan without rays.')
return RationalPolyhedralFan(cones=((), ), rays=(), lattice=lattice)
# remove multiple rays without changing order
rays = normalize_rays(rays, lattice)
rays = sorted( (r, i) for i, r in enumerate(rays) )
distinct_rays = [ rays[i] for i in range(len(rays)) if rays[i][0] != rays[i-1][0] ]
if distinct_rays:
rays = sorted( (i, r) for r, i in distinct_rays )
rays = [ r[1] for r in rays ]
else: # all given rays were the same
rays = [ rays[0][0] ]
lattice = rays[0].parent()
if lattice.dimension() != 2:
raise ValueError('the lattice must be 2-dimensional.')
n = len(rays)
if n == 1 or n == 2 and rays[0] == -rays[1]:
cones = [(i, ) for i in range(n)]
return RationalPolyhedralFan(cones, rays, lattice, False)
import math
# each sorted_rays entry = (angle, ray, original_ray_index)
sorted_rays = sorted( (math.atan2(r[0],r[1]), r, i) for i,r in enumerate(rays) )
cones = []
is_complete = True
for i in range(n):
r0 = sorted_rays[i-1][1]
r1 = sorted_rays[i][1]
if r1.is_zero():
raise ValueError('only non-zero vectors define rays')
assert r0 != r1
cross_prod = r0[0]*r1[1]-r0[1]*r1[0]
if cross_prod < 0:
r0_index = (i-1) % len(sorted_rays)
r1_index = i
cones.append((sorted_rays[r0_index][2], sorted_rays[r1_index][2]))
else:
is_complete = False
return RationalPolyhedralFan(cones, rays, lattice, is_complete)
class Cone_of_fan(ConvexRationalPolyhedralCone):
r"""
Construct a cone belonging to a fan.