passagemath-polyhedra 10.6.31rc3__cp314-cp314-musllinux_1_2_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 (208) 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 +208 -0
  4. passagemath_polyhedra-10.6.31rc3.dist-info/WHEEL +5 -0
  5. passagemath_polyhedra-10.6.31rc3.dist-info/top_level.txt +2 -0
  6. passagemath_polyhedra.libs/libgcc_s-0cd532bd.so.1 +0 -0
  7. passagemath_polyhedra.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
  8. passagemath_polyhedra.libs/libgomp-8949ffbe.so.1.0.0 +0 -0
  9. passagemath_polyhedra.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
  10. sage/all__sagemath_polyhedra.py +50 -0
  11. sage/game_theory/all.py +8 -0
  12. sage/game_theory/catalog.py +6 -0
  13. sage/game_theory/catalog_normal_form_games.py +923 -0
  14. sage/game_theory/cooperative_game.py +844 -0
  15. sage/game_theory/matching_game.py +1181 -0
  16. sage/game_theory/normal_form_game.py +2697 -0
  17. sage/game_theory/parser.py +275 -0
  18. sage/geometry/all__sagemath_polyhedra.py +22 -0
  19. sage/geometry/cone.py +6940 -0
  20. sage/geometry/cone_catalog.py +847 -0
  21. sage/geometry/cone_critical_angles.py +1027 -0
  22. sage/geometry/convex_set.py +1119 -0
  23. sage/geometry/fan.py +3743 -0
  24. sage/geometry/fan_isomorphism.py +389 -0
  25. sage/geometry/fan_morphism.py +1884 -0
  26. sage/geometry/hasse_diagram.py +202 -0
  27. sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
  28. sage/geometry/hyperplane_arrangement/all.py +1 -0
  29. sage/geometry/hyperplane_arrangement/arrangement.py +3895 -0
  30. sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
  31. sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
  32. sage/geometry/hyperplane_arrangement/library.py +825 -0
  33. sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
  34. sage/geometry/hyperplane_arrangement/plot.py +520 -0
  35. sage/geometry/integral_points.py +35 -0
  36. sage/geometry/integral_points_generic_dense.cpython-314-x86_64-linux-musl.so +0 -0
  37. sage/geometry/integral_points_generic_dense.pyx +7 -0
  38. sage/geometry/lattice_polytope.py +5894 -0
  39. sage/geometry/linear_expression.py +773 -0
  40. sage/geometry/newton_polygon.py +767 -0
  41. sage/geometry/point_collection.cpython-314-x86_64-linux-musl.so +0 -0
  42. sage/geometry/point_collection.pyx +1008 -0
  43. sage/geometry/polyhedral_complex.py +2616 -0
  44. sage/geometry/polyhedron/all.py +8 -0
  45. sage/geometry/polyhedron/backend_cdd.py +460 -0
  46. sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
  47. sage/geometry/polyhedron/backend_field.py +347 -0
  48. sage/geometry/polyhedron/backend_normaliz.py +2503 -0
  49. sage/geometry/polyhedron/backend_number_field.py +168 -0
  50. sage/geometry/polyhedron/backend_polymake.py +765 -0
  51. sage/geometry/polyhedron/backend_ppl.py +582 -0
  52. sage/geometry/polyhedron/base.py +1206 -0
  53. sage/geometry/polyhedron/base0.py +1444 -0
  54. sage/geometry/polyhedron/base1.py +886 -0
  55. sage/geometry/polyhedron/base2.py +812 -0
  56. sage/geometry/polyhedron/base3.py +1845 -0
  57. sage/geometry/polyhedron/base4.py +1262 -0
  58. sage/geometry/polyhedron/base5.py +2700 -0
  59. sage/geometry/polyhedron/base6.py +1741 -0
  60. sage/geometry/polyhedron/base7.py +997 -0
  61. sage/geometry/polyhedron/base_QQ.py +1258 -0
  62. sage/geometry/polyhedron/base_RDF.py +98 -0
  63. sage/geometry/polyhedron/base_ZZ.py +934 -0
  64. sage/geometry/polyhedron/base_mutable.py +215 -0
  65. sage/geometry/polyhedron/base_number_field.py +122 -0
  66. sage/geometry/polyhedron/cdd_file_format.py +155 -0
  67. sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
  68. sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-x86_64-linux-musl.so +0 -0
  69. sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
  70. sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
  71. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-x86_64-linux-musl.so +0 -0
  72. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
  73. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
  74. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-x86_64-linux-musl.so +0 -0
  75. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
  76. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
  77. sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
  78. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-x86_64-linux-musl.so +0 -0
  79. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
  80. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
  81. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-x86_64-linux-musl.so +0 -0
  82. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
  83. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
  84. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-x86_64-linux-musl.so +0 -0
  85. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
  86. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
  87. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-x86_64-linux-musl.so +0 -0
  88. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
  89. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
  90. sage/geometry/polyhedron/constructor.py +773 -0
  91. sage/geometry/polyhedron/double_description.py +753 -0
  92. sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
  93. sage/geometry/polyhedron/face.py +1060 -0
  94. sage/geometry/polyhedron/generating_function.py +1810 -0
  95. sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
  96. sage/geometry/polyhedron/library.py +3502 -0
  97. sage/geometry/polyhedron/misc.py +121 -0
  98. sage/geometry/polyhedron/modules/all.py +1 -0
  99. sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
  100. sage/geometry/polyhedron/palp_database.py +447 -0
  101. sage/geometry/polyhedron/parent.py +1279 -0
  102. sage/geometry/polyhedron/plot.py +1986 -0
  103. sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
  104. sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
  105. sage/geometry/polyhedron/representation.py +1723 -0
  106. sage/geometry/pseudolines.py +515 -0
  107. sage/geometry/relative_interior.py +445 -0
  108. sage/geometry/toric_plotter.py +1103 -0
  109. sage/geometry/triangulation/all.py +2 -0
  110. sage/geometry/triangulation/base.cpython-314-x86_64-linux-musl.so +0 -0
  111. sage/geometry/triangulation/base.pyx +963 -0
  112. sage/geometry/triangulation/data.h +147 -0
  113. sage/geometry/triangulation/data.pxd +4 -0
  114. sage/geometry/triangulation/element.py +914 -0
  115. sage/geometry/triangulation/functions.h +10 -0
  116. sage/geometry/triangulation/functions.pxd +4 -0
  117. sage/geometry/triangulation/point_configuration.py +2256 -0
  118. sage/geometry/triangulation/triangulations.h +49 -0
  119. sage/geometry/triangulation/triangulations.pxd +7 -0
  120. sage/geometry/voronoi_diagram.py +319 -0
  121. sage/interfaces/all__sagemath_polyhedra.py +1 -0
  122. sage/interfaces/polymake.py +2028 -0
  123. sage/numerical/all.py +13 -0
  124. sage/numerical/all__sagemath_polyhedra.py +11 -0
  125. sage/numerical/backends/all.py +1 -0
  126. sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
  127. sage/numerical/backends/cvxopt_backend.cpython-314-x86_64-linux-musl.so +0 -0
  128. sage/numerical/backends/cvxopt_backend.pyx +1006 -0
  129. sage/numerical/backends/cvxopt_backend_test.py +19 -0
  130. sage/numerical/backends/cvxopt_sdp_backend.cpython-314-x86_64-linux-musl.so +0 -0
  131. sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
  132. sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-musl.so +0 -0
  133. sage/numerical/backends/cvxpy_backend.pxd +41 -0
  134. sage/numerical/backends/cvxpy_backend.pyx +934 -0
  135. sage/numerical/backends/cvxpy_backend_test.py +13 -0
  136. sage/numerical/backends/generic_backend_test.py +24 -0
  137. sage/numerical/backends/interactivelp_backend.cpython-314-x86_64-linux-musl.so +0 -0
  138. sage/numerical/backends/interactivelp_backend.pxd +36 -0
  139. sage/numerical/backends/interactivelp_backend.pyx +1231 -0
  140. sage/numerical/backends/interactivelp_backend_test.py +12 -0
  141. sage/numerical/backends/logging_backend.py +391 -0
  142. sage/numerical/backends/matrix_sdp_backend.cpython-314-x86_64-linux-musl.so +0 -0
  143. sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
  144. sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
  145. sage/numerical/backends/ppl_backend.cpython-314-x86_64-linux-musl.so +0 -0
  146. sage/numerical/backends/ppl_backend.pyx +1126 -0
  147. sage/numerical/backends/ppl_backend_test.py +13 -0
  148. sage/numerical/backends/scip_backend.cpython-314-x86_64-linux-musl.so +0 -0
  149. sage/numerical/backends/scip_backend.pxd +22 -0
  150. sage/numerical/backends/scip_backend.pyx +1289 -0
  151. sage/numerical/backends/scip_backend_test.py +13 -0
  152. sage/numerical/interactive_simplex_method.py +5338 -0
  153. sage/numerical/knapsack.py +665 -0
  154. sage/numerical/linear_functions.cpython-314-x86_64-linux-musl.so +0 -0
  155. sage/numerical/linear_functions.pxd +31 -0
  156. sage/numerical/linear_functions.pyx +1648 -0
  157. sage/numerical/linear_tensor.py +470 -0
  158. sage/numerical/linear_tensor_constraints.py +448 -0
  159. sage/numerical/linear_tensor_element.cpython-314-x86_64-linux-musl.so +0 -0
  160. sage/numerical/linear_tensor_element.pxd +6 -0
  161. sage/numerical/linear_tensor_element.pyx +459 -0
  162. sage/numerical/mip.cpython-314-x86_64-linux-musl.so +0 -0
  163. sage/numerical/mip.pxd +40 -0
  164. sage/numerical/mip.pyx +3667 -0
  165. sage/numerical/sdp.cpython-314-x86_64-linux-musl.so +0 -0
  166. sage/numerical/sdp.pxd +39 -0
  167. sage/numerical/sdp.pyx +1433 -0
  168. sage/rings/all__sagemath_polyhedra.py +3 -0
  169. sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
  170. sage/rings/polynomial/omega.py +982 -0
  171. sage/schemes/all__sagemath_polyhedra.py +2 -0
  172. sage/schemes/toric/all.py +10 -0
  173. sage/schemes/toric/chow_group.py +1248 -0
  174. sage/schemes/toric/divisor.py +2082 -0
  175. sage/schemes/toric/divisor_class.cpython-314-x86_64-linux-musl.so +0 -0
  176. sage/schemes/toric/divisor_class.pyx +322 -0
  177. sage/schemes/toric/fano_variety.py +1606 -0
  178. sage/schemes/toric/homset.py +650 -0
  179. sage/schemes/toric/ideal.py +451 -0
  180. sage/schemes/toric/library.py +1322 -0
  181. sage/schemes/toric/morphism.py +1958 -0
  182. sage/schemes/toric/points.py +1032 -0
  183. sage/schemes/toric/sheaf/all.py +1 -0
  184. sage/schemes/toric/sheaf/constructor.py +302 -0
  185. sage/schemes/toric/sheaf/klyachko.py +921 -0
  186. sage/schemes/toric/toric_subscheme.py +905 -0
  187. sage/schemes/toric/variety.py +3460 -0
  188. sage/schemes/toric/weierstrass.py +1078 -0
  189. sage/schemes/toric/weierstrass_covering.py +457 -0
  190. sage/schemes/toric/weierstrass_higher.py +288 -0
  191. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
  192. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
  193. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
  194. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
  195. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
  196. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
  197. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
  198. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
  199. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
  200. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
  201. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
  202. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
  203. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
  204. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
  205. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
  206. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
  207. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
  208. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_3d +37977 -0
