passagemath-polyhedra 10.6.31rc3__cp314-cp314-macosx_13_0_arm64.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 (205) hide show
  1. passagemath_polyhedra-10.6.31rc3.dist-info/METADATA +368 -0
  2. passagemath_polyhedra-10.6.31rc3.dist-info/METADATA.bak +371 -0
  3. passagemath_polyhedra-10.6.31rc3.dist-info/RECORD +205 -0
  4. passagemath_polyhedra-10.6.31rc3.dist-info/WHEEL +6 -0
  5. passagemath_polyhedra-10.6.31rc3.dist-info/top_level.txt +2 -0
  6. passagemath_polyhedra.dylibs/libgmp.10.dylib +0 -0
  7. sage/all__sagemath_polyhedra.py +50 -0
  8. sage/game_theory/all.py +8 -0
  9. sage/game_theory/catalog.py +6 -0
  10. sage/game_theory/catalog_normal_form_games.py +923 -0
  11. sage/game_theory/cooperative_game.py +844 -0
  12. sage/game_theory/matching_game.py +1181 -0
  13. sage/game_theory/normal_form_game.py +2697 -0
  14. sage/game_theory/parser.py +275 -0
  15. sage/geometry/all__sagemath_polyhedra.py +22 -0
  16. sage/geometry/cone.py +6940 -0
  17. sage/geometry/cone_catalog.py +847 -0
  18. sage/geometry/cone_critical_angles.py +1027 -0
  19. sage/geometry/convex_set.py +1119 -0
  20. sage/geometry/fan.py +3743 -0
  21. sage/geometry/fan_isomorphism.py +389 -0
  22. sage/geometry/fan_morphism.py +1884 -0
  23. sage/geometry/hasse_diagram.py +202 -0
  24. sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
  25. sage/geometry/hyperplane_arrangement/all.py +1 -0
  26. sage/geometry/hyperplane_arrangement/arrangement.py +3895 -0
  27. sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
  28. sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
  29. sage/geometry/hyperplane_arrangement/library.py +825 -0
  30. sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
  31. sage/geometry/hyperplane_arrangement/plot.py +520 -0
  32. sage/geometry/integral_points.py +35 -0
  33. sage/geometry/integral_points_generic_dense.cpython-314-darwin.so +0 -0
  34. sage/geometry/integral_points_generic_dense.pyx +7 -0
  35. sage/geometry/lattice_polytope.py +5894 -0
  36. sage/geometry/linear_expression.py +773 -0
  37. sage/geometry/newton_polygon.py +767 -0
  38. sage/geometry/point_collection.cpython-314-darwin.so +0 -0
  39. sage/geometry/point_collection.pyx +1008 -0
  40. sage/geometry/polyhedral_complex.py +2616 -0
  41. sage/geometry/polyhedron/all.py +8 -0
  42. sage/geometry/polyhedron/backend_cdd.py +460 -0
  43. sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
  44. sage/geometry/polyhedron/backend_field.py +347 -0
  45. sage/geometry/polyhedron/backend_normaliz.py +2503 -0
  46. sage/geometry/polyhedron/backend_number_field.py +168 -0
  47. sage/geometry/polyhedron/backend_polymake.py +765 -0
  48. sage/geometry/polyhedron/backend_ppl.py +582 -0
  49. sage/geometry/polyhedron/base.py +1206 -0
  50. sage/geometry/polyhedron/base0.py +1444 -0
  51. sage/geometry/polyhedron/base1.py +886 -0
  52. sage/geometry/polyhedron/base2.py +812 -0
  53. sage/geometry/polyhedron/base3.py +1845 -0
  54. sage/geometry/polyhedron/base4.py +1262 -0
  55. sage/geometry/polyhedron/base5.py +2700 -0
  56. sage/geometry/polyhedron/base6.py +1741 -0
  57. sage/geometry/polyhedron/base7.py +997 -0
  58. sage/geometry/polyhedron/base_QQ.py +1258 -0
  59. sage/geometry/polyhedron/base_RDF.py +98 -0
  60. sage/geometry/polyhedron/base_ZZ.py +934 -0
  61. sage/geometry/polyhedron/base_mutable.py +215 -0
  62. sage/geometry/polyhedron/base_number_field.py +122 -0
  63. sage/geometry/polyhedron/cdd_file_format.py +155 -0
  64. sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
  65. sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-darwin.so +0 -0
  66. sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
  67. sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
  68. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-darwin.so +0 -0
  69. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
  70. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
  71. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-darwin.so +0 -0
  72. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
  73. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
  74. sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
  75. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-darwin.so +0 -0
  76. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
  77. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
  78. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-darwin.so +0 -0
  79. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
  80. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
  81. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-darwin.so +0 -0
  82. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
  83. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
  84. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-darwin.so +0 -0
  85. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
  86. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
  87. sage/geometry/polyhedron/constructor.py +773 -0
  88. sage/geometry/polyhedron/double_description.py +753 -0
  89. sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
  90. sage/geometry/polyhedron/face.py +1060 -0
  91. sage/geometry/polyhedron/generating_function.py +1810 -0
  92. sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
  93. sage/geometry/polyhedron/library.py +3502 -0
  94. sage/geometry/polyhedron/misc.py +121 -0
  95. sage/geometry/polyhedron/modules/all.py +1 -0
  96. sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
  97. sage/geometry/polyhedron/palp_database.py +447 -0
  98. sage/geometry/polyhedron/parent.py +1279 -0
  99. sage/geometry/polyhedron/plot.py +1986 -0
  100. sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
  101. sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
  102. sage/geometry/polyhedron/representation.py +1723 -0
  103. sage/geometry/pseudolines.py +515 -0
  104. sage/geometry/relative_interior.py +445 -0
  105. sage/geometry/toric_plotter.py +1103 -0
  106. sage/geometry/triangulation/all.py +2 -0
  107. sage/geometry/triangulation/base.cpython-314-darwin.so +0 -0
  108. sage/geometry/triangulation/base.pyx +963 -0
  109. sage/geometry/triangulation/data.h +147 -0
  110. sage/geometry/triangulation/data.pxd +4 -0
  111. sage/geometry/triangulation/element.py +914 -0
  112. sage/geometry/triangulation/functions.h +10 -0
  113. sage/geometry/triangulation/functions.pxd +4 -0
  114. sage/geometry/triangulation/point_configuration.py +2256 -0
  115. sage/geometry/triangulation/triangulations.h +49 -0
  116. sage/geometry/triangulation/triangulations.pxd +7 -0
  117. sage/geometry/voronoi_diagram.py +319 -0
  118. sage/interfaces/all__sagemath_polyhedra.py +1 -0
  119. sage/interfaces/polymake.py +2028 -0
  120. sage/numerical/all.py +13 -0
  121. sage/numerical/all__sagemath_polyhedra.py +11 -0
  122. sage/numerical/backends/all.py +1 -0
  123. sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
  124. sage/numerical/backends/cvxopt_backend.cpython-314-darwin.so +0 -0
  125. sage/numerical/backends/cvxopt_backend.pyx +1006 -0
  126. sage/numerical/backends/cvxopt_backend_test.py +19 -0
  127. sage/numerical/backends/cvxopt_sdp_backend.cpython-314-darwin.so +0 -0
  128. sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
  129. sage/numerical/backends/cvxpy_backend.cpython-314-darwin.so +0 -0
  130. sage/numerical/backends/cvxpy_backend.pxd +41 -0
  131. sage/numerical/backends/cvxpy_backend.pyx +934 -0
  132. sage/numerical/backends/cvxpy_backend_test.py +13 -0
  133. sage/numerical/backends/generic_backend_test.py +24 -0
  134. sage/numerical/backends/interactivelp_backend.cpython-314-darwin.so +0 -0
  135. sage/numerical/backends/interactivelp_backend.pxd +36 -0
  136. sage/numerical/backends/interactivelp_backend.pyx +1231 -0
  137. sage/numerical/backends/interactivelp_backend_test.py +12 -0
  138. sage/numerical/backends/logging_backend.py +391 -0
  139. sage/numerical/backends/matrix_sdp_backend.cpython-314-darwin.so +0 -0
  140. sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
  141. sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
  142. sage/numerical/backends/ppl_backend.cpython-314-darwin.so +0 -0
  143. sage/numerical/backends/ppl_backend.pyx +1126 -0
  144. sage/numerical/backends/ppl_backend_test.py +13 -0
  145. sage/numerical/backends/scip_backend.cpython-314-darwin.so +0 -0
  146. sage/numerical/backends/scip_backend.pxd +22 -0
  147. sage/numerical/backends/scip_backend.pyx +1289 -0
  148. sage/numerical/backends/scip_backend_test.py +13 -0
  149. sage/numerical/interactive_simplex_method.py +5338 -0
  150. sage/numerical/knapsack.py +665 -0
  151. sage/numerical/linear_functions.cpython-314-darwin.so +0 -0
  152. sage/numerical/linear_functions.pxd +31 -0
  153. sage/numerical/linear_functions.pyx +1648 -0
  154. sage/numerical/linear_tensor.py +470 -0
  155. sage/numerical/linear_tensor_constraints.py +448 -0
  156. sage/numerical/linear_tensor_element.cpython-314-darwin.so +0 -0
  157. sage/numerical/linear_tensor_element.pxd +6 -0
  158. sage/numerical/linear_tensor_element.pyx +459 -0
  159. sage/numerical/mip.cpython-314-darwin.so +0 -0
  160. sage/numerical/mip.pxd +40 -0
  161. sage/numerical/mip.pyx +3667 -0
  162. sage/numerical/sdp.cpython-314-darwin.so +0 -0
  163. sage/numerical/sdp.pxd +39 -0
  164. sage/numerical/sdp.pyx +1433 -0
  165. sage/rings/all__sagemath_polyhedra.py +3 -0
  166. sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
  167. sage/rings/polynomial/omega.py +982 -0
  168. sage/schemes/all__sagemath_polyhedra.py +2 -0
  169. sage/schemes/toric/all.py +10 -0
  170. sage/schemes/toric/chow_group.py +1248 -0
  171. sage/schemes/toric/divisor.py +2082 -0
  172. sage/schemes/toric/divisor_class.cpython-314-darwin.so +0 -0
  173. sage/schemes/toric/divisor_class.pyx +322 -0
  174. sage/schemes/toric/fano_variety.py +1606 -0
  175. sage/schemes/toric/homset.py +650 -0
  176. sage/schemes/toric/ideal.py +451 -0
  177. sage/schemes/toric/library.py +1322 -0
  178. sage/schemes/toric/morphism.py +1958 -0
  179. sage/schemes/toric/points.py +1032 -0
  180. sage/schemes/toric/sheaf/all.py +1 -0
  181. sage/schemes/toric/sheaf/constructor.py +302 -0
  182. sage/schemes/toric/sheaf/klyachko.py +921 -0
  183. sage/schemes/toric/toric_subscheme.py +905 -0
  184. sage/schemes/toric/variety.py +3460 -0
  185. sage/schemes/toric/weierstrass.py +1078 -0
  186. sage/schemes/toric/weierstrass_covering.py +457 -0
  187. sage/schemes/toric/weierstrass_higher.py +288 -0
  188. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
  189. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
  190. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
  191. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
  192. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
  193. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
  194. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
  195. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
  196. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
  197. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
  198. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
  199. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
  200. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
  201. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
  202. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
  203. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
  204. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
  205. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_3d +37977 -0
