| 1 | r""" |
| 2 | Convex rational polyhedral cones |
| 3 | |
| 4 | This module was designed as a part of framework for toric varieties |
| 5 | (:mod:`~sage.schemes.generic.toric_variety`, |
| 6 | :mod:`~sage.schemes.generic.fano_toric_variety`). While the emphasis is on |
| 7 | strictly convex cones, non-strictly convex cones are supported as well. Work |
| 8 | with distinct lattices (in the sense of discrete subgroups spanning vector |
| 9 | spaces) is supported. The default lattice is :class:`ToricLattice |
| 10 | <sage.geometry.toric_lattice.ToricLatticeFactory>` `N` of the appropriate |
| 11 | dimension. The only case when you must specify lattice explicitly is creation |
| 12 | of a 0-dimensional cone, where dimension of the ambient space cannot be |
| 13 | guessed. |
| 14 | |
| 15 | In addition to cones (:class:`ConvexRationalPolyhedralCone`), this module |
| 16 | provides the base class for cones and fans (:class:`IntegralRayCollection`) |
| 17 | and the function for computing Hasse diagrams of finite atomic and coatomic |
| 18 | lattices (in the sense of partially ordered sets where any two elements have |
| 19 | meet and joint) (:func:`hasse_diagram_from_incidences`). |
| 20 | |
| 21 | AUTHORS: |
| 22 | |
| 23 | - Andrey Novoseltsev (2010-05-13): initial version. |
| 24 | |
| 25 | EXAMPLES: |
| 26 | |
| 27 | Use :func:`Cone` to construct cones:: |
| 28 | |
| 29 | sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) |
| 30 | sage: halfspace = Cone([(1,0,0), (0,1,0), (-1,-1,0), (0,0,1)]) |
| 31 | sage: positive_xy = Cone([(1,0,0), (0,1,0)]) |
| 32 | sage: four_rays = Cone([(1,1,1), (1,-1,1), (-1,-1,1), (-1,1,1)]) |
| 33 | |
| 34 | For all of the cones above we have provided primitive generating rays, but in |
| 35 | fact this is not necessary - a cone can be constructed from any collection of |
| 36 | rays (from the same space, of course). If there are non-primitive (or even |
| 37 | non-integral) rays, they will be replaced with primitive ones. If there are |
| 38 | extra rays, they will be discarded. Of course, this means that :func:`Cone` |
| 39 | has to do some work before actually constructing the cone and sometimes it is |
| 40 | not desirable, if you know for sure that your input is already "good". In this |
| 41 | case you can use options ``check=False`` to force :func:`Cone` to use |
| 42 | exactly the directions that you have specified and ``normalize=False`` to |
| 43 | force it to use exactly the rays that you have specified. However, it is |
| 44 | better not to use these possibilities without necessity, since cones are |
| 45 | assumed to be represented by a minimal set of primitive generating rays. |
| 46 | See :func:`Cone` for further documentation on construction. |
| 47 | |
| 48 | Once you have a cone, you can perfrom numerous operations on it. The most |
| 49 | important ones are, probably, ray accessing methods:: |
| 50 | |
| 51 | sage: halfspace.rays() |
| 52 | (N(0, 0, 1), N(1, 0, 0), N(-1, 0, 0), N(0, 1, 0), N(0, -1, 0)) |
| 53 | sage: halfspace.ray(2) |
| 54 | N(-1, 0, 0) |
| 55 | sage: halfspace.ray_matrix() |
| 56 | [ 0 1 -1 0 0] |
| 57 | [ 0 0 0 1 -1] |
| 58 | [ 1 0 0 0 0] |
| 59 | sage: halfspace.ray_set() |
| 60 | frozenset([N(1, 0, 0), N(-1, 0, 0), N(0, 1, 0), N(0, 0, 1), N(0, -1, 0)]) |
| 61 | |
| 62 | If you want to do something with each ray of a cone, you can write :: |
| 63 | |
| 64 | sage: for ray in positive_xy: print ray |
| 65 | N(1, 0, 0) |
| 66 | N(0, 1, 0) |
| 67 | |
| 68 | You can also get an iterator over only some selected rays:: |
| 69 | |
| 70 | sage: for ray in halfspace.ray_iterator([1,2,1]): print ray |
| 71 | N(1, 0, 0) |
| 72 | N(-1, 0, 0) |
| 73 | N(1, 0, 0) |
| 74 | |
| 75 | There are two dimensions associated to each cone - the dimension of the |
| 76 | subspace spanned by the cone and the dimension of the ambient space where it |
| 77 | lives:: |
| 78 | |
| 79 | sage: positive_xy.dim() |
| 80 | 2 |
| 81 | sage: positive_xy.ambient_dim() |
| 82 | 3 |
| 83 | |
| 84 | You also may be interested in this dimension:: |
| 85 | |
| 86 | sage: dim(positive_xy.linear_subspace()) |
| 87 | 0 |
| 88 | sage: dim(halfspace.linear_subspace()) |
| 89 | 2 |
| 90 | |
| 91 | Or, perhaps, all you care about is whether it is zero or not:: |
| 92 | |
| 93 | sage: positive_xy.is_strictly_convex() |
| 94 | True |
| 95 | sage: halfspace.is_strictly_convex() |
| 96 | False |
| 97 | |
| 98 | You can also perform these checks:: |
| 99 | |
| 100 | sage: positive_xy.is_simplicial() |
| 101 | True |
| 102 | sage: four_rays.is_simplicial() |
| 103 | False |
| 104 | sage: positive_xy.is_smooth() |
| 105 | True |
| 106 | |
| 107 | You can work with subcones that form faces of other cones:: |
| 108 | |
| 109 | sage: face = four_rays.faces(dim=2)[0] |
| 110 | sage: face |
| 111 | 2-dimensional face of 3-dimensional cone |
| 112 | sage: face.rays() |
| 113 | (N(1, 1, 1), N(1, -1, 1)) |
| 114 | sage: face.cone_rays() |
| 115 | (0, 1) |
| 116 | sage: four_rays.rays(face.cone_rays()) |
| 117 | (N(1, 1, 1), N(1, -1, 1)) |
| 118 | |
| 119 | If you need to know inclusion relations between faces, use :: |
| 120 | |
| 121 | sage: L = four_rays.face_lattice() |
| 122 | sage: map(len, L.level_sets()) |
| 123 | [1, 4, 4, 1] |
| 124 | sage: face = L.level_sets()[2][0] |
| 125 | sage: face.element.rays() |
| 126 | (N(1, 1, 1), N(1, -1, 1)) |
| 127 | sage: L.hasse_diagram().neighbors_in(face) |
| 128 | [1-dimensional face of 3-dimensional cone, |
| 129 | 1-dimensional face of 3-dimensional cone] |
| 130 | |
| 131 | When all the functionality provided by cones is not enough, you may want to |
| 132 | check if you can do necessary things using lattice polytopes and polyhedra |
| 133 | corresponding to cones:: |
| 134 | |
| 135 | sage: four_rays.lattice_polytope() |
| 136 | A lattice polytope: 3-dimensional, 5 vertices. |
| 137 | sage: four_rays.polyhedron() |
| 138 | A 3-dimensional polyhedron in QQ^3 defined as |
| 139 | the convex hull of 1 vertex and 4 rays. |
| 140 | |
| 141 | And of course you are always welcome to suggest new features that should be |
| 142 | added to cones! |
| 143 | """ |
| 144 | |
| 145 | #***************************************************************************** |
| 146 | # Copyright (C) 2010 Andrey Novoseltsev <novoselt@gmail.com> |
| 147 | # Copyright (C) 2010 William Stein <wstein@gmail.com> |
| 148 | # |
| 149 | # Distributed under the terms of the GNU General Public License (GPL) |
| 150 | # |
| 151 | # http://www.gnu.org/licenses/ |
| 152 | #***************************************************************************** |
| 153 | |
| 154 | |
| 155 | import collections |
| 156 | import copy |
| 157 | import warnings |
| 158 | |
| 159 | from sage.combinat.posets.posets import FinitePoset |
| 160 | from sage.geometry.lattice_polytope import LatticePolytope |
| 161 | from sage.geometry.polyhedra import Polyhedron |
| 162 | from sage.geometry.toric_lattice import ToricLattice, is_ToricLattice |
| 163 | from sage.graphs.all import DiGraph |
| 164 | from sage.matrix.all import matrix, identity_matrix |
| 165 | from sage.misc.all import flatten |
| 166 | from sage.modules.all import span, vector |
| 167 | from sage.rings.all import QQ, RR, ZZ, gcd |
| 168 | from sage.structure.all import SageObject |
| 169 | from sage.structure.coerce import parent |
| 170 | |
| 171 | |
| 172 | def is_Cone(x): |
| 173 | r""" |
| 174 | Check if ``x`` is a cone. |
| 175 | |
| 176 | INPUT: |
| 177 | |
| 178 | - ``x`` -- anything. |
| 179 | |
| 180 | OUTPUT: |
| 181 | |
| 182 | - ``True`` if ``x`` is a cone and ``False`` otherwise. |
| 183 | |
| 184 | EXAMPLES:: |
| 185 | |
| 186 | sage: from sage.geometry.cone import is_Cone |
| 187 | sage: is_Cone(1) |
| 188 | False |
| 189 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 190 | sage: quadrant |
| 191 | 2-dimensional cone |
| 192 | sage: is_Cone(quadrant) |
| 193 | True |
| 194 | """ |
| 195 | return isinstance(x, ConvexRationalPolyhedralCone) |
| 196 | |
| 197 | |
| 198 | def Cone(data, lattice=None, check=True, normalize=True): |
| 199 | r""" |
| 200 | Construct a (not necessarily strictly) convex rational polyhedral cone. |
| 201 | |
| 202 | INPUT: |
| 203 | |
| 204 | - ``data`` -- one of the following: |
| 205 | * :class:`~sage.geometry.polyhedra.Polyhedron` object representing |
| 206 | a valid cone; |
| 207 | * list of rays. Each ray should be given as a list or a vector |
| 208 | convertible to the rational extension of the given ``lattice``; |
| 209 | |
| 210 | - ``lattice`` -- :class:`ToricLattice |
| 211 | <sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any |
| 212 | other object that behaves like these. If not specified, an attempt will |
| 213 | be made to determine an appropriate toric lattice automatically; |
| 214 | |
| 215 | - ``check`` -- by default the input data will be checked for correctness |
| 216 | (e.g. that all rays have the same number of components) and generating |
| 217 | rays will be constructed from ``data``. If you know that the input is |
| 218 | a minimal set of generators of a valid cone, you may significantly |
| 219 | (up to 100 times on simple input) decrease construction time using |
| 220 | ``check=False`` option; |
| 221 | |
| 222 | - ``normalize`` -- you can further speed up construction using |
| 223 | ``normalize=False`` option. In this case ``data`` must be a list of |
| 224 | immutable primitive rays in ``lattice``. In general, you should not use |
| 225 | this option, it is designed for code optimization and does not give as |
| 226 | drastic improvement in speed as the previous one. |
| 227 | |
| 228 | OUTPUT: |
| 229 | |
| 230 | - convex rational polyhedral cone determined by ``data``. |
| 231 | |
| 232 | EXAMPLES: |
| 233 | |
| 234 | Let's define a cone corresponding to the first quadrant of the plane |
| 235 | (note, you can even mix objects of different types to represent rays, as |
| 236 | long as you let this function to perform all the checks and necessary |
| 237 | conversions!):: |
| 238 | |
| 239 | sage: quadrant = Cone([(1,0), [0,1]]) |
| 240 | sage: quadrant |
| 241 | 2-dimensional cone |
| 242 | sage: quadrant.rays() |
| 243 | (N(1, 0), N(0, 1)) |
| 244 | |
| 245 | You may prefer to use :meth:`~IntegralRayCollection.ray_matrix` when you |
| 246 | want to take a look at rays, they will be given as columns:: |
| 247 | |
| 248 | sage: quadrant.ray_matrix() |
| 249 | [1 0] |
| 250 | [0 1] |
| 251 | |
| 252 | If you give more rays than necessary, the extra ones will be discarded:: |
| 253 | |
| 254 | sage: Cone([(1,0), (0,1), (1,1), (0,1)]).rays() |
| 255 | (N(1, 0), N(0, 1)) |
| 256 | |
| 257 | However, this work is not done with ``check=False`` option, so use it |
| 258 | carefully! :: |
| 259 | |
| 260 | sage: Cone([(1,0), (0,1), (1,1), (0,1)], check=False).rays() |
| 261 | (N(1, 0), N(0, 1), N(1, 1), N(0, 1)) |
| 262 | |
| 263 | Even worse things can happen with ``normalize=False`` option:: |
| 264 | |
| 265 | sage: Cone([(1,0), (0,1)], check=False, normalize=False) |
| 266 | Traceback (most recent call last): |
| 267 | ... |
| 268 | AttributeError: 'tuple' object has no attribute 'parent' |
| 269 | |
| 270 | You can construct different "not" cones: not full-dimensional, not |
| 271 | strictly convex, not containing any rays:: |
| 272 | |
| 273 | sage: one_dimensional_cone = Cone([(1,0)]) |
| 274 | sage: one_dimensional_cone.dim() |
| 275 | 1 |
| 276 | sage: half_plane = Cone([(1,0), (0,1), (-1,0)]) |
| 277 | sage: half_plane.rays() |
| 278 | (N(0, 1), N(1, 0), N(-1, 0)) |
| 279 | sage: half_plane.is_strictly_convex() |
| 280 | False |
| 281 | sage: origin = Cone([(0,0)]) |
| 282 | sage: origin.rays() |
| 283 | () |
| 284 | sage: origin.dim() |
| 285 | 0 |
| 286 | sage: origin.ambient_dim() |
| 287 | 2 |
| 288 | |
| 289 | You may construct the cone above without giving any rays, but in this case |
| 290 | you must provide lattice explicitly:: |
| 291 | |
| 292 | sage: origin = Cone([]) |
| 293 | Traceback (most recent call last): |
| 294 | ... |
| 295 | ValueError: lattice must be given explicitly if there are no rays! |
| 296 | sage: origin = Cone([], lattice=ToricLattice(2)) |
| 297 | sage: origin.dim() |
| 298 | 0 |
| 299 | sage: origin.ambient_dim() |
| 300 | 2 |
| 301 | sage: origin.lattice() |
| 302 | 2-dimensional lattice N |
| 303 | |
| 304 | Of course, you can also provide lattice in other cases:: |
| 305 | |
| 306 | sage: L = ToricLattice(3, "L") |
| 307 | sage: c1 = Cone([(1,0,0),(1,1,1)], lattice=L) |
| 308 | sage: c1.rays() |
| 309 | (L(1, 0, 0), L(1, 1, 1)) |
| 310 | |
| 311 | Or you can construct cones from rays of a particular lattice:: |
| 312 | |
| 313 | sage: ray1 = L(1,0,0) |
| 314 | sage: ray2 = L(1,1,1) |
| 315 | sage: c2 = Cone([ray1, ray2]) |
| 316 | sage: c2.rays() |
| 317 | (L(1, 0, 0), L(1, 1, 1)) |
| 318 | sage: c1 == c2 |
| 319 | True |
| 320 | |
| 321 | When the cone in question is not strictly convex, the standard form for |
| 322 | the "generating rays" of the linear subspace is "basis vectors and their |
| 323 | negatives", as in the following example:: |
| 324 | |
| 325 | sage: plane = Cone([(1,0), (0,1), (-1,-1)]) |
| 326 | sage: plane.ray_matrix() |
| 327 | [ 1 -1 0 0] |
| 328 | [ 0 0 1 -1] |
| 329 | """ |
| 330 | # Cone from Polyhedron |
| 331 | if isinstance(data, Polyhedron): |
| 332 | polyhedron = data |
| 333 | if lattice is None: |
| 334 | lattice = ToricLattice(polyhedron.ambient_dim()) |
| 335 | if polyhedron.n_vertices() > 1: |
| 336 | raise ValueError("%s is not a cone!" % polyhedron) |
| 337 | apex = polyhedron.vertices()[0] |
| 338 | if apex.count(0) != len(apex): |
| 339 | raise ValueError("the apex of %s is not at the origin!" |
| 340 | % polyhedron) |
| 341 | rays = normalize_rays(polyhedron.rays(), lattice) |
| 342 | strict_rays = tuple(rays) |
| 343 | lines = tuple(normalize_rays(polyhedron.lines(), lattice)) |
| 344 | for line in lines: |
| 345 | rays.append(line) |
| 346 | rays.append(-line) |
| 347 | rays[-1].set_immutable() |
| 348 | cone = ConvexRationalPolyhedralCone(rays, lattice) |
| 349 | # Save constructed stuff for later use |
| 350 | cone._polyhedron = polyhedron |
| 351 | cone._strict_rays = strict_rays |
| 352 | cone._lines = lines |
| 353 | return cone |
| 354 | # Cone from rays |
| 355 | rays = data |
| 356 | if check or normalize: |
| 357 | # In principle, if check is True, this normalization is redundant, |
| 358 | # since we will need to renormalize the output from Polyhedra, but |
| 359 | # doing it now gives more consistent error messages (and it seems to |
| 360 | # be fast anyway, compared to Polyhedron creation). |
| 361 | rays = normalize_rays(rays, lattice) |
| 362 | if lattice is None: |
| 363 | if rays: |
| 364 | lattice = rays[0].parent() |
| 365 | else: |
| 366 | raise ValueError( |
| 367 | "lattice must be given explicitly if there are no rays!") |
| 368 | if check and rays: |
| 369 | # Any set of rays forms a cone, but we want to keep only generators |
| 370 | return Cone(Polyhedron(rays=rays), lattice) |
| 371 | return ConvexRationalPolyhedralCone(rays, lattice) |
| 372 | |
| 373 | |
| 374 | def normalize_rays(rays, lattice): |
| 375 | r""" |
| 376 | Normalize a list of rational rays, i.e. make them integral. |
| 377 | |
| 378 | INPUT: |
| 379 | |
| 380 | - ``rays`` -- list of rays which can be converted to the rational |
| 381 | extension of ``lattice``; |
| 382 | |
| 383 | - ``lattice`` -- :class:`ToricLattice |
| 384 | <sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any |
| 385 | other object that behaves like these. If ``None``, an attempt will |
| 386 | be made to determine an appropriate toric lattice automatically. |
| 387 | |
| 388 | OUTPUT: |
| 389 | |
| 390 | - list of immutable primitive vectors of the ``lattice`` in the same |
| 391 | directions as original ``rays``. |
| 392 | |
| 393 | EXAMPLES:: |
| 394 | |
| 395 | sage: from sage.geometry.cone import normalize_rays |
| 396 | sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], None) |
| 397 | [N(0, 1), N(0, 1), N(3, 2), N(3, 14)] |
| 398 | sage: L = ToricLattice(2, "L") |
| 399 | sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], L.dual()) |
| 400 | [L*(0, 1), L*(0, 1), L*(3, 2), L*(3, 14)] |
| 401 | sage: ray_in_L = L(0,1) |
| 402 | sage: normalize_rays([ray_in_L, (0, 2), (3, 2), (5/7, 10/3)], None) |
| 403 | [L(0, 1), L(0, 1), L(3, 2), L(3, 14)] |
| 404 | sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], ZZ^2) |
| 405 | [(0, 1), (0, 1), (3, 2), (3, 14)] |
| 406 | sage: normalize_rays([(0, 1), (0, 2), (3, 2), (5/7, 10/3)], ZZ^3) |
| 407 | Traceback (most recent call last): |
| 408 | ... |
| 409 | TypeError: cannot convert (0, 1) to |
| 410 | Vector space of dimension 3 over Rational Field! |
| 411 | sage: normalize_rays([], ZZ^3) |
| 412 | [] |
| 413 | """ |
| 414 | if rays is None: |
| 415 | rays = [] |
| 416 | try: |
| 417 | rays = list(rays) |
| 418 | except TypeError: |
| 419 | raise TypeError( |
| 420 | "rays must be given as a list or a compatible structure!" |
| 421 | "\nGot: %s" % rays) |
| 422 | if rays: |
| 423 | if lattice is None: |
| 424 | ray_parent = parent(rays[0]) |
| 425 | lattice = (ray_parent if is_ToricLattice(ray_parent) |
| 426 | else ToricLattice(len(rays[0]))) |
| 427 | V = lattice.base_extend(QQ) |
| 428 | for n, ray in enumerate(rays): |
| 429 | try: |
| 430 | ray = V(ray) |
| 431 | except TypeError: |
| 432 | raise TypeError("cannot convert %s to %s!" % (ray, V)) |
| 433 | if ray.is_zero(): |
| 434 | ray = lattice(0) |
| 435 | else: |
| 436 | ray = ray.denominator() * ray |
| 437 | ray = lattice(ray / gcd(lattice(ray))) |
| 438 | ray.set_immutable() |
| 439 | rays[n] = ray |
| 440 | return rays |
| 441 | |
| 442 | |
| 443 | class IntegralRayCollection(SageObject, |
| 444 | collections.Container, |
| 445 | collections.Hashable, |
| 446 | collections.Iterable): |
| 447 | r""" |
| 448 | Create a collection of integral rays. |
| 449 | |
| 450 | This is a base class for rational polyhedral cones and fans. |
| 451 | |
| 452 | Ray collections are immutable, but they cache most of the returned values. |
| 453 | |
| 454 | INPUT: |
| 455 | |
| 456 | - ``rays`` -- list of immutable vectors in ``lattice``; |
| 457 | |
| 458 | - ``lattice`` -- :class:`ToricLattice |
| 459 | <sage.geometry.toric_lattice.ToricLatticeFactory>`, `\ZZ^n`, or any |
| 460 | other object that behaves like these. If ``None``, it will be determined |
| 461 | as :func:`parent` of the first ray. Of course, this cannot be done if |
| 462 | there are no rays, so in this case you must give an appropriate |
| 463 | ``lattice`` directly. |
| 464 | |
| 465 | OUTPUT: |
| 466 | |
| 467 | - collection of given integral rays. |
| 468 | |
| 469 | .. WARNING:: |
| 470 | |
| 471 | No correctness check or normalization is performed on the input data. |
| 472 | This class is designed for internal operations and you probably should |
| 473 | not use it directly. |
| 474 | |
| 475 | TESTS:: |
| 476 | |
| 477 | sage: v = vector([0,1]) |
| 478 | sage: v.set_immutable() |
| 479 | sage: c = sage.geometry.cone.IntegralRayCollection([v], None) |
| 480 | sage: c.rays() |
| 481 | ((0, 1),) |
| 482 | sage: TestSuite(c).run() |
| 483 | """ |
| 484 | |
| 485 | def __init__(self, rays, lattice): |
| 486 | r""" |
| 487 | See :class:`IntegralRayCollection` for documentation. |
| 488 | |
| 489 | TESTS:: |
| 490 | |
| 491 | sage: v = vector([0,1]) |
| 492 | sage: v.set_immutable() |
| 493 | sage: sage.geometry.cone.IntegralRayCollection([v], None).rays() |
| 494 | ((0, 1),) |
| 495 | """ |
| 496 | self._rays = tuple(rays) |
| 497 | self._lattice = self._rays[0].parent() if lattice is None else lattice |
| 498 | |
| 499 | def __cmp__(self, right): |
| 500 | r""" |
| 501 | Compare ``self`` and ``right``. |
| 502 | |
| 503 | INPUT: |
| 504 | |
| 505 | - ``right`` -- anything. |
| 506 | |
| 507 | OUTPUT: |
| 508 | |
| 509 | - 0 if ``right`` is of the same type as ``self`` and their rays are |
| 510 | the same and listed in the same order. 1 or -1 otherwise. |
| 511 | |
| 512 | TESTS:: |
| 513 | |
| 514 | sage: c1 = Cone([(1,0), (0,1)]) |
| 515 | sage: c2 = Cone([(0,1), (1,0)]) |
| 516 | sage: c3 = Cone([(0,1), (1,0)]) |
| 517 | sage: cmp(c1, c2) |
| 518 | 1 |
| 519 | sage: cmp(c2, c1) |
| 520 | -1 |
| 521 | sage: cmp(c2, c3) |
| 522 | 0 |
| 523 | sage: c2 is c3 |
| 524 | False |
| 525 | sage: cmp(c1, 1) |
| 526 | -1 |
| 527 | """ |
| 528 | c = cmp(type(self), type(right)) |
| 529 | if c: |
| 530 | return c |
| 531 | return cmp((self.lattice(), self.rays()), |
| 532 | (right.lattice(), right.rays())) |
| 533 | |
| 534 | def __hash__(self): |
| 535 | r""" |
| 536 | Return the hash of ``self`` computed from rays. |
| 537 | |
| 538 | OUTPUT: |
| 539 | |
| 540 | - integer. |
| 541 | |
| 542 | TESTS:: |
| 543 | |
| 544 | sage: c = Cone([(1,0), (0,1)]) |
| 545 | sage: hash(c) # 64-bit |
| 546 | 4372618627376133801 |
| 547 | """ |
| 548 | if "_hash" not in self.__dict__: |
| 549 | self._hash = hash(self._rays) |
| 550 | return self._hash |
| 551 | |
| 552 | def __iter__(self): |
| 553 | r""" |
| 554 | Return an iterator over rays of ``self``. |
| 555 | |
| 556 | OUTPUT: |
| 557 | |
| 558 | - iterator. |
| 559 | |
| 560 | TESTS:: |
| 561 | |
| 562 | sage: c = Cone([(1,0), (0,1)]) |
| 563 | sage: for ray in c: print ray |
| 564 | N(1, 0) |
| 565 | N(0, 1) |
| 566 | """ |
| 567 | return self.ray_iterator() |
| 568 | |
| 569 | def ambient_dim(self): |
| 570 | r""" |
| 571 | Return the dimension of the ambient lattice of ``self``. |
| 572 | |
| 573 | OUTPUT: |
| 574 | |
| 575 | - integer. |
| 576 | |
| 577 | EXAMPLES:: |
| 578 | |
| 579 | sage: c = Cone([(1,0)]) |
| 580 | sage: c.ambient_dim() |
| 581 | 2 |
| 582 | sage: c.dim() |
| 583 | 1 |
| 584 | """ |
| 585 | return self.lattice().dimension() |
| 586 | |
| 587 | def dim(self): |
| 588 | r""" |
| 589 | Return the dimension of the subspace spanned by rays of ``self``. |
| 590 | |
| 591 | OUTPUT: |
| 592 | |
| 593 | - integer. |
| 594 | |
| 595 | EXAMPLES:: |
| 596 | |
| 597 | sage: c = Cone([(1,0)]) |
| 598 | sage: c.ambient_dim() |
| 599 | 2 |
| 600 | sage: c.dim() |
| 601 | 1 |
| 602 | """ |
| 603 | if "_dim" not in self.__dict__: |
| 604 | self._dim = self.ray_matrix().rank() |
| 605 | return self._dim |
| 606 | |
| 607 | def lattice(self): |
| 608 | r""" |
| 609 | Return the ambient lattice of ``self``. |
| 610 | |
| 611 | OUTPUT: |
| 612 | |
| 613 | - lattice. |
| 614 | |
| 615 | EXAMPLES:: |
| 616 | |
| 617 | sage: c = Cone([(1,0)]) |
| 618 | sage: c.lattice() |
| 619 | 2-dimensional lattice N |
| 620 | sage: Cone([], ZZ^3).lattice() |
| 621 | Ambient free module of rank 3 |
| 622 | over the principal ideal domain Integer Ring |
| 623 | """ |
| 624 | return self._lattice |
| 625 | |
| 626 | def nrays(self): |
| 627 | r""" |
| 628 | Return the number of rays of ``self``. |
| 629 | |
| 630 | OUTPUT: |
| 631 | |
| 632 | - integer. |
| 633 | |
| 634 | EXAMPLES:: |
| 635 | |
| 636 | sage: c = Cone([(1,0), (0,1)]) |
| 637 | sage: c.nrays() |
| 638 | 2 |
| 639 | """ |
| 640 | return len(self._rays) |
| 641 | |
| 642 | def ray(self, n): |
| 643 | r""" |
| 644 | Return the ``n``-th ray of ``self``. |
| 645 | |
| 646 | INPUT: |
| 647 | |
| 648 | - ``n`` -- integer, an index of a ray of ``self``. Enumeration of rays |
| 649 | starts with zero. |
| 650 | |
| 651 | OUTPUT: |
| 652 | |
| 653 | - ray, an element of the lattice of ``self``. |
| 654 | |
| 655 | EXAMPLES:: |
| 656 | |
| 657 | sage: c = Cone([(1,0), (0,1)]) |
| 658 | sage: c.ray(0) |
| 659 | N(1, 0) |
| 660 | """ |
| 661 | return self._rays[n] |
| 662 | |
| 663 | def ray_iterator(self, ray_list=None): |
| 664 | r""" |
| 665 | Return an iterator over (some of) the rays of ``self``. |
| 666 | |
| 667 | INPUT: |
| 668 | |
| 669 | - ``ray_list`` -- list of integers, the indices of the requested rays. |
| 670 | If not specified, an iterator over all rays of ``self`` will be |
| 671 | returned. |
| 672 | |
| 673 | OUTPUT: |
| 674 | |
| 675 | - iterator. |
| 676 | |
| 677 | EXAMPLES:: |
| 678 | |
| 679 | sage: c = Cone([(1,0), (0,1), (-1, 0)]) |
| 680 | sage: for ray in c.ray_iterator(): print ray |
| 681 | N(0, 1) |
| 682 | N(1, 0) |
| 683 | N(-1, 0) |
| 684 | sage: for ray in c.ray_iterator([2,1]): print ray |
| 685 | N(-1, 0) |
| 686 | N(1, 0) |
| 687 | """ |
| 688 | if ray_list is None: |
| 689 | for ray in self._rays: |
| 690 | yield ray |
| 691 | else: |
| 692 | rays = self._rays |
| 693 | for n in ray_list: |
| 694 | yield rays[n] |
| 695 | |
| 696 | def ray_matrix(self): |
| 697 | r""" |
| 698 | Return a matrix whose columns are rays of ``self``. |
| 699 | |
| 700 | It can be convenient for linear algebra operations on rays, as well as |
| 701 | for easy-to-read output. |
| 702 | |
| 703 | OUTPUT: |
| 704 | |
| 705 | - matrix. |
| 706 | |
| 707 | EXAMPLES:: |
| 708 | |
| 709 | sage: c = Cone([(1,0), (0,1), (-1, 0)]) |
| 710 | sage: c.ray_matrix() |
| 711 | [ 0 1 -1] |
| 712 | [ 1 0 0] |
| 713 | """ |
| 714 | if "_ray_matrix" not in self.__dict__: |
| 715 | self._ray_matrix = matrix(self.nrays(), self.ambient_dim(), |
| 716 | self._rays).transpose() |
| 717 | self._ray_matrix.set_immutable() |
| 718 | return self._ray_matrix |
| 719 | |
| 720 | def ray_set(self): |
| 721 | r""" |
| 722 | Return rays of ``self`` as a :class:`frozenset`. |
| 723 | |
| 724 | Use :meth:`rays` if you want to get rays in the fixed order. |
| 725 | |
| 726 | OUTPUT: |
| 727 | |
| 728 | - :class:`frozenset` of rays. |
| 729 | |
| 730 | EXAMPLES:: |
| 731 | |
| 732 | sage: c = Cone([(1,0), (0,1), (-1, 0)]) |
| 733 | sage: c.ray_set() |
| 734 | frozenset([N(0, 1), N(1, 0), N(-1, 0)]) |
| 735 | """ |
| 736 | if "_ray_set" not in self.__dict__: |
| 737 | self._ray_set = frozenset(self._rays) |
| 738 | return self._ray_set |
| 739 | |
| 740 | def rays(self, ray_list=None): |
| 741 | r""" |
| 742 | Return rays of ``self`` as a :class:`tuple`. |
| 743 | |
| 744 | INPUT: |
| 745 | |
| 746 | - ``ray_list`` -- list of integers, the indices of the requested rays. |
| 747 | If not specified, all rays of ``self`` will be returned. You may |
| 748 | want to use :meth:`ray_set` if you do not care about the order of |
| 749 | rays. See also :meth:`ray_iterator`. |
| 750 | |
| 751 | OUTPUT: |
| 752 | |
| 753 | - :class:`tuple` of rays. |
| 754 | |
| 755 | EXAMPLES:: |
| 756 | |
| 757 | sage: c = Cone([(1,0), (0,1), (-1, 0)]) |
| 758 | sage: c.rays() |
| 759 | (N(0, 1), N(1, 0), N(-1, 0)) |
| 760 | sage: c.rays([0, 2]) |
| 761 | (N(0, 1), N(-1, 0)) |
| 762 | """ |
| 763 | if ray_list is None: |
| 764 | return self._rays |
| 765 | else: |
| 766 | return tuple(self.ray_iterator(ray_list)) |
| 767 | |
| 768 | |
| 769 | class ConvexRationalPolyhedralCone(IntegralRayCollection): |
| 770 | r""" |
| 771 | Create a convex rational polyhedral cone. |
| 772 | |
| 773 | .. WARNING:: |
| 774 | |
| 775 | This class does not perform any checks of correctness of input nor |
| 776 | does it convert input into the standard representation. Use |
| 777 | :func:`Cone` to construct cones. |
| 778 | |
| 779 | Cones are immutable, but they cache most of the returned values. |
| 780 | |
| 781 | INPUT: |
| 782 | |
| 783 | - same as for :class:`IntegralRayCollection`. |
| 784 | |
| 785 | OUTPUT: |
| 786 | |
| 787 | - convex rational polyhedral cone. |
| 788 | |
| 789 | TESTS:: |
| 790 | |
| 791 | sage: v = vector([0,1]) |
| 792 | sage: v.set_immutable() |
| 793 | sage: c = sage.geometry.cone.ConvexRationalPolyhedralCone([v], None) |
| 794 | sage: c.rays() |
| 795 | ((0, 1),) |
| 796 | sage: TestSuite(c).run() |
| 797 | |
| 798 | sage: c = Cone([(0,1)]) |
| 799 | sage: TestSuite(c).run() |
| 800 | """ |
| 801 | |
| 802 | # No __init__ method, just use the base class. |
| 803 | |
| 804 | def __contains__(self, point): |
| 805 | r""" |
| 806 | Check if ``point`` is contained in ``self``. |
| 807 | |
| 808 | See :meth:`_contains` (which is called by this function) for |
| 809 | documentation. |
| 810 | |
| 811 | TESTS:: |
| 812 | |
| 813 | sage: c = Cone([(1,0), (0,1)]) |
| 814 | sage: (1,1) in c |
| 815 | True |
| 816 | sage: [1,1] in c |
| 817 | True |
| 818 | sage: (-1,0) in c |
| 819 | False |
| 820 | """ |
| 821 | return self._contains(point) |
| 822 | |
| 823 | def __getstate__(self): |
| 824 | r""" |
| 825 | Return the dictionary that should be pickled. |
| 826 | |
| 827 | OUTPUT: |
| 828 | |
| 829 | - :class:`dict`. |
| 830 | |
| 831 | TESTS:: |
| 832 | |
| 833 | sage: loads(dumps(Cone([(1,0)]))) |
| 834 | 1-dimensional cone |
| 835 | """ |
| 836 | state = copy.copy(self.__dict__) |
| 837 | state.pop("_polyhedron", None) # Polyhedron is not picklable. |
| 838 | state.pop("_lattice_polytope", None) # Just to save time and space. |
| 839 | return state |
| 840 | |
| 841 | def _contains(self, point): |
| 842 | r""" |
| 843 | Check if ``point`` is contained in ``self``. |
| 844 | |
| 845 | This function is called by :meth:`__contains__` and :meth:`contains` |
| 846 | to ensure the same call depth for warning messages. |
| 847 | |
| 848 | INPUT: |
| 849 | |
| 850 | - ``point`` -- anything. An attempt will be made to convert it into a |
| 851 | single element of the ambient space of ``self``. If it fails, |
| 852 | ``False`` will be returned. |
| 853 | |
| 854 | OUTPUT: |
| 855 | |
| 856 | - ``True`` if ``point`` is contained in ``self``, ``False`` otherwise. |
| 857 | |
| 858 | TESTS:: |
| 859 | |
| 860 | sage: c = Cone([(1,0), (0,1)]) |
| 861 | sage: c._contains((1,1)) |
| 862 | True |
| 863 | """ |
| 864 | if is_ToricLattice(parent(point)): |
| 865 | # Special treatment for elements of OTHER toric lattices |
| 866 | if point not in self.lattice(): |
| 867 | # This is due to the discussion in Trac #8986 |
| 868 | warnings.warn("you have checked if a cone contains a point " |
| 869 | "from another lattice, this is always False!", |
| 870 | stacklevel=3) |
| 871 | return False |
| 872 | else: |
| 873 | try: # to cook up a point being exact ... |
| 874 | point = self.lattice().base_extend(QQ)(point) |
| 875 | except TypeError: |
| 876 | try: # ... or at least numeric |
| 877 | point = self.lattice().base_extend(RR)(point) |
| 878 | except TypeError: |
| 879 | # Give up, input is really strange |
| 880 | return False |
| 881 | return all(n * point >= 0 for n in self.facet_normals()) |
| 882 | |
| 883 | def _latex_(self): |
| 884 | r""" |
| 885 | Return a LaTeX representation of ``self``. |
| 886 | |
| 887 | OUTPUT: |
| 888 | |
| 889 | - string. |
| 890 | |
| 891 | TESTS:: |
| 892 | |
| 893 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 894 | sage: quadrant._latex_() |
| 895 | '\\sigma^{2}' |
| 896 | """ |
| 897 | return r"\sigma^{%d}" % self.dim() |
| 898 | |
| 899 | def _repr_(self): |
| 900 | r""" |
| 901 | Return a string representation of ``self``. |
| 902 | |
| 903 | OUTPUT: |
| 904 | |
| 905 | - string. |
| 906 | |
| 907 | TESTS:: |
| 908 | |
| 909 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 910 | sage: quadrant._repr_() |
| 911 | '2-dimensional cone' |
| 912 | sage: quadrant |
| 913 | 2-dimensional cone |
| 914 | """ |
| 915 | return "%d-dimensional cone" % self.dim() |
| 916 | |
| 917 | def contains(self, *args): |
| 918 | r""" |
| 919 | Check if a given point is contained in ``self``. |
| 920 | |
| 921 | INPUT: |
| 922 | |
| 923 | - anything. An attempt will be made to convert all arguments into a |
| 924 | single element of the ambient space of ``self``. If it fails, |
| 925 | ``False`` will be returned. |
| 926 | |
| 927 | OUTPUT: |
| 928 | |
| 929 | - ``True`` if the given point is contained in ``self``, ``False`` |
| 930 | otherwise. |
| 931 | |
| 932 | EXAMPLES:: |
| 933 | |
| 934 | sage: c = Cone([(1,0), (0,1)]) |
| 935 | sage: c.contains(c.lattice()(1,0)) |
| 936 | True |
| 937 | sage: c.contains((1,0)) |
| 938 | True |
| 939 | sage: c.contains((1,1)) |
| 940 | True |
| 941 | sage: c.contains(1,1) |
| 942 | True |
| 943 | sage: c.contains((-1,0)) |
| 944 | False |
| 945 | sage: c.contains(c.lattice().dual()(1,0)) #random output (warning) |
| 946 | False |
| 947 | sage: c.contains(c.lattice().dual()(1,0)) |
| 948 | False |
| 949 | sage: c.contains(1) |
| 950 | False |
| 951 | sage: c.contains(1/2, sqrt(3)) |
| 952 | True |
| 953 | sage: c.contains(-1/2, sqrt(3)) |
| 954 | False |
| 955 | """ |
| 956 | point = flatten(args) |
| 957 | if len(point) == 1: |
| 958 | point = point[0] |
| 959 | return self._contains(point) |
| 960 | |
| 961 | def face_lattice(self): |
| 962 | r""" |
| 963 | Return the face lattice of ``self``. |
| 964 | |
| 965 | This lattice will have the origin as the bottom (we do not include the |
| 966 | empty set as a face) and this cone itself as the top. |
| 967 | |
| 968 | OUTPUT: |
| 969 | |
| 970 | - :class:`~sage.combinat.posets.posets.FinitePoset` of |
| 971 | :class:`ConeFace` objects, behaving like other cones, but also |
| 972 | containing the information about their relation to this one, namely, |
| 973 | the generating rays and containing facets. |
| 974 | |
| 975 | EXAMPLES: |
| 976 | |
| 977 | Let's take a look at the face lattice of the first quadrant:: |
| 978 | |
| 979 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 980 | sage: L = quadrant.face_lattice() |
| 981 | sage: L |
| 982 | Finite poset containing 4 elements |
| 983 | |
| 984 | To see all faces arranged by dimension, you can do this:: |
| 985 | |
| 986 | sage: for level in L.level_sets(): print level |
| 987 | [0-dimensional face of 2-dimensional cone] |
| 988 | [1-dimensional face of 2-dimensional cone, |
| 989 | 1-dimensional face of 2-dimensional cone] |
| 990 | [2-dimensional face of 2-dimensional cone] |
| 991 | |
| 992 | To work with a particular face of a particular dimension it is not |
| 993 | enough to do just :: |
| 994 | |
| 995 | sage: face = L.level_sets()[1][0] |
| 996 | sage: face |
| 997 | 1-dimensional face of 2-dimensional cone |
| 998 | sage: face.rays() |
| 999 | Traceback (most recent call last): |
| 1000 | ... |
| 1001 | AttributeError: 'PosetElement' object has no attribute 'rays' |
| 1002 | |
| 1003 | To get the actual face you need one more step:: |
| 1004 | |
| 1005 | sage: face = face.element |
| 1006 | |
| 1007 | Now you can look at the actual rays of this face... :: |
| 1008 | |
| 1009 | sage: face.rays() |
| 1010 | (N(1, 0),) |
| 1011 | |
| 1012 | ... or you can see indices of the rays of the orginal cone that |
| 1013 | correspond to the above ray:: |
| 1014 | |
| 1015 | sage: face.cone_rays() |
| 1016 | (0,) |
| 1017 | sage: quadrant.ray(0) |
| 1018 | N(1, 0) |
| 1019 | |
| 1020 | You can also get the list of facets of the original cone containing |
| 1021 | this face:: |
| 1022 | |
| 1023 | sage: face.cone_facets() |
| 1024 | (1,) |
| 1025 | |
| 1026 | An alternative to extracting faces from the face lattice is to use |
| 1027 | :meth:`faces` method:: |
| 1028 | |
| 1029 | sage: face is quadrant.faces(dim=1)[0] |
| 1030 | True |
| 1031 | |
| 1032 | The advantage of working with the face lattice directly is that you |
| 1033 | can (relatively easily) get faces that are related to the given one:: |
| 1034 | |
| 1035 | sage: face = L.level_sets()[1][0] |
| 1036 | sage: D = L.hasse_diagram() |
| 1037 | sage: D.neighbors(face) |
| 1038 | [0-dimensional face of 2-dimensional cone, |
| 1039 | 2-dimensional face of 2-dimensional cone] |
| 1040 | """ |
| 1041 | if "_face_lattice" not in self.__dict__: |
| 1042 | if not self.is_strictly_convex(): |
| 1043 | raise NotImplementedError("face lattice is currently " |
| 1044 | "implemented only for strictly convex cones!") |
| 1045 | # Should we build it from PALP complete incidences? May be faster. |
| 1046 | # On the other hand this version is also quite nice ;-) |
| 1047 | ray_to_facets = [] |
| 1048 | facet_to_rays = [] |
| 1049 | for ray in self: |
| 1050 | ray_to_facets.append([]) |
| 1051 | for normal in self.facet_normals(): |
| 1052 | facet_to_rays.append([]) |
| 1053 | for i, ray in enumerate(self): |
| 1054 | for j, normal in enumerate(self.facet_normals()): |
| 1055 | if ray * normal == 0: |
| 1056 | ray_to_facets[i].append(j) |
| 1057 | facet_to_rays[j].append(i) |
| 1058 | self._face_lattice = hasse_diagram_from_incidences( |
| 1059 | ray_to_facets, facet_to_rays, ConeFace, cone=self) |
| 1060 | return self._face_lattice |
| 1061 | |
| 1062 | def faces(self, dim=None, codim=None): |
| 1063 | r""" |
| 1064 | Return faces of ``self`` of specified (co)dimension. |
| 1065 | |
| 1066 | INPUT: |
| 1067 | |
| 1068 | - ``dim`` -- integer, dimension of the requested faces; |
| 1069 | |
| 1070 | - ``codim`` -- integer, codimension of the requested faces. |
| 1071 | |
| 1072 | .. NOTE:: |
| 1073 | |
| 1074 | You can specify at most one parameter. If you don't give any, then |
| 1075 | all faces will be returned. |
| 1076 | |
| 1077 | OUTPUT: |
| 1078 | |
| 1079 | - if either ``dim`` or ``codim`` is given, the output will be a |
| 1080 | :class:`tuple` of :class:`ConeFace` objects, behaving like other |
| 1081 | cones, but also containing the information about their relation to |
| 1082 | this one, namely, the generating rays and containing facets; |
| 1083 | |
| 1084 | - if neither ``dim`` nor ``codim`` is given, the output will be the |
| 1085 | :class:`tuple` of tuples as above, giving faces of all dimension. If |
| 1086 | you care about inclusion relations between faces, consider using |
| 1087 | :meth:`face_lattice`. |
| 1088 | |
| 1089 | EXAMPLES: |
| 1090 | |
| 1091 | Let's take a look at the faces of the first quadrant:: |
| 1092 | |
| 1093 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1094 | sage: quadrant.faces() |
| 1095 | ((0-dimensional face of 2-dimensional cone,), |
| 1096 | (1-dimensional face of 2-dimensional cone, |
| 1097 | 1-dimensional face of 2-dimensional cone), |
| 1098 | (2-dimensional face of 2-dimensional cone,)) |
| 1099 | sage: quadrant.faces(dim=1) |
| 1100 | (1-dimensional face of 2-dimensional cone, |
| 1101 | 1-dimensional face of 2-dimensional cone) |
| 1102 | sage: face = quadrant.faces(dim=1)[0] |
| 1103 | |
| 1104 | Now you can look at the actual rays of this face... :: |
| 1105 | |
| 1106 | sage: face.rays() |
| 1107 | (N(1, 0),) |
| 1108 | |
| 1109 | ... or you can see indices of the rays of the orginal cone that |
| 1110 | correspond to the above ray:: |
| 1111 | |
| 1112 | sage: face.cone_rays() |
| 1113 | (0,) |
| 1114 | sage: quadrant.ray(0) |
| 1115 | N(1, 0) |
| 1116 | |
| 1117 | You can also get the list of facets of the original cone containing |
| 1118 | this face:: |
| 1119 | |
| 1120 | sage: face.cone_facets() |
| 1121 | (1,) |
| 1122 | """ |
| 1123 | if "_faces" not in self.__dict__: |
| 1124 | self._faces = tuple(tuple(e.element |
| 1125 | for e in level) |
| 1126 | for level in self.face_lattice().level_sets()) |
| 1127 | if dim is None and codim is None: |
| 1128 | return self._faces |
| 1129 | elif dim is None: |
| 1130 | return self._faces[self.dim() - codim] |
| 1131 | elif codim is None: |
| 1132 | return self._faces[dim] |
| 1133 | raise ValueError( |
| 1134 | "dimension and codimension cannot be specified together!") |
| 1135 | |
| 1136 | def facet_normals(self): |
| 1137 | r""" |
| 1138 | Return normals to facets of ``self``. |
| 1139 | |
| 1140 | .. NOTE:: |
| 1141 | |
| 1142 | For a not full-dimensional cone facet normals will be parallel to |
| 1143 | the subspace spanned by the cone. |
| 1144 | |
| 1145 | OUTPUT: |
| 1146 | |
| 1147 | - :class:`tuple` of vectors. |
| 1148 | |
| 1149 | EXAMPLES:: |
| 1150 | |
| 1151 | sage: cone = Cone([(1,0), (-1,3)]) |
| 1152 | sage: cone.facet_normals() |
| 1153 | ((3, 1), (0, 1)) |
| 1154 | """ |
| 1155 | if "_facet_normals" not in self.__dict__: |
| 1156 | if not self.is_strictly_convex(): |
| 1157 | raise NotImplementedError("facet normals are currently " |
| 1158 | "implemented only for strictly convex cones!") |
| 1159 | lp = self.lattice_polytope() |
| 1160 | self._facet_normals = tuple(lp.facet_normal(i) |
| 1161 | for i in range(lp.nfacets()) |
| 1162 | if lp.facet_constant(i) == 0) |
| 1163 | return self._facet_normals |
| 1164 | |
| 1165 | def facets(self): |
| 1166 | r""" |
| 1167 | Return facets (faces of codimension 1) of ``self``. |
| 1168 | |
| 1169 | This function is a synonym for ``self.faces(codim=1)``. |
| 1170 | |
| 1171 | OUTPUT: |
| 1172 | |
| 1173 | - :class:`tuple` of :class:`ConeFace` objects. |
| 1174 | |
| 1175 | EXAMPLES:: |
| 1176 | |
| 1177 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1178 | sage: quadrant.facets() |
| 1179 | (1-dimensional face of 2-dimensional cone, |
| 1180 | 1-dimensional face of 2-dimensional cone) |
| 1181 | sage: quadrant.facets() is quadrant.faces(codim=1) |
| 1182 | True |
| 1183 | """ |
| 1184 | return self.faces(codim=1) |
| 1185 | |
| 1186 | def intersection(self, other): |
| 1187 | r""" |
| 1188 | Compute the intersection of two cones. |
| 1189 | |
| 1190 | INPUT: |
| 1191 | |
| 1192 | - ``other`` - cone. |
| 1193 | |
| 1194 | OUTPUT: |
| 1195 | |
| 1196 | - cone. |
| 1197 | |
| 1198 | EXAMPLES:: |
| 1199 | |
| 1200 | sage: cone1 = Cone([(1,0), (-1, 3)]) |
| 1201 | sage: cone2 = Cone([(-1,0), (2, 5)]) |
| 1202 | sage: cone1.intersection(cone2).rays() |
| 1203 | (N(2, 5), N(-1, 3)) |
| 1204 | """ |
| 1205 | return Cone(self.polyhedron().intersection(other.polyhedron()), |
| 1206 | self.lattice()) |
| 1207 | |
| 1208 | def is_equivalent(self, other): |
| 1209 | r""" |
| 1210 | Check if ``self`` is "mathematically" the same as ``other``. |
| 1211 | |
| 1212 | INPUT: |
| 1213 | |
| 1214 | - ``other`` - cone. |
| 1215 | |
| 1216 | OUTPUT: |
| 1217 | |
| 1218 | - ``True`` if ``self`` and ``other`` define the same cones as sets of |
| 1219 | points in the same lattice, ``False`` otherwise. |
| 1220 | |
| 1221 | There are three different equivalences between cones `C_1` and `C_2` |
| 1222 | in the same lattice: |
| 1223 | |
| 1224 | #. They have the same generating rays in the same order. |
| 1225 | This is tested by ``C1 == C2``. |
| 1226 | #. They describe the same sets of points. |
| 1227 | This is tested by ``C1.is_equivalent(C2)``. |
| 1228 | #. They are in the same orbit of `GL(n,\ZZ)` (and, therefore, |
| 1229 | correspond to isomorphic affine toric varieties). |
| 1230 | This is tested by ``C1.is_isomorphic(C2)``. |
| 1231 | |
| 1232 | EXAMPLES:: |
| 1233 | |
| 1234 | sage: cone1 = Cone([(1,0), (-1, 3)]) |
| 1235 | sage: cone2 = Cone([(-1,3), (1, 0)]) |
| 1236 | sage: cone1.rays() |
| 1237 | (N(1, 0), N(-1, 3)) |
| 1238 | sage: cone2.rays() |
| 1239 | (N(-1, 3), N(1, 0)) |
| 1240 | sage: cone1 == cone2 |
| 1241 | False |
| 1242 | sage: cone1.is_equivalent(cone2) |
| 1243 | True |
| 1244 | """ |
| 1245 | if self.lattice() != other.lattice(): |
| 1246 | return False |
| 1247 | if self.dim() != other.dim(): # Not necessary, but very fast |
| 1248 | return False |
| 1249 | if self.ray_set() == other.ray_set(): |
| 1250 | return True |
| 1251 | if self.is_strictly_convex() or other.is_strictly_convex(): |
| 1252 | return False # For strictly convex cones ray sets must coincide |
| 1253 | if self.linear_subspace() != other.linear_subspace(): |
| 1254 | return False |
| 1255 | return self.strict_quotient().is_equivalent(other.strict_quotient()) |
| 1256 | |
| 1257 | def is_face_of(self, cone): |
| 1258 | r""" |
| 1259 | Check if ``self`` forms a face of another ``cone``. |
| 1260 | |
| 1261 | INPUT: |
| 1262 | |
| 1263 | - ``cone`` -- cone. |
| 1264 | |
| 1265 | OUTPUT: |
| 1266 | |
| 1267 | - ``True`` if ``self`` is a face of ``cone``, ``False`` otherwise. |
| 1268 | |
| 1269 | EXAMPLES:: |
| 1270 | |
| 1271 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1272 | sage: cone1 = Cone([(1,0)]) |
| 1273 | sage: cone2 = Cone([(1,2)]) |
| 1274 | sage: quadrant.is_face_of(quadrant) |
| 1275 | True |
| 1276 | sage: cone1.is_face_of(quadrant) |
| 1277 | True |
| 1278 | sage: cone2.is_face_of(quadrant) |
| 1279 | False |
| 1280 | """ |
| 1281 | if self.lattice() != cone.lattice(): |
| 1282 | return False |
| 1283 | # Cases of the origin and the whole cone (we don't have empty cones) |
| 1284 | if self.nrays() == 0 or self.is_equivalent(cone): |
| 1285 | return True |
| 1286 | # Obviously False cases |
| 1287 | if (self.dim() >= cone.dim() # if == and face, we return True above |
| 1288 | or self.is_strictly_convex() != cone.is_strictly_convex() |
| 1289 | or self.linear_subspace() != cone.linear_subspace()): |
| 1290 | return False |
| 1291 | # Now we need to worry only about strict quotients |
| 1292 | if not self.strict_quotient().ray_set().issubset( |
| 1293 | cone.strict_quotient().ray_set()): |
| 1294 | return False |
| 1295 | # Maybe this part can be written in a more efficient way |
| 1296 | ph_self = self.strict_quotient().polyhedron() |
| 1297 | ph_cone = cone.strict_quotient().polyhedron() |
| 1298 | containing = [] |
| 1299 | for n, H in enumerate(ph_cone.Hrepresentation()): |
| 1300 | containing.append(n) |
| 1301 | for V in ph_self.Vrepresentation(): |
| 1302 | if H.eval(V) != 0: |
| 1303 | containing.pop() |
| 1304 | break |
| 1305 | m = ph_cone.incidence_matrix().matrix_from_columns(containing) |
| 1306 | # The intersection of all containing hyperplanes should contain only |
| 1307 | # rays of the potential face and the origin |
| 1308 | return map(sum, m.rows()).count(m.ncols()) == ph_self.n_rays() + 1 |
| 1309 | |
| 1310 | def is_isomorphic(self, other): |
| 1311 | r""" |
| 1312 | Check if ``self`` is in the same `GL(n, \ZZ)`-orbit as ``other``. |
| 1313 | |
| 1314 | INPUT: |
| 1315 | |
| 1316 | - ``other`` - cone. |
| 1317 | |
| 1318 | OUTPUT: |
| 1319 | |
| 1320 | - ``True`` if ``self`` and ``other`` are in the same |
| 1321 | `GL(n, \ZZ)`-orbit, ``False`` otherwise. |
| 1322 | |
| 1323 | There are three different equivalences between cones `C_1` and `C_2` |
| 1324 | in the same lattice: |
| 1325 | |
| 1326 | #. They have the same generating rays in the same order. |
| 1327 | This is tested by ``C1 == C2``. |
| 1328 | #. They describe the same sets of points. |
| 1329 | This is tested by ``C1.is_equivalent(C2)``. |
| 1330 | #. They are in the same orbit of `GL(n,\ZZ)` (and, therefore, |
| 1331 | correspond to isomorphic affine toric varieties). |
| 1332 | This is tested by ``C1.is_isomorphic(C2)``. |
| 1333 | |
| 1334 | EXAMPLES:: |
| 1335 | |
| 1336 | sage: cone1 = Cone([(1,0), (0, 3)]) |
| 1337 | sage: cone2 = Cone([(-1,3), (1, 0)]) |
| 1338 | sage: cone1.is_isomorphic(cone2) |
| 1339 | Traceback (most recent call last): |
| 1340 | ... |
| 1341 | NotImplementedError: cone isomorphism is not implemented yet! |
| 1342 | """ |
| 1343 | if self.lattice() != other.lattice(): |
| 1344 | return False |
| 1345 | raise NotImplementedError("cone isomorphism is not implemented yet!") |
| 1346 | |
| 1347 | def is_simplicial(self): |
| 1348 | r""" |
| 1349 | Check if ``self`` is simplicial. |
| 1350 | |
| 1351 | A cone is called *simplicial* if primitive vectors along its |
| 1352 | generating rays form a part of a rational basis of the ambient space. |
| 1353 | |
| 1354 | OUTPUT: |
| 1355 | |
| 1356 | - ``True`` if ``self`` is simplicial, ``False`` otherwise. |
| 1357 | |
| 1358 | EXAMPLES:: |
| 1359 | |
| 1360 | sage: cone1 = Cone([(1,0), (0, 3)]) |
| 1361 | sage: cone2 = Cone([(1,0), (0, 3), (-1,-1)]) |
| 1362 | sage: cone1.is_simplicial() |
| 1363 | True |
| 1364 | sage: cone2.is_simplicial() |
| 1365 | False |
| 1366 | """ |
| 1367 | return self.nrays() == self.dim() |
| 1368 | |
| 1369 | def is_smooth(self): |
| 1370 | r""" |
| 1371 | Check if ``self`` is smooth. |
| 1372 | |
| 1373 | A cone is called *smooth* if primitive vectors along its generating |
| 1374 | rays form a part of an *integral* basis of the ambient space. |
| 1375 | |
| 1376 | OUTPUT: |
| 1377 | |
| 1378 | - ``True`` if ``self`` is smooth, ``False`` otherwise. |
| 1379 | |
| 1380 | EXAMPLES:: |
| 1381 | |
| 1382 | sage: cone1 = Cone([(1,0), (0, 1)]) |
| 1383 | sage: cone2 = Cone([(1,0), (-1, 3)]) |
| 1384 | sage: cone1.is_smooth() |
| 1385 | True |
| 1386 | sage: cone2.is_smooth() |
| 1387 | False |
| 1388 | """ |
| 1389 | if "_is_smooth" not in self.__dict__: |
| 1390 | if not self.is_simplicial(): |
| 1391 | self._is_smooth = False |
| 1392 | else: |
| 1393 | m = self.ray_matrix() |
| 1394 | if self.dim() != self.ambient_dim(): |
| 1395 | m = m.augment(identity_matrix(self.ambient_dim())) |
| 1396 | m = m.matrix_from_columns(m.pivots()) |
| 1397 | self._is_smooth = abs(m.det()) == 1 |
| 1398 | return self._is_smooth |
| 1399 | |
| 1400 | def is_strictly_convex(self): |
| 1401 | r""" |
| 1402 | Check if ``self`` is strictly convex. |
| 1403 | |
| 1404 | A cone is called *strictly convex* if it does not contain any lines. |
| 1405 | |
| 1406 | OUTPUT: |
| 1407 | |
| 1408 | - ``True`` if ``self`` is strictly convex, ``False`` otherwise. |
| 1409 | |
| 1410 | EXAMPLES:: |
| 1411 | |
| 1412 | sage: cone1 = Cone([(1,0), (0, 1)]) |
| 1413 | sage: cone2 = Cone([(1,0), (-1, 0)]) |
| 1414 | sage: cone1.is_strictly_convex() |
| 1415 | True |
| 1416 | sage: cone2.is_strictly_convex() |
| 1417 | False |
| 1418 | """ |
| 1419 | if "_is_strictly_convex" not in self.__dict__: |
| 1420 | self._is_strictly_convex = self.polyhedron().n_lines() == 0 |
| 1421 | return self._is_strictly_convex |
| 1422 | |
| 1423 | def lattice_polytope(self): |
| 1424 | r""" |
| 1425 | Return the lattice polytope associated to ``self``. |
| 1426 | |
| 1427 | The vertices of this polytope are primitive vectors along the |
| 1428 | generating rays of ``self`` and the origin, if ``self`` is strictly |
| 1429 | convex. In this case the origin is the last vertex, so the `i`-th ray |
| 1430 | of the cone always corresponds to the `i`-th vertex of the polytope. |
| 1431 | |
| 1432 | See also :meth:`polyhedron`. |
| 1433 | |
| 1434 | OUTPUT: |
| 1435 | |
| 1436 | - :class:`LatticePolytope |
| 1437 | <sage.geometry.lattice_polytope.LatticePolytopeClass>`. |
| 1438 | |
| 1439 | EXAMPLES:: |
| 1440 | |
| 1441 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1442 | sage: lp = quadrant.lattice_polytope() |
| 1443 | sage: lp |
| 1444 | A lattice polytope: 2-dimensional, 3 vertices. |
| 1445 | sage: lp.vertices() |
| 1446 | [1 0 0] |
| 1447 | [0 1 0] |
| 1448 | |
| 1449 | sage: line = Cone([(1,0), (-1,0)]) |
| 1450 | sage: lp = line.lattice_polytope() |
| 1451 | sage: lp |
| 1452 | A lattice polytope: 1-dimensional, 2 vertices. |
| 1453 | sage: lp.vertices() |
| 1454 | [ 1 -1] |
| 1455 | [ 0 0] |
| 1456 | """ |
| 1457 | if "_lattice_polytope" not in self.__dict__: |
| 1458 | m = self.ray_matrix() |
| 1459 | if self.is_strictly_convex(): |
| 1460 | m = m.augment(matrix(ZZ, self.ambient_dim(), 1)) # the origin |
| 1461 | self._lattice_polytope = LatticePolytope(m, |
| 1462 | compute_vertices=False, copy_vertices=False) |
| 1463 | return self._lattice_polytope |
| 1464 | |
| 1465 | def line_set(self): |
| 1466 | r""" |
| 1467 | Return a set of lines generating the linear subspace of ``self``. |
| 1468 | |
| 1469 | OUTPUT: |
| 1470 | |
| 1471 | - :class:`frozenset` of primitive vectors in the lattice of ``self`` |
| 1472 | giving directions of lines that span the linear subspace of |
| 1473 | ``self``. These lines are arbitrary, but fixed. See also |
| 1474 | :meth:`lines`. |
| 1475 | |
| 1476 | EXAMPLES:: |
| 1477 | |
| 1478 | sage: halfplane = Cone([(1,0), (0,1), (-1,0)]) |
| 1479 | sage: halfplane.line_set() |
| 1480 | frozenset([N(1, 0)]) |
| 1481 | sage: fullplane = Cone([(1,0), (0,1), (-1,-1)]) |
| 1482 | sage: fullplane.line_set() |
| 1483 | frozenset([N(0, 1), N(1, 0)]) |
| 1484 | """ |
| 1485 | if "_line_set" not in self.__dict__: |
| 1486 | self._line_set = frozenset(self.lines()) |
| 1487 | return self._line_set |
| 1488 | |
| 1489 | def linear_subspace(self): |
| 1490 | r""" |
| 1491 | Return the largest linear subspace contained inside of ``self``. |
| 1492 | |
| 1493 | OUTPUT: |
| 1494 | |
| 1495 | - subspace of the ambient space of ``self``. |
| 1496 | |
| 1497 | EXAMPLES:: |
| 1498 | |
| 1499 | sage: halfplane = Cone([(1,0), (0,1), (-1,0)]) |
| 1500 | sage: halfplane.linear_subspace() |
| 1501 | Vector space of degree 2 and dimension 1 over Rational Field |
| 1502 | Basis matrix: |
| 1503 | [1 0] |
| 1504 | """ |
| 1505 | if "_linear_subspace" not in self.__dict__: |
| 1506 | if self.is_strictly_convex(): |
| 1507 | self._linear_subspace = span([vector(QQ, self.ambient_dim())], |
| 1508 | QQ) |
| 1509 | else: |
| 1510 | self._linear_subspace = span(self.lines(), QQ) |
| 1511 | return self._linear_subspace |
| 1512 | |
| 1513 | def lines(self): |
| 1514 | r""" |
| 1515 | Return lines generating the linear subspace of ``self``. |
| 1516 | |
| 1517 | OUTPUT: |
| 1518 | |
| 1519 | - :class:`tuple` of primitive vectors in the lattice of ``self`` |
| 1520 | giving directions of lines that span the linear subspace of |
| 1521 | ``self``. These lines are arbitrary, but fixed. If you do not care |
| 1522 | about the order, see also :meth:`line_set`. |
| 1523 | |
| 1524 | EXAMPLES:: |
| 1525 | |
| 1526 | sage: halfplane = Cone([(1,0), (0,1), (-1,0)]) |
| 1527 | sage: halfplane.lines() |
| 1528 | (N(1, 0),) |
| 1529 | sage: fullplane = Cone([(1,0), (0,1), (-1,-1)]) |
| 1530 | sage: fullplane.lines() |
| 1531 | (N(1, 0), N(0, 1)) |
| 1532 | """ |
| 1533 | if "_lines" not in self.__dict__: |
| 1534 | if self.is_strictly_convex(): |
| 1535 | self._lines = () |
| 1536 | else: |
| 1537 | self._lines = tuple(normalize_rays(self.polyhedron().lines(), |
| 1538 | self.lattice())) |
| 1539 | return self._lines |
| 1540 | |
| 1541 | def polyhedron(self): |
| 1542 | r""" |
| 1543 | Return the polyhedron associated to ``self``. |
| 1544 | |
| 1545 | Mathematically this polyhedron is the same as ``self``. |
| 1546 | |
| 1547 | See also :meth:`lattice_polytope`. |
| 1548 | |
| 1549 | OUTPUT: |
| 1550 | |
| 1551 | - :class:`~sage.geometry.polyhedra.Polyhedron`. |
| 1552 | |
| 1553 | EXAMPLES:: |
| 1554 | |
| 1555 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1556 | sage: ph = quadrant.polyhedron() |
| 1557 | sage: ph |
| 1558 | A 2-dimensional polyhedron in QQ^2 defined as the convex hull |
| 1559 | of 1 vertex and 2 rays. |
| 1560 | sage: line = Cone([(1,0), (-1,0)]) |
| 1561 | sage: ph = line.polyhedron() |
| 1562 | sage: ph |
| 1563 | A 1-dimensional polyhedron in QQ^2 defined as the convex hull |
| 1564 | of 1 vertex and 1 line. |
| 1565 | """ |
| 1566 | if "_polyhedron" not in self.__dict__: |
| 1567 | self._polyhedron = Polyhedron(rays=self.rays()) |
| 1568 | return self._polyhedron |
| 1569 | |
| 1570 | def strict_quotient(self): |
| 1571 | r""" |
| 1572 | Return the quotient of ``self`` by the linear subspace. |
| 1573 | |
| 1574 | We define the **strict quotient** of a cone to be the image of this |
| 1575 | cone in the quotient of the ambient space by the linear subspace of |
| 1576 | the cone, i.e. it is the "complementary part" to the linear subspace. |
| 1577 | |
| 1578 | OUTPUT: |
| 1579 | |
| 1580 | - cone. |
| 1581 | |
| 1582 | EXAMPLES:: |
| 1583 | |
| 1584 | sage: halfplane = Cone([(1,0), (0,1), (-1,0)]) |
| 1585 | sage: ssc = halfplane.strict_quotient() |
| 1586 | sage: ssc |
| 1587 | 1-dimensional cone |
| 1588 | sage: ssc.rays() |
| 1589 | (N(1),) |
| 1590 | sage: line = Cone([(1,0), (-1,0)]) |
| 1591 | sage: ssc = line.strict_quotient() |
| 1592 | sage: ssc |
| 1593 | 0-dimensional cone |
| 1594 | sage: ssc.rays() |
| 1595 | () |
| 1596 | """ |
| 1597 | if "_strict_quotient" not in self.__dict__: |
| 1598 | if self.is_strictly_convex(): |
| 1599 | self._strict_quotient = self |
| 1600 | else: |
| 1601 | L = self.lattice() |
| 1602 | Q = L.base_extend(QQ) / self.linear_subspace() |
| 1603 | # Maybe we can improve this one if we create something special |
| 1604 | # for sublattices. But it seems to be the most natural choice |
| 1605 | # for names. If many subcones land in the same lattice - |
| 1606 | # that's just how it goes. |
| 1607 | if is_ToricLattice(L): |
| 1608 | S = ToricLattice(Q.dimension(), L._name, L._dual_name, |
| 1609 | L._latex_name, L._latex_dual_name) |
| 1610 | else: |
| 1611 | S = ZZ**Q.dimension() |
| 1612 | rays = [Q(ray) for ray in self.rays() if not Q(ray).is_zero()] |
| 1613 | quotient = Cone(rays, S, check=False) |
| 1614 | quotient._is_strictly_convex = True |
| 1615 | self._strict_quotient = quotient |
| 1616 | return self._strict_quotient |
| 1617 | |
| 1618 | |
| 1619 | class ConeFace(ConvexRationalPolyhedralCone): |
| 1620 | r""" |
| 1621 | Constuct a face of a cone (which is again a cone). |
| 1622 | |
| 1623 | This class provides access to the containing cone and face incidence |
| 1624 | information. |
| 1625 | |
| 1626 | .. WARNING:: |
| 1627 | |
| 1628 | This class will sort the incidence information, but it does not check |
| 1629 | that the input defines a valid face. You should not construct objects |
| 1630 | of this class directly. |
| 1631 | |
| 1632 | INPUT: |
| 1633 | |
| 1634 | - ``cone_rays`` -- indices of rays of ``cone`` contained in this face; |
| 1635 | |
| 1636 | - ``cone_facets`` -- indices of facets of ``cone`` containing this face; |
| 1637 | |
| 1638 | - ``cone`` -- cone whose face is constructed. |
| 1639 | |
| 1640 | OUTPUT: |
| 1641 | |
| 1642 | - face of ``cone``. |
| 1643 | |
| 1644 | TESTS: |
| 1645 | |
| 1646 | The following code is likely to construct an invalid face, we just test |
| 1647 | that face creation is working:: |
| 1648 | |
| 1649 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1650 | sage: face = sage.geometry.cone.ConeFace([0], [0], quadrant) |
| 1651 | sage: face |
| 1652 | 1-dimensional face of 2-dimensional cone |
| 1653 | sage: TestSuite(face).run() |
| 1654 | |
| 1655 | The intended way to get objects of this class is the following:: |
| 1656 | |
| 1657 | sage: face = quadrant.facets()[0] |
| 1658 | sage: face |
| 1659 | 1-dimensional face of 2-dimensional cone |
| 1660 | sage: face.cone_rays() |
| 1661 | (0,) |
| 1662 | sage: face.cone_facets() |
| 1663 | (1,) |
| 1664 | """ |
| 1665 | |
| 1666 | def __init__(self, cone_rays, cone_facets, cone): |
| 1667 | r""" |
| 1668 | See :class:`ConeFace` for documentation. |
| 1669 | |
| 1670 | TESTS:: |
| 1671 | |
| 1672 | The following code is likely to construct an invalid face, we just |
| 1673 | test that face creation is working:: |
| 1674 | |
| 1675 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1676 | sage: face = sage.geometry.cone.ConeFace([0], [0], quadrant) |
| 1677 | sage: face |
| 1678 | 1-dimensional face of 2-dimensional cone |
| 1679 | """ |
| 1680 | self._cone_rays = tuple(sorted(cone_rays)) |
| 1681 | self._cone_facets = tuple(sorted(cone_facets)) |
| 1682 | self._cone = cone |
| 1683 | super(ConeFace, self).__init__(cone.rays(self._cone_rays), |
| 1684 | cone.lattice()) |
| 1685 | |
| 1686 | def _latex_(self): |
| 1687 | r""" |
| 1688 | Return a LaTeX representation of ``self``. |
| 1689 | |
| 1690 | OUTPUT: |
| 1691 | |
| 1692 | - string. |
| 1693 | |
| 1694 | TESTS:: |
| 1695 | |
| 1696 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1697 | sage: face = quadrant.facets()[0] |
| 1698 | sage: face._latex_() |
| 1699 | '\\sigma^{1} \\subset \\sigma^{2}' |
| 1700 | """ |
| 1701 | return r"%s \subset %s" % (super(ConeFace, self)._latex_(), |
| 1702 | self.cone()._latex_()) |
| 1703 | |
| 1704 | def _repr_(self): |
| 1705 | r""" |
| 1706 | Return a string representation of ``self``. |
| 1707 | |
| 1708 | OUTPUT: |
| 1709 | |
| 1710 | - string. |
| 1711 | |
| 1712 | TESTS:: |
| 1713 | |
| 1714 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1715 | sage: face = quadrant.facets()[0] |
| 1716 | sage: face |
| 1717 | 1-dimensional face of 2-dimensional cone |
| 1718 | sage: face._repr_() |
| 1719 | '1-dimensional face of 2-dimensional cone' |
| 1720 | """ |
| 1721 | return "%d-dimensional face of %s" % (self.dim(), self.cone()) |
| 1722 | |
| 1723 | def cone(self): |
| 1724 | r""" |
| 1725 | Return the "ambient cone", i.e. the cone whose face ``self`` is. |
| 1726 | |
| 1727 | OUTPUT: |
| 1728 | |
| 1729 | - cone. |
| 1730 | |
| 1731 | EXAMPLES:: |
| 1732 | |
| 1733 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1734 | sage: face = quadrant.facets()[0] |
| 1735 | sage: face.cone() is quadrant |
| 1736 | True |
| 1737 | """ |
| 1738 | return self._cone |
| 1739 | |
| 1740 | def cone_facets(self): |
| 1741 | r""" |
| 1742 | Return indices of facets of the "ambient cone" containing ``self``. |
| 1743 | |
| 1744 | OUTPUT: |
| 1745 | |
| 1746 | - increasing :class:`tuple` of integers. |
| 1747 | |
| 1748 | EXAMPLES:: |
| 1749 | |
| 1750 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1751 | sage: face = quadrant.faces(dim=0)[0] |
| 1752 | sage: face.cone_facets() |
| 1753 | (0, 1) |
| 1754 | """ |
| 1755 | return self._cone_facets |
| 1756 | |
| 1757 | def cone_rays(self): |
| 1758 | r""" |
| 1759 | Return indices of rays of the "ambient cone" contained in ``self``. |
| 1760 | |
| 1761 | OUTPUT: |
| 1762 | |
| 1763 | - increasing :class:`tuple` of integers. |
| 1764 | |
| 1765 | EXAMPLES:: |
| 1766 | |
| 1767 | sage: quadrant = Cone([(1,0), (0,1)]) |
| 1768 | sage: face = quadrant.faces(dim=2)[0] |
| 1769 | sage: face.cone_rays() |
| 1770 | (0, 1) |
| 1771 | """ |
| 1772 | return self._cone_rays |
| 1773 | |
| 1774 | |
| 1775 | def hasse_diagram_from_incidences(atom_to_coatoms, coatom_to_atoms, |
| 1776 | face_constructor=None, **kwds): |
| 1777 | r""" |
| 1778 | Compute the Hasse diagram of an atomic and coatomic lattice. |
| 1779 | |
| 1780 | INPUT: |
| 1781 | |
| 1782 | - ``atom_to_coatoms`` - list, ``atom_to_coatom[i]`` should list all |
| 1783 | coatoms over the ``i``-th atom; |
| 1784 | |
| 1785 | - ``coatom_to_atoms`` - list, ``coatom_to_atom[i]`` should list all |
| 1786 | atoms under the ``i``-th coatom; |
| 1787 | |
| 1788 | - ``face_constructor`` - function or class taking as the first two |
| 1789 | arguments :class:`frozenset` and any number of keyword arguments. It |
| 1790 | will be called to construct a face over atoms passed as the first |
| 1791 | argument and under coatoms passed as the second argument; |
| 1792 | |
| 1793 | - all other keyword arguments will be passed to ``face_constructor`` on |
| 1794 | each call. |
| 1795 | |
| 1796 | OUTPUT: |
| 1797 | |
| 1798 | - :class:`~sage.combinat.posets.posets.FinitePoset` with elements |
| 1799 | constructed by ``face_constructor``. |
| 1800 | |
| 1801 | ALGORITHM: |
| 1802 | |
| 1803 | The detailed description of the used algorithm is given in [KP2002]_. |
| 1804 | |
| 1805 | The code of this function follows the pseudocode description in the |
| 1806 | section 2.5 of the paper, although it is mostly based on frozen sets |
| 1807 | instead of sorted lists - this makes the implementation easier and should |
| 1808 | not cost a big performance penalty. (If one wants to make this function |
| 1809 | faster, it should be probably written in Cython.) |
| 1810 | |
| 1811 | While the title of the paper mentions only polytopes, the algorithm (and |
| 1812 | the implementation provided here) is applicable to any atomic and coatomic |
| 1813 | lattice if both incidences are given, see Section 3.4. |
| 1814 | |
| 1815 | In particular, this function can be used for cones and complete fans. |
| 1816 | |
| 1817 | REFERENCES: |
| 1818 | |
| 1819 | .. [KP2002] |
| 1820 | Volker Kaibel and Marc E. Pfetsch, |
| 1821 | "Computing the Face Lattice of a Polytope from its Vertex-Facet |
| 1822 | Incidences", Computational Geometry: Theory and Applications, |
| 1823 | Volume 23, Issue 3 (November 2002), 281-290. |
| 1824 | Available at http://portal.acm.org/citation.cfm?id=763203 |
| 1825 | |
| 1826 | AUTHORS: |
| 1827 | |
| 1828 | - Andrey Novoseltsev (2010-05-13) with thanks to Marshall Hampton for the |
| 1829 | reference. |
| 1830 | |
| 1831 | EXAMPLES: |
| 1832 | |
| 1833 | Let's construct the Hasse diagram of a lattice of subsets of {0, 1, 2}. |
| 1834 | Our atoms are {0}, {1}, and {2}, while our coatoms are {0,1}, {0,2}, and |
| 1835 | {1,2}. Then incidences are :: |
| 1836 | |
| 1837 | sage: atom_to_coatoms = [(0,1), (0,2), (1,2)] |
| 1838 | sage: coatom_to_atoms = [(0,1), (0,2), (1,2)] |
| 1839 | |
| 1840 | Let's store elements of the lattice as pairs of sorted tuples:: |
| 1841 | |
| 1842 | sage: face_constructor = \ |
| 1843 | ... lambda a, b: (tuple(sorted(a)), tuple(sorted(b))) |
| 1844 | |
| 1845 | Then we can compute the Hasse diagram as :: |
| 1846 | |
| 1847 | sage: L = sage.geometry.cone.hasse_diagram_from_incidences( |
| 1848 | ... atom_to_coatoms, coatom_to_atoms, face_constructor) |
| 1849 | sage: L |
| 1850 | Finite poset containing 8 elements |
| 1851 | sage: for level in L.level_sets(): print level |
| 1852 | [((), (0, 1, 2))] |
| 1853 | [((0,), (0, 1)), ((1,), (0, 2)), ((2,), (1, 2))] |
| 1854 | [((0, 1), (0,)), ((0, 2), (1,)), ((1, 2), (2,))] |
| 1855 | [((0, 1, 2), ())] |
| 1856 | |
| 1857 | For more involved examples see the source code of |
| 1858 | :meth:`~ConvexRationalPolyhedralCone.face_lattice` and |
| 1859 | :meth:`~RationalPolyhedralFan.cone_lattice`. |
| 1860 | """ |
| 1861 | def default_face_constructor(atoms, coatoms, **kwds): |
| 1862 | return (atoms, coatoms) |
| 1863 | if face_constructor is None: |
| 1864 | face_constructor = default_face_constructor |
| 1865 | |
| 1866 | atom_to_coatoms = [frozenset(atc) for atc in atom_to_coatoms] |
| 1867 | A = frozenset(range(len(atom_to_coatoms))) # All atoms |
| 1868 | coatom_to_atoms = [frozenset(cta) for cta in coatom_to_atoms] |
| 1869 | C = frozenset(range(len(coatom_to_atoms))) # All coatoms |
| 1870 | # Comments with numbers correspond to steps in Section 2.5 of the article |
| 1871 | L = DiGraph() # 3: initialize L |
| 1872 | faces = dict() |
| 1873 | atoms = frozenset() |
| 1874 | coatoms = C |
| 1875 | faces[atoms, coatoms] = 0 |
| 1876 | next_index = 1 |
| 1877 | Q = [(atoms, coatoms)] # 4: initialize Q with the empty face |
| 1878 | while Q: # 5 |
| 1879 | q_atoms, q_coatoms = Q.pop() # 6: remove some q from Q |
| 1880 | q = faces[q_atoms, q_coatoms] |
| 1881 | # 7: compute H = {closure(q+atom) : atom not in atoms of q} |
| 1882 | H = dict() |
| 1883 | candidates = set(A.difference(q_atoms)) |
| 1884 | for atom in candidates: |
| 1885 | coatoms = q_coatoms.intersection(atom_to_coatoms[atom]) |
| 1886 | atoms = A |
| 1887 | for coatom in coatoms: |
| 1888 | atoms = atoms.intersection(coatom_to_atoms[coatom]) |
| 1889 | H[atom] = (atoms, coatoms) |
| 1890 | # 8: compute the set G of minimal sets in H |
| 1891 | minimals = set([]) |
| 1892 | while candidates: |
| 1893 | candidate = candidates.pop() |
| 1894 | atoms = H[candidate][0] |
| 1895 | if atoms.isdisjoint(candidates) and atoms.isdisjoint(minimals): |
| 1896 | minimals.add(candidate) |
| 1897 | # Now G == {H[atom] : atom in minimals} |
| 1898 | for atom in minimals: # 9: for g in G: |
| 1899 | g_atoms, g_coatoms = H[atom] |
| 1900 | if (g_atoms, g_coatoms) in faces: |
| 1901 | g = faces[g_atoms, g_coatoms] |
| 1902 | else: # 11: if g was newly created |
| 1903 | g = next_index |
| 1904 | faces[g_atoms, g_coatoms] = g |
| 1905 | next_index += 1 |
| 1906 | Q.append((g_atoms, g_coatoms)) # 12 |
| 1907 | L.add_edge(q, g) # 14 |
| 1908 | # End of algorithm, now construct a FinitePoset |
| 1909 | # In principle, it is recommended to use Poset or in this case perhaps |
| 1910 | # even LatticePoset, but it seems to take several times more time |
| 1911 | # than the above computation, makes unnecessary copies, and crashes. |
| 1912 | # So for now we will mimick the relevant code from Poset. |
| 1913 | # Relabel vertices so {0,...,n} becomes a linear extension of the poset. |
| 1914 | labels = dict() |
| 1915 | for new, old in enumerate(L.topological_sort()): |
| 1916 | labels[old] = new |
| 1917 | L.relabel(labels) |
| 1918 | # Set element labels. |
| 1919 | elements = [None] * next_index |
| 1920 | for face, index in faces.items(): |
| 1921 | elements[labels[index]] = face_constructor(*face, **kwds) |
| 1922 | return FinitePoset(L, elements) |