preimage_cones implemented

• ## doc/en/reference/geometry.rst

# HG changeset patch
# User Andrey Novoseltsev <novoselt@gmail.com>
# Date 1289429460 25200
# Node ID 98a87b6883296d2bea0c123b81b42c17dcce5809
# Parent  8d193571aaec39f6c2f924baeba0d5b94a834ddf
Trac 9972: Add support for fan morphisms.

diff -r 8d193571aaec -r 98a87b688329 doc/en/reference/geometry.rst
 a .. toctree:: :maxdepth: 2 sage/geometry/toric_lattice sage/geometry/cone sage/geometry/fan sage/geometry/fan_morphism sage/geometry/toric_plotter sage/rings/polynomial/groebner_fan sage/geometry/lattice_polytope sage/geometry/polyhedra sage/geometry/fan sage/geometry/toric_lattice sage/geometry/toric_plotter
• ## sage/geometry/all.py

diff -r 8d193571aaec -r 98a87b688329 sage/geometry/all.py
 a from fan import Fan, FaceFan, NormalFan from fan_morphism import FanMorphism from polytope import polymake from polyhedra import Polyhedron, polytopes
• ## new file sage/geometry/fan_morphism.py

diff -r 8d193571aaec -r 98a87b688329 sage/geometry/fan_morphism.py
 - r""" Morphisms between toric lattices compatible with fans This module is a part of the framework for toric varieties (:mod:~sage.schemes.generic.toric_variety, :mod:~sage.schemes.generic.fano_toric_variety). Its main purpose is to provide support for working with lattice morphisms compatible with fans via :class:FanMorphism class. AUTHORS: - Andrey Novoseltsev (2010-10-17): initial version. EXAMPLES: Let's consider the face and normal fans of the "diamond" and the projection to the x-axis:: sage: diamond = lattice_polytope.octahedron(2) sage: face = FaceFan(diamond) sage: normal = NormalFan(diamond) sage: N = face.lattice() sage: H = End(N) sage: phi = H([N.0, 0]) sage: phi Free module morphism defined by the matrix [1 0] [0 0] Domain: 2-d lattice N Codomain: 2-d lattice N sage: FanMorphism(phi, normal, face) Traceback (most recent call last): ... ValueError: the image of generating cone #1 of the domain fan is not contained in a single cone of the codomain fan! Some of the cones of the normal fan fail to be mapped to a single cone of the face fan. We can rectify the situation in the following way:: sage: fm = FanMorphism(phi, normal, face, subdivide=True) sage: fm Fan morphism defined by the matrix [1 0] [0 0] Domain fan: Rational polyhedral fan in 2-d lattice N Codomain fan: Rational polyhedral fan in 2-d lattice N sage: fm.domain_fan().ray_matrix() [-1  1 -1  1  0  0] [ 1  1 -1 -1 -1  1] sage: normal.ray_matrix() [-1  1 -1  1] [ 1  1 -1 -1] As you see, it was necessary to insert two new rays (to prevent "upper" and "lower" cones of the normal fan from being mapped to the whole x-axis). """ #***************************************************************************** #       Copyright (C) 2010 Andrey Novoseltsev #       Copyright (C) 2010 William Stein # #  Distributed under the terms of the GNU General Public License (GPL) # #                  http://www.gnu.org/licenses/ #***************************************************************************** import operator from sage.categories.all import Hom from sage.geometry.cone import Cone from sage.geometry.fan import Fan, is_Fan from sage.matrix.all import is_Matrix from sage.misc.all import latex, walltime from sage.modules.free_module_morphism import (FreeModuleMorphism, is_FreeModuleMorphism) class FanMorphism(FreeModuleMorphism): r""" Create a fan morphism. Let \Sigma_1 and \Sigma_2 be two fans in lattices N_1 and N_2 respectively. Let \phi be a morphism (i.e. a linear map) from N_1 to N_2. We say that \phi is *compatible* with \Sigma_1 and \Sigma_2 if every cone \sigma_1\in\Sigma_1 is mapped by \phi into a single cone \sigma_2\in\Sigma_2, i.e. \phi(\sigma_1)\subset\sigma_2 (\sigma_2 may be different for different \sigma_1). By a **fan morphism** we understand a morphism between two lattices compatible with specified fans in these lattices. Such morphisms behave in exactly the same way as "regular" morphisms between lattices, but: * fan morphisms have a special constructor allowing some automatic adjustments to the initial fans (see below); * fan morphisms are aware of the associated fans and they can be accessed via :meth:codomain_fan and :meth:domain_fan; * fan morphisms can efficiently compute :meth:image_cone of a given cone of the domain fan and :meth:preimage_cones of a given cone of the codomain fan. INPUT: - morphism -- either a morphism between domain and codomain, or an integral matrix defining such a morphism; - domain_fan -- a :class:fan  in the domain; - codomain -- (default: None) either a codomain lattice or a fan in the codomain. If the codomain fan is not given, the image fan (fan generated by images of generating cones) of domain_fan will be used, if possible; - subdivide -- (default: False) if True and domain_fan is not compatible with the codomain fan because it is too coarse, it will be automatically refined to become compatible (the minimal refinement is canonical, so there are no choices involved); - check -- (default: True) if False, given fans and morphism will be assumed to be compatible. Be careful when using this option, since wrong assumptions can lead to wrong and hard-to-detect errors. On the other hand, this option may save you some time; - verbose -- (default: False) if True, some information may be printed during construction of the fan morphism. OUTPUT: - a fan morphism. EXAMPLES: Here we consider the face and normal fans of the "diamond" and the projection to the x-axis:: sage: diamond = lattice_polytope.octahedron(2) sage: face = FaceFan(diamond) sage: normal = NormalFan(diamond) sage: N = face.lattice() sage: H = End(N) sage: phi = H([N.0, 0]) sage: phi Free module morphism defined by the matrix [1 0] [0 0] Domain: 2-d lattice N Codomain: 2-d lattice N sage: fm = FanMorphism(phi, face, normal) sage: fm.domain_fan() is face True Note, that since phi is compatible with these fans, the returned fan is exactly the same object as the initial domain_fan. :: sage: FanMorphism(phi, normal, face) Traceback (most recent call last): ... ValueError: the image of generating cone #1 of the domain fan is not contained in a single cone of the codomain fan! sage: fm = FanMorphism(phi, normal, face, subdivide=True) sage: fm.domain_fan() is normal False sage: fm.domain_fan().ngenerating_cones() 6 We had to subdivide two of the four cones of the normal fan, since they were mapped by phi into non-strictly convex cones. It is possible to omit the codomain fan, in which case the image fan will be used instead of it:: sage: fm = FanMorphism(phi, face) sage: fm.codomain_fan() Rational polyhedral fan in 2-d lattice N sage: fm.codomain_fan().ray_matrix() [ 1 -1] [ 0  0] Now we demonstrate a more subtle example. We take the first quadrant as our domain fan. Then we divide the first quadrant into three cones, throw away the middle one and take the other two as our codomain fan. These fans are incompatible with the identity lattice morphism since the image of the domain fan is out of the support of the codomain fan:: sage: N = ToricLattice(2) sage: phi = End(N).identity() sage: F1 = Fan(cones=[(0,1)], rays=[(1,0), (0,1)]) sage: F2 = Fan(cones=[(0,1), (2,3)], ...            rays=[(1,0), (2,1), (1,2), (0,1)]) sage: FanMorphism(phi, F1, F2) Traceback (most recent call last): ... ValueError: the image of generating cone #0 of the domain fan is not contained in a single cone of the codomain fan! sage: FanMorphism(phi, F1, F2, subdivide=True) Traceback (most recent call last): ... ValueError: morphism defined by [1 0] [0 1] does not map Rational polyhedral fan in 2-d lattice N into the support of Rational polyhedral fan in 2-d lattice N! The problem was detected and handled correctly (i.e. an exception was raised). However, the used algorithm requires extra checks for this situation after constructing a potential subdivision and this can take significant time. You can save about half the time using check=False option, if you know in advance that it is possible to make fans compatible with the morphism by subdividing the domain fan. Of course, if your assumption was incorrect, the result will be wrong and you will get a fan which *does* map into the support of the codomain fan, but is **not** a subdivision of the domain fan. You can test it on the example above:: sage: fm = FanMorphism(phi, F1, F2, subdivide=True, ...                    check=False, verbose=True) Placing ray images... Computing chambers... Subdividing cone 1 of 1... sage: fm.domain_fan().is_equivalent(F2) True """ def __init__(self, morphism, domain_fan, codomain=None, subdivide=False, check=True, verbose=False): r""" Create a fan morphism. See :class:FanMorphism for documentation. TESTS:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: fm Fan morphism defined by the matrix [1 0] [0 1] Domain fan: Rational polyhedral fan in 2-d lattice N Codomain fan: Rational polyhedral fan in 2-d lattice N sage: TestSuite(fm).run(skip="_test_category") """ assert is_Fan(domain_fan) if is_Fan(codomain): codomain, codomain_fan = codomain.lattice(), codomain else: codomain_fan = None if is_FreeModuleMorphism(morphism): parent = morphism.parent() A = morphism.matrix() elif is_Matrix(morphism): A = morphism if codomain is None: raise ValueError("codomain (fan) must be given explicitly if " "morphism is given by a matrix!") parent = Hom(domain_fan.lattice(), codomain) else: raise TypeError("morphism must be either a FreeModuleMorphism " "or a matrix!\nGot: %s" % morphism) super(FanMorphism, self).__init__(parent, A) self._domain_fan = domain_fan self._image_cone = dict() self._preimage_cones = dict() if codomain_fan is None: self._construct_codomain_fan() else: self._codomain_fan = codomain_fan if subdivide: self._subdivide_domain_fan(check, verbose) elif check: self._validate() def _RISGIS(self): r""" Return Ray Images Star Generator Indices Sets. OUTPUT: - a :class:tuple of :class:frozensets of integers, the i-th set is the set of indices of star generators for the minimal cone of the :meth:codomain_fan containing the image of the i-th ray of the :meth:domain_fan. TESTS:: sage: diamond = lattice_polytope.octahedron(2) sage: face = FaceFan(diamond) sage: normal = NormalFan(diamond) sage: N = face.lattice() sage: fm = FanMorphism(identity_matrix(2), ...           normal, face, subdivide=True) sage: fm._RISGIS() (frozenset([3]), frozenset([2]), frozenset([1]), frozenset([0]), frozenset([1, 3]), frozenset([0, 1]), frozenset([0, 2]), frozenset([2, 3])) """ if "_RISGIS_" not in self.__dict__: try: cones = [self._codomain_fan.cone_containing(self(ray)) for ray in self._domain_fan.rays()] except ValueError: self._support_error() self._RISGIS_ = tuple(frozenset(cone.star_generator_indices()) for cone in cones) return self._RISGIS_ def _chambers(self): r""" Return chambers in the domain corresponding to the codomain fan. This function is used during automatic refinement of the domain fans, see :meth:_subdivide_domain_fan. OUTPUT: - a :class:tuple (chambers, cone_to_chamber), where - chambers is a :class:list of :class:cones  in the domain of self; - cone_to_chamber is a :class:list of integers, if its i-th element is j, then the j-th element of chambers is the inverse image of the i-th generating cone of the codomain fan. TESTS:: sage: F = NormalFan(lattice_polytope.octahedron(2)) sage: N = F.lattice() sage: H = End(N) sage: phi = H([N.0, 0]) sage: fm = FanMorphism(phi, F, F, subdivide=True) sage: fm._chambers() ([2-d cone in 2-d lattice N, 1-d cone in 2-d lattice N, 2-d cone in 2-d lattice N], [0, 1, 2, 1]) """ kernel_rays = [] for ray in self.kernel().basis(): kernel_rays.append(ray) kernel_rays.append(-ray) image_rays = [] for ray in self.image().basis(): image_rays.append(ray) image_rays.append(-ray) image = Cone(image_rays) chambers = [] cone_to_chamber = [] for cone in self._codomain_fan: chamber = Cone([self.lift(ray) for ray in cone.intersection(image)] + kernel_rays, lattice=self.domain()) cone_to_chamber.append(len(chambers)) for i, old_chamber in enumerate(chambers): if old_chamber.is_equivalent(chamber): cone_to_chamber[-1] = i break if cone_to_chamber[-1] == len(chambers): chambers.append(chamber) return (chambers, cone_to_chamber) def _construct_codomain_fan(self): r""" Construct the codomain fan as the image of the domain one. .. WARNING:: This method should be called only during initialization. OUTPUT: - none, but the codomain fan of self is set to the constucted fan. TESTS:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl) Traceback (most recent call last): ... ValueError: codomain (fan) must be given explicitly if morphism is given by a matrix! sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, ZZ^2) sage: fm.codomain_fan().ray_matrix()  # indirect doctest [1 0 1] [0 1 1] """ # We literally try to construct the image fan and hope that it works. # If it does not, the fan constructor will raise an exception. domain_fan = self._domain_fan self._codomain_fan = Fan(cones=(domain_cone.ambient_ray_indices() for domain_cone in domain_fan), rays=(self(ray) for ray in domain_fan.rays())) def _latex_(self): r""" Return the \LaTeX representation of self. OUTPUT: - a :class:string. EXAMPLES:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: fm Fan morphism defined by the matrix [1 0] [0 1] Domain fan: Rational polyhedral fan in 2-d lattice N Codomain fan: Rational polyhedral fan in 2-d lattice N sage: print fm._latex_() \left(\begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array}\right) : \Sigma^{2} \to \Sigma^{2} """ return (r"%s : %s \to %s" % (latex(self.matrix()), latex(self.domain_fan()), latex(self.codomain_fan()))) def _repr_(self): r""" Return the string representation of self. OUTPUT: - a :class:string. EXAMPLES:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: print fm._repr_() Fan morphism defined by the matrix [1 0] [0 1] Domain fan: Rational polyhedral fan in 2-d lattice N Codomain fan: Rational polyhedral fan in 2-d lattice N """ return ("Fan morphism defined by the matrix\n" "%s\n" "Domain fan: %s\n" "Codomain fan: %s" % (self.matrix(), self.domain_fan(), self.codomain_fan())) def _subdivide_domain_fan(self, check, verbose): r""" Subdivide the domain fan to make it compatible with the codomain fan. .. WARNING:: This method should be called only during initialization. INPUT: - check -- (default: True) if False, some of the consistency checks will be omitted, which saves time but can potentially lead to wrong results. Currently, with check=False option there will be no check that domain_fan maps to codomain_fan; - verbose -- (default: False) if True, some timing information will be printed in the process. OUTPUT: - none, but the domain fan of self is replaced with its minimal refinement, if possible. Otherwise a ValueError exception is raised. TESTS:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant, quadrant_bl) Traceback (most recent call last): ... ValueError: the image of generating cone #0 of the domain fan is not contained in a single cone of the codomain fan! sage: fm = FanMorphism(identity_matrix(2), quadrant, ...                    quadrant_bl, subdivide=True) sage: fm.domain_fan().ray_matrix()  # indirect doctest [1 0 1] [0 1 1] Now we demonstrate a more subtle example. We take the first quadrant as our domain_fan. Then we divide the first quadrant into three cones, throw away the middle one and take the other two as our codomain_fan. These fans are incompatible with the identity lattice morphism since the image of domain_fan is out of the support of codomain_fan:: sage: N = ToricLattice(2) sage: phi = End(N).identity() sage: F1 = Fan(cones=[(0,1)], rays=[(1,0), (0,1)]) sage: F2 = Fan(cones=[(0,1), (2,3)], ...            rays=[(1,0), (2,1), (1,2), (0,1)]) sage: FanMorphism(phi, F1, F2) Traceback (most recent call last): ... ValueError: the image of generating cone #0 of the domain fan is not contained in a single cone of the codomain fan! sage: FanMorphism(phi, F1, F2, subdivide=True) Traceback (most recent call last): ... ValueError: morphism defined by [1 0] [0 1] does not map Rational polyhedral fan in 2-d lattice N into the support of Rational polyhedral fan in 2-d lattice N! """ domain_fan = self._domain_fan codomain_fan = self._codomain_fan if verbose: start = walltime() print "Placing ray images...", # Figure out where 1-dimensional cones (i.e. rays) are mapped. RISGIS = self._RISGIS() if verbose: print "%.3f ms" % walltime(start) # Subdivide cones that require it. chambers = None # preimages of codomain cones, computed if necessary new_cones = [] for cone_index, domain_cone in enumerate(domain_fan): if reduce(operator.and_, (RISGIS[i] for i in domain_cone.ambient_ray_indices())): new_cones.append(domain_cone) continue dim = domain_cone.dim() if chambers is None: if verbose: start = walltime() print "Computing chambers...", chambers, cone_to_chamber = self._chambers() if verbose: print "%.3f ms" % walltime(start) # Subdivide domain_cone. if verbose: start = walltime() print ("Subdividing cone %d of %d..." % (cone_index + 1, domain_fan.ngenerating_cones())), # Only these chambers intersect domain_cone. containing_chambers = (cone_to_chamber[j] for j in reduce(operator.or_, (RISGIS[i] for i in domain_cone.ambient_ray_indices()))) # We don't care about chambers of small dimension. containing_chambers = (chambers[i] for i in set(containing_chambers) if chambers[i].dim() >= dim) parts = (domain_cone.intersection(chamber) for chamber in containing_chambers) # We cannot leave parts as a generator since we use them twice. parts = [part for part in parts if part.dim() == dim] if check: # Check if the subdivision is complete, i.e. there are no # missing pieces of domain_cone. To do this, we construct a # fan from the obtained parts and check that interior points # of boundary cones of this fan are in the interior of the # original cone. In any case we know that we are constructing # a valid fan, so passing check=False to Fan(...) is OK. if verbose: print "%.3f ms" % walltime(start) start = walltime() print "Checking for missing pieces... ", cone_subdivision = Fan(parts, check=False) for cone in cone_subdivision(dim - 1): if len(cone.star_generators()) == 1: if domain_cone.relative_interior_contains( sum(cone.rays())): self._support_error() new_cones.extend(parts) if verbose: print "%.3f ms" % walltime(start) if len(new_cones) > domain_fan.ngenerating_cones(): # Construct a new fan keeping old rays in the same order new_rays = list(domain_fan.rays()) for cone in new_cones: for ray in cone: if ray not in new_rays: new_rays.append(ray) # Replace domain_fan, this is OK since this method must be called # only during initialization of the FanMorphism. self._domain_fan = Fan(new_cones, new_rays, domain_fan.lattice(), check=False) # Also remove RISGIS for the old fan del self._RISGIS_ def _support_error(self): r""" Raise a ValueError exception due to support incompatibility. OUTPUT: - none, a ValueError exception is raised. TESTS: We deliberately construct an invalid morphism for this demonstration:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), ...             quadrant, quadrant_bl, check=False) Now we report that the morphism is invalid:: sage: fm._support_error() Traceback (most recent call last): ... ValueError: morphism defined by [1 0] [0 1] does not map Rational polyhedral fan in 2-d lattice N into the support of Rational polyhedral fan in 2-d lattice N! """ raise ValueError("morphism defined by\n" "%s\n" "does not map\n" "%s\n" "into the support of\n" "%s!" % (self.matrix(), self.domain_fan(), self.codomain_fan())) def _validate(self): r""" Check if self is indeed compatible with domain and codomain fans. OUTPUT: - none, but a ValueError exception is raised if there is a cone of the domain fan of self which is not completely contained in a single cone of the codomain fan of self, or if one of these fans does not sit in the appropriate lattice. EXAMPLES:: sage: N3 = ToricLattice(3, "N3") sage: N2 = ToricLattice(2, "N2") sage: H = Hom(N3, N2) sage: phi = H([N2.0, N2.1, N2.0]) sage: F1 = Fan(cones=[(0,1,2), (1,2,3)], ...            rays=[(1,1,1), (1,1,-1), (1,-1,1), (1,-1,-1)], ...            lattice=N3) sage: F1.ray_matrix() [ 1  1  1  1] [ 1  1 -1 -1] [ 1 -1  1 -1] sage: [phi(ray) for ray in F1.rays()] [N2(2, 1), N2(0, 1), N2(2, -1), N2(0, -1)] sage: F2 = Fan(cones=[(0,1,2), (1,2,3)], ...            rays=[(1,1,1), (1,1,-1), (1,2,1), (1,2,-1)], ...            lattice=N3) sage: F2.ray_matrix() [ 1  1  1  1] [ 1  1  2  2] [ 1 -1  1 -1] sage: [phi(ray) for ray in F2.rays()] [N2(2, 1), N2(0, 1), N2(2, 2), N2(0, 2)] sage: F3 = Fan(cones=[(0,1), (1,2)], ...            rays=[(1,0), (2,1), (0,1)], ...            lattice=N2) sage: FanMorphism(phi, F2, F3) Fan morphism defined by the matrix [1 0] [0 1] [1 0] Domain fan: Rational polyhedral fan in 3-d lattice N3 Codomain fan: Rational polyhedral fan in 2-d lattice N2 sage: FanMorphism(phi, F1, F3)  # indirect doctest Traceback (most recent call last): ... ValueError: morphism defined by [1 0] [0 1] [1 0] does not map Rational polyhedral fan in 3-d lattice N3 into the support of Rational polyhedral fan in 2-d lattice N2! """ domain_fan = self._domain_fan if domain_fan.lattice() is not self.domain(): raise ValueError("%s does not sit in %s!" % (domain_fan, self.domain())) codomain_fan = self._codomain_fan if codomain_fan.lattice() is not self.codomain(): raise ValueError("%s does not sit in %s!" % (codomain_fan, self.codomain())) RISGIS = self._RISGIS() for n, domain_cone in enumerate(domain_fan): if not reduce(operator.and_, (RISGIS[i] for i in domain_cone.ambient_ray_indices())): raise ValueError("the image of generating cone #%d of the " "domain fan is not contained in a single " "cone of the codomain fan!" % n) def codomain_fan(self): r""" Return the codomain fan of self. OUTPUT: - a :class:fan . EXAMPLES:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: fm.codomain_fan() Rational polyhedral fan in 2-d lattice N sage: fm.codomain_fan() is quadrant True """ return self._codomain_fan def domain_fan(self): r""" Return the codomain fan of self. OUTPUT: - a :class:fan . EXAMPLES:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: fm.domain_fan() Rational polyhedral fan in 2-d lattice N sage: fm.domain_fan() is quadrant_bl True """ return self._domain_fan def image_cone(self, cone): r""" Return the cone of the codomain fan containing the image of cone. INPUT: - cone -- a :class:cone  equivalent to a cone of the :meth:domain_fan of self. OUTPUT: - a :class:cone  of the :meth:codomain_fan of self. EXAMPLES:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: fm.image_cone(Cone([(1,0)])) 1-d cone of Rational polyhedral fan in 2-d lattice N sage: fm.image_cone(Cone([(1,1)])) 2-d cone of Rational polyhedral fan in 2-d lattice N TESTS: We check that complete codomain fans are handled correctly, since a different algorithm is used in this case:: sage: diamond = lattice_polytope.octahedron(2) sage: face = FaceFan(diamond) sage: normal = NormalFan(diamond) sage: N = face.lattice() sage: fm = FanMorphism(identity_matrix(2), ...           normal, face, subdivide=True) sage: fm.image_cone(Cone([(1,0)])) 1-d cone of Rational polyhedral fan in 2-d lattice N sage: fm.image_cone(Cone([(1,1)])) 2-d cone of Rational polyhedral fan in 2-d lattice N """ cone = self._domain_fan.embed(cone) if cone not in self._image_cone: codomain_fan = self._codomain_fan() if cone.is_trivial(): self._image_cone[cone] = codomain_fan(0)[0] elif codomain_fan.is_complete(): # Optimization for a common case RISGIS = self._RISGIS() CSGIS = set(reduce(operator.and_, (RISGIS[i] for i in cone.ambient_ray_indices()))) image_cone = codomain_fan.generating_cone(CSGIS.pop()) for i in CSGIS: image_cone = image_cone.intersection( codomain_fan.generating_cone(i)) self._image_cone[cone] = image_cone else: self._image_cone[cone] = codomain_fan.cone_containing( self(ray) for ray in cone) return self._image_cone[cone] def preimage_cones(self, cone): r""" Return cones of the domain fan whose :meth:image_cone is cone. INPUT: - cone -- a :class:cone  equivalent to a cone of the :meth:codomain_fan of self. OUTPUT: - a :class:tuple of :class:cones  of the :meth:domain_fan of self. EXAMPLES:: sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant = Fan([quadrant]) sage: quadrant_bl = quadrant.subdivide([(1,1)]) sage: fm = FanMorphism(identity_matrix(2), quadrant_bl, quadrant) sage: fm.preimage_cones(Cone([(1,0)])) (1-d cone of Rational polyhedral fan in 2-d lattice N,) sage: fm.preimage_cones(Cone([(1,0), (0,1)])) (1-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) TESTS: We check that reviewer's example from Trac #9972 is handled correctly:: sage: N1 = ToricLattice(1) sage: N2 = ToricLattice(2) sage: Hom21 = Hom(N2, N1) sage: pr = Hom21([N1.0,0]) sage: P1xP1 = toric_varieties.P1xP1() sage: f = FanMorphism(pr, P1xP1.fan()) sage: c = f.image_cone(Cone([(1,0), (0,1)])) sage: c 1-d cone of Rational polyhedral fan in 1-d lattice N sage: f.preimage_cones(c) (1-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) """ cone = self._codomain_fan.embed(cone) domain_fan = self._domain_fan if cone not in self._preimage_cones: # "Images of preimages" must sit in all of these generating cones CSGI = cone.star_generator_indices() RISGIS = self._RISGIS() possible_rays = frozenset(i for i in range(domain_fan.nrays()) if RISGIS[i].issuperset(CSGI)) preimage_cones = [] for dcones in domain_fan.cones(): for dcone in dcones: if (possible_rays.issuperset(dcone.ambient_ray_indices()) and self.image_cone(dcone) == cone): preimage_cones.append(dcone) self._preimage_cones[cone] = tuple(preimage_cones) return self._preimage_cones[cone]