Ticket #11200: trac_11200_Add_fibration_check_to_FanMorphism.patch

File trac_11200_Add_fibration_check_to_FanMorphism.patch, 38.8 KB (added by novoselt, 11 years ago)
  • sage/geometry/fan.py

    # HG changeset patch
    # User Andrey Novoseltsev <novoselt@gmail.com>
    # Date 1306616112 21600
    # Node ID 1405e93136c27b6fb6195fdc6c1efcae6339ae6f
    # Parent  8dd4e5f20b8eeacfcdbbfa3766db6424e2d1594e
    Add is_injective/surjective/bundle/fibration to FanMorphism.
    
    The original point of this patch was to introduce is_fibration method, but
    along the way is_injective/surjective/bundle, index, and a few others were
    added. There were also some other changes:
     * Fan methods for embedding and containment check were adjusted slightly to
       accomodate sublattices and reduce code duplication.
     * Treatment of lattice quotients and sublattices was improved to allow work
       with fans in sublattices.
    
    diff -r 8dd4e5f20b8e -r 1405e93136c2 sage/geometry/fan.py
    a b  
    11251125            False
    11261126            sage: cone1_f.is_equivalent(cone1)
    11271127            True
    1128             sage: cone1   in Fan([cone1, cone2])  # not a cone of any particular fan
     1128            sage: cone1 in Fan([cone1, cone2])  # not a cone of any particular fan
    11291129            True
    11301130            sage: cone1_f in Fan([cone1, cone2])  # belongs to different fan, but equivalent cone
    11311131            True
    11321132        """
    1133         if not is_Cone(cone):
     1133        try:
     1134            self.embed(cone)    # Fails if cone is not in self.
     1135            return True
     1136        except TypeError:   # cone is not a cone
    11341137            return False
    1135         if cone.lattice() != self.lattice():
    1136             warnings.warn("you have checked if a fan contains a cone "
    1137                           "from another lattice, this is always False!",
    1138                           stacklevel=3)
    1139             # We may need to take into account canonical maps, perhaps...
     1138        except ValueError:  # cone is a cone, but wrong
     1139            if not cone.lattice().is_submodule(self.lattice()):
     1140                warnings.warn("you have checked if a fan contains a cone "
     1141                              "from another lattice, this is always False!",
     1142                              stacklevel=3)
    11401143            return False
    1141         if (not cone.is_strictly_convex()
    1142             or not cone.ray_set().issubset(self.ray_set())):
    1143             return False
    1144         try:
    1145             return cone.is_equivalent(self.cone_containing(cone.rays()))
    1146         except ValueError:  # No cone of then fan contains all these rays
    1147             return False
    11481144
    11491145    def support_contains(self, *args):
    11501146        r"""
     
    18991895            ValueError: 2-d cone in 3-d lattice N does not belong
    19001896            to Rational polyhedral fan in 3-d lattice N!
    19011897        """
    1902         assert is_Cone(cone)
     1898        if not is_Cone(cone):
     1899            raise TypeError("%s is not a cone!" % cone)
    19031900        if cone.ambient() is self:
    19041901            return cone
    19051902        rays = self.rays()
    19061903        try:
    1907             # Compute ray indices
     1904            # Compute ray indices.
    19081905            ray_indices = [rays.index(ray) for ray in cone.rays()]
    19091906            # Get the smallest cone containing them
    19101907            result = self.cone_containing(*ray_indices)
    1911             # It should be equivalent to the original one
    1912             if not result.is_equivalent(cone):
     1908            # If there is a cone containing all of the rays of the given cone,
     1909            # they must be among its generating rays and we only need to worry
     1910            # if there are any extra ones.
     1911            if cone.nrays() != result.nrays():
    19131912                raise ValueError
    19141913        except ValueError:
    19151914            raise ValueError("%s does not belong to %s!" % (cone, self))
     
    24622461            gens.append( sum([ self.ray(i)[d] * ring.gen(i)
    24632462                               for i in range(0, self.nrays()) ]) )
    24642463        return ring.ideal(gens)
     2464
     2465
     2466def discard_faces(cones):
     2467    r"""
     2468    Return the cones of the given list which are not faces of each other.
     2469   
     2470    INPUT:
     2471   
     2472    - ``cones`` -- a list of
     2473      :class:`cones <sage.geometry.cone.ConvexRationalPolyhedralCone>`.
     2474     
     2475    OUTPUT:
     2476   
     2477    - a list of
     2478      :class:`cones <sage.geometry.cone.ConvexRationalPolyhedralCone>`,
     2479      sorted by dimension in decreasing order.
     2480     
     2481    EXAMPLES:
     2482   
     2483    Consider all cones of a fan::
     2484   
     2485        sage: Sigma = toric_varieties.P2().fan()
     2486        sage: cones = flatten(Sigma.cones())
     2487        sage: len(cones)
     2488        7
     2489       
     2490    Most of them are not necessary to generate this fan::
     2491   
     2492        sage: from sage.geometry.fan import discard_faces
     2493        sage: len(discard_faces(cones))
     2494        3
     2495        sage: Sigma.ngenerating_cones()
     2496        3
     2497    """
     2498    # Convert to a list or make a copy, so that the input is unchanged.
     2499    cones = list(cones)
     2500    cones.sort(key=lambda cone: cone.dim(), reverse=True)
     2501    generators = []
     2502    for cone in cones:
     2503        if not any(cone.is_face_of(other) for other in generators):
     2504            generators.append(cone)
     2505    return generators
  • sage/geometry/fan_morphism.py

    diff -r 8dd4e5f20b8e -r 1405e93136c2 sage/geometry/fan_morphism.py
    a b  
    1010AUTHORS:
    1111
    1212- Andrey Novoseltsev (2010-10-17): initial version.
     13- Andrey Novoseltsev (2011-04-11): added tests for injectivity/surjectivity,
     14    fibration, bundle, as well as some related methods.
    1315
    1416EXAMPLES:
    1517
     
    7072
    7173from sage.categories.all import Hom
    7274from sage.geometry.cone import Cone
    73 from sage.geometry.fan import Fan, is_Fan
    74 from sage.matrix.all import is_Matrix
    75 from sage.misc.all import latex, walltime
     75from sage.geometry.fan import Fan, is_Fan, discard_faces
     76from sage.matrix.all import matrix, is_Matrix
     77from sage.misc.all import cached_method, latex, walltime
    7678from sage.modules.free_module_morphism import (FreeModuleMorphism,
    7779                                               is_FreeModuleMorphism)
     80from sage.rings.all import ZZ, is_Infinite
    7881
    7982
    8083class FanMorphism(FreeModuleMorphism):
     
    273276        self._image_cone = dict()
    274277        self._preimage_cones = dict()
    275278        self._preimage_fans = dict()
     279        self._primitive_preimage_cones = dict()
    276280        if codomain_fan is None:
    277281            self._construct_codomain_fan()
    278282        else:
     
    405409        self._codomain_fan = Fan(cones=(domain_cone.ambient_ray_indices()
    406410                                        for domain_cone in domain_fan),
    407411                                 rays=(self(ray) for ray in domain_fan.rays()),
     412                                 lattice=self.codomain(),
    408413                                 discard_warning=False)
    409414   
    410415    def _latex_(self):
     
    435440        """
    436441        return (r"%s : %s \to %s" % (latex(self.matrix()),
    437442                        latex(self.domain_fan()), latex(self.codomain_fan())))
    438            
     443
     444    @cached_method
     445    def _ray_index_map(self):
     446        r"""
     447        Return the map between indices of rays in domain and codomain fans.
     448       
     449        OUTPUT:
     450       
     451        - a tuple of integers. If the `i`-th entry is -1, the `i`-th ray of the
     452          domain fan is mapped to the origin. If it is `j`, then the `i`-th ray
     453          of the domain fan is mapped onto the `j`-th ray of the codomain fan.
     454          If there is a ray in the domain fan which is mapped into the relative
     455          interior of a higher dimensional cone, a ``ValueError`` exception is
     456          raised.
     457         
     458        .. NOTE::
     459       
     460            This method is used by :meth:`is_bundle` and :meth:`is_fibration`.
     461         
     462        TESTS::
     463       
     464            sage: Sigma = toric_varieties.dP8().fan()
     465            sage: Sigma_p = toric_varieties.P1().fan()
     466            sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
     467            sage: phi._ray_index_map()
     468            (-1, 1, -1, 0)
     469            sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
     470            sage: xi._ray_index_map()
     471            Traceback (most recent call last):
     472            ...
     473            ValueError: ray #1 is mapped into a 2-d cone!
     474        """
     475        Sigma = self.domain_fan()
     476        ray_index_map = [-1] * Sigma.nrays()
     477        for i, rho in enumerate(Sigma(1)):
     478            sigma_p = self.image_cone(rho)
     479            if sigma_p.nrays() > 1:
     480                raise ValueError("ray #%d is mapped into a %d-d cone!" %
     481                                 (i, sigma_p.dim()))
     482            elif sigma_p.nrays() == 1:
     483                ray_index_map[i] = sigma_p.ambient_ray_indices()[0]
     484        return tuple(ray_index_map)
     485       
    439486    def _repr_(self):
    440487        r"""
    441488        Return the string representation of ``self``.
     
    857904                self._image_cone[cone] = codomain_fan.cone_containing(
    858905                                                    self(ray) for ray in cone)
    859906        return self._image_cone[cone]
     907       
     908    @cached_method
     909    def index(self):
     910        r"""
     911        Return the index of ``self`` as a map between lattices.
     912       
     913        OUTPUT:
     914       
     915        - an integer or infinity.
     916       
     917        EXAMPLES::
     918       
     919            sage: Sigma = toric_varieties.dP8().fan()
     920            sage: Sigma_p = toric_varieties.P1().fan()
     921            sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
     922            sage: phi.index()
     923            1
     924            sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
     925            sage: psi.index()
     926            2
     927            sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
     928            sage: xi.index()
     929            +Infinity
     930        """
     931        return self.matrix().image().index_in(self.codomain())
    860932
     933    @cached_method
     934    def is_bundle(self):
     935        r"""
     936        Check if ``self`` is a bundle.
     937       
     938        OUTPUT:
     939       
     940        - ``True`` if ``self`` is a bundle, ``False`` otherwise.
     941       
     942        Let `\phi: \Sigma \to \Sigma'` be a fan morphism such that the
     943        underlying lattice morphism `\phi: N \to N'` is surjective. Let
     944        `\Sigma_0` be the kernel fan of `\phi`. Then `\phi` is a **bundle** (or
     945        splitting) if there is a subfan `\widehat{\Sigma}` of `\Sigma` such
     946        that the following two conditions are satisfied:
     947       
     948            #. Cones of `\Sigma` are precisely the cones of the form
     949               `\sigma_0 + \widehat{\sigma}`, where `\sigma_0 \in \Sigma_0` and
     950               `\widehat{\sigma} \in \widehat{\Sigma}`.
     951               
     952            #. Cones of `\widehat{\Sigma}` are in bijection with cones of
     953               `\Sigma'` induced by `\phi` and `\phi` maps lattice points in
     954               every cone `\widehat{\sigma} \in \widehat{\Sigma}` bijectively
     955               onto lattice points in `\phi(\widehat{\sigma})`.
     956       
     957        If a fan morphism `\phi: \Sigma \to \Sigma'` is a bundle, then
     958        `X_\Sigma` is a fiber bundle over `X_{\Sigma'}` with fibers
     959        `X_{\Sigma_0, N_0}`, where `N_0` is the kernel lattice of `\phi`. See
     960        [CLS11]_ for more details.
     961       
     962        .. seealso:: :meth:`is_fibration`, :meth:`kernel_fan`.
     963       
     964        REFERENCES:
     965       
     966        ..  [CLS11]
     967            David A. Cox, John Little, and Hal Schenck.
     968            *Toric Varieties*.
     969            Volume 124 of *Graduate Studies in Mathematics*.
     970            American Mathematical Society, Providence, RI, 2011.
     971           
     972        EXAMPLES:
     973
     974        We consider several maps between fans of a del Pezzo surface and the
     975        projective line::
     976       
     977            sage: Sigma = toric_varieties.dP8().fan()
     978            sage: Sigma_p = toric_varieties.P1().fan()
     979            sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
     980            sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
     981            sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
     982            sage: phi.is_bundle()
     983            True
     984            sage: phi.is_fibration()
     985            True
     986            sage: phi.index()
     987            1
     988            sage: psi.is_bundle()
     989            False
     990            sage: psi.is_fibration()
     991            True
     992            sage: psi.index()
     993            2
     994            sage: xi.is_fibration()
     995            False
     996            sage: xi.index()
     997            +Infinity
     998           
     999        The first of these maps induces not only a fibration, but a fiber
     1000        bundle structure. The second map is very similar, yet it fails to be
     1001        a bundle, as its index is 2. The last map is not even a fibration.
     1002        """
     1003        if self.index() != 1:
     1004            return False    # Not surjective between lattices.
     1005        Sigma = self.domain_fan()
     1006        Sigma_p = self.codomain_fan()
     1007        Sigma_0 = self.kernel_fan()
     1008        if (Sigma.ngenerating_cones() !=
     1009            Sigma_0.ngenerating_cones() * Sigma_p.ngenerating_cones()):
     1010            return False    # Definitely no splitting.
     1011        try:
     1012            ray_index_map = self._ray_index_map()
     1013        except ValueError:
     1014            return False  # Rays are not mapped onto rays or the origin.
     1015        # Figure out how Sigma_0 sits inside Sigma in terms of ray indices.
     1016        I_0s = [Sigma.embed(sigma_0).ambient_ray_indices()
     1017                for sigma_0 in Sigma_0]
     1018        # We examine only generating cones, this is sufficient.       
     1019        for sigma_p in Sigma_p:
     1020            primitive_cones = self.primitive_preimage_cones(sigma_p)
     1021            if len(primitive_cones) != 1:   # Should be only sigma_hat.
     1022                return False
     1023            sigma_hat = primitive_cones[0]
     1024            if sigma_p.dim() != sigma_hat.dim():
     1025                return False    # sigma -> sigma_p is not a bijection
     1026            I_p = sigma_p.ambient_ray_indices()
     1027            I_hat = sigma_hat.ambient_ray_indices()
     1028            if I_p != tuple(sorted(ray_index_map[i] for i in I_hat)):
     1029                return False    # sigma -> sigma_p is not a bijection
     1030            # Check that sigma_hat + sigma_0 is always in Sigma.
     1031            for I_0 in I_0s:
     1032                I = tuple(sorted(I_hat + I_0))
     1033                if all(sigma.ambient_ray_indices() != I for sigma in Sigma):
     1034                    return False
     1035        return True
     1036
     1037    @cached_method
     1038    def is_fibration(self):
     1039        r"""
     1040        Check if ``self`` is a fibration.
     1041       
     1042        OUTPUT:
     1043       
     1044        - ``True`` if ``self`` is a fibration, ``False`` otherwise.
     1045       
     1046        A fan morphism `\phi: \Sigma \to \Sigma'` is a **fibration** if for any
     1047        cone `\sigma' \in \Sigma'` and any primitive preimage cone `\sigma \in
     1048        \Sigma` corresponding to `\sigma'` the linear map of vector spaces
     1049        `\phi_\RR` induces a bijection between `\sigma` and `\sigma'`, and, in
     1050        addition, `\phi_\RR: N_\RR \to N'_\RR$ is surjective.
     1051       
     1052        If a fan morphism `\phi: \Sigma \to \Sigma'` is a fibration, then the
     1053        associated morphism between toric varieties `\tilde{\phi}: X_\Sigma \to
     1054        X_{\Sigma'}` is a fibration in the sense that it is surjective and all
     1055        of its fibers have the same dimension, namely `\dim X_\Sigma -
     1056        \dim X_{\Sigma'}`. These fibers do *not* have to be isomorphic, i.e. a
     1057        fibration is not necessarily a fiber bundle. See [HLY02]_ for more
     1058        details.
     1059       
     1060        .. seealso:: :meth:`is_bundle`, :meth:`primitive_preimage_cones`.
     1061       
     1062        REFERENCES:
     1063       
     1064        ..  [HLY02]
     1065            Yi Hu, Chien-Hao Liu, and Shing-Tung Yau.
     1066            Toric morphisms and fibrations of toric Calabi-Yau hypersurfaces.
     1067            *Adv. Theor. Math. Phys.*, 6(3):457-506, 2002.
     1068            arXiv:math/0010082v2 [math.AG].
     1069           
     1070        EXAMPLES:
     1071
     1072        We consider several maps between fans of a del Pezzo surface and the
     1073        projective line::
     1074       
     1075            sage: Sigma = toric_varieties.dP8().fan()
     1076            sage: Sigma_p = toric_varieties.P1().fan()
     1077            sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
     1078            sage: psi = FanMorphism(matrix([[2], [-2]]), Sigma, Sigma_p)
     1079            sage: xi = FanMorphism(matrix([[1, 0]]), Sigma_p, Sigma)
     1080            sage: phi.is_bundle()
     1081            True
     1082            sage: phi.is_fibration()
     1083            True
     1084            sage: phi.index()
     1085            1
     1086            sage: psi.is_bundle()
     1087            False
     1088            sage: psi.is_fibration()
     1089            True
     1090            sage: psi.index()
     1091            2
     1092            sage: xi.is_fibration()
     1093            False
     1094            sage: xi.index()
     1095            +Infinity
     1096           
     1097        The first of these maps induces not only a fibration, but a fiber
     1098        bundle structure. The second map is very similar, yet it fails to be
     1099        a bundle, as its index is 2. The last map is not even a fibration.
     1100       
     1101        TESTS:
     1102       
     1103        We check that reviewer's example on Trac 11200 works as expected::
     1104       
     1105            sage: P1 = toric_varieties.P1()
     1106            sage: A1 = toric_varieties.A1()
     1107            sage: phi = FanMorphism(matrix([[1]]), A1.fan(), P1.fan())
     1108            sage: phi.is_fibration()
     1109            False
     1110        """       
     1111        if not self.is_surjective():
     1112            return False
     1113        try:
     1114            ray_index_map = self._ray_index_map()
     1115        except ValueError:
     1116            return False    # Rays are not mapped onto rays or the origin.
     1117        Sigma_p = self.codomain_fan()
     1118        # Rays are already checked, the origin is trivial, start with 2-cones.
     1119        for d in range(2, Sigma_p.dim() + 1):
     1120            for sigma_p in Sigma_p(d):
     1121                I_p = sigma_p.ambient_ray_indices()
     1122                for sigma in self.primitive_preimage_cones(sigma_p):
     1123                    if sigma.dim() != d:
     1124                        return False    # sigma -> sigma_p is not a bijection
     1125                    I = sigma.ambient_ray_indices()
     1126                    if I_p != tuple(sorted(ray_index_map[i] for i in I)):
     1127                        return False    # sigma -> sigma_p is not a bijection
     1128        return True
     1129       
     1130    @cached_method
     1131    def is_injective(self):
     1132        r"""
     1133        Check if ``self`` is injective.
     1134       
     1135        OUTPUT:
     1136       
     1137        - ``True`` if ``self`` is injective, ``False`` otherwise.
     1138
     1139        Let `\phi: \Sigma \to \Sigma'` be a fan morphism such that the
     1140        underlying lattice morphism `\phi: N \to N'` bijectively maps `N` to a
     1141        *saturated* sublattice of `N'`. Let `\psi: \Sigma \to \Sigma'_0` be the
     1142        restriction of `\phi` to the image. Then `\phi` is **injective** if the
     1143        map between cones corresponding to `\psi` (injectively) maps each cone
     1144        of `\Sigma` to a cone of the same dimension.
     1145       
     1146        If a fan morphism `\phi: \Sigma \to \Sigma'` is injective, then the
     1147        associated morphism between toric varieties `\tilde{\phi}: X_\Sigma \to
     1148        X_{\Sigma'}` is injective.
     1149       
     1150        .. seealso:: :meth:`restrict_to_image`.
     1151               
     1152        EXAMPLES:
     1153       
     1154        Consider the fan of the affine plane::
     1155       
     1156            sage: A2 = toric_varieties.A(2).fan()
     1157
     1158        We will map several fans consisting of a single ray into the interior
     1159        of the 2-cone::
     1160       
     1161            sage: Sigma = Fan([Cone([(1,1)])])
     1162            sage: m = identity_matrix(2)
     1163            sage: FanMorphism(m, Sigma, A2).is_injective()
     1164            False
     1165           
     1166        This morphism was not injective since (in the toric varieties
     1167        interpretation) the 1-dimensional orbit corresponding to the ray was
     1168        mapped to the 0-dimensional orbit corresponding to the 2-cone. ::
     1169       
     1170            sage: Sigma = Fan([Cone([(1,)])])
     1171            sage: m = matrix(1, 2, [1,1])
     1172            sage: FanMorphism(m, Sigma, A2).is_injective()
     1173            True
     1174           
     1175        While the fans in this example are close to the previous one, here the
     1176        ray corresponds to a 0-dimensional orbit. ::
     1177           
     1178            sage: Sigma = Fan([Cone([(1,)])])
     1179            sage: m = matrix(1, 2, [2,2])
     1180            sage: FanMorphism(m, Sigma, A2).is_injective()
     1181            False
     1182           
     1183        Here the problem is that ``m`` maps the domain lattice to a
     1184        non-saturated sublattice of the codomain. The corresponding map of the
     1185        toric varieties is a two-sheeted cover of its image.
     1186       
     1187        We also embed the affine plane into the projective one::
     1188       
     1189            sage: P2 = toric_varieties.P(2).fan()
     1190            sage: m = identity_matrix(2)
     1191            sage: FanMorphism(m, A2, P2).is_injective()
     1192            True
     1193        """       
     1194        if self.matrix().index_in_saturation() != 1:
     1195            return False
     1196        if self.image().dimension() < self.codomain().dimension():
     1197            return self.restrict_to_image().is_injective()
     1198        # Now we know that underlying lattice morphism is bijective.
     1199        Sigma = self.domain_fan()
     1200        return all(all(self.image_cone(sigma).dim() == d for sigma in Sigma(d))
     1201                   for d in range(1, Sigma.dim() + 1))
     1202       
     1203    @cached_method
     1204    def is_surjective(self):
     1205        r"""
     1206        Check if ``self`` is surjective.
     1207       
     1208        OUTPUT:
     1209       
     1210        - ``True`` if ``self`` is surjective, ``False`` otherwise.
     1211       
     1212        A fan morphism `\phi: \Sigma \to \Sigma'` is **surjective** if the
     1213        corresponding map between cones is surjective, i.e. for each cone
     1214        `\sigma' \in \Sigma'` there is at least one preimage cone `\sigma \in
     1215        \Sigma` such that the relative interior of `\sigma` is mapped to the
     1216        relative interior of `\sigma'` and, in addition,
     1217        `\phi_\RR: N_\RR \to N'_\RR$ is surjective.
     1218       
     1219        If a fan morphism `\phi: \Sigma \to \Sigma'` is surjective, then the
     1220        associated morphism between toric varieties `\tilde{\phi}: X_\Sigma \to
     1221        X_{\Sigma'}` is surjective.
     1222       
     1223        .. seealso:: :meth:`is_bundle`, :meth:`is_fibration`,
     1224            :meth:`preimage_cones`.
     1225       
     1226        EXAMPLES:
     1227       
     1228        We check that the blow up of the affine plane at the origin is
     1229        surjective::
     1230       
     1231            sage: A2 = toric_varieties.A(2).fan()
     1232            sage: Bl = A2.subdivide([(1,1)])
     1233            sage: m = identity_matrix(2)
     1234            sage: FanMorphism(m, Bl, A2).is_surjective()
     1235            True
     1236           
     1237        It remains surjective if we throw away "south and north poles" of the
     1238        exceptional divisor::
     1239       
     1240            sage: FanMorphism(m, Fan(Bl.cones(1)), A2).is_surjective()
     1241            True
     1242           
     1243        But a single patch of the blow up does not cover the plane::
     1244           
     1245            sage: F = Fan([Bl.generating_cone(0)])
     1246            sage: FanMorphism(m, F, A2).is_surjective()
     1247            False
     1248       
     1249        TESTS:
     1250       
     1251        We check that reviewer's example on Trac 11200 works as expected::
     1252       
     1253            sage: P1 = toric_varieties.P1()
     1254            sage: A1 = toric_varieties.A1()
     1255            sage: phi = FanMorphism(matrix([[1]]), A1.fan(), P1.fan())
     1256            sage: phi.is_surjective()
     1257            False
     1258        """       
     1259        if is_Infinite(self.index()):
     1260            return False    # Not surjective between vector spaces.           
     1261        for dcones in self.codomain_fan().cones():
     1262            for sigma_p in dcones:
     1263                if not self.preimage_cones(sigma_p):
     1264                    return False
     1265        return True
     1266       
     1267    @cached_method
    8611268    def kernel_fan(self):
    8621269        r"""
    8631270        Return the subfan of the domain fan mapped into the origin.
     
    8681275       
    8691276        .. NOTE::
    8701277       
    871             The lattice of the kernel fan is the kernel sublattice of ``self``.
     1278            The lattice of the kernel fan is the :meth:`kernel` sublattice of
     1279            ``self``.
    8721280         
    873         .. seealso:: :meth:`kernel_lattice`, :meth:`preimage_fan`.
     1281        .. seealso:: :meth:`preimage_fan`.
    8741282         
    8751283        EXAMPLES::
    8761284       
     
    8811289            sage: _.ray_matrix()
    8821290            [1]
    8831291            [1]
    884             sage: fm.kernel_fan().cones() # not tested - TODO: make it work
     1292            sage: fm.kernel_fan().cones()
    8851293            ((0-d cone of Rational polyhedral fan in Sublattice <N(1, 1)>,),
    8861294             (1-d cone of Rational polyhedral fan in Sublattice <N(1, 1)>,))
    8871295        """
    8881296        fan = self.preimage_fan(Cone([], lattice=self.codomain()))
    8891297        return Fan((cone.ambient_ray_indices() for cone in fan), fan.rays(),
    890                    self.kernel_lattice(), check=False)
     1298                   lattice=self.kernel(), check=False)
    8911299           
    892     def kernel_lattice(self):
    893         r"""
    894         Return the sublattice of the domain lattice mapped to the origin.
    895        
    896         OUTPUT:
    897        
    898         - a sublattice.
    899        
    900         .. seealso:: :meth:`kernel_fan`.
    901        
    902         EXAMPLE::
    903 
    904             sage: fan = Fan(rays=[(1,0), (1,1), (0,1)], cones=[(0,1), (1,2)])
    905             sage: fm = FanMorphism(matrix([1,-1]), fan, ToricLattice(1))
    906             sage: fm.kernel_lattice()
    907             Sublattice <N(1, 1)>
    908         """
    909         return self.domain().submodule_with_basis(
    910                                         self.matrix().integer_kernel().basis())
    911        
    9121300    def preimage_cones(self, cone):
    9131301        r"""
    9141302        Return cones of the domain fan whose :meth:`image_cone` is ``cone``.
     
    9231311       
    9241312        - a :class:`tuple` of :class:`cones
    9251313          <sage.geometry.cone.ConvexRationalPolyhedralCone>` of the
    926           :meth:`domain_fan` of ``self``.
     1314          :meth:`domain_fan` of ``self``, sorted by dimension.
    9271315         
    9281316        .. seealso:: :meth:`preimage_fan`.
    9291317         
     
    10301418            self._preimage_fans[cone] = Fan(cones,
    10311419                            domain_fan.rays(sorted(ray_indices)), check=False)
    10321420        return self._preimage_fans[cone]
     1421
     1422    def primitive_preimage_cones(self, cone):
     1423        r"""
     1424        Return the primitive cones of the domain fan corresponding to ``cone``.
     1425       
     1426        INPUT:
     1427       
     1428        - ``cone`` -- a :class:`cone
     1429          <sage.geometry.cone.ConvexRationalPolyhedralCone>` equivalent to a
     1430          cone of the :meth:`codomain_fan` of ``self``.
     1431         
     1432        OUTPUT:
     1433       
     1434        - a :class:`cone <sage.geometry.cone.ConvexRationalPolyhedralCone>`.
     1435       
     1436        Let `\phi: \Sigma \to \Sigma'` be a fan morphism, let `\sigma \in
     1437        \Sigma`, and let `\sigma' = \phi(\sigma)`. Then `\sigma` is a
     1438        **primitive cone corresponding to** `\sigma'` if there is no proper
     1439        face `\tau` of `\sigma` such that `\phi(\tau) = \sigma'`.
     1440       
     1441        Primitive cones play an important role for fibration morphisms.
     1442       
     1443        .. seealso:: :meth:`is_fibration`, :meth:`preimage_cones`,
     1444            :meth:`preimage_fan`.
     1445       
     1446        EXAMPLES:
     1447       
     1448        Consider a projection of a del Pezzo surface onto the projective line::
     1449       
     1450            sage: Sigma = toric_varieties.dP6().fan()
     1451            sage: Sigma.ray_matrix()
     1452            [ 0 -1 -1  0  1  1]
     1453            [ 1  0 -1 -1  0  1]
     1454            sage: Sigma_p = toric_varieties.P1().fan()
     1455            sage: phi = FanMorphism(matrix([[1], [-1]]), Sigma, Sigma_p)
     1456           
     1457        Under this map, one pair of rays is mapped to the origin, one in the
     1458        positive direction, and one in the negative one. Also three
     1459        2-dimensional cones are mapped in the positive direction and three in
     1460        the negative one, so there are 5 preimage cones corresponding to either
     1461        of the rays of the codomain fan ``Sigma_p``::
     1462       
     1463            sage: len(phi.preimage_cones(Cone([(1,)])))
     1464            5       
     1465           
     1466        Yet only rays are primitive::
     1467
     1468            sage: phi.primitive_preimage_cones(Cone([(1,)]))
     1469            (1-d cone of Rational polyhedral fan in 2-d lattice N,
     1470             1-d cone of Rational polyhedral fan in 2-d lattice N)
     1471             
     1472        Since all primitive cones are mapped onto their images bijectively, we
     1473        get a fibration::
     1474       
     1475            sage: phi.is_fibration()
     1476            True
     1477           
     1478        But since there are several primitive cones corresponding to the same
     1479        cone of the codomain fan, this map is not a bundle, even though its
     1480        index is 1::
     1481       
     1482            sage: phi.is_bundle()
     1483            False
     1484            sage: phi.index()
     1485            1
     1486        """
     1487        sigma_p = self._codomain_fan.embed(cone) # Necessary if used as a key
     1488        if sigma_p not in self._primitive_preimage_cones:
     1489            primitive_cones = []
     1490            for sigma in self.preimage_cones(sigma_p):  # Sorted by dimension
     1491                if not any(tau.is_face_of(sigma) for tau in primitive_cones):
     1492                    primitive_cones.append(sigma)
     1493            self._primitive_preimage_cones[sigma_p] = tuple(primitive_cones)
     1494        return self._primitive_preimage_cones[sigma_p]
     1495
     1496    @cached_method
     1497    def restrict_to_image(self):
     1498        r"""
     1499        Return the fan morphism from the domain fan to the image fan.
     1500       
     1501        OUTPUT:
     1502       
     1503        - a :class:`fan morphism <FanMorphism>`.
     1504       
     1505        .. note::
     1506       
     1507            By the *image fan* of a fan morphism we mean the fan generated by
     1508            the intersections of cones of the codomain fan with the image
     1509            vector space of ``self``. The lattice of this fan is the saturation
     1510            of the image of ``self``.
     1511       
     1512        EXAMPLES:
     1513       
     1514        We embed a projective line "diagonally" into the product of two lines::
     1515       
     1516            sage: Sigma = toric_varieties.P1().fan()
     1517            sage: Sigmap = toric_varieties.P1xP1().fan()
     1518            sage: m = matrix(1, 2, [1,1])
     1519            sage: phi = FanMorphism(m, Sigma, Sigmap)
     1520            sage: phi
     1521            Fan morphism defined by the matrix
     1522            [1 1]
     1523            Domain fan: Rational polyhedral fan in 1-d lattice N
     1524            Codomain fan: Rational polyhedral fan in 2-d lattice N
     1525           
     1526        Now we restrict this morphism to its image::
     1527       
     1528            sage: psi = phi.restrict_to_image()
     1529            sage: psi
     1530            Fan morphism defined by the matrix
     1531            [1]
     1532            Domain fan: Rational polyhedral fan in 1-d lattice N
     1533            Codomain fan: Rational polyhedral fan in Sublattice <N(1, 1)>
     1534           
     1535        Note that the matrix defining a morphism to a fan in a sublattice
     1536        operates with generators of this sublattice, so ``[1]`` in the above
     1537        example means that `\psi` sends the only generator of the domain
     1538        lattice to the generator of the image sublattice::
     1539       
     1540            sage: psi.codomain().gens()
     1541            (N(1, 1),)
     1542            sage: psi.codomain_fan().ray_matrix()
     1543            [ 1 -1]
     1544            [ 1 -1]
     1545           
     1546        Restriction to image returns exactly the same map if the corresponding
     1547        map of vector spaces is surjective, e.g. in the case of double
     1548        restriction::
     1549       
     1550            sage: psi.restrict_to_image() is psi
     1551            True
     1552        """
     1553        L = self.image().saturation()
     1554        d = L.dimension()
     1555        if self.codomain().dimension() == d:
     1556            return self
     1557        m = self.matrix()
     1558        m = matrix(ZZ, m.nrows(), d, (L.coordinates(c) for c in m.rows()))
     1559        L_cone = Cone(sum(([g, -g] for g in L.gens()), []))
     1560        Sigma = Fan(cones=discard_faces(L_cone.intersection(cone)
     1561                                        for cone in self.codomain_fan()),
     1562                    lattice=L, check=False)
     1563        return FanMorphism(m, self.domain_fan(), Sigma)
  • sage/geometry/toric_lattice.py

    diff -r 8dd4e5f20b8e -r 1405e93136c2 sage/geometry/toric_lattice.py
    a b  
    192192    return isinstance(x, ToricLattice_generic)
    193193
    194194
     195def is_ToricLatticeQuotient(x):
     196    r"""
     197    Check if ``x`` is a toric lattice quotient.
     198
     199    INPUT:
     200
     201    - ``x`` -- anything.
     202
     203    OUTPUT:
     204
     205    - ``True`` if ``x`` is a toric lattice quotient and ``False`` otherwise.
     206
     207    EXAMPLES::
     208
     209        sage: from sage.geometry.toric_lattice import (
     210        ...     is_ToricLatticeQuotient)
     211        sage: is_ToricLatticeQuotient(1)
     212        False
     213        sage: N = ToricLattice(3)
     214        sage: N
     215        3-d lattice N
     216        sage: is_ToricLatticeQuotient(N)
     217        False
     218        sage: Q = N / N.submodule([(1,2,3), (3,2,1)])
     219        sage: Q
     220        Quotient with torsion of 3-d lattice N
     221        by Sublattice <N(1, 2, 3), N(0, 4, 8)>
     222        sage: is_ToricLatticeQuotient(Q)
     223        True
     224    """
     225    return isinstance(x, ToricLattice_quotient)
     226
     227
    195228class ToricLatticeFactory(UniqueFactory):
    196229    r"""
    197230    Create a lattice for toric geometry objects.
     
    643676                                     positive_point=positive_point,
    644677                                     positive_dual_point=positive_dual_point)
    645678
     679    def saturation(self):
     680        r"""
     681        Return the saturation of ``self``.
     682       
     683        OUTPUT:
     684       
     685        - a :class:`toric lattice <ToricLatticeFactory>`.
     686       
     687        EXAMPLES::
     688       
     689            sage: N = ToricLattice(3)
     690            sage: Ns = N.submodule([(1,2,3), (4,5,6)])
     691            sage: Ns
     692            Sublattice <N(1, 2, 3), N(0, 3, 6)>
     693            sage: Ns_sat = Ns.saturation()
     694            sage: Ns_sat
     695            Sublattice <N(1, 0, -1), N(0, 1, 2)>
     696            sage: Ns_sat is Ns_sat.saturation()
     697            True
     698        """
     699        S = super(ToricLattice_generic, self).saturation()
     700        return S if is_ToricLattice(S) else self.ambient_module().submodule(S)
     701
    646702    def span(self, *args, **kwds):
    647703        """
    648704        Return the span of the given generators.
     
    10201076
    10211077            sage: L = ToricLattice(3, "L")
    10221078            sage: L.submodule_with_basis([(3,2,1),(1,2,3)])._latex_()
    1023             '\\left\\langle\\left(3,\\,2,\\,1\\right)_{L}, \\left(1,\\,2,\\,3\\right)_{L}\\right\\rangle'
     1079            '\\left\\langle\\left(3,\\,2,\\,1\\right)_{L},
     1080             \\left(1,\\,2,\\,3\\right)_{L}\\right\\rangle'
    10241081            sage: L.submodule([(3,2,1),(1,2,3)])._latex_()
    1025             '\\left\\langle\\left(1,\\,2,\\,3\\right)_{L}, \\left(0,\\,4,\\,8\\right)_{L}\\right\\rangle'
     1082            '\\left\\langle\\left(1,\\,2,\\,3\\right)_{L},
     1083             \\left(0,\\,4,\\,8\\right)_{L}\\right\\rangle'
    10261084        """
    10271085        s  = '\\left\\langle'
    10281086        s += ', '.join([ b._latex_() for b in self.basis() ])
    10291087        s += '\\right\\rangle'
    10301088        return s
     1089       
     1090    def dual(self):
     1091        r"""
     1092        Return the lattice dual to ``self``.
     1093
     1094        OUTPUT:
     1095
     1096        - a :class:`toric lattice quotient <ToricLattice_quotient>`.
     1097
     1098        EXAMPLES::
     1099
     1100            sage: N = ToricLattice(3)
     1101            sage: Ns = N.submodule([(1,1,0), (3,2,1)])
     1102            sage: Ns.dual()
     1103            2-d lattice, quotient of 3-d lattice M by Sublattice <M(1, -1, -1)>
     1104        """
     1105        if "_dual" not in self.__dict__:
     1106            if not self is self.saturation():
     1107                raise ValueError("only dual lattices of saturated sublattices "
     1108                                 "can be constructed! Got %s." % self)
     1109            self._dual = (self.ambient_module().dual() /
     1110                          self.basis_matrix().right_kernel())
     1111            self._dual._dual = self
     1112        return self._dual
    10311113
    10321114    def plot(self, **options):
    10331115        r"""
     
    11871269            N[0, 1, 0]
    11881270        """
    11891271        return str(self.lift()).replace("(", "[", 1).replace(")", "]", 1)
    1190    
     1272       
     1273    def set_immutable(self):
     1274        r"""
     1275        Make ``self`` immutable.
     1276       
     1277        OUTPUT:
     1278       
     1279        - none.
     1280       
     1281        .. note:: Elements of toric lattice quotients are always immutable, so
     1282            this method does nothing, it is introduced for compatibility
     1283            purposes only.
     1284           
     1285        EXAMPLES::
     1286       
     1287            sage: N = ToricLattice(3)
     1288            sage: Ns = N.submodule([N(2,4,0), N(9,12,0)])
     1289            sage: Q = N/Ns
     1290            sage: Q.0.set_immutable()
     1291        """
     1292        pass
     1293       
    11911294
    11921295class ToricLattice_quotient(FGP_Module_class):
    11931296    r"""
     
    13441447            sage: N = ToricLattice(3)
    13451448            sage: Ns = N.submodule([N(2,4,0), N(9,12,0)])
    13461449            sage: Q = N/Ns
    1347             sage: x = Q(1,2,3)
     1450            sage: x = Q(1,2,3)  # indirect doctest
    13481451            sage: x
    13491452            N[1, 2, 3]
    13501453            sage: type(x)
  • sage/geometry/toric_lattice_element.pyx

    diff -r 8dd4e5f20b8e -r 1405e93136c2 sage/geometry/toric_lattice_element.pyx
    a b  
    289289            sage: n * B
    290290            (8, 10, 12)
    291291        """
     292        Ns = self.parent()
    292293        # We try to deal only with the case of two lattice elements...
    293294        if is_ToricLatticeElement(other):
    294             if (other.parent().ambient_module()
    295                 is self.parent().ambient_module().dual()):
     295            if other.parent().ambient_module() is Ns.ambient_module().dual():
    296296                # Our own _dot_product_ is disabled
    297297                return Vector_integer_dense._dot_product_(self, other)
    298298            raise CoercionException("only elements of dual toric lattices "
     
    302302        # then the dot product will be called for elements of the same lattice
    303303        if isinstance(other, Vector_integer_dense):
    304304            return Vector_integer_dense._dot_product_(self, other)
     305        # We also allow action on elements of lattice quotients
     306        try:
     307            lift = other.lift()
     308            if is_ToricLatticeElement(lift):
     309                if other.parent().W().is_submodule(Ns.dual().W()):
     310                    return Vector_integer_dense._dot_product_(self, lift)
     311                raise CoercionException("only elements of dual toric lattices "
     312                                        "can act on each other!")
     313        except AttributeError:  # No lift
     314            pass
    305315        # Now let the standard framework work...
    306316        return Vector_integer_dense._act_on_(self, other, self_on_left)
    307317
  • sage/modules/free_module.py

    diff -r 8dd4e5f20b8e -r 1405e93136c2 sage/modules/free_module.py
    a b  
    24682468            return self
    24692469        try:
    24702470            A, _ = self.basis_matrix()._clear_denom()
    2471             S = A.saturation()
    2472             return S.row_space()
     2471            S = A.saturation().row_space()
    24732472        except AttributeError:
    24742473            # fallback in case _clear_denom isn't written
    24752474            V = self.vector_space()
    24762475            A = self.ambient_module()
    2477             return V.intersection(A)
     2476            S = V.intersection(A)
     2477        # Return exactly self if it is already saturated.
     2478        return self if self == S else S
    24782479   
    24792480    def span(self, gens, base_ring=None, check=True, already_echelonized=False):
    24802481        """