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