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