passagemath-polyhedra 10.6.31rc3__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of passagemath-polyhedra might be problematic. Click here for more details.

Files changed (206) hide show
  1. passagemath_polyhedra-10.6.31rc3.dist-info/METADATA +367 -0
  2. passagemath_polyhedra-10.6.31rc3.dist-info/METADATA.bak +369 -0
  3. passagemath_polyhedra-10.6.31rc3.dist-info/RECORD +206 -0
  4. passagemath_polyhedra-10.6.31rc3.dist-info/WHEEL +6 -0
  5. passagemath_polyhedra-10.6.31rc3.dist-info/top_level.txt +2 -0
  6. passagemath_polyhedra.libs/libgmp-6e109695.so.10.5.0 +0 -0
  7. passagemath_polyhedra.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
  8. sage/all__sagemath_polyhedra.py +50 -0
  9. sage/game_theory/all.py +8 -0
  10. sage/game_theory/catalog.py +6 -0
  11. sage/game_theory/catalog_normal_form_games.py +923 -0
  12. sage/game_theory/cooperative_game.py +844 -0
  13. sage/game_theory/matching_game.py +1181 -0
  14. sage/game_theory/normal_form_game.py +2697 -0
  15. sage/game_theory/parser.py +275 -0
  16. sage/geometry/all__sagemath_polyhedra.py +22 -0
  17. sage/geometry/cone.py +6940 -0
  18. sage/geometry/cone_catalog.py +847 -0
  19. sage/geometry/cone_critical_angles.py +1027 -0
  20. sage/geometry/convex_set.py +1119 -0
  21. sage/geometry/fan.py +3743 -0
  22. sage/geometry/fan_isomorphism.py +389 -0
  23. sage/geometry/fan_morphism.py +1884 -0
  24. sage/geometry/hasse_diagram.py +202 -0
  25. sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
  26. sage/geometry/hyperplane_arrangement/all.py +1 -0
  27. sage/geometry/hyperplane_arrangement/arrangement.py +3895 -0
  28. sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
  29. sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
  30. sage/geometry/hyperplane_arrangement/library.py +825 -0
  31. sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
  32. sage/geometry/hyperplane_arrangement/plot.py +520 -0
  33. sage/geometry/integral_points.py +35 -0
  34. sage/geometry/integral_points_generic_dense.cpython-314-x86_64-linux-gnu.so +0 -0
  35. sage/geometry/integral_points_generic_dense.pyx +7 -0
  36. sage/geometry/lattice_polytope.py +5894 -0
  37. sage/geometry/linear_expression.py +773 -0
  38. sage/geometry/newton_polygon.py +767 -0
  39. sage/geometry/point_collection.cpython-314-x86_64-linux-gnu.so +0 -0
  40. sage/geometry/point_collection.pyx +1008 -0
  41. sage/geometry/polyhedral_complex.py +2616 -0
  42. sage/geometry/polyhedron/all.py +8 -0
  43. sage/geometry/polyhedron/backend_cdd.py +460 -0
  44. sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
  45. sage/geometry/polyhedron/backend_field.py +347 -0
  46. sage/geometry/polyhedron/backend_normaliz.py +2503 -0
  47. sage/geometry/polyhedron/backend_number_field.py +168 -0
  48. sage/geometry/polyhedron/backend_polymake.py +765 -0
  49. sage/geometry/polyhedron/backend_ppl.py +582 -0
  50. sage/geometry/polyhedron/base.py +1206 -0
  51. sage/geometry/polyhedron/base0.py +1444 -0
  52. sage/geometry/polyhedron/base1.py +886 -0
  53. sage/geometry/polyhedron/base2.py +812 -0
  54. sage/geometry/polyhedron/base3.py +1845 -0
  55. sage/geometry/polyhedron/base4.py +1262 -0
  56. sage/geometry/polyhedron/base5.py +2700 -0
  57. sage/geometry/polyhedron/base6.py +1741 -0
  58. sage/geometry/polyhedron/base7.py +997 -0
  59. sage/geometry/polyhedron/base_QQ.py +1258 -0
  60. sage/geometry/polyhedron/base_RDF.py +98 -0
  61. sage/geometry/polyhedron/base_ZZ.py +934 -0
  62. sage/geometry/polyhedron/base_mutable.py +215 -0
  63. sage/geometry/polyhedron/base_number_field.py +122 -0
  64. sage/geometry/polyhedron/cdd_file_format.py +155 -0
  65. sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
  66. sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-x86_64-linux-gnu.so +0 -0
  67. sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
  68. sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
  69. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-x86_64-linux-gnu.so +0 -0
  70. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
  71. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
  72. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-x86_64-linux-gnu.so +0 -0
  73. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
  74. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
  75. sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
  76. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-x86_64-linux-gnu.so +0 -0
  77. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
  78. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
  79. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-x86_64-linux-gnu.so +0 -0
  80. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
  81. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
  82. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-x86_64-linux-gnu.so +0 -0
  83. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
  84. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
  85. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-x86_64-linux-gnu.so +0 -0
  86. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
  87. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
  88. sage/geometry/polyhedron/constructor.py +773 -0
  89. sage/geometry/polyhedron/double_description.py +753 -0
  90. sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
  91. sage/geometry/polyhedron/face.py +1060 -0
  92. sage/geometry/polyhedron/generating_function.py +1810 -0
  93. sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
  94. sage/geometry/polyhedron/library.py +3502 -0
  95. sage/geometry/polyhedron/misc.py +121 -0
  96. sage/geometry/polyhedron/modules/all.py +1 -0
  97. sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
  98. sage/geometry/polyhedron/palp_database.py +447 -0
  99. sage/geometry/polyhedron/parent.py +1279 -0
  100. sage/geometry/polyhedron/plot.py +1986 -0
  101. sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
  102. sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
  103. sage/geometry/polyhedron/representation.py +1723 -0
  104. sage/geometry/pseudolines.py +515 -0
  105. sage/geometry/relative_interior.py +445 -0
  106. sage/geometry/toric_plotter.py +1103 -0
  107. sage/geometry/triangulation/all.py +2 -0
  108. sage/geometry/triangulation/base.cpython-314-x86_64-linux-gnu.so +0 -0
  109. sage/geometry/triangulation/base.pyx +963 -0
  110. sage/geometry/triangulation/data.h +147 -0
  111. sage/geometry/triangulation/data.pxd +4 -0
  112. sage/geometry/triangulation/element.py +914 -0
  113. sage/geometry/triangulation/functions.h +10 -0
  114. sage/geometry/triangulation/functions.pxd +4 -0
  115. sage/geometry/triangulation/point_configuration.py +2256 -0
  116. sage/geometry/triangulation/triangulations.h +49 -0
  117. sage/geometry/triangulation/triangulations.pxd +7 -0
  118. sage/geometry/voronoi_diagram.py +319 -0
  119. sage/interfaces/all__sagemath_polyhedra.py +1 -0
  120. sage/interfaces/polymake.py +2028 -0
  121. sage/numerical/all.py +13 -0
  122. sage/numerical/all__sagemath_polyhedra.py +11 -0
  123. sage/numerical/backends/all.py +1 -0
  124. sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
  125. sage/numerical/backends/cvxopt_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  126. sage/numerical/backends/cvxopt_backend.pyx +1006 -0
  127. sage/numerical/backends/cvxopt_backend_test.py +19 -0
  128. sage/numerical/backends/cvxopt_sdp_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  129. sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
  130. sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  131. sage/numerical/backends/cvxpy_backend.pxd +41 -0
  132. sage/numerical/backends/cvxpy_backend.pyx +934 -0
  133. sage/numerical/backends/cvxpy_backend_test.py +13 -0
  134. sage/numerical/backends/generic_backend_test.py +24 -0
  135. sage/numerical/backends/interactivelp_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  136. sage/numerical/backends/interactivelp_backend.pxd +36 -0
  137. sage/numerical/backends/interactivelp_backend.pyx +1231 -0
  138. sage/numerical/backends/interactivelp_backend_test.py +12 -0
  139. sage/numerical/backends/logging_backend.py +391 -0
  140. sage/numerical/backends/matrix_sdp_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  141. sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
  142. sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
  143. sage/numerical/backends/ppl_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  144. sage/numerical/backends/ppl_backend.pyx +1126 -0
  145. sage/numerical/backends/ppl_backend_test.py +13 -0
  146. sage/numerical/backends/scip_backend.cpython-314-x86_64-linux-gnu.so +0 -0
  147. sage/numerical/backends/scip_backend.pxd +22 -0
  148. sage/numerical/backends/scip_backend.pyx +1289 -0
  149. sage/numerical/backends/scip_backend_test.py +13 -0
  150. sage/numerical/interactive_simplex_method.py +5338 -0
  151. sage/numerical/knapsack.py +665 -0
  152. sage/numerical/linear_functions.cpython-314-x86_64-linux-gnu.so +0 -0
  153. sage/numerical/linear_functions.pxd +31 -0
  154. sage/numerical/linear_functions.pyx +1648 -0
  155. sage/numerical/linear_tensor.py +470 -0
  156. sage/numerical/linear_tensor_constraints.py +448 -0
  157. sage/numerical/linear_tensor_element.cpython-314-x86_64-linux-gnu.so +0 -0
  158. sage/numerical/linear_tensor_element.pxd +6 -0
  159. sage/numerical/linear_tensor_element.pyx +459 -0
  160. sage/numerical/mip.cpython-314-x86_64-linux-gnu.so +0 -0
  161. sage/numerical/mip.pxd +40 -0
  162. sage/numerical/mip.pyx +3667 -0
  163. sage/numerical/sdp.cpython-314-x86_64-linux-gnu.so +0 -0
  164. sage/numerical/sdp.pxd +39 -0
  165. sage/numerical/sdp.pyx +1433 -0
  166. sage/rings/all__sagemath_polyhedra.py +3 -0
  167. sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
  168. sage/rings/polynomial/omega.py +982 -0
  169. sage/schemes/all__sagemath_polyhedra.py +2 -0
  170. sage/schemes/toric/all.py +10 -0
  171. sage/schemes/toric/chow_group.py +1248 -0
  172. sage/schemes/toric/divisor.py +2082 -0
  173. sage/schemes/toric/divisor_class.cpython-314-x86_64-linux-gnu.so +0 -0
  174. sage/schemes/toric/divisor_class.pyx +322 -0
  175. sage/schemes/toric/fano_variety.py +1606 -0
  176. sage/schemes/toric/homset.py +650 -0
  177. sage/schemes/toric/ideal.py +451 -0
  178. sage/schemes/toric/library.py +1322 -0
  179. sage/schemes/toric/morphism.py +1958 -0
  180. sage/schemes/toric/points.py +1032 -0
  181. sage/schemes/toric/sheaf/all.py +1 -0
  182. sage/schemes/toric/sheaf/constructor.py +302 -0
  183. sage/schemes/toric/sheaf/klyachko.py +921 -0
  184. sage/schemes/toric/toric_subscheme.py +905 -0
  185. sage/schemes/toric/variety.py +3460 -0
  186. sage/schemes/toric/weierstrass.py +1078 -0
  187. sage/schemes/toric/weierstrass_covering.py +457 -0
  188. sage/schemes/toric/weierstrass_higher.py +288 -0
  189. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
  190. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
  191. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
  192. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
  193. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
  194. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
  195. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
  196. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
  197. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
  198. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
  199. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
  200. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
  201. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
  202. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
  203. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
  204. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
  205. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
  206. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_3d +37977 -0
