Ticket #10336: trac_10336_plot_non_strictly_convex_cones.patch
File trac_10336_plot_non_strictly_convex_cones.patch, 19.9 KB (added by , 12 years ago) |
---|
-
sage/geometry/cone.py
# HG changeset patch # User Andrey Novoseltsev <novoselt@gmail.com> # Date 1290975403 25200 # Node ID 2572cc23715e5f6f229844c4ee398a16777b43ce # Parent 3e3e79e371f99a8ba6d9d3417c71584cf0560d47 Trac 10336: Allow plotting of non-strictly convex cones. This patch also adds computation of the face lattice for non-strictly convex cones and improves arrow scaling for plots of cones and fans. diff -r 3e3e79e371f9 -r 2572cc23715e sage/geometry/cone.py
a b 1760 1760 """ 1761 1761 if "_face_lattice" not in self.__dict__: 1762 1762 if self._ambient is self: 1763 # We need to compute face lattice on our own 1764 if not self.is_strictly_convex(): 1765 raise NotImplementedError("face lattice is currently " 1766 "implemented only for strictly convex cones!") 1767 def ConeFace(rays, facets): 1763 # We need to compute face lattice on our own. To accommodate 1764 # non-strictly convex cones we split rays (or rather their 1765 # indicies) into those in the linear subspace and others, which 1766 # we refer to as atoms. 1767 S = self.linear_subspace() 1768 subspace_rays = [] 1769 atom_to_ray = [] 1770 atom_to_facets = [] 1771 normals = self.facet_normals() 1772 facet_to_atoms = [[] for normal in normals] 1773 for i, ray in enumerate(self): 1774 if ray in S: 1775 subspace_rays.append(i) 1776 else: 1777 facets = [j for j, normal in enumerate(normals) 1778 if ray * normal == 0] 1779 atom_to_facets.append(facets) 1780 atom = len(atom_to_ray) 1781 for j in facets: 1782 facet_to_atoms[j].append(atom) 1783 atom_to_ray.append(i) 1784 1785 def ConeFace(atoms, facets): 1768 1786 if facets: 1787 rays = sorted([atom_to_ray[a] for a in atoms] 1788 + subspace_rays) 1769 1789 face = ConvexRationalPolyhedralCone( 1770 1790 ambient=self, ambient_ray_indices=rays) 1771 1791 # It may be nice if this functionality is exposed, … … 1775 1795 return face 1776 1796 else: 1777 1797 return self 1778 ray_to_facets = [] 1779 facet_to_rays = [] 1780 for ray in self: 1781 ray_to_facets.append([]) 1782 for normal in self.facet_normals(): 1783 facet_to_rays.append([]) 1784 for i, ray in enumerate(self): 1785 for j, normal in enumerate(self.facet_normals()): 1786 if ray * normal == 0: 1787 ray_to_facets[i].append(j) 1788 facet_to_rays[j].append(i) 1798 1789 1799 self._face_lattice = Hasse_diagram_from_incidences( 1790 ray_to_facets, facet_to_rays, ConeFace)1800 atom_to_facets, facet_to_atoms, ConeFace) 1791 1801 else: 1792 1802 # Get face lattice as a sublattice of the ambient one 1793 1803 allowed_indices = frozenset(self._ambient_ray_indices) … … 1852 1862 :class:`tuple` of :class:`cones <ConvexRationalPolyhedralCone>`; 1853 1863 1854 1864 - if neither ``dim`` nor ``codim`` is given, the output will be the 1855 :class:`tuple` of tuples as above, giving faces of all dimension. If1856 you care about inclusion relations between faces, consider using1857 :meth:`face_lattice` or :meth:`adjacent`, :meth:`facet_of`, and1858 :meth:`facet s`.1859 1865 :class:`tuple` of tuples as above, giving faces of all existing 1866 dimensions. If you care about inclusion relations between faces, 1867 consider using :meth:`face_lattice` or :meth:`adjacent`, 1868 :meth:`facet_of`, and :meth:`facets`. 1869 1860 1870 EXAMPLES: 1861 1871 1862 1872 Let's take a look at the faces of the first quadrant:: … … 1885 1895 sage: quadrant.ray(0) 1886 1896 N(1, 0) 1887 1897 1898 Note that it is OK to ask for faces of too small or high dimension:: 1899 1900 sage: quadrant.faces(-1) 1901 () 1902 sage: quadrant.faces(3) 1903 () 1904 1905 In the case of non-strictly convex cones even faces of small 1906 non-negative dimension may be missing:: 1907 1908 sage: halfplane = Cone([(1,0), (0,1), (-1,0)]) 1909 sage: halfplane.faces(0) 1910 () 1911 sage: halfplane.faces() 1912 ((1-d face of 2-d cone in 2-d lattice N,), 1913 (2-d cone in 2-d lattice N,)) 1914 sage: plane = Cone([(1,0), (0,1), (-1,-1)]) 1915 sage: plane.faces(1) 1916 () 1917 sage: plane.faces() 1918 ((2-d cone in 2-d lattice N,),) 1919 1888 1920 TESTS: 1889 1921 1890 1922 Now we check that "general" cones whose dimension is smaller than the … … 1918 1950 raise ValueError( 1919 1951 "dimension and codimension cannot be specified together!") 1920 1952 dim = self.dim() - codim if codim is not None else dim 1921 if dim is not None and (dim < 0 or dim > self.dim()):1922 raise ValueError("%s does not have faces of dimension %d!"1923 % (self, dim))1924 1953 if "_faces" not in self.__dict__: 1925 1954 self._faces = tuple(self._sort_faces(e.element for e in level) 1926 1955 for level in self.face_lattice().level_sets()) 1927 1956 # To avoid duplication and ensure order consistency 1928 if self.dim() > 0:1957 if len(self._faces) > 1: 1929 1958 self._facets = self._faces[-2] 1930 1959 if dim is None: 1931 1960 return self._faces 1932 1961 else: 1933 return self._faces[dim] 1962 lsd = self.linear_subspace().dimension() 1963 return self._faces[dim - lsd] if lsd <= dim <= self.dim() else () 1934 1964 1935 1965 def facet_normals(self): 1936 1966 r""" … … 2206 2236 if self._ambient is cone._ambient: 2207 2237 # Cones are always faces of their ambient structure, so 2208 2238 return self.ray_set().issubset(cone.ray_set()) 2209 # Cases of the origin and the whole cone (we don't have empty cones) 2210 if self.nrays() == 0 or self.is_equivalent(cone): 2239 if self.is_equivalent(cone): 2211 2240 return True 2212 2241 # Obviously False cases 2213 2242 if (self.dim() >= cone.dim() # if == and face, we return True above … … 2518 2547 # Modify ray labels to match the ambient cone or fan. 2519 2548 tp.ray_label = label_list(tp.ray_label, self.nrays(), deg <= 2, 2520 2549 self.ambient_ray_indices()) 2521 result = tp.plot_lattice() + tp.plot_rays() + tp.plot_generators() 2522 if self.dim() >= 2: 2523 # Modify wall labels to match the ambient cone or fan too. 2524 walls = self.faces(2) 2525 try: 2526 ambient_walls = self.ambient().faces(2) 2527 except AttributeError: 2528 ambient_walls = self.ambient().cones(2) 2529 tp.wall_label = label_list(tp.wall_label, len(walls), deg <= 2, 2530 [ambient_walls.index(wall) for wall in walls]) 2531 tp.set_rays(self.ambient().rays()) 2532 result += tp.plot_walls(walls) 2550 result = tp.plot_lattice() + tp.plot_generators() 2551 # To deal with non-strictly convex cones we separate rays and labels. 2552 result += tp.plot_ray_labels() 2553 tp.ray_label = None 2554 lsd = self.linear_subspace().dimension() 2555 if lsd == 1: 2556 # Plot only rays of the line 2557 v = self.lines()[0] 2558 tp.set_rays([v, -v]) 2559 if lsd <= 1: 2560 result += tp.plot_rays() 2561 # Modify wall labels to match the ambient cone or fan too. 2562 walls = self.faces(2) 2563 try: 2564 ambient_walls = self.ambient().faces(2) 2565 except AttributeError: 2566 ambient_walls = self.ambient().cones(2) 2567 tp.wall_label = label_list(tp.wall_label, len(walls), deg <= 2, 2568 [ambient_walls.index(wall) for wall in walls]) 2569 tp.set_rays(self.ambient().rays()) 2570 result += tp.plot_walls(walls) 2533 2571 return result 2534 2572 2535 2573 def polyhedron(self): -
sage/geometry/fan.py
diff -r 3e3e79e371f9 -r 2572cc23715e sage/geometry/fan.py
a b 1630 1630 1631 1631 - :class:`tuple` of cones of ``self`` of the specified (co)dimension, 1632 1632 if either ``dim`` or ``codim`` is given. Otherwise :class:`tuple` of 1633 such tuples for all dimensions.1633 such tuples for all existing dimensions. 1634 1634 1635 1635 EXAMPLES:: 1636 1636 … … 1647 1647 N(-1, 0) 1648 1648 sage: fan.cones(2) 1649 1649 (2-d cone of Rational polyhedral fan in 2-d lattice N,) 1650 1651 You cannot specify both dimension and codimension, even if they 1652 "agree":: 1653 1650 1654 sage: fan(dim=1, codim=1) 1651 1655 Traceback (most recent call last): 1652 1656 ... 1653 1657 ValueError: dimension and codimension 1654 1658 cannot be specified together! 1659 1660 But it is OK to ask for cones of too high or low (co)dimension:: 1661 1662 sage: fan(-1) 1663 () 1664 sage: fan(3) 1665 () 1666 sage: fan(codim=4) 1667 () 1655 1668 """ 1656 1669 if "_cones" not in self.__dict__: 1657 1670 levels = [(e.element for e in level) # Generators … … 1674 1687 rays = list(levels[1]) 1675 1688 rays.sort(key=lambda cone: cone.ambient_ray_indices()[0]) 1676 1689 levels[1] = rays 1677 self._cones = tuple(tuple(level) for level in levels) 1678 if dim is None and codim is None: 1679 return self._cones 1680 elif dim is None: 1681 return self._cones[self.dim() - codim] 1682 elif codim is None: 1683 return self._cones[dim] 1684 raise ValueError( 1690 self._cones = tuple(tuple(level) for level in levels) 1691 if dim is None: 1692 if codim is None: 1693 return self._cones 1694 dim = self.dim() - codim 1695 elif codim is not None: 1696 raise ValueError( 1685 1697 "dimension and codimension cannot be specified together!") 1698 return self._cones[dim] if 0 <= dim < len(self._cones) else () 1686 1699 1687 1700 def contains(self, cone): 1688 1701 r""" -
sage/geometry/polyhedra.py
diff -r 3e3e79e371f9 -r 2572cc23715e sage/geometry/polyhedra.py
a b 6570 6570 coatom_to_atoms = [frozenset(cta) for cta in coatom_to_atoms] 6571 6571 C = frozenset(range(len(coatom_to_atoms))) # All coatoms 6572 6572 # Comments with numbers correspond to steps in Section 2.5 of the article 6573 L = DiGraph( ) # 3: initialize L6573 L = DiGraph(1) # 3: initialize L 6574 6574 faces = dict() 6575 6575 atoms = frozenset() 6576 6576 coatoms = C -
sage/geometry/toric_plotter.py
diff -r 3e3e79e371f9 -r 2572cc23715e sage/geometry/toric_plotter.py
a b 56 56 from sage.plot.all import (Color, Graphics, 57 57 arrow, disk, line, point, polygon, rainbow, text) 58 58 from sage.plot.plot3d.all import text3d 59 from sage.rings.all import RDF , RR59 from sage.rings.all import RDF 60 60 from sage.structure.sage_object import SageObject 61 61 62 62 … … 213 213 raise ValueError("toric objects can be plotted only for " 214 214 "dimensions 1, 2, and 3, not %s!" % dimension) 215 215 self.dimension = dimension 216 self.origin = vector(R R, max(dimension, 2)) # 1-d is plotted in 2-d216 self.origin = vector(RDF, max(dimension, 2)) # 1-d is plotted in 2-d 217 217 if self.mode not in ["box", "generators", "round"]: 218 218 raise ValueError("unrecognized plotting mode: %s!" % mode) 219 # If radius was explicitly set by the user, it sets other bounds too. 220 # If we don't take it into account here, they will be replaced by 221 # automatically computed values. 222 if sd["radius"] is not None: 223 for key in ["xmin", "ymin", "zmin"]: 224 if sd[key] is None: 225 sd[key] = - sd["radius"] 226 for key in ["xmax", "ymax", "zmax"]: 227 if sd[key] is None: 228 sd[key] = sd["radius"] 219 229 # We also set some of the "extra_options" if they were not given. 220 230 if "axes" not in extra_options: 221 231 extra_options["axes"] = False … … 288 298 sd = self.__dict__ 289 299 bounds = ["radius", "xmin", "xmax", "ymin", "ymax", "zmin", "zmax"] 290 300 bounds = [abs(sd[bound]) for bound in bounds if sd[bound] is not None] 291 r = R R(max(bounds + [0.5]) if bounds else 2.5)301 r = RDF(max(bounds + [0.5]) if bounds else 2.5) 292 302 self.radius = r 293 303 round = self.mode == "round" 294 304 for key in ["xmin", "ymin", "zmin"]: … … 296 306 sd[key] = - r 297 307 if sd[key] > - 0.5: 298 308 sd[key] = - 0.5 299 sd[key] = R R(sd[key])309 sd[key] = RDF(sd[key]) 300 310 for key in ["xmax", "ymax", "zmax"]: 301 311 if round or sd[key] is None: 302 312 sd[key] = r 303 313 if sd[key] < 0.5: 304 314 sd[key] = 0.5 305 sd[key] = R R(sd[key])315 sd[key] = RDF(sd[key]) 306 316 if self.show_lattice is None: 307 317 self.show_lattice = (r <= 5) if d <= 2 else r <= 3 308 318 … … 340 350 """ 341 351 if not points: 342 352 return 343 points = [vector(R R, pt) for pt in points]353 points = [vector(RDF, pt) for pt in points] 344 354 sd = self.__dict__ 345 355 346 356 def update(bound, new_value, points): … … 402 412 if d <= 2: 403 413 result += arrow(origin, generator, 404 414 color=color, width=thickness, 415 arrowsize=thickness + 1, 405 416 zorder=zorder, **extra_options) 406 417 else: 407 418 result += line([origin, generator], arrow_head=True, … … 510 521 return point(points, color=self.point_color, size=self.point_size, 511 522 zorder=self.point_zorder, **self.extra_options) 512 523 524 def plot_ray_labels(self): 525 r""" 526 Plot ray labels. 527 528 Usually ray labels are plotted together with rays, but in some cases it 529 is desirable to output them separately. 530 531 Ray generators must be specified during construction or using 532 :meth:`set_rays` before calling this method. 533 534 OUTPUT: 535 536 - a plot. 537 538 EXAMPLES:: 539 540 sage: from sage.geometry.toric_plotter import ToricPlotter 541 sage: tp = ToricPlotter(dict(), 2, [(3,4)]) 542 sage: print tp.plot_ray_labels() 543 Graphics object consisting of 1 graphics primitive 544 """ 545 return self.plot_labels(self.ray_label, 546 [1.1 * ray for ray in self.rays]) 547 513 548 def plot_rays(self): 514 549 r""" 515 Plot ray generators and their labels.550 Plot rays and their labels. 516 551 517 552 Ray generators must be specified during construction or using 518 553 :meth:`set_rays` before calling this method. … … 541 576 result += line([origin, end], 542 577 color=color, thickness=thickness, 543 578 zorder=zorder, **extra_options) 544 result += self.plot_ labels(self.ray_label, [1.1 * ray for ray in rays])579 result += self.plot_ray_labels() 545 580 return result 546 581 547 582 def plot_walls(self, walls): … … 594 629 elif mode == "generators": 595 630 origin = self.origin 596 631 for wall, color in zip(walls, colors): 632 vertices = [rays[i] for i in wall.ambient_ray_indices()] 633 vertices.append(origin) 634 result += Polyhedron(vertices=vertices, field=RDF).render_solid( 635 alpha=alpha, color=color, zorder=zorder, **extra_options) 636 label_sectors = [] 637 round = mode == "round" 638 for wall, color in zip(walls, colors): 639 S = wall.linear_subspace() 640 lsd = S.dimension() 641 if lsd == 0: # Strictly convex wall 597 642 r1, r2 = (rays[i] for i in wall.ambient_ray_indices()) 598 result += polygon([origin, r1, r2], 599 alpha=alpha, color=color, zorder=zorder, **extra_options) 600 elif mode == "round": 601 for wall, color in zip(walls, colors): 602 r1, r2 = (rays[i] for i in wall.ambient_ray_indices()) 643 elif lsd == 1: # wall is a half-plane 644 for i, ray in zip(wall.ambient_ray_indices(), wall.rays()): 645 if ray in S: 646 r1 = rays[i] 647 else: 648 r2 = rays[i] 649 if round: 650 # Plot one "extra" sector 651 result += sector(- r1, r2, 652 alpha=alpha, color=color, zorder=zorder, **extra_options) 653 else: # wall is a plane 654 r1, r2 = S.basis() 655 r1 = vector(RDF, r1) 656 r1 = r1 / r1.norm() * self.radius 657 r2 = vector(RDF, r2) 658 r2 = r2 / r2.norm() * self.radius 659 if round: 660 # Plot three "extra" sectors 661 result += sector(r1, - r2, 662 alpha=alpha, color=color, zorder=zorder, **extra_options) 663 result += sector(- r1, r2, 664 alpha=alpha, color=color, zorder=zorder, **extra_options) 665 result += sector(- r1, - r2, 666 alpha=alpha, color=color, zorder=zorder, **extra_options) 667 label_sectors.append([r1, r2]) 668 if round: 603 669 result += sector(r1, r2, 604 670 alpha=alpha, color=color, zorder=zorder, **extra_options) 605 671 result += self.plot_labels(self.wall_label, 606 [sum(rays[i] for i in wall.ambient_ray_indices()) / 3 607 for wall in walls]) 672 [sum(label_sector) / 3 for label_sector in label_sectors]) 608 673 return result 609 674 610 675 def set_rays(self, generators): … … 637 702 """ 638 703 d = self.dimension 639 704 if d == 1: 640 generators = [vector(R R, 2, (gen[0], 0)) for gen in generators]705 generators = [vector(RDF, 2, (gen[0], 0)) for gen in generators] 641 706 else: 642 generators = [vector(R R, d, gen) for gen in generators]707 generators = [vector(RDF, d, gen) for gen in generators] 643 708 self.generators = generators 644 709 if self.mode == "box": 645 710 rays = [] … … 1026 1091 sage: print sector((3,2,1), (1,2,3)) 1027 1092 Graphics3d Object 1028 1093 """ 1029 ray1 = vector(R R, ray1)1030 ray2 = vector(R R, ray2)1094 ray1 = vector(RDF, ray1) 1095 ray2 = vector(RDF, ray2) 1031 1096 r = ray1.norm() 1032 1097 if len(ray1) == 2: 1033 1098 # Plot an honest sector … … 1045 1110 dr = (ray2 - ray1) / n 1046 1111 points = (ray1 + i * dr for i in range(n + 1)) 1047 1112 points = [r / pt.norm() * pt for pt in points] 1048 points.append(vector(R R, 3))1113 points.append(vector(RDF, 3)) 1049 1114 return polygon(points, **extra_options)