This module was designed as a part of the framework for toric varieties (variety, fano_variety). While the emphasis is on complete full-dimensional fans, arbitrary fans are supported. Work with distinct lattices. The default lattice is ToricLattice \(N\) of the appropriate dimension. The only case when you must specify lattice explicitly is creation of a 0-dimensional fan, where dimension of the ambient space cannot be guessed.
A rational polyhedral fan is a finite collection of strictly convex rational polyhedral cones, such that the intersection of any two cones of the fan is a face of each of them and each face of each cone is also a cone of the fan.
AUTHORS:
EXAMPLES:
Use Fan() to construct fans “explicitly”:
sage: fan = Fan(cones=[(0,1), (1,2)],
... rays=[(1,0), (0,1), (-1,0)])
sage: fan
Rational polyhedral fan in 2-d lattice N
In addition to giving such lists of cones and rays you can also create cones first using Cone() and then combine them into a fan. See the documentation of Fan() for details.
In 2 dimensions there is a unique maximal fan determined by rays, and you can use Fan2d() to construct it:
sage: fan2d = Fan2d(rays=[(1,0), (0,1), (-1,0)])
sage: fan2d.is_equivalent(fan)
True
But keep in mind that in higher dimensions the cone data is essential and cannot be omitted. Instead of building a fan from scratch, for this tutorial we will use an easy way to get two fans assosiated to lattice polytopes: FaceFan() and NormalFan():
sage: fan1 = FaceFan(lattice_polytope.octahedron(3))
sage: fan2 = NormalFan(lattice_polytope.octahedron(3))
Given such “automatic” fans, you may wonder what are their rays and cones:
sage: fan1.rays()
N( 1, 0, 0),
N( 0, 1, 0),
N( 0, 0, 1),
N(-1, 0, 0),
N( 0, -1, 0),
N( 0, 0, -1)
in 3-d lattice N
sage: fan1.generating_cones()
(3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N)
The last output is not very illuminating. Let’s try to improve it:
sage: for cone in fan1: print cone.rays()
N(1, 0, 0),
N(0, 1, 0),
N(0, 0, -1)
in 3-d lattice N
N( 0, 1, 0),
N(-1, 0, 0),
N( 0, 0, -1)
in 3-d lattice N
N(1, 0, 0),
N(0, -1, 0),
N(0, 0, -1)
in 3-d lattice N
N(-1, 0, 0),
N( 0, -1, 0),
N( 0, 0, -1)
in 3-d lattice N
N(1, 0, 0),
N(0, 1, 0),
N(0, 0, 1)
in 3-d lattice N
N( 0, 1, 0),
N( 0, 0, 1),
N(-1, 0, 0)
in 3-d lattice N
N(1, 0, 0),
N(0, 0, 1),
N(0, -1, 0)
in 3-d lattice N
N( 0, 0, 1),
N(-1, 0, 0),
N( 0, -1, 0)
in 3-d lattice N
You can also do
sage: for cone in fan1: print cone.ambient_ray_indices()
(0, 1, 5)
(1, 3, 5)
(0, 4, 5)
(3, 4, 5)
(0, 1, 2)
(1, 2, 3)
(0, 2, 4)
(2, 3, 4)
to see indices of rays of the fan corresponding to each cone.
While the above cycles were over “cones in fan”, it is obvious that we did not get ALL the cones: every face of every cone in a fan must also be in the fan, but all of the above cones were of dimension three. The reason for this behaviour is that in many cases it is enough to work with generating cones of the fan, i.e. cones which are not faces of bigger cones. When you do need to work with lower dimensional cones, you can easily get access to them using cones():
sage: [cone.ambient_ray_indices() for cone in fan1.cones(2)]
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (0, 4),
(2, 4), (3, 4), (1, 5), (3, 5), (4, 5), (0, 5)]
In fact, you don’t have to type .cones:
sage: [cone.ambient_ray_indices() for cone in fan1(2)]
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (0, 4),
(2, 4), (3, 4), (1, 5), (3, 5), (4, 5), (0, 5)]
You may also need to know the inclusion relations between all of the cones of the fan. In this case check out cone_lattice():
sage: L = fan1.cone_lattice()
sage: L
Finite poset containing 28 elements
sage: L.bottom()
0-d cone of Rational polyhedral fan in 3-d lattice N
sage: L.top()
Rational polyhedral fan in 3-d lattice N
sage: cone = L.level_sets()[2][0]
sage: cone
2-d cone of Rational polyhedral fan in 3-d lattice N
sage: L.hasse_diagram().neighbors(cone)
[1-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N,
1-d cone of Rational polyhedral fan in 3-d lattice N,
3-d cone of Rational polyhedral fan in 3-d lattice N]
You can check how “good” a fan is:
sage: fan1.is_complete()
True
sage: fan1.is_simplicial()
True
sage: fan1.is_smooth()
True
The face fan of the octahedron is really good! Time to remember that we have also constructed its normal fan:
sage: fan2.is_complete()
True
sage: fan2.is_simplicial()
False
sage: fan2.is_smooth()
False
This one does have some “problems,” but we can fix them:
sage: fan3 = fan2.make_simplicial()
sage: fan3.is_simplicial()
True
sage: fan3.is_smooth()
False
Note that we had to save the result of make_simplicial() in a new fan. Fans in Sage are immutable, so any operation that does change them constructs a new fan.
We can also make fan3 smooth, but it will take a bit more work:
sage: cube = lattice_polytope.octahedron(3).polar()
sage: sk = cube.skeleton_points(2)
sage: rays = [cube.point(p) for p in sk]
sage: fan4 = fan3.subdivide(new_rays=rays)
sage: fan4.is_smooth()
True
Let’s see how “different” are fan2 and fan4:
sage: fan2.ngenerating_cones()
6
sage: fan2.nrays()
8
sage: fan4.ngenerating_cones()
48
sage: fan4.nrays()
26
Smoothness does not come for free!
Please take a look at the rest of the available functions below and their complete descriptions. If you need any features that are missing, feel free to suggest them. (Or implement them on your own and submit a patch to Sage for inclusion!)
Bases: sage.geometry.cone.ConvexRationalPolyhedralCone
Construct a cone belonging to a fan.
Warning
This class does not check that the input defines a valid cone of a fan. You must not construct objects of this class directly.
In addition to all of the properties of “regular” cones, such cones know their relation to the fan.
INPUT:
OUTPUT:
EXAMPLES:
The intended way to get objects of this class is the following:
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
sage: cone = P1xP1.generating_cone(0)
sage: cone
2-d cone of Rational polyhedral fan in 2-d lattice N
sage: cone.ambient_ray_indices()
(0, 3)
sage: cone.star_generator_indices()
(0,)
Return indices of generating cones of the “ambient fan” containing self.
OUTPUT:
EXAMPLES:
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
sage: cone = P1xP1.generating_cone(0)
sage: cone.star_generator_indices()
(0,)
TESTS:
A mistake in this function used to cause the problem reported in Trac 9782. We check that now everything is working smoothly:
sage: f = Fan([(0, 2, 4),
... (0, 4, 5),
... (0, 3, 5),
... (0, 1, 3),
... (0, 1, 2),
... (2, 4, 6),
... (4, 5, 6),
... (3, 5, 6),
... (1, 3, 6),
... (1, 2, 6)],
... [(-1, 0, 0),
... (0, -1, 0),
... (0, 0, -1),
... (0, 0, 1),
... (0, 1, 2),
... (0, 1, 3),
... (1, 0, 4)])
sage: f.is_complete()
True
sage: X = ToricVariety(f)
sage: X.fan().is_complete()
True
Return indices of generating cones of the “ambient fan” containing self.
OUTPUT:
EXAMPLES:
sage: P1xP1 = FaceFan(lattice_polytope.octahedron(2))
sage: cone = P1xP1.generating_cone(0)
sage: cone.star_generators()
(2-d cone of Rational polyhedral fan in 2-d lattice N,)
Construct the face fan of the given rational polytope.
INPUT:
OUTPUT:
See also NormalFan().
EXAMPLES:
Let’s construct the fan corresponding to the product of two projective lines:
sage: diamond = lattice_polytope.octahedron(2)
sage: P1xP1 = FaceFan(diamond)
sage: P1xP1.rays()
N( 1, 0),
N( 0, 1),
N(-1, 0),
N( 0, -1)
in 2-d lattice N
sage: for cone in P1xP1: print cone.rays()
N(1, 0),
N(0, -1)
in 2-d lattice N
N(-1, 0),
N( 0, -1)
in 2-d lattice N
N(1, 0),
N(0, 1)
in 2-d lattice N
N( 0, 1),
N(-1, 0)
in 2-d lattice N
sage: cuboctahed = polytopes.cuboctahedron()
sage: FaceFan(cuboctahed)
Rational polyhedral fan in 3-d lattice N
TESTS:
sage: cuboctahed.is_lattice_polytope(), cuboctahed.dilation(2).is_lattice_polytope()
(False, True)
sage: fan1 = FaceFan(cuboctahed)
sage: fan2 = FaceFan(cuboctahed.dilation(2).lattice_polytope())
sage: fan1.is_equivalent(fan2)
True
sage: ray = Polyhedron(vertices=[(-1,-1)], rays=[(1,1)])
sage: FaceFan(ray)
Traceback (most recent call last):
...
ValueError: face fans are defined only for
polytopes containing the origin as an interior point!
sage: interval_in_QQ2 = Polyhedron([ (0,-1), (0,+1) ])
sage: FaceFan(interval_in_QQ2).generating_cones()
(1-d cone of Rational polyhedral fan in 2-d lattice N,
1-d cone of Rational polyhedral fan in 2-d lattice N)
sage: FaceFan(Polyhedron([(-1,0), (1,0), (0,1)])) # origin on facet
Traceback (most recent call last):
...
ValueError: face fans are defined only for
polytopes containing the origin as an interior point!
Construct a rational polyhedral fan.
Note
Approximate time to construct a fan consisting of \(n\) cones is \(n^2/5\) seconds. That is half an hour for 100 cones. This time can be significantly reduced in the future, but it is still likely to be \(\sim n^2\) (with, say, \(/500\) instead of \(/5\)). If you know that your input does form a valid fan, use check=False option to skip consistency checks.
INPUT:
OUTPUT:
See also
In 2 dimensions you can cyclically order the rays. Hence the rays determine a unique maximal fan without having to specify the cones, and you can use Fan2d() to construct this fan from just the rays.
EXAMPLES:
Let’s construct a fan corresponding to the projective plane in several ways:
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(0,1), (-1,-1)])
sage: cone3 = Cone([(-1,-1), (1,0)])
sage: P2 = Fan([cone1, cone2, cone2])
Traceback (most recent call last):
...
ValueError: you have provided 3 cones, but only 2 of them are maximal!
Use discard_faces=True if you indeed need to construct a fan from
these cones.
Oops! There was a typo and cone2 was listed twice as a generating cone of the fan. If it was intentional (e.g. the list of cones was generated automatically and it is possible that it contains repetitions or faces of other cones), use discard_faces=True option:
sage: P2 = Fan([cone1, cone2, cone2], discard_faces=True)
sage: P2.ngenerating_cones()
2
However, in this case it was definitely a typo, since the fan of \(\mathbb{P}^2\) has 3 maximal cones:
sage: P2 = Fan([cone1, cone2, cone3])
sage: P2.ngenerating_cones()
3
Looks better. An alternative way is
sage: rays = [(1,0), (0,1), (-1,-1)]
sage: cones = [(0,1), (1,2), (2,0)]
sage: P2a = Fan(cones, rays)
sage: P2a.ngenerating_cones()
3
sage: P2 == P2a
False
That may seem wrong, but it is not:
sage: P2.is_equivalent(P2a)
True
See is_equivalent() for details.
Yet another way to construct this fan is
sage: P2b = Fan(cones, rays, check=False)
sage: P2b.ngenerating_cones()
3
sage: P2a == P2b
True
If you try the above examples, you are likely to notice the difference in speed, so when you are sure that everything is correct, it is a good idea to use check=False option. On the other hand, it is usually NOT a good idea to use normalize=False option:
sage: P2c = Fan(cones, rays, check=False, normalize=False)
Traceback (most recent call last):
...
AttributeError: 'tuple' object has no attribute 'parent'
Yet another way is to use functions FaceFan() and NormalFan() to construct fans from lattice polytopes.
We have not yet used lattice argument, since if was determined automatically:
sage: P2.lattice()
2-d lattice N
sage: P2b.lattice()
2-d lattice N
However, it is necessary to specify it explicitly if you want to construct a fan without rays or cones:
sage: Fan([], [])
Traceback (most recent call last):
...
ValueError: you must specify the lattice
when you construct a fan without rays and cones!
sage: F = Fan([], [], lattice=ToricLattice(2, "L"))
sage: F
Rational polyhedral fan in 2-d lattice L
sage: F.lattice_dim()
2
sage: F.dim()
0
Construct the maximal 2-d fan with given rays.
In two dimensions we can uniquely construct a fan from just rays, just by cyclically ordering the rays and constructing as many cones as possible. This is why we implement a special constructor for this case.
INPUT:
EXAMPLES:
sage: Fan2d([(0,1), (1,0)])
Rational polyhedral fan in 2-d lattice N
sage: Fan2d([], lattice=ToricLattice(2, 'myN'))
Rational polyhedral fan in 2-d lattice myN
The ray order is as specified, even if it is not the cyclic order:
sage: fan1 = Fan2d([(0,1), (1,0)])
sage: fan1.rays()
N(0, 1),
N(1, 0)
in 2-d lattice N
sage: fan2 = Fan2d([(1,0), (0,1)])
sage: fan2.rays()
N(1, 0),
N(0, 1)
in 2-d lattice N
sage: fan1 == fan2, fan1.is_equivalent(fan2)
(False, True)
sage: fan = Fan2d([(1,1), (-1,-1), (1,-1), (-1,1)])
sage: [ cone.ambient_ray_indices() for cone in fan ]
[(2, 1), (1, 3), (3, 0), (0, 2)]
sage: fan.is_complete()
True
TESTS:
sage: Fan2d([(0,1), (0,1)]).generating_cones()
(1-d cone of Rational polyhedral fan in 2-d lattice N,)
sage: Fan2d([(1,1), (-1,-1)]).generating_cones()
(1-d cone of Rational polyhedral fan in 2-d lattice N,
1-d cone of Rational polyhedral fan in 2-d lattice N)
sage: Fan2d([])
Traceback (most recent call last):
...
ValueError: you must specify a 2-dimensional lattice
when you construct a fan without rays.
sage: Fan2d([(3,4)]).rays()
N(3, 4)
in 2-d lattice N
sage: Fan2d([(0,1,0)])
Traceback (most recent call last):
...
ValueError: the lattice must be 2-dimensional.
sage: Fan2d([(0,1), (1,0), (0,0)])
Traceback (most recent call last):
...
ValueError: only non-zero vectors define rays
sage: Fan2d([(0, -2), (2, -10), (1, -3), (2, -9), (2, -12), (1, 1),
... (2, 1), (1, -5), (0, -6), (1, -7), (0, 1), (2, -4),
... (2, -2), (1, -9), (1, -8), (2, -6), (0, -1), (0, -3),
... (2, -11), (2, -8), (1, 0), (0, -5), (1, -4), (2, 0),
... (1, -6), (2, -7), (2, -5), (-1, -3), (1, -1), (1, -2),
... (0, -4), (2, -3), (2, -1)]).cone_lattice()
Finite poset containing 44 elements
sage: Fan2d([(1,1)]).is_complete()
False
sage: Fan2d([(1,1), (-1,-1)]).is_complete()
False
sage: Fan2d([(1,0), (0,1)]).is_complete()
False
Construct the normal fan of the given rational polytope.
INPUT:
OUTPUT:
See also FaceFan().
EXAMPLES:
Let’s construct the fan corresponding to the product of two projective lines:
sage: square = lattice_polytope.octahedron(2).polar()
sage: P1xP1 = NormalFan(square)
sage: P1xP1.rays()
N( 1, 0),
N( 0, 1),
N(-1, 0),
N( 0, -1)
in 2-d lattice N
sage: for cone in P1xP1: print cone.rays()
N(1, 0),
N(0, -1)
in 2-d lattice N
N(-1, 0),
N( 0, -1)
in 2-d lattice N
N(1, 0),
N(0, 1)
in 2-d lattice N
N( 0, 1),
N(-1, 0)
in 2-d lattice N
sage: cuboctahed = polytopes.cuboctahedron()
sage: NormalFan(cuboctahed)
Rational polyhedral fan in 3-d lattice N
TESTS:
sage: cuboctahed.is_lattice_polytope(), cuboctahed.dilation(2).is_lattice_polytope()
(False, True)
sage: fan1 = NormalFan(cuboctahed)
sage: fan2 = NormalFan(cuboctahed.dilation(2).lattice_polytope())
sage: fan1.is_equivalent(fan2)
True
Bases: sage.geometry.cone.IntegralRayCollection, _abcoll.Callable, _abcoll.Container
Create a rational polyhedral fan.
Warning
This class does not perform any checks of correctness of input nor does it convert input into the standard representation. Use Fan() to construct fans from “raw data” or FaceFan() and NormalFan() to get fans associated to polytopes.
Fans are immutable, but they cache most of the returned values.
INPUT:
OUTPUT:
Return the Gale transform of self.
OUTPUT:
A matrix over \(ZZ\).
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.Gale_transform()
[ 1 0 1 0 -2]
[ 0 1 0 1 -2]
sage: _.base_ring()
Integer Ring
Return the Stanley-Reisner ideal.
INPUT:
OUTPUT:
EXAMPLES:
sage: fan = Fan([[0,1,3],[3,4],[2,0],[1,2,4]], [(-3, -2, 1), (0, 0, 1), (3, -2, 1), (-1, -1, 1), (1, -1, 1)])
sage: fan.Stanley_Reisner_ideal( PolynomialRing(QQ,5,'A, B, C, D, E') )
Ideal (A*E, C*D, A*B*C, B*D*E) of Multivariate Polynomial Ring in A, B, C, D, E over Rational Field
Return the Cartesian product of self with other.
INPUT:
OUTPUT:
EXAMPLES:
sage: K = ToricLattice(1, 'K')
sage: fan1 = Fan([[0],[1]],[(1,),(-1,)], lattice=K)
sage: L = ToricLattice(2, 'L')
sage: fan2 = Fan(rays=[(1,0),(0,1),(-1,-1)],
... cones=[[0,1],[1,2],[2,0]], lattice=L)
sage: fan1.cartesian_product(fan2)
Rational polyhedral fan in 3-d lattice K+L
sage: _.ngenerating_cones()
6
Return the chain complex of the fan.
To a \(d\)-dimensional fan \(\Sigma\), one can canonically associate a chain complex \(K^\bullet\)
where the leftmost non-zero entry is in degree \(0\) and the rightmost entry in degree \(d\). See [Klyachko], eq. (3.2). This complex computes the homology of \(|\Sigma|\subset N_\RR\) with arbitrary support,
For a complete fan, this is just the non-compactly supported homology of \(\RR^d\). In this case, \(H_0(K)=\ZZ\) and \(0\) in all non-zero degrees.
For a complete fan, there is an extended chain complex
where we take the first \(\ZZ\) term to be in degree -1. This complex is an exact sequence, that is, all homology groups vanish.
The orientation of each cone is chosen as in oriented_boundary().
INPUT:
OUTPUT:
The complex associated to the fan as a ChainComplex. Raises a ValueError if the extended complex is requested for a non-complete fan.
EXAMPLES:
sage: fan = toric_varieties.P(3).fan()
sage: K_normal = fan.complex(); K_normal
Chain complex with at most 4 nonzero terms over Integer Ring
sage: K_normal.homology()
{0: Z, 1: 0, 2: 0, 3: 0}
sage: K_extended = fan.complex(extended=True); K_extended
Chain complex with at most 5 nonzero terms over Integer Ring
sage: K_extended.homology()
{0: 0, 1: 0, 2: 0, 3: 0, -1: 0}
Homology computations are much faster over \(\QQ\) if you don’t care about the torsion coefficients:
sage: toric_varieties.P2_123().fan().complex(extended=True, base_ring=QQ)
Chain complex with at most 4 nonzero terms over Rational Field
sage: _.homology()
{0: Vector space of dimension 0 over Rational Field,
1: Vector space of dimension 0 over Rational Field,
2: Vector space of dimension 0 over Rational Field,
-1: Vector space of dimension 0 over Rational Field}
The extended complex is only defined for complete fans:
sage: fan = Fan([ Cone([(1,0)]) ])
sage: fan.is_complete()
False
sage: fan.complex(extended=True)
Traceback (most recent call last):
...
ValueError: The extended complex is only defined for complete fans!
The definition of the complex does not refer to the ambient space of the fan, so it does not distinguish a fan from the same fan embedded in a subspace:
sage: K1 = Fan([Cone([(-1,)]), Cone([(1,)])]).complex()
sage: K2 = Fan([Cone([(-1,0,0)]), Cone([(1,0,0)])]).complex()
sage: K1 == K2
True
Things get more complicated for non-complete fans:
sage: fan = Fan([Cone([(1,1,1)]),
... Cone([(1,0,0),(0,1,0)]),
... Cone([(-1,0,0),(0,-1,0),(0,0,-1)])])
sage: fan.complex().homology()
{0: 0, 1: 0, 2: Z x Z, 3: 0}
sage: fan = Fan([Cone([(1,0,0),(0,1,0)]),
... Cone([(-1,0,0),(0,-1,0),(0,0,-1)])])
sage: fan.complex().homology()
{0: 0, 1: 0, 2: Z, 3: 0}
sage: fan = Fan([Cone([(-1,0,0),(0,-1,0),(0,0,-1)])])
sage: fan.complex().homology()
{0: 0, 1: 0, 2: 0, 3: 0}
REFERENCES:
[Klyachko] | A. A. Klyachko, Equivariant Bundles on Toral Varieties. Mathematics of the USSR - Izvestiya 35 (1990), 337-375. |
Return the smallest cone of self containing all given points.
INPUT:
OUTPUT:
Note
We think of the origin as of the smallest cone containing no rays at all. If there is no ray in self that contains all rays, a ValueError exception will be raised.
EXAMPLES:
sage: cone1 = Cone([(0,-1), (1,0)])
sage: cone2 = Cone([(1,0), (0,1)])
sage: f = Fan([cone1, cone2])
sage: f.rays()
N(0, 1),
N(0, -1),
N(1, 0)
in 2-d lattice N
sage: f.cone_containing(0) # ray index
1-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing(0, 1) # ray indices
Traceback (most recent call last):
...
ValueError: there is no cone in
Rational polyhedral fan in 2-d lattice N
containing all of the given rays! Ray indices: [0, 1]
sage: f.cone_containing(0, 2) # ray indices
2-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing((0,1)) # point
1-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing([(0,1)]) # point
1-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing((1,1))
2-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing((1,1), (1,0))
2-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing()
0-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing((0,0))
0-d cone of Rational polyhedral fan in 2-d lattice N
sage: f.cone_containing((-1,1))
Traceback (most recent call last):
...
ValueError: there is no cone in
Rational polyhedral fan in 2-d lattice N
containing all of the given points! Points: [N(-1, 1)]
TESTS:
sage: fan = Fan(cones=[(0,1,2,3), (0,1,4)],
... rays=[(1,1,1), (1,-1,1), (1,-1,-1), (1,1,-1), (0,0,1)])
sage: fan.cone_containing(0).rays()
N(1, 1, 1)
in 3-d lattice N
Return the cone lattice of self.
This lattice will have the origin as the bottom (we do not include the empty set as a cone) and the fan itself as the top.
OUTPUT:
See also cones().
EXAMPLES:
Cone lattices can be computed for arbitrary fans:
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan.rays()
N( 0, 1),
N( 1, 0),
N(-1, 0)
in 2-d lattice N
sage: for cone in fan: print cone.ambient_ray_indices()
(0, 1)
(2,)
sage: L = fan.cone_lattice()
sage: L
Finite poset containing 6 elements
These 6 elements are the origin, three rays, one two-dimensional cone, and the fan itself. Since we do add the fan itself as the largest face, you should be a little bit careful with this last element:
sage: for face in L: print face.ambient_ray_indices()
Traceback (most recent call last):
...
AttributeError: 'RationalPolyhedralFan'
object has no attribute 'ambient_ray_indices'
sage: L.top()
Rational polyhedral fan in 2-d lattice N
For example, you can do
sage: for l in L.level_sets()[:-1]:
... print [f.ambient_ray_indices() for f in l]
[()]
[(0,), (1,), (2,)]
[(0, 1)]
If the fan is complete, its cone lattice is atomic and coatomic and can (and will!) be computed in a much more efficient way, but the interface is exactly the same:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: L = fan.cone_lattice()
sage: for l in L.level_sets()[:-1]:
... print [f.ambient_ray_indices() for f in l]
[()]
[(0,), (1,), (2,), (3,)]
[(0, 1), (1, 2), (0, 3), (2, 3)]
Let’s also consider the cone lattice of a fan generated by a single cone:
sage: fan = Fan([cone1])
sage: L = fan.cone_lattice()
sage: L
Finite poset containing 5 elements
Here these 5 elements correspond to the origin, two rays, one generating cone of dimension two, and the whole fan. While this single cone “is” the whole fan, it is consistent and convenient to distinguish them in the cone lattice.
Return the specified cones of self.
INPUT:
Note
You can specify at most one input parameter.
OUTPUT:
EXAMPLES:
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan(dim=0)
(0-d cone of Rational polyhedral fan in 2-d lattice N,)
sage: fan(codim=2)
(0-d cone of Rational polyhedral fan in 2-d lattice N,)
sage: for cone in fan.cones(1): cone.ray(0)
N(0, 1)
N(1, 0)
N(-1, 0)
sage: fan.cones(2)
(2-d cone of Rational polyhedral fan in 2-d lattice N,)
You cannot specify both dimension and codimension, even if they “agree”:
sage: fan(dim=1, codim=1)
Traceback (most recent call last):
...
ValueError: dimension and codimension
cannot be specified together!
But it is OK to ask for cones of too high or low (co)dimension:
sage: fan(-1)
()
sage: fan(3)
()
sage: fan(codim=4)
()
Check if a given cone is equivalent to a cone of the fan.
INPUT:
OUTPUT:
Note
Recall that a fan is a (finite) collection of cones. A cone is contained in a fan if it is equivalent to one of the cones of the fan. In particular, it is possible that all rays of the cone are in the fan, but the cone itself is not.
If you want to know whether a point is in the support of the fan, you should use support_contains().
EXAMPLES:
We first construct a simple fan:
sage: cone1 = Cone([(0,-1), (1,0)])
sage: cone2 = Cone([(1,0), (0,1)])
sage: f = Fan([cone1, cone2])
Now we check if some cones are in this fan. First, we make sure that the order of rays of the input cone does not matter (check=False option ensures that rays of these cones will be listed exactly as they are given):
sage: f.contains(Cone([(1,0), (0,1)], check=False))
True
sage: f.contains(Cone([(0,1), (1,0)], check=False))
True
Now we check that a non-generating cone is in our fan:
sage: f.contains(Cone([(1,0)]))
True
sage: Cone([(1,0)]) in f # equivalent to the previous command
True
Finally, we test some cones which are not in this fan:
sage: f.contains(Cone([(1,1)]))
False
sage: f.contains(Cone([(1,0), (-0,1)]))
True
A point is not a cone:
sage: n = f.lattice()(1,1); n
N(1, 1)
sage: f.contains(n)
False
Return the cone equivalent to the given one, but sitting in self.
You may need to use this method before calling methods of cone that depend on the ambient structure, such as ambient_ray_indices() or facet_of(). The cone returned by this method will have self as ambient. If cone does not represent a valid cone of self, ValueError exception is raised.
Note
This method is very quick if self is already the ambient structure of cone, so you can use without extra checks and performance hit even if cone is likely to sit in self but in principle may not.
INPUT:
OUTPUT:
EXAMPLES:
Let’s take a 3-d fan generated by a cone on 4 rays:
sage: f = Fan([Cone([(1,0,1), (0,1,1), (-1,0,1), (0,-1,1)])])
Then any ray generates a 1-d cone of this fan, but if you construct such a cone directly, it will not “sit” inside the fan:
sage: ray = Cone([(0,-1,1)])
sage: ray
1-d cone in 3-d lattice N
sage: ray.ambient_ray_indices()
(0,)
sage: ray.adjacent()
()
sage: ray.ambient()
1-d cone in 3-d lattice N
If we want to operate with this ray as a part of the fan, we need to embed it first:
sage: e_ray = f.embed(ray)
sage: e_ray
1-d cone of Rational polyhedral fan in 3-d lattice N
sage: e_ray.rays()
N(0, -1, 1)
in 3-d lattice N
sage: e_ray is ray
False
sage: e_ray.is_equivalent(ray)
True
sage: e_ray.ambient_ray_indices()
(3,)
sage: e_ray.adjacent()
(1-d cone of Rational polyhedral fan in 3-d lattice N,
1-d cone of Rational polyhedral fan in 3-d lattice N)
sage: e_ray.ambient()
Rational polyhedral fan in 3-d lattice N
Not every cone can be embedded into a fixed fan:
sage: f.embed(Cone([(0,0,1)]))
Traceback (most recent call last):
...
ValueError: 1-d cone in 3-d lattice N does not belong
to Rational polyhedral fan in 3-d lattice N!
sage: f.embed(Cone([(1,0,1), (-1,0,1)]))
Traceback (most recent call last):
...
ValueError: 2-d cone in 3-d lattice N does not belong
to Rational polyhedral fan in 3-d lattice N!
Return the n-th generating cone of self.
INPUT:
OUTPUT:
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.generating_cone(0)
2-d cone of Rational polyhedral fan in 2-d lattice N
Return generating cones of self.
OUTPUT:
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.generating_cones()
(2-d cone of Rational polyhedral fan in 2-d lattice N,
2-d cone of Rational polyhedral fan in 2-d lattice N,
2-d cone of Rational polyhedral fan in 2-d lattice N,
2-d cone of Rational polyhedral fan in 2-d lattice N)
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan.generating_cones()
(2-d cone of Rational polyhedral fan in 2-d lattice N,
1-d cone of Rational polyhedral fan in 2-d lattice N)
Check if self is complete.
A rational polyhedral fan is complete if its cones fill the whole space.
OUTPUT:
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.is_complete()
True
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan.is_complete()
False
Check if self is “mathematically” the same as other.
INPUT:
OUTPUT:
There are three different equivalences between fans \(F_1\) and \(F_2\) in the same lattice:
Note that virtual_rays() are included into consideration for all of the above equivalences.
EXAMPLES:
sage: fan1 = Fan(cones=[(0,1), (1,2)],
... rays=[(1,0), (0,1), (-1,-1)],
... check=False)
sage: fan2 = Fan(cones=[(2,1), (0,2)],
... rays=[(1,0), (-1,-1), (0,1)],
... check=False)
sage: fan3 = Fan(cones=[(0,1), (1,2)],
... rays=[(1,0), (0,1), (-1,1)],
... check=False)
sage: fan1 == fan2
False
sage: fan1.is_equivalent(fan2)
True
sage: fan1 == fan3
False
sage: fan1.is_equivalent(fan3)
False
Check if self is in the same \(GL(n, \ZZ)\)-orbit as other.
There are three different equivalences between fans \(F_1\) and \(F_2\) in the same lattice:
Note that virtual_rays() are included into consideration for all of the above equivalences.
INPUT:
OUTPUT:
See also
If you want to obtain the actual fan isomorphism, use isomorphism().
EXAMPLES:
Here we pick an \(SL(2,\ZZ)\) matrix m and then verify that the image fan is isomorphic:
sage: rays = ((1, 1), (0, 1), (-1, -1), (1, 0))
sage: cones = [(0,1), (1,2), (2,3), (3,0)]
sage: fan1 = Fan(cones, rays)
sage: m = matrix([[-2,3],[1,-1]])
sage: fan2 = Fan(cones, [vector(r)*m for r in rays])
sage: fan1.is_isomorphic(fan2)
True
sage: fan1.is_equivalent(fan2)
False
sage: fan1 == fan2
False
These fans are “mirrors” of each other:
sage: fan1 = Fan(cones=[(0,1), (1,2)],
... rays=[(1,0), (0,1), (-1,-1)],
... check=False)
sage: fan2 = Fan(cones=[(0,1), (1,2)],
... rays=[(1,0), (0,-1), (-1,1)],
... check=False)
sage: fan1 == fan2
False
sage: fan1.is_equivalent(fan2)
False
sage: fan1.is_isomorphic(fan2)
True
sage: fan1.is_isomorphic(fan1)
True
Check if self is simplicial.
A rational polyhedral fan is simplicial if all of its cones are, i.e. primitive vectors along generating rays of every cone form a part of a rational basis of the ambient space.
OUTPUT:
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.is_simplicial()
True
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan.is_simplicial()
True
In fact, any fan in a two-dimensional ambient space is simplicial. This is no longer the case in dimension three:
sage: fan = NormalFan(lattice_polytope.octahedron(3))
sage: fan.is_simplicial()
False
sage: fan.generating_cone(0).nrays()
4
Check if self is smooth.
A rational polyhedral fan is smooth if all of its cones are, i.e. primitive vectors along generating rays of every cone form a part of an integral basis of the ambient space. In this case the corresponding toric variety is smooth.
A fan in an \(n\)-dimensional lattice is smooth up to codimension \(c\) if all cones of codimension greater than or equal to \(c\) are smooth, i.e. if all cones of dimension less than or equal to \(n-c\) are smooth. In this case the singular set of the corresponding toric variety is of dimension less than \(c\).
INPUT:
OUTPUT:
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.is_smooth()
True
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan.is_smooth()
True
sage: fan = NormalFan(lattice_polytope.octahedron(2))
sage: fan.is_smooth()
False
sage: fan.is_smooth(codim=1)
True
sage: fan.generating_cone(0).rays()
N(-1, 1),
N(-1, -1)
in 2-d lattice N
sage: fan.generating_cone(0).rays().matrix().det()
2
Return a fan isomorphism from self to other.
INPUT:
OUTPUT:
A fan isomorphism. If no such isomorphism exists, a FanNotIsomorphicError is raised.
EXAMPLES:
sage: rays = ((1, 1), (0, 1), (-1, -1), (3, 1))
sage: cones = [(0,1), (1,2), (2,3), (3,0)]
sage: fan1 = Fan(cones, rays)
sage: m = matrix([[-2,3],[1,-1]])
sage: fan2 = Fan(cones, [vector(r)*m for r in rays])
sage: fan1.isomorphism(fan2)
Fan morphism defined by the matrix
[-2 3]
[ 1 -1]
Domain fan: Rational polyhedral fan in 2-d lattice N
Codomain fan: Rational polyhedral fan in 2-d lattice N
sage: fan2.isomorphism(fan1)
Fan morphism defined by the matrix
[1 3]
[1 2]
Domain fan: Rational polyhedral fan in 2-d lattice N
Codomain fan: Rational polyhedral fan in 2-d lattice N
sage: fan1.isomorphism(toric_varieties.P2().fan())
Traceback (most recent call last):
...
FanNotIsomorphicError
Return the ideal generated by linear relations
INPUT:
OUTPUT:
Returns the ideal, in the given ring, generated by the linear relations of the rays. In toric geometry, this corresponds to rational equivalence of divisors.
EXAMPLES:
sage: fan = Fan([[0,1,3],[3,4],[2,0],[1,2,4]], [(-3, -2, 1), (0, 0, 1), (3, -2, 1), (-1, -1, 1), (1, -1, 1)])
sage: fan.linear_equivalence_ideal( PolynomialRing(QQ,5,'A, B, C, D, E') )
Ideal (-3*A + 3*C - D + E, -2*A - 2*C - D - E, A + B + C + D + E) of Multivariate Polynomial Ring in A, B, C, D, E over Rational Field
Construct a simplicial fan subdividing self.
It is a synonym for subdivide() with make_simplicial=True option.
INPUT:
OUTPUT:
EXAMPLES:
sage: fan = NormalFan(lattice_polytope.octahedron(3))
sage: fan.is_simplicial()
False
sage: fan.ngenerating_cones()
6
sage: new_fan = fan.make_simplicial()
sage: new_fan.is_simplicial()
True
sage: new_fan.ngenerating_cones()
12
Return the number of generating cones of self.
OUTPUT:
EXAMPLES:
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan.ngenerating_cones()
4
sage: cone1 = Cone([(1,0), (0,1)])
sage: cone2 = Cone([(-1,0)])
sage: fan = Fan([cone1, cone2])
sage: fan.ngenerating_cones()
2
Return the facets bounding cone with their induced orientation.
INPUT:
OUTPUT:
The boundary cones of cone as a formal linear combination of cones with coefficients \(\pm 1\). Each summand is a facet of cone and the coefficient indicates whether their (chosen) orientation argrees or disagrees with the “outward normal first” boundary orientation. Note that the orientation of any individial cone is arbitrary. This method once and for all picks orientations for all cones and then computes the boundaries relative to that chosen orientation.
If cone is the fan itself, the generating cones with their orientation relative to the ambient space are returned.
See complex() for the associated chain complex. If you do not require the orientation, use cone.facets() instead.
EXAMPLES:
sage: fan = toric_varieties.P(3).fan()
sage: cone = fan(2)[0]
sage: bdry = fan.oriented_boundary(cone); bdry
1-d cone of Rational polyhedral fan in 3-d lattice N
- 1-d cone of Rational polyhedral fan in 3-d lattice N
sage: bdry[0]
(1, 1-d cone of Rational polyhedral fan in 3-d lattice N)
sage: bdry[1]
(-1, 1-d cone of Rational polyhedral fan in 3-d lattice N)
sage: fan.oriented_boundary(bdry[0][1])
-0-d cone of Rational polyhedral fan in 3-d lattice N
sage: fan.oriented_boundary(bdry[1][1])
-0-d cone of Rational polyhedral fan in 3-d lattice N
If you pass the fan itself, this method returns the orientation of the generating cones which is determined by the order of the rays in cone.ray_basis()
sage: fan.oriented_boundary(fan)
-3-d cone of Rational polyhedral fan in 3-d lattice N
+ 3-d cone of Rational polyhedral fan in 3-d lattice N
- 3-d cone of Rational polyhedral fan in 3-d lattice N
+ 3-d cone of Rational polyhedral fan in 3-d lattice N
sage: [cone.rays().basis().matrix().det()
... for cone in fan.generating_cones()]
[-1, 1, -1, 1]
A non-full dimensional fan:
sage: cone = Cone([(4,5)])
sage: fan = Fan([cone])
sage: fan.oriented_boundary(cone)
0-d cone of Rational polyhedral fan in 2-d lattice N
sage: fan.oriented_boundary(fan)
1-d cone of Rational polyhedral fan in 2-d lattice N
TESTS:
sage: fan = toric_varieties.P2().fan()
sage: trivial_cone = fan(0)[0]
sage: fan.oriented_boundary(trivial_cone)
0
Plot self.
INPUT:
OUTPUT:
EXAMPLES:
sage: fan = toric_varieties.dP6().fan()
sage: fan.plot()
Return the primitive collections.
OUTPUT:
Returns the subsets \(\{i_1,\dots,i_k\} \subset \{ 1,\dots,n\}\) such that
Note
By replacing the multiindices \(\{i_1,\dots,i_k\}\) of each primitive collection with the monomials \(x_{i_1}\cdots x_{i_k}\) one generates the Stanley-Reisner ideal in \(\ZZ[x_1,\dots]\).
REFERENCES:
V.V. Batyrev, On the classification of smooth projective toric varieties, Tohoku Math.J. 43 (1991), 569-585
EXAMPLES:
sage: fan = Fan([[0,1,3],[3,4],[2,0],[1,2,4]], [(-3, -2, 1), (0, 0, 1), (3, -2, 1), (-1, -1, 1), (1, -1, 1)])
sage: fan.primitive_collections()
[frozenset([0, 4]), frozenset([2, 3]), frozenset([0, 1, 2]), frozenset([1, 3, 4])]
Construct a new fan subdividing self.
INPUT:
OUTPUT:
Currently the “default” algorithm corresponds to iterative stellar subdivision for each ray in new_rays.
EXAMPLES:
sage: fan = NormalFan(lattice_polytope.octahedron(3))
sage: fan.is_simplicial()
False
sage: fan.ngenerating_cones()
6
sage: fan.nrays()
8
sage: new_fan = fan.subdivide(new_rays=[(1,0,0)])
sage: new_fan.is_simplicial()
False
sage: new_fan.ngenerating_cones()
9
sage: new_fan.nrays()
9
TESTS:
We check that Trac #11902 is fixed:
sage: fan = toric_varieties.P2().fan()
sage: fan.subdivide(new_rays=[(0,0)])
Traceback (most recent call last):
...
ValueError: the origin cannot be used for fan subdivision!
Check if a point is contained in the support of the fan.
The support of a fan is the union of all cones of the fan. If you want to know whether the fan contains a given cone, you should use contains() instead.
INPUT:
OUTPUT:
TESTS:
sage: cone1 = Cone([(0,-1), (1,0)])
sage: cone2 = Cone([(1,0), (0,1)])
sage: f = Fan([cone1, cone2])
We check if some points are in this fan:
sage: f.support_contains(f.lattice()(1,0))
True
sage: f.support_contains(cone1) # a cone is not a point of the lattice
False
sage: f.support_contains((1,0))
True
sage: f.support_contains(1,1)
True
sage: f.support_contains((-1,0))
False
sage: f.support_contains(f.lattice().dual()(1,0)) #random output (warning)
False
sage: f.support_contains(f.lattice().dual()(1,0))
False
sage: f.support_contains(1)
False
sage: f.support_contains(0) # 0 converts to the origin in the lattice
True
sage: f.support_contains(1/2, sqrt(3))
True
sage: f.support_contains(-1/2, sqrt(3))
False
Return the graph of 1- and 2-cones.
OUTPUT:
An edge-colored graph. The vertices correspond to the 1-cones (i.e. rays) of the fan. Two vertices are joined by an edge iff the rays span a 2-cone of the fan. The edges are colored by pairs of integers that classify the 2-cones up to \(GL(2,\ZZ)\) transformation, see classify_cone_2d().
EXAMPLES:
sage: dP8 = toric_varieties.dP8()
sage: g = dP8.fan().vertex_graph()
sage: g
Graph on 4 vertices
sage: set(dP8.fan(1)) == set(g.vertices())
True
sage: g.edge_labels() # all edge labels the same since every cone is smooth
[(1, 0), (1, 0), (1, 0), (1, 0)]
sage: g = toric_varieties.Cube_deformation(10).fan().vertex_graph()
sage: g.automorphism_group().order()
48
sage: g.automorphism_group(edge_labels=True).order()
4
Return (some of the) virtual rays of self.
Let \(N\) be the \(D\)-dimensional lattice() of a \(d\)-dimensional fan \(\Sigma\) in \(N_\RR\). Then the corresponding toric variety is of the form \(X \times (\CC^*)^{D-d}\). The actual rays() of \(\Sigma\) give a canonical choice of homogeneous coordinates on \(X\). This function returns an arbitrary but fixed choice of virtual rays corresponding to a (non-canonical) choice of homogeneous coordinates on the torus factor. Combinatorially primitive integral generators of virtual rays span the \(D-d\) dimensions of \(N_\QQ\) “missed” by the actual rays. (In general addition of virtual rays is not sufficient to span \(N\) over \(\ZZ\).)
..note:
You may use a particular choice of virtual rays by passing optional
argument ``virtual_rays`` to the :func:`Fan` constructor.
INPUT:
OUTPUT:
EXAMPLES:
sage: f = Fan([Cone([(1,0,1,0), (0,1,1,0)])])
sage: f.virtual_rays()
N(0, 0, 0, 1),
N(0, 0, 1, 0)
in 4-d lattice N
sage: f.rays()
N(1, 0, 1, 0),
N(0, 1, 1, 0)
in 4-d lattice N
sage: f.virtual_rays([0])
N(0, 0, 0, 1)
in 4-d lattice N
You can also give virtual ray indices directly, without packing them into a list:
sage: f.virtual_rays(0)
N(0, 0, 0, 1)
in 4-d lattice N
TESTS:
sage: N = ToricLattice(4)
sage: for i in range(10):
... c = Cone([N.random_element() for j in range(i/2)], lattice=N)
... f = Fan([c])
... assert matrix(f.rays() + f.virtual_rays()).rank() == 4
... assert f.dim() + len(f.virtual_rays()) == 4
Return the cones of the given list which are not faces of each other.
INPUT:
OUTPUT:
EXAMPLES:
Consider all cones of a fan:
sage: Sigma = toric_varieties.P2().fan()
sage: cones = flatten(Sigma.cones())
sage: len(cones)
7
Most of them are not necessary to generate this fan:
sage: from sage.geometry.fan import discard_faces
sage: len(discard_faces(cones))
3
sage: Sigma.ngenerating_cones()
3
Check if x is a Fan.
INPUT:
OUTPUT:
EXAMPLES:
sage: from sage.geometry.fan import is_Fan
sage: is_Fan(1)
False
sage: fan = FaceFan(lattice_polytope.octahedron(2))
sage: fan
Rational polyhedral fan in 2-d lattice N
sage: is_Fan(fan)
True