passagemath-polyhedra 10.6.31rc3__cp314-cp314-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of passagemath-polyhedra might be problematic. Click here for more details.

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