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
@@ -0,0 +1,1884 @@
1
+ # sage_setup: distribution = sagemath-polyhedra
2
+ # sage.doctest: needs sage.combinat sage.graphs
3
+ r"""
4
+ Morphisms between toric lattices compatible with fans
5
+
6
+ This module is a part of the framework for toric varieties
7
+ (:mod:`~sage.schemes.toric.variety`,
8
+ :mod:`~sage.schemes.toric.fano_variety`). Its main purpose is to
9
+ provide support for working with lattice morphisms compatible with fans via
10
+ :class:`FanMorphism` class.
11
+
12
+ AUTHORS:
13
+
14
+ - Andrey Novoseltsev (2010-10-17): initial version.
15
+ - Andrey Novoseltsev (2011-04-11): added tests for injectivity/surjectivity,
16
+ fibration, bundle, as well as some related methods.
17
+
18
+ EXAMPLES:
19
+
20
+ Let's consider the face and normal fans of the "diamond" and the projection
21
+ to the `x`-axis::
22
+
23
+ sage: diamond = lattice_polytope.cross_polytope(2)
24
+ sage: face = FaceFan(diamond, lattice=ToricLattice(2))
25
+ sage: normal = NormalFan(diamond)
26
+ sage: N = face.lattice()
27
+ sage: H = End(N)
28
+ sage: phi = H([N.0, 0])
29
+ sage: phi
30
+ Free module morphism defined by the matrix
31
+ [1 0]
32
+ [0 0]
33
+ Domain: 2-d lattice N
34
+ Codomain: 2-d lattice N
35
+ sage: FanMorphism(phi, normal, face)
36
+ Traceback (most recent call last):
37
+ ...
38
+ ValueError: the image of generating cone #1 of the domain fan
39
+ is not contained in a single cone of the codomain fan!
40
+
41
+ Some of the cones of the normal fan fail to be mapped to a single cone of the
42
+ face fan. We can rectify the situation in the following way::
43
+
44
+ sage: fm = FanMorphism(phi, normal, face, subdivide=True)
45
+ sage: fm
46
+ Fan morphism defined by the matrix
47
+ [1 0]
48
+ [0 0]
49
+ Domain fan: Rational polyhedral fan in 2-d lattice N
50
+ Codomain fan: Rational polyhedral fan in 2-d lattice N
51
+ sage: fm.domain_fan().rays()
52
+ N( 1, 1),
53
+ N( 1, -1),
54
+ N(-1, -1),
55
+ N(-1, 1),
56
+ N( 0, -1),
57
+ N( 0, 1)
58
+ in 2-d lattice N
59
+ sage: normal.rays()
60
+ N( 1, 1),
61
+ N( 1, -1),
62
+ N(-1, -1),
63
+ N(-1, 1)
64
+ in 2-d lattice N
65
+
66
+ As you see, it was necessary to insert two new rays (to prevent "upper" and
67
+ "lower" cones of the normal fan from being mapped to the whole `x`-axis).
68
+ """
69
+
70
+
71
+ # ****************************************************************************
72
+ # Copyright (C) 2010 Andrey Novoseltsev <novoselt@gmail.com>
73
+ # Copyright (C) 2010 William Stein <wstein@gmail.com>
74
+ #
75
+ # Distributed under the terms of the GNU General Public License (GPL)
76
+ #
77
+ # https://www.gnu.org/licenses/
78
+ # ****************************************************************************
79
+
80
+ import operator
81
+
82
+ from sage.categories.homset import Hom
83
+ from sage.geometry.cone import Cone
84
+ from sage.geometry.fan import Fan, RationalPolyhedralFan
85
+ from sage.matrix.constructor import matrix
86
+ from sage.matrix.special import identity_matrix
87
+ from sage.structure.element import Matrix
88
+ from sage.misc.cachefunc import cached_method
89
+ from sage.misc.misc_c import prod
90
+ from sage.modules.free_module_morphism import FreeModuleMorphism
91
+ from sage.rings.infinity import Infinity
92
+ from sage.rings.integer_ring import ZZ
93
+ from sage.rings.infinity import InfinityElement
94
+ from functools import reduce
95
+
96
+
97
+ class FanMorphism(FreeModuleMorphism):
98
+ r"""
99
+ Create a fan morphism.
100
+
101
+ Let `\Sigma_1` and `\Sigma_2` be two fans in lattices `N_1` and `N_2`
102
+ respectively. Let `\phi` be a morphism (i.e. a linear map) from `N_1` to
103
+ `N_2`. We say that `\phi` is *compatible* with `\Sigma_1` and `\Sigma_2`
104
+ if every cone `\sigma_1\in\Sigma_1` is mapped by `\phi` into a single cone
105
+ `\sigma_2\in\Sigma_2`, i.e. `\phi(\sigma_1)\subset\sigma_2` (`\sigma_2`
106
+ may be different for different `\sigma_1`).
107
+
108
+ By a **fan morphism** we understand a morphism between two lattices
109
+ compatible with specified fans in these lattices. Such morphisms behave in
110
+ exactly the same way as "regular" morphisms between lattices, but:
111
+
112
+ * fan morphisms have a special constructor allowing some automatic
113
+ adjustments to the initial fans (see below);
114
+ * fan morphisms are aware of the associated fans and they can be
115
+ accessed via :meth:`codomain_fan` and :meth:`domain_fan`;
116
+ * fan morphisms can efficiently compute :meth:`image_cone` of a given
117
+ cone of the domain fan and :meth:`preimage_cones` of a given cone of
118
+ the codomain fan.
119
+
120
+ INPUT:
121
+
122
+ - ``morphism`` -- either a morphism between domain and codomain, or an
123
+ integral matrix defining such a morphism;
124
+
125
+ - ``domain_fan`` -- a :class:`fan
126
+ <sage.geometry.fan.RationalPolyhedralFan>` in the domain
127
+
128
+ - ``codomain`` -- (default: ``None``) either a codomain lattice or a fan in
129
+ the codomain. If the codomain fan is not given, the image fan (fan
130
+ generated by images of generating cones) of ``domain_fan`` will be used,
131
+ if possible.
132
+
133
+ - ``subdivide`` -- boolean (default: ``False``); if ``True`` and ``domain_fan`` is
134
+ not compatible with the codomain fan because it is too coarse, it will be
135
+ automatically refined to become compatible (the minimal refinement is
136
+ canonical, so there are no choices involved)
137
+
138
+ - ``check`` -- boolean (default: ``True``); if ``False``, given fans and morphism
139
+ will be assumed to be compatible. Be careful when using this option,
140
+ since wrong assumptions can lead to wrong and hard-to-detect errors. On
141
+ the other hand, this option may save you some time.
142
+
143
+ - ``verbose`` -- boolean (default: ``False``); if ``True``, some information may be
144
+ printed during construction of the fan morphism
145
+
146
+ OUTPUT: a fan morphism
147
+
148
+ EXAMPLES:
149
+
150
+ Here we consider the face and normal fans of the "diamond" and the
151
+ projection to the `x`-axis::
152
+
153
+ sage: diamond = lattice_polytope.cross_polytope(2)
154
+ sage: face = FaceFan(diamond, lattice=ToricLattice(2))
155
+ sage: normal = NormalFan(diamond)
156
+ sage: N = face.lattice()
157
+ sage: H = End(N)
158
+ sage: phi = H([N.0, 0])
159
+ sage: phi
160
+ Free module morphism defined by the matrix
161
+ [1 0]
162
+ [0 0]
163
+ Domain: 2-d lattice N
164
+ Codomain: 2-d lattice N
165
+ sage: fm = FanMorphism(phi, face, normal)
166
+ sage: fm.domain_fan() is face
167
+ True
168
+
169
+ Note, that since ``phi`` is compatible with these fans, the returned
170
+ fan is exactly the same object as the initial ``domain_fan``. ::
171
+
172
+ sage: FanMorphism(phi, normal, face)
173
+ Traceback (most recent call last):
174
+ ...
175
+ ValueError: the image of generating cone #1 of the domain fan
176
+ is not contained in a single cone of the codomain fan!
177
+ sage: fm = FanMorphism(phi, normal, face, subdivide=True)
178
+ sage: fm.domain_fan() is normal
179
+ False
180
+ sage: fm.domain_fan().ngenerating_cones()
181
+ 6
182
+
183
+ We had to subdivide two of the four cones of the normal fan, since
184
+ they were mapped by ``phi`` into non-strictly convex cones.
185
+
186
+ It is possible to omit the codomain fan, in which case the image fan will
187
+ be used instead of it::
188
+
189
+ sage: fm = FanMorphism(phi, face)
190
+ sage: fm.codomain_fan()
191
+ Rational polyhedral fan in 2-d lattice N
192
+ sage: fm.codomain_fan().rays()
193
+ N( 1, 0),
194
+ N(-1, 0)
195
+ in 2-d lattice N
196
+
197
+ Now we demonstrate a more subtle example. We take the first quadrant as our
198
+ domain fan. Then we divide the first quadrant into three cones, throw away
199
+ the middle one and take the other two as our codomain fan. These fans are
200
+ incompatible with the identity lattice morphism since the image of the
201
+ domain fan is out of the support of the codomain fan::
202
+
203
+ sage: N = ToricLattice(2)
204
+ sage: phi = End(N).identity()
205
+ sage: F1 = Fan(cones=[(0,1)], rays=[(1,0), (0,1)])
206
+ sage: F2 = Fan(cones=[(0,1), (2,3)],
207
+ ....: rays=[(1,0), (2,1), (1,2), (0,1)])
208
+ sage: FanMorphism(phi, F1, F2)
209
+ Traceback (most recent call last):
210
+ ...
211
+ ValueError: the image of generating cone #0 of the domain fan
212
+ is not contained in a single cone of the codomain fan!
213
+ sage: FanMorphism(phi, F1, F2, subdivide=True)
214
+ Traceback (most recent call last):
215
+ ...
216
+ ValueError: morphism defined by
217
+ [1 0]
218
+ [0 1]
219
+ does not map
220
+ Rational polyhedral fan in 2-d lattice N
221
+ into the support of
222
+ Rational polyhedral fan in 2-d lattice N!
223
+
224
+ The problem was detected and handled correctly (i.e. an exception was
225
+ raised). However, the used algorithm requires extra checks for this
226
+ situation after constructing a potential subdivision and this can take
227
+ significant time. You can save about half the time using
228
+ ``check=False`` option, if you know in advance that it is possible to
229
+ make fans compatible with the morphism by subdividing the domain fan.
230
+ Of course, if your assumption was incorrect, the result will be wrong
231
+ and you will get a fan which *does* map into the support of the
232
+ codomain fan, but is **not** a subdivision of the domain fan. You
233
+ can test it on the example above::
234
+
235
+ sage: fm = FanMorphism(phi, F1, F2, subdivide=True,
236
+ ....: check=False, verbose=True)
237
+ Placing ray images (... ms)
238
+ Computing chambers (... ms)
239
+ Number of domain cones: 1.
240
+ Number of chambers: 2.
241
+ Cone 0 sits in chambers 0 1 (... ms)
242
+ sage: fm.domain_fan().is_equivalent(F2)
243
+ True
244
+ """
245
+
246
+ def __init__(self, morphism, domain_fan,
247
+ codomain=None,
248
+ subdivide=False,
249
+ check=True,
250
+ verbose=False):
251
+ r"""
252
+ Create a fan morphism.
253
+
254
+ See :class:`FanMorphism` for documentation.
255
+
256
+ TESTS::
257
+
258
+ sage: quadrant = Cone([(1,0), (0,1)])
259
+ sage: quadrant = Fan([quadrant])
260
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
261
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
262
+ sage: fm
263
+ Fan morphism defined by the matrix
264
+ [1 0]
265
+ [0 1]
266
+ Domain fan: Rational polyhedral fan in 2-d lattice N
267
+ Codomain fan: Rational polyhedral fan in 2-d lattice N
268
+ sage: TestSuite(fm).run(skip='_test_category')
269
+ """
270
+ assert isinstance(domain_fan, RationalPolyhedralFan)
271
+ if isinstance(codomain, RationalPolyhedralFan):
272
+ codomain, codomain_fan = codomain.lattice(), codomain
273
+ else:
274
+ codomain_fan = None
275
+ if isinstance(morphism, FreeModuleMorphism):
276
+ parent = morphism.parent()
277
+ A = morphism.matrix()
278
+ elif isinstance(morphism, Matrix):
279
+ A = morphism
280
+ if codomain is None:
281
+ raise ValueError("codomain (fan) must be given explicitly if "
282
+ "morphism is given by a matrix!")
283
+ parent = Hom(domain_fan.lattice(), codomain)
284
+ else:
285
+ raise TypeError("morphism must be either a FreeModuleMorphism "
286
+ "or a matrix!\nGot: %s" % morphism)
287
+ super().__init__(parent, A)
288
+ self._domain_fan = domain_fan
289
+ self._image_cone = dict()
290
+ self._preimage_cones = dict()
291
+ self._preimage_fans = dict()
292
+ self._primitive_preimage_cones = dict()
293
+ if codomain_fan is None:
294
+ self._construct_codomain_fan(check)
295
+ else:
296
+ self._codomain_fan = codomain_fan
297
+ if subdivide:
298
+ self._subdivide_domain_fan(check, verbose)
299
+ elif check:
300
+ self._validate()
301
+
302
+ def __mul__(self, right):
303
+ """
304
+ Return the composition of ``self`` and ``right``.
305
+
306
+ INPUT:
307
+
308
+ - ``right`` -- a :class:`FanMorphism` whose :meth:`codomain_fan` can be
309
+ mapped to :meth:`domain_fan` of ``self`` via identity map of lattices
310
+
311
+ OUTPUT: a :class:`FanMorphism`
312
+
313
+ EXAMPLES::
314
+
315
+ sage: A2 = toric_varieties.A2()
316
+ sage: P3 = toric_varieties.P(3) # needs palp
317
+ sage: m = matrix([(2,0,0), (1,1,0)])
318
+ sage: phi = A2.hom(m, P3).fan_morphism(); phi # needs palp
319
+ Fan morphism defined by the matrix
320
+ [2 0 0]
321
+ [1 1 0]
322
+ Domain fan: Rational polyhedral fan in 2-d lattice N
323
+ Codomain fan: Rational polyhedral fan in 3-d lattice N
324
+ sage: prod(phi.factor()) # indirect test # needs palp
325
+ Fan morphism defined by the matrix
326
+ [2 0 0]
327
+ [1 1 0]
328
+ Domain fan: Rational polyhedral fan in 2-d lattice N
329
+ Codomain fan: Rational polyhedral fan in 3-d lattice N
330
+ """
331
+ if not isinstance(right, FanMorphism):
332
+ raise TypeError(
333
+ "fan morphisms should be composed with fan morphisms")
334
+ # We don't need it, we just check compatibility of fans:
335
+ FanMorphism(identity_matrix(self.domain().dimension()),
336
+ right.codomain_fan(), self.domain_fan())
337
+ m = right.matrix() * self.matrix()
338
+ return FanMorphism(m, right.domain_fan(), self.codomain_fan())
339
+
340
+ def _RISGIS(self):
341
+ r"""
342
+ Return Ray Images Star Generator Indices Sets.
343
+
344
+ OUTPUT:
345
+
346
+ - a :class:`tuple` of :class:`frozenset`s of integers, the `i`-th set
347
+ is the set of indices of star generators for the minimal cone of the
348
+ :meth:`codomain_fan` containing the image of the `i`-th ray of the
349
+ :meth:`domain_fan`.
350
+
351
+ TESTS::
352
+
353
+ sage: diamond = lattice_polytope.cross_polytope(2)
354
+ sage: face = FaceFan(diamond)
355
+ sage: normal = NormalFan(diamond)
356
+ sage: N = face.lattice()
357
+ sage: fm = FanMorphism(identity_matrix(2),
358
+ ....: normal, face, subdivide=True)
359
+ sage: fm._RISGIS()
360
+ (frozenset({2}),
361
+ frozenset({3}),
362
+ frozenset({0}),
363
+ frozenset({1}),
364
+ frozenset({0, 1}),
365
+ frozenset({0, 3}),
366
+ frozenset({2, 3}),
367
+ frozenset({1, 2}))
368
+ """
369
+ if "_RISGIS_" not in self.__dict__:
370
+ try:
371
+ cones = [self._codomain_fan.cone_containing(self(ray))
372
+ for ray in self._domain_fan.rays()]
373
+ except ValueError:
374
+ self._support_error()
375
+ self._RISGIS_ = tuple(frozenset(cone.star_generator_indices())
376
+ for cone in cones)
377
+ return self._RISGIS_
378
+
379
+ def _chambers(self):
380
+ r"""
381
+ Return chambers in the domain corresponding to the codomain fan.
382
+
383
+ This function is used during automatic refinement of the domain fans,
384
+ see :meth:`_subdivide_domain_fan`.
385
+
386
+ OUTPUT:
387
+
388
+ - a :class:`tuple` ``(chambers, cone_to_chamber)``, where
389
+
390
+ - ``chambers`` is a :class:`list` of :class:`cones
391
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` in the domain of
392
+ ``self``
393
+
394
+ - ``cone_to_chamber`` is a :class:`list` of integers, if its `i`-th
395
+ element is `j`, then the `j`-th element of ``chambers`` is the
396
+ inverse image of the `i`-th generating cone of the codomain fan.
397
+
398
+ TESTS::
399
+
400
+ sage: F = NormalFan(lattice_polytope.cross_polytope(2))
401
+ sage: N = F.lattice()
402
+ sage: H = End(N)
403
+ sage: phi = H([N.0, 0])
404
+ sage: fm = FanMorphism(phi, F, F, subdivide=True)
405
+ sage: fm._chambers()
406
+ ([2-d cone in 2-d lattice N,
407
+ 1-d cone in 2-d lattice N,
408
+ 2-d cone in 2-d lattice N],
409
+ [0, 1, 2, 1])
410
+ """
411
+ kernel_rays = []
412
+ for ray in self.kernel().basis():
413
+ kernel_rays.append(ray)
414
+ kernel_rays.append(-ray)
415
+ image_rays = []
416
+ for ray in self.image().basis():
417
+ image_rays.append(ray)
418
+ image_rays.append(-ray)
419
+ image = Cone(image_rays)
420
+ chambers = []
421
+ cone_to_chamber = []
422
+ for cone in self._codomain_fan:
423
+ chamber = Cone([self.lift(ray) for ray in cone.intersection(image)]
424
+ + kernel_rays, lattice=self.domain())
425
+ cone_to_chamber.append(len(chambers))
426
+ for i, old_chamber in enumerate(chambers):
427
+ if old_chamber.is_equivalent(chamber):
428
+ cone_to_chamber[-1] = i
429
+ break
430
+ if cone_to_chamber[-1] == len(chambers):
431
+ chambers.append(chamber)
432
+ return (chambers, cone_to_chamber)
433
+
434
+ def _construct_codomain_fan(self, check):
435
+ r"""
436
+ Construct the codomain fan as the image of the domain one.
437
+
438
+ .. WARNING::
439
+
440
+ This method should be called only during initialization.
441
+
442
+ INPUT:
443
+
444
+ - ``check`` -- passed on to the fan constructor
445
+
446
+ OUTPUT: none, but the codomain fan of ``self`` is set to the constructed fan
447
+
448
+ TESTS::
449
+
450
+ sage: quadrant = Cone([(1,0), (0,1)])
451
+ sage: quadrant = Fan([quadrant])
452
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
453
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl)
454
+ Traceback (most recent call last):
455
+ ...
456
+ ValueError: codomain (fan) must be given explicitly
457
+ if morphism is given by a matrix!
458
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, ZZ^2)
459
+ sage: fm.codomain_fan().rays() # indirect doctest
460
+ (1, 0),
461
+ (0, 1),
462
+ (1, 1)
463
+ in Ambient free module of rank 2
464
+ over the principal ideal domain Integer Ring
465
+ """
466
+ # We literally try to construct the image fan and hope that it works.
467
+ # If it does not, the fan constructor will raise an exception.
468
+ domain_fan = self._domain_fan
469
+ self._codomain_fan = Fan(cones=(domain_cone.ambient_ray_indices()
470
+ for domain_cone in domain_fan),
471
+ rays=(self(ray) for ray in domain_fan.rays()),
472
+ lattice=self.codomain(),
473
+ discard_faces=True, check=check)
474
+
475
+ def _latex_(self):
476
+ r"""
477
+ Return the `\LaTeX` representation of ``self``.
478
+
479
+ OUTPUT: a :class:`string`
480
+
481
+ EXAMPLES::
482
+
483
+ sage: quadrant = Cone([(1,0), (0,1)])
484
+ sage: quadrant = Fan([quadrant])
485
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
486
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
487
+ sage: fm
488
+ Fan morphism defined by the matrix
489
+ [1 0]
490
+ [0 1]
491
+ Domain fan: Rational polyhedral fan in 2-d lattice N
492
+ Codomain fan: Rational polyhedral fan in 2-d lattice N
493
+ sage: print(fm._latex_())
494
+ \left(\begin{array}{rr}
495
+ 1 & 0 \\
496
+ 0 & 1
497
+ \end{array}\right) : \Sigma^{2} \to \Sigma^{2}
498
+ """
499
+ from sage.misc.latex import latex
500
+ return (r"%s : %s \to %s" % (latex(self.matrix()),
501
+ latex(self.domain_fan()), latex(self.codomain_fan())))
502
+
503
+ @cached_method
504
+ def _ray_index_map(self):
505
+ r"""
506
+ Return the map between indices of rays in domain and codomain fans.
507
+
508
+ OUTPUT:
509
+
510
+ - a tuple of integers. If the `i`-th entry is -1, the `i`-th ray of the
511
+ domain fan is mapped to the origin. If it is `j`, then the `i`-th ray
512
+ of the domain fan is mapped onto the `j`-th ray of the codomain fan.
513
+ If there is a ray in the domain fan which is mapped into the relative
514
+ interior of a higher dimensional cone, a :exc:`ValueError`
515
+ exception is raised.
516
+
517
+ .. NOTE::
518
+
519
+ This method is used by :meth:`is_bundle` and :meth:`is_fibration`.
520
+
521
+ TESTS::
522
+
523
+ sage: # needs palp
524
+ sage: Sigma = toric_varieties.dP8().fan()
525
+ sage: Sigma_p = toric_varieties.P1().fan()
526
+ sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
527
+ sage: phi._ray_index_map()
528
+ (-1, 1, -1, 0)
529
+ sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
530
+ sage: xi._ray_index_map()
531
+ Traceback (most recent call last):
532
+ ...
533
+ ValueError: ray #1 is mapped into a 2-d cone!
534
+ """
535
+ Sigma = self.domain_fan()
536
+ ray_index_map = [-1] * Sigma.nrays()
537
+ for i, rho in enumerate(Sigma(1)):
538
+ sigma_p = self.image_cone(rho)
539
+ if sigma_p.nrays() > 1:
540
+ raise ValueError("ray #%d is mapped into a %d-d cone!" %
541
+ (i, sigma_p.dim()))
542
+ elif sigma_p.nrays() == 1:
543
+ ray_index_map[i] = sigma_p.ambient_ray_indices()[0]
544
+ return tuple(ray_index_map)
545
+
546
+ def _repr_(self):
547
+ r"""
548
+ Return the string representation of ``self``.
549
+
550
+ OUTPUT: a :class:`string`
551
+
552
+ EXAMPLES::
553
+
554
+ sage: quadrant = Cone([(1,0), (0,1)])
555
+ sage: quadrant = Fan([quadrant])
556
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
557
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
558
+ sage: print(fm._repr_())
559
+ Fan morphism defined by the matrix
560
+ [1 0]
561
+ [0 1]
562
+ Domain fan: Rational polyhedral fan in 2-d lattice N
563
+ Codomain fan: Rational polyhedral fan in 2-d lattice N
564
+ """
565
+ return ("Fan morphism defined by the matrix\n"
566
+ "%s\n"
567
+ "Domain fan: %s\n"
568
+ "Codomain fan: %s"
569
+ % (self.matrix(), self.domain_fan(), self.codomain_fan()))
570
+
571
+ def _subdivide_domain_fan(self, check, verbose):
572
+ r"""
573
+ Subdivide the domain fan to make it compatible with the codomain fan.
574
+
575
+ .. WARNING::
576
+
577
+ This method should be called only during initialization.
578
+
579
+ INPUT:
580
+
581
+ - ``check`` -- boolean (default: ``True``); if ``False``, some of the
582
+ consistency checks will be omitted, which saves time but can
583
+ potentially lead to wrong results. Currently, with
584
+ ``check=False`` option there will be no check that ``domain_fan``
585
+ maps to ``codomain_fan``
586
+
587
+ - ``verbose`` -- boolean (default: ``False``); if ``True``, some timing
588
+ information will be printed in the process
589
+
590
+ OUTPUT:
591
+
592
+ - none, but the domain fan of ``self`` is replaced with its minimal
593
+ refinement, if possible. Otherwise a :exc:`ValueError`
594
+ exception is raised.
595
+
596
+ TESTS::
597
+
598
+ sage: quadrant = Cone([(1,0), (0,1)])
599
+ sage: quadrant = Fan([quadrant])
600
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
601
+ sage: fm = FanMorphism(identity_matrix(2), quadrant, quadrant_bl)
602
+ Traceback (most recent call last):
603
+ ...
604
+ ValueError: the image of generating cone #0 of the domain fan
605
+ is not contained in a single cone of the codomain fan!
606
+ sage: fm = FanMorphism(identity_matrix(2), quadrant,
607
+ ....: quadrant_bl, subdivide=True)
608
+ sage: fm.domain_fan().rays() # indirect doctest
609
+ N(1, 0),
610
+ N(0, 1),
611
+ N(1, 1)
612
+ in 2-d lattice N
613
+
614
+ Now we demonstrate a more subtle example. We take the first quadrant
615
+ as our ``domain_fan``. Then we divide the first quadrant into three
616
+ cones, throw away the middle one and take the other two as our
617
+ ``codomain_fan``. These fans are incompatible with the identity
618
+ lattice morphism since the image of ``domain_fan`` is out of the
619
+ support of ``codomain_fan``::
620
+
621
+ sage: N = ToricLattice(2)
622
+ sage: phi = End(N).identity()
623
+ sage: F1 = Fan(cones=[(0,1)], rays=[(1,0), (0,1)])
624
+ sage: F2 = Fan(cones=[(0,1), (2,3)],
625
+ ....: rays=[(1,0), (2,1), (1,2), (0,1)])
626
+ sage: FanMorphism(phi, F1, F2)
627
+ Traceback (most recent call last):
628
+ ...
629
+ ValueError: the image of generating cone #0 of the domain fan
630
+ is not contained in a single cone of the codomain fan!
631
+ sage: FanMorphism(phi, F1, F2, subdivide=True)
632
+ Traceback (most recent call last):
633
+ ...
634
+ ValueError: morphism defined by
635
+ [1 0]
636
+ [0 1]
637
+ does not map
638
+ Rational polyhedral fan in 2-d lattice N
639
+ into the support of
640
+ Rational polyhedral fan in 2-d lattice N!
641
+
642
+ We check that :issue:`10943` is fixed::
643
+
644
+ sage: Sigma = Fan(rays=[(1,1,0), (1,-1,0)], cones=[(0,1)])
645
+ sage: Sigma_prime = FaceFan(lattice_polytope.cross_polytope(3))
646
+ sage: fm = FanMorphism(identity_matrix(3),
647
+ ....: Sigma, Sigma_prime, subdivide=True)
648
+ sage: fm.domain_fan().rays()
649
+ N(1, 1, 0),
650
+ N(1, -1, 0),
651
+ N(1, 0, 0)
652
+ in 3-d lattice N
653
+ sage: [cone.ambient_ray_indices() for cone in fm.domain_fan()]
654
+ [(0, 2), (1, 2)]
655
+
656
+ sage: sigma = Cone([(0,1), (3,1)])
657
+ sage: Sigma = Fan([sigma])
658
+ sage: Sigma_prime = Sigma.subdivide([(1,1), (2,1)])
659
+ sage: FanMorphism(identity_matrix(2),
660
+ ....: Sigma, Sigma_prime, subdivide=True)
661
+ Fan morphism defined by the matrix
662
+ [1 0]
663
+ [0 1]
664
+ Domain fan: Rational polyhedral fan in 2-d lattice N
665
+ Codomain fan: Rational polyhedral fan in 2-d lattice N
666
+ """
667
+ domain_fan = self._domain_fan
668
+ lattice_dim = self.domain().dimension()
669
+ if verbose:
670
+ from sage.misc.timing import walltime
671
+ start = walltime()
672
+ print("Placing ray images", end=" ")
673
+ # Figure out where 1-dimensional cones (i.e. rays) are mapped.
674
+ RISGIS = self._RISGIS()
675
+ if verbose:
676
+ print("(%.3f ms)" % walltime(start))
677
+ # Subdivide cones that require it.
678
+ chambers = None # preimages of codomain cones, computed if necessary
679
+ new_cones = []
680
+ for cone_index, domain_cone in enumerate(domain_fan):
681
+ if reduce(operator.and_,
682
+ (RISGIS[i] for i in domain_cone.ambient_ray_indices())):
683
+ # There is a codomain cone containing all rays of this domain
684
+ # cone, no need to subdivide it.
685
+ new_cones.append(domain_cone)
686
+ continue
687
+ dim = domain_cone.dim()
688
+ if chambers is None:
689
+ if verbose:
690
+ start = walltime()
691
+ print("Computing chambers", end=" ")
692
+ chambers, cone_to_chamber = self._chambers()
693
+ if verbose:
694
+ print("(%.3f ms)" % walltime(start))
695
+ print("Number of domain cones: %d.\n"
696
+ "Number of chambers: %d." %
697
+ (domain_fan.ngenerating_cones(), len(chambers)))
698
+ # Subdivide domain_cone.
699
+ if verbose:
700
+ start = walltime()
701
+ print("Cone %d sits in chambers" % cone_index, end=" ")
702
+ # It seems that there is no quick way to determine which chambers
703
+ # do NOT intersect this domain cone, so we go over all of them.
704
+ parts = []
705
+ for chamber_index, chamber in enumerate(chambers):
706
+ # If chamber is small, intersection will be small.
707
+ if chamber.dim() < dim:
708
+ continue
709
+ new_part = domain_cone.intersection(chamber)
710
+ # If intersection is small, it is not a generating cone.
711
+ if new_part.dim() < dim:
712
+ continue
713
+ # Small cones may have repetitive intersections with chambers.
714
+ if (dim == lattice_dim or
715
+ not any(part.is_equivalent(new_part) for part in parts)):
716
+ parts.append(new_part)
717
+ if verbose:
718
+ print(chamber_index, end=" ")
719
+ if check:
720
+ # Check if the subdivision is complete, i.e. there are no
721
+ # missing pieces of domain_cone. To do this, we construct a
722
+ # fan from the obtained parts and check that interior points
723
+ # of boundary cones of this fan are in the interior of the
724
+ # original cone. In any case we know that we are constructing
725
+ # a valid fan, so passing check=False to Fan(...) is OK.
726
+ if verbose:
727
+ print("(%.3f ms)" % walltime(start))
728
+ start = walltime()
729
+ print("Checking for missing pieces", end=" ")
730
+ cone_subdivision = Fan(parts, check=False)
731
+ for cone in cone_subdivision(dim - 1):
732
+ if len(cone.star_generators()) == 1:
733
+ if domain_cone.relative_interior_contains(
734
+ sum(cone.rays())):
735
+ self._support_error()
736
+ new_cones.extend(parts)
737
+ if verbose:
738
+ print("(%.3f ms)" % walltime(start))
739
+ if len(new_cones) > domain_fan.ngenerating_cones():
740
+ # Construct a new fan keeping old rays in the same order
741
+ new_rays = list(domain_fan.rays())
742
+ for cone in new_cones:
743
+ for ray in cone:
744
+ if ray not in new_rays:
745
+ new_rays.append(ray)
746
+ # Replace domain_fan, this is OK since this method must be called
747
+ # only during initialization of the FanMorphism.
748
+ self._domain_fan = Fan(new_cones, new_rays, domain_fan.lattice(),
749
+ check=False)
750
+ # Also remove RISGIS for the old fan
751
+ del self._RISGIS_
752
+
753
+ def _support_error(self):
754
+ r"""
755
+ Raise a :exc:`ValueError` exception due to support incompatibility.
756
+
757
+ OUTPUT: none, a :exc:`ValueError` exception is raised
758
+
759
+ TESTS:
760
+
761
+ We deliberately construct an invalid morphism for this demonstration::
762
+
763
+ sage: quadrant = Cone([(1,0), (0,1)])
764
+ sage: quadrant = Fan([quadrant])
765
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
766
+ sage: fm = FanMorphism(identity_matrix(2),
767
+ ....: quadrant, quadrant_bl, check=False)
768
+
769
+ Now we report that the morphism is invalid::
770
+
771
+ sage: fm._support_error()
772
+ Traceback (most recent call last):
773
+ ...
774
+ ValueError: morphism defined by
775
+ [1 0]
776
+ [0 1]
777
+ does not map
778
+ Rational polyhedral fan in 2-d lattice N
779
+ into the support of
780
+ Rational polyhedral fan in 2-d lattice N!
781
+ """
782
+ raise ValueError("morphism defined by\n"
783
+ "%s\n"
784
+ "does not map\n"
785
+ "%s\n"
786
+ "into the support of\n"
787
+ "%s!"
788
+ % (self.matrix(), self.domain_fan(), self.codomain_fan()))
789
+
790
+ def _validate(self):
791
+ r"""
792
+ Check if ``self`` is indeed compatible with domain and codomain fans.
793
+
794
+ OUTPUT:
795
+
796
+ - none, but a :exc:`ValueError` exception is raised if there is
797
+ a cone of the domain fan of ``self`` which is not completely
798
+ contained in a single cone of the codomain fan of ``self``,
799
+ or if one of these fans does not sit in the appropriate lattice.
800
+
801
+ EXAMPLES::
802
+
803
+ sage: N3 = ToricLattice(3, "N3")
804
+ sage: N2 = ToricLattice(2, "N2")
805
+ sage: H = Hom(N3, N2)
806
+ sage: phi = H([N2.0, N2.1, N2.0])
807
+ sage: F1 = Fan(cones=[(0,1,2), (1,2,3)],
808
+ ....: rays=[(1,1,1), (1,1,-1), (1,-1,1), (1,-1,-1)],
809
+ ....: lattice=N3)
810
+ sage: F1.rays()
811
+ N3(1, 1, 1),
812
+ N3(1, 1, -1),
813
+ N3(1, -1, 1),
814
+ N3(1, -1, -1)
815
+ in 3-d lattice N3
816
+ sage: [phi(ray) for ray in F1.rays()]
817
+ [N2(2, 1), N2(0, 1), N2(2, -1), N2(0, -1)]
818
+ sage: F2 = Fan(cones=[(0,1,2), (1,2,3)],
819
+ ....: rays=[(1,1,1), (1,1,-1), (1,2,1), (1,2,-1)],
820
+ ....: lattice=N3)
821
+ sage: F2.rays()
822
+ N3(1, 1, 1),
823
+ N3(1, 1, -1),
824
+ N3(1, 2, 1),
825
+ N3(1, 2, -1)
826
+ in 3-d lattice N3
827
+ sage: [phi(ray) for ray in F2.rays()]
828
+ [N2(2, 1), N2(0, 1), N2(2, 2), N2(0, 2)]
829
+ sage: F3 = Fan(cones=[(0,1), (1,2)],
830
+ ....: rays=[(1,0), (2,1), (0,1)],
831
+ ....: lattice=N2)
832
+ sage: FanMorphism(phi, F2, F3)
833
+ Fan morphism defined by the matrix
834
+ [1 0]
835
+ [0 1]
836
+ [1 0]
837
+ Domain fan: Rational polyhedral fan in 3-d lattice N3
838
+ Codomain fan: Rational polyhedral fan in 2-d lattice N2
839
+ sage: FanMorphism(phi, F1, F3) # indirect doctest
840
+ Traceback (most recent call last):
841
+ ...
842
+ ValueError: morphism defined by
843
+ [1 0]
844
+ [0 1]
845
+ [1 0]
846
+ does not map
847
+ Rational polyhedral fan in 3-d lattice N3
848
+ into the support of
849
+ Rational polyhedral fan in 2-d lattice N2!
850
+
851
+
852
+ TESTS::
853
+
854
+ sage: trivialfan2 = Fan([], [], lattice=ToricLattice(2))
855
+ sage: trivialfan3 = Fan([], [], lattice=ToricLattice(3))
856
+ sage: FanMorphism(zero_matrix(2,3), trivialfan2, trivialfan3)
857
+ Fan morphism defined by the matrix
858
+ [0 0 0]
859
+ [0 0 0]
860
+ Domain fan: Rational polyhedral fan in 2-d lattice N
861
+ Codomain fan: Rational polyhedral fan in 3-d lattice N
862
+ """
863
+ domain_fan = self._domain_fan
864
+ if domain_fan.lattice() is not self.domain():
865
+ raise ValueError("%s does not sit in %s!"
866
+ % (domain_fan, self.domain()))
867
+ codomain_fan = self._codomain_fan
868
+ if codomain_fan.lattice() is not self.codomain():
869
+ raise ValueError("%s does not sit in %s!"
870
+ % (codomain_fan, self.codomain()))
871
+ RISGIS = self._RISGIS()
872
+ for n, domain_cone in enumerate(domain_fan):
873
+ if not domain_cone.is_trivial() and \
874
+ not reduce(operator.and_,
875
+ (RISGIS[i] for i in domain_cone.ambient_ray_indices())):
876
+ raise ValueError("the image of generating cone #%d of the "
877
+ "domain fan is not contained in a single "
878
+ "cone of the codomain fan!" % n)
879
+
880
+ def codomain_fan(self, dim=None, codim=None):
881
+ r"""
882
+ Return the codomain fan of ``self``.
883
+
884
+ INPUT:
885
+
886
+ - ``dim`` -- dimension of the requested cones
887
+
888
+ - ``codim`` -- codimension of the requested cones
889
+
890
+ OUTPUT:
891
+
892
+ - :class:`rational polyhedral fan
893
+ <sage.geometry.fan.RationalPolyhedralFan>` if no parameters were
894
+ given, :class:`tuple` of :class:`cones
895
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` otherwise.
896
+
897
+ EXAMPLES::
898
+
899
+ sage: quadrant = Cone([(1,0), (0,1)])
900
+ sage: quadrant = Fan([quadrant])
901
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
902
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
903
+ sage: fm.codomain_fan()
904
+ Rational polyhedral fan in 2-d lattice N
905
+ sage: fm.codomain_fan() is quadrant
906
+ True
907
+ """
908
+ return self._codomain_fan(dim=dim, codim=codim)
909
+
910
+ def domain_fan(self, dim=None, codim=None):
911
+ r"""
912
+ Return the codomain fan of ``self``.
913
+
914
+ INPUT:
915
+
916
+ - ``dim`` -- dimension of the requested cones
917
+
918
+ - ``codim`` -- codimension of the requested cones
919
+
920
+ OUTPUT:
921
+
922
+ - :class:`rational polyhedral fan
923
+ <sage.geometry.fan.RationalPolyhedralFan>` if no parameters were
924
+ given, :class:`tuple` of :class:`cones
925
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` otherwise.
926
+
927
+ EXAMPLES::
928
+
929
+ sage: quadrant = Cone([(1,0), (0,1)])
930
+ sage: quadrant = Fan([quadrant])
931
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
932
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
933
+ sage: fm.domain_fan()
934
+ Rational polyhedral fan in 2-d lattice N
935
+ sage: fm.domain_fan() is quadrant_bl
936
+ True
937
+ """
938
+ return self._domain_fan(dim=dim, codim=codim)
939
+
940
+ def image_cone(self, cone):
941
+ r"""
942
+ Return the cone of the codomain fan containing the image of ``cone``.
943
+
944
+ INPUT:
945
+
946
+ - ``cone`` -- a :class:`cone
947
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` equivalent to a
948
+ cone of the :meth:`domain_fan` of ``self``.
949
+
950
+ OUTPUT:
951
+
952
+ - a :class:`cone <sage.geometry.cone.ConvexRationalPolyhedralCone>`
953
+ of the :meth:`codomain_fan` of ``self``.
954
+
955
+ EXAMPLES::
956
+
957
+ sage: quadrant = Cone([(1,0), (0,1)])
958
+ sage: quadrant = Fan([quadrant])
959
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
960
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
961
+ sage: fm.image_cone(Cone([(1,0)]))
962
+ 1-d cone of Rational polyhedral fan in 2-d lattice N
963
+ sage: fm.image_cone(Cone([(1,1)]))
964
+ 2-d cone of Rational polyhedral fan in 2-d lattice N
965
+
966
+ TESTS:
967
+
968
+ We check that complete codomain fans are handled correctly, since a
969
+ different algorithm is used in this case::
970
+
971
+ sage: diamond = lattice_polytope.cross_polytope(2)
972
+ sage: face = FaceFan(diamond, lattice=ToricLattice(2))
973
+ sage: normal = NormalFan(diamond)
974
+ sage: N = face.lattice()
975
+ sage: fm = FanMorphism(identity_matrix(2),
976
+ ....: normal, face, subdivide=True)
977
+ sage: fm.image_cone(Cone([(1,0)]))
978
+ 1-d cone of Rational polyhedral fan in 2-d lattice N
979
+ sage: fm.image_cone(Cone([(1,1)]))
980
+ 2-d cone of Rational polyhedral fan in 2-d lattice N
981
+ """
982
+ cone = self._domain_fan.embed(cone)
983
+ if cone not in self._image_cone:
984
+ codomain_fan = self._codomain_fan()
985
+ if cone.is_trivial():
986
+ self._image_cone[cone] = codomain_fan(0)[0]
987
+ elif codomain_fan.is_complete():
988
+ # Optimization for a common case
989
+ RISGIS = self._RISGIS()
990
+ CSGIS = set(reduce(operator.and_,
991
+ (RISGIS[i] for i in cone.ambient_ray_indices())))
992
+ image_cone = codomain_fan.generating_cone(CSGIS.pop())
993
+ for i in CSGIS:
994
+ image_cone = image_cone.intersection(
995
+ codomain_fan.generating_cone(i))
996
+ self._image_cone[cone] = image_cone
997
+ else:
998
+ self._image_cone[cone] = codomain_fan.cone_containing(
999
+ self(ray) for ray in cone)
1000
+ return self._image_cone[cone]
1001
+
1002
+ def index(self, cone=None):
1003
+ r"""
1004
+ Return the index of ``self`` as a map between lattices.
1005
+
1006
+ INPUT:
1007
+
1008
+ - ``cone`` -- (default: ``None``) a :class:`cone
1009
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` of the
1010
+ :meth:`codomain_fan` of ``self``.
1011
+
1012
+ OUTPUT: integer, infinity, or ``None``
1013
+
1014
+ If no cone was specified, this function computes the index of the
1015
+ image of ``self`` in the codomain. If a cone `\sigma` was given, the
1016
+ index of ``self`` over `\sigma` is computed in the sense of
1017
+ Definition 2.1.7 of [HLY2002]_: if `\sigma'` is any cone of the
1018
+ :meth:`domain_fan` of ``self`` whose relative interior is mapped to the
1019
+ relative interior of `\sigma`, it is the index of the image of
1020
+ `N'(\sigma')` in `N(\sigma)`, where `N'` and `N` are domain and codomain
1021
+ lattices respectively. While that definition was formulated for the case
1022
+ of the finite index only, we extend it to the infinite one as well and
1023
+ return ``None`` if there is no `\sigma'` at all. See examples below for
1024
+ situations when such things happen. Note also that the index of ``self``
1025
+ is the same as index over the trivial cone.
1026
+
1027
+ EXAMPLES::
1028
+
1029
+ sage: # needs palp
1030
+ sage: Sigma = toric_varieties.dP8().fan()
1031
+ sage: Sigma_p = toric_varieties.P1().fan()
1032
+ sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
1033
+ sage: phi.index()
1034
+ 1
1035
+ sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
1036
+ sage: psi.index()
1037
+ 2
1038
+ sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
1039
+ sage: xi.index()
1040
+ +Infinity
1041
+
1042
+ Infinite index in the last example indicates that the image has positive
1043
+ codimension in the codomain. Let's look at the rays of our fans::
1044
+
1045
+ sage: Sigma_p.rays() # needs palp
1046
+ N( 1),
1047
+ N(-1)
1048
+ in 1-d lattice N
1049
+ sage: Sigma.rays() # needs palp
1050
+ N( 1, 1),
1051
+ N( 0, 1),
1052
+ N(-1, -1),
1053
+ N( 1, 0)
1054
+ in 2-d lattice N
1055
+ sage: xi.factor()[0].domain_fan().rays() # needs palp
1056
+ N(-1, 0),
1057
+ N( 1, 0)
1058
+ in Sublattice <N(1, 0)>
1059
+
1060
+ We see that one of the rays of the fan of ``P1`` is mapped to a ray,
1061
+ while the other one to the interior of some 2-d cone. Both rays
1062
+ correspond to single points on ``P1``, yet one is mapped to the
1063
+ distinguished point of a torus invariant curve of ``dP8`` (with the
1064
+ rest of this curve being uncovered) and the other to a fixed point
1065
+ of ``dP8`` (thus completely covering this torus orbit in ``dP8``).
1066
+
1067
+ We should therefore expect the following behaviour: all indices over
1068
+ 1-d cones are ``None``, except for one which is infinite, and all
1069
+ indices over 2-d cones are ``None``, except for one which is 1::
1070
+
1071
+ sage: [xi.index(cone) for cone in Sigma(1)] # needs palp
1072
+ [None, None, None, +Infinity]
1073
+ sage: [xi.index(cone) for cone in Sigma(2)] # needs palp
1074
+ [None, 1, None, None]
1075
+
1076
+ TESTS::
1077
+
1078
+ sage: # needs palp
1079
+ sage: Sigma = toric_varieties.dP8().fan()
1080
+ sage: Sigma_p = toric_varieties.Cube_nonpolyhedral().fan()
1081
+ sage: m = matrix([[2,6,10], [7,11,13]])
1082
+ sage: zeta = FanMorphism(m, Sigma, Sigma_p, subdivide=True)
1083
+ sage: [zeta.index(cone) for cone in flatten(Sigma_p.cones())]
1084
+ [+Infinity, None, None, None, None, None, None, None, None, None,
1085
+ 4, 4, None, 4, None, None, 2, None, 4, None, 4, 1, 1, 1, 1, 1, 1]
1086
+ sage: zeta = prod(zeta.factor()[1:])
1087
+ sage: Sigma_p = zeta.codomain_fan()
1088
+ sage: [zeta.index(cone) for cone in flatten(Sigma_p.cones())]
1089
+ [4, 4, 4, 1, 4, 4, 4, 1, 1, 1, 1, 1, 1]
1090
+ sage: zeta.index() == zeta.index(Sigma_p(0)[0])
1091
+ True
1092
+ """
1093
+ if cone is None:
1094
+ try:
1095
+ return self.matrix().image().index_in(self.codomain())
1096
+ except ArithmeticError:
1097
+ cone = Cone([], lattice=self.codomain())
1098
+ cone = self._codomain_fan.embed(cone)
1099
+ PPCs = self.primitive_preimage_cones(cone)
1100
+ if not PPCs:
1101
+ return None
1102
+ Q = cone.sublattice_quotient()
1103
+ S = Q.submodule([self(g)
1104
+ for g in PPCs[0].sublattice_complement().gens()])
1105
+ i = prod((Q/S).invariants())
1106
+ return i if i > 0 else Infinity
1107
+
1108
+ def is_birational(self):
1109
+ r"""
1110
+ Check if ``self`` is birational.
1111
+
1112
+ OUTPUT: ``True`` if ``self`` is birational, ``False`` otherwise
1113
+
1114
+ For fan morphisms this check is equivalent to ``self.index() == 1`` and
1115
+ means that the corresponding map between toric varieties is birational.
1116
+
1117
+ EXAMPLES::
1118
+
1119
+ sage: # needs palp
1120
+ sage: Sigma = toric_varieties.dP8().fan()
1121
+ sage: Sigma_p = toric_varieties.P1().fan()
1122
+ sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
1123
+ sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
1124
+ sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
1125
+ sage: phi.index(), psi.index(), xi.index()
1126
+ (1, 2, +Infinity)
1127
+ sage: phi.is_birational(), psi.is_birational(), xi.is_birational()
1128
+ (True, False, False)
1129
+ """
1130
+ return self.index() == 1
1131
+
1132
+ @cached_method
1133
+ def is_bundle(self):
1134
+ r"""
1135
+ Check if ``self`` is a bundle.
1136
+
1137
+ OUTPUT: ``True`` if ``self`` is a bundle, ``False`` otherwise
1138
+
1139
+ Let `\phi: \Sigma \to \Sigma'` be a fan morphism such that the
1140
+ underlying lattice morphism `\phi: N \to N'` is surjective. Let
1141
+ `\Sigma_0` be the kernel fan of `\phi`. Then `\phi` is a **bundle** (or
1142
+ splitting) if there is a subfan `\widehat{\Sigma}` of `\Sigma` such
1143
+ that the following two conditions are satisfied:
1144
+
1145
+ #. Cones of `\Sigma` are precisely the cones of the form
1146
+ `\sigma_0 + \widehat{\sigma}`, where `\sigma_0 \in \Sigma_0` and
1147
+ `\widehat{\sigma} \in \widehat{\Sigma}`.
1148
+
1149
+ #. Cones of `\widehat{\Sigma}` are in bijection with cones of
1150
+ `\Sigma'` induced by `\phi` and `\phi` maps lattice points in
1151
+ every cone `\widehat{\sigma} \in \widehat{\Sigma}` bijectively
1152
+ onto lattice points in `\phi(\widehat{\sigma})`.
1153
+
1154
+ If a fan morphism `\phi: \Sigma \to \Sigma'` is a bundle, then
1155
+ `X_\Sigma` is a fiber bundle over `X_{\Sigma'}` with fibers
1156
+ `X_{\Sigma_0, N_0}`, where `N_0` is the kernel lattice of `\phi`. See
1157
+ [CLS2011]_ for more details.
1158
+
1159
+ .. SEEALSO:: :meth:`is_fibration`, :meth:`kernel_fan`.
1160
+
1161
+ EXAMPLES:
1162
+
1163
+ We consider several maps between fans of a del Pezzo surface and the
1164
+ projective line::
1165
+
1166
+ sage: # needs palp
1167
+ sage: Sigma = toric_varieties.dP8().fan()
1168
+ sage: Sigma_p = toric_varieties.P1().fan()
1169
+ sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
1170
+ sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
1171
+ sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
1172
+ sage: phi.is_bundle()
1173
+ True
1174
+ sage: phi.is_fibration()
1175
+ True
1176
+ sage: phi.index()
1177
+ 1
1178
+ sage: psi.is_bundle()
1179
+ False
1180
+ sage: psi.is_fibration()
1181
+ True
1182
+ sage: psi.index()
1183
+ 2
1184
+ sage: xi.is_fibration()
1185
+ False
1186
+ sage: xi.index()
1187
+ +Infinity
1188
+
1189
+ The first of these maps induces not only a fibration, but a fiber
1190
+ bundle structure. The second map is very similar, yet it fails to be
1191
+ a bundle, as its index is 2. The last map is not even a fibration.
1192
+ """
1193
+ if self.index() != 1:
1194
+ return False # Not surjective between lattices.
1195
+ Sigma = self.domain_fan()
1196
+ Sigma_p = self.codomain_fan()
1197
+ Sigma_0 = self.kernel_fan()
1198
+ if (Sigma.ngenerating_cones() !=
1199
+ Sigma_0.ngenerating_cones() * Sigma_p.ngenerating_cones()):
1200
+ return False # Definitely no splitting.
1201
+ try:
1202
+ ray_index_map = self._ray_index_map()
1203
+ except ValueError:
1204
+ return False # Rays are not mapped onto rays or the origin.
1205
+ # Figure out how Sigma_0 sits inside Sigma in terms of ray indices.
1206
+ I_0s = [Sigma.embed(sigma_0).ambient_ray_indices()
1207
+ for sigma_0 in Sigma_0]
1208
+ # We examine only generating cones, this is sufficient.
1209
+ for sigma_p in Sigma_p:
1210
+ primitive_cones = self.primitive_preimage_cones(sigma_p)
1211
+ if len(primitive_cones) != 1: # Should be only sigma_hat.
1212
+ return False
1213
+ sigma_hat = primitive_cones[0]
1214
+ if sigma_p.dim() != sigma_hat.dim():
1215
+ return False # sigma -> sigma_p is not a bijection
1216
+ I_p = sigma_p.ambient_ray_indices()
1217
+ I_hat = sigma_hat.ambient_ray_indices()
1218
+ if I_p != tuple(sorted(ray_index_map[i] for i in I_hat)):
1219
+ return False # sigma -> sigma_p is not a bijection
1220
+ # Check that sigma_hat + sigma_0 is always in Sigma.
1221
+ for I_0 in I_0s:
1222
+ I = tuple(sorted(I_hat + I_0))
1223
+ if all(sigma.ambient_ray_indices() != I for sigma in Sigma):
1224
+ return False
1225
+ return True
1226
+
1227
+ @cached_method
1228
+ def is_fibration(self):
1229
+ r"""
1230
+ Check if ``self`` is a fibration.
1231
+
1232
+ OUTPUT: ``True`` if ``self`` is a fibration, ``False`` otherwise
1233
+
1234
+ A fan morphism `\phi: \Sigma \to \Sigma'` is a **fibration**
1235
+ if for any cone `\sigma' \in \Sigma'` and any primitive
1236
+ preimage cone `\sigma \in \Sigma` corresponding to `\sigma'`
1237
+ the linear map of vector spaces `\phi_\RR` induces a bijection
1238
+ between `\sigma` and `\sigma'`, and, in addition, `\phi` is
1239
+ :meth:`dominant <is_dominant>` (that is, `\phi_\RR: N_\RR \to
1240
+ N'_\RR` is surjective).
1241
+
1242
+ If a fan morphism `\phi: \Sigma \to \Sigma'` is a fibration, then the
1243
+ associated morphism between toric varieties `\tilde{\phi}: X_\Sigma \to
1244
+ X_{\Sigma'}` is a fibration in the sense that it is surjective and all
1245
+ of its fibers have the same dimension, namely `\dim X_\Sigma -
1246
+ \dim X_{\Sigma'}`. These fibers do *not* have to be isomorphic, i.e. a
1247
+ fibration is not necessarily a fiber bundle. See [HLY2002]_ for more
1248
+ details.
1249
+
1250
+ .. SEEALSO:: :meth:`is_bundle`, :meth:`primitive_preimage_cones`.
1251
+
1252
+ EXAMPLES:
1253
+
1254
+ We consider several maps between fans of a del Pezzo surface and the
1255
+ projective line::
1256
+
1257
+ sage: # needs palp
1258
+ sage: Sigma = toric_varieties.dP8().fan()
1259
+ sage: Sigma_p = toric_varieties.P1().fan()
1260
+ sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
1261
+ sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
1262
+ sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
1263
+ sage: phi.is_bundle()
1264
+ True
1265
+ sage: phi.is_fibration()
1266
+ True
1267
+ sage: phi.index()
1268
+ 1
1269
+ sage: psi.is_bundle()
1270
+ False
1271
+ sage: psi.is_fibration()
1272
+ True
1273
+ sage: psi.index()
1274
+ 2
1275
+ sage: xi.is_fibration()
1276
+ False
1277
+ sage: xi.index()
1278
+ +Infinity
1279
+
1280
+ The first of these maps induces not only a fibration, but a fiber
1281
+ bundle structure. The second map is very similar, yet it fails to be
1282
+ a bundle, as its index is 2. The last map is not even a fibration.
1283
+
1284
+ TESTS:
1285
+
1286
+ We check that reviewer's example on :issue:`11200` works as expected::
1287
+
1288
+ sage: P1 = toric_varieties.P1()
1289
+ sage: A1 = toric_varieties.A1()
1290
+ sage: phi = FanMorphism(matrix([[1]]), A1.fan(), P1.fan())
1291
+ sage: phi.is_fibration()
1292
+ False
1293
+ """
1294
+ if not self.is_surjective():
1295
+ return False
1296
+ try:
1297
+ ray_index_map = self._ray_index_map()
1298
+ except ValueError:
1299
+ return False # Rays are not mapped onto rays or the origin.
1300
+ Sigma_p = self.codomain_fan()
1301
+ # Rays are already checked, the origin is trivial, start with 2-cones.
1302
+ for d in range(2, Sigma_p.dim() + 1):
1303
+ for sigma_p in Sigma_p(d):
1304
+ I_p = sigma_p.ambient_ray_indices()
1305
+ for sigma in self.primitive_preimage_cones(sigma_p):
1306
+ if sigma.dim() != d:
1307
+ return False # sigma -> sigma_p is not a bijection
1308
+ I = sigma.ambient_ray_indices()
1309
+ if I_p != tuple(sorted(ray_index_map[i] for i in I)):
1310
+ return False # sigma -> sigma_p is not a bijection
1311
+ return True
1312
+
1313
+ @cached_method
1314
+ def is_injective(self):
1315
+ r"""
1316
+ Check if ``self`` is injective.
1317
+
1318
+ OUTPUT: ``True`` if ``self`` is injective, ``False`` otherwise
1319
+
1320
+ Let `\phi: \Sigma \to \Sigma'` be a fan morphism such that the
1321
+ underlying lattice morphism `\phi: N \to N'` bijectively maps `N` to a
1322
+ *saturated* sublattice of `N'`. Let `\psi: \Sigma \to \Sigma'_0` be the
1323
+ restriction of `\phi` to the image. Then `\phi` is **injective** if the
1324
+ map between cones corresponding to `\psi` (injectively) maps each cone
1325
+ of `\Sigma` to a cone of the same dimension.
1326
+
1327
+ If a fan morphism `\phi: \Sigma \to \Sigma'` is injective, then the
1328
+ associated morphism between toric varieties `\tilde{\phi}: X_\Sigma \to
1329
+ X_{\Sigma'}` is injective.
1330
+
1331
+ .. SEEALSO:: :meth:`factor`.
1332
+
1333
+ EXAMPLES:
1334
+
1335
+ Consider the fan of the affine plane::
1336
+
1337
+ sage: A2 = toric_varieties.A(2).fan()
1338
+
1339
+ We will map several fans consisting of a single ray into the interior
1340
+ of the 2-cone::
1341
+
1342
+ sage: Sigma = Fan([Cone([(1,1)])])
1343
+ sage: m = identity_matrix(2)
1344
+ sage: FanMorphism(m, Sigma, A2).is_injective()
1345
+ False
1346
+
1347
+ This morphism was not injective since (in the toric varieties
1348
+ interpretation) the 1-dimensional orbit corresponding to the ray was
1349
+ mapped to the 0-dimensional orbit corresponding to the 2-cone. ::
1350
+
1351
+ sage: Sigma = Fan([Cone([(1,)])])
1352
+ sage: m = matrix(1, 2, [1,1])
1353
+ sage: FanMorphism(m, Sigma, A2).is_injective()
1354
+ True
1355
+
1356
+ While the fans in this example are close to the previous one, here the
1357
+ ray corresponds to a 0-dimensional orbit. ::
1358
+
1359
+ sage: Sigma = Fan([Cone([(1,)])])
1360
+ sage: m = matrix(1, 2, [2,2])
1361
+ sage: FanMorphism(m, Sigma, A2).is_injective()
1362
+ False
1363
+
1364
+ Here the problem is that ``m`` maps the domain lattice to a
1365
+ non-saturated sublattice of the codomain. The corresponding map of the
1366
+ toric varieties is a two-sheeted cover of its image.
1367
+
1368
+ We also embed the affine plane into the projective one::
1369
+
1370
+ sage: P2 = toric_varieties.P(2).fan() # needs palp
1371
+ sage: m = identity_matrix(2)
1372
+ sage: FanMorphism(m, A2, P2).is_injective() # needs palp
1373
+ True
1374
+ """
1375
+ if self.matrix().index_in_saturation() != 1:
1376
+ return False
1377
+ if self.image().dimension() < self.codomain().dimension():
1378
+ return prod(self.factor()[1:]).is_injective()
1379
+ # Now we know that underlying lattice morphism is bijective.
1380
+ Sigma = self.domain_fan()
1381
+ return all(self.image_cone(sigma).dim() == d
1382
+ for d in range(1, Sigma.dim() + 1) for sigma in Sigma(d))
1383
+
1384
+ @cached_method
1385
+ def is_surjective(self):
1386
+ r"""
1387
+ Check if ``self`` is surjective.
1388
+
1389
+ OUTPUT: ``True`` if ``self`` is surjective, ``False`` otherwise
1390
+
1391
+ A fan morphism `\phi: \Sigma \to \Sigma'` is **surjective** if the
1392
+ corresponding map between cones is surjective, i.e. for each cone
1393
+ `\sigma' \in \Sigma'` there is at least one preimage cone `\sigma \in
1394
+ \Sigma` such that the relative interior of `\sigma` is mapped to the
1395
+ relative interior of `\sigma'` and, in addition,
1396
+ `\phi_\RR: N_\RR \to N'_\RR` is surjective.
1397
+
1398
+ If a fan morphism `\phi: \Sigma \to \Sigma'` is surjective, then the
1399
+ associated morphism between toric varieties `\tilde{\phi}: X_\Sigma \to
1400
+ X_{\Sigma'}` is surjective.
1401
+
1402
+ .. SEEALSO:: :meth:`is_bundle`, :meth:`is_fibration`,
1403
+ :meth:`preimage_cones`, :meth:`is_complete`.
1404
+
1405
+ EXAMPLES:
1406
+
1407
+ We check that the blow up of the affine plane at the origin is
1408
+ surjective::
1409
+
1410
+ sage: A2 = toric_varieties.A(2).fan()
1411
+ sage: Bl = A2.subdivide([(1,1)])
1412
+ sage: m = identity_matrix(2)
1413
+ sage: FanMorphism(m, Bl, A2).is_surjective()
1414
+ True
1415
+
1416
+ It remains surjective if we throw away "south and north poles" of the
1417
+ exceptional divisor::
1418
+
1419
+ sage: FanMorphism(m, Fan(Bl.cones(1)), A2).is_surjective()
1420
+ True
1421
+
1422
+ But a single patch of the blow up does not cover the plane::
1423
+
1424
+ sage: F = Fan([Bl.generating_cone(0)])
1425
+ sage: FanMorphism(m, F, A2).is_surjective()
1426
+ False
1427
+
1428
+ TESTS:
1429
+
1430
+ We check that reviewer's example on :issue:`11200` works as expected::
1431
+
1432
+ sage: P1 = toric_varieties.P1()
1433
+ sage: A1 = toric_varieties.A1()
1434
+ sage: phi = FanMorphism(matrix([[1]]), A1.fan(), P1.fan())
1435
+ sage: phi.is_surjective()
1436
+ False
1437
+ """
1438
+ if isinstance(self.index(), InfinityElement):
1439
+ return False # Not surjective between vector spaces.
1440
+ for dcones in self.codomain_fan().cones():
1441
+ for sigma_p in dcones:
1442
+ if not self.preimage_cones(sigma_p):
1443
+ return False
1444
+ return True
1445
+
1446
+ @cached_method
1447
+ def is_dominant(self):
1448
+ r"""
1449
+ Return whether the fan morphism is dominant.
1450
+
1451
+ A fan morphism `\phi` is dominant if it is surjective as a map
1452
+ of vector spaces. That is, `\phi_\RR: N_\RR \to N'_\RR` is
1453
+ surjective.
1454
+
1455
+ If the domain fan is :meth:`complete
1456
+ <sage.geometry.fan.RationalPolyhedralFan.is_complete>`, then
1457
+ this implies that the fan morphism is :meth:`surjective
1458
+ <is_surjective>`.
1459
+
1460
+ If the fan morphism is dominant, then the associated morphism
1461
+ of toric varieties is dominant in the algebraic-geometric
1462
+ sense (that is, surjective onto a dense subset).
1463
+
1464
+ OUTPUT: boolean
1465
+
1466
+ EXAMPLES::
1467
+
1468
+ sage: P1 = toric_varieties.P1()
1469
+ sage: A1 = toric_varieties.A1()
1470
+ sage: phi = FanMorphism(matrix([[1]]), A1.fan(), P1.fan())
1471
+ sage: phi.is_dominant()
1472
+ True
1473
+ sage: phi.is_surjective()
1474
+ False
1475
+ """
1476
+ return self.matrix().rank() == self.codomain_fan().dim()
1477
+
1478
+ @cached_method
1479
+ def kernel_fan(self):
1480
+ r"""
1481
+ Return the subfan of the domain fan mapped into the origin.
1482
+
1483
+ OUTPUT: a :class:`fan <sage.geometry.fan.RationalPolyhedralFan>`
1484
+
1485
+ .. NOTE::
1486
+
1487
+ The lattice of the kernel fan is the :meth:`kernel` sublattice of
1488
+ ``self``.
1489
+
1490
+ .. SEEALSO:: :meth:`preimage_fan`.
1491
+
1492
+ EXAMPLES::
1493
+
1494
+ sage: fan = Fan(rays=[(1,0), (1,1), (0,1)], cones=[(0,1), (1,2)])
1495
+ sage: fm = FanMorphism(matrix(2, 1, [1,-1]), fan, ToricLattice(1))
1496
+ sage: fm.kernel_fan()
1497
+ Rational polyhedral fan in Sublattice <N(1, 1)>
1498
+ sage: _.rays()
1499
+ N(1, 1)
1500
+ in Sublattice <N(1, 1)>
1501
+ sage: fm.kernel_fan().cones()
1502
+ ((0-d cone of Rational polyhedral fan in Sublattice <N(1, 1)>,),
1503
+ (1-d cone of Rational polyhedral fan in Sublattice <N(1, 1)>,))
1504
+ """
1505
+ fan = self.preimage_fan(Cone([], lattice=self.codomain()))
1506
+ return Fan((cone.ambient_ray_indices() for cone in fan), fan.rays(),
1507
+ lattice=self.kernel(), check=False)
1508
+
1509
+ def preimage_cones(self, cone):
1510
+ r"""
1511
+ Return cones of the domain fan whose :meth:`image_cone` is ``cone``.
1512
+
1513
+ INPUT:
1514
+
1515
+ - ``cone`` -- a :class:`cone
1516
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` equivalent to a
1517
+ cone of the :meth:`codomain_fan` of ``self``.
1518
+
1519
+ OUTPUT:
1520
+
1521
+ - a :class:`tuple` of :class:`cones
1522
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` of the
1523
+ :meth:`domain_fan` of ``self``, sorted by dimension.
1524
+
1525
+ .. SEEALSO:: :meth:`preimage_fan`.
1526
+
1527
+ EXAMPLES::
1528
+
1529
+ sage: quadrant = Cone([(1,0), (0,1)])
1530
+ sage: quadrant = Fan([quadrant])
1531
+ sage: quadrant_bl = quadrant.subdivide([(1,1)])
1532
+ sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant)
1533
+ sage: fm.preimage_cones(Cone([(1,0)]))
1534
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,)
1535
+ sage: fm.preimage_cones(Cone([(1,0), (0,1)]))
1536
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,
1537
+ 2-d cone of Rational polyhedral fan in 2-d lattice N,
1538
+ 2-d cone of Rational polyhedral fan in 2-d lattice N)
1539
+
1540
+ TESTS:
1541
+
1542
+ We check that reviewer's example from :issue:`9972` is handled correctly::
1543
+
1544
+ sage: # needs palp
1545
+ sage: N1 = ToricLattice(1)
1546
+ sage: N2 = ToricLattice(2)
1547
+ sage: Hom21 = Hom(N2, N1)
1548
+ sage: pr = Hom21([N1.0,0])
1549
+ sage: P1xP1 = toric_varieties.P1xP1()
1550
+ sage: f = FanMorphism(pr, P1xP1.fan())
1551
+ sage: c = f.image_cone(Cone([(1,0), (0,1)])); c
1552
+ 1-d cone of Rational polyhedral fan in 1-d lattice N
1553
+ sage: f.preimage_cones(c)
1554
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,
1555
+ 2-d cone of Rational polyhedral fan in 2-d lattice N,
1556
+ 2-d cone of Rational polyhedral fan in 2-d lattice N)
1557
+ """
1558
+ cone = self._codomain_fan.embed(cone)
1559
+ if cone not in self._preimage_cones:
1560
+ # "Images of preimages" must sit in all of these generating cones
1561
+ CSGI = cone.star_generator_indices()
1562
+ RISGIS = self._RISGIS()
1563
+ domain_fan = self._domain_fan
1564
+ possible_rays = frozenset(i for i in range(domain_fan.nrays())
1565
+ if RISGIS[i].issuperset(CSGI))
1566
+ preimage_cones = []
1567
+ for dcones in domain_fan.cones():
1568
+ for dcone in dcones:
1569
+ if (possible_rays.issuperset(dcone.ambient_ray_indices())
1570
+ and self.image_cone(dcone) == cone):
1571
+ preimage_cones.append(dcone)
1572
+ self._preimage_cones[cone] = tuple(preimage_cones)
1573
+ return self._preimage_cones[cone]
1574
+
1575
+ def preimage_fan(self, cone):
1576
+ r"""
1577
+ Return the subfan of the domain fan mapped into ``cone``.
1578
+
1579
+ INPUT:
1580
+
1581
+ - ``cone`` -- a :class:`cone
1582
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` equivalent to a
1583
+ cone of the :meth:`codomain_fan` of ``self``
1584
+
1585
+ OUTPUT: a :class:`fan <sage.geometry.fan.RationalPolyhedralFan>`
1586
+
1587
+ .. NOTE::
1588
+
1589
+ The preimage fan of ``cone`` consists of all cones of the
1590
+ :meth:`domain_fan` which are mapped into ``cone``, including those
1591
+ that are mapped into its boundary. So this fan is not necessarily
1592
+ generated by :meth:`preimage_cones` of ``cone``.
1593
+
1594
+ .. SEEALSO:: :meth:`kernel_fan`, :meth:`preimage_cones`.
1595
+
1596
+ EXAMPLES::
1597
+
1598
+ sage: quadrant_cone = Cone([(1,0), (0,1)])
1599
+ sage: quadrant_fan = Fan([quadrant_cone])
1600
+ sage: quadrant_bl = quadrant_fan.subdivide([(1,1)])
1601
+ sage: fm = FanMorphism(identity_matrix(2),
1602
+ ....: quadrant_bl, quadrant_fan)
1603
+ sage: fm.preimage_fan(Cone([(1,0)])).cones()
1604
+ ((0-d cone of Rational polyhedral fan in 2-d lattice N,),
1605
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,))
1606
+ sage: fm.preimage_fan(quadrant_cone).ngenerating_cones()
1607
+ 2
1608
+ sage: len(fm.preimage_cones(quadrant_cone))
1609
+ 3
1610
+ """
1611
+ cone = self._codomain_fan.embed(cone)
1612
+ if cone not in self._preimage_fans:
1613
+ # Find all cones mapped into the given one, including its boundary.
1614
+ domain_fan = self._domain_fan
1615
+ cones = []
1616
+ for dcones in reversed(domain_fan.cones()):
1617
+ for dcone in dcones:
1618
+ if (not any(dcone.is_face_of(other) for other in cones) and
1619
+ self.image_cone(dcone).is_face_of(cone)):
1620
+ cones.append(dcone)
1621
+ # Now form the fan from these cones, keeping the ray order.
1622
+ ray_indices = set(cones[0].ambient_ray_indices())
1623
+ for c in cones[1:]:
1624
+ ray_indices.update(c.ambient_ray_indices())
1625
+ self._preimage_fans[cone] = Fan(cones,
1626
+ domain_fan.rays(sorted(ray_indices)), check=False)
1627
+ return self._preimage_fans[cone]
1628
+
1629
+ def primitive_preimage_cones(self, cone):
1630
+ r"""
1631
+ Return the primitive cones of the domain fan corresponding to ``cone``.
1632
+
1633
+ INPUT:
1634
+
1635
+ - ``cone`` -- a :class:`cone
1636
+ <sage.geometry.cone.ConvexRationalPolyhedralCone>` equivalent to a
1637
+ cone of the :meth:`codomain_fan` of ``self``
1638
+
1639
+ OUTPUT: a :class:`cone <sage.geometry.cone.ConvexRationalPolyhedralCone>`
1640
+
1641
+ Let `\phi: \Sigma \to \Sigma'` be a fan morphism, let `\sigma \in
1642
+ \Sigma`, and let `\sigma' = \phi(\sigma)`. Then `\sigma` is a
1643
+ **primitive cone corresponding to** `\sigma'` if there is no proper
1644
+ face `\tau` of `\sigma` such that `\phi(\tau) = \sigma'`.
1645
+
1646
+ Primitive cones play an important role for fibration morphisms.
1647
+
1648
+ .. SEEALSO:: :meth:`is_fibration`, :meth:`preimage_cones`,
1649
+ :meth:`preimage_fan`.
1650
+
1651
+ EXAMPLES:
1652
+
1653
+ Consider a projection of a del Pezzo surface onto the projective line::
1654
+
1655
+ sage: Sigma = toric_varieties.dP6().fan() # needs palp
1656
+ sage: Sigma.rays() # needs palp
1657
+ N( 0, 1),
1658
+ N(-1, 0),
1659
+ N(-1, -1),
1660
+ N( 0, -1),
1661
+ N( 1, 0),
1662
+ N( 1, 1)
1663
+ in 2-d lattice N
1664
+ sage: Sigma_p = toric_varieties.P1().fan()
1665
+ sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p) # needs palp
1666
+
1667
+ Under this map, one pair of rays is mapped to the origin, one in the
1668
+ positive direction, and one in the negative one. Also three
1669
+ 2-dimensional cones are mapped in the positive direction and three in
1670
+ the negative one, so there are 5 preimage cones corresponding to either
1671
+ of the rays of the codomain fan ``Sigma_p``::
1672
+
1673
+ sage: len(phi.preimage_cones(Cone([(1,)]))) # needs palp
1674
+ 5
1675
+
1676
+ Yet only rays are primitive::
1677
+
1678
+ sage: phi.primitive_preimage_cones(Cone([(1,)])) # needs palp
1679
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,
1680
+ 1-d cone of Rational polyhedral fan in 2-d lattice N)
1681
+
1682
+ Since all primitive cones are mapped onto their images bijectively, we
1683
+ get a fibration::
1684
+
1685
+ sage: phi.is_fibration() # needs palp
1686
+ True
1687
+
1688
+ But since there are several primitive cones corresponding to the same
1689
+ cone of the codomain fan, this map is not a bundle, even though its
1690
+ index is 1::
1691
+
1692
+ sage: phi.is_bundle() # needs palp
1693
+ False
1694
+ sage: phi.index() # needs palp
1695
+ 1
1696
+ """
1697
+ sigma_p = self._codomain_fan.embed(cone) # Necessary if used as a key
1698
+ if sigma_p not in self._primitive_preimage_cones:
1699
+ primitive_cones = []
1700
+ for sigma in self.preimage_cones(sigma_p): # Sorted by dimension
1701
+ if not any(tau.is_face_of(sigma) for tau in primitive_cones):
1702
+ primitive_cones.append(sigma)
1703
+ self._primitive_preimage_cones[sigma_p] = tuple(primitive_cones)
1704
+ return self._primitive_preimage_cones[sigma_p]
1705
+
1706
+ def factor(self):
1707
+ r"""
1708
+ Factor ``self`` into injective * birational * surjective morphisms.
1709
+
1710
+ OUTPUT:
1711
+
1712
+ - a triple of :class:`FanMorphism` `(\phi_i, \phi_b, \phi_s)`, such that
1713
+ `\phi_s` is surjective, `\phi_b` is birational, `\phi_i` is injective,
1714
+ and ``self`` is equal to `\phi_i \circ \phi_b \circ \phi_s`.
1715
+
1716
+ Intermediate fans live in the saturation of the image of ``self``
1717
+ as a map between lattices and are the image of the :meth:`domain_fan`
1718
+ and the restriction of the :meth:`codomain_fan`, i.e. if ``self`` maps
1719
+ `\Sigma \to \Sigma'`, then we have factorization into
1720
+
1721
+ .. MATH::
1722
+
1723
+ \Sigma
1724
+ \twoheadrightarrow
1725
+ \Sigma_s
1726
+ \to
1727
+ \Sigma_i
1728
+ \hookrightarrow
1729
+ \Sigma.
1730
+
1731
+ .. NOTE::
1732
+
1733
+ * `\Sigma_s` is the finest fan with the smallest support that is
1734
+ compatible with ``self``: any fan morphism from `\Sigma` given by
1735
+ the same map of lattices as ``self`` factors through `\Sigma_s`.
1736
+
1737
+ * `\Sigma_i` is the coarsest fan of the largest support that is
1738
+ compatible with ``self``: any fan morphism into `\Sigma'` given by
1739
+ the same map of lattices as ``self`` factors though `\Sigma_i`.
1740
+
1741
+ EXAMPLES:
1742
+
1743
+ We map an affine plane into a projective 3-space in such a way, that it
1744
+ becomes "a double cover of a chart of the blow up of one of the
1745
+ coordinate planes"::
1746
+
1747
+ sage: A2 = toric_varieties.A2()
1748
+ sage: P3 = toric_varieties.P(3) # needs palp
1749
+ sage: m = matrix([(2,0,0), (1,1,0)])
1750
+ sage: phi = A2.hom(m, P3) # needs palp
1751
+ sage: phi.as_polynomial_map() # needs palp
1752
+ Scheme morphism:
1753
+ From: 2-d affine toric variety
1754
+ To: 3-d CPR-Fano toric variety covered by 4 affine patches
1755
+ Defn: Defined on coordinates by sending [x : y] to
1756
+ [x^2*y : y : 1 : 1]
1757
+
1758
+ Now we will work with the underlying fan morphism::
1759
+
1760
+ sage: # needs palp
1761
+ sage: phi = phi.fan_morphism(); phi
1762
+ Fan morphism defined by the matrix
1763
+ [2 0 0]
1764
+ [1 1 0]
1765
+ Domain fan: Rational polyhedral fan in 2-d lattice N
1766
+ Codomain fan: Rational polyhedral fan in 3-d lattice N
1767
+ sage: phi.is_surjective(), phi.is_birational(), phi.is_injective()
1768
+ (False, False, False)
1769
+ sage: phi_i, phi_b, phi_s = phi.factor()
1770
+ sage: phi_s.is_surjective(), phi_b.is_birational(), phi_i.is_injective()
1771
+ (True, True, True)
1772
+ sage: prod(phi.factor()) == phi
1773
+ True
1774
+
1775
+ Double cover (surjective)::
1776
+
1777
+ sage: A2.fan().rays()
1778
+ N(1, 0),
1779
+ N(0, 1)
1780
+ in 2-d lattice N
1781
+ sage: phi_s # needs palp
1782
+ Fan morphism defined by the matrix
1783
+ [2 0]
1784
+ [1 1]
1785
+ Domain fan: Rational polyhedral fan in 2-d lattice N
1786
+ Codomain fan: Rational polyhedral fan in Sublattice <N(1, 0, 0), N(0, 1, 0)>
1787
+ sage: phi_s.codomain_fan().rays() # needs palp
1788
+ N(1, 0, 0),
1789
+ N(1, 1, 0)
1790
+ in Sublattice <N(1, 0, 0), N(0, 1, 0)>
1791
+
1792
+ Blowup chart (birational)::
1793
+
1794
+ sage: phi_b # needs palp
1795
+ Fan morphism defined by the matrix
1796
+ [1 0]
1797
+ [0 1]
1798
+ Domain fan: Rational polyhedral fan in Sublattice <N(1, 0, 0), N(0, 1, 0)>
1799
+ Codomain fan: Rational polyhedral fan in Sublattice <N(1, 0, 0), N(0, 1, 0)>
1800
+ sage: phi_b.codomain_fan().rays() # needs palp
1801
+ N(-1, -1, 0),
1802
+ N( 0, 1, 0),
1803
+ N( 1, 0, 0)
1804
+ in Sublattice <N(1, 0, 0), N(0, 1, 0)>
1805
+
1806
+ Coordinate plane inclusion (injective)::
1807
+
1808
+ sage: phi_i # needs palp
1809
+ Fan morphism defined by the matrix
1810
+ [1 0 0]
1811
+ [0 1 0]
1812
+ Domain fan: Rational polyhedral fan in Sublattice <N(1, 0, 0), N(0, 1, 0)>
1813
+ Codomain fan: Rational polyhedral fan in 3-d lattice N
1814
+ sage: phi.codomain_fan().rays() # needs palp
1815
+ N( 1, 0, 0),
1816
+ N( 0, 1, 0),
1817
+ N( 0, 0, 1),
1818
+ N(-1, -1, -1)
1819
+ in 3-d lattice N
1820
+
1821
+ TESTS::
1822
+
1823
+ sage: phi_s.matrix() * phi_b.matrix() * phi_i.matrix() == m # needs palp
1824
+ True
1825
+
1826
+ sage: # needs palp
1827
+ sage: phi.domain_fan() is phi_s.domain_fan()
1828
+ True
1829
+ sage: phi_s.codomain_fan() is phi_b.domain_fan()
1830
+ True
1831
+ sage: phi_b.codomain_fan() is phi_i.domain_fan()
1832
+ True
1833
+ sage: phi_i.codomain_fan() is phi.codomain_fan()
1834
+ True
1835
+
1836
+ sage: trivialfan2 = Fan([], [], lattice=ToricLattice(2))
1837
+ sage: trivialfan3 = Fan([], [], lattice=ToricLattice(3))
1838
+ sage: f = FanMorphism(zero_matrix(2,3), trivialfan2, trivialfan3)
1839
+ sage: [phi.matrix().dimensions() for phi in f.factor()]
1840
+ [(0, 3), (0, 0), (2, 0)]
1841
+ """
1842
+ L = self.image().saturation()
1843
+ d = L.dimension()
1844
+ m = self.matrix()
1845
+ m = matrix(ZZ, m.nrows(), d, (L.coordinates(c) for c in m.rows()))
1846
+ phi_s = FanMorphism(m, self.domain_fan(), L, check=False)
1847
+ Sigma_prime = self.codomain_fan()
1848
+ L_cone = Cone(sum(([g, -g] for g in L.gens()), []), lattice=L)
1849
+ Sigma_i = Fan(cones=(L_cone.intersection(cone) for cone in Sigma_prime),
1850
+ lattice=L, discard_faces=True, check=False)
1851
+ phi_b = FanMorphism(identity_matrix(d), phi_s.codomain_fan(), Sigma_i,
1852
+ check=False)
1853
+ phi_i = FanMorphism(L.basis_matrix(), Sigma_i, Sigma_prime, check=False)
1854
+ return (phi_i, phi_b, phi_s)
1855
+
1856
+ def relative_star_generators(self, domain_cone):
1857
+ """
1858
+ Return the relative star generators of ``domain_cone``.
1859
+
1860
+ INPUT:
1861
+
1862
+ - ``domain_cone`` -- a cone of the :meth:`domain_fan` of ``self``
1863
+
1864
+ OUTPUT:
1865
+
1866
+ - :meth:`~RationalPolyhedralFan.star_generators` of ``domain_cone``
1867
+ viewed as a cone of :meth:`preimage_fan` of :meth:`image_cone` of
1868
+ ``domain_cone``.
1869
+
1870
+ EXAMPLES::
1871
+
1872
+ sage: A2 = toric_varieties.A(2).fan()
1873
+ sage: Bl = A2.subdivide([(1,1)])
1874
+ sage: f = FanMorphism(identity_matrix(2), Bl, A2)
1875
+ sage: for c1 in Bl(1):
1876
+ ....: print(f.relative_star_generators(c1))
1877
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,)
1878
+ (1-d cone of Rational polyhedral fan in 2-d lattice N,)
1879
+ (2-d cone of Rational polyhedral fan in 2-d lattice N,
1880
+ 2-d cone of Rational polyhedral fan in 2-d lattice N)
1881
+ """
1882
+ base_cone = self.image_cone(domain_cone)
1883
+ preimg_fan = self.preimage_fan(base_cone)
1884
+ return preimg_fan.embed(domain_cone).star_generators()