@@ -0,0 +1,3895 @@
1
+ # sage_setup: distribution = sagemath-polyhedra
2
+ r"""
3
+ Hyperplane Arrangements
4
+
5
+ Before talking about hyperplane arrangements, let us start with
6
+ individual hyperplanes. This package uses certain linear expressions
7
+ to represent hyperplanes, that is, a linear expression `3x + 3y - 5z - 7`
8
+ stands for the hyperplane with the equation `3x + 3y - 5z = 7`. To create it
9
+ in Sage, you first have to create a :class:`HyperplaneArrangements`
10
+ object to define the variables `x`, `y`, `z`::
11
+
12
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
13
+ sage: h = 3*x + 2*y - 5*z - 7; h
14
+ Hyperplane 3*x + 2*y - 5*z - 7
15
+ sage: h.normal()
16
+ (3, 2, -5)
17
+ sage: h.constant_term()
18
+ -7
19
+
20
+ The individual hyperplanes behave like the linear expression with
21
+ regard to addition and scalar multiplication, which is why you can do
22
+ linear combinations of the coordinates::
23
+
24
+ sage: -2*h
25
+ Hyperplane -6*x - 4*y + 10*z + 14
26
+ sage: x, y, z
27
+ (Hyperplane x + 0*y + 0*z + 0,
28
+ Hyperplane 0*x + y + 0*z + 0,
29
+ Hyperplane 0*x + 0*y + z + 0)
30
+
31
+ See :mod:`sage.geometry.hyperplane_arrangement.hyperplane` for more
32
+ functionality of the individual hyperplanes.
33
+
34
+ Arrangements
35
+ ------------
36
+
37
+ There are several ways to create hyperplane arrangements:
38
+
39
+ Notation (i): by passing individual hyperplanes to the
40
+ :class:`HyperplaneArrangements` object::
41
+
42
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
43
+ sage: box = x | y | x-1 | y-1; box
44
+ Arrangement <y - 1 | y | x - 1 | x>
45
+ sage: box == H(x, y, x-1, y-1) # alternative syntax
46
+ True
47
+
48
+ Notation (ii): by passing anything that defines a hyperplane, for
49
+ example a coefficient vector and constant term::
50
+
51
+ sage: H = HyperplaneArrangements(QQ, ('x', 'y'))
52
+ sage: triangle = H([(1, 0), 0], [(0, 1), 0], [(1,1), -1]); triangle
53
+ Arrangement <y | x | x + y - 1>
54
+
55
+ sage: H.inject_variables()
56
+ Defining x, y
57
+ sage: triangle == x | y | x+y-1
58
+ True
59
+
60
+ The default base field is `\QQ`, the rational numbers. Finite fields are also
61
+ supported::
62
+
63
+ sage: H.<x,y,z> = HyperplaneArrangements(GF(5))
64
+ sage: a = H([(1,2,3), 4], [(5,6,7), 8]); a
65
+ Arrangement <y + 2*z + 3 | x + 2*y + 3*z + 4>
66
+
67
+ Number fields are also possible::
68
+
69
+ sage: # needs sage.rings.number_field
70
+ sage: x = polygen(QQ, 'x')
71
+ sage: NF.<a> = NumberField(x**4 - 5*x**2 + 5, embedding=1.90)
72
+ sage: H.<y,z> = HyperplaneArrangements(NF)
73
+ sage: A = H([[(-a**3 + 3*a, -a**2 + 4), 1], [(a**3 - 4*a, -1), 1],
74
+ ....: [(0, 2*a**2 - 6), 1], [(-a**3 + 4*a, -1), 1],
75
+ ....: [(a**3 - 3*a, -a**2 + 4), 1]])
76
+ sage: A
77
+ Arrangement of 5 hyperplanes of dimension 2 and rank 2
78
+ sage: A.base_ring()
79
+ Number Field in a with defining polynomial x^4 - 5*x^2 + 5
80
+ with a = 1.902113032590308?
81
+
82
+ Notation (iii): a list or tuple of hyperplanes::
83
+
84
+ sage: H.<x,y,z> = HyperplaneArrangements(GF(5))
85
+ sage: k = [x+i for i in range(4)]; k
86
+ [Hyperplane x + 0*y + 0*z + 0, Hyperplane x + 0*y + 0*z + 1,
87
+ Hyperplane x + 0*y + 0*z + 2, Hyperplane x + 0*y + 0*z + 3]
88
+ sage: H(k)
89
+ Arrangement <x | x + 1 | x + 2 | x + 3>
90
+
91
+ Notation (iv): using the library of arrangements::
92
+
93
+ sage: hyperplane_arrangements.braid(4) # needs sage.graphs
94
+ Arrangement of 6 hyperplanes of dimension 4 and rank 3
95
+ sage: hyperplane_arrangements.semiorder(3)
96
+ Arrangement of 6 hyperplanes of dimension 3 and rank 2
97
+ sage: hyperplane_arrangements.graphical(graphs.PetersenGraph()) # needs sage.graphs
98
+ Arrangement of 15 hyperplanes of dimension 10 and rank 9
99
+ sage: hyperplane_arrangements.Ish(5)
100
+ Arrangement of 20 hyperplanes of dimension 5 and rank 4
101
+
102
+ Notation (v): from the bounding hyperplanes of a polyhedron::
103
+
104
+ sage: a = polytopes.cube().hyperplane_arrangement(); a
105
+ Arrangement of 6 hyperplanes of dimension 3 and rank 3
106
+ sage: a.n_regions()
107
+ 27
108
+
109
+ New arrangements from old::
110
+
111
+ sage: # needs sage.graphs
112
+ sage: a = hyperplane_arrangements.braid(3)
113
+ sage: b = a.add_hyperplane([4, 1, 2, 3])
114
+ sage: b
115
+ Arrangement <t1 - t2 | t0 - t1 | t0 - t2 | t0 + 2*t1 + 3*t2 + 4>
116
+ sage: c = b.deletion([4, 1, 2, 3])
117
+ sage: a == c
118
+ True
119
+
120
+ sage: # needs sage.combinat sage.graphs
121
+ sage: a = hyperplane_arrangements.braid(3)
122
+ sage: b = a.union(hyperplane_arrangements.semiorder(3))
123
+ sage: b == a | hyperplane_arrangements.semiorder(3) # alternate syntax
124
+ True
125
+ sage: b == hyperplane_arrangements.Catalan(3)
126
+ True
127
+ sage: a
128
+ Arrangement <t1 - t2 | t0 - t1 | t0 - t2>
129
+
130
+ sage: a = hyperplane_arrangements.coordinate(4)
131
+ sage: h = a.hyperplanes()[0]
132
+ sage: b = a.restriction(h)
133
+ sage: b == hyperplane_arrangements.coordinate(3)
134
+ True
135
+
136
+ Properties of Arrangements
137
+ --------------------------
138
+
139
+ A hyperplane arrangement is *essential* if the normals to its
140
+ hyperplanes span the ambient space. Otherwise, it is *inessential*.
141
+ The essentialization is formed by intersecting the hyperplanes by this
142
+ normal space (actually, it is a bit more complicated over finite
143
+ fields)::
144
+
145
+ sage: # needs sage.graphs
146
+ sage: a = hyperplane_arrangements.braid(4); a
147
+ Arrangement of 6 hyperplanes of dimension 4 and rank 3
148
+ sage: a.is_essential()
149
+ False
150
+ sage: a.rank() < a.dimension() # double-check
151
+ True
152
+ sage: a.essentialization()
153
+ Arrangement of 6 hyperplanes of dimension 3 and rank 3
154
+
155
+ The connected components of the complement of the hyperplanes of an arrangement
156
+ in `\RR^n` are called the *regions* of the arrangement::
157
+
158
+ sage: a = hyperplane_arrangements.semiorder(3)
159
+ sage: b = a.essentialization(); b
160
+ Arrangement of 6 hyperplanes of dimension 2 and rank 2
161
+ sage: b.n_regions()
162
+ 19
163
+ sage: b.regions()
164
+ (A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 6 vertices,
165
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
166
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
167
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
168
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
169
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
170
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
171
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
172
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
173
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
174
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
175
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
176
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
177
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
178
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
179
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
180
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
181
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
182
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays)
183
+ sage: b.bounded_regions()
184
+ (A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 6 vertices,
185
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
186
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
187
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
188
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
189
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
190
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices)
191
+ sage: b.n_bounded_regions()
192
+ 7
193
+ sage: a.unbounded_regions()
194
+ (A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
195
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
196
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
197
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
198
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
199
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
200
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
201
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
202
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
203
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
204
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
205
+ A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line)
206
+
207
+ The distance between regions is defined as the number of hyperplanes
208
+ separating them. For example::
209
+
210
+ sage: # needs sage.combinat
211
+ sage: r1 = b.regions()[0]
212
+ sage: r2 = b.regions()[1]
213
+ sage: b.distance_between_regions(r1, r2)
214
+ 1
215
+ sage: [hyp for hyp in b if b.is_separating_hyperplane(r1, r2, hyp)]
216
+ [Hyperplane 2*t1 + t2 + 1]
217
+ sage: b.distance_enumerator(r1) # generating function for distances from r1
218
+ 6*x^3 + 6*x^2 + 6*x + 1
219
+
220
+ .. NOTE::
221
+
222
+ *bounded region* really mean *relatively bounded* here. A region is
223
+ relatively bounded if its intersection with space spanned by the normals
224
+ of the hyperplanes in the arrangement is bounded.
225
+
226
+ The intersection poset of a hyperplane arrangement is the collection
227
+ of all nonempty intersections of hyperplanes in the arrangement,
228
+ ordered by reverse inclusion. It includes the ambient space of the
229
+ arrangement (as the intersection over the empty set)::
230
+
231
+ sage: # needs sage.graphs
232
+ sage: a = hyperplane_arrangements.braid(3)
233
+ sage: p = a.intersection_poset()
234
+ sage: p.is_ranked()
235
+ True
236
+ sage: p.order_polytope()
237
+ A 5-dimensional polyhedron in ZZ^5 defined as the convex hull of 10 vertices
238
+
239
+ The characteristic polynomial is a basic invariant of a hyperplane
240
+ arrangement. It is defined as
241
+
242
+ .. MATH::
243
+
244
+ \chi(x) := \sum_{w\in P} \mu(w) x^{dim(w)}
245
+
246
+ where `P` is the
247
+ :meth:`~HyperplaneArrangementElement.intersection_poset` of the
248
+ arrangement and `\mu` is the Möbius function of `P`::
249
+
250
+ sage: # long time
251
+ sage: a = hyperplane_arrangements.semiorder(5)
252
+ sage: a.characteristic_polynomial() # about a second on Core i7
253
+ x^5 - 20*x^4 + 180*x^3 - 790*x^2 + 1380*x
254
+ sage: a.poincare_polynomial()
255
+ 1380*x^4 + 790*x^3 + 180*x^2 + 20*x + 1
256
+ sage: a.n_regions()
257
+ 2371
258
+ sage: charpoly = a.characteristic_polynomial()
259
+ sage: charpoly(-1)
260
+ -2371
261
+ sage: a.n_bounded_regions()
262
+ 751
263
+ sage: charpoly(1)
264
+ 751
265
+
266
+ For finer invariants derived from the intersection poset, see
267
+ :meth:`~HyperplaneArrangementElement.whitney_number` and
268
+ :meth:`~HyperplaneArrangementElement.doubly_indexed_whitney_number`.
269
+
270
+ Miscellaneous methods (see documentation for an explanation)::
271
+
272
+ sage: a = hyperplane_arrangements.semiorder(3)
273
+ sage: a.has_good_reduction(5) # needs sage.graphs sage.rings.finite_rings
274
+ True
275
+ sage: b = a.change_ring(GF(5))
276
+ sage: pa = a.intersection_poset() # needs sage.graphs
277
+ sage: pb = b.intersection_poset() # needs sage.graphs sage.rings.finite_rings
278
+ sage: pa.is_isomorphic(pb) # needs sage.graphs sage.rings.finite_rings
279
+ True
280
+ sage: a.face_vector() # needs sage.graphs
281
+ (0, 12, 30, 19)
282
+ sage: a.face_vector() # needs sage.graphs
283
+ (0, 12, 30, 19)
284
+ sage: a.is_central()
285
+ False
286
+ sage: a.is_linear()
287
+ False
288
+ sage: a.sign_vector((1,1,1))
289
+ (-1, 1, -1, 1, -1, 1)
290
+ sage: a.varchenko_matrix()[:6, :6]
291
+ [ 1 h2 h2*h4 h2*h3 h2*h3*h4 h2*h3*h4*h5]
292
+ [ h2 1 h4 h3 h3*h4 h3*h4*h5]
293
+ [ h2*h4 h4 1 h3*h4 h3 h3*h5]
294
+ [ h2*h3 h3 h3*h4 1 h4 h4*h5]
295
+ [ h2*h3*h4 h3*h4 h3 h4 1 h5]
296
+ [h2*h3*h4*h5 h3*h4*h5 h3*h5 h4*h5 h5 1]
297
+
298
+ There are extensive methods for visualizing hyperplane arrangements in
299
+ low dimensions. See :meth:`~HyperplaneArrangementElement.plot` for
300
+ details.
301
+
302
+ TESTS::
303
+
304
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
305
+ sage: h = H([(1, 106), 106266], [(83, 101), 157866], [(111, 110), 186150], [(453, 221), 532686],
306
+ ....: [(407, 237), 516882], [(55, 32), 75620], [(221, 114), 289346], [(452, 115), 474217],
307
+ ....: [(406, 131), 453521], [(28, 9), 32446], [(287, 19), 271774], [(241, 35), 244022],
308
+ ....: [(231, 1), 210984], [(185, 17), 181508], [(23, -8), 16609])
309
+ sage: h.n_regions()
310
+ 85
311
+
312
+ sage: H()
313
+ Empty hyperplane arrangement of dimension 2
314
+
315
+ sage: Zero = HyperplaneArrangements(QQ)
316
+ sage: Zero
317
+ Hyperplane arrangements in 0-dimensional linear space over Rational Field with coordinate
318
+ sage: Zero()
319
+ Empty hyperplane arrangement of dimension 0
320
+ sage: Zero.an_element() # needs sage.rings.real_interval_field
321
+ Empty hyperplane arrangement of dimension 0
322
+
323
+ AUTHORS:
324
+
325
+ - David Perkinson (2013-06): initial version
326
+
327
+ - Qiaoyu Yang (2013-07)
328
+
329
+ - Kuai Yu (2013-07)
330
+
331
+ - Volker Braun (2013-10): Better Sage integration, major code refactoring.
332
+
333
+ This module implements hyperplane arrangements defined over the
334
+ rationals or over finite fields. The original motivation was to make
335
+ a companion to Richard Stanley's notes [Sta2007]_ on hyperplane
336
+ arrangements.
337
+ """
338
+
339
+ # *****************************************************************************
340
+ # Copyright (C) 2013 David Perkinson <davidp@reed.edu>
341
+ # Volker Braun <vbraun.name@gmail.com>
342
+ #
343
+ # This program is free software: you can redistribute it and/or modify
344
+ # it under the terms of the GNU General Public License as published by
345
+ # the Free Software Foundation, either version 2 of the License, or
346
+ # (at your option) any later version.
347
+ # http://www.gnu.org/licenses/
348
+ # *****************************************************************************
349
+
350
+ # Possible extensions for hyperplane_arrangement.py:
351
+ # - the big face lattice
352
+ # - create ties with the Sage matroid methods
353
+ # - hyperplane arrangements over other fields
354
+
355
+ from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane
356
+ from sage.matrix.constructor import matrix, vector
357
+ from sage.misc.cachefunc import cached_method
358
+ from sage.modules.free_module import VectorSpace
359
+ from sage.rings.integer_ring import ZZ
360
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
361
+ from sage.rings.rational_field import QQ
362
+ from sage.structure.parent import Parent
363
+ from sage.structure.element import Element
364
+ from sage.structure.richcmp import richcmp
365
+ from sage.structure.unique_representation import UniqueRepresentation
366
+
367
+
368
+ class HyperplaneArrangementElement(Element):
369
+ """
370
+ A hyperplane arrangement.
371
+
372
+ .. WARNING::
373
+
374
+ You should never create
375
+ :class:`HyperplaneArrangementElement` instances directly,
376
+ always use the parent.
377
+ """
378
+ def __init__(self, parent, hyperplanes, check=True, backend=None):
379
+ """
380
+ Construct a hyperplane arrangement.
381
+
382
+ INPUT:
383
+
384
+ - ``parent`` -- the parent :class:`HyperplaneArrangements`
385
+
386
+ - ``hyperplanes`` -- tuple of hyperplanes
387
+
388
+ - ``check`` -- boolean (default: ``True``); whether to check input
389
+
390
+ - ``backend`` -- string (optional); the backend to
391
+ use for the related polyhedral objects
392
+
393
+ EXAMPLES::
394
+
395
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
396
+ sage: elt = H(x, y); elt
397
+ Arrangement <y | x>
398
+ sage: TestSuite(elt).run()
399
+
400
+ It is possible to specify a backend for polyhedral computations::
401
+
402
+ sage: # needs sage.rings.number_field
403
+ sage: R.<sqrt5> = QuadraticField(5)
404
+ sage: H = HyperplaneArrangements(R, names='xyz')
405
+ sage: x, y, z = H.gens()
406
+ sage: A = H(sqrt5*x + 2*y + 3*z, backend='normaliz')
407
+ sage: A.backend()
408
+ 'normaliz'
409
+ sage: A.regions()[0].backend() # optional - pynormaliz
410
+ 'normaliz'
411
+ """
412
+ super().__init__(parent)
413
+ self._hyperplanes = hyperplanes
414
+ self._backend = backend
415
+ if check:
416
+ if not isinstance(hyperplanes, tuple):
417
+ raise ValueError("the hyperplanes must be given as a tuple")
418
+ if not all(isinstance(h, Hyperplane) for h in hyperplanes):
419
+ raise ValueError("not all elements are hyperplanes")
420
+ if not all(h.parent() is self.parent().ambient_space() for h in hyperplanes):
421
+ raise ValueError("not all hyperplanes are in the ambient space")
422
+
423
+ def _first_ngens(self, n):
424
+ """
425
+ Workaround to support the construction with names.
426
+
427
+ INPUT/OUTPUT: see :meth:`HyperplaneArrangements._first_ngens`
428
+
429
+ EXAMPLES::
430
+
431
+ sage: a.<x,y,z> = hyperplane_arrangements.braid(3) # indirect doctest # needs sage.graphs
432
+ sage: (x, y) == a._first_ngens(2) # needs sage.graphs
433
+ True
434
+ """
435
+ return self.parent()._first_ngens(n)
436
+
437
+ def __getitem__(self, i):
438
+ """
439
+ Return the `i`-th hyperplane.
440
+
441
+ INPUT:
442
+
443
+ - ``i`` -- integer
444
+
445
+ OUTPUT: the `i`-th hyperplane
446
+
447
+ EXAMPLES::
448
+
449
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
450
+ sage: h = x|y; h
451
+ Arrangement <y | x>
452
+ sage: h[0]
453
+ Hyperplane 0*x + y + 0
454
+ sage: h[1]
455
+ Hyperplane x + 0*y + 0
456
+ """
457
+ return self._hyperplanes[i]
458
+
459
+ def __hash__(self):
460
+ r"""
461
+ TESTS::
462
+
463
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
464
+ sage: h = x|y; h
465
+ Arrangement <y | x>
466
+ sage: len_dict = {h: len(h)}
467
+ """
468
+ return hash(self.hyperplanes())
469
+
470
+ def n_hyperplanes(self):
471
+ r"""
472
+ Return the number of hyperplanes in the arrangement.
473
+
474
+ OUTPUT: integer
475
+
476
+ EXAMPLES::
477
+
478
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
479
+ sage: A = H([1,1,0], [2,3,-1], [4,5,3])
480
+ sage: A.n_hyperplanes()
481
+ 3
482
+ sage: len(A) # equivalent
483
+ 3
484
+ """
485
+ return len(self._hyperplanes)
486
+
487
+ __len__ = n_hyperplanes
488
+
489
+ def hyperplanes(self):
490
+ r"""
491
+ Return the hyperplanes in the arrangement as a tuple.
492
+
493
+ OUTPUT: a tuple
494
+
495
+ EXAMPLES::
496
+
497
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
498
+ sage: A = H([1,1,0], [2,3,-1], [4,5,3])
499
+ sage: A.hyperplanes()
500
+ (Hyperplane x + 0*y + 1, Hyperplane 3*x - y + 2, Hyperplane 5*x + 3*y + 4)
501
+
502
+ Note that the hyperplanes can be indexed as if they were a list::
503
+
504
+ sage: A[0]
505
+ Hyperplane x + 0*y + 1
506
+ """
507
+ return self._hyperplanes
508
+
509
+ def _repr_(self):
510
+ r"""
511
+ String representation for a hyperplane arrangement.
512
+
513
+ OUTPUT: string
514
+
515
+ EXAMPLES::
516
+
517
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
518
+ sage: H(x, y, x-1, y-1)
519
+ Arrangement <y - 1 | y | x - 1 | x>
520
+ sage: x | y | x - 1 | y - 1 | x + y | x - y
521
+ Arrangement of 6 hyperplanes of dimension 2 and rank 2
522
+ sage: H()
523
+ Empty hyperplane arrangement of dimension 2
524
+ """
525
+ if len(self) == 0:
526
+ return 'Empty hyperplane arrangement of dimension {0}'.format(self.dimension())
527
+ elif len(self) < 5:
528
+ hyperplanes = ' | '.join(h._repr_linear(include_zero=False) for h in self._hyperplanes)
529
+ return 'Arrangement <{0}>'.format(hyperplanes)
530
+ return 'Arrangement of {0} hyperplanes of dimension {1} and rank {2}'.format(
531
+ len(self), self.dimension(), self.rank())
532
+
533
+ def dimension(self):
534
+ """
535
+ Return the ambient space dimension of the arrangement.
536
+
537
+ OUTPUT: integer
538
+
539
+ EXAMPLES::
540
+
541
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
542
+ sage: (x | x-1 | x+1).dimension()
543
+ 2
544
+ sage: H(x).dimension()
545
+ 2
546
+ """
547
+ return self.parent().ngens()
548
+
549
+ def rank(self):
550
+ """
551
+ Return the rank.
552
+
553
+ OUTPUT:
554
+
555
+ The dimension of the span of the normals to the
556
+ hyperplanes in the arrangement.
557
+
558
+ EXAMPLES::
559
+
560
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
561
+ sage: A = H([[0, 1, 2, 3],[-3, 4, 5, 6]])
562
+ sage: A.dimension()
563
+ 3
564
+ sage: A.rank()
565
+ 2
566
+
567
+ sage: # needs sage.graphs
568
+ sage: B = hyperplane_arrangements.braid(3)
569
+ sage: B.hyperplanes()
570
+ (Hyperplane 0*t0 + t1 - t2 + 0,
571
+ Hyperplane t0 - t1 + 0*t2 + 0,
572
+ Hyperplane t0 + 0*t1 - t2 + 0)
573
+ sage: B.dimension()
574
+ 3
575
+ sage: B.rank()
576
+ 2
577
+
578
+ sage: # needs cddexec
579
+ sage: p = polytopes.simplex(5, project=True)
580
+ sage: H = p.hyperplane_arrangement()
581
+ sage: H.rank()
582
+ 5
583
+ """
584
+ R = self.parent().base_ring()
585
+ normals = [h.normal() for h in self]
586
+ return matrix(R, normals).rank()
587
+
588
+ def backend(self):
589
+ """
590
+ Return the backend used for polyhedral objects.
591
+
592
+ OUTPUT: string giving the backend or ``None`` if none is specified
593
+
594
+ EXAMPLES:
595
+
596
+ By default, no backend is specified::
597
+
598
+ sage: H = HyperplaneArrangements(QQ)
599
+ sage: A = H()
600
+ sage: A.backend()
601
+
602
+ Otherwise, one may specify a polyhedral backend::
603
+
604
+ sage: A = H(backend='ppl')
605
+ sage: A.backend()
606
+ 'ppl'
607
+ sage: A = H(backend='normaliz')
608
+ sage: A.backend()
609
+ 'normaliz'
610
+ """
611
+ return self._backend
612
+
613
+ def _richcmp_(self, other, op):
614
+ """
615
+ Compare two hyperplane arrangements.
616
+
617
+ EXAMPLES::
618
+
619
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
620
+ sage: H(x) == H(y)
621
+ False
622
+
623
+ TESTS::
624
+
625
+ sage: H(x) == 0
626
+ False
627
+ """
628
+ return richcmp(self._hyperplanes, other._hyperplanes, op)
629
+
630
+ def union(self, other):
631
+ r"""
632
+ The union of ``self`` with ``other``.
633
+
634
+ INPUT:
635
+
636
+ - ``other`` -- a hyperplane arrangement or something that can
637
+ be converted into a hyperplane arrangement
638
+
639
+ OUTPUT: a new hyperplane arrangement
640
+
641
+ EXAMPLES::
642
+
643
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
644
+ sage: A = H([1,2,3], [0,1,1], [0,1,-1], [1,-1,0], [1,1,0])
645
+ sage: B = H([1,1,1], [1,-1,1], [1,0,-1])
646
+ sage: C = A.union(B); C
647
+ Arrangement of 8 hyperplanes of dimension 2 and rank 2
648
+ sage: C == A | B # syntactic sugar
649
+ True
650
+
651
+ A single hyperplane is coerced into a hyperplane arrangement
652
+ if necessary::
653
+
654
+ sage: A.union(x+y-1)
655
+ Arrangement of 6 hyperplanes of dimension 2 and rank 2
656
+ sage: A.add_hyperplane(x+y-1) # alias
657
+ Arrangement of 6 hyperplanes of dimension 2 and rank 2
658
+
659
+ sage: P.<x,y> = HyperplaneArrangements(RR)
660
+ sage: C = P(2*x + 4*y + 5)
661
+ sage: C.union(A)
662
+ Arrangement of 6 hyperplanes of dimension 2 and rank 2
663
+ """
664
+ P = self.parent()
665
+ other_h = P(other)
666
+ hyperplanes = self._hyperplanes + other_h._hyperplanes
667
+ result = P(*hyperplanes, backend=self._backend)
668
+ return result
669
+
670
+ add_hyperplane = union
671
+
672
+ __or__ = union
673
+
674
+ def plot(self, **kwds):
675
+ """
676
+ Plot the hyperplane arrangement.
677
+
678
+ OUTPUT: a graphics object
679
+
680
+ EXAMPLES::
681
+
682
+ sage: L.<x, y> = HyperplaneArrangements(QQ)
683
+ sage: L(x, y, x + y - 2).plot() # needs sage.plot sage.symbolic
684
+ Graphics object consisting of 3 graphics primitives
685
+ """
686
+ from sage.geometry.hyperplane_arrangement.plot import plot
687
+ return plot(self, **kwds)
688
+
689
+ def cone(self, variable='t'):
690
+ r"""
691
+ Return the cone over the hyperplane arrangement.
692
+
693
+ INPUT:
694
+
695
+ - ``variable`` -- string; the name of the additional variable
696
+
697
+ OUTPUT:
698
+
699
+ A new hyperplane arrangement `L`.
700
+ Its equations consist of `[0, -d, a_1, \ldots, a_n]` for each
701
+ `[d, a_1, \ldots, a_n]` in the original arrangement and the
702
+ equation `[0, 1, 0, \ldots, 0]` (maybe not in this order).
703
+
704
+ .. WARNING::
705
+
706
+ While there is an almost-one-to-one correspondence between the
707
+ hyperplanes of ``self`` and those of ``self.cone()``, there is
708
+ no guarantee that the order in which they appear in
709
+ ``self.hyperplanes()`` will match the order in which their
710
+ counterparts in ``self.cone()`` will appear in
711
+ ``self.cone().hyperplanes()``! This warning does not apply
712
+ to ordered hyperplane arrangements.
713
+
714
+ EXAMPLES::
715
+
716
+ sage: # needs sage.combinat
717
+ sage: a.<x,y,z> = hyperplane_arrangements.semiorder(3)
718
+ sage: b = a.cone()
719
+ sage: a.characteristic_polynomial().factor()
720
+ x * (x^2 - 6*x + 12)
721
+ sage: b.characteristic_polynomial().factor()
722
+ (x - 1) * x * (x^2 - 6*x + 12)
723
+ sage: a.hyperplanes()
724
+ (Hyperplane 0*x + y - z - 1,
725
+ Hyperplane 0*x + y - z + 1,
726
+ Hyperplane x - y + 0*z - 1,
727
+ Hyperplane x - y + 0*z + 1,
728
+ Hyperplane x + 0*y - z - 1,
729
+ Hyperplane x + 0*y - z + 1)
730
+ sage: b.hyperplanes()
731
+ (Hyperplane -t + 0*x + y - z + 0,
732
+ Hyperplane -t + x - y + 0*z + 0,
733
+ Hyperplane -t + x + 0*y - z + 0,
734
+ Hyperplane t + 0*x + 0*y + 0*z + 0,
735
+ Hyperplane t + 0*x + y - z + 0,
736
+ Hyperplane t + x - y + 0*z + 0,
737
+ Hyperplane t + x + 0*y - z + 0)
738
+ """
739
+ hyperplanes = []
740
+ for h in self.hyperplanes():
741
+ new_h = [0] + [h.b()] + list(h.A())
742
+ hyperplanes.append(new_h)
743
+ hyperplanes.append([0, 1] + [0] * self.dimension())
744
+ P = self.parent()
745
+ names = (variable,) + P._names
746
+ H = type(P).__base__(P.base_ring(), names=names)
747
+ return H(*hyperplanes, backend=self._backend)
748
+
749
+ @cached_method
750
+ def intersection_poset(self, element_label='int'):
751
+ r"""
752
+ Return the intersection poset of the hyperplane arrangement.
753
+
754
+ INPUT:
755
+
756
+ - ``element_label`` -- (default: ``'int'``) specify how an
757
+ intersection should be represented; must be one of the following:
758
+
759
+ * ``'subspace'`` -- as a subspace
760
+ * ``'subset'`` -- as a subset of the defining hyperplanes
761
+ * ``'int'`` -- as an integer
762
+
763
+ OUTPUT:
764
+
765
+ The poset of non-empty intersections of hyperplanes, with intersections
766
+ represented by integers, subsets of integers or subspaces (see the
767
+ examples for more details).
768
+
769
+ EXAMPLES:
770
+
771
+ By default, the elements of the poset are the integers from `0` through
772
+ the cardinality of the poset *minus one*. The element labelled `0`
773
+ always corresponds to the ambient vector space, and the hyperplanes
774
+ themselves are labelled `1, 2, \ldots, n`, where `n` is the number
775
+ of hyperplanes of the arrangement. ::
776
+
777
+ sage: A = hyperplane_arrangements.coordinate(2)
778
+ sage: L = A.intersection_poset(); L # needs sage.combinat
779
+ Finite poset containing 4 elements
780
+ sage: sorted(L) # needs sage.combinat
781
+ [0, 1, 2, 3]
782
+ sage: L.level_sets() # needs sage.combinat
783
+ [[0], [1, 2], [3]]
784
+
785
+ ::
786
+
787
+ sage: # needs sage.combinat
788
+ sage: A = hyperplane_arrangements.semiorder(3)
789
+ sage: L = A.intersection_poset(); L
790
+ Finite poset containing 19 elements
791
+ sage: sorted(L)
792
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
793
+ sage: [sorted(level_set) for level_set in L.level_sets()]
794
+ [[0], [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]]
795
+
796
+ By passing the argument ``element_label="subset"``, each element of the
797
+ intersection poset is labelled by the set of indices of the hyperplanes
798
+ whose intersection is said element. The index of a hyperplane is its
799
+ index in ``self.hyperplanes()``. ::
800
+
801
+ sage: A = hyperplane_arrangements.semiorder(3)
802
+ sage: L = A.intersection_poset(element_label='subset') # needs sage.combinat
803
+ sage: [sorted(level, key=sorted) for level in L.level_sets()] # needs sage.combinat
804
+ [[{}],
805
+ [{0}, {1}, {2}, {3}, {4}, {5}],
806
+ [{0, 2}, {0, 3}, {0, 4}, {0, 5}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 4}, {2, 5}, {3, 4}, {3, 5}]]
807
+
808
+ ::
809
+
810
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
811
+ sage: A = H((y, y-1, y+1, x-y, x+y))
812
+ sage: L = A.intersection_poset(element_label='subset') # needs sage.combinat
813
+ sage: sorted(L, key=sorted) # needs sage.combinat
814
+ [{}, {0}, {0, 3}, {0, 4}, {1}, {1, 3, 4}, {2}, {2, 3}, {2, 4}, {3}, {4}]
815
+
816
+ One can instead use affine subspaces as elements,
817
+ which is what is used to compute the poset in the first place::
818
+
819
+ sage: A = hyperplane_arrangements.coordinate(2)
820
+ sage: L = A.intersection_poset(element_label='subspace'); L # needs sage.combinat
821
+ Finite poset containing 4 elements
822
+ sage: sorted(L, key=lambda S: (S.dimension(), # needs sage.combinat
823
+ ....: S.linear_part().basis_matrix()))
824
+ [Affine space p + W where:
825
+ p = (0, 0)
826
+ W = Vector space of degree 2 and dimension 0 over Rational Field
827
+ Basis matrix: [],
828
+ Affine space p + W where:
829
+ p = (0, 0)
830
+ W = Vector space of degree 2 and dimension 1 over Rational Field
831
+ Basis matrix: [0 1],
832
+ Affine space p + W where:
833
+ p = (0, 0)
834
+ W = Vector space of degree 2 and dimension 1 over Rational Field
835
+ Basis matrix: [1 0],
836
+ Affine space p + W where:
837
+ p = (0, 0)
838
+ W = Vector space of dimension 2 over Rational Field]
839
+ """
840
+ if element_label == "int":
841
+ def update(mapping, val, I0):
842
+ mapping[val] = len(mapping)
843
+ elif element_label == "subset":
844
+ from sage.sets.set import Set
845
+
846
+ def update(mapping, val, I0):
847
+ mapping[val] = Set(val)
848
+ elif element_label == "subspace":
849
+ def update(mapping, val, I0):
850
+ mapping[val] = I0
851
+ else:
852
+ raise ValueError("invalid element label type")
853
+
854
+ K = self.base_ring()
855
+ from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace
856
+
857
+ whole_space = AffineSubspace(0, VectorSpace(K, self.dimension()))
858
+ hyperplanes = [H._affine_subspace() for H in self.hyperplanes()]
859
+
860
+ mapping = {}
861
+ update(mapping, frozenset(), whole_space)
862
+ for i, H in enumerate(hyperplanes):
863
+ update(mapping, frozenset([i]), H)
864
+
865
+ hasse = {frozenset(): [frozenset([i]) for i in range(len(hyperplanes))]}
866
+ cur_level = [(frozenset([i]), H) for i, H in enumerate(hyperplanes)]
867
+
868
+ while cur_level:
869
+ new_level = {}
870
+ for label, T in cur_level:
871
+ edges = []
872
+ for i, H in enumerate(hyperplanes):
873
+ I0 = H.intersection(T)
874
+ if I0 is not None and I0 != T:
875
+ try:
876
+ target = new_level[I0]
877
+ except KeyError:
878
+ target = set(label)
879
+ new_level[I0] = target
880
+ target.add(i)
881
+ edges.append(target)
882
+ hasse[label] = edges
883
+ for label, T in cur_level:
884
+ # Freeze them in place now
885
+ hasse[label] = [frozenset(X) for X in hasse[label]]
886
+ cur_level = [(frozenset(X), T) for T, X in new_level.items()]
887
+ for label, T in cur_level:
888
+ update(mapping, label, T)
889
+
890
+ from sage.combinat.posets.posets import Poset
891
+ return Poset({mapping[i]: [mapping[j] for j in val] for i, val in hasse.items()})
892
+
893
+ def _slow_characteristic_polynomial(self):
894
+ """
895
+ Return the characteristic polynomial of the hyperplane arrangement.
896
+
897
+ This is the slow computation directly from the definition. For
898
+ educational use only.
899
+
900
+ EXAMPLES::
901
+
902
+ sage: a = hyperplane_arrangements.coordinate(2)
903
+ sage: a._slow_characteristic_polynomial() # needs sage.combinat
904
+ x^2 - 2*x + 1
905
+ """
906
+ from sage.rings.polynomial.polynomial_ring import polygen
907
+ x = polygen(QQ, 'x')
908
+ P = self.intersection_poset()
909
+ n = self.dimension()
910
+ return sum([P.moebius_function(0, p) * x**(n - P.rank(p)) for p in P])
911
+
912
+ @cached_method
913
+ def characteristic_polynomial(self):
914
+ r"""
915
+ Return the characteristic polynomial of the hyperplane arrangement.
916
+
917
+ OUTPUT: the characteristic polynomial in `\QQ[x]`
918
+
919
+ EXAMPLES::
920
+
921
+ sage: a = hyperplane_arrangements.coordinate(2)
922
+ sage: a.characteristic_polynomial()
923
+ x^2 - 2*x + 1
924
+
925
+ TESTS::
926
+
927
+ sage: H.<s,t,u,v> = HyperplaneArrangements(QQ)
928
+ sage: m = matrix([(0, -1, 0, 1, -1), (0, -1, 1, -1, 0), (0, -1, 1, 0, -1),
929
+ ....: (0, 1, 0, 0, 0), (0, 1, 0, 1, -1), (0, 1, 1, -1, 0), (0, 1, 1, 0, -1)])
930
+ sage: R.<x> = QQ[]
931
+ sage: expected_charpoly = (x - 1) * x * (x^2 - 6*x + 12)
932
+ sage: for s in SymmetricGroup(4): # long time (about a second on a Core i7)
933
+ ....: m_perm = [m.column(i) for i in [0, s(1), s(2), s(3), s(4)]]
934
+ ....: m_perm = matrix(m_perm).transpose()
935
+ ....: charpoly = H(m_perm.rows()).characteristic_polynomial()
936
+ ....: assert charpoly == expected_charpoly
937
+
938
+ Check the corner case of the empty arrangement::
939
+
940
+ sage: E = H()
941
+ sage: E.characteristic_polynomial()
942
+ 1
943
+ """
944
+ from sage.rings.polynomial.polynomial_ring import polygen
945
+ x = polygen(QQ, 'x')
946
+ if self.rank() == 1:
947
+ return x**(self.dimension() - 1) * (x - len(self))
948
+ if self.rank() == 0:
949
+ return x ** 0
950
+
951
+ H = self[0]
952
+ R = self.restriction(H)
953
+ charpoly_R = R.characteristic_polynomial()
954
+ D = self.deletion(H)
955
+ charpoly_D = D.characteristic_polynomial()
956
+ return charpoly_D - charpoly_R
957
+
958
+ @cached_method
959
+ def poincare_polynomial(self):
960
+ r"""
961
+ Return the Poincaré polynomial of the hyperplane arrangement.
962
+
963
+ OUTPUT: the Poincaré polynomial in `\QQ[x]`
964
+
965
+ EXAMPLES::
966
+
967
+ sage: a = hyperplane_arrangements.coordinate(2)
968
+ sage: a.poincare_polynomial()
969
+ x^2 + 2*x + 1
970
+ """
971
+ charpoly = self.characteristic_polynomial()
972
+ R = charpoly.parent()
973
+ x = R.gen(0)
974
+ poincare = (-x)**self.dimension() * charpoly(-QQ(1)/x)
975
+ return R(poincare)
976
+
977
+ @cached_method
978
+ def cocharacteristic_polynomial(self):
979
+ r"""
980
+ Return the cocharacteristic polynomial of ``self``.
981
+
982
+ The cocharacteristic polynomial of a hyperplane arrangement `A`
983
+ is defined by
984
+
985
+ .. MATH::
986
+
987
+ \Psi_A(z) := \sum_{X \in L} |\mu(B,X)| z^{\dim X},
988
+
989
+ where `L` is the intersection poset of `A`, `B` is the minimal
990
+ element of `L` (here, the `0` dimensional subspace), and
991
+ `\mu` is the Möbius function of `L`.
992
+
993
+ OUTPUT: the cocharacteristic polynomial in `\ZZ[z]`
994
+
995
+ EXAMPLES::
996
+
997
+ sage: A = hyperplane_arrangements.coordinate(2)
998
+ sage: A.cocharacteristic_polynomial() # needs sage.graphs
999
+ z^2 + 2*z + 1
1000
+ sage: B = hyperplane_arrangements.braid(3) # needs sage.groups
1001
+ sage: B.cocharacteristic_polynomial() # needs sage.graphs sage.groups
1002
+ 2*z^3 + 3*z^2 + z
1003
+
1004
+ TESTS::
1005
+
1006
+ sage: I = hyperplane_arrangements.Ish(2)
1007
+ sage: I.is_central()
1008
+ False
1009
+ sage: I.cocharacteristic_polynomial()
1010
+ Traceback (most recent call last):
1011
+ ...
1012
+ ValueError: only defined for central hyperplane arrangements
1013
+ """
1014
+ if not self.is_central():
1015
+ raise ValueError("only defined for central hyperplane arrangements")
1016
+
1017
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1018
+ R = PolynomialRing(ZZ, 'z')
1019
+ z = R.gen()
1020
+ L = self.intersection_poset(element_label='subspace').dual()
1021
+ B = L.minimal_elements()[0]
1022
+ return R.sum(abs(L.moebius_function(B, X)) * z**X.dimension()
1023
+ for X in L)
1024
+
1025
+ @cached_method
1026
+ def primitive_eulerian_polynomial(self):
1027
+ r"""
1028
+ Return the primitive Eulerian polynomial of ``self``.
1029
+
1030
+ The primitive Eulerian polynomial of a hyperplane arrangement `A`
1031
+ is defined [BHS2023]_ by
1032
+
1033
+ .. MATH::
1034
+
1035
+ P_A(z) := \sum_{X \in L} |\mu(B,X)| (z - 1)^{\mathrm{codim} X},
1036
+
1037
+ where `L` is the intersection poset of `A`, `B` is the minimal
1038
+ element of `L` (here, the `0` dimensional subspace), and
1039
+ `\mu` is the Möbius function of `L`.
1040
+
1041
+ OUTPUT: the primitive Eulerian polynomial in `\ZZ[z]`
1042
+
1043
+ EXAMPLES::
1044
+
1045
+ sage: A = hyperplane_arrangements.coordinate(2)
1046
+ sage: A.primitive_eulerian_polynomial() # needs sage.graphs
1047
+ z^2
1048
+ sage: B = hyperplane_arrangements.braid(3) # needs sage.groups
1049
+ sage: B.primitive_eulerian_polynomial() # needs sage.graphs sage.groups
1050
+ z^2 + z
1051
+
1052
+ sage: H = hyperplane_arrangements.Shi(['B',2]).cone()
1053
+ sage: H.is_simplicial()
1054
+ False
1055
+ sage: H.primitive_eulerian_polynomial() # needs sage.graphs
1056
+ z^3 + 11*z^2 + 4*z
1057
+
1058
+ sage: H = hyperplane_arrangements.graphical(graphs.CycleGraph(4)) # needs sage.graphs
1059
+ sage: H.primitive_eulerian_polynomial() # needs sage.graphs
1060
+ z^3 + 3*z^2 - z
1061
+
1062
+ We verify Example 2.4 in [BHS2023]_ for `k = 2,3,4,5`::
1063
+
1064
+ sage: R.<x,y> = HyperplaneArrangements(QQ)
1065
+ sage: for k in range(2,6): # needs sage.graphs
1066
+ ....: H = R([x+j*y for j in range(k)])
1067
+ ....: H.primitive_eulerian_polynomial()
1068
+ z^2
1069
+ z^2 + z
1070
+ z^2 + 2*z
1071
+ z^2 + 3*z
1072
+
1073
+ We verify Equation (4) in [BHS2023]_ on some examples::
1074
+
1075
+ sage: # needs sage.graphs
1076
+ sage: R.<x> = ZZ[]
1077
+ sage: Arr = [hyperplane_arrangements.braid(n) for n in range(2,6)]
1078
+ sage: all(R(A.cocharacteristic_polynomial()(1/(x-1)) * (x-1)^A.dimension())
1079
+ ....: == R(A.primitive_eulerian_polynomial()) for A in Arr)
1080
+ True
1081
+
1082
+ We compute types `H_3` and `F_4` in Table 1 of [BHS2023]_::
1083
+
1084
+ sage: # needs sage.libs.gap
1085
+ sage: W = CoxeterGroup(['H',3], implementation='matrix')
1086
+ sage: A = HyperplaneArrangements(W.base_ring(), tuple(f'x{s}' for s in range(W.rank())))
1087
+ sage: H = A([[0] + list(r) for r in W.positive_roots()])
1088
+ sage: H.is_simplicial() # needs sage.graphs
1089
+ True
1090
+ sage: H.primitive_eulerian_polynomial()
1091
+ z^3 + 28*z^2 + 16*z
1092
+
1093
+ sage: # needs sage.libs.gap
1094
+ sage: W = CoxeterGroup(['F',4], implementation='permutation')
1095
+ sage: A = HyperplaneArrangements(QQ, tuple(f'x{s}' for s in range(W.rank())))
1096
+ sage: H = A([[0] + list(r) for r in W.positive_roots()])
1097
+ sage: H.primitive_eulerian_polynomial() # long time # needs sage.graphs
1098
+ z^4 + 116*z^3 + 220*z^2 + 48*z
1099
+
1100
+ We verify Proposition 2.5 in [BHS2023]_ on the braid arrangement
1101
+ `B_k` for `k = 2,3,4,5`::
1102
+
1103
+ sage: B = [hyperplane_arrangements.braid(k) for k in range(2,6)] # needs sage.graphs
1104
+ sage: all(H.is_simplicial() for H in B) # needs sage.graphs
1105
+ True
1106
+ sage: all(c > 0 for H in B # needs sage.graphs
1107
+ ....: for c in H.primitive_eulerian_polynomial().coefficients())
1108
+ True
1109
+
1110
+ We verify Example 9.4 in [BHS2023]_ showing a hyperplane arrangement
1111
+ whose primitive Eulerian polynomial does not have real roots (in
1112
+ general, the graphical arrangement of a cycle graph corresponds
1113
+ to the arrangements in Example 9.4)::
1114
+
1115
+ sage: # needs sage.graphs
1116
+ sage: H = hyperplane_arrangements.graphical(graphs.CycleGraph(5))
1117
+ sage: pep = H.primitive_eulerian_polynomial(); pep
1118
+ z^4 + 6*z^3 - 4*z^2 + z
1119
+ sage: pep.roots(QQbar)
1120
+ [(-6.626418492719221?, 1),
1121
+ (0, 1),
1122
+ (0.3132092463596102? - 0.2298065541510677?*I, 1),
1123
+ (0.3132092463596102? + 0.2298065541510677?*I, 1)]
1124
+ sage: pep.roots(AA)
1125
+ [(-6.626418492719221?, 1), (0, 1)]
1126
+
1127
+ TESTS::
1128
+
1129
+ sage: I = hyperplane_arrangements.Ish(2)
1130
+ sage: I.is_central()
1131
+ False
1132
+ sage: I.primitive_eulerian_polynomial()
1133
+ Traceback (most recent call last):
1134
+ ...
1135
+ ValueError: only defined for central hyperplane arrangements
1136
+ """
1137
+ if not self.is_central():
1138
+ raise ValueError("only defined for central hyperplane arrangements")
1139
+
1140
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1141
+ R = PolynomialRing(ZZ, 'z')
1142
+ z = R.gen()
1143
+ L = self.intersection_poset(element_label='subspace').dual()
1144
+ B = L.minimal_elements()[0]
1145
+ n = self.dimension()
1146
+ return R.sum(abs(L.moebius_function(B, X)) * (z - 1)**(n-X.dimension())
1147
+ for X in L)
1148
+
1149
+ def deletion(self, hyperplanes):
1150
+ r"""
1151
+ Return the hyperplane arrangement obtained by removing ``h``.
1152
+
1153
+ INPUT:
1154
+
1155
+ - ``h`` -- a hyperplane or hyperplane arrangement
1156
+
1157
+ OUTPUT:
1158
+
1159
+ A new hyperplane arrangement with the given hyperplane(s)
1160
+ ``h`` removed.
1161
+
1162
+ .. SEEALSO::
1163
+
1164
+ :meth:`restriction`
1165
+
1166
+ EXAMPLES::
1167
+
1168
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1169
+ sage: A = H([0,1,0], [1,0,1], [-1,0,1], [0,1,-1], [0,1,1]); A
1170
+ Arrangement of 5 hyperplanes of dimension 2 and rank 2
1171
+ sage: A.deletion(x)
1172
+ Arrangement <y - 1 | y + 1 | x - y | x + y>
1173
+ sage: h = H([0,1,0], [0,1,1])
1174
+ sage: A.deletion(h)
1175
+ Arrangement <y - 1 | y + 1 | x - y>
1176
+
1177
+ TESTS::
1178
+
1179
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1180
+ sage: A = H([0,1,0], [1,0,1], [-1,0,1], [0,1,-1], [0,1,1])
1181
+ sage: h = H([0,4,0])
1182
+ sage: A.deletion(h)
1183
+ Arrangement <y - 1 | y + 1 | x - y | x + y>
1184
+ sage: l = H([1,2,3])
1185
+ sage: A.deletion(l)
1186
+ Traceback (most recent call last):
1187
+ ...
1188
+ ValueError: hyperplane is not in the arrangement
1189
+
1190
+ Checks that deletion preserves the backend::
1191
+
1192
+ sage: H = HyperplaneArrangements(QQ, names='xyz')
1193
+ sage: x,y,z = H.gens()
1194
+ sage: h1,h2 = [1*x+2*y+3*z, 3*x+2*y+1*z]
1195
+ sage: A = H(h1,h2,backend='normaliz')
1196
+ sage: A.deletion(h2).backend()
1197
+ 'normaliz'
1198
+ """
1199
+ parent = self.parent()
1200
+ hyperplanes = parent(hyperplanes)
1201
+ planes = list(self)
1202
+ for hyperplane in hyperplanes:
1203
+ try:
1204
+ planes.remove(hyperplane)
1205
+ except ValueError:
1206
+ raise ValueError('hyperplane is not in the arrangement')
1207
+ return parent(*planes, backend=self._backend)
1208
+
1209
+ def restriction(self, hyperplane, repetitions=False):
1210
+ r"""
1211
+ Return the restriction to a hyperplane.
1212
+
1213
+ INPUT:
1214
+
1215
+ - ``hyperplane`` -- a hyperplane of the hyperplane arrangement
1216
+
1217
+ - ``repetitions`` -- boolean (default: ``False``); eliminate
1218
+ repetitions for ordered arrangements
1219
+
1220
+ OUTPUT:
1221
+
1222
+ The restriction `\mathcal{A}_H` of the
1223
+ hyperplane arrangement `\mathcal{A}` to the given ``hyperplane`` `H`.
1224
+
1225
+ EXAMPLES::
1226
+
1227
+ sage: # needs sage.graphs
1228
+ sage: A.<u,x,y,z> = hyperplane_arrangements.braid(4); A
1229
+ Arrangement of 6 hyperplanes of dimension 4 and rank 3
1230
+ sage: H = A[0]; H
1231
+ Hyperplane 0*u + 0*x + y - z + 0
1232
+ sage: R = A.restriction(H); R
1233
+ Arrangement <x - z | u - x | u - z>
1234
+ sage: A.add_hyperplane(z).restriction(z)
1235
+ Arrangement of 6 hyperplanes of dimension 3 and rank 3
1236
+ sage: A.add_hyperplane(u).restriction(u)
1237
+ Arrangement of 6 hyperplanes of dimension 3 and rank 3
1238
+ sage: D = A.deletion(H); D
1239
+ Arrangement of 5 hyperplanes of dimension 4 and rank 3
1240
+ sage: ca = A.characteristic_polynomial()
1241
+ sage: cr = R.characteristic_polynomial()
1242
+ sage: cd = D.characteristic_polynomial()
1243
+ sage: ca
1244
+ x^4 - 6*x^3 + 11*x^2 - 6*x
1245
+ sage: cd - cr
1246
+ x^4 - 6*x^3 + 11*x^2 - 6*x
1247
+
1248
+ .. SEEALSO::
1249
+
1250
+ :meth:`deletion`
1251
+
1252
+ TESTS:
1253
+
1254
+ Checks that restriction preserves the backend::
1255
+
1256
+ sage: H = HyperplaneArrangements(QQ, names='xyz')
1257
+ sage: x,y,z = H.gens()
1258
+ sage: h1,h2 = [1*x+2*y+3*z, 3*x+2*y+1*z]
1259
+ sage: A = H(h1, h2, backend='normaliz')
1260
+ sage: A.restriction(h2).backend() # needs sage.combinat
1261
+ 'normaliz'
1262
+ """
1263
+ parent = self.parent()
1264
+ hyperplane = parent(hyperplane)[0]
1265
+ if hyperplane not in self.hyperplanes():
1266
+ raise ValueError('hyperplane not in arrangement')
1267
+ pivot = hyperplane._normal_pivot()
1268
+ hyperplanes = []
1269
+ for h in self:
1270
+ rescale = h.A()[pivot] / hyperplane.A()[pivot]
1271
+ h = h - rescale * hyperplane
1272
+ A = list(h.A())
1273
+ A_pivot = A.pop(pivot)
1274
+ assert A_pivot == 0
1275
+ if all(a == 0 for a in A):
1276
+ continue
1277
+ b = h.b()
1278
+ hyperplanes.append([A, b])
1279
+ names = list(parent._names)
1280
+ names.pop(pivot)
1281
+ from sage.geometry.hyperplane_arrangement.ordered_arrangement import OrderedHyperplaneArrangements
1282
+ if isinstance(parent, OrderedHyperplaneArrangements):
1283
+ H = OrderedHyperplaneArrangements(parent.base_ring(), names=tuple(names))
1284
+ if not repetitions:
1285
+ L = list(hyperplanes)
1286
+ hyperplanes = ()
1287
+ for h in L:
1288
+ if h not in hyperplanes:
1289
+ hyperplanes += (h,)
1290
+ else:
1291
+ H = HyperplaneArrangements(parent.base_ring(), names=tuple(names))
1292
+ result = H(*hyperplanes, signed=False, backend=self._backend)
1293
+ return result
1294
+
1295
+ def change_ring(self, base_ring):
1296
+ """
1297
+ Return hyperplane arrangement over the new base ring.
1298
+
1299
+ INPUT:
1300
+
1301
+ - ``base_ring`` -- the new base ring; must be a field for
1302
+ hyperplane arrangements
1303
+
1304
+ OUTPUT:
1305
+
1306
+ The hyperplane arrangement obtained by changing the base
1307
+ field, as a new hyperplane arrangement.
1308
+
1309
+ .. WARNING::
1310
+
1311
+ While there is often a one-to-one correspondence between the
1312
+ hyperplanes of ``self`` and those of
1313
+ ``self.change_ring(base_ring)``, there is
1314
+ no guarantee that the order in which they appear in
1315
+ ``self.hyperplanes()`` will match the order in which their
1316
+ counterparts in ``self.cone()`` will appear in
1317
+ ``self.change_ring(base_ring).hyperplanes()``!
1318
+
1319
+ EXAMPLES::
1320
+
1321
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1322
+ sage: A = H([(1,1), 0], [(2,3), -1])
1323
+ sage: A.change_ring(FiniteField(2))
1324
+ Arrangement <y + 1 | x + y>
1325
+
1326
+ TESTS:
1327
+
1328
+ Checks that changing the ring preserves the backend::
1329
+
1330
+ sage: H = HyperplaneArrangements(QQ, names='xyz')
1331
+ sage: x,y,z = H.gens()
1332
+ sage: h1, h2 = [1*x+2*y+3*z, 3*x+2*y+1*z]
1333
+ sage: A = H(h1, h2, backend='normaliz')
1334
+ sage: A.change_ring(RDF).backend()
1335
+ 'normaliz'
1336
+ """
1337
+ parent = self.parent().change_ring(base_ring)
1338
+ return parent(self, backend=self._backend)
1339
+
1340
+ @cached_method
1341
+ def n_regions(self):
1342
+ r"""
1343
+ The number of regions of the hyperplane arrangement.
1344
+
1345
+ OUTPUT: integer
1346
+
1347
+ EXAMPLES::
1348
+
1349
+ sage: A = hyperplane_arrangements.semiorder(3)
1350
+ sage: A.n_regions()
1351
+ 19
1352
+
1353
+ TESTS::
1354
+
1355
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1356
+ sage: A = H([(1,1), 0], [(2,3), -1], [(4,5), 3])
1357
+ sage: B = A.change_ring(FiniteField(7))
1358
+ sage: B.n_regions()
1359
+ Traceback (most recent call last):
1360
+ ...
1361
+ TypeError: base field must have characteristic zero
1362
+
1363
+ Check that :issue:`30749` is fixed::
1364
+
1365
+ sage: # needs sage.rings.number_field
1366
+ sage: R.<y> = QQ[]
1367
+ sage: v1 = AA.polynomial_root(AA.common_polynomial(y^2 - 3),
1368
+ ....: RIF(RR(1.7320508075688772), RR(1.7320508075688774)))
1369
+ sage: v2 = QQbar.polynomial_root(AA.common_polynomial(y^4 - y^2 + 1),
1370
+ ....: CIF(RIF(RR(0.8660254037844386), RR(0.86602540378443871)),
1371
+ ....: RIF(-RR(0.50000000000000011), -RR(0.49999999999999994))))
1372
+ sage: my_vectors = (vector(AA, [-v1, -1, 1]), vector(AA, [0, 2, 1]), vector(AA, [v1, -1, 1]),
1373
+ ....: vector(AA, [1, 0, 0]), vector(AA, [1/2, AA(-1/2*v2^3 + v2),0]),
1374
+ ....: vector(AA, [-1/2, AA(-1/2*v2^3 + v2), 0]))
1375
+ sage: H = HyperplaneArrangements(AA, names='xyz')
1376
+ sage: x,y,z = H.gens()
1377
+ sage: A = H(backend='normaliz') # optional - pynormaliz
1378
+ sage: for v in my_vectors: # optional - pynormaliz
1379
+ ....: a, b, c = v
1380
+ ....: A = A.add_hyperplane(a*x + b*y + c*z)
1381
+ sage: A.n_regions() # optional - pynormaliz
1382
+ 24
1383
+ """
1384
+ if self.base_ring().characteristic() != 0:
1385
+ raise TypeError('base field must have characteristic zero')
1386
+ charpoly = self.characteristic_polynomial()
1387
+ return (-1)**self.dimension() * charpoly(-1)
1388
+
1389
+ @cached_method
1390
+ def n_bounded_regions(self):
1391
+ r"""
1392
+ Return the number of (relatively) bounded regions.
1393
+
1394
+ OUTPUT:
1395
+
1396
+ An integer. The number of relatively bounded regions of the
1397
+ hyperplane arrangement.
1398
+
1399
+ EXAMPLES::
1400
+
1401
+ sage: A = hyperplane_arrangements.semiorder(3)
1402
+ sage: A.n_bounded_regions()
1403
+ 7
1404
+
1405
+ TESTS::
1406
+
1407
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1408
+ sage: A = H([(1,1),0], [(2,3),-1], [(4,5),3])
1409
+ sage: B = A.change_ring(FiniteField(7))
1410
+ sage: B.n_bounded_regions()
1411
+ Traceback (most recent call last):
1412
+ ...
1413
+ TypeError: base field must have characteristic zero
1414
+ """
1415
+ if self.base_ring().characteristic() != 0:
1416
+ raise TypeError('base field must have characteristic zero')
1417
+ charpoly = self.characteristic_polynomial()
1418
+ return (-1)**self.rank() * charpoly(1)
1419
+
1420
+ def has_good_reduction(self, p) -> bool:
1421
+ r"""
1422
+ Return whether the hyperplane arrangement has good reduction mod `p`.
1423
+
1424
+ Let `A` be a hyperplane arrangement with equations defined
1425
+ over the integers, and let `B` be the hyperplane arrangement
1426
+ defined by reducing these equations modulo a prime `p`. Then
1427
+ `A` has good reduction modulo `p` if the intersection posets
1428
+ of `A` and `B` are isomorphic.
1429
+
1430
+ INPUT:
1431
+
1432
+ - ``p`` -- prime number
1433
+
1434
+ OUTPUT: boolean
1435
+
1436
+ EXAMPLES::
1437
+
1438
+ sage: # needs sage.combinat
1439
+ sage: a = hyperplane_arrangements.semiorder(3)
1440
+ sage: a.has_good_reduction(5)
1441
+ True
1442
+ sage: a.has_good_reduction(3)
1443
+ False
1444
+ sage: b = a.change_ring(GF(3))
1445
+ sage: a.characteristic_polynomial()
1446
+ x^3 - 6*x^2 + 12*x
1447
+ sage: b.characteristic_polynomial() # not equal to that for a
1448
+ x^3 - 6*x^2 + 10*x
1449
+ """
1450
+ if self.base_ring() != QQ:
1451
+ raise TypeError('arrangement must be defined over QQ')
1452
+ if not p.is_prime():
1453
+ raise TypeError('must reduce modulo a prime number')
1454
+ from sage.rings.finite_rings.finite_field_constructor import GF
1455
+ a = self.change_ring(GF(p))
1456
+ p = self.intersection_poset()
1457
+ q = a.intersection_poset()
1458
+ return p.is_isomorphic(q)
1459
+
1460
+ def is_linear(self):
1461
+ r"""
1462
+ Test whether all hyperplanes pass through the origin.
1463
+
1464
+ OUTPUT: boolean
1465
+
1466
+ EXAMPLES::
1467
+
1468
+ sage: a = hyperplane_arrangements.semiorder(3)
1469
+ sage: a.is_linear()
1470
+ False
1471
+ sage: b = hyperplane_arrangements.braid(3) # needs sage.graphs
1472
+ sage: b.is_linear() # needs sage.graphs
1473
+ True
1474
+
1475
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1476
+ sage: c = H(x+1, y+1)
1477
+ sage: c.is_linear()
1478
+ False
1479
+ sage: c.is_central()
1480
+ True
1481
+ """
1482
+ return all(hyperplane.b() == 0 for hyperplane in self)
1483
+
1484
+ def is_essential(self):
1485
+ r"""
1486
+ Test whether the hyperplane arrangement is essential.
1487
+
1488
+ A hyperplane arrangement is essential if the span of the normals
1489
+ of its hyperplanes spans the ambient space.
1490
+
1491
+ .. SEEALSO::
1492
+
1493
+ :meth:`essentialization`
1494
+
1495
+ OUTPUT: boolean
1496
+
1497
+ EXAMPLES::
1498
+
1499
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1500
+ sage: H(x, x+1).is_essential()
1501
+ False
1502
+ sage: H(x, y).is_essential()
1503
+ True
1504
+ """
1505
+ return self.rank() == self.dimension()
1506
+
1507
+ @cached_method
1508
+ def is_central(self, certificate=False):
1509
+ r"""
1510
+ Test whether the intersection of all the hyperplanes is nonempty.
1511
+
1512
+ A hyperplane arrangement is central if the intersection of all the
1513
+ hyperplanes in the arrangement is nonempty.
1514
+
1515
+ INPUT:
1516
+
1517
+ - ``certificate`` -- boolean (default: ``False``); specifies whether
1518
+ to return the center as a polyhedron (possibly empty) as part
1519
+ of the output
1520
+
1521
+ OUTPUT: if ``certificate`` is ``True``, returns a tuple containing:
1522
+
1523
+ 1. A boolean
1524
+ 2. The polyhedron defined to be the intersection of all the hyperplanes
1525
+
1526
+ If ``certificate`` is ``False``, returns a boolean.
1527
+
1528
+ EXAMPLES::
1529
+
1530
+ sage: a = hyperplane_arrangements.braid(2) # needs sage.graphs
1531
+ sage: a.is_central() # needs sage.graphs
1532
+ True
1533
+
1534
+ The Catalan arrangement in dimension 3 is not central::
1535
+
1536
+ sage: b = hyperplane_arrangements.Catalan(3)
1537
+ sage: b.is_central(certificate=True)
1538
+ (False, The empty polyhedron in QQ^3)
1539
+
1540
+ The empty arrangement in dimension 5 is central::
1541
+
1542
+ sage: H = HyperplaneArrangements(QQ, names=tuple(['x'+str(i) for i in range(7)]))
1543
+ sage: c = H()
1544
+ sage: c.is_central(certificate=True)
1545
+ (True, A 7-dimensional polyhedron in QQ^7 defined
1546
+ as the convex hull of 1 vertex and 7 lines)
1547
+ """
1548
+ R = self.base_ring()
1549
+ # If there are no hyperplanes in the arrangement,
1550
+ # the center is the entire ambient space
1551
+ if self.n_hyperplanes() == 0:
1552
+ if certificate:
1553
+ from sage.geometry.polyhedron.parent import Polyhedra
1554
+ pp = Polyhedra(R, self.dimension(), backend=self._backend)
1555
+ return (True, pp.universe())
1556
+ else:
1557
+ return True
1558
+ # The center is the set of points contained in all hyperplanes,
1559
+ # expressible as the solution set of m*x=b with m and b as follows:
1560
+ m = matrix(R, [h.normal() for h in self])
1561
+ b = vector(R, [h.b() for h in self])
1562
+ try:
1563
+ x = m.solve_right(b)
1564
+ except ValueError:
1565
+ # The solution set is empty, therefore the center is empty
1566
+ if certificate:
1567
+ from sage.geometry.polyhedron.parent import Polyhedra
1568
+ pp = Polyhedra(R, self.dimension(), backend=self._backend)
1569
+ return (False, pp.empty())
1570
+ else:
1571
+ return False
1572
+ # The center is the kernel of m translated by x.
1573
+ if certificate:
1574
+ Ker = m.right_kernel()
1575
+ from sage.geometry.polyhedron.constructor import Polyhedron
1576
+ return (True, Polyhedron(base_ring=R, vertices=[x],
1577
+ lines=Ker.basis(),
1578
+ backend=self._backend))
1579
+ else:
1580
+ return True
1581
+
1582
+ def center(self):
1583
+ r"""
1584
+ Return the center of the hyperplane arrangement.
1585
+
1586
+ The polyhedron defined to be the set of all points in the
1587
+ ambient space of the arrangement that lie on all of the
1588
+ hyperplanes.
1589
+
1590
+ OUTPUT: a polyhedron
1591
+
1592
+ EXAMPLES:
1593
+
1594
+ The empty hyperplane arrangement has the entire ambient space as its
1595
+ center::
1596
+
1597
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1598
+ sage: A = H()
1599
+ sage: A.center()
1600
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 lines
1601
+
1602
+ The Shi arrangement in dimension 3 has an empty center::
1603
+
1604
+ sage: A = hyperplane_arrangements.Shi(3)
1605
+ sage: A.center()
1606
+ The empty polyhedron in QQ^3
1607
+
1608
+ The Braid arrangement in dimension 3 has a center that is neither
1609
+ empty nor full-dimensional::
1610
+
1611
+ sage: A = hyperplane_arrangements.braid(3) # needs sage.combinat
1612
+ sage: A.center() # needs sage.combinat
1613
+ A 1-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex and 1 line
1614
+ """
1615
+ return self.is_central(certificate=True)[1]
1616
+
1617
+ @cached_method
1618
+ def is_simplicial(self):
1619
+ r"""
1620
+ Test whether the arrangement is simplicial.
1621
+
1622
+ A region is simplicial if the normal vectors of its bounding hyperplanes
1623
+ are linearly independent. A hyperplane arrangement is said to be
1624
+ simplicial if every region is simplicial.
1625
+
1626
+ OUTPUT: boolean; whether the hyperplane arrangement is simplicial
1627
+
1628
+ EXAMPLES::
1629
+
1630
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
1631
+ sage: A = H([[0,1,1,1], [0,1,2,3]])
1632
+ sage: A.is_simplicial()
1633
+ True
1634
+ sage: A = H([[0,1,1,1], [0,1,2,3], [0,1,3,2]])
1635
+ sage: A.is_simplicial()
1636
+ True
1637
+ sage: A = H([[0,1,1,1], [0,1,2,3], [0,1,3,2], [0,2,1,3]])
1638
+ sage: A.is_simplicial()
1639
+ False
1640
+ sage: hyperplane_arrangements.braid(3).is_simplicial() # needs sage.graphs
1641
+ True
1642
+ """
1643
+ # if the arr is not essential, grab the essential version and check there.
1644
+ if not self.is_essential():
1645
+ return self.essentialization().is_simplicial()
1646
+
1647
+ # Check that the number of facets for each region is equal to rank
1648
+ rank = self.rank()
1649
+ return all(R.n_facets() == rank for R in self.regions())
1650
+
1651
+ @cached_method
1652
+ def essentialization(self):
1653
+ r"""
1654
+ Return the essentialization of the hyperplane arrangement.
1655
+
1656
+ The essentialization of a hyperplane arrangement whose base field
1657
+ has characteristic 0 is obtained by intersecting the hyperplanes by
1658
+ the space spanned by their normal vectors.
1659
+
1660
+ OUTPUT:
1661
+
1662
+ The essentialization `\mathcal{A}'` of `\mathcal{A}` as a
1663
+ new hyperplane arrangement.
1664
+
1665
+ EXAMPLES::
1666
+
1667
+ sage: a = hyperplane_arrangements.braid(3) # needs sage.graphs
1668
+ sage: a.is_essential() # needs sage.graphs
1669
+ False
1670
+ sage: a.essentialization() # needs sage.graphs
1671
+ Arrangement <t1 - t2 | t1 + 2*t2 | 2*t1 + t2>
1672
+
1673
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1674
+ sage: B = H([(1,0),1], [(1,0),-1])
1675
+ sage: B.is_essential()
1676
+ False
1677
+ sage: B.essentialization()
1678
+ Arrangement <-x + 1 | x + 1>
1679
+ sage: B.essentialization().parent()
1680
+ Hyperplane arrangements in 1-dimensional linear space over
1681
+ Rational Field with coordinate x
1682
+
1683
+ sage: H.<x,y> = HyperplaneArrangements(GF(2))
1684
+ sage: C = H([(1,1),1], [(1,1),0])
1685
+ sage: C.essentialization()
1686
+ Arrangement <y | y + 1>
1687
+
1688
+ sage: h = hyperplane_arrangements.semiorder(4)
1689
+ sage: h.essentialization()
1690
+ Arrangement of 12 hyperplanes of dimension 3 and rank 3
1691
+
1692
+ TESTS::
1693
+
1694
+ sage: b = hyperplane_arrangements.coordinate(2)
1695
+ sage: b.is_essential()
1696
+ True
1697
+ sage: b.essentialization() is b
1698
+ True
1699
+ """
1700
+ def echelon_col_iter(row_iter):
1701
+ """helper to iterat over the echelon pivot column indices"""
1702
+ for row in row_iter:
1703
+ if row == 0:
1704
+ return
1705
+ for pivot in range(self.dimension()):
1706
+ if row[pivot] != 0:
1707
+ break
1708
+ assert row[pivot] == 1
1709
+ yield pivot, row
1710
+
1711
+ if self.is_essential():
1712
+ return self
1713
+ parent = self.parent()
1714
+ H = parent.ambient_space()
1715
+ R = parent.base_ring()
1716
+ hyperplanes = self.hyperplanes()
1717
+ normals = matrix(R, [h.normal() for h in self]).transpose()
1718
+ # find a (any) complement to the normals
1719
+ if R.characteristic() == 0:
1720
+ complement_basis = normals.kernel().echelonized_basis()
1721
+ else:
1722
+ # we don't necessarily have an orthogonal complement, pick any complement
1723
+ complement_basis = []
1724
+ for pivot, row in echelon_col_iter(normals.echelon_form().rows()):
1725
+ v = [0] * self.dimension()
1726
+ v[pivot] = 1
1727
+ complement_basis.append(vector(R, v))
1728
+ # reduce the hyperplane equations
1729
+ echelon_pivots = [] # the column indices where N has 1s from the echelonization
1730
+ for pivot, row in echelon_col_iter(complement_basis):
1731
+ assert row[pivot] == 1
1732
+ echelon_pivots.append(pivot)
1733
+ hyperplanes = [h - h.A()[pivot] * H(row, 0) for h in hyperplanes]
1734
+ # eliminate the pivot'ed coordinates
1735
+ restricted = []
1736
+ for h in hyperplanes:
1737
+ A = h.A()
1738
+ if A == 0:
1739
+ continue
1740
+ A = [A[i] for i in range(self.dimension()) if i not in echelon_pivots]
1741
+ b = h.b()
1742
+ restricted.append([A, b])
1743
+ names = tuple(name for i, name in enumerate(parent._names) if i not in echelon_pivots)
1744
+ # Construct the result
1745
+ restricted_parent = HyperplaneArrangements(R, names=names)
1746
+ return restricted_parent(*restricted, signed=False, backend=self._backend)
1747
+
1748
+ def sign_vector(self, p):
1749
+ r"""
1750
+ Indicates on which side of each hyperplane the given
1751
+ point `p` lies.
1752
+
1753
+ The base field must have characteristic zero.
1754
+
1755
+ INPUT:
1756
+
1757
+ - ``p`` -- point as a list/tuple/iterable
1758
+
1759
+ OUTPUT:
1760
+
1761
+ A vector whose entries are in `[-1, 0, +1]`.
1762
+
1763
+ EXAMPLES::
1764
+
1765
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1766
+ sage: A = H([(1,0), 0], [(0,1), 1]); A
1767
+ Arrangement <y + 1 | x>
1768
+ sage: A.sign_vector([2, -2])
1769
+ (-1, 1)
1770
+ sage: A.sign_vector((-1, -1))
1771
+ (0, -1)
1772
+
1773
+ TESTS::
1774
+
1775
+ sage: H.<x,y> = HyperplaneArrangements(GF(3))
1776
+ sage: A = H(x, y)
1777
+ sage: A.sign_vector([1, 2])
1778
+ Traceback (most recent call last):
1779
+ ...
1780
+ ValueError: characteristic must be zero
1781
+ """
1782
+ if self.base_ring().characteristic() != 0:
1783
+ raise ValueError('characteristic must be zero')
1784
+ from sage.functions.generalized import sign
1785
+ values = [hyperplane(p) for hyperplane in self]
1786
+ signs = vector(ZZ, [sign(_) for _ in values])
1787
+ signs.set_immutable()
1788
+ return signs
1789
+
1790
+ def face_vector(self):
1791
+ r"""
1792
+ Return the face vector.
1793
+
1794
+ OUTPUT: a vector of integers
1795
+
1796
+ The `d`-th entry is the number of faces of dimension `d`. A
1797
+ *face* is the intersection of a region with a hyperplane of
1798
+ the arrangement.
1799
+
1800
+ EXAMPLES::
1801
+
1802
+ sage: A = hyperplane_arrangements.Shi(3)
1803
+ sage: A.face_vector() # needs sage.combinat
1804
+ (0, 6, 21, 16)
1805
+ """
1806
+ m = self.whitney_data()[0]
1807
+ v = list(sum(m.transpose().apply_map(abs)))
1808
+ v.reverse()
1809
+ v = vector(ZZ, [0]*(self.dimension() - self.rank()) + v)
1810
+ v.set_immutable()
1811
+ return v
1812
+
1813
+ @cached_method
1814
+ def _parallel_hyperplanes(self) -> tuple:
1815
+ """
1816
+ Return the hyperplanes grouped into parallel sets.
1817
+
1818
+ OUTPUT:
1819
+
1820
+ A tuple with one entry per set of parallel hyperplanes. Each
1821
+ entry is a tuple of triples, one for each parallel hyperplane
1822
+ in the parallel set. The triple consists of the hyperplane,
1823
+ the normal vector `A`, and the constant `b` of the hyperplane
1824
+ equation `Ax+b`. The normalization is such that `A` is the
1825
+ same for each hyperplane of the parallel set, and the order is
1826
+ in increasing order of the `b` values.
1827
+
1828
+ In other words, each parallel set of hyperplanes is also
1829
+ ordered by the order with which a common normal passes through
1830
+ them.
1831
+
1832
+ EXAMPLES::
1833
+
1834
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1835
+ sage: h = (x + 2*y | 2*x + 4*y + 1 | -x/4 - y/2 + 1); h
1836
+ Arrangement <-x - 2*y + 4 | x + 2*y | 2*x + 4*y + 1>
1837
+ sage: h._parallel_hyperplanes()[0]
1838
+ ((Hyperplane -x - 2*y + 4, (1, 2), -4),
1839
+ (Hyperplane x + 2*y + 0, (1, 2), 0),
1840
+ (Hyperplane 2*x + 4*y + 1, (1, 2), 1/2))
1841
+
1842
+ sage: hyperplane_arrangements.Shi(3)._parallel_hyperplanes()
1843
+ (((Hyperplane 0*t0 + t1 - t2 - 1, (0, 1, -1), -1),
1844
+ (Hyperplane 0*t0 + t1 - t2 + 0, (0, 1, -1), 0)),
1845
+ ((Hyperplane t0 - t1 + 0*t2 - 1, (1, -1, 0), -1),
1846
+ (Hyperplane t0 - t1 + 0*t2 + 0, (1, -1, 0), 0)),
1847
+ ((Hyperplane t0 + 0*t1 - t2 - 1, (1, 0, -1), -1),
1848
+ (Hyperplane t0 + 0*t1 - t2 + 0, (1, 0, -1), 0)))
1849
+ """
1850
+ V = self.parent().ambient_space()
1851
+ parallels = {}
1852
+ for hyperplane in self:
1853
+ through_origin = V([list(hyperplane.A()), 0]).primitive(signed=False)
1854
+ parallel_planes = parallels.get(through_origin, [])
1855
+ A = through_origin.A()
1856
+ b = hyperplane.b() * (A / hyperplane.A())
1857
+ parallel_planes.append([b, (hyperplane, A, b)])
1858
+ parallels[through_origin] = parallel_planes
1859
+ parallels = sorted(tuple(hyperplane[1] for hyperplane in sorted(value))
1860
+ for key, value in parallels.items())
1861
+ return tuple(parallels)
1862
+
1863
+ def vertices(self, exclude_sandwiched=False):
1864
+ """
1865
+ Return the vertices.
1866
+
1867
+ The vertices are the zero-dimensional faces, see
1868
+ :meth:`face_vector`.
1869
+
1870
+ INPUT:
1871
+
1872
+ - ``exclude_sandwiched`` -- boolean (default:
1873
+ ``False``). Whether to exclude hyperplanes that are
1874
+ sandwiched between parallel hyperplanes. Useful if you only
1875
+ need the convex hull.
1876
+
1877
+ OUTPUT:
1878
+
1879
+ The vertices in a sorted tuple. Each vertex is returned as a
1880
+ vector in the ambient vector space.
1881
+
1882
+ EXAMPLES::
1883
+
1884
+ sage: # needs sage.combinat
1885
+ sage: A = hyperplane_arrangements.Shi(3).essentialization()
1886
+ sage: A.dimension()
1887
+ 2
1888
+ sage: A.face_vector()
1889
+ (6, 21, 16)
1890
+ sage: A.vertices()
1891
+ ((-2/3, 1/3), (-1/3, -1/3), (0, -1), (0, 0), (1/3, -2/3), (2/3, -1/3))
1892
+ sage: point2d(A.vertices(), size=20) + A.plot() # needs sage.plot sage.symbolic
1893
+ Graphics object consisting of 7 graphics primitives
1894
+
1895
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1896
+ sage: chessboard = []
1897
+ sage: N = 8
1898
+ sage: for x0 in range(N + 1):
1899
+ ....: for y0 in range(N + 1):
1900
+ ....: chessboard.extend([x-x0, y-y0])
1901
+ sage: chessboard = H(chessboard)
1902
+ sage: len(chessboard.vertices())
1903
+ 81
1904
+ sage: chessboard.vertices(exclude_sandwiched=True)
1905
+ ((0, 0), (0, 8), (8, 0), (8, 8))
1906
+ """
1907
+ import itertools
1908
+ from sage.matroids.constructor import Matroid
1909
+ R = self.parent().base_ring()
1910
+ parallels = self._parallel_hyperplanes()
1911
+ A_list = [parallel[0][1] for parallel in parallels]
1912
+ b_list_list = [[-hyperplane[2] for hyperplane in parallel]
1913
+ for parallel in parallels]
1914
+ if exclude_sandwiched:
1915
+ def skip(b_list):
1916
+ if len(b_list) == 1:
1917
+ return b_list
1918
+ return [b_list[0], b_list[-1]]
1919
+ b_list_list = [skip(_) for _ in b_list_list]
1920
+ M = Matroid(groundset=range(len(parallels)), matrix=matrix(A_list).transpose())
1921
+ d = self.dimension()
1922
+ # vertices are solutions v * lhs = rhs
1923
+ lhs = matrix(R, d, d)
1924
+ rhs = vector(R, d)
1925
+ vertices = set()
1926
+ for indices in M.independent_sets(d):
1927
+ for row, i in enumerate(indices):
1928
+ lhs[row] = A_list[i]
1929
+ b_list = [b_list_list[i] for i in indices]
1930
+ for b in itertools.product(*b_list):
1931
+ for i in range(d):
1932
+ rhs[i] = b[i]
1933
+ vertex = lhs.solve_right(rhs)
1934
+ vertex.set_immutable()
1935
+ vertices.add(vertex)
1936
+ return tuple(sorted(vertices))
1937
+
1938
+ def _make_region(self, hyperplanes):
1939
+ """
1940
+ Helper method to construct a region.
1941
+
1942
+ INPUT:
1943
+
1944
+ - ``hyperplanes`` -- list/tuple/iterable of hyperplanes
1945
+
1946
+ OUTPUT:
1947
+
1948
+ The polyhedron constructed from taking the linear expressions
1949
+ as inequalities.
1950
+
1951
+ EXAMPLES::
1952
+
1953
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1954
+ sage: h = H(x)
1955
+ sage: h._make_region([x, 1-x, y, 1-y])
1956
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
1957
+
1958
+ TESTS:
1959
+
1960
+ Checks that it creates the regions with the appropriate backend::
1961
+
1962
+ sage: h = H(x,backend='normaliz')
1963
+ sage: h._make_region([x, 1-x, y, 1-y]).backend() # optional - pynormaliz
1964
+ 'normaliz'
1965
+ """
1966
+ ieqs = [h.dense_coefficient_list() for h in hyperplanes]
1967
+ from sage.geometry.polyhedron.constructor import Polyhedron
1968
+ return Polyhedron(ieqs=ieqs, ambient_dim=self.dimension(),
1969
+ base_ring=self.parent().base_ring(),
1970
+ backend=self._backend)
1971
+
1972
+ @cached_method
1973
+ def regions(self):
1974
+ r"""
1975
+ Return the regions of the hyperplane arrangement.
1976
+
1977
+ The base field must have characteristic zero.
1978
+
1979
+ OUTPUT: a tuple containing the regions as polyhedra
1980
+
1981
+ The regions are the connected components of the complement of
1982
+ the union of the hyperplanes as a subset of `\RR^n`.
1983
+
1984
+ EXAMPLES::
1985
+
1986
+ sage: a = hyperplane_arrangements.braid(2) # needs sage.graphs
1987
+ sage: a.regions() # needs sage.graphs
1988
+ (A 2-dimensional polyhedron in QQ^2 defined
1989
+ as the convex hull of 1 vertex, 1 ray, 1 line,
1990
+ A 2-dimensional polyhedron in QQ^2 defined
1991
+ as the convex hull of 1 vertex, 1 ray, 1 line)
1992
+
1993
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
1994
+ sage: A = H(x, y+1)
1995
+ sage: A.regions()
1996
+ (A 2-dimensional polyhedron in QQ^2 defined
1997
+ as the convex hull of 1 vertex and 2 rays,
1998
+ A 2-dimensional polyhedron in QQ^2 defined
1999
+ as the convex hull of 1 vertex and 2 rays,
2000
+ A 2-dimensional polyhedron in QQ^2 defined
2001
+ as the convex hull of 1 vertex and 2 rays,
2002
+ A 2-dimensional polyhedron in QQ^2 defined
2003
+ as the convex hull of 1 vertex and 2 rays)
2004
+
2005
+ sage: chessboard = []
2006
+ sage: N = 8
2007
+ sage: for x0 in range(N + 1):
2008
+ ....: for y0 in range(N + 1):
2009
+ ....: chessboard.extend([x-x0, y-y0])
2010
+ sage: chessboard = H(chessboard)
2011
+ sage: len(chessboard.bounded_regions()) # long time, 359 ms on a Core i7
2012
+ 64
2013
+
2014
+ Example 6 of [KP2020]_::
2015
+
2016
+ sage: from itertools import product
2017
+ sage: def zero_one(d):
2018
+ ....: for x in product([0,1], repeat=d):
2019
+ ....: if any(x):
2020
+ ....: yield [0] + list(x)
2021
+
2022
+ sage: K.<x,y> = HyperplaneArrangements(QQ)
2023
+ sage: A = K(*zero_one(2))
2024
+ sage: len(A.regions())
2025
+ 6
2026
+ sage: K.<x,y,z> = HyperplaneArrangements(QQ)
2027
+ sage: A = K(*zero_one(3))
2028
+ sage: len(A.regions())
2029
+ 32
2030
+ sage: K.<x,y,z,w> = HyperplaneArrangements(QQ)
2031
+ sage: A = K(*zero_one(4))
2032
+ sage: len(A.regions())
2033
+ 370
2034
+ sage: K.<x,y,z,w,r> = HyperplaneArrangements(QQ)
2035
+ sage: A = K(*zero_one(5))
2036
+ sage: len(A.regions()) # not tested (~25s)
2037
+ 11292
2038
+
2039
+ It is possible to specify the backend::
2040
+
2041
+ sage: # needs sage.rings.number_field
2042
+ sage: K.<q> = CyclotomicField(9)
2043
+ sage: L.<r9> = NumberField((q + q**(-1)).minpoly(),
2044
+ ....: embedding=AA(q + q**-1))
2045
+ sage: norms = [[1, 1/3*(-2*r9**2-r9+1), 0],
2046
+ ....: [1, -r9**2 - r9, 0],
2047
+ ....: [1, -r9**2 + 1, 0],
2048
+ ....: [1, -r9**2, 0],
2049
+ ....: [1, r9**2 - 4, -r9**2+3]]
2050
+ sage: H.<x,y,z> = HyperplaneArrangements(L)
2051
+ sage: A = H(backend='normaliz')
2052
+ sage: for v in norms:
2053
+ ....: a,b,c = v
2054
+ ....: A = A.add_hyperplane(a*x + b*y + c*z)
2055
+ sage: R = A.regions() # optional - pynormaliz
2056
+ sage: R[0].backend() # optional - pynormaliz
2057
+ 'normaliz'
2058
+
2059
+ TESTS::
2060
+
2061
+ sage: K.<x,y,z,w,r> = HyperplaneArrangements(QQ)
2062
+ sage: A = K()
2063
+ sage: A.regions()
2064
+ (A 5-dimensional polyhedron in QQ^5
2065
+ defined as the convex hull of 1 vertex and 5 lines,)
2066
+ """
2067
+ if self.base_ring().characteristic() != 0:
2068
+ raise ValueError('base field must have characteristic zero')
2069
+ from sage.geometry.polyhedron.constructor import Polyhedron
2070
+ R = self.base_ring()
2071
+ dim = self.dimension()
2072
+ be = self._backend
2073
+ universe = Polyhedron(eqns=[[0] + [0] * dim],
2074
+ base_ring=R,
2075
+ backend=be)
2076
+ regions = [universe]
2077
+ if self.is_linear() and self.n_hyperplanes():
2078
+ # We only take the positive half w.r. to the first hyperplane.
2079
+ # We fix this by appending all negative regions in the end.
2080
+ regions = None
2081
+
2082
+ for hyperplane in self:
2083
+ ieq = vector(R, hyperplane.dense_coefficient_list())
2084
+ pos_half = Polyhedron(ieqs=[ieq], base_ring=R, backend=be)
2085
+ neg_half = Polyhedron(ieqs=[-ieq], base_ring=R, backend=be)
2086
+ if not regions:
2087
+ # See comment above.
2088
+ regions = [pos_half]
2089
+ continue
2090
+ subdivided = []
2091
+ for region in regions:
2092
+ # For each region we determine, if the hyperplane splits it.
2093
+ splits = False
2094
+
2095
+ # Determine if all vertices lie on one side of the hyperplane.
2096
+ # If so, we determine on which side.
2097
+ valuations = tuple(ieq[0] + ieq[1:]*v[:] for v in region.vertices())
2098
+ direction = 0
2099
+ if any(x > 0 for x in valuations):
2100
+ direction = 1
2101
+ if any(x < 0 for x in valuations):
2102
+ if direction:
2103
+ splits = True
2104
+ else:
2105
+ direction = -1
2106
+
2107
+ if not splits:
2108
+ # All vertices lie in one closed halfspace of the hyperplane.
2109
+ region_lines = region.lines()
2110
+ if direction == 0:
2111
+ # In this case all vertices lie on the hyperplane and we must
2112
+ # check if rays are contained in one closed halfspace given by the hyperplane.
2113
+ valuations = tuple(ieq[1:]*ray[:] for ray in region.rays())
2114
+ if region_lines:
2115
+ valuations += tuple(ieq[1:]*line[:] for line in region_lines)
2116
+ valuations += tuple(-ieq[1:]*line[:] for line in region_lines)
2117
+ if any(x > 0 for x in valuations) and any(x < 0 for x in valuations):
2118
+ splits = True
2119
+ else:
2120
+ # In this case, at least one of the vertices is not on the hyperplane.
2121
+ # So we check if any ray or line pokes the hyperplane.
2122
+ if (any(ieq[1:]*r[:]*direction < 0 for r in region.rays()) or
2123
+ any(ieq[1:]*ll[:] != 0 for ll in region_lines)):
2124
+ splits = True
2125
+
2126
+ if splits:
2127
+ subdivided.append(region.intersection(pos_half))
2128
+ subdivided.append(region.intersection(neg_half))
2129
+ else:
2130
+ subdivided.append(region)
2131
+ regions = subdivided
2132
+
2133
+ if self.is_linear() and self.n_hyperplanes():
2134
+ # We have treated so far only the positive half space w.r. to the first hyperplane.
2135
+ return tuple(regions) + tuple(-x for x in regions)
2136
+ else:
2137
+ return tuple(regions)
2138
+
2139
+ @cached_method
2140
+ def poset_of_regions(self, B=None, numbered_labels=True):
2141
+ r"""
2142
+ Return the poset of regions for a central hyperplane arrangement.
2143
+
2144
+ The poset of regions is a partial order on the set of regions
2145
+ where the regions are ordered by `R\leq R'` if and only if
2146
+ `S(R) \subseteq S(R')` where `S(R)` is the set of hyperplanes which
2147
+ separate the region `R` from the base region `B`.
2148
+
2149
+ INPUT:
2150
+
2151
+ - ``B`` -- a region (optional); if ``None``, then
2152
+ an arbitrary region is chosen as the base region
2153
+
2154
+ - ``numbered_labels`` -- boolean (default: ``True``); if ``True``,
2155
+ then the elements of the poset are numbered. Else they are labelled
2156
+ with the regions themselves.
2157
+
2158
+ OUTPUT: a Poset object containing the poset of regions
2159
+
2160
+ EXAMPLES::
2161
+
2162
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
2163
+ sage: A = H([[0,1,1,1], [0,1,2,3]])
2164
+ sage: A.poset_of_regions() # needs sage.combinat
2165
+ Finite poset containing 4 elements
2166
+
2167
+ sage: # needs sage.combinat sage.graphs
2168
+ sage: A = hyperplane_arrangements.braid(3)
2169
+ sage: A.poset_of_regions()
2170
+ Finite poset containing 6 elements
2171
+ sage: A.poset_of_regions(numbered_labels=False)
2172
+ Finite poset containing 6 elements
2173
+ sage: A = hyperplane_arrangements.braid(4)
2174
+ sage: A.poset_of_regions()
2175
+ Finite poset containing 24 elements
2176
+
2177
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
2178
+ sage: A = H([[0,1,1,1], [0,1,2,3], [0,1,3,2], [0,2,1,3]])
2179
+ sage: R = A.regions()
2180
+ sage: base_region = R[3]
2181
+ sage: A.poset_of_regions(B=base_region) # needs sage.combinat
2182
+ Finite poset containing 14 elements
2183
+ """
2184
+ from sage.combinat.posets.posets import Poset
2185
+
2186
+ # We use RX to keep track of indexes and R to keep track of which regions
2187
+ # we've already hit. This poset is graded, so we can go one set at a time
2188
+ RX = self.regions()
2189
+ R = set(RX)
2190
+ if B in R:
2191
+ R.discard(B)
2192
+ else:
2193
+ B = R.pop()
2194
+
2195
+ # Will record the edges in our poset
2196
+ edges = []
2197
+
2198
+ # Start with rank=0 for the poset
2199
+ nextTest = [B]
2200
+
2201
+ # While we have objects in our set R
2202
+ while R:
2203
+ # Transfer the "next step" to the "current step"
2204
+ curTest = list(nextTest)
2205
+ nextTest = set()
2206
+ # we want to test each region that we haven't hit yet
2207
+ for r in R:
2208
+ # Since it's graded, it suffices to look at the regions of the previous rank
2209
+ for b in curTest:
2210
+ if self.distance_between_regions(b, r) == 1:
2211
+ nextTest.add(r)
2212
+ if numbered_labels:
2213
+ edges.append([RX.index(b), RX.index(r)])
2214
+ else:
2215
+ edges.append([b, r])
2216
+ for x in nextTest:
2217
+ R.discard(x)
2218
+
2219
+ if numbered_labels:
2220
+ return Poset([range(len(RX)), edges])
2221
+ else:
2222
+ return Poset([RX, edges])
2223
+
2224
+ @cached_method
2225
+ def closed_faces(self, labelled=True):
2226
+ r"""
2227
+ Return the closed faces of the hyperplane arrangement ``self``
2228
+ (provided that ``self`` is defined over a totally ordered field).
2229
+
2230
+ Let `\mathcal{A}` be a hyperplane arrangement in the vector
2231
+ space `K^n`, whose hyperplanes are the zero sets of the
2232
+ affine-linear functions `u_1, u_2, \ldots, u_N`. (We consider
2233
+ these functions `u_1, u_2, \ldots, u_N`, and not just the
2234
+ hyperplanes, as given. We also assume the field `K` to be
2235
+ totally ordered.) For any point `x \in K^n`, we define the
2236
+ *sign vector* of `x` to be the vector
2237
+ `(v_1, v_2, \ldots, v_N) \in \{-1, 0, 1\}^N` such that (for each
2238
+ `i`) the number `v_i` is the sign of `u_i(x)`. For any
2239
+ `v \in \{-1, 0, 1\}^N`, we let `F_v` be the set of all `x \in K^n`
2240
+ which have sign vector `v`. The nonempty ones among all these
2241
+ subsets `F_v` are called the *open faces* of `\mathcal{A}`. They
2242
+ form a partition of the set `K^n`.
2243
+
2244
+ Furthermore, for any
2245
+ `v = (v_1, v_2, \ldots, v_N) \in \{-1, 0, 1\}^N`, we let `G_v` be
2246
+ the set of all `x \in K^n` such that, for every `i`, the sign of
2247
+ `u_i(x)` is either `0` or `v_i`.
2248
+ Then, `G_v` is a polyhedron. The nonempty ones among all these
2249
+ polyhedra `G_v` are called the *closed faces* of `\mathcal{A}`.
2250
+ While several sign vectors `v` can lead to one and the same
2251
+ closed face `G_v`, we can assign to every closed face a canonical
2252
+ choice of a sign vector: Namely, if `G` is a closed face of
2253
+ `\mathcal{A}`, then the *sign vector* of `G` is defined to be the
2254
+ vector `(v_1, v_2, \ldots, v_N) \in \{-1, 0, 1\}^N` where `x` is
2255
+ any point in the relative interior of `G` and where, for each `i`,
2256
+ the number `v_i` is the sign of `u_i(x)`. (This does not depend on
2257
+ the choice of `x`.)
2258
+
2259
+ There is a one-to-one correspondence between the closed faces and
2260
+ the open faces of `\mathcal{A}`. It sends a closed face `G` to
2261
+ the open face `F_v`, where `v` is the sign vector of `G`; this
2262
+ `F_v` is also the relative interior of `G_v`. The inverse map
2263
+ sends any open face `O` to the closure of `O`.
2264
+
2265
+ INPUT:
2266
+
2267
+ - ``labelled`` -- boolean (default: ``True``); if ``True``, then
2268
+ this method returns not the faces itself but rather pairs
2269
+ `(v, F)` where `F` is a closed face and `v` is its sign vector
2270
+ (here, the order and the orientation of the
2271
+ `u_1, u_2, \ldots, u_N` is as given by ``self.hyperplanes()``).
2272
+
2273
+ OUTPUT:
2274
+
2275
+ A tuple containing the closed faces as polyhedra, or (if
2276
+ ``labelled`` is set to ``True``) the pairs of sign vectors and
2277
+ corresponding closed faces.
2278
+
2279
+ .. TODO::
2280
+
2281
+ Should the output rather be a dictionary where the keys are
2282
+ the sign vectors and the values are the faces?
2283
+
2284
+ EXAMPLES::
2285
+
2286
+ sage: # needs sage.graphs
2287
+ sage: a = hyperplane_arrangements.braid(2)
2288
+ sage: a.hyperplanes()
2289
+ (Hyperplane t0 - t1 + 0,)
2290
+ sage: a.closed_faces()
2291
+ (((0,), A 1-dimensional polyhedron in QQ^2 defined
2292
+ as the convex hull of 1 vertex and 1 line),
2293
+ ((1,), A 2-dimensional polyhedron in QQ^2 defined
2294
+ as the convex hull of 1 vertex, 1 ray, 1 line),
2295
+ ((-1,), A 2-dimensional polyhedron in QQ^2 defined
2296
+ as the convex hull of 1 vertex, 1 ray, 1 line))
2297
+ sage: a.closed_faces(labelled=False)
2298
+ (A 1-dimensional polyhedron in QQ^2 defined
2299
+ as the convex hull of 1 vertex and 1 line,
2300
+ A 2-dimensional polyhedron in QQ^2 defined
2301
+ as the convex hull of 1 vertex, 1 ray, 1 line,
2302
+ A 2-dimensional polyhedron in QQ^2 defined
2303
+ as the convex hull of 1 vertex, 1 ray, 1 line)
2304
+ sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()]
2305
+ [((0,), A 1-dimensional polyhedron in QQ^2 defined
2306
+ as the convex hull of 1 vertex and 1 line, (0, 0)),
2307
+ ((1,), A 2-dimensional polyhedron in QQ^2 defined
2308
+ as the convex hull of 1 vertex, 1 ray, 1 line, (0, -1)),
2309
+ ((-1,), A 2-dimensional polyhedron in QQ^2 defined
2310
+ as the convex hull of 1 vertex, 1 ray, 1 line, (-1, 0))]
2311
+
2312
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
2313
+ sage: a = H(x, y+1)
2314
+ sage: a.hyperplanes()
2315
+ (Hyperplane 0*x + y + 1, Hyperplane x + 0*y + 0)
2316
+ sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()]
2317
+ [((0, 0), A 0-dimensional polyhedron in QQ^2 defined
2318
+ as the convex hull of 1 vertex, (0, -1)),
2319
+ ((0, 1), A 1-dimensional polyhedron in QQ^2 defined
2320
+ as the convex hull of 1 vertex and 1 ray, (1, -1)),
2321
+ ((0, -1), A 1-dimensional polyhedron in QQ^2 defined
2322
+ as the convex hull of 1 vertex and 1 ray, (-1, -1)),
2323
+ ((1, 0), A 1-dimensional polyhedron in QQ^2 defined
2324
+ as the convex hull of 1 vertex and 1 ray, (0, 0)),
2325
+ ((1, 1), A 2-dimensional polyhedron in QQ^2 defined
2326
+ as the convex hull of 1 vertex and 2 rays, (1, 0)),
2327
+ ((1, -1), A 2-dimensional polyhedron in QQ^2 defined
2328
+ as the convex hull of 1 vertex and 2 rays, (-1, 0)),
2329
+ ((-1, 0), A 1-dimensional polyhedron in QQ^2 defined
2330
+ as the convex hull of 1 vertex and 1 ray, (0, -2)),
2331
+ ((-1, 1), A 2-dimensional polyhedron in QQ^2 defined
2332
+ as the convex hull of 1 vertex and 2 rays, (1, -2)),
2333
+ ((-1, -1), A 2-dimensional polyhedron in QQ^2 defined
2334
+ as the convex hull of 1 vertex and 2 rays, (-1, -2))]
2335
+
2336
+ sage: # needs sage.graphs
2337
+ sage: a = hyperplane_arrangements.braid(3)
2338
+ sage: a.hyperplanes()
2339
+ (Hyperplane 0*t0 + t1 - t2 + 0,
2340
+ Hyperplane t0 - t1 + 0*t2 + 0,
2341
+ Hyperplane t0 + 0*t1 - t2 + 0)
2342
+ sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()]
2343
+ [((0, 0, 0), A 1-dimensional polyhedron in QQ^3 defined
2344
+ as the convex hull of 1 vertex and 1 line, (0, 0, 0)),
2345
+ ((0, 1, 1), A 2-dimensional polyhedron in QQ^3 defined
2346
+ as the convex hull of 1 vertex, 1 ray, 1 line, (0, -1, -1)),
2347
+ ((0, -1, -1), A 2-dimensional polyhedron in QQ^3 defined
2348
+ as the convex hull of 1 vertex, 1 ray, 1 line, (-1, 0, 0)),
2349
+ ((1, 0, 1), A 2-dimensional polyhedron in QQ^3 defined
2350
+ as the convex hull of 1 vertex, 1 ray, 1 line, (1, 1, 0)),
2351
+ ((1, 1, 1), A 3-dimensional polyhedron in QQ^3 defined
2352
+ as the convex hull of 1 vertex, 2 rays, 1 line, (0, -1, -2)),
2353
+ ((1, -1, 0), A 2-dimensional polyhedron in QQ^3 defined
2354
+ as the convex hull of 1 vertex, 1 ray, 1 line, (-1, 0, -1)),
2355
+ ((1, -1, 1), A 3-dimensional polyhedron in QQ^3 defined
2356
+ as the convex hull of 1 vertex, 2 rays, 1 line, (1, 2, 0)),
2357
+ ((1, -1, -1), A 3-dimensional polyhedron in QQ^3 defined
2358
+ as the convex hull of 1 vertex, 2 rays, 1 line, (-2, 0, -1)),
2359
+ ((-1, 0, -1), A 2-dimensional polyhedron in QQ^3 defined
2360
+ as the convex hull of 1 vertex, 1 ray, 1 line, (0, 0, 1)),
2361
+ ((-1, 1, 0), A 2-dimensional polyhedron in QQ^3 defined
2362
+ as the convex hull of 1 vertex, 1 ray, 1 line, (1, 0, 1)),
2363
+ ((-1, 1, 1), A 3-dimensional polyhedron in QQ^3 defined
2364
+ as the convex hull of 1 vertex, 2 rays, 1 line, (0, -2, -1)),
2365
+ ((-1, 1, -1), A 3-dimensional polyhedron in QQ^3 defined
2366
+ as the convex hull of 1 vertex, 2 rays, 1 line, (1, 0, 2)),
2367
+ ((-1, -1, -1), A 3-dimensional polyhedron in QQ^3 defined
2368
+ as the convex hull of 1 vertex, 2 rays, 1 line, (-1, 0, 1))]
2369
+
2370
+ Let us check that the number of closed faces with a given
2371
+ dimension computed using ``self.closed_faces()`` equals the one
2372
+ computed using :meth:`face_vector`::
2373
+
2374
+ sage: def test_number(a):
2375
+ ....: Qx = PolynomialRing(QQ, 'x'); x = Qx.gen()
2376
+ ....: RHS = Qx.sum(vi * x ** i for i, vi in enumerate(a.face_vector()))
2377
+ ....: LHS = Qx.sum(x ** F[1].dim() for F in a.closed_faces())
2378
+ ....: return LHS == RHS
2379
+ sage: a = hyperplane_arrangements.Catalan(2)
2380
+ sage: test_number(a) # needs sage.combinat
2381
+ True
2382
+ sage: a = hyperplane_arrangements.Shi(3)
2383
+ sage: test_number(a) # long time # needs sage.combinat
2384
+ True
2385
+
2386
+ TESTS:
2387
+
2388
+ An empty border case::
2389
+
2390
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
2391
+ sage: a = H()
2392
+ sage: a.closed_faces()
2393
+ (((),
2394
+ A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 lines),)
2395
+ """
2396
+ R = self.base_ring()
2397
+ if R.characteristic() != 0:
2398
+ raise ValueError('base field must have characteristic zero')
2399
+ from sage.geometry.polyhedron.constructor import Polyhedron
2400
+ dim = self.dimension()
2401
+ hypes = self.hyperplanes()
2402
+ be = self._backend
2403
+ universe = Polyhedron(eqns=[[0] + [0] * dim], base_ring=R, backend=be)
2404
+ faces = [((), universe)]
2405
+ for k, hyperplane in enumerate(hypes):
2406
+ # Loop invariant:
2407
+ # ``faces == Hk.closed_faces()``, where ``Hk`` is the
2408
+ # hyperplane arrangement given by the first ``k`` hyperplanes
2409
+ # in the list ``hypes`` (that is, by ``hypes[:k]``).
2410
+ ieq = vector(R, hyperplane.dense_coefficient_list())
2411
+ zero_half = Polyhedron(eqns=[ieq], base_ring=R, backend=be)
2412
+ # ``zero_half`` is the hyperplane ``hyperplane`` itself
2413
+ # (viewed as a polyhedron).
2414
+ pos_half = Polyhedron(ieqs=[ieq], base_ring=R, backend=be)
2415
+ neg_half = Polyhedron(ieqs=[-ieq], base_ring=R, backend=be)
2416
+ subdivided = []
2417
+ for signs, face in faces:
2418
+ # So ``face`` is a face of the hyperplane arrangement
2419
+ # given by the first ``k`` hyperplanes in the list
2420
+ # ``hypes``, and ``signs`` is the corresponding
2421
+ # (length-``k``) sign vector.
2422
+ face_dim = face.dim()
2423
+ # Adding the intersection of ``face`` with ``hyperplane``:
2424
+ zero_part = face.intersection(zero_half)
2425
+ zero_part_dim = zero_part.dim()
2426
+ if zero_part_dim == face_dim:
2427
+ # If the intersection of ``face`` with ``hyperplane``
2428
+ # has the same dimension as ``face``, then this
2429
+ # intersection *is* ``face``, so we can continue
2430
+ # (without adding the other two intersections, since
2431
+ # those are empty):
2432
+ subdivided.append((signs + (0,), face))
2433
+ continue
2434
+ # If we are here, then ``face`` is not contained in
2435
+ # ``hyperplane``.
2436
+ if zero_part_dim >= 0:
2437
+ # Do not append ``zero_part`` yet! It might be
2438
+ # redundant (in the sense that some of its defining
2439
+ # inequalities are always equalities on it). Check for
2440
+ # this:
2441
+ zero_part_point = zero_part.representative_point()
2442
+ for l, testhype in enumerate(hypes[:k]):
2443
+ if signs[l] != 0:
2444
+ h = testhype.dense_coefficient_list()
2445
+ testval = R.sum(h[i+1] * gi for i, gi in enumerate(zero_part_point)) + h[0]
2446
+ if testval == 0:
2447
+ break
2448
+ else:
2449
+ # Now we know ``zero_part`` is not redundant.
2450
+ subdivided.append((signs + (0,), zero_part))
2451
+ # Adding the intersection of ``face`` with the positive
2452
+ # halfspace:
2453
+ pos_part = face.intersection(pos_half)
2454
+ pos_part_dim = pos_part.dim()
2455
+ if pos_part_dim == face_dim:
2456
+ # If this condition is not satisfied, then
2457
+ # ``pos_part`` is either ``zero_part`` or the empty
2458
+ # set; in either case we need not add it. Conversely,
2459
+ # if it is satisfied, then ``pos_part`` is not yet in
2460
+ # ``subdivided``, nor is it redundant.
2461
+ subdivided.append((signs + (1,), pos_part))
2462
+ neg_part = face.intersection(neg_half)
2463
+ neg_part_dim = neg_part.dim()
2464
+ if neg_part_dim == face_dim:
2465
+ # If this condition is not satisfied, then
2466
+ # ``neg_part`` is either ``zero_part`` or the empty
2467
+ # set; in either case we need not add it. Conversely,
2468
+ # if it is satisfied, then ``neg_part`` is not yet in
2469
+ # ``subdivided``, nor is it redundant.
2470
+ subdivided.append((signs + (-1,), neg_part))
2471
+ faces = subdivided
2472
+ if labelled:
2473
+ return tuple(faces)
2474
+ # Or, if we want a dictionary:
2475
+ # return {F[0]: F[1] for F in faces}
2476
+ return tuple(x[1] for x in faces)
2477
+
2478
+ def face_product(self, F, G, normalize=True):
2479
+ r"""
2480
+ Return the product `FG` in the face semigroup of ``self``, where
2481
+ `F` and `G` are two closed faces of ``self``.
2482
+
2483
+ The face semigroup of a hyperplane arrangement `\mathcal{A}` is
2484
+ defined as follows: As a set, it is the set of all open faces
2485
+ of ``self`` (see :meth:`closed_faces`). Its product is defined by
2486
+ the following rule: If `F` and `G` are two open faces of
2487
+ `\mathcal{A}`, then `FG` is an open face of `\mathcal{A}`, and
2488
+ for every hyperplane `H \in \mathcal{A}`, the open face `FG` lies
2489
+ on the same side of `H` as `F` unless `F \subseteq H`, in which
2490
+ case `FG` lies on the same side of `H` as `G`. Alternatively,
2491
+ `FG` can be defined as follows: If `f` and `g` are two points in
2492
+ `F` and `G`, respectively, then `FG` is the face that contains
2493
+ the point `(f + \varepsilon g) / (1 + \varepsilon)` for any
2494
+ sufficiently small positive `\varepsilon`.
2495
+
2496
+ In our implementation, the face semigroup consists of closed faces
2497
+ rather than open faces (thanks to the 1-to-1 correspondence
2498
+ between open faces and closed faces, this is not really a
2499
+ different semigroup); these closed faces are given as polyhedra.
2500
+
2501
+ The face semigroup of a hyperplane arrangement is always a
2502
+ left-regular band (i.e., a semigroup satisfying the identities
2503
+ `x^2 = x` and `xyx = xy`). When the arrangement is central, then
2504
+ this semigroup is a monoid. See [Br2000]_ (Appendix A in
2505
+ particular) for further properties.
2506
+
2507
+ INPUT:
2508
+
2509
+ - ``F``, ``G`` -- two faces of ``self`` (as polyhedra)
2510
+
2511
+ - ``normalize`` -- boolean (default: ``True``); if ``True``, then
2512
+ this method returns the precise instance of `FG` in the list
2513
+ returned by ``self.closed_faces()``, rather than creating a new
2514
+ instance
2515
+
2516
+ EXAMPLES::
2517
+
2518
+ sage: # needs sage.graphs
2519
+ sage: a = hyperplane_arrangements.braid(3)
2520
+ sage: a.hyperplanes()
2521
+ (Hyperplane 0*t0 + t1 - t2 + 0,
2522
+ Hyperplane t0 - t1 + 0*t2 + 0,
2523
+ Hyperplane t0 + 0*t1 - t2 + 0)
2524
+ sage: faces = {F0: F1 for F0, F1 in a.closed_faces()}
2525
+ sage: xGyEz = faces[(0, 1, 1)] # closed face x >= y = z
2526
+ sage: xGyEz.representative_point()
2527
+ (0, -1, -1)
2528
+ sage: xGyEz = faces[(0, 1, 1)] # closed face x >= y = z
2529
+ sage: xGyEz.representative_point()
2530
+ (0, -1, -1)
2531
+ sage: yGxGz = faces[(1, -1, 1)] # closed face y >= x >= z
2532
+ sage: xGyGz = faces[(1, 1, 1)] # closed face x >= y >= z
2533
+ sage: a.face_product(xGyEz, yGxGz) == xGyGz
2534
+ True
2535
+ sage: a.face_product(yGxGz, xGyEz) == yGxGz
2536
+ True
2537
+ sage: xEzGy = faces[(-1, 1, 0)] # closed face x = z >= y
2538
+ sage: xGzGy = faces[(-1, 1, 1)] # closed face x >= z >= y
2539
+ sage: a.face_product(xEzGy, yGxGz) == xGzGy
2540
+ True
2541
+ """
2542
+ f = F.representative_point()
2543
+ g = G.representative_point()
2544
+ n = len(f)
2545
+ R = self.base_ring()
2546
+ from sage.geometry.polyhedron.constructor import Polyhedron
2547
+ eqns = [[0] + [0] * n]
2548
+ ieqs = []
2549
+ signs = []
2550
+ for hyperplane in self.hyperplanes():
2551
+ # Decide which side of ``hyperplane`` our face ``FG`` will be
2552
+ # on.
2553
+ H = hyperplane.dense_coefficient_list()
2554
+ ieq = vector(R, H)
2555
+ x = R.sum(H[i+1] * fi for i, fi in enumerate(f)) + H[0]
2556
+ if x < 0:
2557
+ side = -1
2558
+ elif x > 0:
2559
+ side = 1
2560
+ else:
2561
+ x = R.sum(H[i+1] * gi for i, gi in enumerate(g)) + H[0]
2562
+ if x < 0:
2563
+ side = -1
2564
+ elif x > 0:
2565
+ side = 1
2566
+ else:
2567
+ side = 0
2568
+ signs.append(side)
2569
+ if side == 0:
2570
+ eqns.append(ieq)
2571
+ elif side == -1:
2572
+ ieqs.append(-ieq)
2573
+ else:
2574
+ ieqs.append(ieq)
2575
+ face = Polyhedron(eqns=eqns, ieqs=ieqs, base_ring=R, backend=self._backend)
2576
+ if not normalize:
2577
+ return face
2578
+ # Look for ``I`` in ``self.closed_faces()``:
2579
+ for I in self.closed_faces():
2580
+ if I[0] == tuple(signs):
2581
+ return I[1]
2582
+
2583
+ def face_semigroup_algebra(self, field=None, names='e'):
2584
+ r"""
2585
+ Return the face semigroup algebra of ``self``.
2586
+
2587
+ This is the semigroup algebra of the face semigroup of ``self``
2588
+ (see :meth:`face_product` for the definition of the semigroup).
2589
+
2590
+ Due to limitations of the current Sage codebase (e.g., semigroup
2591
+ algebras do not profit from the functionality of the
2592
+ :class:`FiniteDimensionalAlgebra` class), this is implemented not
2593
+ as a semigroup algebra, but as a
2594
+ :class:`FiniteDimensionalAlgebra`. The closed faces of ``self``
2595
+ (in the order in which the :meth:`closed_faces` method outputs
2596
+ them) are identified with the vectors `(0, 0, \ldots, 0, 1, 0, 0,
2597
+ \ldots, 0)` (with the `1` moving from left to right).
2598
+
2599
+ INPUT:
2600
+
2601
+ - ``field`` -- a field (default: `\QQ`), to be used as the
2602
+ base ring for the algebra (can also be a commutative ring, but
2603
+ then certain representation-theoretical methods might misbehave)
2604
+
2605
+ - ``names`` -- (default: ``'e'``) string; names for the basis
2606
+ elements of the algebra
2607
+
2608
+ .. TODO::
2609
+
2610
+ Also implement it as an actual semigroup algebra?
2611
+
2612
+ EXAMPLES::
2613
+
2614
+ sage: # needs sage.graphs
2615
+ sage: a = hyperplane_arrangements.braid(3)
2616
+ sage: [(i, F[0]) for i, F in enumerate(a.closed_faces())]
2617
+ [(0, (0, 0, 0)),
2618
+ (1, (0, 1, 1)),
2619
+ (2, (0, -1, -1)),
2620
+ (3, (1, 0, 1)),
2621
+ (4, (1, 1, 1)),
2622
+ (5, (1, -1, 0)),
2623
+ (6, (1, -1, 1)),
2624
+ (7, (1, -1, -1)),
2625
+ (8, (-1, 0, -1)),
2626
+ (9, (-1, 1, 0)),
2627
+ (10, (-1, 1, 1)),
2628
+ (11, (-1, 1, -1)),
2629
+ (12, (-1, -1, -1))]
2630
+ sage: U = a.face_semigroup_algebra(); U
2631
+ Finite-dimensional algebra of degree 13 over Rational Field
2632
+ sage: e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 = U.basis()
2633
+ sage: e0 * e1
2634
+ e1
2635
+ sage: e0 * e5
2636
+ e5
2637
+ sage: e5 * e0
2638
+ e5
2639
+ sage: e3 * e2
2640
+ e6
2641
+ sage: e7 * e12
2642
+ e7
2643
+ sage: e3 * e12
2644
+ e6
2645
+ sage: e4 * e8
2646
+ e4
2647
+ sage: e8 * e4
2648
+ e11
2649
+ sage: e8 * e1
2650
+ e11
2651
+ sage: e5 * e12
2652
+ e7
2653
+ sage: (e3 + 2*e4) * (e1 - e7)
2654
+ e4 - e6
2655
+
2656
+ sage: U3 = a.face_semigroup_algebra(field=GF(3)); U3 # needs sage.graphs sage.rings.finite_rings
2657
+ Finite-dimensional algebra of degree 13 over Finite Field of size 3
2658
+
2659
+ TESTS:
2660
+
2661
+ The ``names`` keyword works::
2662
+
2663
+ sage: # needs sage.graphs
2664
+ sage: a = hyperplane_arrangements.braid(3)
2665
+ sage: U = a.face_semigroup_algebra(names='x'); U
2666
+ Finite-dimensional algebra of degree 13 over Rational Field
2667
+ sage: e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 = U.basis()
2668
+ sage: e0 * e1
2669
+ x1
2670
+ """
2671
+ if field is None:
2672
+ from sage.rings.rational_field import QQ
2673
+ field = QQ
2674
+ zero = field.zero()
2675
+ one = field.one()
2676
+ from sage.matrix.matrix_space import MatrixSpace
2677
+ Fs = [F0 for F0, F1 in self.closed_faces()]
2678
+ # ``Fs`` is the list of the sign vectors of all closed faces of
2679
+ # ``self``.
2680
+ Fdict = {v: i for i, v in enumerate(Fs)}
2681
+ # ``Fdict`` is a dictionary whose keys are the sign vectors of the
2682
+ # closed faces of ``self``, and whose values are their positions
2683
+ # in the list ``Fs``.
2684
+ N = len(Fs)
2685
+ # Some hackery to generate a matrix quickly and without
2686
+ # unnecessary sanitization/ducktyping:
2687
+ MS = MatrixSpace(field, N, N)
2688
+ table = []
2689
+ for j, sj in enumerate(Fs):
2690
+ matrix_j = []
2691
+ for i, si in enumerate(Fs):
2692
+ row_i = [zero] * N
2693
+ sk = [sil if sil != 0 else sj[l]
2694
+ for l, sil in enumerate(si)]
2695
+ k = Fdict[tuple(sk)]
2696
+ row_i[k] = one
2697
+ matrix_j += row_i
2698
+ table.append(MS(matrix_j, coerce=False))
2699
+ from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra import FiniteDimensionalAlgebra as FDA
2700
+ return FDA(field, table, names=names, assume_associative=True)
2701
+
2702
+ def region_containing_point(self, p):
2703
+ r"""
2704
+ The region in the hyperplane arrangement containing a given point.
2705
+
2706
+ The base field must have characteristic zero.
2707
+
2708
+ INPUT:
2709
+
2710
+ - ``p`` -- point
2711
+
2712
+ OUTPUT:
2713
+
2714
+ A polyhedron. A :exc:`ValueError` is raised if the point is not
2715
+ interior to a region, that is, sits on a hyperplane.
2716
+
2717
+ EXAMPLES::
2718
+
2719
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
2720
+ sage: A = H([(1,0), 0], [(0,1), 1], [(0,1), -1], [(1,-1), 0], [(1,1), 0])
2721
+ sage: A.region_containing_point([1,2])
2722
+ A 2-dimensional polyhedron in QQ^2 defined
2723
+ as the convex hull of 2 vertices and 2 rays
2724
+
2725
+ TESTS::
2726
+
2727
+ sage: A = H([(1,1),0], [(2,3),-1], [(4,5),3])
2728
+ sage: B = A.change_ring(FiniteField(7))
2729
+ sage: B.region_containing_point((1,2))
2730
+ Traceback (most recent call last):
2731
+ ...
2732
+ ValueError: base field must have characteristic zero
2733
+
2734
+ sage: A = H([(1,1),0], [(2,3),-1], [(4,5),3])
2735
+ sage: A.region_containing_point((1,-1))
2736
+ Traceback (most recent call last):
2737
+ ...
2738
+ ValueError: point sits on a hyperplane
2739
+ """
2740
+ if self.base_ring().characteristic() != 0:
2741
+ raise ValueError('base field must have characteristic zero')
2742
+ sign_vector = self.sign_vector(p)
2743
+ ieqs = []
2744
+ for i, hyperplane in enumerate(self):
2745
+ sign = sign_vector[i]
2746
+ if sign == 1:
2747
+ ieqs.append(hyperplane)
2748
+ elif sign == -1:
2749
+ ieqs.append(-hyperplane)
2750
+ else:
2751
+ assert sign == 0
2752
+ raise ValueError('point sits on a hyperplane')
2753
+ return self._make_region(ieqs)
2754
+
2755
+ @cached_method
2756
+ def _bounded_region_indices(self):
2757
+ r"""
2758
+ Return the relatively bounded regions.
2759
+
2760
+ OUTPUT:
2761
+
2762
+ Tuple of integers. The positions of the relatively bounded
2763
+ regions in :meth:`regions`.
2764
+
2765
+ EXAMPLES::
2766
+
2767
+ sage: a = hyperplane_arrangements.semiorder(3)
2768
+ sage: a._bounded_region_indices()
2769
+ (2, 7, 8, 9, 10, 11, 16)
2770
+ """
2771
+ from sage.geometry.polyhedron.constructor import Polyhedron
2772
+ normal = Polyhedron(vertices=[[0]*self.dimension()],
2773
+ lines=[hyperplane.normal() for hyperplane in self],
2774
+ backend=self._backend)
2775
+ if normal.dim() == 0:
2776
+ transverse = lambda poly: poly
2777
+ else:
2778
+ transverse = lambda poly: poly.intersection(normal)
2779
+ return tuple(i for i, region in enumerate(self.regions())
2780
+ if transverse(region).is_compact())
2781
+
2782
+ def bounded_regions(self):
2783
+ r"""
2784
+ Return the relatively bounded regions of the arrangement.
2785
+
2786
+ A region is relatively bounded if its intersection with the space
2787
+ spanned by the normals to the hyperplanes is bounded. This is the
2788
+ same as being bounded in the case that the hyperplane arrangement
2789
+ is essential. It is assumed that the arrangement is defined over
2790
+ the rationals.
2791
+
2792
+ OUTPUT:
2793
+
2794
+ Tuple of polyhedra. The relatively bounded regions of the
2795
+ arrangement.
2796
+
2797
+ .. SEEALSO::
2798
+
2799
+ :meth:`unbounded_regions`
2800
+
2801
+ EXAMPLES::
2802
+
2803
+ sage: # needs sage.combinat
2804
+ sage: A = hyperplane_arrangements.semiorder(3)
2805
+ sage: A.bounded_regions()
2806
+ (A 3-dimensional polyhedron in QQ^3 defined
2807
+ as the convex hull of 3 vertices and 1 line,
2808
+ A 3-dimensional polyhedron in QQ^3 defined
2809
+ as the convex hull of 3 vertices and 1 line,
2810
+ A 3-dimensional polyhedron in QQ^3 defined
2811
+ as the convex hull of 3 vertices and 1 line,
2812
+ A 3-dimensional polyhedron in QQ^3 defined
2813
+ as the convex hull of 6 vertices and 1 line,
2814
+ A 3-dimensional polyhedron in QQ^3 defined
2815
+ as the convex hull of 3 vertices and 1 line,
2816
+ A 3-dimensional polyhedron in QQ^3 defined
2817
+ as the convex hull of 3 vertices and 1 line,
2818
+ A 3-dimensional polyhedron in QQ^3 defined
2819
+ as the convex hull of 3 vertices and 1 line)
2820
+ sage: A.bounded_regions()[0].is_compact() # the regions are only *relatively* bounded
2821
+ False
2822
+ sage: A.is_essential()
2823
+ False
2824
+ """
2825
+ return tuple(self.regions()[i] for i in self._bounded_region_indices())
2826
+
2827
+ def unbounded_regions(self):
2828
+ r"""
2829
+ Return the relatively bounded regions of the arrangement.
2830
+
2831
+ OUTPUT:
2832
+
2833
+ Tuple of polyhedra. The regions of the arrangement that are not
2834
+ relatively bounded. It is assumed that the arrangement is
2835
+ defined over the rationals.
2836
+
2837
+ .. SEEALSO::
2838
+
2839
+ :meth:`bounded_regions`
2840
+
2841
+ EXAMPLES::
2842
+
2843
+ sage: # needs sage.combinat
2844
+ sage: A = hyperplane_arrangements.semiorder(3)
2845
+ sage: B = A.essentialization()
2846
+ sage: B.n_regions() - B.n_bounded_regions()
2847
+ 12
2848
+ sage: B.unbounded_regions()
2849
+ (A 2-dimensional polyhedron in QQ^2 defined
2850
+ as the convex hull of 3 vertices and 1 ray,
2851
+ A 2-dimensional polyhedron in QQ^2 defined
2852
+ as the convex hull of 3 vertices and 1 ray,
2853
+ A 2-dimensional polyhedron in QQ^2 defined
2854
+ as the convex hull of 1 vertex and 2 rays,
2855
+ A 2-dimensional polyhedron in QQ^2 defined
2856
+ as the convex hull of 3 vertices and 1 ray,
2857
+ A 2-dimensional polyhedron in QQ^2 defined
2858
+ as the convex hull of 1 vertex and 2 rays,
2859
+ A 2-dimensional polyhedron in QQ^2 defined
2860
+ as the convex hull of 3 vertices and 1 ray,
2861
+ A 2-dimensional polyhedron in QQ^2 defined
2862
+ as the convex hull of 1 vertex and 2 rays,
2863
+ A 2-dimensional polyhedron in QQ^2 defined
2864
+ as the convex hull of 3 vertices and 1 ray,
2865
+ A 2-dimensional polyhedron in QQ^2 defined
2866
+ as the convex hull of 1 vertex and 2 rays,
2867
+ A 2-dimensional polyhedron in QQ^2 defined
2868
+ as the convex hull of 3 vertices and 1 ray,
2869
+ A 2-dimensional polyhedron in QQ^2 defined
2870
+ as the convex hull of 1 vertex and 2 rays,
2871
+ A 2-dimensional polyhedron in QQ^2 defined
2872
+ as the convex hull of 1 vertex and 2 rays)
2873
+ """
2874
+ s = set(range(self.n_regions())).difference(set(self._bounded_region_indices()))
2875
+ return tuple(self.regions()[i] for i in s)
2876
+
2877
+ @cached_method
2878
+ def whitney_data(self):
2879
+ r"""
2880
+ Return the Whitney numbers.
2881
+
2882
+ .. SEEALSO::
2883
+
2884
+ :meth:`whitney_number`,
2885
+ :meth:`doubly_indexed_whitney_number`
2886
+
2887
+ OUTPUT:
2888
+
2889
+ A pair of integer matrices. The two matrices are the
2890
+ doubly-indexed Whitney numbers of the first or second kind,
2891
+ respectively. The `i,j`-th entry is the `i,j`-th
2892
+ doubly-indexed Whitney number.
2893
+
2894
+ EXAMPLES::
2895
+
2896
+ sage: # needs sage.combinat
2897
+ sage: A = hyperplane_arrangements.Shi(3)
2898
+ sage: A.whitney_data()
2899
+ (
2900
+ [ 1 -6 9] [ 1 6 6]
2901
+ [ 0 6 -15] [ 0 6 15]
2902
+ [ 0 0 6], [ 0 0 6]
2903
+ )
2904
+ """
2905
+ p = self.intersection_poset()
2906
+ r = p.rank_function()
2907
+ top = r(p.maximal_elements()[0])
2908
+ from sage.matrix.constructor import zero_matrix
2909
+ m1 = zero_matrix(ZZ, top+1, top+1)
2910
+ m2 = zero_matrix(ZZ, top+1, top+1)
2911
+ for i, j in p.relations_iterator():
2912
+ m1[r(i), r(j)] += p.moebius_function(i, j)
2913
+ m2[r(i), r(j)] += 1
2914
+ m1.set_immutable()
2915
+ m2.set_immutable()
2916
+ return (m1, m2)
2917
+
2918
+ def doubly_indexed_whitney_number(self, i, j, kind=1):
2919
+ r"""
2920
+ Return the `i,j`-th doubly-indexed Whitney number.
2921
+
2922
+ If ``kind=1``, this number is obtained by adding the Möbius function
2923
+ values `mu(x,y)` over all `x, y` in the intersection poset with
2924
+ `\mathrm{rank}(x) = i` and `\mathrm{rank}(y) = j`.
2925
+
2926
+ If `kind=2`, this number is the number of elements `x,y` in the
2927
+ intersection poset such that `x \leq y` with ranks `i` and `j`,
2928
+ respectively.
2929
+
2930
+ INPUT:
2931
+
2932
+ - ``i``, ``j`` -- integers
2933
+
2934
+ - ``kind`` -- (default: 1) 1 or 2
2935
+
2936
+ OUTPUT:
2937
+
2938
+ Integer. The `(i,j)`-th entry of the ``kind`` Whitney number.
2939
+
2940
+ .. SEEALSO::
2941
+
2942
+ :meth:`whitney_number`,
2943
+ :meth:`whitney_data`
2944
+
2945
+ EXAMPLES::
2946
+
2947
+ sage: # needs sage.combinat
2948
+ sage: A = hyperplane_arrangements.Shi(3)
2949
+ sage: A.doubly_indexed_whitney_number(0, 2)
2950
+ 9
2951
+ sage: A.whitney_number(2)
2952
+ 9
2953
+ sage: A.doubly_indexed_whitney_number(1, 2)
2954
+ -15
2955
+
2956
+ REFERENCES:
2957
+
2958
+ - [GZ1983]_
2959
+ """
2960
+ if 0 <= i and j <= self.dimension():
2961
+ if kind == 1:
2962
+ return self.whitney_data()[0][i, j]
2963
+ elif kind == 2:
2964
+ return self.whitney_data()[1][i, j]
2965
+ raise ValueError('argument out of range')
2966
+
2967
+ def whitney_number(self, k, kind=1):
2968
+ r"""
2969
+ Return the ``k``-th Whitney number.
2970
+
2971
+ If ``kind=1``, this number is obtained by summing the Möbius function
2972
+ values `mu(0, x)` over all `x` in the intersection poset with
2973
+ `\mathrm{rank}(x) = k`.
2974
+
2975
+ If ``kind=2``, this number is the number of elements `x, y` in the
2976
+ intersection poset such that `x \leq y` with ranks `i` and `j`,
2977
+ respectively.
2978
+
2979
+ See [GZ1983]_ for more details.
2980
+
2981
+ INPUT:
2982
+
2983
+ - ``k`` -- integer
2984
+
2985
+ - ``kind`` -- 1 or 2 (default: 1)
2986
+
2987
+ OUTPUT:
2988
+
2989
+ Integer. The ``k``-th Whitney number.
2990
+
2991
+ .. SEEALSO::
2992
+
2993
+ :meth:`doubly_indexed_whitney_number`
2994
+ :meth:`whitney_data`
2995
+
2996
+ EXAMPLES::
2997
+
2998
+ sage: # needs sage.combinat
2999
+ sage: A = hyperplane_arrangements.Shi(3)
3000
+ sage: A.whitney_number(0)
3001
+ 1
3002
+ sage: A.whitney_number(1)
3003
+ -6
3004
+ sage: A.whitney_number(2)
3005
+ 9
3006
+ sage: A.characteristic_polynomial()
3007
+ x^3 - 6*x^2 + 9*x
3008
+ sage: A.whitney_number(1, kind=2)
3009
+ 6
3010
+ sage: p = A.intersection_poset()
3011
+ sage: r = p.rank_function()
3012
+ sage: len([i for i in p if r(i) == 1])
3013
+ 6
3014
+ """
3015
+ if k >= 0 and k <= self.dimension():
3016
+ if kind == 1:
3017
+ return self.whitney_data()[0][0, k]
3018
+ elif kind == 2:
3019
+ return self.whitney_data()[1][0, k]
3020
+ raise ValueError('argument out of range')
3021
+
3022
+ def is_separating_hyperplane(self, region1, region2, hyperplane):
3023
+ r"""
3024
+ Test whether the ``hyperplane`` separates the given regions.
3025
+
3026
+ INPUT:
3027
+
3028
+ - ``region1``, ``region2`` -- polyhedra or list/tuple/iterable
3029
+ of coordinates which are regions of the arrangement or an interior
3030
+ point of a region
3031
+
3032
+ - ``hyperplane`` -- a hyperplane
3033
+
3034
+ OUTPUT: boolean; whether the hyperplane ``hyperplane`` separate the
3035
+ given regions
3036
+
3037
+ EXAMPLES::
3038
+
3039
+ sage: A.<x,y> = hyperplane_arrangements.coordinate(2)
3040
+ sage: A.is_separating_hyperplane([1,1], [2,1], y)
3041
+ False
3042
+ sage: A.is_separating_hyperplane([1,1], [-1,1], x)
3043
+ True
3044
+ sage: r = A.region_containing_point([1,1])
3045
+ sage: s = A.region_containing_point([-1,1])
3046
+ sage: A.is_separating_hyperplane(r, s, x)
3047
+ True
3048
+ """
3049
+ if self.base_ring().characteristic() != 0:
3050
+ raise ValueError('requires characteristic zero')
3051
+ try:
3052
+ p1 = region1.representative_point()
3053
+ except AttributeError:
3054
+ p1 = list(region1)
3055
+ try:
3056
+ p2 = region2.representative_point()
3057
+ except AttributeError:
3058
+ p2 = list(region2)
3059
+ from sage.functions.generalized import sign
3060
+ s = sign(hyperplane(p1)) * sign(hyperplane(p2))
3061
+ if s < 0:
3062
+ return True
3063
+ if s > 0:
3064
+ return False
3065
+ raise ValueError('point lies on hyperplane')
3066
+
3067
+ def distance_between_regions(self, region1, region2):
3068
+ r"""
3069
+ Return the number of hyperplanes separating the two regions.
3070
+
3071
+ INPUT:
3072
+
3073
+ - ``region1``, ``region2`` -- regions of the arrangement or
3074
+ representative points of regions
3075
+
3076
+ OUTPUT: integer; the number of hyperplanes separating the two regions
3077
+
3078
+ EXAMPLES::
3079
+
3080
+ sage: c = hyperplane_arrangements.coordinate(2)
3081
+ sage: r = c.region_containing_point([-1, -1])
3082
+ sage: s = c.region_containing_point([1, 1])
3083
+ sage: c.distance_between_regions(r, s)
3084
+ 2
3085
+ sage: c.distance_between_regions(s, s)
3086
+ 0
3087
+ """
3088
+ count = sum(1 for hyperplane in self
3089
+ if self.is_separating_hyperplane(region1, region2, hyperplane))
3090
+ return ZZ(count)
3091
+
3092
+ def distance_enumerator(self, base_region):
3093
+ r"""
3094
+ Return the generating function for the number of hyperplanes
3095
+ at given distance.
3096
+
3097
+ INPUT:
3098
+
3099
+ - ``base_region`` -- region of arrangement or point in region
3100
+
3101
+ OUTPUT:
3102
+
3103
+ A polynomial `f(x)` for which the coefficient of `x^i` is the
3104
+ number of hyperplanes of distance `i` from ``base_region``,
3105
+ i.e., the number of hyperplanes separated by `i` hyperplanes
3106
+ from ``base_region``.
3107
+
3108
+ EXAMPLES::
3109
+
3110
+ sage: c = hyperplane_arrangements.coordinate(3)
3111
+ sage: c.distance_enumerator(c.region_containing_point([1,1,1]))
3112
+ x^3 + 3*x^2 + 3*x + 1
3113
+ """
3114
+ d = [self.distance_between_regions(r, base_region) for r in self.regions()]
3115
+ d = [d.count(i) for i in range(max(d)+1)]
3116
+ from sage.rings.polynomial.polynomial_ring import polygen
3117
+ x = polygen(QQ, 'x')
3118
+ return sum([d[i]*x**i for i in range(len(d))])
3119
+
3120
+ @cached_method
3121
+ def varchenko_matrix(self, names='h'):
3122
+ r"""
3123
+ Return the Varchenko matrix of the arrangement.
3124
+
3125
+ Let `H_1, \ldots, H_s` and `R_1, \ldots, R_t` denote the hyperplanes
3126
+ and regions, respectively, of the arrangement. Let `S =
3127
+ \QQ[h_1, \ldots, h_s]`, a polynomial ring with indeterminate `h_i`
3128
+ corresponding to hyperplane `H_i`. The Varchenko matrix is
3129
+ the `t \times t` matrix with `i,j`-th entry the product of
3130
+ those `h_k` such that `H_k` separates `R_i` and `R_j`.
3131
+
3132
+ INPUT:
3133
+
3134
+ - ``names`` -- string or list/tuple/iterable of strings. The
3135
+ variable names for the polynomial ring `S`
3136
+
3137
+ OUTPUT: the Varchenko matrix
3138
+
3139
+ EXAMPLES::
3140
+
3141
+ sage: a = hyperplane_arrangements.coordinate(3)
3142
+ sage: v = a.varchenko_matrix(); v
3143
+ [ 1 h2 h1 h1*h2 h0*h1*h2 h0*h1 h0*h2 h0]
3144
+ [ h2 1 h1*h2 h1 h0*h1 h0*h1*h2 h0 h0*h2]
3145
+ [ h1 h1*h2 1 h2 h0*h2 h0 h0*h1*h2 h0*h1]
3146
+ [ h1*h2 h1 h2 1 h0 h0*h2 h0*h1 h0*h1*h2]
3147
+ [h0*h1*h2 h0*h1 h0*h2 h0 1 h2 h1 h1*h2]
3148
+ [ h0*h1 h0*h1*h2 h0 h0*h2 h2 1 h1*h2 h1]
3149
+ [ h0*h2 h0 h0*h1*h2 h0*h1 h1 h1*h2 1 h2]
3150
+ [ h0 h0*h2 h0*h1 h0*h1*h2 h1*h2 h1 h2 1]
3151
+ sage: factor(det(v))
3152
+ (h2 - 1)^4 * (h2 + 1)^4 * (h1 - 1)^4 * (h1 + 1)^4 * (h0 - 1)^4 * (h0 + 1)^4
3153
+
3154
+ TESTS:
3155
+
3156
+ Verify that :issue:`36490` is fixed::
3157
+
3158
+ sage: hyperplane_arrangements.coordinate(1).varchenko_matrix()
3159
+ [1 h]
3160
+ [h 1]
3161
+ """
3162
+ from sage.matrix.constructor import identity_matrix
3163
+ from sage.misc.misc_c import prod
3164
+ k = len(self)
3165
+ R = PolynomialRing(QQ, names, k)
3166
+ h = R.gens()
3167
+ region = self.regions()
3168
+ n = len(region)
3169
+ v = identity_matrix(R, n, n)
3170
+ for i in range(n):
3171
+ for j in range(i + 1, n):
3172
+ t = prod(h[p] for p in range(k) if
3173
+ self.is_separating_hyperplane(region[i], region[j], self[p]))
3174
+ v[i, j] = v[j, i] = t
3175
+ v.set_immutable()
3176
+ return v
3177
+
3178
+ @cached_method
3179
+ def matroid(self):
3180
+ r"""
3181
+ Return the matroid associated to ``self``.
3182
+
3183
+ Let `A` denote a central hyperplane arrangement and `n_H` the
3184
+ normal vector of some hyperplane `H \in A`. We define a matroid
3185
+ `M_A` as the linear matroid spanned by `\{ n_H | H \in A \}`.
3186
+ The matroid `M_A` is such that the lattice of flats of `M` is
3187
+ isomorphic to the intersection lattice of `A`
3188
+ (Proposition 3.6 in [Sta2007]_).
3189
+
3190
+ EXAMPLES::
3191
+
3192
+ sage: P.<x,y,z> = HyperplaneArrangements(QQ)
3193
+ sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z)
3194
+ sage: M = A.matroid(); M
3195
+ Linear matroid of rank 3 on 7 elements represented over the Rational Field
3196
+
3197
+ We check the lattice of flats is isomorphic to the
3198
+ intersection lattice::
3199
+
3200
+ sage: f = sum([list(M.flats(i)) for i in range(M.rank() + 1)], [])
3201
+ sage: PF = Poset([f, lambda x, y: x < y]) # needs sage.combinat
3202
+ sage: PF.is_isomorphic(A.intersection_poset()) # needs sage.combinat
3203
+ True
3204
+ """
3205
+ if not self.is_central():
3206
+ raise ValueError("the hyperplane arrangement must be central")
3207
+ norms = [p.normal() for p in self]
3208
+ from sage.matroids.constructor import Matroid
3209
+ return Matroid(matrix=matrix(norms).transpose())
3210
+
3211
+ def orlik_solomon_algebra(self, base_ring=None, ordering=None, **kwds):
3212
+ """
3213
+ Return the Orlik-Solomon algebra of ``self``.
3214
+
3215
+ INPUT:
3216
+
3217
+ - ``base_ring`` -- (default: the base field of ``self``) the ring
3218
+ over which the Orlik-Solomon algebra will be defined
3219
+ - ``ordering`` -- (optional) an ordering of the ground set
3220
+
3221
+ EXAMPLES::
3222
+
3223
+ sage: P.<x,y,z> = HyperplaneArrangements(QQ)
3224
+ sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z)
3225
+ sage: A.orlik_solomon_algebra()
3226
+ Orlik-Solomon algebra of Linear matroid of rank 3 on 7 elements
3227
+ represented over the Rational Field
3228
+ sage: A.orlik_solomon_algebra(base_ring=ZZ)
3229
+ Orlik-Solomon algebra of Linear matroid of rank 3 on 7 elements
3230
+ represented over the Rational Field
3231
+ """
3232
+ if base_ring is None:
3233
+ base_ring = self.base_ring()
3234
+ return self.matroid().orlik_solomon_algebra(base_ring, ordering, **kwds)
3235
+
3236
+ def orlik_terao_algebra(self, base_ring=None, ordering=None, **kwds):
3237
+ """
3238
+ Return the Orlik-Terao algebra of ``self``.
3239
+
3240
+ INPUT:
3241
+
3242
+ - ``base_ring`` -- (default: the base field of ``self``) the ring
3243
+ over which the Orlik-Terao algebra will be defined
3244
+ - ``ordering`` -- (optional) an ordering of the ground set
3245
+
3246
+ EXAMPLES::
3247
+
3248
+ sage: P.<x,y,z> = HyperplaneArrangements(QQ)
3249
+ sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z)
3250
+ sage: A.orlik_terao_algebra()
3251
+ Orlik-Terao algebra of Linear matroid of rank 3 on 7 elements
3252
+ represented over the Rational Field over Rational Field
3253
+ sage: A.orlik_terao_algebra(base_ring=QQ['t'])
3254
+ Orlik-Terao algebra of Linear matroid of rank 3 on 7 elements
3255
+ represented over the Rational Field
3256
+ over Univariate Polynomial Ring in t over Rational Field
3257
+ """
3258
+ if base_ring is None:
3259
+ base_ring = self.base_ring()
3260
+ return self.matroid().orlik_terao_algebra(base_ring, ordering, **kwds)
3261
+
3262
+ @cached_method
3263
+ def minimal_generated_number(self):
3264
+ r"""
3265
+ Return the minimum `k` such that ``self`` is `k`-generated.
3266
+
3267
+ Let `A` be a central hyperplane arrangement. Let `W_k` denote
3268
+ the solution space of the linear system corresponding to the
3269
+ linear dependencies among the hyperplanes of `A` of length at
3270
+ most `k`. We say `A` is `k`-*generated* if
3271
+ `\dim W_k = \operatorname{rank} A`.
3272
+
3273
+ Equivalently this says all dependencies forming the Orlik-Terao
3274
+ ideal are generated by at most `k` hyperplanes.
3275
+
3276
+ EXAMPLES:
3277
+
3278
+ We construct Example 2.2 from [Yuz1993]_::
3279
+
3280
+ sage: P.<x,y,z> = HyperplaneArrangements(QQ)
3281
+ sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z)
3282
+ sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z)
3283
+ sage: A.minimal_generated_number()
3284
+ 3
3285
+ sage: B.minimal_generated_number()
3286
+ 4
3287
+
3288
+ TESTS:
3289
+
3290
+ Check that :issue:`26705` is fixed::
3291
+
3292
+ sage: # needs sage.combinat sage.groups
3293
+ sage: w = WeylGroup(['A', 4]).from_reduced_word([3, 4, 2, 1])
3294
+ sage: I = w.inversion_arrangement()
3295
+ sage: I
3296
+ Arrangement <a4 | a1 | a1 + a2 | a1 + a2 + a3 + a4>
3297
+ sage: I.minimal_generated_number()
3298
+ 0
3299
+ sage: I.is_formal()
3300
+ True
3301
+ """
3302
+ V = VectorSpace(self.base_ring(), self.dimension())
3303
+ W = VectorSpace(self.base_ring(), self.n_hyperplanes())
3304
+ r = self.rank()
3305
+ M = self.matroid()
3306
+ if len(M.groundset()) == r: # there are no circuits
3307
+ return ZZ.zero()
3308
+ norms = M.representation().columns()
3309
+ circuits = M.circuits()
3310
+ for i in range(2, self.n_hyperplanes()):
3311
+ sol = []
3312
+ for d in circuits:
3313
+ if len(d) > i:
3314
+ continue
3315
+ d = list(d)
3316
+ dep = V.linear_dependence([norms[j] for j in d])
3317
+ w = W.zero().list()
3318
+ for j, k in enumerate(d):
3319
+ w[k] = dep[0][j]
3320
+ sol.append(w)
3321
+ mat = matrix(sol)
3322
+ if mat.right_kernel().dimension() == r:
3323
+ return i
3324
+ return self.n_hyperplanes()
3325
+
3326
+ def is_formal(self):
3327
+ """
3328
+ Return if ``self`` is formal.
3329
+
3330
+ A hyperplane arrangement is *formal* if it is 3-generated [Yuz1993]_,
3331
+ where `k`-generated is defined in :meth:`minimal_generated_number`.
3332
+
3333
+ EXAMPLES::
3334
+
3335
+ sage: P.<x,y,z> = HyperplaneArrangements(QQ)
3336
+ sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z)
3337
+ sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z)
3338
+ sage: A.is_formal()
3339
+ True
3340
+ sage: B.is_formal()
3341
+ False
3342
+ """
3343
+ return self.minimal_generated_number() <= 3
3344
+
3345
+ def defining_polynomial(self):
3346
+ r"""
3347
+ Return the defining polynomial of ``A``.
3348
+
3349
+ Let `A = (H_i)_i` be a hyperplane arrangement in a vector space `V`
3350
+ corresponding to the null spaces of `\alpha_{H_i} \in V^*`. Then
3351
+ the *defining polynomial* of `A` is given by
3352
+
3353
+ .. MATH::
3354
+
3355
+ Q(A) = \prod_i \alpha_{H_i} \in S(V^*).
3356
+
3357
+ EXAMPLES::
3358
+
3359
+ sage: H.<x,y,z> = HyperplaneArrangements(QQ)
3360
+ sage: A = H([2*x + y - z, -x - 2*y + z])
3361
+ sage: p = A.defining_polynomial(); p
3362
+ -2*x^2 - 5*x*y - 2*y^2 + 3*x*z + 3*y*z - z^2
3363
+ sage: p.factor()
3364
+ (-1) * (x + 2*y - z) * (2*x + y - z)
3365
+ """
3366
+ S = self.parent().ambient_space().symmetric_space()
3367
+ return S.prod(H.to_symmetric_space() for H in self)
3368
+
3369
+ @cached_method
3370
+ def derivation_module_free_chain(self):
3371
+ r"""
3372
+ Return a free chain for the derivation module if one
3373
+ exists, otherwise return ``None``.
3374
+
3375
+ .. SEEALSO::
3376
+
3377
+ :meth:`is_free`
3378
+
3379
+ EXAMPLES::
3380
+
3381
+ sage: # needs sage.combinat sage.groups
3382
+ sage: W = WeylGroup(['A',3], prefix='s')
3383
+ sage: A = W.long_element().inversion_arrangement()
3384
+ sage: for M in A.derivation_module_free_chain(): print("%s\n"%M)
3385
+ [ 1 0 0]
3386
+ [ 0 1 0]
3387
+ [ 0 0 a3]
3388
+ <BLANKLINE>
3389
+ [ 1 0 0]
3390
+ [ 0 0 1]
3391
+ [ 0 a2 0]
3392
+ <BLANKLINE>
3393
+ [ 1 0 0]
3394
+ [ 0 -1 -1]
3395
+ [ 0 a2 -a3]
3396
+ <BLANKLINE>
3397
+ [ 0 1 0]
3398
+ [ 0 0 1]
3399
+ [a1 0 0]
3400
+ <BLANKLINE>
3401
+ [ 1 0 -1]
3402
+ [a3 -1 0]
3403
+ [a1 0 a2]
3404
+ <BLANKLINE>
3405
+ [ 1 0 0]
3406
+ [ a3 -1 -1]
3407
+ [ 0 a1 -a2 - a3]
3408
+ <BLANKLINE>
3409
+ """
3410
+ if not self.is_central():
3411
+ raise NotImplementedError("only implemented for central arrangements")
3412
+ from sage.geometry.hyperplane_arrangement.check_freeness import construct_free_chain
3413
+ return construct_free_chain(self)
3414
+
3415
+ @cached_method(key=lambda self, a: None)
3416
+ def is_free(self, algorithm='singular'):
3417
+ r"""
3418
+ Return if ``self`` is free.
3419
+
3420
+ A hyperplane arrangement `A` is free if the module
3421
+ of derivations `\operatorname{Der}(A)` is a free `S`-module,
3422
+ where `S` is the corresponding symmetric space.
3423
+
3424
+ INPUT:
3425
+
3426
+ - ``algorithm`` -- (default: ``'singular'``) can be one of
3427
+ the following:
3428
+
3429
+ * ``'singular'`` -- use Singular's minimal free resolution
3430
+ * ``'BC'`` -- use the algorithm given by Barakat and Cuntz
3431
+ in [BC2012]_ (much slower than using Singular)
3432
+
3433
+ ALGORITHM:
3434
+
3435
+ .. RUBRIC:: singular
3436
+
3437
+ Check that the minimal free resolution has length at most 2
3438
+ by using Singular.
3439
+
3440
+ .. RUBRIC:: BC
3441
+
3442
+ This implementation follows [BC2012]_ by constructing a chain
3443
+ of free modules
3444
+
3445
+ .. MATH::
3446
+
3447
+ D(A) = D(A_n) < D(A_{n-1}) < \cdots < D(A_1) < D(A_0)
3448
+
3449
+ corresponding to some ordering of the arrangements `A_0 \subset
3450
+ A_1 \subset \cdots \subset A_{n-1} \subset A_n = A`. Such a
3451
+ chain is found by using a backtracking algorithm.
3452
+
3453
+ EXAMPLES:
3454
+
3455
+ For type `A` arrangements, chordality is equivalent to freeness.
3456
+ We verify that in type `A_3`::
3457
+
3458
+ sage: W = WeylGroup(['A', 3], prefix='s') # needs sage.combinat sage.groups
3459
+ sage: for x in W: # needs sage.combinat sage.groups
3460
+ ....: A = x.inversion_arrangement()
3461
+ ....: assert A.matroid().is_chordal() == A.is_free()
3462
+
3463
+ TESTS:
3464
+
3465
+ We check that the algorithms agree::
3466
+
3467
+ sage: W = WeylGroup(['B', 3], prefix='s') # needs sage.combinat sage.groups
3468
+ sage: for x in W: # long time # needs sage.combinat sage.groups
3469
+ ....: A = x.inversion_arrangement()
3470
+ ....: assert (A.is_free(algorithm='BC')
3471
+ ....: == A.is_free(algorithm='singular'))
3472
+ """
3473
+ if not self.is_central():
3474
+ raise NotImplementedError("only implemented for central arrangements")
3475
+ if algorithm == "singular":
3476
+ # TODO: Implement this using libSingular
3477
+ mres = self.defining_polynomial().jacobian_ideal()._singular_().mres(0)
3478
+ return len(mres) <= 2
3479
+ elif algorithm == "BC":
3480
+ return self.derivation_module_free_chain() is not None
3481
+ else:
3482
+ raise ValueError("invalid algorithm")
3483
+
3484
+ def derivation_module_basis(self, algorithm='singular'):
3485
+ """
3486
+ Return a basis for the derivation module of ``self`` if
3487
+ one exists, otherwise return ``None``.
3488
+
3489
+ .. SEEALSO::
3490
+
3491
+ :meth:`derivation_module_free_chain`, :meth:`is_free`
3492
+
3493
+ INPUT:
3494
+
3495
+ - ``algorithm`` -- (default: ``'singular'``) can be one of
3496
+ the following:
3497
+
3498
+ * ``'singular'`` -- use Singular's minimal free resolution
3499
+ * ``'BC'`` -- use the algorithm given by Barakat and Cuntz
3500
+ in [BC2012]_ (much slower than using Singular)
3501
+
3502
+ OUTPUT:
3503
+
3504
+ A basis for the derivation module (over `S`, the
3505
+ :meth:`symmetric space
3506
+ <sage.geometry.hyperplane_arrangement.hyperplane.AmbientVectorSpace.symmetric_space>`)
3507
+ as vectors of a free module over `S`.
3508
+
3509
+ ALGORITHM:
3510
+
3511
+ .. RUBRIC:: Singular
3512
+
3513
+ This gets the reduced syzygy module of the Jacobian ideal of
3514
+ the defining polynomial `f` of ``self``. It then checks Saito's
3515
+ criterion that the determinant of the basis matrix is a scalar
3516
+ multiple of `f`. If the basis matrix is not square or it fails
3517
+ Saito's criterion, then we check if the arrangement is free.
3518
+ If it is free, then we fall back to the Barakat-Cuntz algorithm.
3519
+
3520
+ .. RUBRIC:: BC
3521
+
3522
+ Return the product of the derivation module free chain matrices.
3523
+ See Section 6 of [BC2012]_.
3524
+
3525
+ EXAMPLES::
3526
+
3527
+ sage: # needs sage.combinat sage.groups
3528
+ sage: W = WeylGroup(['A', 2], prefix='s')
3529
+ sage: A = W.long_element().inversion_arrangement()
3530
+ sage: A.derivation_module_basis()
3531
+ [(a1, a2), (0, a1*a2 + a2^2)]
3532
+
3533
+ TESTS:
3534
+
3535
+ We check the algorithms produce a basis with the same exponents::
3536
+
3537
+ sage: W = WeylGroup(['A', 2], prefix='s') # needs sage.combinat sage.groups
3538
+ sage: def exponents(B):
3539
+ ....: return sorted([max(x.degree() for x in b) for b in B])
3540
+ sage: for x in W: # long time # needs sage.combinat sage.groups
3541
+ ....: A = x.inversion_arrangement()
3542
+ ....: B = A.derivation_module_basis(algorithm='singular')
3543
+ ....: Bp = A.derivation_module_basis(algorithm='BC')
3544
+ ....: if B is None:
3545
+ ....: assert Bp is None
3546
+ ....: else:
3547
+ ....: assert exponents(B) == exponents(Bp)
3548
+ """
3549
+ alg = algorithm # prevent possible changes to a global variable
3550
+ if alg == "singular":
3551
+ # import sage.libs.singular.function_factory
3552
+ # syz = sage.libs.singular.function_factory.ff.syz
3553
+ f = self.defining_polynomial()
3554
+ I = f + f.jacobian_ideal()
3555
+ IS = I._singular_()
3556
+ ISS = IS.syz()
3557
+ MSTD = ISS.mstd()
3558
+ basis = MSTD[2]._sage_().transpose().submatrix(0, 1)
3559
+ try:
3560
+ det = basis.det()
3561
+ # Check using Saito's criterion
3562
+ if det / f in f.parent().base_ring() and not det.is_zero():
3563
+ return basis.rows()
3564
+ except ValueError: # Non-square matrix or det = 0
3565
+ pass
3566
+ # Check if it is free
3567
+ if not self.is_free(algorithm=alg):
3568
+ return None
3569
+ # The syzygy module did not give a basis, but since it is free,
3570
+ # fallback to the Barakat-Cuntz method
3571
+ alg = "BC"
3572
+ if alg == "BC":
3573
+ C = self.derivation_module_free_chain()
3574
+ if C is not None:
3575
+ if not C: # C is an empty list
3576
+ S = self.parent().ambient_space().symmetric_space()
3577
+ return matrix.identity(S, self.dimension()).rows()
3578
+ from sage.misc.misc_c import prod
3579
+ return prod(reversed(C)).rows()
3580
+ return None
3581
+ else:
3582
+ raise ValueError("invalid algorithm")
3583
+
3584
+
3585
+ class HyperplaneArrangements(Parent, UniqueRepresentation):
3586
+ """
3587
+ Hyperplane arrangements.
3588
+
3589
+ For more information on hyperplane arrangements, see
3590
+ :mod:`sage.geometry.hyperplane_arrangement.arrangement`.
3591
+
3592
+ INPUT:
3593
+
3594
+ - ``base_ring`` -- ring; the base ring
3595
+
3596
+ - ``names`` -- tuple of strings; the variable names
3597
+
3598
+ EXAMPLES::
3599
+
3600
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
3601
+ sage: x
3602
+ Hyperplane x + 0*y + 0
3603
+ sage: x + y
3604
+ Hyperplane x + y + 0
3605
+ sage: H(x, y, x-1, y-1)
3606
+ Arrangement <y - 1 | y | x - 1 | x>
3607
+ """
3608
+ Element = HyperplaneArrangementElement
3609
+
3610
+ def __init__(self, base_ring, names=tuple()):
3611
+ """
3612
+ Initialize ``self``.
3613
+
3614
+ TESTS::
3615
+
3616
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
3617
+ sage: K = HyperplaneArrangements(QQ, names=('x', 'y'))
3618
+ sage: H is K
3619
+ True
3620
+ sage: type(K)
3621
+ <class 'sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangements_with_category'>
3622
+ sage: K.change_ring(RR).gen(0) # needs sage.rings.real_mpfr
3623
+ Hyperplane 1.00000000000000*x + 0.000000000000000*y + 0.000000000000000
3624
+
3625
+ TESTS::
3626
+
3627
+ sage: H.<x,y> = HyperplaneArrangements(QQ)
3628
+ sage: TestSuite(H).run()
3629
+ sage: K = HyperplaneArrangements(QQ)
3630
+ sage: TestSuite(K).run() # needs sage.rings.real_interval_field
3631
+ """
3632
+ from sage.categories.sets_cat import Sets
3633
+ from sage.rings.ring import _Fields
3634
+ if base_ring not in _Fields:
3635
+ raise ValueError('base ring must be a field')
3636
+ super().__init__(category=Sets())
3637
+ self._base_ring = base_ring
3638
+ self._names = names
3639
+
3640
+ def base_ring(self):
3641
+ """
3642
+ Return the base ring.
3643
+
3644
+ OUTPUT: the base ring of the hyperplane arrangement
3645
+
3646
+ EXAMPLES::
3647
+
3648
+ sage: L.<x,y> = HyperplaneArrangements(QQ)
3649
+ sage: L.base_ring()
3650
+ Rational Field
3651
+ """
3652
+ return self._base_ring
3653
+
3654
+ def change_ring(self, base_ring):
3655
+ """
3656
+ Return hyperplane arrangements over a different base ring.
3657
+
3658
+ INPUT:
3659
+
3660
+ - ``base_ring`` -- a ring; the new base ring
3661
+
3662
+ OUTPUT:
3663
+
3664
+ A new :class:`HyperplaneArrangements` instance over the new
3665
+ base ring.
3666
+
3667
+ EXAMPLES::
3668
+
3669
+ sage: L.<x,y> = HyperplaneArrangements(QQ)
3670
+ sage: L.gen(0)
3671
+ Hyperplane x + 0*y + 0
3672
+ sage: L.change_ring(RR).gen(0)
3673
+ Hyperplane 1.00000000000000*x + 0.000000000000000*y + 0.000000000000000
3674
+
3675
+ TESTS::
3676
+
3677
+ sage: L.change_ring(QQ) is L
3678
+ True
3679
+ """
3680
+ return HyperplaneArrangements(base_ring, names=self._names)
3681
+
3682
+ @cached_method
3683
+ def ambient_space(self):
3684
+ """
3685
+ Return the ambient space.
3686
+
3687
+ The ambient space is the parent of hyperplanes. That is, new
3688
+ hyperplanes are always constructed internally from the ambient
3689
+ space instance.
3690
+
3691
+ EXAMPLES::
3692
+
3693
+ sage: L.<x, y> = HyperplaneArrangements(QQ)
3694
+ sage: L.ambient_space()([(1,0), 0])
3695
+ Hyperplane x + 0*y + 0
3696
+ sage: L.ambient_space()([(1,0), 0]) == x
3697
+ True
3698
+ """
3699
+ return AmbientVectorSpace(self.base_ring(), self._names)
3700
+
3701
+ def _repr_(self):
3702
+ """
3703
+ Return a string representation.
3704
+
3705
+ OUTPUT: string
3706
+
3707
+ EXAMPLES::
3708
+
3709
+ sage: L.<x, y> = HyperplaneArrangements(QQ); L
3710
+ Hyperplane arrangements in 2-dimensional linear space over Rational Field with coordinates x, y
3711
+ """
3712
+ return 'Hyperplane arrangements in {0}'.format(self.ambient_space())
3713
+
3714
+ def _element_constructor_(self, *args, **kwds):
3715
+ """
3716
+ Construct an element of ``self``.
3717
+
3718
+ INPUT:
3719
+
3720
+ - ``*args`` -- positional arguments, each defining a
3721
+ hyperplane; alternatively, a single polytope or a single
3722
+ hyperplane arrangement
3723
+
3724
+ - ``signed`` -- boolean (default: ``True``); whether to
3725
+ preserve signs of hyperplane equations
3726
+
3727
+ - ``warn_duplicates`` -- boolean (default: ``False``);
3728
+ whether to issue a warning if duplicate hyperplanes were
3729
+ passed -- note that duplicate hyperplanes are always removed,
3730
+ whether or not there is a warning shown
3731
+
3732
+ - ``check`` -- boolean (default: ``True``); whether to
3733
+ perform argument checking
3734
+
3735
+ EXAMPLES::
3736
+
3737
+ sage: L.<x, y> = HyperplaneArrangements(QQ)
3738
+ sage: L._element_constructor_(x, y)
3739
+ Arrangement <y | x>
3740
+ sage: L._element_constructor_([x, y])
3741
+ Arrangement <y | x>
3742
+ sage: L._element_constructor_([0, 1, 0], [0, 0, 1])
3743
+ Arrangement <y | x>
3744
+ sage: L._element_constructor_([[0, 1, 0], [0, 0, 1]])
3745
+ Arrangement <y | x>
3746
+
3747
+ sage: L._element_constructor_(polytopes.hypercube(2))
3748
+ Arrangement <-x + 1 | -y + 1 | y + 1 | x + 1>
3749
+
3750
+ sage: L(x, x, warn_duplicates=True)
3751
+ doctest:...: UserWarning: Input contained 2 hyperplanes, but only 1 are distinct.
3752
+ Arrangement <x>
3753
+ sage: L(-x, x + y - 1, signed=False)
3754
+ Arrangement <-x - y + 1 | x>
3755
+
3756
+ TESTS::
3757
+
3758
+ sage: L()
3759
+ Empty hyperplane arrangement of dimension 2
3760
+ sage: L(0) # zero is equivalent to no argument, Issue #8648
3761
+ Empty hyperplane arrangement of dimension 2
3762
+ sage: L(0*x) # degenerate hyperplane is NOT allowed
3763
+ Traceback (most recent call last):
3764
+ ...
3765
+ ValueError: linear expression must be non-constant to define a hyperplane
3766
+ sage: L(0*x, y) # ditto
3767
+ Traceback (most recent call last):
3768
+ ...
3769
+ ValueError: linear expression must be non-constant to define a hyperplane
3770
+ """
3771
+ if len(args) == 1:
3772
+ arg = args[0]
3773
+ if isinstance(arg, HyperplaneArrangementElement) and args[0].parent() is self:
3774
+ # optimization if argument is already a hyperplane arrangement
3775
+ return arg
3776
+ if arg == 0 and not isinstance(arg, Hyperplane):
3777
+ # zero = neutral element under addition = the empty hyperplane arrangement
3778
+ args = []
3779
+ # process keyword arguments
3780
+ not_char2 = (self.base_ring().characteristic() != 2)
3781
+ signed = kwds.pop('signed', not_char2)
3782
+ warn_duplicates = kwds.pop('warn_duplicates', False)
3783
+ check = kwds.pop('check', True)
3784
+ backend = kwds.pop('backend', None)
3785
+ if len(kwds) > 0:
3786
+ raise ValueError('unknown keyword argument')
3787
+ # process positional arguments
3788
+ AA = self.ambient_space()
3789
+ try:
3790
+ hyperplanes = [AA(_) for _ in args]
3791
+ except (TypeError, ValueError, AttributeError):
3792
+ if len(args) > 1:
3793
+ raise
3794
+ arg = args[0]
3795
+ if hasattr(arg, 'Hrepresentation'):
3796
+ hyperplanes = [AA(h) for h in arg.Hrepresentation()]
3797
+ else:
3798
+ hyperplanes = [AA(_) for _ in arg]
3799
+ hyperplanes = [h.primitive(signed) for h in hyperplanes]
3800
+ n = len(hyperplanes)
3801
+ hyperplanes = set(hyperplanes)
3802
+ if warn_duplicates and n != len(hyperplanes):
3803
+ from warnings import warn
3804
+ warn('Input contained {0} hyperplanes, but only {1} are distinct.'.format(n, len(hyperplanes)))
3805
+ # argument checking (optional but recommended)
3806
+ if check:
3807
+ if signed and not not_char2:
3808
+ raise ValueError('cannot be signed in characteristic 2')
3809
+ for h in hyperplanes:
3810
+ if h.A() == 0:
3811
+ raise ValueError('linear expression must be non-constant to define a hyperplane')
3812
+ if not_char2 and -h in hyperplanes:
3813
+ raise ValueError('arrangement cannot simultaneously have h and -h as hyperplane')
3814
+ return self.element_class(self, tuple(sorted(hyperplanes)), backend=backend)
3815
+
3816
+ @cached_method
3817
+ def ngens(self):
3818
+ """
3819
+ Return the number of linear variables.
3820
+
3821
+ OUTPUT: integer
3822
+
3823
+ EXAMPLES::
3824
+
3825
+ sage: L.<x, y, z> = HyperplaneArrangements(QQ); L
3826
+ Hyperplane arrangements in 3-dimensional linear space
3827
+ over Rational Field with coordinates x, y, z
3828
+ sage: L.ngens()
3829
+ 3
3830
+ """
3831
+ return len(self._names)
3832
+
3833
+ @cached_method
3834
+ def gens(self) -> tuple:
3835
+ """
3836
+ Return the coordinate hyperplanes.
3837
+
3838
+ OUTPUT: a tuple of linear expressions, one for each linear variable
3839
+
3840
+ EXAMPLES::
3841
+
3842
+ sage: L = HyperplaneArrangements(QQ, ('x', 'y', 'z'))
3843
+ sage: L.gens()
3844
+ (Hyperplane x + 0*y + 0*z + 0,
3845
+ Hyperplane 0*x + y + 0*z + 0,
3846
+ Hyperplane 0*x + 0*y + z + 0)
3847
+ """
3848
+ return self.ambient_space().gens()
3849
+
3850
+ def gen(self, i):
3851
+ """
3852
+ Return the `i`-th coordinate hyperplane.
3853
+
3854
+ INPUT:
3855
+
3856
+ - ``i`` -- integer
3857
+
3858
+ OUTPUT: a linear expression
3859
+
3860
+ EXAMPLES::
3861
+
3862
+ sage: L.<x, y, z> = HyperplaneArrangements(QQ); L
3863
+ Hyperplane arrangements in
3864
+ 3-dimensional linear space over Rational Field with coordinates x, y, z
3865
+ sage: L.gen(0)
3866
+ Hyperplane x + 0*y + 0*z + 0
3867
+ """
3868
+ return self.gens()[i]
3869
+
3870
+ def _coerce_map_from_(self, P):
3871
+ """
3872
+ Return whether there is a coercion.
3873
+
3874
+ TESTS::
3875
+
3876
+ sage: # needs sage.rings.real_mpfr
3877
+ sage: L.<x> = HyperplaneArrangements(QQ); L
3878
+ Hyperplane arrangements in 1-dimensional linear space over Rational Field with coordinate x
3879
+ sage: M.<y> = HyperplaneArrangements(RR); M
3880
+ Hyperplane arrangements in 1-dimensional linear space over Real Field with 53 bits of precision with coordinate y
3881
+ sage: L.coerce_map_from(ZZ)
3882
+ Coercion map:
3883
+ From: Integer Ring
3884
+ To: Hyperplane arrangements in 1-dimensional linear space over Rational Field with coordinate x
3885
+ sage: M.coerce_map_from(L)
3886
+ Coercion map:
3887
+ From: Hyperplane arrangements in 1-dimensional linear space over Rational Field with coordinate x
3888
+ To: Hyperplane arrangements in 1-dimensional linear space over Real Field with 53 bits of precision with coordinate y
3889
+ sage: L.coerce_map_from(M)
3890
+ """
3891
+ if self.ambient_space().has_coerce_map_from(P):
3892
+ return True
3893
+ if isinstance(P, HyperplaneArrangements):
3894
+ return self.base_ring().has_coerce_map_from(P.base_ring())
3895
+ return super()._coerce_map_from_(P)