@@ -0,0 +1,2616 @@
1
+ # sage_setup: distribution = sagemath-polyhedra
2
+ # sage.doctest: needs sage.graphs
3
+ r"""
4
+ Finite polyhedral complexes
5
+
6
+ This module implements the basic structure of finite polyhedral complexes.
7
+ For more information, see :class:`PolyhedralComplex`.
8
+
9
+ AUTHORS:
10
+
11
+ - Yuan Zhou (2021-05): initial implementation
12
+
13
+ List of PolyhedralComplex methods
14
+ ---------------------------------
15
+
16
+ **Maximal cells and cells**
17
+
18
+ .. csv-table::
19
+ :class: contentstable
20
+ :widths: 30, 70
21
+ :delim: |
22
+
23
+ :meth:`~PolyhedralComplex.maximal_cells` | Return the dictionary of the maximal cells in this polyhedral complex.
24
+ :meth:`~PolyhedralComplex.maximal_cell_iterator` | Return an iterator over maximal cells in this polyhedral complex.
25
+ :meth:`~PolyhedralComplex.maximal_cells_sorted` | Return the sorted list of all maximal cells in this polyhedral complex.
26
+ :meth:`~PolyhedralComplex.n_maximal_cells` | List the maximal cells of dimension `n` in this polyhedral complex.
27
+ :meth:`~PolyhedralComplex._n_maximal_cells_sorted` | Return the sorted list of maximal cells of dim `n` in this complex.
28
+ :meth:`~PolyhedralComplex.is_maximal_cell` | Return ``True`` if the given cell is a maximal cell in this complex.
29
+ :meth:`~PolyhedralComplex.cells` | Return the dictionary of the cells in this polyhedral complex.
30
+ :meth:`~PolyhedralComplex.cell_iterator` | Return an iterator over cells in this polyhedral complex.
31
+ :meth:`~PolyhedralComplex.cells_sorted` | Return the sorted list of all cells in this polyhedral complex.
32
+ :meth:`~sage.topology.cell_complex.GenericCellComplex.n_cells` | List the cells of dimension `n` in this polyhedral complex.
33
+ :meth:`~PolyhedralComplex._n_cells_sorted` | Return the sorted list of `n`-cells in this polyhedral complex.
34
+ :meth:`~PolyhedralComplex.is_cell` | Return ``True`` if the given cell is in this polyhedral complex.
35
+ :meth:`~PolyhedralComplex.face_poset` | Return the poset of nonempty cells in the polyhedral complex.
36
+ :meth:`~PolyhedralComplex.relative_boundary_cells` | List the maximal cells on the boundary of the polyhedral complex.
37
+
38
+ **Properties of the polyhedral complex**
39
+
40
+ .. csv-table::
41
+ :class: contentstable
42
+ :widths: 30, 70
43
+ :delim: |
44
+
45
+ :meth:`~PolyhedralComplex.dimension` | Return the dimension of the polyhedral complex.
46
+ :meth:`~PolyhedralComplex.ambient_dimension` | Return the ambient dimension of the polyhedral complex.
47
+ :meth:`~PolyhedralComplex.is_pure` | Return ``True`` if the polyhedral complex is pure.
48
+ :meth:`~PolyhedralComplex.is_full_dimensional` | Return ``True`` if the polyhedral complex is full dimensional.
49
+ :meth:`~PolyhedralComplex.is_compact` | Return ``True`` if the polyhedral complex is bounded.
50
+ :meth:`~PolyhedralComplex.is_connected` | Return ``True`` if the polyhedral complex is connected.
51
+ :meth:`~PolyhedralComplex.is_subcomplex` | Return ``True`` if this complex is a subcomplex of the other.
52
+ :meth:`~PolyhedralComplex.is_convex` | Return ``True`` if the polyhedral complex is convex.
53
+ :meth:`~PolyhedralComplex.is_mutable` | Return ``True`` if the polyhedral complex is mutable.
54
+ :meth:`~PolyhedralComplex.is_immutable` | Return ``True`` if the polyhedral complex is not mutable.
55
+ :meth:`~PolyhedralComplex.is_simplicial_complex` | Return ``True`` if the polyhedral complex is a simplicial complex.
56
+ :meth:`~PolyhedralComplex.is_polyhedral_fan` | Return ``True`` if the polyhedral complex is a fan.
57
+ :meth:`~PolyhedralComplex.is_simplicial_fan` | Return ``True`` if the polyhedral complex is a simplicial fan.
58
+
59
+ **New polyhedral complexes from old ones**
60
+
61
+ .. csv-table::
62
+ :class: contentstable
63
+ :widths: 30, 70
64
+ :delim: |
65
+
66
+ :meth:`~PolyhedralComplex.connected_component` | Return the connected component containing a cell as a subcomplex.
67
+ :meth:`~PolyhedralComplex.connected_components` | Return the connected components of this polyhedral complex.
68
+ :meth:`~PolyhedralComplex.n_skeleton` | Return the `n`-skeleton of this polyhedral complex.
69
+ :meth:`~PolyhedralComplex.stratify` | Return the (pure) subcomplex formed by the maximal cells of dim `n` in this complex.
70
+ :meth:`~PolyhedralComplex.boundary_subcomplex` | Return the boundary subcomplex of this polyhedral complex.
71
+ :meth:`~PolyhedralComplex.product` | Return the (Cartesian) product of this polyhedral complex with another one.
72
+ :meth:`~PolyhedralComplex.disjoint_union` | Return the disjoint union of this polyhedral complex with another one.
73
+ :meth:`~PolyhedralComplex.union` | Return the union of this polyhedral complex with another one.
74
+ :meth:`~PolyhedralComplex.join` | Return the join of this polyhedral complex with another one.
75
+ :meth:`~PolyhedralComplex.subdivide` | Return a new polyhedral complex (with option ``make_simplicial``) subdividing this one.
76
+
77
+ **Update polyhedral complex**
78
+
79
+ .. csv-table::
80
+ :class: contentstable
81
+ :widths: 30, 70
82
+ :delim: |
83
+
84
+ :meth:`~PolyhedralComplex.set_immutable` | Make this polyhedral complex immutable.
85
+ :meth:`~PolyhedralComplex.add_cell` | Add a cell to this polyhedral complex.
86
+ :meth:`~PolyhedralComplex.remove_cell` | Remove a cell from this polyhedral complex.
87
+
88
+ **Miscellaneous**
89
+
90
+ .. csv-table::
91
+ :class: contentstable
92
+ :widths: 30, 70
93
+ :delim: |
94
+
95
+ :meth:`~PolyhedralComplex.plot` | Return a Graphic object showing the plot of polyhedral complex.
96
+ :meth:`~PolyhedralComplex.graph` | Return a directed graph corresponding to the 1-skeleton of this polyhedral complex, given that it is bounded.
97
+ :meth:`~PolyhedralComplex.union_as_polyhedron` | Return a ``Polyhedron`` which is the union of cells in this polyhedral complex, given that it is convex.
98
+
99
+ Classes and functions
100
+ ---------------------
101
+ """
102
+
103
+ # ****************************************************************************
104
+ # Copyright (C) 2021 Yuan Zhou <yuan.zhou@uky.edu>
105
+ #
106
+ # This program is free software: you can redistribute it and/or modify
107
+ # it under the terms of the GNU General Public License as published by
108
+ # the Free Software Foundation, either version 2 of the License, or
109
+ # (at your option) any later version.
110
+ # https://www.gnu.org/licenses/
111
+ # ****************************************************************************
112
+
113
+ from copy import copy
114
+
115
+ import sage.geometry.abc
116
+
117
+ from sage.topology.cell_complex import GenericCellComplex
118
+ from sage.geometry.polyhedron.constructor import Polyhedron
119
+ from sage.modules.free_module_element import vector
120
+ from sage.rings.integer_ring import ZZ
121
+ from sage.graphs.graph import Graph
122
+ from sage.combinat.posets.posets import Poset
123
+ from sage.combinat.subset import powerset
124
+
125
+
126
+ class PolyhedralComplex(GenericCellComplex):
127
+ r"""
128
+ A polyhedral complex.
129
+
130
+ A **polyhedral complex** `PC` is a collection of polyhedra in a certain
131
+ ambient space `\RR^n` such that the following hold.
132
+
133
+ - If a polyhedron `P` is in `PC`, then all the faces of `P` are in `PC`.
134
+
135
+ - If polyhedra `P` and `Q` are in `PC`, then `P \cap Q` is either empty
136
+ or a face of both `P` and `Q`.
137
+
138
+ In this context, a "polyhedron" means the geometric realization
139
+ of a polyhedron. This is in contrast to :mod:`simplicial complex
140
+ <sage.topology.simplicial_complex>`, whose cells are abstract simplices.
141
+ The concept of a polyhedral complex generalizes that of a **geometric**
142
+ simplicial complex.
143
+
144
+ .. NOTE::
145
+
146
+ This class derives from
147
+ :class:`~sage.topology.cell_complex.GenericCellComplex`, and so
148
+ inherits its methods. Some of those methods are not listed here;
149
+ see the :mod:`Generic Cell Complex <sage.topology.cell_complex>`
150
+ page instead.
151
+
152
+ INPUT:
153
+
154
+ - ``maximal_cells`` -- list, tuple, or dictionary (indexed by
155
+ dimension) of cells of the Complex. Each cell is of class
156
+ :class:`Polyhedron` of the same ambient dimension. To set up a
157
+ :class:PolyhedralComplex, it is sufficient to provide the maximal
158
+ faces. Use keyword argument ``partial=True`` to set up a partial
159
+ polyhedral complex, which is a subset of the faces (viewed as
160
+ relatively open) of a polyhedral complex that is not necessarily
161
+ closed under taking intersection.
162
+
163
+ - ``maximality_check`` -- boolean (default: ``True``);
164
+ if ``True``, then the constructor checks that each given
165
+ maximal cell is indeed maximal, and ignores those that are not
166
+
167
+ - ``face_to_face_check`` -- boolean (default: ``False``);
168
+ if ``True``, then the constructor checks whether the cells
169
+ are face-to-face, and it raises a :exc:`ValueError` if they are not
170
+
171
+ - ``is_mutable`` and ``is_immutable`` -- boolean (default: ``True`` and
172
+ ``False`` respectively); set ``is_mutable=False`` or ``is_immutable=True``
173
+ to make this polyhedral complex immutable
174
+
175
+ - ``backend`` -- string (optional); the name of the backend used for
176
+ computations on Sage polyhedra; if it is not given, then each cell has
177
+ its own backend; otherwise it must be one of the following:
178
+
179
+ * ``'ppl'`` -- the Parma Polyhedra Library
180
+
181
+ * ``'cdd'`` -- CDD
182
+
183
+ * ``'normaliz'`` -- normaliz
184
+
185
+ * ``'polymake'`` -- polymake
186
+
187
+ * ``'field'`` -- a generic Sage implementation
188
+
189
+ - ``ambient_dim`` -- integer (optional); used to set up an empty
190
+ complex in the intended ambient space
191
+
192
+ EXAMPLES::
193
+
194
+ sage: pc = PolyhedralComplex([
195
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1/7, 2/7)]),
196
+ ....: Polyhedron(vertices=[(1/7, 2/7), (0, 0), (0, 1/4)])])
197
+ sage: [p.Vrepresentation() for p in pc.cells_sorted()]
198
+ [(A vertex at (0, 0), A vertex at (0, 1/4), A vertex at (1/7, 2/7)),
199
+ (A vertex at (0, 0), A vertex at (1/3, 1/3), A vertex at (1/7, 2/7)),
200
+ (A vertex at (0, 0), A vertex at (0, 1/4)),
201
+ (A vertex at (0, 0), A vertex at (1/7, 2/7)),
202
+ (A vertex at (0, 0), A vertex at (1/3, 1/3)),
203
+ (A vertex at (0, 1/4), A vertex at (1/7, 2/7)),
204
+ (A vertex at (1/3, 1/3), A vertex at (1/7, 2/7)),
205
+ (A vertex at (0, 0),),
206
+ (A vertex at (0, 1/4),),
207
+ (A vertex at (1/7, 2/7),),
208
+ (A vertex at (1/3, 1/3),)]
209
+ sage: pc.plot() # needs sage.plot
210
+ Graphics object consisting of 10 graphics primitives
211
+ sage: pc.is_pure()
212
+ True
213
+ sage: pc.is_full_dimensional()
214
+ True
215
+ sage: pc.is_compact()
216
+ True
217
+ sage: pc.boundary_subcomplex()
218
+ Polyhedral complex with 4 maximal cells
219
+ sage: pc.is_convex()
220
+ True
221
+ sage: pc.union_as_polyhedron().Hrepresentation()
222
+ (An inequality (1, -4) x + 1 >= 0,
223
+ An inequality (-1, 1) x + 0 >= 0,
224
+ An inequality (1, 0) x + 0 >= 0)
225
+ sage: pc.face_poset()
226
+ Finite poset containing 11 elements
227
+ sage: pc.is_connected()
228
+ True
229
+ sage: pc.connected_component() == pc
230
+ True
231
+
232
+ TESTS:
233
+
234
+ Check that non-maximal cells are ignored if ``maximality_check=True``::
235
+
236
+ sage: pc = PolyhedralComplex([
237
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
238
+ ....: Polyhedron(vertices=[(1, 2), (0, 0)]) ])
239
+ sage: pc.maximal_cells()
240
+ {2: {A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices}}
241
+
242
+ Check that non face-to-face can be detected::
243
+
244
+ sage: PolyhedralComplex([
245
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
246
+ ....: Polyhedron(vertices=[(2, 2), (0, 0)]) ],
247
+ ....: face_to_face_check=True)
248
+ Traceback (most recent call last):
249
+ ...
250
+ ValueError: the given cells are not face-to-face
251
+
252
+ Check that all the cells must have the same ambient dimension::
253
+
254
+ sage: PolyhedralComplex([
255
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
256
+ ....: Polyhedron(vertices=[[2], [0]]) ])
257
+ Traceback (most recent call last):
258
+ ...
259
+ ValueError: the given cells are not polyhedra in the same ambient space
260
+
261
+ Check that backend is passed to all the cells::
262
+
263
+ sage: P = Polyhedron(vertices=[(0, 0), (1, 1)])
264
+ sage: P.backend()
265
+ 'ppl'
266
+
267
+ sage: # needs cddexec_gmp
268
+ sage: pc = PolyhedralComplex([P], backend='cdd')
269
+ sage: Q = pc.maximal_cells_sorted()[0]
270
+ sage: Q.backend()
271
+ 'cdd'
272
+ """
273
+ def __init__(self, maximal_cells=None, backend=None, maximality_check=True,
274
+ face_to_face_check=False, is_mutable=True, is_immutable=False,
275
+ ambient_dim=None) -> None:
276
+ r"""
277
+ Define a PolyhedralComplex.
278
+
279
+ See ``PolyhedralComplex`` for more information.
280
+
281
+ EXAMPLES::
282
+
283
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[(1, 1), (0, 0)])])
284
+ sage: pc
285
+ Polyhedral complex with 1 maximal cell
286
+ sage: TestSuite(pc).run()
287
+ """
288
+ self._backend = backend
289
+ if maximal_cells is None:
290
+ cells_dict = {}
291
+ elif isinstance(maximal_cells, (list, tuple)):
292
+ if backend:
293
+ maximal_cells = [p.base_extend(p.base_ring(), backend)
294
+ for p in maximal_cells]
295
+ cells_dict = cells_list_to_cells_dict(maximal_cells)
296
+ elif isinstance(maximal_cells, dict):
297
+ cells_dict = {}
298
+ for k, l in maximal_cells.items():
299
+ if backend:
300
+ cells_dict[k] = {p.base_extend(p.base_ring(), backend)
301
+ for p in l}
302
+ else:
303
+ cells_dict[k] = set(l)
304
+ else:
305
+ raise ValueError("the maximal cells are not given in correct form")
306
+ if not cells_dict:
307
+ self._dim = -1
308
+ if ambient_dim is None:
309
+ ambient_dim = -1
310
+ else:
311
+ self._dim = max(cells_dict.keys())
312
+ if ambient_dim is None:
313
+ ambient_dim = next(iter(cells_dict[self._dim])).ambient_dim()
314
+ self._ambient_dim = ambient_dim
315
+ self._maximal_cells = cells_dict
316
+ if not all((isinstance(cell, sage.geometry.abc.Polyhedron) and
317
+ cell.ambient_dim() == self._ambient_dim)
318
+ for cell in self.maximal_cell_iterator()):
319
+ raise ValueError("the given cells are not polyhedra " +
320
+ "in the same ambient space")
321
+ # initialize the attributes
322
+ self._is_convex = None
323
+ self._polyhedron = None
324
+ self._maximal_cells_sorted = None # needed for hash
325
+ self._cells = None
326
+ self._face_poset = None
327
+
328
+ if maximality_check:
329
+ self.cells() # compute self._cells and self._face_poset
330
+ self._maximal_cells = cells_list_to_cells_dict(
331
+ self._face_poset.maximal_elements())
332
+ if face_to_face_check:
333
+ poset = self.face_poset()
334
+ maximal_cells = poset.maximal_elements() # a list
335
+ for i in range(len(maximal_cells)):
336
+ p = maximal_cells[i]
337
+ for j in range(i, len(maximal_cells)):
338
+ q = maximal_cells[j]
339
+ r = p.intersection(q)
340
+ if not (r.is_empty() or (r in poset) and
341
+ poset.is_gequal(p, r) and poset.is_gequal(q, r)):
342
+ raise ValueError("the given cells are not face-to-face")
343
+ self._is_immutable = False
344
+ if not is_mutable or is_immutable:
345
+ self.set_immutable()
346
+
347
+ def cells(self, subcomplex=None) -> dict:
348
+ """
349
+ The cells of this polyhedral complex, in the form of a dictionary:
350
+ the keys are integers, representing dimension, and the value
351
+ associated to an integer `d` is the set of `d`-cells.
352
+
353
+ INPUT:
354
+
355
+ - ``subcomplex`` -- (optional) if a subcomplex is given then
356
+ return the cells which are **not** in this subcomplex
357
+
358
+ EXAMPLES::
359
+
360
+ sage: pc = PolyhedralComplex([
361
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
362
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
363
+ sage: list(pc.cells().keys())
364
+ [2, 1, 0]
365
+ """
366
+ if subcomplex is not None:
367
+ raise NotImplementedError("providing subcomplex is not implemented")
368
+ if self._cells is not None:
369
+ return self._cells
370
+ maximal_cells = self.maximal_cells()
371
+ cells = {}
372
+ covers = {}
373
+ for k in range(self._dim, -1, -1):
374
+ if k in maximal_cells:
375
+ if k not in cells:
376
+ cells[k] = set()
377
+ cells[k].update(maximal_cells[k])
378
+ if k in cells:
379
+ for cell in cells[k]:
380
+ if cell not in covers:
381
+ covers[cell] = []
382
+ for facet in cell.facets():
383
+ p = facet.as_polyhedron()
384
+ if p not in covers:
385
+ covers[p] = []
386
+ covers[p].append(cell)
387
+ if (k-1) not in cells:
388
+ cells[k-1] = set()
389
+ cells[k-1].add(p)
390
+ self._face_poset = Poset(covers)
391
+ self._cells = cells
392
+ return self._cells
393
+
394
+ def cell_iterator(self, increasing=True):
395
+ """
396
+ An iterator for the cells in this polyhedral complex.
397
+
398
+ INPUT:
399
+
400
+ - ``increasing`` -- boolean (default: ``True``); if ``True``, return
401
+ cells in increasing order of dimension, thus starting with the
402
+ zero-dimensional cells; otherwise it returns cells in decreasing
403
+ order of dimension
404
+
405
+ .. NOTE::
406
+
407
+ Among the cells of a fixed dimension, there is no sorting.
408
+
409
+ EXAMPLES::
410
+
411
+ sage: pc = PolyhedralComplex([
412
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
413
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
414
+ sage: len(list(pc.cell_iterator()))
415
+ 11
416
+ """
417
+ cells = self.cells()
418
+ dim_index = range(self.dimension() + 1)
419
+ if not increasing:
420
+ dim_index = reversed(dim_index)
421
+ for d in dim_index:
422
+ if d in cells:
423
+ yield from cells[d]
424
+
425
+ def _n_cells_sorted(self, n, subcomplex=None) -> list:
426
+ """
427
+ Sorted list of cells of dimension ``n`` of this polyhedral complex.
428
+
429
+ INPUT:
430
+
431
+ - ``n`` -- nonnegative integer; the dimension
432
+ - ``subcomplex`` -- (optional) if a subcomplex is given then
433
+ return the cells which are **not** in this subcomplex
434
+
435
+ EXAMPLES::
436
+
437
+ sage: pc = PolyhedralComplex([
438
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
439
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
440
+ sage: [p.Vrepresentation() for p in pc._n_cells_sorted(1)]
441
+ [(A vertex at (0, 0), A vertex at (0, 2)),
442
+ (A vertex at (0, 0), A vertex at (1, 1)),
443
+ (A vertex at (0, 0), A vertex at (1, 2)),
444
+ (A vertex at (0, 2), A vertex at (1, 2)),
445
+ (A vertex at (1, 1), A vertex at (1, 2))]
446
+ sage: pc._n_cells_sorted(3)
447
+ []
448
+ """
449
+ n_cells = self.n_cells(n, subcomplex)
450
+ return sorted(n_cells,
451
+ key=lambda p: (p.vertices(), p.rays(), p.lines()))
452
+
453
+ def cells_sorted(self, subcomplex=None) -> list:
454
+ """
455
+ The sorted list of the cells of this polyhedral complex
456
+ in non-increasing dimensions.
457
+
458
+ INPUT:
459
+
460
+ - ``subcomplex`` -- (optional) if a subcomplex is given then
461
+ return the cells which are **not** in this subcomplex
462
+
463
+ EXAMPLES::
464
+
465
+ sage: pc = PolyhedralComplex([
466
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
467
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
468
+ sage: len(pc.cells_sorted())
469
+ 11
470
+ sage: pc.cells_sorted()[0].Vrepresentation()
471
+ (A vertex at (0, 0), A vertex at (0, 2), A vertex at (1, 2))
472
+ """
473
+ cells = []
474
+ for n in range(self._dim, -1, -1):
475
+ cells.extend(self._n_cells_sorted(n, subcomplex))
476
+ return cells
477
+
478
+ def maximal_cells(self) -> dict:
479
+ """
480
+ The maximal cells of this polyhedral complex, in the form of a
481
+ dictionary: the keys are integers, representing dimension, and the
482
+ value associated to an integer `d` is the set of `d`-maximal cells.
483
+
484
+ .. WARNING::
485
+
486
+ This may give the wrong answer if the polyhedral complex
487
+ was constructed with ``maximality_check`` set to ``False``.
488
+
489
+ EXAMPLES::
490
+
491
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
492
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
493
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
494
+ sage: pc = PolyhedralComplex([p1, p2, p3])
495
+ sage: len(pc.maximal_cells()[2])
496
+ 2
497
+ sage: 1 in pc.maximal_cells()
498
+ False
499
+
500
+ Wrong answer due to ``maximality_check=False``::
501
+
502
+ sage: pc_invalid = PolyhedralComplex([p1, p2, p3],
503
+ ....: maximality_check=False)
504
+ sage: len(pc_invalid.maximal_cells()[1])
505
+ 1
506
+ """
507
+ return self._maximal_cells
508
+
509
+ def maximal_cell_iterator(self, increasing=False):
510
+ r"""
511
+ An iterator for the maximal cells in this polyhedral complex.
512
+
513
+ INPUT:
514
+
515
+ - ``increasing`` -- boolean (default: ``False``); if ``True``, return
516
+ maximal cells in increasing order of dimension.
517
+ Otherwise it returns cells in decreasing order of dimension.
518
+
519
+ .. NOTE::
520
+
521
+ Among the cells of a fixed dimension, there is no sorting.
522
+
523
+ .. WARNING::
524
+
525
+ This may give the wrong answer if the polyhedral complex
526
+ was constructed with ``maximality_check`` set to ``False``.
527
+
528
+ EXAMPLES::
529
+
530
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
531
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
532
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
533
+ sage: pc = PolyhedralComplex([p1, p2, p3])
534
+ sage: len(list(pc.maximal_cell_iterator()))
535
+ 2
536
+
537
+ Wrong answer due to ``maximality_check=False``::
538
+
539
+ sage: pc_invalid = PolyhedralComplex([p1, p2, p3],
540
+ ....: maximality_check=False)
541
+ sage: len(list(pc_invalid.maximal_cell_iterator()))
542
+ 3
543
+ """
544
+ maximal_cells = self.maximal_cells()
545
+ dim_index = range(-1, self.dimension() + 1)
546
+ if not increasing:
547
+ dim_index = reversed(dim_index)
548
+ for d in dim_index:
549
+ if d in maximal_cells:
550
+ yield from maximal_cells[d]
551
+
552
+ def n_maximal_cells(self, n) -> list:
553
+ r"""
554
+ List of maximal cells of dimension ``n`` of this polyhedral complex.
555
+
556
+ INPUT:
557
+
558
+ - ``n`` -- nonnegative integer; the dimension
559
+
560
+ .. NOTE::
561
+
562
+ The resulting list need not be sorted. If you want a sorted
563
+ list of `n`-cells, use :meth:`_n_maximal_cells_sorted`.
564
+
565
+ .. WARNING::
566
+
567
+ This may give the wrong answer if the polyhedral complex
568
+ was constructed with ``maximality_check`` set to ``False``.
569
+
570
+ EXAMPLES::
571
+
572
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
573
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
574
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
575
+ sage: pc = PolyhedralComplex([p1, p2, p3])
576
+ sage: len(pc.n_maximal_cells(2))
577
+ 2
578
+ sage: len(pc.n_maximal_cells(1))
579
+ 0
580
+
581
+ Wrong answer due to ``maximality_check=False``::
582
+
583
+ sage: pc_invalid = PolyhedralComplex([p1, p2, p3],
584
+ ....: maximality_check=False)
585
+ sage: len(pc_invalid.n_maximal_cells(1))
586
+ 1
587
+ """
588
+ if n in self.maximal_cells():
589
+ return list(self.maximal_cells()[n])
590
+ return []
591
+
592
+ def _n_maximal_cells_sorted(self, n) -> list:
593
+ """
594
+ Sorted list of maximal cells of dimension ``n`` of this polyhedral
595
+ complex.
596
+
597
+ INPUT:
598
+
599
+ - ``n`` -- nonnegative integer; the dimension
600
+
601
+ .. WARNING::
602
+
603
+ This may give the wrong answer if the polyhedral complex
604
+ was constructed with ``maximality_check`` set to ``False``.
605
+
606
+ EXAMPLES::
607
+
608
+ sage: pc = PolyhedralComplex([
609
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
610
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
611
+ sage: pc._n_maximal_cells_sorted(2)[0].vertices_list()
612
+ [[0, 0], [0, 2], [1, 2]]
613
+ """
614
+ n_maximal_cells = self.n_maximal_cells(n)
615
+ return sorted(n_maximal_cells,
616
+ key=lambda p: (p.vertices(), p.rays(), p.lines()))
617
+
618
+ def maximal_cells_sorted(self) -> list:
619
+ """
620
+ Return the sorted list of the maximal cells of this polyhedral complex
621
+ by non-increasing dimensions.
622
+
623
+ EXAMPLES::
624
+
625
+ sage: pc = PolyhedralComplex([
626
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
627
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
628
+ sage: [p.vertices_list() for p in pc.maximal_cells_sorted()]
629
+ [[[0, 0], [0, 2], [1, 2]], [[0, 0], [1, 1], [1, 2]]]
630
+ """
631
+ if self._maximal_cells_sorted is None:
632
+ maximal_cells = []
633
+ for n in range(self._dim, -1, -1):
634
+ maximal_cells.extend(self._n_maximal_cells_sorted(n))
635
+ self._maximal_cells_sorted = maximal_cells
636
+ return self._maximal_cells_sorted
637
+
638
+ def is_maximal_cell(self, c) -> bool:
639
+ """
640
+ Return whether the given cell ``c`` is a maximal cell of ``self``.
641
+
642
+ .. WARNING::
643
+
644
+ This may give the wrong answer if the polyhedral complex
645
+ was constructed with ``maximality_check`` set to ``False``.
646
+
647
+ EXAMPLES::
648
+
649
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
650
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
651
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
652
+ sage: pc = PolyhedralComplex([p1, p2, p3])
653
+ sage: pc.is_maximal_cell(p1)
654
+ True
655
+ sage: pc.is_maximal_cell(p3)
656
+ False
657
+
658
+ Wrong answer due to ``maximality_check=False``::
659
+
660
+ sage: pc_invalid = PolyhedralComplex([p1, p2, p3],
661
+ ....: maximality_check=False)
662
+ sage: pc_invalid.is_maximal_cell(p3)
663
+ True
664
+ """
665
+ d = c.dimension()
666
+ # return (c in self.n_maximal_cells(d)) # use set instead of list
667
+ return (d in self.maximal_cells()) and (c in self.maximal_cells()[d])
668
+
669
+ def is_cell(self, c) -> bool:
670
+ """
671
+ Return whether the given cell ``c`` is a cell of ``self``.
672
+
673
+ EXAMPLES::
674
+
675
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
676
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
677
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
678
+ sage: pc = PolyhedralComplex([p1, p2])
679
+ sage: pc.is_cell(p3)
680
+ True
681
+ sage: pc.is_cell(Polyhedron(vertices=[(0, 0)]))
682
+ True
683
+ """
684
+ d = c.dimension()
685
+ return (d in self.cells()) and (c in self.cells()[d])
686
+
687
+ def dimension(self):
688
+ """
689
+ The dimension of this cell complex: the maximum
690
+ dimension of its cells.
691
+
692
+ EXAMPLES::
693
+
694
+ sage: pc = PolyhedralComplex([
695
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
696
+ ....: Polyhedron(vertices=[(1, 2), (0, 2)]) ])
697
+ sage: pc.dimension()
698
+ 2
699
+ sage: empty_pc = PolyhedralComplex([])
700
+ sage: empty_pc.dimension()
701
+ -1
702
+ """
703
+ return self._dim
704
+
705
+ def ambient_dimension(self):
706
+ """
707
+ The ambient dimension of this cell complex: the ambient
708
+ dimension of each of its cells.
709
+
710
+ EXAMPLES::
711
+
712
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[(1, 2, 3)])])
713
+ sage: pc.ambient_dimension()
714
+ 3
715
+ sage: empty_pc = PolyhedralComplex([])
716
+ sage: empty_pc.ambient_dimension()
717
+ -1
718
+ sage: pc0 = PolyhedralComplex(ambient_dim=2)
719
+ sage: pc0.ambient_dimension()
720
+ 2
721
+ """
722
+ return self._ambient_dim
723
+
724
+ def plot(self, **kwds):
725
+ """
726
+ Return a plot of the polyhedral complex, if it is of dim at most 3.
727
+
728
+ INPUT:
729
+
730
+ - ``explosion_factor`` -- (default: 0) if positive, separate the cells of
731
+ the complex by extra space. In this case, the following keyword arguments
732
+ can be passed to :func:`exploded_plot`:
733
+
734
+ - ``center`` -- (default: ``None``, denoting the origin) the center of explosion
735
+ - ``sticky_vertices`` -- (default: ``False``) boolean or dict;
736
+ whether to draw line segments between shared vertices of the given polyhedra.
737
+ A dict gives options for :func:`sage.plot.line`.
738
+ - ``sticky_center`` -- (default: ``True``) boolean or dict. When ``center`` is
739
+ a vertex of some of the polyhedra, whether to draw line segments connecting the
740
+ ``center`` to the shifted copies of these vertices.
741
+ A dict gives options for :func:`sage.plot.line`.
742
+
743
+ - ``color`` -- (default: ``None``) if ``'rainbow'``, assign a different color
744
+ to every maximal cell; otherwise, passed on to
745
+ :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`.
746
+
747
+ - other keyword arguments are passed on to
748
+ :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`.
749
+
750
+ EXAMPLES::
751
+
752
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
753
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
754
+ sage: p3 = Polyhedron(vertices=[(0, 0), (0, 2), (-1, 1)])
755
+ sage: pc1 = PolyhedralComplex([p1, p2, p3, -p1, -p2, -p3])
756
+ sage: bb = dict(xmin=-2, xmax=2, ymin=-3, ymax=3, axes=False)
757
+ sage: g0 = pc1.plot(color='rainbow', **bb) # needs sage.plot
758
+ sage: g1 = pc1.plot(explosion_factor=0.5, **bb) # needs cddexec sage.plot
759
+ sage: g2 = pc1.plot(explosion_factor=1, color='rainbow', alpha=0.5, **bb) # needs sage.plot
760
+ sage: graphics_array([g0, g1, g2]).show(axes=False) # not tested
761
+
762
+ sage: pc2 = PolyhedralComplex([polytopes.hypercube(3)])
763
+ sage: pc3 = pc2.subdivide(new_vertices=[(0, 0, 0)])
764
+ sage: g3 = pc3.plot(explosion_factor=1, color='rainbow', # needs sage.plot
765
+ ....: alpha=0.5, axes=False, online=True)
766
+ sage: pc4 = pc2.subdivide(make_simplicial=True)
767
+ sage: g4 = pc4.plot(explosion_factor=1, center=(1, -1, 1), fill='blue', # needs sage.plot
768
+ ....: wireframe='white', point={'color':'red', 'size':10},
769
+ ....: alpha=0.6, online=True)
770
+ sage: pc5 = PolyhedralComplex([
771
+ ....: Polyhedron(rays=[[1,0,0], [0,1,0], [0,0,-1]]),
772
+ ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,-1]]),
773
+ ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,1]]),
774
+ ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,-1]]),
775
+ ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,1]]),
776
+ ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,-1]]),
777
+ ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,1]])])
778
+ sage: g5 = pc5.plot(explosion_factor=0.3, color='rainbow', alpha=0.8, # needs cddexec sage.plot
779
+ ....: point={'size': 20}, axes=False, online=True)
780
+ """
781
+ if self.dimension() > 3:
782
+ raise ValueError("cannot plot in high dimension")
783
+ if kwds.get('explosion_factor', 0):
784
+ return exploded_plot(self.maximal_cell_iterator(), **kwds)
785
+
786
+ from sage.plot.colors import rainbow
787
+ from sage.plot.graphics import Graphics
788
+
789
+ color = kwds.get('color')
790
+ polyhedra = self.maximal_cell_iterator()
791
+ if color == 'rainbow':
792
+ polyhedra = list(polyhedra)
793
+ cell_colors_dict = dict(zip(polyhedra,
794
+ rainbow(len(polyhedra))))
795
+ g = Graphics()
796
+ for cell in polyhedra:
797
+ options = copy(kwds)
798
+ if color == 'rainbow':
799
+ options['color'] = cell_colors_dict[cell]
800
+ g += cell.plot(**options)
801
+ return g
802
+
803
+ def is_pure(self) -> bool:
804
+ """
805
+ Test if this polyhedral complex is pure.
806
+
807
+ A polyhedral complex is pure if and only if all of its maximal cells
808
+ have the same dimension.
809
+
810
+ .. WARNING::
811
+
812
+ This may give the wrong answer if the polyhedral complex
813
+ was constructed with ``maximality_check`` set to ``False``.
814
+
815
+ EXAMPLES::
816
+
817
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
818
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
819
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
820
+ sage: pc = PolyhedralComplex([p1, p2, p3])
821
+ sage: pc.is_pure()
822
+ True
823
+
824
+ Wrong answer due to ``maximality_check=False``::
825
+
826
+ sage: pc_invalid = PolyhedralComplex([p1, p2, p3],
827
+ ....: maximality_check=False)
828
+ sage: pc_invalid.is_pure()
829
+ False
830
+ """
831
+ return len(self._maximal_cells) == 1
832
+
833
+ def is_full_dimensional(self) -> bool:
834
+ """
835
+ Return whether this polyhedral complex is full-dimensional.
836
+
837
+ This means that its dimension is equal to its ambient dimension.
838
+
839
+ EXAMPLES::
840
+
841
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
842
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
843
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
844
+ sage: pc = PolyhedralComplex([p1, p2, p3])
845
+ sage: pc.is_full_dimensional()
846
+ True
847
+ sage: PolyhedralComplex([p3]).is_full_dimensional()
848
+ False
849
+ """
850
+ return self._dim == self._ambient_dim
851
+
852
+ def __hash__(self) -> int:
853
+ """
854
+ Compute the hash value of ``self`` using its ``maximal_cells_sorted``.
855
+
856
+ EXAMPLES::
857
+
858
+ sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)])
859
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])
860
+ sage: pc1 = PolyhedralComplex([p1, p2], is_mutable=False)
861
+ sage: hash(pc1) == hash(pc1)
862
+ True
863
+ sage: pc2 = PolyhedralComplex([p2, p1], is_mutable=False)
864
+ sage: hash(pc1) == hash(pc2)
865
+ True
866
+ sage: pc3 = PolyhedralComplex([p1, p2])
867
+ sage: hash(pc3)
868
+ Traceback (most recent call last):
869
+ ...
870
+ ValueError: this polyhedral complex must be immutable; call set_immutable()
871
+ """
872
+ if not self._is_immutable:
873
+ raise ValueError("this polyhedral complex must be immutable; " +
874
+ "call set_immutable()")
875
+ return hash(tuple(self.maximal_cells_sorted()))
876
+
877
+ def __eq__(self, right) -> bool:
878
+ """
879
+ Two polyhedral complexes are equal iff their maximal cells are equal.
880
+
881
+ EXAMPLES::
882
+
883
+ sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)])
884
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])
885
+ sage: pc1 = PolyhedralComplex([p1, p2])
886
+ sage: pc1 == pc1
887
+ True
888
+ sage: pc2 = PolyhedralComplex([p2, p1])
889
+ sage: pc1 == pc2
890
+ True
891
+ """
892
+ return isinstance(right, PolyhedralComplex) and (
893
+ self.maximal_cells_sorted() == right.maximal_cells_sorted())
894
+
895
+ def __ne__(self, right) -> bool:
896
+ """
897
+ Return ``True`` if ``self`` and ``right`` are not equal.
898
+
899
+ EXAMPLES::
900
+
901
+ sage: pc1 = PolyhedralComplex([
902
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)])])
903
+ sage: pc2 = PolyhedralComplex([
904
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])])
905
+ sage: pc1 != pc2
906
+ True
907
+ """
908
+ return not self.__eq__(right)
909
+
910
+ def __copy__(self):
911
+ """
912
+ Return a mutable copy of ``self``.
913
+
914
+ EXAMPLES::
915
+
916
+ sage: pc1 = PolyhedralComplex([Polyhedron(vertices=[(0, 0)])])
917
+ sage: pc2 = copy(pc1)
918
+ sage: pc1 == pc2
919
+ True
920
+ """
921
+ return PolyhedralComplex(self._maximal_cells, maximality_check=False,
922
+ backend=self._backend)
923
+
924
+ def _an_element_(self):
925
+ """
926
+ Return a (maximal) cell of this complex.
927
+
928
+ EXAMPLES::
929
+
930
+ sage: PolyhedralComplex()._an_element_()
931
+ Traceback (most recent call last):
932
+ ...
933
+ EmptySetError: the complex is empty
934
+ sage: pc = PolyhedralComplex([
935
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]),
936
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])])
937
+ sage: element = pc._an_element_().vertices_list()
938
+ sage: element # random output (one of the two maximal cells)
939
+ [[0, 0], [0, 1/2], [1, 2]]
940
+ sage: element in ([[0, 0], [0, 1/2], [1, 2]], [[0, 0], [1/3, 1/3], [1, 2]])
941
+ True
942
+ """
943
+ try:
944
+ return next(self.maximal_cell_iterator(increasing=False))
945
+ except StopIteration:
946
+ from sage.categories.sets_cat import EmptySetError
947
+ raise EmptySetError("the complex is empty")
948
+
949
+ def __contains__(self, x) -> bool:
950
+ """
951
+ Return ``True`` if ``x`` is a polyhedron which is contained in this complex.
952
+
953
+ EXAMPLES::
954
+
955
+ sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)])
956
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])
957
+ sage: pc = PolyhedralComplex([p1, p2])
958
+ sage: (p1 in pc) and (p2 in pc)
959
+ True
960
+ sage: Polyhedron(vertices=[(1, 2), (0, 0)]) in pc
961
+ True
962
+ sage: Polyhedron(vertices=[(1, 1), (0, 0)]) in pc
963
+ False
964
+ sage: Polyhedron(vertices=[(0, 0)]) in pc
965
+ True
966
+ sage: (0, 0) in pc # not a polyhedron
967
+ False
968
+ """
969
+ if not isinstance(x, sage.geometry.abc.Polyhedron):
970
+ return False
971
+ dim = x.dimension()
972
+ return dim in self.cells() and x in self.cells()[dim]
973
+
974
+ def __call__(self, x):
975
+ """
976
+ If ``x`` is a polyhedron in this complex, return it.
977
+ Otherwise, raise a :exc:`ValueError`.
978
+
979
+ EXAMPLES::
980
+
981
+ sage: pc = PolyhedralComplex([
982
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]),
983
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])])
984
+ sage: pc(Polyhedron(vertices=[(1, 2), (0, 0)]))
985
+ A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices
986
+ sage: pc(Polyhedron(vertices=[(1, 1)]))
987
+ Traceback (most recent call last):
988
+ ...
989
+ ValueError: the polyhedron is not in this complex
990
+ """
991
+ if x not in self:
992
+ raise ValueError('the polyhedron is not in this complex')
993
+ return x
994
+
995
+ def face_poset(self):
996
+ r"""
997
+ The face poset of this polyhedral complex, the poset of
998
+ nonempty cells, ordered by inclusion.
999
+
1000
+ EXAMPLES::
1001
+
1002
+ sage: pc = PolyhedralComplex([
1003
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]),
1004
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])])
1005
+ sage: poset = pc.face_poset()
1006
+ sage: poset
1007
+ Finite poset containing 11 elements
1008
+ sage: d = {i: i.vertices_matrix() for i in poset}
1009
+ sage: poset.plot(element_labels=d) # needs sage.plot
1010
+ Graphics object consisting of 28 graphics primitives
1011
+
1012
+ For a nonbounded polyhedral complex::
1013
+
1014
+ sage: pc = PolyhedralComplex([
1015
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]),
1016
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]),
1017
+ ....: Polyhedron(vertices=[(-1/2, -1/2)], lines=[(1, -1)]),
1018
+ ....: Polyhedron(rays=[(1, 0)])])
1019
+ sage: poset = pc.face_poset()
1020
+ sage: poset
1021
+ Finite poset containing 13 elements
1022
+ sage: d = {i:''.join([str(v)+'\n'
1023
+ ....: for v in i.Vrepresentation()]) for i in poset}
1024
+ sage: poset.show(element_labels=d, figsize=15) # not tested
1025
+ sage: pc = PolyhedralComplex([
1026
+ ....: Polyhedron(rays=[(1,0),(0,1)]),
1027
+ ....: Polyhedron(rays=[(-1,0),(0,1)]),
1028
+ ....: Polyhedron(rays=[(-1,0),(0,-1)]),
1029
+ ....: Polyhedron(rays=[(1,0),(0,-1)])])
1030
+ sage: pc.face_poset()
1031
+ Finite poset containing 9 elements
1032
+ """
1033
+ if self._face_poset is None:
1034
+ self.cells() # poset is obtained and cached in cells()
1035
+ return self._face_poset
1036
+
1037
+ def is_subcomplex(self, other) -> bool:
1038
+ r"""
1039
+ Return whether ``self`` is a subcomplex of ``other``.
1040
+
1041
+ INPUT:
1042
+
1043
+ - ``other`` -- a polyhedral complex
1044
+
1045
+ Each maximal cell of ``self`` must be a cell of ``other``
1046
+ for this to be ``True``.
1047
+
1048
+ EXAMPLES::
1049
+
1050
+ sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)])
1051
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])
1052
+ sage: p3 = Polyhedron(vertices=[(0, 0), (1, 0)])
1053
+ sage: pc = PolyhedralComplex([p1, Polyhedron(vertices=[(1, 0)])])
1054
+ sage: pc.is_subcomplex(PolyhedralComplex([p1, p2, p3]))
1055
+ True
1056
+ sage: pc.is_subcomplex(PolyhedralComplex([p1, p2]))
1057
+ False
1058
+ """
1059
+ other_cells = other.cells()
1060
+ for (d, stratum) in self.maximal_cells().items():
1061
+ if not stratum.issubset(other_cells.get(d, set())):
1062
+ return False
1063
+ return True
1064
+
1065
+ def is_compact(self) -> bool:
1066
+ """
1067
+ Test for boundedness of the polyhedral complex.
1068
+
1069
+ EXAMPLES::
1070
+
1071
+ sage: p1 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])
1072
+ sage: p2 = Polyhedron(rays=[(1, 0)])
1073
+ sage: PolyhedralComplex([p1]).is_compact()
1074
+ True
1075
+ sage: PolyhedralComplex([p1, p2]).is_compact()
1076
+ False
1077
+ """
1078
+ return all(p.is_compact() for p in self.maximal_cell_iterator())
1079
+
1080
+ def graph(self):
1081
+ """
1082
+ Return the 1-skeleton of this polyhedral complex, as a graph.
1083
+
1084
+ The vertices of the graph are of type ``vector``. This raises
1085
+ a :exc:`NotImplementedError` if the polyhedral complex is unbounded.
1086
+
1087
+ .. WARNING::
1088
+
1089
+ This may give the wrong answer if the polyhedral complex
1090
+ was constructed with ``maximality_check`` set to ``False``.
1091
+
1092
+ EXAMPLES::
1093
+
1094
+ sage: pc = PolyhedralComplex([
1095
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
1096
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
1097
+ sage: g = pc.graph(); g
1098
+ Graph on 4 vertices
1099
+ sage: g.vertices(sort=True)
1100
+ [(0, 0), (0, 2), (1, 1), (1, 2)]
1101
+ sage: g.edges(sort=True, labels=False)
1102
+ [((0, 0), (0, 2)), ((0, 0), (1, 1)), ((0, 0), (1, 2)), ((0, 2), (1, 2)), ((1, 1), (1, 2))]
1103
+ sage: PolyhedralComplex([Polyhedron(rays=[(1,1)])]).graph()
1104
+ Traceback (most recent call last):
1105
+ ...
1106
+ NotImplementedError: the polyhedral complex is unbounded
1107
+
1108
+ Wrong answer due to ``maximality_check=False``::
1109
+
1110
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1111
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1112
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
1113
+ sage: PolyhedralComplex([p1, p2]).is_pure()
1114
+ True
1115
+ sage: PolyhedralComplex([p2, p3], maximality_check=True).is_pure()
1116
+ True
1117
+ sage: PolyhedralComplex([p2, p3], maximality_check=False).is_pure()
1118
+ False
1119
+ """
1120
+ if not self.is_compact():
1121
+ raise NotImplementedError("the polyhedral complex is unbounded")
1122
+ edges = self.n_cells(1)
1123
+ d = {}
1124
+ for e in edges:
1125
+ v, max_e = sorted(e.vertices_matrix().columns())
1126
+ if v in d:
1127
+ d[v].append(max_e)
1128
+ else:
1129
+ d[v] = [max_e]
1130
+ for v in self.n_maximal_cells(0):
1131
+ d[v] = []
1132
+ return Graph(d)
1133
+
1134
+ def is_connected(self) -> bool:
1135
+ """
1136
+ Return whether ``self`` is connected.
1137
+
1138
+ EXAMPLES::
1139
+
1140
+ sage: pc1 = PolyhedralComplex([
1141
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
1142
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
1143
+ sage: pc1.is_connected()
1144
+ True
1145
+ sage: pc2 = PolyhedralComplex([
1146
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
1147
+ ....: Polyhedron(vertices=[(0, 2)])])
1148
+ sage: pc2.is_connected()
1149
+ False
1150
+ sage: pc3 = PolyhedralComplex([
1151
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]),
1152
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]),
1153
+ ....: Polyhedron(vertices=[(-1/2, -1/2)], lines=[(1, -1)]),
1154
+ ....: Polyhedron(rays=[(1, 0)])])
1155
+ sage: pc3.is_connected()
1156
+ False
1157
+ sage: pc4 = PolyhedralComplex([
1158
+ ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]),
1159
+ ....: Polyhedron(rays=[(1, 0)])])
1160
+ sage: pc4.is_connected()
1161
+ True
1162
+ """
1163
+ if self.is_compact():
1164
+ return self.graph().is_connected() # faster than using poset?
1165
+ else:
1166
+ return self.face_poset().is_connected()
1167
+
1168
+ def connected_component(self, cell=None):
1169
+ """
1170
+ Return the connected component of this polyhedral complex
1171
+ containing a given cell.
1172
+
1173
+ INPUT:
1174
+
1175
+ - ``cell`` -- (default: ``self.an_element()``) a cell of ``self``
1176
+
1177
+ OUTPUT:
1178
+
1179
+ The connected component containing ``cell``. If the polyhedral complex
1180
+ is empty or if it does not contain the given cell, raise an error.
1181
+
1182
+ EXAMPLES::
1183
+
1184
+ sage: t1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1185
+ sage: t2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1186
+ sage: v1 = Polyhedron(vertices=[(1, 1)])
1187
+ sage: v2 = Polyhedron(vertices=[(0, 2)])
1188
+ sage: v3 = Polyhedron(vertices=[(-1, 0)])
1189
+ sage: o = Polyhedron(vertices=[(0, 0)])
1190
+ sage: r = Polyhedron(rays=[(1, 0)])
1191
+ sage: l = Polyhedron(vertices=[(-1, 0)], lines=[(1, -1)])
1192
+ sage: pc1 = PolyhedralComplex([t1, t2])
1193
+ sage: pc1.connected_component() == pc1
1194
+ True
1195
+ sage: pc1.connected_component(v1) == pc1
1196
+ True
1197
+ sage: pc2 = PolyhedralComplex([t1, v2])
1198
+ sage: pc2.connected_component(t1) == PolyhedralComplex([t1])
1199
+ True
1200
+ sage: pc2.connected_component(o) == PolyhedralComplex([t1])
1201
+ True
1202
+ sage: pc2.connected_component(v3)
1203
+ Traceback (most recent call last):
1204
+ ...
1205
+ ValueError: the polyhedral complex does not contain the given cell
1206
+ sage: pc2.connected_component(r)
1207
+ Traceback (most recent call last):
1208
+ ...
1209
+ ValueError: the polyhedral complex does not contain the given cell
1210
+ sage: pc3 = PolyhedralComplex([t1, t2, r])
1211
+ sage: pc3.connected_component(v2) == pc3
1212
+ True
1213
+ sage: pc4 = PolyhedralComplex([t1, t2, r, l])
1214
+ sage: pc4.connected_component(o) == pc3
1215
+ True
1216
+ sage: pc4.connected_component(v3)
1217
+ Traceback (most recent call last):
1218
+ ...
1219
+ ValueError: the polyhedral complex does not contain the given cell
1220
+ sage: pc5 = PolyhedralComplex([t1, t2, r, l, v3])
1221
+ sage: pc5.connected_component(v3) == PolyhedralComplex([v3])
1222
+ True
1223
+ sage: PolyhedralComplex([]).connected_component()
1224
+ Traceback (most recent call last):
1225
+ ...
1226
+ ValueError: the empty polyhedral complex has no connected components
1227
+ """
1228
+ if self.dimension() == -1:
1229
+ raise ValueError(
1230
+ "the empty polyhedral complex has no connected components")
1231
+ if cell is None:
1232
+ cell = self._an_element_()
1233
+ if self.is_compact(): # use graph (faster than poset?)
1234
+ if not cell.is_compact():
1235
+ raise ValueError(
1236
+ "the polyhedral complex does not contain the given cell")
1237
+ v = cell.vertices_matrix().columns()[0]
1238
+ g = self.graph()
1239
+ if v not in g:
1240
+ raise ValueError(
1241
+ "the polyhedral complex does not contain the given cell")
1242
+ vertices = g.connected_component_containing_vertex(v, sort=False)
1243
+ facets = [f for f in self.maximal_cell_iterator()
1244
+ if any(vf in f.vertices_matrix().columns()
1245
+ for vf in vertices)]
1246
+ else: # use face_poset
1247
+ g = self.face_poset().hasse_diagram()
1248
+ if cell not in g:
1249
+ raise ValueError(
1250
+ "the polyhedral complex does not contain the given cell")
1251
+ faces = g.connected_component_containing_vertex(cell, sort=False)
1252
+ facets = [f for f in self.maximal_cell_iterator()
1253
+ if f in faces]
1254
+ return PolyhedralComplex(facets, maximality_check=False,
1255
+ is_immutable=self._is_immutable,
1256
+ backend=self._backend)
1257
+
1258
+ def connected_components(self) -> list:
1259
+ """
1260
+ Return the connected components of this polyhedral complex,
1261
+ as list of (sub-)PolyhedralComplexes.
1262
+
1263
+ EXAMPLES::
1264
+
1265
+ sage: t1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1266
+ sage: t2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1267
+ sage: v1 = Polyhedron(vertices=[(1, 1)])
1268
+ sage: v2 = Polyhedron(vertices=[(0, 2)])
1269
+ sage: v3 = Polyhedron(vertices=[(-1, 0)])
1270
+ sage: o = Polyhedron(vertices=[(0, 0)])
1271
+ sage: r = Polyhedron(rays=[(1, 0)])
1272
+ sage: l = Polyhedron(vertices=[(-1, 0)], lines=[(1, -1)])
1273
+ sage: pc1 = PolyhedralComplex([t1, t2])
1274
+ sage: len(pc1.connected_components())
1275
+ 1
1276
+ sage: pc2 = PolyhedralComplex([t1, v2])
1277
+ sage: len(pc2.connected_components())
1278
+ 2
1279
+ sage: pc3 = PolyhedralComplex([t1, t2, r])
1280
+ sage: len(pc3.connected_components())
1281
+ 1
1282
+ sage: pc4 = PolyhedralComplex([t1, t2, r, l])
1283
+ sage: len(pc4.connected_components())
1284
+ 2
1285
+ sage: pc5 = PolyhedralComplex([t1, t2, r, l, v3])
1286
+ sage: len(pc5.connected_components())
1287
+ 3
1288
+ sage: PolyhedralComplex([]).connected_components()
1289
+ Traceback (most recent call last):
1290
+ ...
1291
+ ValueError: the empty polyhedral complex has no connected components
1292
+ """
1293
+ if self.dimension() == -1:
1294
+ raise ValueError(
1295
+ "the empty polyhedral complex has no connected components")
1296
+ if self.is_compact(): # use graph (faster than poset)?
1297
+ g = self.graph()
1298
+ lists_of_vertices = g.connected_components(sort=False)
1299
+ lists_of_facets = [[f for f in self.maximal_cell_iterator()
1300
+ if any(vf in f.vertices_matrix().columns()
1301
+ for vf in vertices)]
1302
+ for vertices in lists_of_vertices]
1303
+ else: # use face_poset
1304
+ g = self.face_poset().hasse_diagram()
1305
+ lists_of_faces = g.connected_components(sort=False)
1306
+ lists_of_facets = [
1307
+ [f for f in self.maximal_cell_iterator() if f in faces]
1308
+ for faces in lists_of_faces]
1309
+ return [PolyhedralComplex(facets, maximality_check=False,
1310
+ is_immutable=self._is_immutable,
1311
+ backend=self._backend)
1312
+ for facets in lists_of_facets]
1313
+
1314
+ def n_skeleton(self, n):
1315
+ r"""
1316
+ The `n`-skeleton of this polyhedral complex.
1317
+
1318
+ The `n`-skeleton of a polyhedral complex is obtained by discarding
1319
+ all of the cells in dimensions larger than `n`.
1320
+
1321
+ INPUT:
1322
+
1323
+ - ``n`` -- nonnegative integer; the dimension
1324
+
1325
+ .. SEEALSO::
1326
+
1327
+ :meth:`stratify`
1328
+
1329
+ EXAMPLES::
1330
+
1331
+ sage: pc = PolyhedralComplex([
1332
+ ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]),
1333
+ ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])])
1334
+ sage: pc.n_skeleton(2)
1335
+ Polyhedral complex with 2 maximal cells
1336
+ sage: pc.n_skeleton(1)
1337
+ Polyhedral complex with 5 maximal cells
1338
+ sage: pc.n_skeleton(0)
1339
+ Polyhedral complex with 4 maximal cells
1340
+ """
1341
+ if n >= self.dimension():
1342
+ return copy(self)
1343
+ facets = [f for f in self.maximal_cell_iterator() if f.dimension() < n]
1344
+ facets.extend(self.n_cells(n))
1345
+ return PolyhedralComplex(facets, maximality_check=False,
1346
+ is_immutable=self._is_immutable,
1347
+ backend=self._backend)
1348
+
1349
+ def stratify(self, n):
1350
+ r"""
1351
+ Return the pure sub-polyhedral complex which is constructed from the
1352
+ `n`-dimensional maximal cells of this polyhedral complex.
1353
+
1354
+ .. SEEALSO::
1355
+
1356
+ :meth:`n_skeleton`
1357
+
1358
+ .. WARNING::
1359
+
1360
+ This may give the wrong answer if the polyhedral complex
1361
+ was constructed with ``maximality_check`` set to ``False``.
1362
+
1363
+ EXAMPLES::
1364
+
1365
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1366
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1367
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
1368
+ sage: pc = PolyhedralComplex([p1, p2, p3])
1369
+ sage: pc.stratify(2) == pc
1370
+ True
1371
+ sage: pc.stratify(1)
1372
+ Polyhedral complex with 0 maximal cells
1373
+
1374
+ Wrong answer due to ``maximality_check=False``::
1375
+
1376
+ sage: pc_invalid = PolyhedralComplex([p1, p2, p3],
1377
+ ....: maximality_check=False)
1378
+ sage: pc_invalid.stratify(1)
1379
+ Polyhedral complex with 1 maximal cell
1380
+ """
1381
+ n_faces = self.n_maximal_cells(n)
1382
+ return PolyhedralComplex(n_faces, maximality_check=False,
1383
+ is_immutable=self._is_immutable,
1384
+ backend=self._backend)
1385
+
1386
+ def boundary_subcomplex(self):
1387
+ """
1388
+ Return the sub-polyhedral complex that is the boundary of ``self``.
1389
+
1390
+ A point `P` is on the boundary of a set `S` if `P` is in the
1391
+ closure of `S` but not in the interior of `S`.
1392
+
1393
+ EXAMPLES::
1394
+
1395
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1396
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1397
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
1398
+ sage: bd = PolyhedralComplex([p1, p2]).boundary_subcomplex()
1399
+ sage: len(bd.n_maximal_cells(2))
1400
+ 0
1401
+ sage: len(bd.n_maximal_cells(1))
1402
+ 4
1403
+ sage: pt = PolyhedralComplex([p3])
1404
+ sage: pt.boundary_subcomplex() == pt
1405
+ True
1406
+
1407
+ Test on polyhedral complex which is not pure::
1408
+
1409
+ sage: pc_non_pure = PolyhedralComplex([p1, p3])
1410
+ sage: pc_non_pure.boundary_subcomplex() == pc_non_pure.n_skeleton(1)
1411
+ True
1412
+
1413
+ Test with ``maximality_check == False``::
1414
+
1415
+ sage: pc_invalid = PolyhedralComplex([p2, p3],
1416
+ ....: maximality_check=False)
1417
+ sage: pc_invalid.boundary_subcomplex() == pc_invalid.n_skeleton(1)
1418
+ True
1419
+
1420
+ Test unbounded cases::
1421
+
1422
+ sage: pc1 = PolyhedralComplex([
1423
+ ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]])])
1424
+ sage: pc1.boundary_subcomplex() == pc1.n_skeleton(1)
1425
+ True
1426
+ sage: pc1b = PolyhedralComplex([Polyhedron(
1427
+ ....: vertices=[[1,0,0], [0,1,0]], rays=[[1,0,0],[0,1,0]])])
1428
+ sage: pc1b.boundary_subcomplex() == pc1b
1429
+ True
1430
+ sage: pc2 = PolyhedralComplex([
1431
+ ....: Polyhedron(vertices=[[-1,0], [1,0]], lines=[[0,1]])])
1432
+ sage: pc2.boundary_subcomplex() == pc2.n_skeleton(1)
1433
+ True
1434
+ sage: pc3 = PolyhedralComplex([
1435
+ ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]]),
1436
+ ....: Polyhedron(vertices=[[1,0], [0,-1]], rays=[[1,0], [0,-1]])])
1437
+ sage: pc3.boundary_subcomplex() == pc3.n_skeleton(1)
1438
+ False
1439
+ """
1440
+ if self.is_full_dimensional():
1441
+ return PolyhedralComplex(self.relative_boundary_cells(),
1442
+ is_immutable=self._is_immutable,
1443
+ backend=self._backend)
1444
+ else:
1445
+ ans = copy(self)
1446
+ if self._is_immutable:
1447
+ ans.set_immutable()
1448
+ return ans
1449
+
1450
+ def relative_boundary_cells(self) -> list:
1451
+ r"""
1452
+ Return the maximal cells of the relative-boundary sub-complex.
1453
+
1454
+ A point `P` is in the relative boundary of a set `S` if `P` is in the
1455
+ closure of `S` but not in the relative interior of `S`.
1456
+
1457
+ .. WARNING::
1458
+
1459
+ This may give the wrong answer if the polyhedral complex
1460
+ was constructed with ``maximality_check`` set to ``False``.
1461
+
1462
+ EXAMPLES::
1463
+
1464
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1465
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1466
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
1467
+ sage: p4 = Polyhedron(vertices=[(2, 2)])
1468
+ sage: pc = PolyhedralComplex([p1, p2])
1469
+ sage: rbd_cells = pc.relative_boundary_cells()
1470
+ sage: len(rbd_cells)
1471
+ 4
1472
+ sage: all(p.dimension() == 1 for p in rbd_cells)
1473
+ True
1474
+ sage: pc_lower_dim = PolyhedralComplex([p3])
1475
+ sage: sorted([p.vertices() for p in pc_lower_dim.relative_boundary_cells()])
1476
+ [(A vertex at (0, 2),), (A vertex at (1, 2),)]
1477
+
1478
+ Test on polyhedral complex which is not pure::
1479
+
1480
+ sage: pc_non_pure = PolyhedralComplex([p1, p3, p4])
1481
+ sage: (set(pc_non_pure.relative_boundary_cells())
1482
+ ....: == set([f.as_polyhedron() for f in p1.faces(1)] + [p3, p4]))
1483
+ True
1484
+
1485
+ Test with ``maximality_check == False``::
1486
+
1487
+ sage: pc_invalid = PolyhedralComplex([p2, p3],
1488
+ ....: maximality_check=False)
1489
+ sage: (set(pc_invalid.relative_boundary_cells())
1490
+ ....: == set([f.as_polyhedron() for f in p2.faces(1)]))
1491
+ True
1492
+
1493
+ Test unbounded case::
1494
+
1495
+ sage: pc3 = PolyhedralComplex([
1496
+ ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]]),
1497
+ ....: Polyhedron(vertices=[[1,0], [0,-1]], rays=[[1,0], [0,-1]])])
1498
+ sage: len(pc3.relative_boundary_cells())
1499
+ 4
1500
+ """
1501
+ d = self.dimension()
1502
+ poset = self.face_poset()
1503
+ faces = self.n_cells(d - 1)
1504
+ ans = [face for face in faces if len(poset.upper_covers(face)) == 1]
1505
+ if not self.is_pure():
1506
+ ans.extend(p for p in poset.maximal_elements() if p.dimension() < d)
1507
+ return ans
1508
+
1509
+ def is_convex(self) -> bool:
1510
+ r"""
1511
+ Return whether the set of points in ``self`` is a convex set.
1512
+
1513
+ When ``self`` is convex, the union of its cells is a Polyhedron.
1514
+
1515
+ .. SEEALSO::
1516
+
1517
+ :meth:`union_as_polyhedron`
1518
+
1519
+ EXAMPLES::
1520
+
1521
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1522
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1523
+ sage: p3 = Polyhedron(vertices=[(0, 0), (1, 1), (2, 0)])
1524
+ sage: p4 = Polyhedron(vertices=[(2, 2)])
1525
+ sage: PolyhedralComplex([p1, p2]).is_convex()
1526
+ True
1527
+ sage: PolyhedralComplex([p1, p3]).is_convex()
1528
+ False
1529
+ sage: PolyhedralComplex([p1, p4]).is_convex()
1530
+ False
1531
+
1532
+ Test unbounded cases::
1533
+
1534
+ sage: pc1 = PolyhedralComplex([
1535
+ ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]])])
1536
+ sage: pc1.is_convex()
1537
+ True
1538
+ sage: pc2 = PolyhedralComplex([
1539
+ ....: Polyhedron(vertices=[[-1,0], [1,0]], lines=[[0,1]])])
1540
+ sage: pc2.is_convex()
1541
+ True
1542
+ sage: pc3 = PolyhedralComplex([
1543
+ ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]]),
1544
+ ....: Polyhedron(vertices=[[1,0], [0,-1]], rays=[[1,0], [0,-1]])])
1545
+ sage: pc3.is_convex()
1546
+ False
1547
+ sage: pc4 = PolyhedralComplex([Polyhedron(rays=[[1,0], [-1,1]]),
1548
+ ....: Polyhedron(rays=[[1,0], [-1,-1]])])
1549
+ sage: pc4.is_convex()
1550
+ False
1551
+
1552
+ The whole 3d space minus the first orthant is not convex::
1553
+
1554
+ sage: pc5 = PolyhedralComplex([
1555
+ ....: Polyhedron(rays=[[1,0,0], [0,1,0], [0,0,-1]]),
1556
+ ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,-1]]),
1557
+ ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,1]]),
1558
+ ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,-1]]),
1559
+ ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,1]]),
1560
+ ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,-1]]),
1561
+ ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,1]])])
1562
+ sage: pc5.is_convex()
1563
+ False
1564
+
1565
+ Test some non-full-dimensional examples::
1566
+
1567
+ sage: l = PolyhedralComplex([Polyhedron(vertices=[(1, 2), (0, 2)])])
1568
+ sage: l.is_convex()
1569
+ True
1570
+ sage: pc1b = PolyhedralComplex([Polyhedron(
1571
+ ....: vertices=[[1,0,0], [0,1,0]], rays=[[1,0,0],[0,1,0]])])
1572
+ sage: pc1b.is_convex()
1573
+ True
1574
+ sage: pc4b = PolyhedralComplex([
1575
+ ....: Polyhedron(rays=[[1,0,0], [-1,1,0]]),
1576
+ ....: Polyhedron(rays=[[1,0,0], [-1,-1,0]])])
1577
+ sage: pc4b.is_convex()
1578
+ False
1579
+ """
1580
+ if self._is_convex is not None:
1581
+ return self._is_convex
1582
+ if not self.is_pure():
1583
+ self._is_convex = False
1584
+ return False
1585
+ d = self.dimension()
1586
+ if not self.is_full_dimensional():
1587
+ # if max cells must lie in different subspaces, can't be convex.
1588
+ from sage.modules.free_module import span
1589
+ f = self.n_maximal_cells(d)[0]
1590
+ affine_space = span(f.equations_list(), f.base_ring())
1591
+ for f in self.n_maximal_cells(d)[1::]:
1592
+ if span(f.equations_list(), f.base_ring()) != affine_space:
1593
+ self._is_convex = False
1594
+ return False
1595
+ # orient the (relative) boundary halfspaces toward a strict convex
1596
+ # combination of the vertices. Then check if all vertices are contained
1597
+ # After making sure that the affine hulls of the cells are the same,
1598
+ # it does not matter that is not full dimensional.
1599
+ boundaries = self.relative_boundary_cells()
1600
+ vertices = set()
1601
+ rays = set()
1602
+ lines = set()
1603
+ for cell in boundaries:
1604
+ # it suffices to consider only vertices on the boundaries
1605
+ # Note that a line (as polyhedron) has vertex too
1606
+ for v in cell.vertices_list():
1607
+ vv = vector(v)
1608
+ vv.set_immutable()
1609
+ vertices.add(vv)
1610
+ for cell in self.n_maximal_cells(d):
1611
+ for r in cell.rays_list():
1612
+ rr = vector(r)
1613
+ rr.set_immutable()
1614
+ rays.add(rr)
1615
+ for li in cell.lines_list():
1616
+ ll = vector(li)
1617
+ ll.set_immutable()
1618
+ lines.add(ll)
1619
+ center = sum(vertices) / len(vertices)
1620
+ for cell in boundaries:
1621
+ for equation in cell.equations_list():
1622
+ coeff = vector(equation[1::])
1623
+ const = equation[0]
1624
+ if const + coeff * center == 0:
1625
+ sign = 0
1626
+ elif const + coeff * center > 0:
1627
+ sign = 1
1628
+ for v in vertices:
1629
+ if const + coeff * v < 0:
1630
+ self._is_convex = False
1631
+ return False
1632
+ elif const + coeff * center < 0:
1633
+ sign = -1
1634
+ for v in vertices:
1635
+ if const + coeff * v > 0:
1636
+ self._is_convex = False
1637
+ return False
1638
+ for r in rays:
1639
+ if sign == 0:
1640
+ sign = coeff * r
1641
+ else:
1642
+ if sign * (coeff * r) < 0:
1643
+ self._is_convex = False
1644
+ return False
1645
+ # lines are in the affine space of each boundary cell already
1646
+ self._is_convex = True
1647
+ self._polyhedron = Polyhedron(vertices=vertices, rays=rays, lines=lines,
1648
+ backend=self._backend)
1649
+ return True
1650
+
1651
+ def union_as_polyhedron(self):
1652
+ """
1653
+ Return ``self`` as a :class:`Polyhedron` if ``self`` is convex.
1654
+
1655
+ EXAMPLES::
1656
+
1657
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1658
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1659
+ sage: p3 = Polyhedron(vertices=[(0, 0), (1, 1), (2, 0)])
1660
+ sage: P = PolyhedralComplex([p1, p2]).union_as_polyhedron()
1661
+ sage: P.vertices_list()
1662
+ [[0, 0], [0, 2], [1, 1], [1, 2]]
1663
+ sage: PolyhedralComplex([p1, p3]).union_as_polyhedron()
1664
+ Traceback (most recent call last):
1665
+ ...
1666
+ ValueError: the polyhedral complex is not convex
1667
+ """
1668
+ if not self.is_convex():
1669
+ raise ValueError("the polyhedral complex is not convex")
1670
+ return self._polyhedron
1671
+
1672
+ def product(self, right):
1673
+ """
1674
+ The (Cartesian) product of this polyhedral complex with another one.
1675
+
1676
+ INPUT:
1677
+
1678
+ - ``right`` -- the other polyhedral complex (the right-hand factor)
1679
+
1680
+ OUTPUT: the product ``self x right``
1681
+
1682
+ EXAMPLES::
1683
+
1684
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1685
+ sage: pc_square = pc.product(pc)
1686
+ sage: pc_square
1687
+ Polyhedral complex with 1 maximal cell
1688
+ sage: next(pc_square.maximal_cell_iterator()).vertices()
1689
+ (A vertex at (0, 0),
1690
+ A vertex at (0, 1),
1691
+ A vertex at (1, 0),
1692
+ A vertex at (1, 1))
1693
+ """
1694
+ maximal_cells = [f.product(g) for f in self.maximal_cell_iterator()
1695
+ for g in right.maximal_cell_iterator()]
1696
+ return PolyhedralComplex(maximal_cells, maximality_check=False,
1697
+ is_immutable=(self._is_immutable and
1698
+ right._is_immutable),
1699
+ backend=self._backend)
1700
+
1701
+ def disjoint_union(self, right):
1702
+ """
1703
+ The disjoint union of this polyhedral complex with another one.
1704
+
1705
+ INPUT:
1706
+
1707
+ - ``right`` -- the other polyhedral complex (the right-hand factor)
1708
+
1709
+ EXAMPLES::
1710
+
1711
+ sage: p1 = Polyhedron(vertices=[(-1, 0), (0, 0), (0, 1)])
1712
+ sage: p2 = Polyhedron(vertices=[(0, -1), (0, 0), (1, 0)])
1713
+ sage: p3 = Polyhedron(vertices=[(0, -1), (1, -1), (1, 0)])
1714
+ sage: pc = PolyhedralComplex([p1]).disjoint_union(PolyhedralComplex([p3]))
1715
+ sage: set(pc.maximal_cell_iterator()) == set([p1, p3])
1716
+ True
1717
+ sage: pc.disjoint_union(PolyhedralComplex([p2]))
1718
+ Traceback (most recent call last):
1719
+ ...
1720
+ ValueError: the two complexes are not disjoint
1721
+ """
1722
+ maximal_cells_self = list(self.maximal_cell_iterator())
1723
+ maximal_cells_right = list(right.maximal_cell_iterator())
1724
+ for cell in maximal_cells_self:
1725
+ for cell_right in maximal_cells_right:
1726
+ if not cell.intersection(cell_right).is_empty():
1727
+ raise ValueError("the two complexes are not disjoint")
1728
+ return PolyhedralComplex(maximal_cells_self + maximal_cells_right,
1729
+ maximality_check=False,
1730
+ face_to_face_check=False,
1731
+ is_immutable=(self._is_immutable and
1732
+ right._is_immutable),
1733
+ backend=self._backend)
1734
+
1735
+ def union(self, right):
1736
+ """
1737
+ The union of this polyhedral complex with another one.
1738
+
1739
+ INPUT:
1740
+
1741
+ - ``right`` -- the other polyhedral complex (the right-hand factor)
1742
+
1743
+ EXAMPLES::
1744
+
1745
+ sage: p1 = Polyhedron(vertices=[(-1, 0), (0, 0), (0, 1)])
1746
+ sage: p2 = Polyhedron(vertices=[(0, -1), (0, 0), (1, 0)])
1747
+ sage: p3 = Polyhedron(vertices=[(0, -1), (1, -1), (1, 0)])
1748
+ sage: pc = PolyhedralComplex([p1]).union(PolyhedralComplex([p3]))
1749
+ sage: set(pc.maximal_cell_iterator()) == set([p1, p3])
1750
+ True
1751
+ sage: pc.union(PolyhedralComplex([p2]))
1752
+ Polyhedral complex with 3 maximal cells
1753
+ sage: p4 = Polyhedron(vertices=[(0, -1), (0, 0), (1, 0), (1, -1)])
1754
+ sage: pc.union(PolyhedralComplex([p4]))
1755
+ Traceback (most recent call last):
1756
+ ...
1757
+ ValueError: the given cells are not face-to-face
1758
+ """
1759
+ maximal_cells = list(self.maximal_cell_iterator()) + list(
1760
+ right.maximal_cell_iterator())
1761
+ return PolyhedralComplex(maximal_cells, maximality_check=True,
1762
+ face_to_face_check=True,
1763
+ is_immutable=(self._is_immutable and
1764
+ right._is_immutable),
1765
+ backend=self._backend)
1766
+
1767
+ def join(self, right):
1768
+ """
1769
+ The join of this polyhedral complex with another one.
1770
+
1771
+ INPUT:
1772
+
1773
+ - ``right`` -- the other polyhedral complex (the right-hand factor)
1774
+
1775
+ EXAMPLES::
1776
+
1777
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1778
+ sage: pc_join = pc.join(pc)
1779
+ sage: pc_join
1780
+ Polyhedral complex with 1 maximal cell
1781
+ sage: next(pc_join.maximal_cell_iterator()).vertices()
1782
+ (A vertex at (0, 0, 0),
1783
+ A vertex at (0, 0, 1),
1784
+ A vertex at (0, 1, 1),
1785
+ A vertex at (1, 0, 0))
1786
+ """
1787
+ maximal_cells = [f.join(g) for f in self.maximal_cell_iterator()
1788
+ for g in right.maximal_cell_iterator()]
1789
+ return PolyhedralComplex(maximal_cells, maximality_check=False,
1790
+ is_immutable=(self._is_immutable and
1791
+ right._is_immutable),
1792
+ backend=self._backend)
1793
+
1794
+ ############################################################
1795
+ # abstract methods not implemented in generic cell complex
1796
+ ############################################################
1797
+
1798
+ def wedge(self, right):
1799
+ """
1800
+ The wedge (one-point union) of ``self`` with ``right``.
1801
+
1802
+ .. TODO::
1803
+
1804
+ Implement the wedge product of two polyhedral complexes.
1805
+
1806
+ EXAMPLES::
1807
+
1808
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1809
+ sage: pc.wedge(pc)
1810
+ Traceback (most recent call last):
1811
+ ...
1812
+ NotImplementedError: wedge is not implemented for polyhedral complex
1813
+ """
1814
+ raise NotImplementedError("wedge is not implemented for "
1815
+ + "polyhedral complex")
1816
+
1817
+ ############################################################
1818
+ # chain complexes, homology
1819
+ ############################################################
1820
+ def chain_complex(self, subcomplex=None, augmented=False,
1821
+ verbose=False, check=True, dimensions=None,
1822
+ base_ring=ZZ, cochain=False):
1823
+ """
1824
+ The chain complex associated to this polyhedral complex.
1825
+
1826
+ .. TODO::
1827
+
1828
+ Implement chain complexes of a polyhedral complex.
1829
+
1830
+ EXAMPLES::
1831
+
1832
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1833
+ sage: pc.chain_complex()
1834
+ Traceback (most recent call last):
1835
+ ...
1836
+ NotImplementedError: chain_complex is not implemented for polyhedral complex
1837
+ """
1838
+ raise NotImplementedError("chain_complex is not implemented for "
1839
+ + "polyhedral complex")
1840
+
1841
+ def alexander_whitney(self, cell, dim_left):
1842
+ """
1843
+ The decomposition of ``cell`` in this complex into left and right
1844
+ factors, suitable for computing cup products.
1845
+
1846
+ .. TODO::
1847
+
1848
+ Implement :meth:`alexander_whitney` of a polyhedral complex.
1849
+
1850
+ EXAMPLES::
1851
+
1852
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1853
+ sage: pc.alexander_whitney(None, 1)
1854
+ Traceback (most recent call last):
1855
+ ...
1856
+ NotImplementedError: alexander_whitney is not implemented for polyhedral complex
1857
+ """
1858
+ raise NotImplementedError("alexander_whitney is not implemented for "
1859
+ + "polyhedral complex")
1860
+
1861
+ ############################################################
1862
+ # end of chain complexes, homology
1863
+ ############################################################
1864
+
1865
+ # this function overrides the standard one for GenericCellComplex,
1866
+ # this one counts the number of maximal cells, not all cells, to
1867
+ # avoid calling and computing self.cells()
1868
+ def _repr_(self) -> str:
1869
+ """
1870
+ Print representation.
1871
+
1872
+ .. WARNING::
1873
+
1874
+ This may give the wrong answer if the polyhedral complex
1875
+ was constructed with ``maximality_check`` set to ``False``.
1876
+
1877
+ EXAMPLES::
1878
+
1879
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
1880
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
1881
+ sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)])
1882
+ sage: PolyhedralComplex([p1, p2, p3])
1883
+ Polyhedral complex with 2 maximal cells
1884
+
1885
+ Wrong answer due to ``maximality_check=False``::
1886
+
1887
+ sage: PolyhedralComplex([p1, p2, p3], maximality_check=False)
1888
+ Polyhedral complex with 3 maximal cells
1889
+ """
1890
+ num = len(list(self.maximal_cell_iterator()))
1891
+ if num == 1:
1892
+ return "Polyhedral complex with %s maximal cell" % num
1893
+ else:
1894
+ return "Polyhedral complex with %s maximal cells" % num
1895
+
1896
+ def set_immutable(self) -> None:
1897
+ """
1898
+ Make this polyhedral complex immutable.
1899
+
1900
+ EXAMPLES::
1901
+
1902
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1903
+ sage: pc.is_mutable()
1904
+ True
1905
+ sage: pc.set_immutable()
1906
+ sage: pc.is_mutable()
1907
+ False
1908
+ """
1909
+ self._is_immutable = True
1910
+
1911
+ def is_mutable(self) -> bool:
1912
+ """
1913
+ Return whether ``self`` is mutable.
1914
+
1915
+ EXAMPLES::
1916
+
1917
+ sage: pc1 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1918
+ sage: pc1.is_mutable()
1919
+ True
1920
+ sage: pc2 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])],
1921
+ ....: is_mutable=False)
1922
+ sage: pc2.is_mutable()
1923
+ False
1924
+ sage: pc1 == pc2
1925
+ True
1926
+ sage: pc3 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])],
1927
+ ....: is_immutable=True)
1928
+ sage: pc3.is_mutable()
1929
+ False
1930
+ sage: pc2 == pc3
1931
+ True
1932
+ """
1933
+ return not self._is_immutable
1934
+
1935
+ def is_immutable(self) -> bool:
1936
+ """
1937
+ Return whether ``self`` is immutable.
1938
+
1939
+ EXAMPLES::
1940
+
1941
+ sage: pc1 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])])
1942
+ sage: pc1.is_immutable()
1943
+ False
1944
+ sage: pc2 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])],
1945
+ ....: is_mutable=False)
1946
+ sage: pc2.is_immutable()
1947
+ True
1948
+ sage: pc3 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])],
1949
+ ....: is_immutable=True)
1950
+ sage: pc3.is_immutable()
1951
+ True
1952
+ """
1953
+ return self._is_immutable
1954
+
1955
+ def add_cell(self, cell):
1956
+ """
1957
+ Add a cell to this polyhedral complex.
1958
+
1959
+ INPUT:
1960
+
1961
+ - ``cell`` -- a polyhedron
1962
+
1963
+ This **changes** the polyhedral complex, by adding a new cell and all
1964
+ of its subfaces.
1965
+
1966
+ EXAMPLES:
1967
+
1968
+ Set up an empty complex in the intended ambient space, then add a cell::
1969
+
1970
+ sage: pc = PolyhedralComplex(ambient_dim=2)
1971
+ sage: pc.add_cell(Polyhedron(vertices=[(1, 2), (0, 2)]))
1972
+ sage: pc
1973
+ Polyhedral complex with 1 maximal cell
1974
+
1975
+ If you add a cell which is already present, there is no effect::
1976
+
1977
+ sage: pc.add_cell(Polyhedron(vertices=[(1, 2)]))
1978
+ sage: pc
1979
+ Polyhedral complex with 1 maximal cell
1980
+ sage: pc.dimension()
1981
+ 1
1982
+
1983
+ Add a cell and check that dimension is correctly updated::
1984
+
1985
+ sage: pc.add_cell(Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]))
1986
+ sage: pc.dimension()
1987
+ 2
1988
+ sage: pc.maximal_cells()
1989
+ {2: {A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices}}
1990
+ sage: pc.is_convex()
1991
+ True
1992
+
1993
+ Add another cell and check that the properties are correctly updated::
1994
+
1995
+ sage: pc.add_cell(Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]))
1996
+ sage: pc
1997
+ Polyhedral complex with 2 maximal cells
1998
+ sage: len(pc._cells[1])
1999
+ 5
2000
+ sage: pc._face_poset
2001
+ Finite poset containing 11 elements
2002
+ sage: pc._is_convex
2003
+ True
2004
+ sage: pc._polyhedron.vertices_list()
2005
+ [[0, 0], [0, 2], [1, 1], [1, 2]]
2006
+
2007
+ Add a ray which makes the complex non convex::
2008
+
2009
+ sage: pc.add_cell(Polyhedron(rays=[(1, 0)]))
2010
+ sage: pc
2011
+ Polyhedral complex with 3 maximal cells
2012
+ sage: len(pc._cells[1])
2013
+ 6
2014
+ sage: (pc._is_convex is False) and (pc._polyhedron is None)
2015
+ True
2016
+
2017
+ TESTS::
2018
+
2019
+ sage: pc.add_cell(Polyhedron(vertices=[[0]]))
2020
+ Traceback (most recent call last):
2021
+ ...
2022
+ ValueError: the given cell is not a polyhedron in the same ambient space
2023
+ sage: pc.add_cell(Polyhedron(vertices=[(1, 1), (0, 0), (2, 0)]))
2024
+ Traceback (most recent call last):
2025
+ ...
2026
+ ValueError: the cell is not face-to-face with complex
2027
+ sage: pc.set_immutable()
2028
+ sage: pc.add_cell(Polyhedron(vertices=[(-1, -1)]))
2029
+ Traceback (most recent call last):
2030
+ ...
2031
+ ValueError: this polyhedral complex is not mutable
2032
+ """
2033
+ if self._is_immutable:
2034
+ raise ValueError("this polyhedral complex is not mutable")
2035
+ if not isinstance(cell, sage.geometry.abc.Polyhedron) or cell.ambient_dim() != self._ambient_dim:
2036
+ raise ValueError("the given cell is not a polyhedron " +
2037
+ "in the same ambient space")
2038
+ # if cell is already in self, do nothing.
2039
+ if self.is_cell(cell):
2040
+ return
2041
+ if self._backend:
2042
+ cell = cell.base_extend(cell.base_ring(), self._backend)
2043
+ # update cells and face poset
2044
+ cells = self.cells()
2045
+ covers = {p: self.face_poset().upper_covers(p)
2046
+ for p in self.cell_iterator()}
2047
+ d = cell.dimension()
2048
+ d_cells = [cell]
2049
+ if d not in cells:
2050
+ cells[d] = set(d_cells)
2051
+ else:
2052
+ cells[d].add(cell)
2053
+ covers[cell] = []
2054
+ while d > 0:
2055
+ d = d - 1
2056
+ new_facets = []
2057
+ for c in d_cells:
2058
+ for facet in c.facets():
2059
+ p = facet.as_polyhedron()
2060
+ if d not in cells:
2061
+ cells[d] = set()
2062
+ if p not in cells[d]:
2063
+ cells[d].add(p)
2064
+ covers[p] = [c]
2065
+ new_facets.append(p)
2066
+ else:
2067
+ covers[p].append(c)
2068
+ d_cells = new_facets
2069
+ self._face_poset = poset = Poset(covers)
2070
+ self._cells = cells
2071
+ # check face-to-face between cell and previous maximal cells
2072
+ for p in self.maximal_cell_iterator():
2073
+ r = p.intersection(cell)
2074
+ if not (r.is_empty() or (r in poset) and
2075
+ poset.is_gequal(p, r) and poset.is_gequal(cell, r)):
2076
+ raise ValueError("the cell is not face-to-face with complex")
2077
+ # update dim and maximal cells
2078
+ d = cell.dimension()
2079
+ self._dim = max(d, self._dim)
2080
+ maximal_cells = poset.maximal_elements() # a list
2081
+ self._maximal_cells = cells_list_to_cells_dict(maximal_cells)
2082
+ # update convexity if self was known to be convex, reset otherwise.
2083
+ if self._is_convex:
2084
+ try:
2085
+ new_complex = PolyhedralComplex([self._polyhedron, cell],
2086
+ face_to_face_check=True)
2087
+ except ValueError:
2088
+ self._is_convex = False
2089
+ self._polyhedron = None
2090
+ else:
2091
+ self._is_convex = new_complex.is_convex()
2092
+ self._polyhedron = new_complex._polyhedron
2093
+ else:
2094
+ self._is_convex = None
2095
+ self._polyhedron = None
2096
+ # reset cached attribute
2097
+ self._maximal_cells_sorted = None # needed for hash
2098
+
2099
+ def remove_cell(self, cell, check=False):
2100
+ r"""
2101
+ Remove ``cell`` from ``self`` and all the cells that contain ``cell``
2102
+ as a subface.
2103
+
2104
+ INPUT:
2105
+
2106
+ - ``cell`` -- a cell of the polyhedral complex
2107
+
2108
+ - ``check`` -- boolean (default: ``False``); if ``True``,
2109
+ raise an error if ``cell`` is not a cell of this complex
2110
+
2111
+ This does not return anything; instead, it **changes** the
2112
+ polyhedral complex.
2113
+
2114
+ EXAMPLES:
2115
+
2116
+ If you add a cell which is already present, there is no effect::
2117
+
2118
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
2119
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
2120
+ sage: r = Polyhedron(rays=[(1, 0)])
2121
+ sage: pc = PolyhedralComplex([p1, p2, r])
2122
+ sage: pc.dimension()
2123
+ 2
2124
+ sage: pc.remove_cell(Polyhedron(vertices=[(0, 0), (1, 2)]))
2125
+ sage: pc.dimension()
2126
+ 1
2127
+ sage: pc
2128
+ Polyhedral complex with 5 maximal cells
2129
+ sage: pc.remove_cell(Polyhedron(vertices=[(1, 2)]))
2130
+ sage: pc.dimension()
2131
+ 1
2132
+ sage: pc
2133
+ Polyhedral complex with 3 maximal cells
2134
+ sage: pc.remove_cell(Polyhedron(vertices=[(0, 0)]))
2135
+ sage: pc.dimension()
2136
+ 0
2137
+
2138
+ TESTS:
2139
+
2140
+ Check that :exc:`ValueError` and empty complex are treated properly::
2141
+
2142
+ sage: p = Polyhedron(vertices=[[1]])
2143
+ sage: pc = PolyhedralComplex([p])
2144
+ sage: pc.remove_cell(Polyhedron(vertices=[[0]]), check=True)
2145
+ Traceback (most recent call last):
2146
+ ...
2147
+ ValueError: trying to remove a cell which is not in the polyhedral complex
2148
+ sage: pc.remove_cell(Polyhedron(vertices=[(1, 1)]))
2149
+ Traceback (most recent call last):
2150
+ ...
2151
+ ValueError: the given cell is not a polyhedron in the same ambient space
2152
+ sage: pc.remove_cell(p)
2153
+ sage: pc.dimension()
2154
+ -1
2155
+ sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0]])], is_mutable=False)
2156
+ sage: pc.remove_cell(Polyhedron(vertices=[[0]]))
2157
+ Traceback (most recent call last):
2158
+ ...
2159
+ ValueError: this polyhedral complex is not mutable
2160
+
2161
+ Check that this function is coherent with
2162
+ :meth:`~sage.topology.simplicial_complex.SimplicialComplex.remove_face`::
2163
+
2164
+ sage: v1 = (1, 0, 0, 0); v2 = (0, 1, 0, 0); v3 = (0, 0, 1, 0); v4 = (0, 0, 0, 1)
2165
+ sage: Z = PolyhedralComplex([Polyhedron(vertices=[v1, v2, v3, v4])]); Z
2166
+ Polyhedral complex with 1 maximal cell
2167
+ sage: Z.remove_cell(Polyhedron(vertices=[v1, v2]))
2168
+ sage: Z
2169
+ Polyhedral complex with 2 maximal cells
2170
+ sage: [c.vertices_list() for c in Z.maximal_cells_sorted()]
2171
+ [[[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]],
2172
+ [[0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 0, 0]]]
2173
+
2174
+ sage: v0 = (0, 0, 0, 0)
2175
+ sage: S = PolyhedralComplex([Polyhedron(vertices=[v0, v1, v2]), Polyhedron(vertices=[v2, v3])])
2176
+ sage: S.maximal_cells()
2177
+ {1: {A 1-dimensional polyhedron in ZZ^4 defined as the convex hull of 2 vertices},
2178
+ 2: {A 2-dimensional polyhedron in ZZ^4 defined as the convex hull of 3 vertices}}
2179
+ sage: S.remove_cell(Polyhedron(vertices=[v0, v1, v2]))
2180
+ sage: S
2181
+ Polyhedral complex with 4 maximal cells
2182
+ sage: [c.vertices_list() for c in S.maximal_cells_sorted()]
2183
+ [[[0, 0, 0, 0], [0, 1, 0, 0]],
2184
+ [[0, 0, 0, 0], [1, 0, 0, 0]],
2185
+ [[0, 0, 1, 0], [0, 1, 0, 0]],
2186
+ [[0, 1, 0, 0], [1, 0, 0, 0]]]
2187
+
2188
+ sage: T = PolyhedralComplex([Polyhedron(vertices=[[1], [2]]), Polyhedron(vertices=[[1], [-3]])])
2189
+ sage: T.remove_cell(Polyhedron(vertices=[[-3], [1]]))
2190
+ sage: [c.vertices_list() for c in T.maximal_cells_sorted()]
2191
+ [[[1], [2]], [[-3]]]
2192
+ sage: [c.vertices_list() for c in T.cells_sorted()]
2193
+ [[[1], [2]], [[-3]], [[1]], [[2]]]
2194
+ """
2195
+ if self._is_immutable:
2196
+ raise ValueError("this polyhedral complex is not mutable")
2197
+ if not isinstance(cell, sage.geometry.abc.Polyhedron) or cell.ambient_dim() != self._ambient_dim:
2198
+ raise ValueError("the given cell is not a polyhedron " +
2199
+ "in the same ambient space")
2200
+ # if cell is not in self, delete nothing.
2201
+ if not self.is_cell(cell): # self.cells() is called
2202
+ if check:
2203
+ raise ValueError("trying to remove a cell which is not " +
2204
+ "in the polyhedral complex")
2205
+ return
2206
+ # update cells and face poset
2207
+ poset = self._face_poset
2208
+ deleting = poset.order_filter([cell])
2209
+ for c in deleting:
2210
+ d = c.dimension()
2211
+ self._cells[d].remove(c)
2212
+ if not self._cells[d]:
2213
+ del self._cells[d]
2214
+ covers = {p: [q for q in poset.upper_covers(p) if q not in deleting]
2215
+ for p in self.cell_iterator()}
2216
+ self._face_poset = Poset(covers)
2217
+ # update dim and maximal cells
2218
+ maximal_cells = self._face_poset.maximal_elements() # a list
2219
+ self._maximal_cells = cells_list_to_cells_dict(maximal_cells)
2220
+ if not maximal_cells:
2221
+ self._dim = -1
2222
+ else:
2223
+ self._dim = max(self._maximal_cells.keys())
2224
+ # reset cached attributes
2225
+ self._maximal_cells_sorted = None # needed for hash
2226
+ self._is_convex = None
2227
+ self._polyhedron = None
2228
+
2229
+ def is_simplicial_complex(self) -> bool:
2230
+ """
2231
+ Test if this polyhedral complex is a simplicial complex.
2232
+
2233
+ A polyhedral complex is **simplicial** if all of its (maximal) cells
2234
+ are simplices, i.e., every cell is a bounded polytope with `d+1`
2235
+ vertices, where `d` is the dimension of the polytope.
2236
+
2237
+ EXAMPLES::
2238
+
2239
+ sage: p1 = Polyhedron(vertices=[(0, 0), (1, 1), (1, 2)])
2240
+ sage: p2 = Polyhedron(rays=[(1, 0)])
2241
+ sage: PolyhedralComplex([p1]).is_simplicial_complex()
2242
+ True
2243
+ sage: PolyhedralComplex([p2]).is_simplicial_complex()
2244
+ False
2245
+ """
2246
+ return all(p.is_simplex() for p in self.maximal_cell_iterator())
2247
+
2248
+ def is_polyhedral_fan(self) -> bool:
2249
+ """
2250
+ Test if this polyhedral complex is a polyhedral fan.
2251
+
2252
+ A polyhedral complex is a **fan** if all of its (maximal) cells
2253
+ are cones.
2254
+
2255
+ EXAMPLES::
2256
+
2257
+ sage: p1 = Polyhedron(vertices=[(0, 0), (1, 1), (1, 2)])
2258
+ sage: p2 = Polyhedron(rays=[(1, 0)])
2259
+ sage: PolyhedralComplex([p1]).is_polyhedral_fan()
2260
+ False
2261
+ sage: PolyhedralComplex([p2]).is_polyhedral_fan()
2262
+ True
2263
+ sage: halfplane = Polyhedron(rays=[(1, 0), (-1, 0), (0, 1)])
2264
+ sage: PolyhedralComplex([halfplane]).is_polyhedral_fan()
2265
+ True
2266
+ """
2267
+ return all((p.n_vertices() == 1) and (
2268
+ vector(p.vertices_list()[0]) == p.ambient_space().zero())
2269
+ for p in self.maximal_cell_iterator())
2270
+
2271
+ def is_simplicial_fan(self) -> bool:
2272
+ """
2273
+ Test if this polyhedral complex is a simplicial fan.
2274
+
2275
+ A polyhedral complex is a **simplicial fan** if all of its (maximal)
2276
+ cells are simplicial cones, i.e., every cell is a pointed cone (with
2277
+ vertex being the origin) generated by `d` linearly independent rays,
2278
+ where `d` is the dimension of the cone.
2279
+
2280
+ EXAMPLES::
2281
+
2282
+ sage: p1 = Polyhedron(vertices=[(0, 0), (1, 1), (1, 2)])
2283
+ sage: p2 = Polyhedron(rays=[(1, 0)])
2284
+ sage: PolyhedralComplex([p1]).is_simplicial_fan()
2285
+ False
2286
+ sage: PolyhedralComplex([p2]).is_simplicial_fan()
2287
+ True
2288
+ sage: halfplane = Polyhedron(rays=[(1, 0), (-1, 0), (0, 1)])
2289
+ sage: PolyhedralComplex([halfplane]).is_simplicial_fan()
2290
+ False
2291
+ """
2292
+ return self.is_polyhedral_fan() and all(
2293
+ (p.n_lines() == 0 and p.n_rays() == p.dimension())
2294
+ for p in self.maximal_cell_iterator())
2295
+
2296
+ def subdivide(self, make_simplicial=False,
2297
+ new_vertices=None, new_rays=None):
2298
+ """
2299
+ Construct a new polyhedral complex by iterative stellar subdivision of
2300
+ ``self`` for each new vertex/ray given.
2301
+
2302
+ Currently, subdivision is only supported for bounded polyhedral complex
2303
+ or polyhedral fan.
2304
+
2305
+ INPUT:
2306
+
2307
+ - ``make_simplicial`` -- boolean (default: ``False``); if ``True``,
2308
+ the returned polyhedral complex is simplicial
2309
+
2310
+ - ``new_vertices``, ``new_rays`` -- list (optional); new generators
2311
+ to be added during subdivision
2312
+
2313
+ EXAMPLES::
2314
+
2315
+ sage: square_vertices = [(1, 1, 1), (-1, 1, 1), (-1, -1, 1), (1, -1, 1)]
2316
+ sage: pc = PolyhedralComplex([
2317
+ ....: Polyhedron(vertices=[(0, 0, 0)] + square_vertices),
2318
+ ....: Polyhedron(vertices=[(0, 0, 2)] + square_vertices)])
2319
+ sage: pc.is_compact() and not pc.is_simplicial_complex()
2320
+ True
2321
+ sage: subdivided_pc = pc.subdivide(new_vertices=[(0, 0, 1)])
2322
+ sage: subdivided_pc
2323
+ Polyhedral complex with 8 maximal cells
2324
+ sage: subdivided_pc.is_simplicial_complex()
2325
+ True
2326
+ sage: simplicial_pc = pc.subdivide(make_simplicial=True)
2327
+ sage: simplicial_pc
2328
+ Polyhedral complex with 4 maximal cells
2329
+ sage: simplicial_pc.is_simplicial_complex()
2330
+ True
2331
+
2332
+ sage: # needs sage.symbolic
2333
+ sage: fan = PolyhedralComplex([Polyhedron(rays=square_vertices)])
2334
+ sage: fan.is_polyhedral_fan() and not fan.is_simplicial_fan()
2335
+ True
2336
+ sage: fan.subdivide(new_vertices=[(0, 0, 1)])
2337
+ Traceback (most recent call last):
2338
+ ...
2339
+ ValueError: new vertices cannot be used for subdivision
2340
+ sage: subdivided_fan = fan.subdivide(new_rays=[(0, 0, 1)])
2341
+ sage: subdivided_fan
2342
+ Polyhedral complex with 4 maximal cells
2343
+ sage: subdivided_fan.is_simplicial_fan()
2344
+ True
2345
+ sage: simplicial_fan = fan.subdivide(make_simplicial=True)
2346
+ sage: simplicial_fan
2347
+ Polyhedral complex with 2 maximal cells
2348
+ sage: simplicial_fan.is_simplicial_fan()
2349
+ True
2350
+
2351
+ sage: # needs sage.symbolic
2352
+ sage: halfspace = PolyhedralComplex([Polyhedron(rays=[(0, 0, 1)],
2353
+ ....: lines=[(1, 0, 0), (0, 1, 0)])])
2354
+ sage: halfspace.is_simplicial_fan()
2355
+ False
2356
+ sage: subdiv_halfspace = halfspace.subdivide(make_simplicial=True)
2357
+ sage: subdiv_halfspace
2358
+ Polyhedral complex with 4 maximal cells
2359
+ sage: subdiv_halfspace.is_simplicial_fan()
2360
+ True
2361
+ """
2362
+ if self.is_compact():
2363
+ if new_rays:
2364
+ raise ValueError("rays/lines cannot be used for subdivision")
2365
+ # bounded version of `fan.subdivide`; not require rational.
2366
+ vertices = set()
2367
+ if make_simplicial and not self.is_simplicial_complex():
2368
+ for p in self.maximal_cell_iterator():
2369
+ for v in p.vertices_list():
2370
+ vertices.add(tuple(v))
2371
+ if new_vertices:
2372
+ for v in new_vertices:
2373
+ vertices.add(tuple(v))
2374
+ if not vertices:
2375
+ return self # Nothing has to be done
2376
+ # bounded version of `fan._subdivide_stellar`; not require rational.
2377
+ cells = list(self.maximal_cell_iterator())
2378
+ for v in vertices:
2379
+ new = []
2380
+ for cell in cells:
2381
+ if v in cell:
2382
+ for cell_facet in cell.facets():
2383
+ facet = cell_facet.as_polyhedron()
2384
+ if v in facet:
2385
+ continue
2386
+ p = facet.convex_hull(Polyhedron(vertices=[v]))
2387
+ new.append(p)
2388
+ else:
2389
+ new.append(cell)
2390
+ cells = new
2391
+ return PolyhedralComplex(cells, maximality_check=False,
2392
+ backend=self._backend)
2393
+ elif self.is_polyhedral_fan():
2394
+ if new_vertices and any(vi != 0 for v in new_vertices for vi in v):
2395
+ raise ValueError("new vertices cannot be used for subdivision")
2396
+ # mimic :meth:`~sage.geometry.fan <RationalPolyhedralFan>.subdivide`
2397
+ # but here we allow for non-pointed cones, and we subdivide them.
2398
+ rays_normalized = set()
2399
+ self_rays = []
2400
+ cones = []
2401
+ for p in self.maximal_cell_iterator():
2402
+ prays = p.rays_list()
2403
+ for r in prays:
2404
+ r_n = vector(r).normalized()
2405
+ r_n.set_immutable()
2406
+ if r_n not in rays_normalized:
2407
+ rays_normalized.add(r_n)
2408
+ self_rays.append(vector(r))
2409
+ plines = p.lines_list()
2410
+ if not plines:
2411
+ cones.append(p)
2412
+ continue
2413
+ # consider a line as two rays
2414
+ for pl in plines:
2415
+ l_plus = vector(pl).normalized()
2416
+ l_plus.set_immutable()
2417
+ if l_plus not in rays_normalized:
2418
+ rays_normalized.add(l_plus)
2419
+ self_rays.append(vector(pl))
2420
+ l_minus = (-vector(pl)).normalized()
2421
+ l_minus.set_immutable()
2422
+ if l_minus not in rays_normalized:
2423
+ rays_normalized.add(l_minus)
2424
+ self_rays.append(-vector(pl))
2425
+ # subdivide the non-pointed p into pointed cones
2426
+ # we rely on the canonical V-repr of Sage polyhedra.
2427
+ num_lines = len(plines)
2428
+ for neg_rays in powerset(range(num_lines)):
2429
+ lines = [vector(plines[i]) if i not in neg_rays
2430
+ else -vector(plines[i]) for i in range(num_lines)]
2431
+ cones.append(Polyhedron(rays=(prays + lines),
2432
+ backend=self._backend))
2433
+ rays = []
2434
+ if new_rays:
2435
+ for r in new_rays:
2436
+ if vector(r).is_zero():
2437
+ raise ValueError("zero cannot be used for subdivision")
2438
+ r_n = vector(r).normalized()
2439
+ r_n.set_immutable()
2440
+ if r_n not in rays_normalized:
2441
+ rays_normalized.add(r_n)
2442
+ rays.append(vector(r))
2443
+ if make_simplicial and not self.is_simplicial_fan():
2444
+ rays = self_rays + rays
2445
+ if not rays:
2446
+ return self # Nothing has to be done
2447
+ # mimic :class:`RationalPolyhedralFan`._subdivide_stellar(rays)
2448
+ # start with self maximal cells (subdivided into pointed cones)
2449
+ for ray in rays:
2450
+ new = []
2451
+ for cone in cones:
2452
+ if ray in cone:
2453
+ for cone_facet in cone.facets():
2454
+ facet = cone_facet.as_polyhedron()
2455
+ if ray in facet:
2456
+ continue
2457
+ new_cone = facet.convex_hull(Polyhedron(rays=[ray]))
2458
+ new.append(new_cone)
2459
+ else:
2460
+ new.append(cone)
2461
+ cones = new
2462
+ return PolyhedralComplex(cones, maximality_check=False,
2463
+ backend=self._backend)
2464
+ else:
2465
+ # TODO: ``self`` is unbounded, make it projectively simplicial.
2466
+ # (1) homogenize self of dim d to fan in space of dim d+1;
2467
+ # (2) call fan.subdivide(make_simplicial=True);
2468
+ # (3) take section back to the space of dim d.
2469
+ raise NotImplementedError('subdivision of a non-compact polyhedral ' +
2470
+ 'complex that is not a fan is not supported')
2471
+
2472
+ ############################################################
2473
+ # Helper functions
2474
+ ############################################################
2475
+
2476
+
2477
+ def cells_list_to_cells_dict(cells_list) -> dict:
2478
+ r"""
2479
+ Helper function that returns the dictionary whose keys are the dimensions,
2480
+ and the value associated to an integer `d` is the set of `d`-dimensional
2481
+ polyhedra in the given list.
2482
+
2483
+ EXAMPLES::
2484
+
2485
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
2486
+ sage: p2 = Polyhedron(vertices=[(1, 1), (0, 0)])
2487
+ sage: p3 = Polyhedron(vertices=[(0, 0)])
2488
+ sage: p4 = Polyhedron(vertices=[(1, 1)])
2489
+ sage: sage.geometry.polyhedral_complex.cells_list_to_cells_dict([p1, p2, p3, p4])
2490
+ {0: {A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex,
2491
+ A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex},
2492
+ 1: {A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices},
2493
+ 2: {A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices}}
2494
+ """
2495
+ cells_dict = {}
2496
+ for cell in cells_list:
2497
+ d = cell.dimension()
2498
+ if d in cells_dict:
2499
+ cells_dict[d].add(cell)
2500
+ else:
2501
+ cells_dict[d] = set([cell])
2502
+ return cells_dict
2503
+
2504
+
2505
+ def exploded_plot(polyhedra, *,
2506
+ center=None, explosion_factor=1, sticky_vertices=False,
2507
+ sticky_center=True, point=None, **kwds):
2508
+ r"""
2509
+ Return a plot of several ``polyhedra`` in one figure with extra space
2510
+ between them.
2511
+
2512
+ INPUT:
2513
+
2514
+ - ``polyhedra`` -- an iterable of
2515
+ :class:`~sage.geometry.polyhedron.base.Polyhedron_base` objects
2516
+
2517
+ - ``center`` -- (default: ``None``, denoting the origin) the center of
2518
+ explosion
2519
+
2520
+ - ``explosion_factor`` -- (default: 1) a nonnegative number; translate
2521
+ polyhedra by this factor of the distance from ``center`` to their center
2522
+
2523
+ - ``sticky_vertices`` -- (default: ``False``) boolean or dict; whether to
2524
+ draw line segments between shared vertices of the given polyhedra. A dict
2525
+ gives options for :func:`sage.plot.line`.
2526
+
2527
+ - ``sticky_center`` -- (default: ``True``) boolean or dict. When ``center``
2528
+ is a vertex of some of the polyhedra, whether to draw line segments
2529
+ connecting the ``center`` to the shifted copies of these vertices. A dict
2530
+ gives options for :func:`sage.plot.line`.
2531
+
2532
+ - ``color`` -- (default: ``None``) if ``'rainbow'``, assign a different
2533
+ color to every maximal cell and every vertex; otherwise, passed on to
2534
+ :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`
2535
+
2536
+ - other keyword arguments are passed on to
2537
+ :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`
2538
+
2539
+ EXAMPLES::
2540
+
2541
+ sage: from sage.geometry.polyhedral_complex import exploded_plot
2542
+ sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])
2543
+ sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])
2544
+ sage: p3 = Polyhedron(vertices=[(0, 0), (1, 1), (2, 0)])
2545
+ sage: exploded_plot([p1, p2, p3]) # needs sage.plot
2546
+ Graphics object consisting of 20 graphics primitives
2547
+ sage: exploded_plot([p1, p2, p3], center=(1, 1)) # needs sage.plot
2548
+ Graphics object consisting of 19 graphics primitives
2549
+ sage: exploded_plot([p1, p2, p3], center=(1, 1), sticky_vertices=True) # needs sage.plot
2550
+ Graphics object consisting of 23 graphics primitives
2551
+ """
2552
+ from sage.plot.colors import rainbow
2553
+ from sage.plot.graphics import Graphics
2554
+ from sage.plot.line import line
2555
+ from sage.plot.point import point as plot_point
2556
+ import itertools
2557
+
2558
+ polyhedra = list(polyhedra)
2559
+ g = Graphics()
2560
+ if not polyhedra:
2561
+ return g
2562
+ dim = polyhedra[0].ambient_dimension()
2563
+ if center is None:
2564
+ from sage.rings.rational_field import QQ
2565
+ center = vector(QQ, dim)
2566
+ else:
2567
+ center = vector(center)
2568
+ translations = [explosion_factor * ((p.center()
2569
+ + sum(r.vector() for r in p.rays()))
2570
+ - center)
2571
+ for p in polyhedra]
2572
+ vertex_translations_dict = {}
2573
+ for P, t in zip(polyhedra, translations):
2574
+ for v in P.vertices():
2575
+ v = v.vector()
2576
+ v.set_immutable()
2577
+ vertex_translations_dict[v] = vertex_translations_dict.get(v, [])
2578
+ vertex_translations_dict[v].append(v + t)
2579
+
2580
+ color = kwds.get('color')
2581
+ if color == 'rainbow':
2582
+ cell_colors_dict = dict(zip(polyhedra,
2583
+ rainbow(len(polyhedra))))
2584
+ for p, t in zip(polyhedra, translations):
2585
+ options = copy(kwds)
2586
+ if color == 'rainbow':
2587
+ options['color'] = cell_colors_dict[p]
2588
+ g += (p + t).plot(point=False, **options)
2589
+
2590
+ if sticky_vertices or sticky_center:
2591
+ if sticky_vertices is True:
2592
+ sticky_vertices = dict(color='gray')
2593
+ if sticky_center is True:
2594
+ sticky_center = dict(color='gray')
2595
+ for vertex, vertex_translations in vertex_translations_dict.items():
2596
+ if vertex == center:
2597
+ if sticky_center:
2598
+ for vt in vertex_translations:
2599
+ g += line((center, vt), **sticky_center)
2600
+ else:
2601
+ if sticky_vertices:
2602
+ for vt1, vt2 in itertools.combinations(vertex_translations, 2):
2603
+ g += line((vt1, vt2), **sticky_vertices)
2604
+ if point is None:
2605
+ # default from sage.geometry.polyhedron.plot
2606
+ point = dict(size=10)
2607
+ if point is not False:
2608
+ if color == 'rainbow':
2609
+ vertex_colors_dict = dict(zip(vertex_translations_dict.keys(),
2610
+ rainbow(len(vertex_translations_dict.keys()))))
2611
+ for vertex, vertex_translations in vertex_translations_dict.items():
2612
+ options = copy(point)
2613
+ if color == 'rainbow':
2614
+ options['color'] = vertex_colors_dict[vertex]
2615
+ g += plot_point(vertex_translations, **options)
2616
+ return g