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