@@ -0,0 +1,2700 @@
1
+ # sage_setup: distribution = sagemath-polyhedra
2
+ r"""
3
+ Base class for polyhedra: Methods for constructing new polyhedra
4
+
5
+ Except for affine hull and affine hull projection.
6
+ """
7
+
8
+ # ****************************************************************************
9
+ # Copyright (C) 2008-2012 Marshall Hampton <hamptonio@gmail.com>
10
+ # Copyright (C) 2011-2015 Volker Braun <vbraun.name@gmail.com>
11
+ # Copyright (C) 2012-2018 Frederic Chapoton
12
+ # Copyright (C) 2013 Andrey Novoseltsev
13
+ # Copyright (C) 2014-2017 Moritz Firsching
14
+ # Copyright (C) 2014-2019 Thierry Monteil
15
+ # Copyright (C) 2015 Nathann Cohen
16
+ # Copyright (C) 2015-2017 Jeroen Demeyer
17
+ # Copyright (C) 2015-2017 Vincent Delecroix
18
+ # Copyright (C) 2015-2018 Dima Pasechnik
19
+ # Copyright (C) 2015-2020 Jean-Philippe Labbe <labbe at math.huji.ac.il>
20
+ # Copyright (C) 2015-2021 Matthias Koeppe
21
+ # Copyright (C) 2016-2019 Daniel Krenn
22
+ # Copyright (C) 2017 Marcelo Forets
23
+ # Copyright (C) 2017-2018 Mark Bell
24
+ # Copyright (C) 2019 Julian Ritter
25
+ # Copyright (C) 2019-2020 Laith Rastanawi
26
+ # Copyright (C) 2019-2020 Sophia Elia
27
+ # Copyright (C) 2019-2021 Jonathan Kliem <jonathan.kliem@gmail.com>
28
+ #
29
+ # This program is free software: you can redistribute it and/or modify
30
+ # it under the terms of the GNU General Public License as published by
31
+ # the Free Software Foundation, either version 2 of the License, or
32
+ # (at your option) any later version.
33
+ # https://www.gnu.org/licenses/
34
+ # ****************************************************************************
35
+
36
+ from sage.features import FeatureNotPresentError
37
+ from sage.structure.element import coerce_binop, Vector, Matrix
38
+
39
+ from sage.rings.integer_ring import ZZ
40
+ from sage.rings.rational_field import QQ
41
+ from sage.matrix.constructor import matrix
42
+
43
+ from sage.modules.free_module_element import vector
44
+
45
+ from .base4 import Polyhedron_base4
46
+
47
+
48
+ class Polyhedron_base5(Polyhedron_base4):
49
+ """
50
+ Methods constructing new polyhedra
51
+ except for affine hull and affine hull projection.
52
+
53
+ See :class:`sage.geometry.polyhedron.base.Polyhedron_base`.
54
+
55
+ TESTS::
56
+
57
+ sage: from sage.geometry.polyhedron.base5 import Polyhedron_base5
58
+ sage: P = polytopes.cube()
59
+ sage: Q = polytopes.cross_polytope(3)
60
+
61
+ Unary operations::
62
+
63
+ sage: Polyhedron_base5.__neg__(P)
64
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
65
+ sage: Polyhedron_base5.polar(P)
66
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 6 vertices
67
+ sage: Polyhedron_base5.pyramid(P)
68
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices
69
+ sage: Polyhedron_base5.bipyramid(P)
70
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 10 vertices
71
+ sage: Polyhedron_base5.prism(P)
72
+ A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 16 vertices
73
+ sage: Polyhedron_base5.truncation(P)
74
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 24 vertices
75
+ sage: Polyhedron_base5.lawrence_polytope(P)
76
+ A 11-dimensional polyhedron in ZZ^11 defined as the convex hull of 16 vertices
77
+
78
+ Binary operations::
79
+
80
+ sage: Polyhedron_base5.minkowski_sum(P, Q)
81
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 24 vertices
82
+ sage: Polyhedron_base5.minkowski_difference(P, Q)
83
+ A 0-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex
84
+ sage: Polyhedron_base5.product(P, Q)
85
+ A 6-dimensional polyhedron in ZZ^6 defined as the convex hull of 48 vertices
86
+ sage: Polyhedron_base5.join(P, Q)
87
+ A 7-dimensional polyhedron in ZZ^7 defined as the convex hull of 14 vertices
88
+ sage: Polyhedron_base5.subdirect_sum(P, Q)
89
+ A 6-dimensional polyhedron in ZZ^6 defined as the convex hull of 14 vertices
90
+ sage: Polyhedron_base5.direct_sum(P, Q)
91
+ A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 14 vertices
92
+ sage: Polyhedron_base5.convex_hull(P, Q)
93
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
94
+ sage: Polyhedron_base5.intersection(P, Q)
95
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices
96
+
97
+ Actions::
98
+
99
+ sage: Polyhedron_base5.translation(P, vector([1, 1, 1]))
100
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
101
+ sage: Polyhedron_base5.dilation(P, 2)
102
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
103
+ sage: Polyhedron_base5.__truediv__(P, 2)
104
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices
105
+ sage: Polyhedron_base5.linear_transformation(P, matrix([[2, 1, 2], [1, 2, 4], [3, 1, 2]]))
106
+ A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices
107
+
108
+ Methods using a face::
109
+
110
+ sage: Polyhedron_base5.face_truncation(P, P.facets()[0])
111
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices
112
+ sage: Polyhedron_base5.stack(P, P.facets()[0])
113
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 9 vertices
114
+ sage: Polyhedron_base5.wedge(P, P.facets()[0])
115
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 12 vertices
116
+ sage: Polyhedron_base5.face_split(P, P.faces(2)[0])
117
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 10 vertices
118
+
119
+ Methods using a vertex or vector::
120
+
121
+ sage: Polyhedron_base5.lawrence_extension(P, P.vertices()[0])
122
+ A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 9 vertices
123
+ sage: Polyhedron_base5.one_point_suspension(P, P.vertices()[0])
124
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices
125
+ """
126
+
127
+ ###########################################################
128
+ # Unary operations.
129
+ ###########################################################
130
+
131
+ def __neg__(self):
132
+ """
133
+ Negation of a polytope is defined as inverting the coordinates.
134
+
135
+ EXAMPLES::
136
+
137
+ sage: t = polytopes.simplex(3,project=False); t.vertices()
138
+ (A vertex at (0, 0, 0, 1), A vertex at (0, 0, 1, 0),
139
+ A vertex at (0, 1, 0, 0), A vertex at (1, 0, 0, 0))
140
+ sage: neg_ = -t
141
+ sage: neg_.vertices()
142
+ (A vertex at (-1, 0, 0, 0), A vertex at (0, -1, 0, 0),
143
+ A vertex at (0, 0, -1, 0), A vertex at (0, 0, 0, -1))
144
+
145
+ TESTS::
146
+
147
+ sage: p = Polyhedron(ieqs=[[1,1,0]])
148
+ sage: p.rays()
149
+ (A ray in the direction (1, 0),)
150
+ sage: pneg = p.__neg__()
151
+ sage: pneg.rays()
152
+ (A ray in the direction (-1, 0),)
153
+ """
154
+ return self.dilation(-1)
155
+
156
+ def polar(self, in_affine_span=False):
157
+ """
158
+ Return the polar (dual) polytope.
159
+
160
+ The original vertices are translated so that their barycenter
161
+ is at the origin, and then the vertices are used as the
162
+ coefficients in the polar inequalities.
163
+
164
+ The polytope must be full-dimensional, unless ``in_affine_span`` is ``True``.
165
+ If ``in_affine_span`` is ``True``, then the operation will be performed in the
166
+ linear/affine span of the polyhedron (after translation).
167
+
168
+ EXAMPLES::
169
+
170
+ sage: p = Polyhedron(vertices=[[0,0,1], [0,1,0], [1,0,0], [0,0,0], [1,1,1]],
171
+ ....: base_ring=QQ); p
172
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 5 vertices
173
+ sage: p.polar()
174
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 6 vertices
175
+
176
+ sage: cube = polytopes.hypercube(3)
177
+ sage: octahedron = polytopes.cross_polytope(3)
178
+ sage: cube_dual = cube.polar()
179
+ sage: octahedron == cube_dual
180
+ True
181
+
182
+ ``in_affine_span`` somewhat ignores equations, performing the polar in the
183
+ spanned subspace (after translating barycenter to origin)::
184
+
185
+ sage: P = polytopes.simplex(3, base_ring=QQ)
186
+ sage: P.polar(in_affine_span=True)
187
+ A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 4 vertices
188
+
189
+ Embedding the polytope in a higher dimension, commutes with polar in this case::
190
+
191
+ sage: point = Polyhedron([[0]])
192
+ sage: P = polytopes.cube().change_ring(QQ)
193
+ sage: (P*point).polar(in_affine_span=True) == P.polar()*point
194
+ True
195
+
196
+ TESTS:
197
+
198
+ Check that :issue:`25081` is fixed::
199
+
200
+ sage: # needs cddexec_gmp
201
+ sage: C = polytopes.hypercube(4, backend='cdd')
202
+ sage: C.polar().backend()
203
+ 'cdd'
204
+
205
+ Check that :issue:`28850` is fixed::
206
+
207
+ sage: P = polytopes.simplex(3, base_ring=QQ)
208
+ sage: P.polar()
209
+ Traceback (most recent call last):
210
+ ...
211
+ ValueError: not full-dimensional; try with 'in_affine_span=True'
212
+
213
+ Check that the double description is set up correctly::
214
+
215
+ sage: P = Polyhedron([[1,0],[0,1],[-1,-1]], backend='field')
216
+ sage: Q = P.change_ring(QQ, backend='ppl')
217
+ sage: P.polar() == Q.polar()
218
+ True
219
+
220
+ sage: P = polytopes.simplex(4, backend='field')
221
+ sage: Q = P.change_ring(QQ, backend='ppl')
222
+ sage: P.polar(in_affine_span=True) == Q.polar(in_affine_span=True)
223
+ True
224
+
225
+ Check that it works, even when the equations are not orthogonal to each other::
226
+
227
+ sage: P = polytopes.cube()*Polyhedron([[0,0,0]])
228
+ sage: P = P.change_ring(QQ)
229
+
230
+ sage: from sage.geometry.polyhedron.backend_field import Polyhedron_field
231
+ sage: from sage.geometry.polyhedron.parent import Polyhedra_field
232
+ sage: parent = Polyhedra_field(QQ, 6, 'field')
233
+ sage: equations = [[0, 0, 0, 0, 1, 1, 1], [0, 0, 0, 0, -1, 1, -1], [0, 0, 0, 0, 1, -1, -1]]
234
+ sage: Q = Polyhedron_field(parent, [P.vertices(), [], []], [P.inequalities(), equations],
235
+ ....: Vrep_minimal=True, Hrep_minimal=True)
236
+ sage: Q == P
237
+ True
238
+ sage: Q.polar(in_affine_span=True) == P.polar(in_affine_span=True)
239
+ True
240
+ """
241
+ if not self.is_compact():
242
+ raise ValueError("not a polytope")
243
+ if not in_affine_span and not self.dim() == self.ambient_dim():
244
+ raise ValueError("not full-dimensional; try with 'in_affine_span=True'")
245
+
246
+ t_Vrep, t_Hrep, parent = self._translation_double_description(-self.center())
247
+ t_verts = t_Vrep[0]
248
+ t_ieqs = t_Hrep[0]
249
+ t_eqns = t_Hrep[1]
250
+
251
+ new_ieqs = ((1,) + tuple(-v) for v in t_verts)
252
+ if self.n_vertices() == 1:
253
+ new_verts = self.vertices()
254
+ elif not self.n_equations():
255
+ new_verts = ((-h/h[0])[1:] for h in t_ieqs)
256
+ else:
257
+ # Transform the equations such that the normals are pairwise orthogonal.
258
+ t_eqns = list(t_eqns)
259
+ for i, h in enumerate(t_eqns):
260
+ for h1 in t_eqns[:i]:
261
+ a = h[1:]*h1[1:]
262
+ if a:
263
+ b = h1[1:]*h1[1:]
264
+ t_eqns[i] = b*h - a*h1
265
+
266
+ def move_vertex_to_subspace(vertex):
267
+ for h in t_eqns:
268
+ offset = vertex*h[1:]+h[0]
269
+ vertex = vertex-h[1:]*offset/(h[1:]*h[1:])
270
+ return vertex
271
+
272
+ new_verts = (move_vertex_to_subspace((-h/h[0])[1:]) for h in t_ieqs)
273
+
274
+ pref_rep = 'Hrep' if self.n_vertices() <= self.n_inequalities() else 'Vrep'
275
+
276
+ return parent.element_class(parent, [new_verts, [], []],
277
+ [new_ieqs, t_eqns],
278
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
279
+
280
+ def pyramid(self):
281
+ """
282
+ Return a polyhedron that is a pyramid over the original.
283
+
284
+ EXAMPLES::
285
+
286
+ sage: square = polytopes.hypercube(2); square
287
+ A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices
288
+ sage: egyptian_pyramid = square.pyramid(); egyptian_pyramid
289
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 5 vertices
290
+ sage: egyptian_pyramid.n_vertices()
291
+ 5
292
+ sage: for v in egyptian_pyramid.vertex_generator(): print(v)
293
+ A vertex at (0, -1, -1)
294
+ A vertex at (0, -1, 1)
295
+ A vertex at (0, 1, -1)
296
+ A vertex at (0, 1, 1)
297
+ A vertex at (1, 0, 0)
298
+
299
+ TESTS::
300
+
301
+ sage: polytopes.simplex(backend='cdd').pyramid().backend() # needs cddexec_gmp
302
+ 'cdd'
303
+ """
304
+ assert self.is_compact(), "Not a polytope."
305
+ c = self.center()
306
+
307
+ from itertools import chain
308
+ new_verts = chain(([0] + x for x in self.Vrep_generator()),
309
+ [[1] + list(c)])
310
+ new_ieqs = chain(([i.b()] + [-c*i.A() - i.b()] + list(i.A()) for i in self.inequalities()),
311
+ [[0, 1] + [0]*self.ambient_dim()])
312
+ new_eqns = ([e.b()] + [0] + list(e.A()) for e in self.equations())
313
+
314
+ pref_rep = 'Hrep' if self.n_vertices() > self.n_inequalities() else 'Vrep'
315
+ parent = self.parent().base_extend(self.center().parent(), ambient_dim=self.ambient_dim()+1)
316
+
317
+ if self.n_vertices() == 1:
318
+ # Fix the polyhedron with one vertex.
319
+ return parent.element_class(parent, [new_verts, [], []], None)
320
+
321
+ return parent.element_class(parent, [new_verts, [], []],
322
+ [new_ieqs, new_eqns],
323
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
324
+
325
+ def _test_pyramid(self, tester=None, **options):
326
+ """
327
+ Run tests on the methods related to pyramids.
328
+
329
+ TESTS:
330
+
331
+ sage: polytopes.regular_polygon(4)._test_pyramid() # needs sage.rings.number_field
332
+ """
333
+ if tester is None:
334
+ tester = self._tester(**options)
335
+
336
+ def check_pyramid_certificate(P, cert):
337
+ others = set(v for v in P.vertices() if not v == cert)
338
+ if others:
339
+ tester.assertTrue(any(set(f.ambient_Vrepresentation()) == others for f in P.facets()))
340
+
341
+ if self.is_compact():
342
+ b, cert = self.is_pyramid(certificate=True)
343
+ if b:
344
+ check_pyramid_certificate(self, cert)
345
+
346
+ if 1 < self.n_vertices() < 50 and self.n_facets() < 50:
347
+ pyr = self.pyramid()
348
+ b, cert = pyr.is_pyramid(certificate=True)
349
+ tester.assertTrue(b)
350
+ check_pyramid_certificate(pyr, cert)
351
+ else:
352
+ with tester.assertRaises(AssertionError):
353
+ pyr = self.pyramid()
354
+
355
+ if self.is_compact() and 1 < self.n_vertices() < 50 and self.n_facets() < 50:
356
+ # Check the pyramid of the polar.
357
+ self_fraction_field = self.base_extend(QQ)
358
+
359
+ polar = self_fraction_field.polar(in_affine_span=True)
360
+ pyr_polar = polar.pyramid()
361
+ b, cert = pyr_polar.is_pyramid(certificate=True)
362
+ tester.assertTrue(b)
363
+ check_pyramid_certificate(pyr_polar, cert)
364
+
365
+ pyr = self_fraction_field.pyramid()
366
+ polar_pyr = pyr.polar(in_affine_span=True)
367
+ b, cert = polar_pyr.is_pyramid(certificate=True)
368
+ tester.assertTrue(b)
369
+ check_pyramid_certificate(polar_pyr, cert)
370
+
371
+ try:
372
+ import sage.graphs.graph
373
+ except ImportError:
374
+ pass
375
+ else:
376
+ tester.assertTrue(pyr_polar.is_combinatorially_isomorphic(pyr_polar))
377
+
378
+ # Basic properties of the pyramid.
379
+
380
+ # Check that the prism preserves the backend.
381
+ tester.assertEqual(pyr.backend(), self.backend())
382
+
383
+ tester.assertEqual(1 + self.n_vertices(), pyr.n_vertices())
384
+ tester.assertEqual(self.n_equations(), pyr.n_equations())
385
+ tester.assertEqual(1 + self.n_inequalities(), pyr.n_inequalities())
386
+
387
+ if self.n_vertices() < 15 and self.n_facets() < 15:
388
+ pyr._test_basic_properties()
389
+
390
+ def bipyramid(self):
391
+ """
392
+ Return a polyhedron that is a bipyramid over the original.
393
+
394
+ EXAMPLES::
395
+
396
+ sage: octahedron = polytopes.cross_polytope(3)
397
+ sage: cross_poly_4d = octahedron.bipyramid()
398
+ sage: cross_poly_4d.n_vertices()
399
+ 8
400
+ sage: q = [list(v) for v in cross_poly_4d.vertex_generator()]; q
401
+ [[-1, 0, 0, 0],
402
+ [0, -1, 0, 0],
403
+ [0, 0, -1, 0],
404
+ [0, 0, 0, -1],
405
+ [0, 0, 0, 1],
406
+ [0, 0, 1, 0],
407
+ [0, 1, 0, 0],
408
+ [1, 0, 0, 0]]
409
+
410
+ Now check that bipyramids of cross-polytopes are cross-polytopes::
411
+
412
+ sage: q2 = [list(v) for v in polytopes.cross_polytope(4).vertex_generator()]
413
+ sage: [v in q2 for v in q]
414
+ [True, True, True, True, True, True, True, True]
415
+
416
+ TESTS::
417
+
418
+ sage: polytopes.simplex(backend='cdd').bipyramid().backend() # needs cddexec_gmp
419
+ 'cdd'
420
+ """
421
+ c = self.center()
422
+ from itertools import chain
423
+ new_verts = chain(([0] + list(x) for x in self.vertex_generator()),
424
+ [[1] + list(c), [-1] + list(c)])
425
+ new_rays = ([0] + r for r in self.rays())
426
+ new_lines = ([0] + l for l in self.lines())
427
+ new_ieqs = chain(([i.b()] + [ c*i.A() + i.b()] + list(i.A()) for i in self.inequalities()),
428
+ ([i.b()] + [-c*i.A() - i.b()] + list(i.A()) for i in self.inequalities()))
429
+ new_eqns = ([e.b()] + [0] + list(e.A()) for e in self.equations())
430
+
431
+ pref_rep = 'Hrep' if 2 + (self.n_vertices() + self.n_rays()) >= 2*self.n_inequalities() else 'Vrep'
432
+ parent = self.parent().base_extend(self.center().parent(), ambient_dim=self.ambient_dim()+1)
433
+
434
+ if c not in self.relative_interior():
435
+ # Fix polyhedra with non-proper center.
436
+ return parent.element_class(parent, [new_verts, new_rays, new_lines], None)
437
+
438
+ return parent.element_class(parent, [new_verts, new_rays, new_lines],
439
+ [new_ieqs, new_eqns],
440
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
441
+
442
+ def _test_bipyramid(self, tester=None, **options):
443
+ """
444
+ Run tests on the method :meth:`.bipyramid`.
445
+
446
+ TESTS::
447
+
448
+ sage: polytopes.cross_polytope(3)._test_bipyramid()
449
+ """
450
+ if tester is None:
451
+ tester = self._tester(**options)
452
+
453
+ if (self.n_vertices() + self.n_rays() >= 40
454
+ or self.n_facets() >= 40
455
+ or self.n_vertices() <= 1):
456
+ return
457
+
458
+ bipyramid = self.bipyramid()
459
+
460
+ # Check that the double description is set up correctly.
461
+ if self.base_ring().is_exact() and self.n_vertices() + self.n_rays() < 15 and self.n_facets() < 15:
462
+ bipyramid._test_basic_properties(tester)
463
+
464
+ # Check that the bipyramid preserves the backend.
465
+ tester.assertEqual(bipyramid.backend(), self.backend())
466
+
467
+ if self.center() not in self.relative_interior():
468
+ # In this case (unbounded) the bipyramid behaves a bit different.
469
+ return
470
+
471
+ tester.assertEqual(2 + self.n_vertices(), bipyramid.n_vertices())
472
+ tester.assertEqual(self.n_rays(), bipyramid.n_rays())
473
+ tester.assertEqual(self.n_lines(), bipyramid.n_lines())
474
+ tester.assertEqual(self.n_equations(), bipyramid.n_equations())
475
+ tester.assertEqual(2*self.n_inequalities(), bipyramid.n_inequalities())
476
+
477
+ if not self.is_compact():
478
+ # ``is_bipyramid`` is only implemented for compact polyhedra.
479
+ return
480
+
481
+ b, cert = bipyramid.is_bipyramid(certificate=True)
482
+ tester.assertTrue(b)
483
+
484
+ if not self.is_bipyramid() and self.base_ring().is_exact():
485
+ # In this case the certificate is unique.
486
+
487
+ R = self.base_ring()
488
+ a = (R(1),) + tuple(self.center())
489
+ b = (R(-1),) + tuple(self.center())
490
+ c, d = (tuple(v) for v in cert)
491
+ tester.assertEqual(sorted([a, b]), sorted([c, d]))
492
+
493
+ def prism(self):
494
+ """
495
+ Return a prism of the original polyhedron.
496
+
497
+ EXAMPLES::
498
+
499
+ sage: square = polytopes.hypercube(2)
500
+ sage: cube = square.prism()
501
+ sage: cube
502
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
503
+ sage: hypercube = cube.prism()
504
+ sage: hypercube.n_vertices()
505
+ 16
506
+
507
+ TESTS::
508
+
509
+ sage: polytopes.simplex(backend='cdd').prism().backend() # needs cddexec_gmp
510
+ 'cdd'
511
+ """
512
+ from itertools import chain
513
+ new_verts = chain(([0] + v for v in self.vertices()),
514
+ ([1] + v for v in self.vertices()))
515
+ new_rays = ([0] + r for r in self.rays())
516
+ new_lines = ([0] + l for l in self.lines())
517
+ new_eqns = ([e.b()] + [0] + list(e[1:]) for e in self.equations())
518
+ new_ieqs = chain(([i.b()] + [0] + list(i[1:]) for i in self.inequalities()),
519
+ [[0, 1] + [0]*self.ambient_dim(), [1, -1] + [0]*self.ambient_dim()])
520
+
521
+ pref_rep = 'Hrep' if 2*(self.n_vertices() + self.n_rays()) >= self.n_inequalities() + 2 else 'Vrep'
522
+ parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim()+1)
523
+
524
+ if not self.vertices():
525
+ # Fix the empty polyhedron.
526
+ return parent.element_class(parent, [[], [], []], None)
527
+
528
+ return parent.element_class(parent, [new_verts, new_rays, new_lines],
529
+ [new_ieqs, new_eqns],
530
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
531
+
532
+ def _test_prism(self, tester=None, **options):
533
+ """
534
+ Run tests on the method :meth:`.prism`.
535
+
536
+ TESTS::
537
+
538
+ sage: polytopes.cross_polytope(3)._test_prism()
539
+ """
540
+ if tester is None:
541
+ tester = self._tester(**options)
542
+
543
+ if self.n_vertices() + self.n_rays() < 40 and self.n_facets() < 40:
544
+ prism = self.prism()
545
+
546
+ # Check that the double description is set up correctly.
547
+ if self.base_ring().is_exact() and self.n_vertices() + self.n_rays() < 15 and self.n_facets() < 15:
548
+ prism._test_basic_properties(tester)
549
+
550
+ # Check that the prism preserves the backend.
551
+ tester.assertEqual(prism.backend(), self.backend())
552
+
553
+ tester.assertEqual(2*self.n_vertices(), prism.n_vertices())
554
+ tester.assertEqual(self.n_rays(), prism.n_rays())
555
+ tester.assertEqual(self.n_lines(), prism.n_lines())
556
+ tester.assertEqual(self.n_equations(), prism.n_equations())
557
+ if self.is_empty():
558
+ return
559
+
560
+ tester.assertEqual(2 + self.n_inequalities(), prism.n_inequalities())
561
+
562
+ if not self.is_compact():
563
+ # ``is_prism`` only implemented for compact polyhedra.
564
+ return
565
+
566
+ b, cert = prism.is_prism(certificate=True)
567
+ tester.assertTrue(b)
568
+
569
+ if not self.is_prism() and self.base_ring().is_exact():
570
+ # In this case the certificate is unique.
571
+
572
+ R = self.base_ring()
573
+ cert_set = set(frozenset(tuple(v) for v in f) for f in cert)
574
+ expected_cert = set(frozenset((i,) + tuple(v)
575
+ for v in self.vertices())
576
+ for i in (R(0), R(1)))
577
+ tester.assertEqual(cert_set, expected_cert)
578
+
579
+ def truncation(self, cut_frac=None):
580
+ r"""
581
+ Return a new polyhedron formed from two points on each edge
582
+ between two vertices.
583
+
584
+ INPUT:
585
+
586
+ - ``cut_frac`` -- integer; how deeply to cut into the edge
587
+ Default is `\frac{1}{3}`
588
+
589
+ OUTPUT: a Polyhedron object, truncated as described above
590
+
591
+ EXAMPLES::
592
+
593
+ sage: cube = polytopes.hypercube(3)
594
+ sage: trunc_cube = cube.truncation()
595
+ sage: trunc_cube.n_vertices()
596
+ 24
597
+ sage: trunc_cube.n_inequalities()
598
+ 14
599
+
600
+ TESTS::
601
+
602
+ sage: polytopes.simplex(backend='field').truncation().backend()
603
+ 'field'
604
+ """
605
+ if cut_frac is None:
606
+ cut_frac = ZZ.one() / 3
607
+
608
+ new_vertices = []
609
+ for e in self.bounded_edges():
610
+ new_vertices.append((1 - cut_frac) * e[0]() + cut_frac * e[1]())
611
+ new_vertices.append(cut_frac * e[0]() + (1 - cut_frac) * e[1]())
612
+
613
+ new_vertices = [list(v) for v in new_vertices]
614
+ new_rays = self.rays()
615
+ new_lines = self.lines()
616
+
617
+ parent = self.parent().base_extend(cut_frac)
618
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
619
+
620
+ def lawrence_polytope(self):
621
+ r"""
622
+ Return the Lawrence polytope of ``self``.
623
+
624
+ Let `P` be a `d`-polytope in `\RR^r` with `n` vertices. The Lawrence
625
+ polytope of `P` is the polytope whose vertices are the columns of the
626
+ following `(r+n)`-by-`2n` matrix.
627
+
628
+ .. MATH::
629
+
630
+ \begin{pmatrix}
631
+ V & V \\
632
+ I_n & 2I_n
633
+ \end{pmatrix},
634
+
635
+ where `V` is the `r`-by-`n` vertices matrix of `P`.
636
+
637
+ EXAMPLES::
638
+
639
+ sage: P = polytopes.octahedron()
640
+ sage: L = P.lawrence_polytope(); L
641
+ A 9-dimensional polyhedron in ZZ^9 defined as the convex hull of 12 vertices
642
+ sage: V = P.vertices_list()
643
+ sage: for i, v in enumerate(V):
644
+ ....: v = v + i*[0]
645
+ ....: P = P.lawrence_extension(v)
646
+ sage: P == L
647
+ True
648
+
649
+ REFERENCES:
650
+
651
+ For more information, see Section 6.6 of [Zie2007]_.
652
+ """
653
+ from sage.matrix.constructor import block_matrix
654
+
655
+ if not self.is_compact():
656
+ raise NotImplementedError("self must be a polytope")
657
+
658
+ V = self.vertices_matrix().transpose()
659
+ n = self.n_vertices()
660
+ I_n = matrix.identity(n)
661
+ lambda_V = block_matrix([[V, I_n], [V, 2*I_n]])
662
+ parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim() + n)
663
+ return parent.element_class(parent, [lambda_V, [], []], None)
664
+
665
+ def deformation_cone(self):
666
+ r"""
667
+ Return the deformation cone of ``self``.
668
+
669
+ Let `P` be a `d`-polytope in `\RR^r` with `n` facets. The deformation
670
+ cone is a polyhedron in `\RR^n` whose points are the right-hand side `b`
671
+ in `Ax\leq b` where `A` is the matrix of facet normals of ``self``, so
672
+ that the resulting polytope has a normal fan which is a coarsening of
673
+ the normal fan of ``self``.
674
+
675
+ EXAMPLES:
676
+
677
+ Let's examine the deformation cone of the square with one truncated
678
+ vertex::
679
+
680
+ sage: tc = Polyhedron([(1, -1), (1/3, 1), (1, 1/3), (-1, 1), (-1, -1)])
681
+ sage: dc = tc.deformation_cone()
682
+ sage: dc.an_element()
683
+ (2, 1, 1, 0, 0)
684
+ sage: [_.A() for _ in tc.Hrepresentation()]
685
+ [(1, 0), (0, 1), (0, -1), (-3, -3), (-1, 0)]
686
+ sage: P = Polyhedron(rays=[(1, 0, 2), (0, 1, 1), (0, -1, 1), (-3, -3, 0), (-1, 0, 0)])
687
+ sage: P.rays()
688
+ (A ray in the direction (-1, -1, 0),
689
+ A ray in the direction (-1, 0, 0),
690
+ A ray in the direction (0, -1, 1),
691
+ A ray in the direction (0, 1, 1),
692
+ A ray in the direction (1, 0, 2))
693
+
694
+ Now, let's compute the deformation cone of the pyramid over a square
695
+ and verify that it is not full dimensional::
696
+
697
+ sage: py = Polyhedron([(0, -1, -1), (0, -1, 1), (0, 1, -1), (0, 1, 1), (1, 0, 0)])
698
+ sage: dc_py = py.deformation_cone(); dc_py
699
+ A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 1 vertex, 1 ray, 3 lines
700
+ sage: [ineq.b() for ineq in py.Hrepresentation()]
701
+ [0, 1, 1, 1, 1]
702
+ sage: r = dc_py.rays()[0]
703
+ sage: l1,l2,l3 = dc_py.lines()
704
+ sage: r.vector()-l1.vector()/2-l2.vector()-l3.vector()/2
705
+ (0, 1, 1, 1, 1)
706
+
707
+ .. SEEALSO::
708
+
709
+ :meth:`~sage.schemes.toric.variety.Kaehler_cone`
710
+
711
+ REFERENCES:
712
+
713
+ For more information, see Section 5.4 of [DLRS2010]_ and Section
714
+ 2.2 of [ACEP2020].
715
+ """
716
+ from .constructor import Polyhedron
717
+ m = matrix([ineq.A() for ineq in self.Hrepresentation()])
718
+ m = m.transpose()
719
+ m_ker = m.right_kernel_matrix(basis='computed')
720
+ gale = tuple(m_ker.columns())
721
+ collection = (f.ambient_H_indices() for f in self.faces(0))
722
+ n = len(gale)
723
+ c = None
724
+ for cone_indices in collection:
725
+ dual_cone = Polyhedron(rays=[gale[i] for i in range(n) if i not in
726
+ cone_indices])
727
+ c = c.intersection(dual_cone) if c is not None else dual_cone
728
+ preimages = [m_ker.solve_right(r.vector()) for r in c.rays()]
729
+ return Polyhedron(lines=m.rows(), rays=preimages)
730
+
731
+ ###########################################################
732
+ # Binary operations.
733
+ ###########################################################
734
+
735
+ @coerce_binop
736
+ def minkowski_sum(self, other):
737
+ r"""
738
+ Return the Minkowski sum.
739
+
740
+ Minkowski addition of two subsets of a vector space is defined
741
+ as
742
+
743
+ .. MATH::
744
+
745
+ X \oplus Y =
746
+ \bigcup_{y\in Y} (X+y) =
747
+ \bigcup_{x\in X, y\in Y} (x+y)
748
+
749
+ See :meth:`minkowski_difference` for a partial inverse operation.
750
+
751
+ INPUT:
752
+
753
+ - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
754
+
755
+ OUTPUT: the Minkowski sum of ``self`` and ``other``
756
+
757
+ EXAMPLES::
758
+
759
+ sage: X = polytopes.hypercube(3)
760
+ sage: Y = Polyhedron(vertices=[(0,0,0), (0,0,1/2), (0,1/2,0), (1/2,0,0)])
761
+ sage: X+Y
762
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 13 vertices
763
+
764
+ sage: four_cube = polytopes.hypercube(4)
765
+ sage: four_simplex = Polyhedron(vertices=[[0, 0, 0, 1], [0, 0, 1, 0],
766
+ ....: [0, 1, 0, 0], [1, 0, 0, 0]])
767
+ sage: four_cube + four_simplex
768
+ A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 36 vertices
769
+ sage: four_cube.minkowski_sum(four_simplex) == four_cube + four_simplex
770
+ True
771
+
772
+ sage: poly_spam = Polyhedron([[3,4,5,2], [1,0,0,1], [0,0,0,0],
773
+ ....: [0,4,3,2], [-3,-3,-3,-3]], base_ring=ZZ)
774
+ sage: poly_eggs = Polyhedron([[5,4,5,4], [-4,5,-4,5],
775
+ ....: [4,-5,4,-5], [0,0,0,0]], base_ring=QQ)
776
+ sage: poly_spam + poly_spam + poly_eggs
777
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 12 vertices
778
+ """
779
+ new_vertices = []
780
+ for v1 in self.vertex_generator():
781
+ for v2 in other.vertex_generator():
782
+ new_vertices.append(list(v1() + v2()))
783
+ if new_vertices != []:
784
+ new_rays = self.rays() + other.rays()
785
+ new_lines = self.lines() + other.lines()
786
+ return self.parent().element_class(self.parent(), [new_vertices, new_rays, new_lines], None)
787
+ else:
788
+ return self.parent().element_class(self.parent(), None, None)
789
+
790
+ _add_ = minkowski_sum
791
+
792
+ @coerce_binop
793
+ def minkowski_difference(self, other):
794
+ r"""
795
+ Return the Minkowski difference.
796
+
797
+ Minkowski subtraction can equivalently be defined via
798
+ Minkowski addition (see :meth:`minkowski_sum`) or as
799
+ set-theoretic intersection via
800
+
801
+ .. MATH::
802
+
803
+ X \ominus Y =
804
+ (X^c \oplus Y)^c =
805
+ \bigcap_{y\in Y} (X-y)
806
+
807
+ where superscript-"c" means the complement in the ambient
808
+ vector space. The Minkowski difference of convex sets is
809
+ convex, and the difference of polyhedra is again a
810
+ polyhedron. We only consider the case of polyhedra in the
811
+ following. Note that it is not quite the inverse of
812
+ addition. In fact:
813
+
814
+ * `(X+Y)-Y = X` for any polyhedra `X`, `Y`.
815
+
816
+ * `(X-Y)+Y \subseteq X`
817
+
818
+ * `(X-Y)+Y = X` if and only if Y is a Minkowski summand of X.
819
+
820
+ INPUT:
821
+
822
+ - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
823
+
824
+ OUTPUT:
825
+
826
+ The Minkowski difference of ``self`` and ``other``. Also known
827
+ as Minkowski subtraction of ``other`` from ``self``.
828
+
829
+ EXAMPLES::
830
+
831
+ sage: X = polytopes.hypercube(3)
832
+ sage: Y = Polyhedron(vertices=[(0,0,0), (0,0,1), (0,1,0), (1,0,0)]) / 2
833
+ sage: (X+Y)-Y == X
834
+ True
835
+ sage: (X-Y)+Y < X
836
+ True
837
+
838
+ The polyhedra need not be full-dimensional::
839
+
840
+ sage: X2 = Polyhedron(vertices=[(-1,-1,0), (1,-1,0), (-1,1,0), (1,1,0)])
841
+ sage: Y2 = Polyhedron(vertices=[(0,0,0), (0,1,0), (1,0,0)]) / 2
842
+ sage: (X2+Y2)-Y2 == X2
843
+ True
844
+ sage: (X2-Y2)+Y2 < X2
845
+ True
846
+
847
+ Minus sign is really an alias for :meth:`minkowski_difference`
848
+ ::
849
+
850
+ sage: four_cube = polytopes.hypercube(4)
851
+ sage: four_simplex = Polyhedron(vertices=[[0, 0, 0, 1], [0, 0, 1, 0],
852
+ ....: [0, 1, 0, 0], [1, 0, 0, 0]])
853
+ sage: four_cube - four_simplex
854
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 16 vertices
855
+ sage: four_cube.minkowski_difference(four_simplex) == four_cube - four_simplex
856
+ True
857
+
858
+ Coercion of the base ring works::
859
+
860
+ sage: poly_spam = Polyhedron([[3,4,5,2], [1,0,0,1], [0,0,0,0],
861
+ ....: [0,4,3,2], [-3,-3,-3,-3]], base_ring=ZZ)
862
+ sage: poly_eggs = Polyhedron([[5,4,5,4], [-4,5,-4,5],
863
+ ....: [4,-5,4,-5], [0,0,0,0]], base_ring=QQ) / 100
864
+ sage: poly_spam - poly_eggs
865
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 5 vertices
866
+
867
+ TESTS::
868
+
869
+ sage: X = polytopes.hypercube(2)
870
+ sage: Y = Polyhedron(vertices=[(1,1)])
871
+ sage: (X-Y).Vrepresentation()
872
+ (A vertex at (0, -2), A vertex at (0, 0), A vertex at (-2, 0), A vertex at (-2, -2))
873
+
874
+ sage: Y = Polyhedron(vertices=[(1,1), (0,0)])
875
+ sage: (X-Y).Vrepresentation()
876
+ (A vertex at (0, -1), A vertex at (0, 0), A vertex at (-1, 0), A vertex at (-1, -1))
877
+
878
+ sage: X = X + Y # now Y is a Minkowski summand of X
879
+ sage: (X+Y)-Y == X
880
+ True
881
+ sage: (X-Y)+Y == X
882
+ True
883
+
884
+ Testing that :issue:`28506` is fixed::
885
+
886
+ sage: Q = Polyhedron([[1,0],[0,1]])
887
+ sage: S = Polyhedron([[0,0],[1,2]])
888
+ sage: S.minkowski_difference(Q)
889
+ A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices
890
+ """
891
+ if other.is_empty():
892
+ return self.parent().universe() # empty intersection = everything
893
+ if not other.is_compact():
894
+ raise NotImplementedError('only subtracting compact polyhedra is implemented')
895
+ new_eqns = []
896
+ for eq in self.equations():
897
+ values = [ eq.A() * v.vector() for v in other.vertices() ]
898
+ eq = list(eq)
899
+ eq[0] += min(values) # shift constant term
900
+ new_eqns.append(eq)
901
+ P = self.parent()
902
+ new_ieqs = []
903
+ for ieq in self.inequalities():
904
+ values = [ ieq.A() * v.vector() for v in other.vertices() ]
905
+ ieq = list(ieq)
906
+ ieq[0] += min(values) # shift constant term
907
+ new_ieqs.append(ieq)
908
+
909
+ # Some vertices might need fractions.
910
+ P = self.parent().change_ring(self.base_ring().fraction_field())
911
+ return P.element_class(P, None, [new_ieqs, new_eqns])
912
+
913
+ def __sub__(self, other):
914
+ r"""
915
+ Implement minus binary operation.
916
+
917
+ Polyhedra are not a ring with respect to dilatation and
918
+ Minkowski sum, for example `X\oplus(-1)*Y \not= X\ominus Y`.
919
+
920
+ INPUT:
921
+
922
+ - ``other`` -- a translation vector or a polyhedron
923
+
924
+ OUTPUT:
925
+
926
+ Either translation by the negative of the given vector or
927
+ Minkowski subtraction by the given polyhedron.
928
+
929
+ EXAMPLES::
930
+
931
+ sage: X = polytopes.hypercube(2)
932
+ sage: v = vector([1,1])
933
+ sage: (X - v/2).Vrepresentation()
934
+ (A vertex at (-3/2, -3/2), A vertex at (-3/2, 1/2),
935
+ A vertex at (1/2, -3/2), A vertex at (1/2, 1/2))
936
+ sage: (X-v)+v == X
937
+ True
938
+
939
+ sage: Y = Polyhedron(vertices=[(1/2,0), (0,1/2)])
940
+ sage: (X-Y).Vrepresentation()
941
+ (A vertex at (1/2, -1), A vertex at (1/2, 1/2),
942
+ A vertex at (-1, 1/2), A vertex at (-1, -1))
943
+ sage: (X+Y)-Y == X
944
+ True
945
+ """
946
+ if isinstance(other, Polyhedron_base5):
947
+ return self.minkowski_difference(other)
948
+ return self + (-other)
949
+
950
+ def product(self, other):
951
+ """
952
+ Return the Cartesian product.
953
+
954
+ INPUT:
955
+
956
+ - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
957
+
958
+ OUTPUT:
959
+
960
+ The Cartesian product of ``self`` and ``other`` with a
961
+ suitable base ring to encompass the two.
962
+
963
+ EXAMPLES::
964
+
965
+ sage: P1 = Polyhedron([[0], [1]], base_ring=ZZ)
966
+ sage: P2 = Polyhedron([[0], [1]], base_ring=QQ)
967
+ sage: P1.product(P2)
968
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
969
+
970
+ The Cartesian product is the product in the semiring of polyhedra::
971
+
972
+ sage: P1 * P1
973
+ A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices
974
+ sage: P1 * P2
975
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
976
+ sage: P2 * P2
977
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
978
+ sage: 2 * P1
979
+ A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices
980
+ sage: P1 * 2.0 # needs cddexec
981
+ A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices
982
+
983
+ An alias is :meth:`cartesian_product`::
984
+
985
+ sage: P1.cartesian_product(P2) == P1.product(P2)
986
+ True
987
+
988
+ TESTS:
989
+
990
+ Check that :issue:`15253` is fixed::
991
+
992
+ sage: polytopes.hypercube(1) * polytopes.hypercube(2)
993
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
994
+ """
995
+ try:
996
+ new_ring = self.parent()._coerce_base_ring(other)
997
+ except TypeError:
998
+ raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
999
+ + " and " + str(other.parent()))
1000
+
1001
+ from itertools import chain
1002
+
1003
+ new_vertices = (tuple(x) + tuple(y)
1004
+ for x in self.vertex_generator() for y in other.vertex_generator())
1005
+
1006
+ self_zero = tuple(0 for _ in range( self.ambient_dim()))
1007
+ other_zero = tuple(0 for _ in range(other.ambient_dim()))
1008
+
1009
+ rays = chain((tuple(r) + other_zero for r in self.ray_generator()),
1010
+ (self_zero + tuple(r) for r in other.ray_generator()))
1011
+
1012
+ lines = chain((tuple(l) + other_zero for l in self.line_generator()),
1013
+ (self_zero + tuple(l) for l in other.line_generator()))
1014
+
1015
+ if self.n_vertices() == 0 or other.n_vertices() == 0:
1016
+ # In this case we obtain the empty polyhedron.
1017
+ # There is not vertex to attach the rays or lines to.
1018
+ # By our convention, in this case the polyhedron shall also not have rays or lines.
1019
+ rays = ()
1020
+ lines = ()
1021
+
1022
+ ieqs = chain((tuple(i) + other_zero
1023
+ for i in self.inequality_generator()),
1024
+ ((i.b(),) + self_zero + tuple(i.A())
1025
+ for i in other.inequality_generator()))
1026
+
1027
+ eqns = chain((tuple(e) + other_zero
1028
+ for e in self.equation_generator()),
1029
+ ((e.b(),) + self_zero + tuple(e.A())
1030
+ for e in other.equation_generator()))
1031
+
1032
+ pref_rep = 'Vrep' if self.n_vertices() + self.n_rays() + other.n_vertices() + other.n_rays() \
1033
+ <= self.n_inequalities() + other.n_inequalities() else 'Hrep'
1034
+
1035
+ parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim())
1036
+ return parent.element_class(parent, [new_vertices, rays, lines],
1037
+ [ieqs, eqns],
1038
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
1039
+
1040
+ _mul_ = product
1041
+
1042
+ cartesian_product = product
1043
+
1044
+ def _test_product(self, tester=None, **options):
1045
+ """
1046
+ Run tests on the method :meth:`.product`.
1047
+
1048
+ TESTS::
1049
+
1050
+ sage: polytopes.cross_polytope(3)._test_product()
1051
+ """
1052
+ from sage.rings.real_double import RDF
1053
+ from .library import polytopes
1054
+
1055
+ if tester is None:
1056
+ tester = self._tester(**options)
1057
+
1058
+ if self.n_vertices() + self.n_rays() < 40 and self.n_facets() < 40:
1059
+ # Check that the product preserves the backend, where possible.
1060
+ try:
1061
+ P = polytopes.simplex(backend='cdd')
1062
+ except FeatureNotPresentError:
1063
+ pass
1064
+ else:
1065
+ tester.assertEqual((self*P).backend(), self.backend())
1066
+ Q = polytopes.simplex(backend='ppl')
1067
+ tester.assertEqual((self*Q).backend(), self.backend())
1068
+
1069
+ # And that it changes the backend correctly where necessary.
1070
+ try:
1071
+ from sage.rings.qqbar import AA
1072
+ import sage.libs.pari
1073
+ except ImportError:
1074
+ pass
1075
+ else:
1076
+ if self.base_ring() is not AA and AA.has_coerce_map_from(self.base_ring()):
1077
+ R = self*polytopes.regular_polygon(5, exact=True)
1078
+ assert R
1079
+ if RDF.has_coerce_map_from(self.base_ring()):
1080
+ try:
1081
+ P5 = polytopes.regular_polygon(5, exact=False)
1082
+ except FeatureNotPresentError:
1083
+ pass
1084
+ else:
1085
+ R = self * P5
1086
+ assert R
1087
+
1088
+ if self.base_ring() in (ZZ, QQ):
1089
+ # Check that the double description is set up correctly.
1090
+ self_field = self.base_extend(self.base_ring(), backend='field')
1091
+ try:
1092
+ P = polytopes.permutahedron(4, backend='field').base_extend(QQ)
1093
+ except ImportError:
1094
+ pass
1095
+ else:
1096
+ (self_field * P)._test_basic_properties(tester)
1097
+ from .constructor import Polyhedron
1098
+ Q = Polyhedron(rays=[[1,0,0,0],[0,1,1,0]], lines=[[0,1,0,1]], backend='field')
1099
+ (self_field * Q)._test_basic_properties(tester)
1100
+
1101
+ def join(self, other):
1102
+ """
1103
+ Return the join of ``self`` and ``other``.
1104
+
1105
+ The join of two polyhedra is obtained by first placing the two objects in
1106
+ two non-intersecting affine subspaces `V`, and `W` whose affine hull is
1107
+ the whole ambient space, and finally by taking the convex hull of their
1108
+ union. The dimension of the join is the sum of the dimensions of the
1109
+ two polyhedron plus 1.
1110
+
1111
+ INPUT:
1112
+
1113
+ - ``other`` -- a polyhedron
1114
+
1115
+ EXAMPLES::
1116
+
1117
+ sage: P1 = Polyhedron([[0],[1]], base_ring=ZZ)
1118
+ sage: P2 = Polyhedron([[0],[1]], base_ring=QQ)
1119
+ sage: P1.join(P2)
1120
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices
1121
+ sage: P1.join(P1)
1122
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices
1123
+ sage: P2.join(P2)
1124
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices
1125
+
1126
+ An unbounded example::
1127
+
1128
+ sage: R1 = Polyhedron(rays=[[1]])
1129
+ sage: R1.join(R1)
1130
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 2 vertices and 2 rays
1131
+
1132
+ TESTS::
1133
+
1134
+ sage: C = polytopes.hypercube(5)
1135
+ sage: S = Polyhedron([[1]])
1136
+ sage: C.join(S).is_combinatorially_isomorphic(C.pyramid()) # needs sage.graphs
1137
+ True
1138
+
1139
+ sage: # needs cddexec_gmp
1140
+ sage: P = polytopes.simplex(backend='cdd')
1141
+ sage: Q = polytopes.simplex(backend='ppl')
1142
+ sage: P.join(Q).backend()
1143
+ 'cdd'
1144
+ sage: Q.join(P).backend()
1145
+ 'ppl'
1146
+
1147
+ Check that the double description is set up correctly::
1148
+
1149
+ sage: P = polytopes.cross_polytope(4)
1150
+ sage: P1 = polytopes.cross_polytope(4, backend='field')
1151
+ sage: P.join(P) == P1.join(P1)
1152
+ True
1153
+
1154
+ sage: P = 4*polytopes.hypercube(4)
1155
+ sage: P1 = 4*polytopes.hypercube(4, backend='field')
1156
+ sage: P.join(P) == P1.join(P1)
1157
+ True
1158
+
1159
+ sage: P = polytopes.permutahedron(4)
1160
+ sage: P1 = polytopes.permutahedron(4, backend='field')
1161
+ sage: P.join(P) == P1.join(P1)
1162
+ True
1163
+ """
1164
+ try:
1165
+ new_ring = self.parent()._coerce_base_ring(other)
1166
+ except TypeError:
1167
+ raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
1168
+ + " and " + str(other.parent()))
1169
+
1170
+ from itertools import chain
1171
+
1172
+ dim_self = self.ambient_dim()
1173
+ dim_other = other.ambient_dim()
1174
+ parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim() + 1)
1175
+
1176
+ new_vertices1 = (list(x) + [0]*dim_other + [0] for x in self.vertex_generator())
1177
+ new_vertices2 = ([0]*dim_self + list(x) + [1] for x in other.vertex_generator())
1178
+ new_vertices = chain(new_vertices1, new_vertices2)
1179
+
1180
+ new_rays1 = (list(r) + [0]*dim_other + [0] for r in self.ray_generator())
1181
+ new_rays2 = ([0]*dim_self + list(r) + [1] for r in other.ray_generator())
1182
+ new_rays = chain(new_rays1, new_rays2)
1183
+
1184
+ new_lines1 = (list(l) + [0]*dim_other + [0] for l in self.line_generator())
1185
+ new_lines2 = ([0]*dim_self + list(l) + [1] for l in other.line_generator())
1186
+ new_lines = chain(new_lines1, new_lines2)
1187
+
1188
+ if not self.is_compact() or not other.is_compact() or self.n_vertices() <= 1 or other.n_vertices() <= 1:
1189
+ # Cases for which the below double description does not work.
1190
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
1191
+
1192
+ # Facet defining inequalities that contain the corresponding vertices from ``new_vertices1``
1193
+ # and all vertices from ``new_vertices2``.
1194
+ new_inequalities1 = ([i[0]] + list(i[1:]) + [0]*dim_other + [-i[0]] for i in self.inequality_generator())
1195
+
1196
+ # Facet defining inequalities that contain the corresponding vertices from ``new_vertices2``
1197
+ # and all vertices from ``new_vertices1``.
1198
+ new_inequalities2 = ([0] + [0]*dim_self + list(i[1:]) + [i[0]] for i in other.inequality_generator())
1199
+
1200
+ new_inequalities = chain(new_inequalities1, new_inequalities2)
1201
+
1202
+ # Equations that all vertices corresponding to ``new_vertices1`` satisfy.
1203
+ # For any vertex from ``new_vertices2`` the condition is trivial.
1204
+ new_equations1 = ([e[0]] + list(e[1:]) + [0]*dim_other + [-e[0]] for e in self.equation_generator())
1205
+
1206
+ # Equations that all vertices corresponding to ``new_vertices2`` satisfy.
1207
+ # For any vertex from ``new_vertices1`` the condition is trivial.
1208
+ new_equations2 = ([0] + [0]*dim_self + list(e[1:]) + [e[0]] for e in other.equation_generator())
1209
+
1210
+ new_equations = chain(new_equations1, new_equations2)
1211
+
1212
+ new_n_inequalities = self.n_inequalities() + other.n_inequalities()
1213
+ new_n_vertices = self.n_vertices() + other.n_vertices()
1214
+ new_n_rays = self.n_rays() + other.n_rays()
1215
+
1216
+ pref_rep = 'Vrep' if new_n_vertices + new_n_rays <= new_n_inequalities else 'Hrep'
1217
+
1218
+ return parent.element_class(parent,
1219
+ [new_vertices, new_rays, new_lines],
1220
+ [new_inequalities, new_equations],
1221
+ Vrep_minimal=True, Hrep_minimal=True,
1222
+ pref_rep=pref_rep)
1223
+
1224
+ def subdirect_sum(self, other):
1225
+ """
1226
+ Return the subdirect sum of ``self`` and ``other``.
1227
+
1228
+ The subdirect sum of two polyhedron is a projection of the join of the
1229
+ two polytopes. It is obtained by placing the two objects in orthogonal subspaces
1230
+ intersecting at the origin.
1231
+
1232
+ INPUT:
1233
+
1234
+ - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
1235
+
1236
+ EXAMPLES::
1237
+
1238
+ sage: P1 = Polyhedron([[1], [2]], base_ring=ZZ)
1239
+ sage: P2 = Polyhedron([[3], [4]], base_ring=QQ)
1240
+ sage: sds = P1.subdirect_sum(P2); sds
1241
+ A 2-dimensional polyhedron in QQ^2
1242
+ defined as the convex hull of 4 vertices
1243
+ sage: sds.vertices()
1244
+ (A vertex at (0, 3),
1245
+ A vertex at (0, 4),
1246
+ A vertex at (1, 0),
1247
+ A vertex at (2, 0))
1248
+
1249
+ .. SEEALSO::
1250
+
1251
+ :meth:`join`
1252
+ :meth:`direct_sum`
1253
+
1254
+ TESTS::
1255
+
1256
+ sage: # needs cddexec_gmp
1257
+ sage: P = polytopes.simplex(backend='cdd')
1258
+ sage: Q = polytopes.simplex(backend='ppl')
1259
+ sage: P.subdirect_sum(Q).backend()
1260
+ 'cdd'
1261
+ sage: Q.subdirect_sum(P).backend()
1262
+ 'ppl'
1263
+ """
1264
+ try:
1265
+ new_ring = self.parent()._coerce_base_ring(other)
1266
+ except TypeError:
1267
+ raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
1268
+ + " and " + str(other.parent()))
1269
+
1270
+ dim_self = self.ambient_dim()
1271
+ dim_other = other.ambient_dim()
1272
+
1273
+ new_vertices = [list(x)+[0]*dim_other for x in self.vertex_generator()] + \
1274
+ [[0]*dim_self+list(x) for x in other.vertex_generator()]
1275
+ new_rays = []
1276
+ new_rays.extend( [ r+[0]*dim_other
1277
+ for r in self.ray_generator() ] )
1278
+ new_rays.extend( [ [0]*dim_self+r
1279
+ for r in other.ray_generator() ] )
1280
+ new_lines = []
1281
+ new_lines.extend( [ l+[0]*dim_other
1282
+ for l in self.line_generator() ] )
1283
+ new_lines.extend( [ [0]*dim_self+l
1284
+ for l in other.line_generator() ] )
1285
+
1286
+ parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim())
1287
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
1288
+
1289
+ def direct_sum(self, other):
1290
+ """
1291
+ Return the direct sum of ``self`` and ``other``.
1292
+
1293
+ The direct sum of two polyhedron is the subdirect sum of the two, when
1294
+ they have the origin in their interior. To avoid checking if the origin
1295
+ is contained in both, we place the affine subspace containing ``other``
1296
+ at the center of ``self``.
1297
+
1298
+ INPUT:
1299
+
1300
+ - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
1301
+
1302
+ EXAMPLES::
1303
+
1304
+ sage: P1 = Polyhedron([[1], [2]], base_ring=ZZ)
1305
+ sage: P2 = Polyhedron([[3], [4]], base_ring=QQ)
1306
+ sage: ds = P1.direct_sum(P2);ds
1307
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
1308
+ sage: ds.vertices()
1309
+ (A vertex at (1, 0),
1310
+ A vertex at (2, 0),
1311
+ A vertex at (3/2, -1/2),
1312
+ A vertex at (3/2, 1/2))
1313
+
1314
+ .. SEEALSO::
1315
+
1316
+ :meth:`join`
1317
+ :meth:`subdirect_sum`
1318
+
1319
+ TESTS:
1320
+
1321
+ Check that the backend is preserved::
1322
+
1323
+ sage: # needs cddexec_gmp
1324
+ sage: P = polytopes.simplex(backend='cdd')
1325
+ sage: Q = polytopes.simplex(backend='ppl')
1326
+ sage: P.direct_sum(Q).backend()
1327
+ 'cdd'
1328
+ sage: Q.direct_sum(P).backend()
1329
+ 'ppl'
1330
+
1331
+ Check that :issue:`28506` is fixed::
1332
+
1333
+ sage: s2 = polytopes.simplex(2)
1334
+ sage: s3 = polytopes.simplex(3)
1335
+ sage: s2.direct_sum(s3)
1336
+ A 5-dimensional polyhedron in QQ^7 defined as the convex hull of 7 vertices
1337
+ """
1338
+ try:
1339
+ # Some vertices might need fractions.
1340
+ new_ring = self.parent()._coerce_base_ring(other).fraction_field()
1341
+ except TypeError:
1342
+ raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
1343
+ + " and " + str(other.parent()))
1344
+
1345
+ dim_self = self.ambient_dim()
1346
+ dim_other = other.ambient_dim()
1347
+
1348
+ new_vertices = [list(x) + [0]*dim_other for x in self.vertex_generator()] + \
1349
+ [list(self.center()) + list(x.vector() - other.center()) for x in other.vertex_generator()]
1350
+ new_rays = []
1351
+ new_rays.extend( [ r + [0]*dim_other
1352
+ for r in self.ray_generator() ] )
1353
+ new_rays.extend( [ [0]*dim_self + r
1354
+ for r in other.ray_generator() ] )
1355
+ new_lines = []
1356
+ new_lines.extend( [ l + [0]*dim_other
1357
+ for l in self.line_generator() ] )
1358
+ new_lines.extend( [ [0]*dim_self + l
1359
+ for l in other.line_generator() ] )
1360
+
1361
+ parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim())
1362
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
1363
+
1364
+ @coerce_binop
1365
+ def convex_hull(self, other):
1366
+ """
1367
+ Return the convex hull of the set-theoretic union of the two
1368
+ polyhedra.
1369
+
1370
+ INPUT:
1371
+
1372
+ - ``other`` -- a :class:`Polyhedron`
1373
+
1374
+ OUTPUT: the convex hull
1375
+
1376
+ EXAMPLES::
1377
+
1378
+ sage: # needs cddexec
1379
+ sage: a_simplex = polytopes.simplex(3, project=True)
1380
+ sage: verts = a_simplex.vertices()
1381
+ sage: verts = [[x[0]*3/5 + x[1]*4/5, -x[0]*4/5 + x[1]*3/5, x[2]] for x in verts]
1382
+ sage: another_simplex = Polyhedron(vertices=verts)
1383
+ sage: simplex_union = a_simplex.convex_hull(another_simplex)
1384
+ sage: simplex_union.n_vertices()
1385
+ 7
1386
+ """
1387
+ hull_vertices = self.vertices() + other.vertices()
1388
+ hull_rays = self.rays() + other.rays()
1389
+ hull_lines = self.lines() + other.lines()
1390
+ return self.parent().element_class(self.parent(), [hull_vertices, hull_rays, hull_lines], None)
1391
+
1392
+ @coerce_binop
1393
+ def intersection(self, other):
1394
+ r"""
1395
+ Return the intersection of one polyhedron with another.
1396
+
1397
+ INPUT:
1398
+
1399
+ - ``other`` -- a :class:`Polyhedron`
1400
+
1401
+ OUTPUT: the intersection
1402
+
1403
+ Note that the intersection of two `\ZZ`-polyhedra might not be
1404
+ a `\ZZ`-polyhedron. In this case, a `\QQ`-polyhedron is
1405
+ returned.
1406
+
1407
+ EXAMPLES::
1408
+
1409
+ sage: cube = polytopes.hypercube(3)
1410
+ sage: oct = polytopes.cross_polytope(3)
1411
+ sage: cube.intersection(oct*2)
1412
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 12 vertices
1413
+
1414
+ As a shorthand, one may use::
1415
+
1416
+ sage: cube & oct*2
1417
+ A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 12 vertices
1418
+
1419
+ The intersection of two `\ZZ`-polyhedra is not necessarily a `\ZZ`-polyhedron::
1420
+
1421
+ sage: P = Polyhedron([(0,0),(1,1)], base_ring=ZZ)
1422
+ sage: P.intersection(P)
1423
+ A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices
1424
+ sage: Q = Polyhedron([(0,1),(1,0)], base_ring=ZZ)
1425
+ sage: P.intersection(Q)
1426
+ A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex
1427
+ sage: _.Vrepresentation()
1428
+ (A vertex at (1/2, 1/2),)
1429
+
1430
+ TESTS:
1431
+
1432
+ Check that :issue:`19012` is fixed::
1433
+
1434
+ sage: # needs sage.rings.number_field
1435
+ sage: K.<a> = QuadraticField(5)
1436
+ sage: P = Polyhedron([[0, 0], [0, a], [1, 1]])
1437
+ sage: Q = Polyhedron(ieqs=[[-1, a, 1]])
1438
+ sage: P.intersection(Q)
1439
+ A 2-dimensional polyhedron in
1440
+ (Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790?)^2
1441
+ defined as the convex hull of 4 vertices
1442
+ """
1443
+ new_ieqs = self.inequalities() + other.inequalities()
1444
+ new_eqns = self.equations() + other.equations()
1445
+ parent = self.parent()
1446
+ try:
1447
+ intersection = parent.element_class(parent, None, [new_ieqs, new_eqns])
1448
+
1449
+ # Force calculation of the vertices.
1450
+ _ = intersection.n_vertices()
1451
+ return intersection
1452
+ except TypeError as msg:
1453
+ if self.base_ring() is ZZ:
1454
+ parent = parent.base_extend(QQ)
1455
+ return parent.element_class(parent, None, [new_ieqs, new_eqns])
1456
+ else:
1457
+ raise TypeError(msg)
1458
+
1459
+ __and__ = intersection
1460
+
1461
+ ###########################################################
1462
+ # Actions.
1463
+ ###########################################################
1464
+
1465
+ def _acted_upon_(self, actor, self_on_left):
1466
+ """
1467
+ Implement the action by scalars, vectors, matrices or other polyhedra.
1468
+
1469
+ INPUT:
1470
+
1471
+ - ``actor`` -- one of the following:
1472
+ - a scalar, not necessarily in :meth:`base_ring`,
1473
+ - a :class:`Polyhedron`,
1474
+ - a :class:`sage.modules.free_module_element.vector`,
1475
+ - a :class:`sage.matrix.constructor.matrix`,
1476
+ - ``self_on_right`` -- must be ``False`` for actor a matrix;
1477
+ ignored otherwise
1478
+
1479
+ OUTPUT:
1480
+
1481
+ - Dilation for a scalar
1482
+ - Product for a polyhedron
1483
+ - Translation for a vector
1484
+ - Linear transformation for a matrix
1485
+
1486
+ EXAMPLES:
1487
+
1488
+ ``actor`` is a scalar::
1489
+
1490
+ sage: p = Polyhedron(vertices = [[t,t^2,t^3] for t in srange(2,6)])
1491
+ sage: p._acted_upon_(2, True) == p.dilation(2)
1492
+ True
1493
+ sage: p*2 == p.dilation(2)
1494
+ True
1495
+
1496
+ ``actor`` is a polyhedron::
1497
+
1498
+ sage: p*p == p.product(p)
1499
+ True
1500
+
1501
+ ``actor`` is a vector::
1502
+
1503
+ sage: p + vector(ZZ,[1,2,3]) == p.translation([1,2,3])
1504
+ True
1505
+
1506
+ ``actor`` is a matrix::
1507
+
1508
+ sage: matrix(ZZ,[[1,2,3]]) * p
1509
+ A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices
1510
+
1511
+ A matrix must act from the left::
1512
+
1513
+ sage: p * matrix(ZZ, [[1,2,3]]*3)
1514
+ Traceback (most recent call last):
1515
+ ...
1516
+ ValueError: matrices should act on the left
1517
+ """
1518
+ if isinstance(actor, Polyhedron_base5):
1519
+ return self.product(actor)
1520
+ elif isinstance(actor, Vector):
1521
+ return self.translation(actor)
1522
+ elif isinstance(actor, Matrix):
1523
+ if self_on_left:
1524
+ raise ValueError("matrices should act on the left")
1525
+ else:
1526
+ return self.linear_transformation(actor)
1527
+ else:
1528
+ return self.dilation(actor)
1529
+
1530
+ def translation(self, displacement):
1531
+ """
1532
+ Return the translated polyhedron.
1533
+
1534
+ INPUT:
1535
+
1536
+ - ``displacement`` -- a displacement vector or a list/tuple of
1537
+ coordinates that determines a displacement vector
1538
+
1539
+ OUTPUT: the translated polyhedron
1540
+
1541
+ .. SEEALSO:: :meth:`linear_transformation`, :meth:`dilation`
1542
+
1543
+ EXAMPLES::
1544
+
1545
+ sage: P = Polyhedron([[0,0], [1,0], [0,1]], base_ring=ZZ)
1546
+ sage: P.translation([2,1])
1547
+ A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices
1548
+ sage: P.translation(vector(QQ, [2,1]))
1549
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices
1550
+
1551
+ TESTS::
1552
+
1553
+ sage: P = Polyhedron([[0,0], [1,0], [0,1]], base_ring=ZZ, backend='field')
1554
+ sage: P.translation([2,1]).backend()
1555
+ 'field'
1556
+
1557
+ Check that precomputed data is set up correctly::
1558
+
1559
+ sage: P = polytopes.permutahedron(4)*Polyhedron(lines=[[1]])
1560
+ sage: Q = P.change_ring(P.base_ring(), backend='field')
1561
+ sage: P + vector([1,2,3,4,5]) == Q + vector([1,2,3,4,5])
1562
+ True
1563
+ sage: P + vector([1,2,3,4,5/2]) == Q + vector([1,2,3,4,5/2])
1564
+ True
1565
+ """
1566
+ Vrep, Hrep, parent = self._translation_double_description(displacement)
1567
+
1568
+ pref_rep = 'Vrep' if self.n_vertices() + self.n_rays() <= self.n_inequalities() else 'Hrep'
1569
+
1570
+ return parent.element_class(parent, Vrep, Hrep,
1571
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
1572
+
1573
+ def _translation_double_description(self, displacement):
1574
+ r"""
1575
+ Return the input parameters for the translation.
1576
+
1577
+ INPUT:
1578
+
1579
+ - ``displacement`` -- a displacement vector or a list/tuple of
1580
+ coordinates that determines a displacement vector
1581
+
1582
+ OUTPUT: tuple of consisting of new Vrepresentation, Hrepresentation and parent
1583
+
1584
+ .. SEEALSO::
1585
+
1586
+ :meth:`translation`
1587
+
1588
+ EXAMPLES::
1589
+
1590
+ sage: P = Polyhedron([[0,0], [1,0], [0,1]], base_ring=ZZ)
1591
+ sage: Vrep, Hrep, parent = P._translation_double_description([2,1])
1592
+ sage: [tuple(x) for x in Vrep], [tuple(x) for x in Hrep], parent
1593
+ ([((2, 1), (2, 2), (3, 1)), (), ()],
1594
+ [((-2, 1, 0), (-1, 0, 1), (4, -1, -1)), ()],
1595
+ Polyhedra in ZZ^2)
1596
+ """
1597
+ displacement = vector(displacement)
1598
+ new_vertices = (x.vector()+displacement for x in self.vertex_generator())
1599
+ new_rays = self.rays()
1600
+ new_lines = self.lines()
1601
+ parent = self.parent().base_extend(displacement)
1602
+
1603
+ # Replace a hyperplane of the form A*x + b >= 0 by
1604
+ # A(x-displacement) + b >= 0 <=> Ax + b - A*displacement >= 0.
1605
+ # Likewise for equations.
1606
+ def get_new(x):
1607
+ y = x.vector().change_ring(parent.base_ring())
1608
+ y[0] -= x.A()*displacement
1609
+ return y
1610
+
1611
+ new_ieqs = (get_new(x) for x in self.inequality_generator())
1612
+ new_eqns = (get_new(x) for x in self.equation_generator())
1613
+ return [new_vertices, new_rays, new_lines], [new_ieqs, new_eqns], parent
1614
+
1615
+ def dilation(self, scalar):
1616
+ """
1617
+ Return the dilated (uniformly stretched) polyhedron.
1618
+
1619
+ INPUT:
1620
+
1621
+ - ``scalar`` -- a scalar, not necessarily in :meth:`base_ring`
1622
+
1623
+ OUTPUT:
1624
+
1625
+ The polyhedron dilated by that scalar, possibly coerced to a
1626
+ bigger base ring.
1627
+
1628
+ .. SEEALSO:: :meth:`linear_transformation`, :meth:`translation`
1629
+
1630
+ EXAMPLES::
1631
+
1632
+ sage: p = Polyhedron(vertices=[[t,t^2,t^3] for t in srange(2,6)])
1633
+ sage: next(p.vertex_generator())
1634
+ A vertex at (2, 4, 8)
1635
+ sage: p2 = p.dilation(2)
1636
+ sage: next(p2.vertex_generator())
1637
+ A vertex at (4, 8, 16)
1638
+ sage: p.dilation(2) == p * 2
1639
+ True
1640
+
1641
+ TESTS:
1642
+
1643
+ Dilation of empty polyhedra works, see :issue:`14987`::
1644
+
1645
+ sage: p = Polyhedron(ambient_dim=2); p
1646
+ The empty polyhedron in ZZ^2
1647
+ sage: p.dilation(3)
1648
+ The empty polyhedron in ZZ^2
1649
+
1650
+ sage: p = Polyhedron(vertices=[(1,1)], rays=[(1,0)], lines=[(0,1)])
1651
+ sage: (-p).rays()
1652
+ (A ray in the direction (-1, 0),)
1653
+ sage: (-p).lines()
1654
+ (A line in the direction (0, 1),)
1655
+
1656
+ sage: (0*p).rays()
1657
+ ()
1658
+ sage: (0*p).lines()
1659
+ ()
1660
+ """
1661
+ parent = self.parent().base_extend(scalar)
1662
+
1663
+ if scalar == 0:
1664
+ new_vertices = tuple(self.ambient_space().zero() for v in self.vertex_generator())
1665
+ new_rays = []
1666
+ new_lines = []
1667
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
1668
+
1669
+ one = parent.base_ring().one()
1670
+ sign = one if scalar > 0 else -one
1671
+
1672
+ make_new_Hrep = lambda h: tuple(scalar*sign*x if i == 0 else sign*x
1673
+ for i, x in enumerate(h._vector))
1674
+
1675
+ new_vertices = (tuple(scalar*x for x in v._vector) for v in self.vertex_generator())
1676
+ new_rays = (tuple(sign*x for x in r._vector) for r in self.ray_generator())
1677
+ new_lines = self.line_generator()
1678
+ new_inequalities = map(make_new_Hrep, self.inequality_generator())
1679
+ new_equations = map(make_new_Hrep, self.equation_generator())
1680
+
1681
+ pref_rep = 'Vrep' if self.n_vertices() + self.n_rays() <= self.n_inequalities() else 'Hrep'
1682
+
1683
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines],
1684
+ [new_inequalities, new_equations],
1685
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
1686
+
1687
+ def __truediv__(self, scalar):
1688
+ """
1689
+ Divide by a scalar factor.
1690
+
1691
+ See :meth:`dilation` for details.
1692
+
1693
+ EXAMPLES::
1694
+
1695
+ sage: p = Polyhedron(vertices = [[t,t^2,t^3] for t in srange(2,4)])
1696
+ sage: (p/5).Vrepresentation()
1697
+ (A vertex at (2/5, 4/5, 8/5), A vertex at (3/5, 9/5, 27/5))
1698
+ sage: (p/int(5)).Vrepresentation() # needs cddexec
1699
+ (A vertex at (0.4, 0.8, 1.6), A vertex at (0.6, 1.8, 5.4))
1700
+ """
1701
+ return self.dilation(1/scalar)
1702
+
1703
+ def _test_dilation(self, tester=None, **options):
1704
+ """
1705
+ Run tests on the method :meth:`.dilation`.
1706
+
1707
+ TESTS::
1708
+
1709
+ sage: polytopes.cross_polytope(3)._test_dilation()
1710
+ """
1711
+ from sage.rings.real_double import RDF
1712
+ from .base import Polyhedron_base
1713
+
1714
+ if tester is None:
1715
+ tester = self._tester(**options)
1716
+
1717
+ # Testing that the backend is preserved.
1718
+ tester.assertEqual(self.dilation(2*self.base_ring().gen()).backend(), self.backend())
1719
+ tester.assertEqual(self.dilation(ZZ(3)).backend(), self.backend())
1720
+
1721
+ if self.n_vertices() + self.n_rays() > 40:
1722
+ # Avoid long time computations.
1723
+ return
1724
+
1725
+ # Testing that the double description is set up correctly.
1726
+ if self.base_ring().is_exact():
1727
+ if self.base_ring() in (QQ, ZZ):
1728
+ p = self.base_extend(self.base_ring(), backend='field')
1729
+ (ZZ(2) * p)._test_basic_properties(tester)
1730
+ (ZZ(2)/2 * p)._test_basic_properties(tester)
1731
+ (ZZ(-3) * p)._test_basic_properties(tester)
1732
+ (ZZ(-1)/2 * p)._test_basic_properties(tester)
1733
+ else:
1734
+ tester.assertIsInstance(ZZ(1)/3*self, Polyhedron_base)
1735
+
1736
+ try:
1737
+ from sage.rings.qqbar import AA
1738
+ import sage.libs.pari
1739
+ except ImportError:
1740
+ return
1741
+
1742
+ if self.n_vertices() > 20 or self.base_ring() is AA:
1743
+ # Avoid long time computations.
1744
+ return
1745
+
1746
+ # Some sanity check on the volume (only run for relatively small instances).
1747
+ if self.dim() > -1 and self.is_compact() and self.base_ring().is_exact():
1748
+ tester.assertEqual(self.dilation(3).volume(measure='induced'), self.volume(measure='induced')*3**self.dim())
1749
+
1750
+ # Testing coercion with algebraic numbers.
1751
+ try:
1752
+ from sage.rings.number_field.number_field import QuadraticField
1753
+ K1 = QuadraticField(2, embedding=AA(2).sqrt())
1754
+ sqrt2 = K1.gen()
1755
+ K2 = QuadraticField(3, embedding=AA(3).sqrt())
1756
+ sqrt3 = K2.gen()
1757
+ except ImportError:
1758
+ pass
1759
+ else:
1760
+ if self.base_ring() in (QQ, ZZ, AA, RDF):
1761
+ tester.assertIsInstance(sqrt2*self, Polyhedron_base)
1762
+ tester.assertIsInstance(sqrt3*self, Polyhedron_base)
1763
+ elif hasattr(self.base_ring(), "composite_fields"):
1764
+ for scalar, K in ((sqrt2, K1), (sqrt3, K2)):
1765
+ new_ring = None
1766
+ try:
1767
+ new_ring = self.base_ring().composite_fields()[0]
1768
+ except (KeyError, AttributeError, TypeError):
1769
+ # This isn't about testing composite fields.
1770
+ pass
1771
+ if new_ring:
1772
+ p = self.change_ring(new_ring)
1773
+ tester.assertIsInstance(scalar*p, Polyhedron_base)
1774
+
1775
+ def linear_transformation(self, linear_transf,
1776
+ new_base_ring=None):
1777
+ """
1778
+ Return the linear transformation of ``self``.
1779
+
1780
+ INPUT:
1781
+
1782
+ - ``linear_transf`` -- a matrix, not necessarily in :meth:`base_ring`
1783
+ - ``new_base_ring`` -- ring (optional); specify the new base ring;
1784
+ may avoid coercion failure
1785
+
1786
+ OUTPUT:
1787
+
1788
+ The polyhedron transformed by that matrix, possibly coerced to a
1789
+ bigger base ring.
1790
+
1791
+ .. SEEALSO:: :meth:`dilation`, :meth:`translation`
1792
+
1793
+ EXAMPLES::
1794
+
1795
+ sage: b3 = polytopes.Birkhoff_polytope(3)
1796
+ sage: proj_mat = matrix([[0,1,0,0,0,0,0,0,0], [0,0,0,1,0,0,0,0,0],
1797
+ ....: [0,0,0,0,0,1,0,0,0], [0,0,0,0,0,0,0,1,0]])
1798
+ sage: b3_proj = proj_mat * b3; b3_proj
1799
+ A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices
1800
+
1801
+ sage: # needs sage.rings.number_field
1802
+ sage: square = polytopes.regular_polygon(4)
1803
+ sage: square.vertices_list()
1804
+ [[0, -1], [1, 0], [-1, 0], [0, 1]]
1805
+ sage: transf = matrix([[1,1], [0,1]])
1806
+ sage: sheared = transf * square
1807
+ sage: sheared.vertices_list()
1808
+ [[-1, -1], [1, 0], [-1, 0], [1, 1]]
1809
+ sage: sheared == square.linear_transformation(transf)
1810
+ True
1811
+
1812
+ Specifying the new base ring may avoid coercion failure::
1813
+
1814
+ sage: # needs sage.rings.number_field
1815
+ sage: K.<sqrt2> = QuadraticField(2)
1816
+ sage: L.<sqrt3> = QuadraticField(3)
1817
+ sage: P = polytopes.cube()*sqrt2
1818
+ sage: M = matrix([[sqrt3, 0, 0], [0, sqrt3, 0], [0, 0, 1]])
1819
+ sage: P.linear_transformation(M, new_base_ring=K.composite_fields(L)[0])
1820
+ A 3-dimensional polyhedron in
1821
+ (Number Field in sqrt2sqrt3 with defining polynomial x^4 - 10*x^2 + 1
1822
+ with sqrt2sqrt3 = 0.3178372451957823?)^3
1823
+ defined as the convex hull of 8 vertices
1824
+
1825
+ Linear transformation without specified new base ring fails in this case::
1826
+
1827
+ sage: M*P # needs sage.rings.number_field
1828
+ Traceback (most recent call last):
1829
+ ...
1830
+ TypeError: unsupported operand parent(s) for *:
1831
+ 'Full MatrixSpace of 3 by 3 dense matrices over Number Field in sqrt3
1832
+ with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878?' and
1833
+ 'Full MatrixSpace of 3 by 8 dense matrices over Number Field in sqrt2
1834
+ with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?'
1835
+
1836
+ TESTS:
1837
+
1838
+ One can scale by a scalar as follows::
1839
+
1840
+ sage: P = polytopes.cube()
1841
+ sage: P2 = P.linear_transformation(2); P2
1842
+ A 3-dimensional polyhedron in QQ^3 defined as
1843
+ the convex hull of 8 vertices
1844
+ sage: P2.volume()
1845
+ 64
1846
+
1847
+ Linear transformation respects backend::
1848
+
1849
+ sage: P = polytopes.simplex(backend='field')
1850
+ sage: t = matrix([[1,1,1,1],[0,1,1,1],[0,0,1,1],[0,0,0,1]])
1851
+ sage: P.linear_transformation(t).backend()
1852
+ 'field'
1853
+
1854
+ Check that coercion works::
1855
+
1856
+ sage: (1.0 * proj_mat) * b3 # needs cddexec
1857
+ A 3-dimensional polyhedron in RDF^4 defined as the convex hull of 5 vertices
1858
+ sage: (1/1 * proj_mat) * b3
1859
+ A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 5 vertices
1860
+ sage: (AA(2).sqrt() * proj_mat) * b3 # needs sage.rings.number_field
1861
+ A 3-dimensional polyhedron in AA^4 defined as the convex hull of 5 vertices
1862
+
1863
+ Check that zero-matrices act correctly::
1864
+
1865
+ sage: Matrix([]) * b3
1866
+ A 0-dimensional polyhedron in ZZ^0 defined as the convex hull of 1 vertex
1867
+ sage: Matrix([[0 for _ in range(9)]]) * b3
1868
+ A 0-dimensional polyhedron in ZZ^1 defined as the convex hull of 1 vertex
1869
+ sage: Matrix([[0 for _ in range(9)] for _ in range(4)]) * b3
1870
+ A 0-dimensional polyhedron in ZZ^4 defined as the convex hull of 1 vertex
1871
+ sage: Matrix([[0 for _ in range(8)]]) * b3
1872
+ Traceback (most recent call last):
1873
+ ...
1874
+ TypeError: unsupported operand parent(s) for *:
1875
+ 'Full MatrixSpace of 1 by 8 dense matrices over Integer Ring' and
1876
+ 'Full MatrixSpace of 9 by 6 dense matrices over Integer Ring'
1877
+ sage: Matrix(ZZ, []) * b3
1878
+ A 0-dimensional polyhedron in ZZ^0 defined as the convex hull of 1 vertex
1879
+ sage: Matrix(ZZ, [[],[]]) * b3
1880
+ Traceback (most recent call last):
1881
+ ...
1882
+ TypeError: unsupported operand parent(s) for *:
1883
+ 'Full MatrixSpace of 2 by 0 dense matrices over Integer Ring' and
1884
+ 'Full MatrixSpace of 9 by 6 dense matrices over Integer Ring'
1885
+
1886
+ Check that the precomputed double description is correct::
1887
+
1888
+ sage: P = polytopes.permutahedron(4)
1889
+ sage: Q = P.change_ring(QQ, backend='field')
1890
+ sage: P.affine_hull_projection() == Q.affine_hull_projection()
1891
+ True
1892
+
1893
+ sage: M = matrix([[1, 2, 3, 4], [2, 3, 4, 5], [0, 0, 5, 1], [0, 2, 0, 3]])
1894
+ sage: M*P == M*Q
1895
+ True
1896
+
1897
+ sage: M = matrix([[1, 2, 3, 4], [2, 3, 4, 5], [0, 0, 5, 1], [0, 2, 0, 3], [0, 1, 0, -3]])
1898
+ sage: M*P == M*Q
1899
+ True
1900
+ """
1901
+ is_injective = False
1902
+
1903
+ if linear_transf in self.base_ring():
1904
+ # allow for scalar input
1905
+ linear_transf = linear_transf * self.ambient_vector_space().matrix()
1906
+
1907
+ if linear_transf.nrows() != 0:
1908
+ if new_base_ring:
1909
+ R = new_base_ring
1910
+ else:
1911
+ R = self.base_ring()
1912
+
1913
+ # Multiplying a matrix with a vector is slow.
1914
+ # So we multiply the entire vertex matrix etc.
1915
+ # Still we create generators, as possibly the Vrepresentation
1916
+ # will be discarded later on.
1917
+ if self.n_vertices():
1918
+ new_vertices = iter((linear_transf*self.vertices_matrix(R)).transpose())
1919
+ else:
1920
+ new_vertices = ()
1921
+ if self.n_rays():
1922
+ new_rays = iter(matrix(R, self.rays())*linear_transf.transpose())
1923
+ else:
1924
+ new_rays = ()
1925
+ if self.n_lines():
1926
+ new_lines = iter(matrix(R, self.lines())*linear_transf.transpose())
1927
+ else:
1928
+ new_lines = ()
1929
+
1930
+ if self.is_compact() and self.n_vertices() and self.n_inequalities():
1931
+ homogeneous_basis = matrix(R, ( [1] + list(v) for v in self.an_affine_basis() )).transpose()
1932
+
1933
+ # To convert first to a list and then to a matrix seems to be necessary to obtain a meaningful error,
1934
+ # in case the number of columns doesn't match the dimension.
1935
+ new_homogeneous_basis = matrix(list( [1] + list(linear_transf*vector(R, v)) for v in self.an_affine_basis()) ).transpose()
1936
+
1937
+ if self.dim() + 1 == new_homogeneous_basis.rank():
1938
+ # The transformation is injective on the polytope.
1939
+ is_injective = True
1940
+
1941
+ # Let V be the homogeneous vertex matrix (each vertex a column)
1942
+ # and M the linear transformation.
1943
+ # Then M*V is the new homogeneous vertex matrix.
1944
+
1945
+ # Let H be the inequalities matrix (each inequality a row).
1946
+ # If we find N such that N*M*V = V than the new inequalities are
1947
+ # given by H*N.
1948
+
1949
+ # Note that such N must exist, as our map is injective on the polytope.
1950
+ # It is uniquely defined by considering a basis of the homogeneous vertices.
1951
+ N = new_homogeneous_basis.solve_left(homogeneous_basis)
1952
+ new_inequalities = iter(matrix(R, self.inequalities())*N)
1953
+
1954
+ # The equations are the left kernel matrix of the homogeneous vertices
1955
+ # or equivalently a basis thereof.
1956
+ new_equations = (new_homogeneous_basis.transpose()).right_kernel_matrix()
1957
+
1958
+ else:
1959
+ new_vertices = [[] for v in self.vertex_generator()]
1960
+ new_rays = []
1961
+ new_lines = []
1962
+
1963
+ new_dim = linear_transf.nrows()
1964
+ par = self.parent()
1965
+
1966
+ if new_base_ring:
1967
+ new_parent = par.change_ring(new_base_ring, ambient_dim=new_dim)
1968
+ else:
1969
+ new_parent = par.base_extend(linear_transf.base_ring(), ambient_dim=new_dim)
1970
+
1971
+ if is_injective:
1972
+ # Set up with both Vrepresentation and Hrepresentation.
1973
+ pref_rep = 'Vrep' if self.n_vertices() <= self.n_inequalities() else 'Hrep'
1974
+
1975
+ return new_parent.element_class(new_parent, [new_vertices, new_rays, new_lines],
1976
+ [new_inequalities, new_equations],
1977
+ Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
1978
+
1979
+ return new_parent.element_class(new_parent, [tuple(new_vertices), tuple(new_rays), tuple(new_lines)], None)
1980
+
1981
+ def _test_linear_transformation(self, tester=None, **options):
1982
+ """
1983
+ Run some tests on linear transformation.
1984
+
1985
+ TESTS::
1986
+
1987
+ sage: Polyhedron(rays=[(0,1)])._test_linear_transformation()
1988
+ """
1989
+ if tester is None:
1990
+ tester = self._tester(**options)
1991
+
1992
+ if self.n_vertices() > 200 or self.n_facets() > 200:
1993
+ # Avoid very long doctests.
1994
+ return
1995
+
1996
+ # Check that :issue:`30146` is fixed.
1997
+ from sage.matrix.special import identity_matrix
1998
+ tester.assertEqual(self, self.linear_transformation(identity_matrix(self.ambient_dim())))
1999
+
2000
+ ###########################################################
2001
+ # Methods using a face.
2002
+ ###########################################################
2003
+
2004
+ def face_truncation(self, face, linear_coefficients=None, cut_frac=None):
2005
+ r"""
2006
+ Return a new polyhedron formed by truncating a face by an hyperplane.
2007
+
2008
+ By default, the normal vector of the hyperplane used to truncate the
2009
+ polyhedron is obtained by taking the barycenter vector of the cone
2010
+ corresponding to the truncated face in the normal fan of the
2011
+ polyhedron. It is possible to change the direction using the option
2012
+ ``linear_coefficients``.
2013
+
2014
+ To determine how deep the truncation is done, the method uses the
2015
+ parameter ``cut_frac``. By default it is equal to `\frac{1}{3}`. Once
2016
+ the normal vector of the cutting hyperplane is chosen, the vertices of
2017
+ polyhedron are evaluated according to the corresponding linear
2018
+ function. The parameter `\frac{1}{3}` means that the cutting
2019
+ hyperplane is placed `\frac{1}{3}` of the way from the vertices of the
2020
+ truncated face to the next evaluated vertex.
2021
+
2022
+ INPUT:
2023
+
2024
+ - ``face`` -- a :class:`~sage.geometry.polyhedron.face.PolyhedronFace`
2025
+ - ``linear_coefficients`` -- tuple of integer. Specifies the coefficient
2026
+ of the normal vector of the cutting hyperplane used to truncate the
2027
+ face.
2028
+ The default direction is determined using the normal fan of the
2029
+ polyhedron.
2030
+ - ``cut_frac`` -- number between 0 and 1. Determines where the
2031
+ hyperplane cuts the polyhedron. A value close to 0 cuts very close
2032
+ to the face, whereas a value close to 1 cuts very close to the next
2033
+ vertex (according to the normal vector of the cutting hyperplane).
2034
+ Default is `\frac{1}{3}`.
2035
+
2036
+ OUTPUT: a Polyhedron object, truncated as described above
2037
+
2038
+ EXAMPLES::
2039
+
2040
+ sage: Cube = polytopes.hypercube(3)
2041
+ sage: vertex_trunc1 = Cube.face_truncation(Cube.faces(0)[0])
2042
+ sage: vertex_trunc1.f_vector()
2043
+ (1, 10, 15, 7, 1)
2044
+ sage: tuple(f.ambient_V_indices() for f in vertex_trunc1.faces(2))
2045
+ ((4, 5, 6, 7, 9),
2046
+ (0, 3, 4, 8, 9),
2047
+ (0, 1, 6, 7, 8),
2048
+ (7, 8, 9),
2049
+ (2, 3, 4, 5),
2050
+ (1, 2, 5, 6),
2051
+ (0, 1, 2, 3))
2052
+ sage: vertex_trunc1.vertices()
2053
+ (A vertex at (1, -1, -1),
2054
+ A vertex at (1, 1, -1),
2055
+ A vertex at (1, 1, 1),
2056
+ A vertex at (1, -1, 1),
2057
+ A vertex at (-1, -1, 1),
2058
+ A vertex at (-1, 1, 1),
2059
+ A vertex at (-1, 1, -1),
2060
+ A vertex at (-1, -1/3, -1),
2061
+ A vertex at (-1/3, -1, -1),
2062
+ A vertex at (-1, -1, -1/3))
2063
+ sage: vertex_trunc2 = Cube.face_truncation(Cube.faces(0)[0], cut_frac=1/2)
2064
+ sage: vertex_trunc2.f_vector()
2065
+ (1, 10, 15, 7, 1)
2066
+ sage: tuple(f.ambient_V_indices() for f in vertex_trunc2.faces(2))
2067
+ ((4, 5, 6, 7, 9),
2068
+ (0, 3, 4, 8, 9),
2069
+ (0, 1, 6, 7, 8),
2070
+ (7, 8, 9),
2071
+ (2, 3, 4, 5),
2072
+ (1, 2, 5, 6),
2073
+ (0, 1, 2, 3))
2074
+ sage: vertex_trunc2.vertices()
2075
+ (A vertex at (1, -1, -1),
2076
+ A vertex at (1, 1, -1),
2077
+ A vertex at (1, 1, 1),
2078
+ A vertex at (1, -1, 1),
2079
+ A vertex at (-1, -1, 1),
2080
+ A vertex at (-1, 1, 1),
2081
+ A vertex at (-1, 1, -1),
2082
+ A vertex at (-1, 0, -1),
2083
+ A vertex at (0, -1, -1),
2084
+ A vertex at (-1, -1, 0))
2085
+ sage: vertex_trunc3 = Cube.face_truncation(Cube.faces(0)[0], cut_frac=0.3) # needs cddexec
2086
+ sage: vertex_trunc3.vertices() # needs cddexec
2087
+ (A vertex at (-1.0, -1.0, 1.0),
2088
+ A vertex at (-1.0, 1.0, -1.0),
2089
+ A vertex at (-1.0, 1.0, 1.0),
2090
+ A vertex at (1.0, 1.0, -1.0),
2091
+ A vertex at (1.0, 1.0, 1.0),
2092
+ A vertex at (1.0, -1.0, 1.0),
2093
+ A vertex at (1.0, -1.0, -1.0),
2094
+ A vertex at (-0.4, -1.0, -1.0),
2095
+ A vertex at (-1.0, -0.4, -1.0),
2096
+ A vertex at (-1.0, -1.0, -0.4))
2097
+ sage: edge_trunc = Cube.face_truncation(Cube.faces(1)[11])
2098
+ sage: edge_trunc.f_vector()
2099
+ (1, 10, 15, 7, 1)
2100
+ sage: tuple(f.ambient_V_indices() for f in edge_trunc.faces(2))
2101
+ ((0, 5, 6, 7),
2102
+ (1, 4, 5, 6, 8),
2103
+ (6, 7, 8, 9),
2104
+ (0, 2, 3, 7, 9),
2105
+ (1, 2, 8, 9),
2106
+ (0, 3, 4, 5),
2107
+ (1, 2, 3, 4))
2108
+ sage: face_trunc = Cube.face_truncation(Cube.faces(2)[2])
2109
+ sage: face_trunc.vertices()
2110
+ (A vertex at (1, -1, -1),
2111
+ A vertex at (1, 1, -1),
2112
+ A vertex at (1, 1, 1),
2113
+ A vertex at (1, -1, 1),
2114
+ A vertex at (-1/3, -1, 1),
2115
+ A vertex at (-1/3, 1, 1),
2116
+ A vertex at (-1/3, 1, -1),
2117
+ A vertex at (-1/3, -1, -1))
2118
+ sage: face_trunc.face_lattice().is_isomorphic(Cube.face_lattice()) # needs sage.combinat sage.graphs
2119
+ True
2120
+
2121
+ TESTS:
2122
+
2123
+ Testing that the backend is preserved::
2124
+
2125
+ sage: Cube = polytopes.cube(backend='field')
2126
+ sage: face_trunc = Cube.face_truncation(Cube.faces(2)[0])
2127
+ sage: face_trunc.backend()
2128
+ 'field'
2129
+
2130
+ Testing that :issue:`28506` is fixed::
2131
+
2132
+ sage: P = polytopes.twenty_four_cell()
2133
+ sage: P = P.dilation(6)
2134
+ sage: P = P.change_ring(ZZ)
2135
+ sage: P.face_truncation(P.faces(2)[0], cut_frac=1)
2136
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 27 vertices
2137
+ """
2138
+ if cut_frac is None:
2139
+ cut_frac = ZZ.one() / 3
2140
+
2141
+ face_vertices = face.vertices()
2142
+
2143
+ normal_vectors = []
2144
+
2145
+ for facet in self.Hrepresentation():
2146
+ if all(facet.contains(x) and not facet.interior_contains(x)
2147
+ for x in face_vertices):
2148
+ # The facet contains the face
2149
+ normal_vectors.append(facet.A())
2150
+
2151
+ if linear_coefficients is not None:
2152
+ normal_vector = sum(linear_coefficients[i]*normal_vectors[i]
2153
+ for i in range(len(normal_vectors)))
2154
+ else:
2155
+ normal_vector = sum(normal_vectors)
2156
+
2157
+ B = - normal_vector * (face_vertices[0].vector())
2158
+
2159
+ linear_evaluation = set(-normal_vector * (v.vector()) for v in self.vertices())
2160
+
2161
+ if B == max(linear_evaluation):
2162
+ C = max(linear_evaluation.difference(set([B])))
2163
+ else:
2164
+ C = min(linear_evaluation.difference(set([B])))
2165
+
2166
+ cut_height = (1 - cut_frac) * B + cut_frac * C
2167
+ ineq_vector = tuple([cut_height]) + tuple(normal_vector)
2168
+
2169
+ new_ieqs = self.inequalities_list() + [ineq_vector]
2170
+ new_eqns = self.equations_list()
2171
+
2172
+ # Some vertices might need fractions.
2173
+ parent = self.parent().base_extend(cut_frac/1)
2174
+ return parent.element_class(parent, None, [new_ieqs, new_eqns])
2175
+
2176
+ def stack(self, face, position=None):
2177
+ r"""
2178
+ Return a new polyhedron formed by stacking onto a ``face``. Stacking a
2179
+ face adds a new vertex located slightly outside of the designated face.
2180
+
2181
+ INPUT:
2182
+
2183
+ - ``face`` -- a PolyhedronFace
2184
+
2185
+ - ``position`` -- a positive number. Determines a relative distance
2186
+ from the barycenter of ``face``. A value close to 0 will place the
2187
+ new vertex close to the face and a large value further away. Default
2188
+ is `1`. If the given value is too large, an error is returned.
2189
+
2190
+ OUTPUT: a Polyhedron object
2191
+
2192
+ EXAMPLES::
2193
+
2194
+ sage: cube = polytopes.cube()
2195
+ sage: square_face = cube.facets()[2]
2196
+ sage: stacked_square = cube.stack(square_face)
2197
+ sage: stacked_square.f_vector()
2198
+ (1, 9, 16, 9, 1)
2199
+
2200
+ sage: edge_face = cube.faces(1)[3]
2201
+ sage: stacked_edge = cube.stack(edge_face)
2202
+ sage: stacked_edge.f_vector()
2203
+ (1, 9, 17, 10, 1)
2204
+
2205
+ sage: cube.stack(cube.faces(0)[0])
2206
+ Traceback (most recent call last):
2207
+ ...
2208
+ ValueError: cannot stack onto a vertex
2209
+
2210
+ sage: stacked_square_half = cube.stack(square_face, position=1/2)
2211
+ sage: stacked_square_half.f_vector()
2212
+ (1, 9, 16, 9, 1)
2213
+ sage: stacked_square_large = cube.stack(square_face, position=10)
2214
+
2215
+ sage: # needs sage.rings.number_field
2216
+ sage: hexaprism = polytopes.regular_polygon(6).prism()
2217
+ sage: hexaprism.f_vector()
2218
+ (1, 12, 18, 8, 1)
2219
+ sage: square_face = hexaprism.faces(2)[2]
2220
+ sage: stacked_hexaprism = hexaprism.stack(square_face)
2221
+ sage: stacked_hexaprism.f_vector()
2222
+ (1, 13, 22, 11, 1)
2223
+
2224
+ sage: hexaprism.stack(square_face, position=4) # needs sage.rings.number_field
2225
+ Traceback (most recent call last):
2226
+ ...
2227
+ ValueError: the chosen position is too large
2228
+
2229
+ sage: s = polytopes.simplex(7)
2230
+ sage: f = s.faces(3)[69]
2231
+ sage: sf = s.stack(f); sf
2232
+ A 7-dimensional polyhedron in QQ^8 defined as the convex hull of 9 vertices
2233
+ sage: sf.vertices()
2234
+ (A vertex at (-4, -4, -4, -4, 17/4, 17/4, 17/4, 17/4),
2235
+ A vertex at (0, 0, 0, 0, 0, 0, 0, 1),
2236
+ A vertex at (0, 0, 0, 0, 0, 0, 1, 0),
2237
+ A vertex at (0, 0, 0, 0, 0, 1, 0, 0),
2238
+ A vertex at (0, 0, 0, 0, 1, 0, 0, 0),
2239
+ A vertex at (0, 0, 0, 1, 0, 0, 0, 0),
2240
+ A vertex at (0, 0, 1, 0, 0, 0, 0, 0),
2241
+ A vertex at (0, 1, 0, 0, 0, 0, 0, 0),
2242
+ A vertex at (1, 0, 0, 0, 0, 0, 0, 0))
2243
+
2244
+ It is possible to stack on unbounded faces::
2245
+
2246
+ sage: Q = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]])
2247
+ sage: E = Q.faces(1)
2248
+ sage: Q.stack(E[0],1/2).Vrepresentation()
2249
+ (A vertex at (0, 1),
2250
+ A vertex at (1, 0),
2251
+ A ray in the direction (1, 1),
2252
+ A vertex at (2, 0))
2253
+ sage: Q.stack(E[1],1/2).Vrepresentation()
2254
+ (A vertex at (0, 1),
2255
+ A vertex at (0, 2),
2256
+ A vertex at (1, 0),
2257
+ A ray in the direction (1, 1))
2258
+ sage: Q.stack(E[2],1/2).Vrepresentation()
2259
+ (A vertex at (0, 0),
2260
+ A vertex at (0, 1),
2261
+ A vertex at (1, 0),
2262
+ A ray in the direction (1, 1))
2263
+
2264
+ Stacking requires a proper face::
2265
+
2266
+ sage: Q.stack(Q.faces(2)[0])
2267
+ Traceback (most recent call last):
2268
+ ...
2269
+ ValueError: can only stack on proper face
2270
+
2271
+ TESTS:
2272
+
2273
+ Checking that the backend is preserved::
2274
+
2275
+ sage: Cube = polytopes.cube(backend='field')
2276
+ sage: stack = Cube.stack(Cube.faces(2)[0])
2277
+ sage: stack.backend()
2278
+ 'field'
2279
+
2280
+ Taking the stacking vertex too far with the parameter ``position``
2281
+ may result in a failure to produce the desired
2282
+ (combinatorial type of) polytope.
2283
+ The interval of permitted values is always open.
2284
+ This is the smallest unpermitted value::
2285
+
2286
+ sage: P = polytopes.octahedron()
2287
+ sage: P.stack(P.faces(2)[0], position=4)
2288
+ Traceback (most recent call last):
2289
+ ...
2290
+ ValueError: the chosen position is too large
2291
+
2292
+ Testing that :issue:`29057` is fixed::
2293
+
2294
+ sage: P = polytopes.cross_polytope(4)
2295
+ sage: P.stack(P.faces(3)[0])
2296
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices
2297
+ """
2298
+ from sage.geometry.polyhedron.face import PolyhedronFace
2299
+ if not isinstance(face, PolyhedronFace):
2300
+ raise TypeError("{} should be a PolyhedronFace of {}".format(face, self))
2301
+ elif face.dim() == 0:
2302
+ raise ValueError("cannot stack onto a vertex")
2303
+ elif face.dim() == -1 or face.dim() == self.dim():
2304
+ raise ValueError("can only stack on proper face")
2305
+ if position is None:
2306
+ position = 1
2307
+
2308
+ barycenter = ZZ.one()*sum([v.vector() for v in face.vertices()]) / len(face.vertices())
2309
+ locus_polyhedron = face.stacking_locus()
2310
+ repr_point = locus_polyhedron.representative_point()
2311
+ new_vertex = (1-position)*barycenter + position*repr_point
2312
+ if not locus_polyhedron.relative_interior_contains(new_vertex):
2313
+ raise ValueError("the chosen position is too large")
2314
+
2315
+ parent = self.parent().base_extend(new_vertex)
2316
+ return parent.element_class(parent, [self.vertices() + (new_vertex,), self.rays(), self.lines()], None)
2317
+
2318
+ def wedge(self, face, width=1):
2319
+ r"""
2320
+ Return the wedge over a ``face`` of the polytope ``self``.
2321
+
2322
+ The wedge over a face `F` of a polytope `P` with width `w \not= 0`
2323
+ is defined as:
2324
+
2325
+ .. MATH::
2326
+
2327
+ (P \times \mathbb{R}) \cap \{a^\top x + |w x_{d+1}| \leq b\}
2328
+
2329
+ where `\{x | a^\top x = b\}` is a supporting hyperplane defining `F`.
2330
+
2331
+ INPUT:
2332
+
2333
+ - ``face`` -- a PolyhedronFace of ``self``, the face which we take
2334
+ the wedge over
2335
+ - ``width`` -- a nonzero number (default: ``1``);
2336
+ specifies how wide the wedge will be
2337
+
2338
+ OUTPUT:
2339
+
2340
+ A (bounded) polyhedron
2341
+
2342
+ EXAMPLES::
2343
+
2344
+ sage: # needs sage.rings.number_field
2345
+ sage: P_4 = polytopes.regular_polygon(4)
2346
+ sage: W1 = P_4.wedge(P_4.faces(1)[0]); W1
2347
+ A 3-dimensional polyhedron in AA^3 defined as the convex hull of 6 vertices
2348
+ sage: triangular_prism = polytopes.regular_polygon(3).prism()
2349
+ sage: W1.is_combinatorially_isomorphic(triangular_prism) # needs sage.graphs
2350
+ True
2351
+
2352
+ sage: Q = polytopes.hypersimplex(4,2)
2353
+ sage: W2 = Q.wedge(Q.faces(2)[7]); W2
2354
+ A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 9 vertices
2355
+ sage: W2.vertices()
2356
+ (A vertex at (1, 1, 0, 0, 1),
2357
+ A vertex at (1, 1, 0, 0, -1),
2358
+ A vertex at (1, 0, 1, 0, 1),
2359
+ A vertex at (1, 0, 1, 0, -1),
2360
+ A vertex at (1, 0, 0, 1, 1),
2361
+ A vertex at (1, 0, 0, 1, -1),
2362
+ A vertex at (0, 0, 1, 1, 0),
2363
+ A vertex at (0, 1, 1, 0, 0),
2364
+ A vertex at (0, 1, 0, 1, 0))
2365
+
2366
+ sage: W3 = Q.wedge(Q.faces(1)[11]); W3
2367
+ A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 10 vertices
2368
+ sage: W3.vertices()
2369
+ (A vertex at (1, 1, 0, 0, -2),
2370
+ A vertex at (1, 1, 0, 0, 2),
2371
+ A vertex at (1, 0, 1, 0, -2),
2372
+ A vertex at (1, 0, 1, 0, 2),
2373
+ A vertex at (1, 0, 0, 1, 1),
2374
+ A vertex at (1, 0, 0, 1, -1),
2375
+ A vertex at (0, 1, 0, 1, 0),
2376
+ A vertex at (0, 1, 1, 0, 1),
2377
+ A vertex at (0, 0, 1, 1, 0),
2378
+ A vertex at (0, 1, 1, 0, -1))
2379
+
2380
+ sage: C_3_7 = polytopes.cyclic_polytope(3,7)
2381
+ sage: P_6 = polytopes.regular_polygon(6) # needs sage.rings.number_field
2382
+ sage: W4 = P_6.wedge(P_6.faces(1)[0]) # needs sage.rings.number_field
2383
+ sage: W4.is_combinatorially_isomorphic(C_3_7.polar()) # needs sage.graphs sage.rings.number_field
2384
+ True
2385
+
2386
+ REFERENCES:
2387
+
2388
+ For more information, see Chapter 15 of [HoDaCG17]_.
2389
+
2390
+ TESTS:
2391
+
2392
+ The backend should be preserved as long as the value of width permits.
2393
+ The base_ring will change to the field of fractions of the current
2394
+ base_ring, unless ``width`` forces a different ring. ::
2395
+
2396
+ sage: P = polytopes.cyclic_polytope(3,7, base_ring=ZZ, backend='field')
2397
+ sage: W1 = P.wedge(P.faces(2)[0]); W1.base_ring(); W1.backend()
2398
+ Rational Field
2399
+ 'field'
2400
+ sage: W2 = P.wedge(P.faces(2)[0], width=5/2); W2.base_ring(); W2.backend()
2401
+ Rational Field
2402
+ 'field'
2403
+ sage: W2 = P.wedge(P.faces(2)[9], width=4/2); W2.base_ring(); W2.backend()
2404
+ Rational Field
2405
+ 'field'
2406
+ sage: W2.vertices()
2407
+ (A vertex at (3, 9, 27, -1/2),
2408
+ A vertex at (4, 16, 64, -2),
2409
+ A vertex at (6, 36, 216, -10),
2410
+ A vertex at (5, 25, 125, -5),
2411
+ A vertex at (2, 4, 8, 0),
2412
+ A vertex at (1, 1, 1, 0),
2413
+ A vertex at (0, 0, 0, 0),
2414
+ A vertex at (3, 9, 27, 1/2),
2415
+ A vertex at (4, 16, 64, 2),
2416
+ A vertex at (6, 36, 216, 10),
2417
+ A vertex at (5, 25, 125, 5))
2418
+ sage: W2 = P.wedge(P.faces(2)[2], width=1.0); W2.base_ring(); W2.backend() # needs cddexec
2419
+ Real Double Field
2420
+ 'cdd'
2421
+ """
2422
+ width = width*ZZ.one()
2423
+
2424
+ if not self.is_compact():
2425
+ raise ValueError("polyhedron 'self' must be a polytope")
2426
+
2427
+ if width == 0:
2428
+ raise ValueError("the width should be nonzero")
2429
+
2430
+ from sage.geometry.polyhedron.face import PolyhedronFace
2431
+ if not isinstance(face, PolyhedronFace):
2432
+ raise TypeError("{} should be a PolyhedronFace of {}".format(face, self))
2433
+
2434
+ F_Hrep = vector([0]*(self.ambient_dim()+1))
2435
+ for facet in face.ambient_Hrepresentation():
2436
+ if facet.is_inequality():
2437
+ F_Hrep = F_Hrep + facet.vector()
2438
+ F_Hrep = list(F_Hrep)
2439
+
2440
+ parent = self.parent()
2441
+ parent1 = parent.base_extend(self.base_ring(), ambient_dim=1)
2442
+ parent2 = parent.base_extend(width.base_ring().fraction_field(), ambient_dim=1 + self.ambient_dim())
2443
+
2444
+ L = parent1.element_class(parent1, [[[0]], [], [[1]]], None)
2445
+ Q = self.product(L)
2446
+ ieqs = [F_Hrep + [width], F_Hrep + [-width]]
2447
+
2448
+ H = parent2.element_class(parent2, None, [ieqs, []])
2449
+ return Q.intersection(H)
2450
+
2451
+ def face_split(self, face):
2452
+ """
2453
+ Return the face splitting of the face ``face``.
2454
+
2455
+ Splitting a face correspond to the bipyramid (see :meth:`bipyramid`)
2456
+ of ``self`` where the two new vertices are placed above and below
2457
+ the center of ``face`` instead of the center of the whole polyhedron.
2458
+ The two new vertices are placed in the new dimension at height `-1` and
2459
+ `1`.
2460
+
2461
+ INPUT:
2462
+
2463
+ - ``face`` -- a PolyhedronFace or a Vertex
2464
+
2465
+ EXAMPLES::
2466
+
2467
+ sage: # needs sage.rings.number_field
2468
+ sage: pentagon = polytopes.regular_polygon(5)
2469
+ sage: f = pentagon.faces(1)[0]
2470
+ sage: fsplit_pentagon = pentagon.face_split(f)
2471
+ sage: fsplit_pentagon.f_vector()
2472
+ (1, 7, 14, 9, 1)
2473
+
2474
+ TESTS:
2475
+
2476
+ Check that :issue:`28668` is fixed::
2477
+
2478
+ sage: P = polytopes.octahedron()
2479
+ sage: P.face_split(P.faces(2)[0])
2480
+ A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 8 vertices
2481
+
2482
+ .. SEEALSO::
2483
+
2484
+ :meth:`one_point_suspension`
2485
+ """
2486
+ from sage.geometry.polyhedron.representation import Vertex
2487
+ from sage.geometry.polyhedron.face import PolyhedronFace
2488
+ if isinstance(face, Vertex):
2489
+ new_vertices = [list(x) + [0] for x in self.vertex_generator()] + \
2490
+ [list(face) + [x] for x in [-1, 1]] # Splitting the vertex
2491
+ elif isinstance(face, PolyhedronFace):
2492
+ new_vertices = [list(x) + [0] for x in self.vertex_generator()] + \
2493
+ [list(face.as_polyhedron().center()) + [x] for x in [-1, 1]] # Splitting the face
2494
+ else:
2495
+ raise TypeError("the face {} should be a Vertex or PolyhedronFace".format(face))
2496
+
2497
+ new_rays = []
2498
+ new_rays.extend( [ r + [0] for r in self.ray_generator() ] )
2499
+
2500
+ new_lines = []
2501
+ new_lines.extend( [ l + [0] for l in self.line_generator() ] )
2502
+
2503
+ parent = self.parent().change_ring(self.base_ring().fraction_field(), ambient_dim=self.ambient_dim()+1)
2504
+ return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
2505
+
2506
+ ###########################################################
2507
+ # Methods using a vertex or vector.
2508
+ ###########################################################
2509
+
2510
+ def lawrence_extension(self, v):
2511
+ """
2512
+ Return the Lawrence extension of ``self`` on the point ``v``.
2513
+
2514
+ Let `P` be a polytope and `v` be a vertex of `P` or a point outside
2515
+ `P`. The Lawrence extension of `P` on `v` is the convex hull of
2516
+ `(v,1),(v,2)` and `(u,0)` for all vertices `u` in `P` other than `v`
2517
+ if `v` is a vertex.
2518
+
2519
+ INPUT:
2520
+
2521
+ - ``v`` -- a vertex of ``self`` or a point outside it
2522
+
2523
+ EXAMPLES::
2524
+
2525
+ sage: P = polytopes.cube()
2526
+ sage: P.lawrence_extension(P.vertices()[0])
2527
+ A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 9 vertices
2528
+ sage: P.lawrence_extension([-1,-1,-1])
2529
+ A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 9 vertices
2530
+
2531
+ REFERENCES:
2532
+
2533
+ For more information, see Section 6.6 of [Zie2007]_.
2534
+ """
2535
+ if not self.is_compact():
2536
+ raise NotImplementedError("self must be a polytope")
2537
+
2538
+ V = self.vertices_list()
2539
+ v = list(v)
2540
+
2541
+ if self.contains(v) and (v not in V):
2542
+ raise ValueError("{} must not be a vertex or outside self".format(v))
2543
+
2544
+ lambda_V = [u + [0] for u in V if u != v] + [v+[1]] + [v+[2]]
2545
+ parent = self.parent().base_extend(vector(v), ambient_dim=self.ambient_dim() + 1)
2546
+ return parent.element_class(parent, [lambda_V, [], []], None)
2547
+
2548
+ def _test_lawrence(self, tester=None, **options):
2549
+ """
2550
+ Run tests on the methods related to lawrence extensions.
2551
+
2552
+ TESTS:
2553
+
2554
+ Check that :issue:`28725` is fixed::
2555
+
2556
+ sage: polytopes.regular_polygon(3)._test_lawrence() # needs sage.rings.number_field
2557
+
2558
+ Check that :issue:`30293` is fixed::
2559
+
2560
+ sage: polytopes.cube()._test_lawrence()
2561
+ """
2562
+ if tester is None:
2563
+ tester = self._tester(**options)
2564
+
2565
+ if self.backend() == 'normaliz' and self.base_ring() not in (ZZ, QQ):
2566
+ # Speeds up the doctest for significantly.
2567
+ self = self.change_ring(self._internal_base_ring)
2568
+
2569
+ if not self.is_compact():
2570
+ with tester.assertRaises(NotImplementedError):
2571
+ self.lawrence_polytope()
2572
+ with tester.assertRaises(NotImplementedError):
2573
+ self.lawrence_extension(self.vertices()[0])
2574
+ return
2575
+
2576
+ if self.n_vertices() > 1:
2577
+ # ``v`` must be a vertex or outside ``self``.
2578
+ with tester.assertRaises(ValueError):
2579
+ self.lawrence_extension(self.center())
2580
+
2581
+ if self.n_vertices() >= 40 or self.n_facets() > 40:
2582
+ # Avoid very long tests.
2583
+ return
2584
+
2585
+ if self.n_vertices():
2586
+ from sage.misc.prandom import randint
2587
+ v = self.vertices()[randint(0, self.n_vertices()-1)].vector()
2588
+
2589
+ # A lawrence extension with a vertex.
2590
+ P = self.lawrence_extension(v)
2591
+ tester.assertEqual(self.dim() + 1, P.dim())
2592
+ tester.assertEqual(self.n_vertices() + 1, P.n_vertices())
2593
+ tester.assertEqual(self.backend(), P.backend())
2594
+
2595
+ if self.n_vertices() > 1:
2596
+ # A lawrence extension with a point outside of the polyhedron.
2597
+ Q = self.lawrence_extension(2*v - self.center())
2598
+ tester.assertEqual(self.dim() + 1, Q.dim())
2599
+ tester.assertEqual(self.n_vertices() + 2, Q.n_vertices())
2600
+ tester.assertEqual(self.backend(), Q.backend()) # Any backend should handle the fraction field.
2601
+
2602
+ import warnings
2603
+
2604
+ with warnings.catch_warnings():
2605
+ warnings.simplefilter("error")
2606
+ try:
2607
+ from sage.rings.real_double_field import RDF
2608
+ two = RDF(2.0)
2609
+ # Implicitly checks :issue:`30328`.
2610
+ R = self.lawrence_extension(two * v - self.center())
2611
+ tester.assertEqual(self.dim() + 1, R.dim())
2612
+ tester.assertEqual(self.n_vertices() + 2, R.n_vertices())
2613
+
2614
+ tester.assertTrue(Q.is_combinatorially_isomorphic(R))
2615
+ except ImportError:
2616
+ # RDF not available
2617
+ pass
2618
+ except UserWarning:
2619
+ # Data is numerically complicated.
2620
+ pass
2621
+ except ValueError as err:
2622
+ if "Numerical inconsistency" not in err.args[0]:
2623
+ raise err
2624
+
2625
+ if self.n_vertices() >= 12 or (self.base_ring() not in (ZZ, QQ) and self.backend() == 'field'):
2626
+ # Avoid very long tests.
2627
+ return
2628
+
2629
+ P = self.lawrence_polytope()
2630
+ tester.assertEqual(self.dim() + self.n_vertices(), P.dim())
2631
+ tester.assertEqual(self.n_vertices()*2, P.n_vertices())
2632
+ tester.assertEqual(self.backend(), P.backend())
2633
+ tester.assertTrue(P.is_lawrence_polytope())
2634
+
2635
+ # Construct the lawrence polytope iteratively by lawrence extensions.
2636
+ V = self.vertices_list()
2637
+ Q = self
2638
+ i = 0
2639
+ for v in V:
2640
+ v = v + i*[0]
2641
+ Q = Q.lawrence_extension(v)
2642
+ i = i + 1
2643
+ tester.assertEqual(P, Q)
2644
+
2645
+ def one_point_suspension(self, vertex):
2646
+ """
2647
+ Return the one-point suspension of ``self`` by splitting the vertex
2648
+ ``vertex``.
2649
+
2650
+ The resulting polyhedron has one more vertex and its dimension
2651
+ increases by one.
2652
+
2653
+ INPUT:
2654
+
2655
+ - ``vertex`` -- a Vertex of ``self``
2656
+
2657
+ EXAMPLES::
2658
+
2659
+ sage: cube = polytopes.cube()
2660
+ sage: v = cube.vertices()[0]
2661
+ sage: ops_cube = cube.one_point_suspension(v)
2662
+ sage: ops_cube.f_vector()
2663
+ (1, 9, 24, 24, 9, 1)
2664
+
2665
+ sage: # needs sage.rings.number_field
2666
+ sage: pentagon = polytopes.regular_polygon(5)
2667
+ sage: v = pentagon.vertices()[0]
2668
+ sage: ops_pentagon = pentagon.one_point_suspension(v)
2669
+ sage: ops_pentagon.f_vector()
2670
+ (1, 6, 12, 8, 1)
2671
+
2672
+ It works with a polyhedral face as well::
2673
+
2674
+ sage: vv = cube.faces(0)[1]
2675
+ sage: ops_cube2 = cube.one_point_suspension(vv)
2676
+ sage: ops_cube == ops_cube2
2677
+ True
2678
+
2679
+ .. SEEALSO::
2680
+
2681
+ :meth:`face_split`
2682
+
2683
+ TESTS::
2684
+
2685
+ sage: e = cube.faces(1)[0]
2686
+ sage: cube.one_point_suspension(e)
2687
+ Traceback (most recent call last):
2688
+ ...
2689
+ TypeError: the vertex
2690
+ A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices
2691
+ should be a Vertex or PolyhedronFace of dimension 0
2692
+ """
2693
+ from sage.geometry.polyhedron.representation import Vertex
2694
+ from sage.geometry.polyhedron.face import PolyhedronFace
2695
+ if isinstance(vertex, Vertex):
2696
+ return self.face_split(vertex)
2697
+ elif isinstance(vertex, PolyhedronFace) and vertex.dim() == 0:
2698
+ return self.face_split(vertex)
2699
+ else:
2700
+ raise TypeError("the vertex {} should be a Vertex or PolyhedronFace of dimension 0".format(vertex))