Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/geometry/hyperplane_arrangement/arrangement.py
8817 views
1
r"""
2
Hyperplane Arrangements
3
4
Before talking about hyperplane arrangements, let us start with
5
individual hyperplanes. This package uses certain linear expressions
6
to represent hyperplanes, that is, a linear expression `3x + 3y - 5z - 7`
7
stands for the hyperplane with the equation `x + 3y - 5z = 7`. To create it
8
in Sage, you first have to create a :class:`HyperplaneArrangements`
9
object to define the variables `x`, `y`, `z`::
10
11
sage: H.<x,y,z> = HyperplaneArrangements(QQ)
12
sage: h = 3*x + 2*y - 5*z - 7; h
13
Hyperplane 3*x + 2*y - 5*z - 7
14
sage: h.normal()
15
(3, 2, -5)
16
sage: h.constant_term()
17
-7
18
19
The individual hyperplanes behave like the linear expression with
20
regard to addition and scalar multiplication, which is why you can do
21
linear combinations of the coordinates::
22
23
sage: -2*h
24
Hyperplane -6*x - 4*y + 10*z + 14
25
sage: x, y, z
26
(Hyperplane x + 0*y + 0*z + 0, Hyperplane 0*x + y + 0*z + 0, Hyperplane 0*x + 0*y + z + 0)
27
28
See :mod:`sage.geometry.hyperplane_arrangement.hyperplane` for more
29
functionality of the individual hyperplanes.
30
31
Arrangements
32
------------
33
34
There are several ways to create hyperplane arrangements:
35
36
Notation (i): by passing individual hyperplanes to the
37
:class:`HyperplaneArrangements` object::
38
39
sage: H.<x,y> = HyperplaneArrangements(QQ)
40
sage: box = x | y | x-1 | y-1; box
41
Arrangement <y - 1 | y | x - 1 | x>
42
sage: box == H(x, y, x-1, y-1) # alternative syntax
43
True
44
45
Notation (ii): by passing anything that defines a hyperplane, for
46
example a coefficient vector and constant term::
47
48
sage: H = HyperplaneArrangements(QQ, ('x', 'y'))
49
sage: triangle = H([(1, 0), 0], [(0, 1), 0], [(1,1), -1]); triangle
50
Arrangement <y | x | x + y - 1>
51
52
sage: H.inject_variables()
53
Defining x, y
54
sage: triangle == x | y | x+y-1
55
True
56
57
The default base field is `\QQ`, the rational numbers. Finite fields are also
58
supported::
59
60
sage: H.<x,y,z> = HyperplaneArrangements(GF(5))
61
sage: a = H([(1,2,3), 4], [(5,6,7), 8]); a
62
Arrangement <y + 2*z + 3 | x + 2*y + 3*z + 4>
63
64
Notation (iii): a list or tuple of hyperplanes::
65
66
sage: H.<x,y,z> = HyperplaneArrangements(GF(5))
67
sage: k = [x+i for i in range(4)]; k
68
[Hyperplane x + 0*y + 0*z + 0, Hyperplane x + 0*y + 0*z + 1,
69
Hyperplane x + 0*y + 0*z + 2, Hyperplane x + 0*y + 0*z + 3]
70
sage: H(k)
71
Arrangement <x | x + 1 | x + 2 | x + 3>
72
73
Notation (iv): using the library of arrangements::
74
75
sage: hyperplane_arrangements.braid(4)
76
Arrangement of 6 hyperplanes of dimension 4 and rank 3
77
sage: hyperplane_arrangements.semiorder(3)
78
Arrangement of 6 hyperplanes of dimension 3 and rank 2
79
sage: hyperplane_arrangements.graphical(graphs.PetersenGraph())
80
Arrangement of 15 hyperplanes of dimension 10 and rank 9
81
sage: hyperplane_arrangements.Ish(5)
82
Arrangement of 20 hyperplanes of dimension 5 and rank 4
83
84
Notation (v): from the bounding hyperplanes of a polyhedron::
85
86
sage: a = polytopes.n_cube(3).hyperplane_arrangement(); a
87
Arrangement of 6 hyperplanes of dimension 3 and rank 3
88
sage: a.n_regions()
89
27
90
91
New arrangements from old::
92
93
sage: a = hyperplane_arrangements.braid(3)
94
sage: b = a.add_hyperplane([4, 1, 2, 3])
95
sage: b
96
Arrangement <t1 - t2 | t0 - t1 | t0 - t2 | t0 + 2*t1 + 3*t2 + 4>
97
sage: c = b.deletion([4, 1, 2, 3])
98
sage: a == c
99
True
100
101
sage: a = hyperplane_arrangements.braid(3)
102
sage: b = a.union(hyperplane_arrangements.semiorder(3))
103
sage: b == a | hyperplane_arrangements.semiorder(3) # alternate syntax
104
True
105
sage: b == hyperplane_arrangements.Catalan(3)
106
True
107
108
sage: a
109
Arrangement <t1 - t2 | t0 - t1 | t0 - t2>
110
sage: a = hyperplane_arrangements.coordinate(4)
111
sage: h = a.hyperplanes()[0]
112
sage: b = a.restriction(h)
113
sage: b == hyperplane_arrangements.coordinate(3)
114
True
115
116
A hyperplane arrangement is *essential* is the normals to its
117
hyperplane span the ambient space. Otherwise, it is *inessential*.
118
The essentialization is formed by intersecting the hyperplanes by this
119
normal space (actually, it is a bit more complicated over finite
120
fields)::
121
122
sage: a = hyperplane_arrangements.braid(4); a
123
Arrangement of 6 hyperplanes of dimension 4 and rank 3
124
sage: a.is_essential()
125
False
126
sage: a.rank() < a.dimension() # double-check
127
True
128
sage: a.essentialization()
129
Arrangement of 6 hyperplanes of dimension 3 and rank 3
130
131
The connected components of the complement of the hyperplanes of an arrangement
132
in `\RR^n` are called the *regions* of the arrangement::
133
134
sage: a = hyperplane_arrangements.semiorder(3)
135
sage: b = a.essentialization(); b
136
Arrangement of 6 hyperplanes of dimension 2 and rank 2
137
sage: b.n_regions()
138
19
139
sage: b.regions()
140
(A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 6 vertices,
141
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
142
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
143
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
144
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
145
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
146
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
147
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
148
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
149
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
150
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
151
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
152
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
153
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
154
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
155
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
156
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
157
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
158
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays)
159
sage: b.bounded_regions()
160
(A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 6 vertices,
161
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
162
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
163
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices,
164
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 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
sage: b.n_bounded_regions()
168
7
169
sage: a.unbounded_regions()
170
(A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
171
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
172
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
173
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
174
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
175
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
176
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
177
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
178
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
179
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line,
180
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices, 1 ray, 1 line,
181
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex, 2 rays, 1 line)
182
183
The distance between regions is defined as the number of hyperplanes
184
separating them. For example::
185
186
sage: r1 = b.regions()[0]
187
sage: r2 = b.regions()[1]
188
sage: b.distance_between_regions(r1, r2)
189
1
190
sage: [hyp for hyp in b if b.is_separating_hyperplane(r1, r2, hyp)]
191
[Hyperplane 2*t1 + t2 + 1]
192
sage: b.distance_enumerator(r1) # generating function for distances from r1
193
6*x^3 + 6*x^2 + 6*x + 1
194
195
.. NOTE::
196
197
*bounded region* really mean *relatively bounded* here. A region is
198
relatively bounded if its intersection with space spanned by the normals
199
of the hyperplanes in the arrangement is bounded.
200
201
The intersection poset of a hyperplane arrangement is the collection
202
of all nonempty intersections of hyperplanes in the arrangement,
203
ordered by reverse inclusion. It includes the ambient space of the
204
arrangement (as the intersection over the empty set)::
205
206
sage: a = hyperplane_arrangements.braid(3)
207
sage: p = a.intersection_poset()
208
sage: p.is_ranked()
209
True
210
sage: p.order_polytope()
211
A 5-dimensional polyhedron in QQ^5 defined as the convex hull of 10 vertices
212
213
The characteristic polynomial is a basic invariant of a hyperplane
214
arrangement. It is defined as
215
216
.. MATH::
217
218
\chi(x) := \sum_{w\in P} \mu(w) x^{dim(w)}
219
220
where the sum is `P` is the
221
:meth:`~HyperplaneArrangementElement.intersection_poset` of the
222
arrangement and `\mu` is the Moebius function of `P`::
223
224
sage: a = hyperplane_arrangements.semiorder(5)
225
sage: a.characteristic_polynomial() # long time (about a second on Core i7)
226
x^5 - 20*x^4 + 180*x^3 - 790*x^2 + 1380*x
227
sage: a.poincare_polynomial() # long time
228
1380*x^4 + 790*x^3 + 180*x^2 + 20*x + 1
229
sage: a.n_regions() # long time
230
2371
231
sage: charpoly = a.characteristic_polynomial() # long time
232
sage: charpoly(-1) # long time
233
-2371
234
sage: a.n_bounded_regions() # long time
235
751
236
sage: charpoly(1) # long time
237
751
238
239
For finer invariants derived from the intersection poset, see
240
:meth:`~HyperplaneArrangementElement.whitney_number` and
241
:meth:`~HyperplaneArrangementElement.doubly_indexed_whitney_number`.
242
243
Miscellaneous methods (see documentation for an explanation)::
244
245
sage: a = hyperplane_arrangements.semiorder(3)
246
sage: a.has_good_reduction(5)
247
True
248
sage: b = a.change_ring(GF(5))
249
sage: pa = a.intersection_poset()
250
sage: pb = b.intersection_poset()
251
sage: pa.is_isomorphic(pb)
252
True
253
sage: a.face_vector()
254
(0, 12, 30, 19)
255
sage: a.face_vector()
256
(0, 12, 30, 19)
257
sage: a.is_central()
258
False
259
sage: a.is_linear()
260
False
261
sage: a.sign_vector((1,1,1))
262
(-1, 1, -1, 1, -1, 1)
263
sage: a.varchenko_matrix()
264
[ 1 h2 h2*h4 h2*h3 h2*h3*h4 h2*h3*h4*h5]
265
[ h2 1 h4 h3 h3*h4 h3*h4*h5]
266
[ h2*h4 h4 1 h3*h4 h3 h3*h5]
267
[ h2*h3 h3 h3*h4 1 h4 h4*h5]
268
[ h2*h3*h4 h3*h4 h3 h4 1 h5]
269
[h2*h3*h4*h5 h3*h4*h5 h3*h5 h4*h5 h5 1]
270
271
There are extensive methods for visualizing hyperplane arrangements in
272
low dimensions. See :meth:`~HyperplaneArrangementElement.plot` for
273
details.
274
275
TESTS::
276
277
sage: H.<x,y> = HyperplaneArrangements(QQ)
278
sage: h = H([(1, 106), 106266], [(83, 101), 157866], [(111, 110), 186150], [(453, 221), 532686],
279
....: [(407, 237), 516882], [(55, 32), 75620], [(221, 114), 289346], [(452, 115), 474217],
280
....: [(406, 131), 453521], [(28, 9), 32446], [(287, 19), 271774], [(241, 35), 244022],
281
....: [(231, 1), 210984], [(185, 17), 181508], [(23, -8), 16609])
282
sage: h.n_regions()
283
85
284
285
sage: H()
286
Empty hyperplane arrangement of dimension 2
287
288
sage: Zero = HyperplaneArrangements(QQ)
289
sage: Zero
290
Hyperplane arrangements in 0-dimensional linear space over Rational Field with coordinate
291
sage: Zero()
292
Empty hyperplane arrangement of dimension 0
293
sage: Zero.an_element()
294
Empty hyperplane arrangement of dimension 0
295
296
AUTHORS:
297
298
- David Perkinson (2013-06): initial version
299
300
- Qiaoyu Yang (2013-07)
301
302
- Kuai Yu (2013-07)
303
304
- Volker Braun (2013-10): Better Sage integration, major code refactoring.
305
306
This module implements hyperplane arrangements defined over the
307
rationals or over finite fields. The original motivation was to make
308
a companion to Richard Stanley's notes [RS]_ on hyperplane
309
arrangements.
310
311
REFERENCES:
312
313
.. [RS]
314
Stanley, Richard: *Hyperplane Arrangements*,
315
Geometric Combinatorics (E. Miller, V. Reiner, and B. Sturmfels, eds.),
316
IAS/Park City Mathematics Series, vol. 13, American Mathematical Society,
317
Providence, RI, 2007, pp. 389-496.
318
"""
319
320
#*****************************************************************************
321
# Copyright (C) 2013 David Perkinson <[email protected]>
322
# Volker Braun <[email protected]>
323
#
324
# Distributed under the terms of the GNU General Public License (GPL)
325
# as published by the Free Software Foundation; either version 2 of
326
# the License, or (at your option) any later version.
327
# http://www.gnu.org/licenses/
328
#*****************************************************************************
329
330
# Possible extensions for hyperplane_arrangement.py:
331
# - the big face lattice
332
# - Orlik-Solomon algebras
333
# - create ties with the Sage matroid methods
334
# - hyperplane arrangements over other fields
335
336
from sage.structure.parent import Parent
337
from sage.structure.element import Element
338
from sage.structure.unique_representation import UniqueRepresentation
339
from sage.rings.all import QQ, ZZ
340
from sage.misc.cachefunc import cached_method
341
from sage.misc.misc import uniq
342
from sage.matrix.constructor import matrix, vector
343
344
from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane
345
346
347
348
class HyperplaneArrangementElement(Element):
349
"""
350
An element in a hyperplane arrangement.
351
352
.. WARNING::
353
354
You should never create
355
:class:`HyperplaneArrangementElement` instances directly,
356
always use the parent.
357
"""
358
def __init__(self, parent, hyperplanes, check=True):
359
"""
360
Construct a hyperplane arrangement.
361
362
INPUT:
363
364
- ``parent`` -- the parent :class:`HyperplaneArrangements`
365
366
- ``hyperplanes`` -- a tuple of hyperplanes
367
368
- ``check`` -- boolean (optional; default ``True``); whether
369
to check input
370
371
EXAMPLES::
372
373
sage: H.<x,y> = HyperplaneArrangements(QQ)
374
sage: elt = H(x, y); elt
375
Arrangement <y | x>
376
sage: TestSuite(elt).run()
377
"""
378
super(HyperplaneArrangementElement, self).__init__(parent)
379
self._hyperplanes = hyperplanes
380
if check:
381
if not isinstance(hyperplanes, tuple):
382
raise ValueError("the hyperplanes must be given as a tuple")
383
if not all(isinstance(h, Hyperplane) for h in hyperplanes):
384
raise ValueError("not all elements are hyperplanes")
385
if not all(h.parent() is self.parent().ambient_space() for h in hyperplanes):
386
raise ValueError("not all hyperplanes are in the ambient space")
387
388
def _first_ngens(self, n):
389
"""
390
Workaround to support the construction with names.
391
392
INPUT/OUTPUT:
393
394
See :meth:`HyperplaneArrangements._first_ngens`.
395
396
EXAMPLES::
397
398
sage: a.<x,y,z> = hyperplane_arrangements.braid(3) # indirect doctest
399
sage: (x, y) == a._first_ngens(2)
400
True
401
"""
402
return self.parent()._first_ngens(n)
403
404
def __getitem__(self, i):
405
"""
406
Return the `i`-th hyperplane.
407
408
INPUT:
409
410
- ``i`` -- integer
411
412
OUTPUT:
413
414
The `i`-th hyperplane.
415
416
EXAMPLES::
417
418
sage: H.<x,y> = HyperplaneArrangements(QQ)
419
sage: h = x|y; h
420
Arrangement <y | x>
421
sage: h[0]
422
Hyperplane 0*x + y + 0
423
sage: h[1]
424
Hyperplane x + 0*y + 0
425
"""
426
return self._hyperplanes[i]
427
428
def n_hyperplanes(self):
429
r"""
430
Return the number of hyperplanes in the arrangement.
431
432
OUTPUT:
433
434
An integer.
435
436
EXAMPLES::
437
438
sage: H.<x,y> = HyperplaneArrangements(QQ)
439
sage: A = H([1,1,0], [2,3,-1], [4,5,3])
440
sage: A.n_hyperplanes()
441
3
442
sage: len(A) # equivalent
443
3
444
"""
445
return len(self._hyperplanes)
446
447
__len__ = n_hyperplanes
448
449
def hyperplanes(self):
450
r"""
451
Return the number of hyperplanes in the arrangement.
452
453
OUTPUT:
454
455
An integer.
456
457
EXAMPLES::
458
459
sage: H.<x,y> = HyperplaneArrangements(QQ)
460
sage: A = H([1,1,0], [2,3,-1], [4,5,3])
461
sage: A.hyperplanes()
462
(Hyperplane x + 0*y + 1, Hyperplane 3*x - y + 2, Hyperplane 5*x + 3*y + 4)
463
464
Note that the hyperplanes can be indexed as if they were a list::
465
466
sage: A[0]
467
Hyperplane x + 0*y + 1
468
"""
469
return self._hyperplanes
470
471
def _repr_(self):
472
r"""
473
String representation for a hyperplane arrangement.
474
475
OUTPUT:
476
477
A string.
478
479
EXAMPLES::
480
481
sage: H.<x,y> = HyperplaneArrangements(QQ)
482
sage: H(x, y, x-1, y-1)
483
Arrangement <y - 1 | y | x - 1 | x>
484
sage: x | y | x - 1 | y - 1 | x + y | x - y
485
Arrangement of 6 hyperplanes of dimension 2 and rank 2
486
sage: H()
487
Empty hyperplane arrangement of dimension 2
488
"""
489
if len(self) == 0:
490
return 'Empty hyperplane arrangement of dimension {0}'.format(self.dimension())
491
elif len(self) < 5:
492
hyperplanes = ' | '.join(h._repr_linear(include_zero=False) for h in self._hyperplanes)
493
return 'Arrangement <{0}>'.format(hyperplanes)
494
return 'Arrangement of {0} hyperplanes of dimension {1} and rank {2}'.format(
495
len(self), self.dimension(), self.rank())
496
497
def dimension(self):
498
"""
499
Return the ambient space dimension of the arrangement.
500
501
OUTPUT:
502
503
An integer.
504
505
EXAMPLES::
506
507
sage: H.<x,y> = HyperplaneArrangements(QQ)
508
sage: (x | x-1 | x+1).dimension()
509
2
510
sage: H(x).dimension()
511
2
512
"""
513
return self.parent().ngens()
514
515
def rank(self):
516
"""
517
Return the rank.
518
519
OUTPUT:
520
521
The dimension of the span of the normals to the
522
hyperplanes in the arrangement.
523
524
EXAMPLES::
525
526
sage: H.<x,y,z> = HyperplaneArrangements(QQ)
527
sage: A = H([[0, 1, 2, 3],[-3, 4, 5, 6]])
528
sage: A.dimension()
529
3
530
sage: A.rank()
531
2
532
533
sage: B = hyperplane_arrangements.braid(3)
534
sage: B.hyperplanes()
535
(Hyperplane 0*t0 + t1 - t2 + 0,
536
Hyperplane t0 - t1 + 0*t2 + 0,
537
Hyperplane t0 + 0*t1 - t2 + 0)
538
sage: B.dimension()
539
3
540
sage: B.rank()
541
2
542
543
sage: p = polytopes.n_simplex(5)
544
sage: H = p.hyperplane_arrangement()
545
sage: H.rank()
546
5
547
"""
548
R = self.parent().base_ring()
549
normals = [h.normal() for h in self]
550
return matrix(R, normals).rank()
551
552
def __cmp__(self, other):
553
"""
554
Compare two hyperplane arrangements.
555
556
EXAMPLES::
557
558
sage: H.<x,y,z> = HyperplaneArrangements(QQ)
559
sage: H(x) == H(y)
560
False
561
sage: H(x) == 0
562
False
563
"""
564
assert (type(self) is type(other)) and (self.parent() is other.parent()) # guaranteed by framework
565
return cmp(self._hyperplanes, other._hyperplanes)
566
567
def union(self, other):
568
r"""
569
The union of ``self`` with ``other``.
570
571
INPUT:
572
573
- ``other`` -- a hyperplane arrangement or something that can
574
be converted into a hyperplane arrangement
575
576
OUTPUT:
577
578
A new hyperplane arrangement.
579
580
EXAMPLES::
581
582
sage: H.<x,y> = HyperplaneArrangements(QQ)
583
sage: A = H([1,2,3], [0,1,1], [0,1,-1], [1,-1,0], [1,1,0])
584
sage: B = H([1,1,1], [1,-1,1], [1,0,-1])
585
sage: A.union(B)
586
Arrangement of 8 hyperplanes of dimension 2 and rank 2
587
sage: A | B # syntactic sugar
588
Arrangement of 8 hyperplanes of dimension 2 and rank 2
589
590
A single hyperplane is coerced into a hyperplane arrangement
591
if necessary::
592
593
sage: A.union(x+y-1)
594
Arrangement of 6 hyperplanes of dimension 2 and rank 2
595
sage: A.add_hyperplane(x+y-1) # alias
596
Arrangement of 6 hyperplanes of dimension 2 and rank 2
597
598
sage: P.<x,y> = HyperplaneArrangements(RR)
599
sage: C = P(2*x + 4*y + 5)
600
sage: C.union(A)
601
Arrangement of 6 hyperplanes of dimension 2 and rank 2
602
"""
603
P = self.parent()
604
other = P(other)
605
hyperplanes = self._hyperplanes + other._hyperplanes
606
return P(*hyperplanes)
607
608
add_hyperplane = union
609
610
__or__ = union
611
612
def plot(self, **kwds):
613
"""
614
Plot the hyperplane arrangement.
615
616
OUTPUT:
617
618
A graphics object.
619
620
EXAMPLES::
621
622
sage: L.<x, y> = HyperplaneArrangements(QQ)
623
sage: L(x, y, x+y-2).plot()
624
"""
625
from sage.geometry.hyperplane_arrangement.plot import plot
626
return plot(self, **kwds)
627
628
def cone(self, variable='t'):
629
r"""
630
Return the cone over the hyperplane arrangement.
631
632
INPUT:
633
634
- ``variable`` -- string; the name of the additional variable
635
636
OUTPUT:
637
638
A new yperplane arrangement. Its equations consist of
639
`[0, -d, a_1, \ldots, a_n]` for each `[d, a_1, \ldots, a_n]` in the
640
original arrangement and the equation `[0, 1, 0, \ldots, 0]`.
641
642
EXAMPLES::
643
644
sage: a.<x,y,z> = hyperplane_arrangements.semiorder(3)
645
sage: b = a.cone()
646
sage: a.characteristic_polynomial().factor()
647
x * (x^2 - 6*x + 12)
648
sage: b.characteristic_polynomial().factor()
649
(x - 1) * x * (x^2 - 6*x + 12)
650
sage: a.hyperplanes()
651
(Hyperplane 0*x + y - z - 1,
652
Hyperplane 0*x + y - z + 1,
653
Hyperplane x - y + 0*z - 1,
654
Hyperplane x - y + 0*z + 1,
655
Hyperplane x + 0*y - z - 1,
656
Hyperplane x + 0*y - z + 1)
657
sage: b.hyperplanes()
658
(Hyperplane -t + 0*x + y - z + 0,
659
Hyperplane -t + x - y + 0*z + 0,
660
Hyperplane -t + x + 0*y - z + 0,
661
Hyperplane t + 0*x + 0*y + 0*z + 0,
662
Hyperplane t + 0*x + y - z + 0,
663
Hyperplane t + x - y + 0*z + 0,
664
Hyperplane t + x + 0*y - z + 0)
665
"""
666
hyperplanes = []
667
for h in self.hyperplanes():
668
new_h = [0] + [h.b()] + list(h.A())
669
hyperplanes.append(new_h)
670
hyperplanes.append([0, 1] + [0] * self.dimension())
671
P = self.parent()
672
names = (variable,) + P._names
673
H = HyperplaneArrangements(self.parent().base_ring(), names=names)
674
return H(*hyperplanes)
675
676
@cached_method
677
def intersection_poset(self):
678
r"""
679
Return the intersection poset of the hyperplane arrangement.
680
681
OUTPUT:
682
683
The poset of non-empty intersections of hyperplanes.
684
685
EXAMPLES::
686
687
sage: a = hyperplane_arrangements.coordinate(2)
688
sage: a.intersection_poset()
689
Finite poset containing 4 elements
690
691
sage: A = hyperplane_arrangements.semiorder(3)
692
sage: A.intersection_poset()
693
Finite poset containing 19 elements
694
"""
695
K = self.base_ring()
696
from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace
697
from sage.modules.all import VectorSpace
698
whole_space = AffineSubspace(0, VectorSpace(K, self.dimension()))
699
L = [[whole_space]]
700
active = True
701
codim = 0
702
while active:
703
active = False
704
new_level = []
705
for T in L[codim]:
706
for H in self:
707
I = H._affine_subspace().intersection(T)
708
if I is not None and I != T and I not in new_level:
709
new_level.append(I)
710
active = True
711
if active:
712
L.append(new_level)
713
codim += 1
714
from sage.misc.flatten import flatten
715
L = flatten(L)
716
t = {}
717
for i in range(len(L)):
718
t[i] = L[i]
719
cmp_fn = lambda p, q: t[q] < t[p]
720
from sage.combinat.posets.posets import Poset
721
return Poset((t, cmp_fn))
722
723
def _slow_characteristic_polynomial(self):
724
"""
725
Return the characteristic polynomial of the hyperplane arrangement.
726
727
This is the slow computation directly from the definition. For
728
educational use only.
729
730
EXAMPLES::
731
732
sage: a = hyperplane_arrangements.coordinate(2)
733
sage: a._slow_characteristic_polynomial()
734
x^2 - 2*x + 1
735
"""
736
from sage.rings.polynomial.polynomial_ring import polygen
737
x = polygen(QQ, 'x')
738
P = self.intersection_poset()
739
n = self.dimension()
740
return sum([P.mobius_function(0, p) * x**(n - P.rank(p)) for p in P])
741
742
@cached_method
743
def characteristic_polynomial(self):
744
r"""
745
Return the characteristic polynomial of the hyperplane arrangement.
746
747
OUTPUT:
748
749
The characteristic polynomial in `\QQ[x]`.
750
751
EXAMPLES::
752
753
sage: a = hyperplane_arrangements.coordinate(2)
754
sage: a.characteristic_polynomial()
755
x^2 - 2*x + 1
756
757
TESTS::
758
759
sage: H.<s,t,u,v> = HyperplaneArrangements(QQ)
760
sage: m = matrix([(0, -1, 0, 1, -1), (0, -1, 1, -1, 0), (0, -1, 1, 0, -1),
761
....: (0, 1, 0, 0, 0), (0, 1, 0, 1, -1), (0, 1, 1, -1, 0), (0, 1, 1, 0, -1)])
762
sage: R.<x> = QQ[]
763
sage: expected_charpoly = (x - 1) * x * (x^2 - 6*x + 12)
764
sage: for s in SymmetricGroup(4): # long time (about a second on a Core i7)
765
....: m_perm = [m.column(i) for i in [0, s(1), s(2), s(3), s(4)]]
766
....: m_perm = matrix(m_perm).transpose()
767
....: charpoly = H(m_perm.rows()).characteristic_polynomial()
768
....: assert charpoly == expected_charpoly
769
"""
770
from sage.rings.polynomial.polynomial_ring import polygen
771
x = polygen(QQ, 'x')
772
if self.rank() == 1:
773
return x**(self.dimension() - 1) * (x - len(self))
774
775
H = self[0]
776
R = self.restriction(H)
777
charpoly_R = R.characteristic_polynomial()
778
D = self.deletion(H)
779
charpoly_D = D.characteristic_polynomial()
780
return charpoly_D - charpoly_R
781
782
@cached_method
783
def poincare_polynomial(self):
784
r"""
785
Return the Poincare polynomial of the hyperplane arrangement.
786
787
OUTPUT:
788
789
The Poincare polynomial in `\QQ[x]`.
790
791
EXAMPLES::
792
793
sage: a = hyperplane_arrangements.coordinate(2)
794
sage: a.poincare_polynomial()
795
x^2 + 2*x + 1
796
"""
797
charpoly = self.characteristic_polynomial()
798
R = charpoly.parent()
799
x = R.gen(0)
800
poincare = (-x)**self.dimension() * charpoly(-QQ(1)/x)
801
return R(poincare)
802
803
def deletion(self, hyperplanes):
804
r"""
805
Return the hyperplane arrangement obtained by removing ``h``.
806
807
INPUT:
808
809
- ``h`` -- a hyperplane or hyperplane arrangement
810
811
OUTPUT:
812
813
A new hyperplane arrangement with the given hyperplane(s)
814
``h`` removed.
815
816
.. SEEALSO::
817
818
:meth:`restriction`
819
820
EXAMPLES::
821
822
sage: H.<x,y> = HyperplaneArrangements(QQ)
823
sage: A = H([0,1,0], [1,0,1], [-1,0,1], [0,1,-1], [0,1,1]); A
824
Arrangement of 5 hyperplanes of dimension 2 and rank 2
825
sage: A.deletion(x)
826
Arrangement <y - 1 | y + 1 | x - y | x + y>
827
sage: h = H([0,1,0], [0,1,1])
828
sage: A.deletion(h)
829
Arrangement <y - 1 | y + 1 | x - y>
830
831
TESTS::
832
833
sage: H.<x,y> = HyperplaneArrangements(QQ)
834
sage: A = H([0,1,0], [1,0,1], [-1,0,1], [0,1,-1], [0,1,1])
835
sage: h = H([0,4,0])
836
sage: A.deletion(h)
837
Arrangement <y - 1 | y + 1 | x - y | x + y>
838
sage: l = H([1,2,3])
839
sage: A.deletion(l)
840
Traceback (most recent call last):
841
...
842
ValueError: hyperplane is not in the arrangement
843
"""
844
parent = self.parent()
845
hyperplanes = parent(hyperplanes)
846
planes = list(self)
847
for hyperplane in hyperplanes:
848
try:
849
planes.remove(hyperplane)
850
except ValueError:
851
raise ValueError('hyperplane is not in the arrangement')
852
return parent(*planes)
853
854
def restriction(self, hyperplane):
855
r"""
856
Return the restriction to a hyperplane.
857
858
INPUT:
859
860
- ``hyperplane`` -- a hyperplane of the hyperplane arrangement
861
862
OUTPUT:
863
864
The restriction of the hyperplane arrangement to the given
865
``hyperplane``.
866
867
EXAMPLES::
868
869
sage: A.<u,x,y,z> = hyperplane_arrangements.braid(4); A
870
Arrangement of 6 hyperplanes of dimension 4 and rank 3
871
sage: H = A[0]; H
872
Hyperplane 0*u + 0*x + y - z + 0
873
sage: R = A.restriction(H); R
874
Arrangement <x - z | u - x | u - z>
875
sage: D = A.deletion(H); D
876
Arrangement of 5 hyperplanes of dimension 4 and rank 3
877
sage: ca = A.characteristic_polynomial()
878
sage: cr = R.characteristic_polynomial()
879
sage: cd = D.characteristic_polynomial()
880
sage: ca
881
x^4 - 6*x^3 + 11*x^2 - 6*x
882
sage: cd - cr
883
x^4 - 6*x^3 + 11*x^2 - 6*x
884
885
.. SEEALSO::
886
887
:meth:`deletion`
888
"""
889
parent = self.parent()
890
hyperplane = parent(hyperplane)[0]
891
if hyperplane not in self.hyperplanes():
892
raise ValueError('hyperplane not in arrangement')
893
pivot = hyperplane._normal_pivot()
894
hyperplanes = []
895
for h in self:
896
rescale = h.A()[pivot] / hyperplane.A()[pivot]
897
h = h - rescale * hyperplane
898
A = list(h.A())
899
A_pivot = A.pop(pivot)
900
assert A_pivot == 0
901
if all(a == 0 for a in A):
902
continue
903
b = h.b()
904
hyperplanes.append([A, b])
905
names = list(parent._names)
906
names.pop(pivot)
907
H = HyperplaneArrangements(parent.base_ring(), names=tuple(names))
908
return H(*hyperplanes, signed=False)
909
910
def change_ring(self, base_ring):
911
"""
912
Return hyperplane arrangement over the new base ring.
913
914
INPUT:
915
916
- ``base_ring`` -- the new base ring; must be a field for
917
hyperplane arrangements
918
919
OUTPUT:
920
921
The hyperplane arrangement obtained by changing the base
922
field, as a new hyperplane arrangement.
923
924
EXAMPLES::
925
926
sage: H.<x,y> = HyperplaneArrangements(QQ)
927
sage: A = H([(1,1), 0], [(2,3), -1])
928
sage: A.change_ring(FiniteField(2))
929
Arrangement <y + 1 | x + y>
930
"""
931
parent = self.parent().change_ring(base_ring)
932
return parent(self)
933
934
@cached_method
935
def n_regions(self):
936
r"""
937
The number of regions of the hyperplane arrangement.
938
939
OUTPUT:
940
941
An integer.
942
943
EXAMPLES::
944
945
sage: A = hyperplane_arrangements.semiorder(3)
946
sage: A.n_regions()
947
19
948
949
TESTS::
950
951
sage: H.<x,y> = HyperplaneArrangements(QQ)
952
sage: A = H([(1,1), 0], [(2,3), -1], [(4,5), 3])
953
sage: B = A.change_ring(FiniteField(7))
954
sage: B.n_regions()
955
Traceback (most recent call last):
956
...
957
TypeError: base field must have characteristic zero
958
"""
959
if self.base_ring().characteristic() != 0:
960
raise TypeError('base field must have characteristic zero')
961
charpoly = self.characteristic_polynomial()
962
return (-1)**self.dimension() * charpoly(-1)
963
964
@cached_method
965
def n_bounded_regions(self):
966
r"""
967
Return the number of (relatively) bounded regions.
968
969
OUTPUT:
970
971
An integer. The number of relatively bounded regions of the
972
hyperplane arrangement.
973
974
EXAMPLES::
975
976
sage: A = hyperplane_arrangements.semiorder(3)
977
sage: A.n_bounded_regions()
978
7
979
980
TESTS::
981
982
sage: H.<x,y> = HyperplaneArrangements(QQ)
983
sage: A = H([(1,1),0], [(2,3),-1], [(4,5),3])
984
sage: B = A.change_ring(FiniteField(7))
985
sage: B.n_bounded_regions()
986
Traceback (most recent call last):
987
...
988
TypeError: base field must have characteristic zero
989
"""
990
if self.base_ring().characteristic() != 0:
991
raise TypeError('base field must have characteristic zero')
992
charpoly = self.characteristic_polynomial()
993
return (-1)**self.rank() * charpoly(1)
994
995
def has_good_reduction(self, p):
996
r"""
997
Return whether the hyperplane arrangement has good reduction mod `p`.
998
999
Let `A` be a hyperplane arrangement with equations defined
1000
over the integers, and let `B` be the hyperplane arrangement
1001
defined by reducing these equations modulo a prime `p`. Then
1002
`A` has good reduction modulo `p` if the intersection posets
1003
of `A` and `B` are isomorphic.
1004
1005
INPUT:
1006
1007
- ``p`` -- prime number
1008
1009
OUTPUT:
1010
1011
A boolean.
1012
1013
EXAMPLES::
1014
1015
sage: a = hyperplane_arrangements.semiorder(3)
1016
sage: a.has_good_reduction(5)
1017
True
1018
sage: a.has_good_reduction(3)
1019
False
1020
sage: b = a.change_ring(GF(3))
1021
sage: a.characteristic_polynomial()
1022
x^3 - 6*x^2 + 12*x
1023
sage: b.characteristic_polynomial() # not equal to that for a
1024
x^3 - 6*x^2 + 10*x
1025
"""
1026
if self.base_ring() != QQ:
1027
raise TypeError('arrangement must be defined over QQ')
1028
if not p.is_prime():
1029
raise TypeError('must reduce modulo a prime number')
1030
from sage.rings.all import GF
1031
a = self.change_ring(GF(p))
1032
p = self.intersection_poset()
1033
q = a.intersection_poset()
1034
return p.is_isomorphic(q)
1035
1036
def is_linear(self):
1037
r"""
1038
Test whether all hyperplanes pass through the origin.
1039
1040
OUTPUT:
1041
1042
A boolean. Whether all the hyperplanes pass through the origin.
1043
1044
EXAMPLES::
1045
1046
sage: a = hyperplane_arrangements.semiorder(3)
1047
sage: a.is_linear()
1048
False
1049
sage: b = hyperplane_arrangements.braid(3)
1050
sage: b.is_linear()
1051
True
1052
1053
sage: H.<x,y> = HyperplaneArrangements(QQ)
1054
sage: c = H(x+1, y+1)
1055
sage: c.is_linear()
1056
False
1057
sage: c.is_central()
1058
True
1059
"""
1060
return all(hyperplane.b() == 0 for hyperplane in self)
1061
1062
def is_essential(self):
1063
r"""
1064
Test whether the hyperplane arrangement is essential.
1065
1066
A hyperplane arrangement is essential if the span of the normals
1067
of its hyperplanes spans the ambient space.
1068
1069
.. SEEALSO::
1070
1071
:meth:`essentialization`
1072
1073
OUTPUT:
1074
1075
A boolean indicating whether the hyperplane arrangement is essential.
1076
1077
EXAMPLES::
1078
1079
sage: H.<x,y> = HyperplaneArrangements(QQ)
1080
sage: H(x, x+1).is_essential()
1081
False
1082
sage: H(x, y).is_essential()
1083
True
1084
"""
1085
return self.rank() == self.dimension()
1086
1087
@cached_method
1088
def is_central(self):
1089
r"""
1090
Test whether the intersection of all the hyperplanes is nonempty.
1091
1092
OUTPUT:
1093
1094
A boolean whether the hyperplane arrangement is such that the
1095
intersection of all the hyperplanes in the arrangement is
1096
nonempty.
1097
1098
EXAMPLES::
1099
1100
sage: a = hyperplane_arrangements.braid(2)
1101
sage: a.is_central()
1102
True
1103
"""
1104
R = self.base_ring()
1105
m = matrix(R, [h.normal() for h in self])
1106
b = vector(R, [h.b() for h in self])
1107
try:
1108
m.solve_right(b)
1109
return True
1110
except ValueError:
1111
return False
1112
1113
@cached_method
1114
def essentialization(self):
1115
r"""
1116
Return the essentialization of the hyperplane arrangement.
1117
1118
The essentialization of a hyperplane arrangement whose base field
1119
has characteristic 0 is obtained by intersecting the hyperplanes by
1120
the space spanned by their normal vectors.
1121
1122
OUTPUT:
1123
1124
The essentialization as a new hyperplane arrangement.
1125
1126
EXAMPLES::
1127
1128
sage: a = hyperplane_arrangements.braid(3)
1129
sage: a.is_essential()
1130
False
1131
sage: a.essentialization()
1132
Arrangement <t1 - t2 | t1 + 2*t2 | 2*t1 + t2>
1133
1134
sage: H.<x,y> = HyperplaneArrangements(QQ)
1135
sage: B = H([(1,0),1], [(1,0),-1])
1136
sage: B.is_essential()
1137
False
1138
sage: B.essentialization()
1139
Arrangement <-x + 1 | x + 1>
1140
sage: B.essentialization().parent()
1141
Hyperplane arrangements in 1-dimensional linear space over
1142
Rational Field with coordinate x
1143
1144
sage: H.<x,y> = HyperplaneArrangements(GF(2))
1145
sage: C = H([(1,1),1], [(1,1),0])
1146
sage: C.essentialization()
1147
Arrangement <y | y + 1>
1148
1149
sage: h = hyperplane_arrangements.semiorder(4)
1150
sage: h.essentialization()
1151
Arrangement of 12 hyperplanes of dimension 3 and rank 3
1152
1153
TESTS::
1154
1155
sage: b = hyperplane_arrangements.coordinate(2)
1156
sage: b.is_essential()
1157
True
1158
sage: b.essentialization() is b
1159
True
1160
"""
1161
def echelon_col_iter(row_iter):
1162
"""helper to iterat over the echelon pivot column indices"""
1163
for row in row_iter:
1164
if row == 0:
1165
raise StopIteration
1166
for pivot in range(self.dimension()):
1167
if row[pivot] != 0:
1168
break
1169
assert row[pivot] == 1
1170
yield pivot, row
1171
1172
if self.is_essential():
1173
return self
1174
parent = self.parent()
1175
H = parent.ambient_space()
1176
R = parent.base_ring()
1177
hyperplanes = self.hyperplanes()
1178
normals = matrix(R, [h.normal() for h in self]).transpose()
1179
# find a (any) complement to the normals
1180
if R.characteristic() == 0:
1181
complement_basis = normals.kernel().echelonized_basis()
1182
else:
1183
# we don't necessarily have an orthogonal complement, pick any complement
1184
complement_basis = []
1185
for pivot, row in echelon_col_iter(normals.echelon_form().rows()):
1186
v = [0] * self.dimension()
1187
v[pivot] = 1
1188
complement_basis.append(vector(R, v))
1189
# reduce the hyperplane equations
1190
echelon_pivots = [] # the column indices where N has 1's from the echelonization
1191
for pivot, row in echelon_col_iter(complement_basis):
1192
assert row[pivot] == 1
1193
echelon_pivots.append(pivot)
1194
hyperplanes = [h - h.A()[pivot] * H(row, 0) for h in hyperplanes]
1195
# eliminate the pivot'ed coodinates
1196
restricted = []
1197
for h in hyperplanes:
1198
A = h.A()
1199
if A == 0:
1200
continue
1201
A = [A[i] for i in range(self.dimension()) if i not in echelon_pivots]
1202
b = h.b()
1203
restricted.append([A, b])
1204
names = tuple(name for i, name in enumerate(parent._names) if i not in echelon_pivots)
1205
# Construct the result
1206
restricted_parent = HyperplaneArrangements(R, names=names)
1207
return restricted_parent(*restricted, signed=False)
1208
1209
def sign_vector(self, p):
1210
r"""
1211
Indicates on which side of each hyperplane the given
1212
point `p` lies.
1213
1214
The base field must have characteristic zero.
1215
1216
INPUT:
1217
1218
- ``p`` -- point as a list/tuple/iterable
1219
1220
OUTPUT:
1221
1222
A vector whose entries are in `[-1, 0, +1]`.
1223
1224
EXAMPLES::
1225
1226
sage: H.<x,y> = HyperplaneArrangements(QQ)
1227
sage: A = H([(1,0), 0], [(0,1), 1]); A
1228
Arrangement <y + 1 | x>
1229
sage: A.sign_vector([2, -2])
1230
(-1, 1)
1231
sage: A.sign_vector((-1, -1))
1232
(0, -1)
1233
1234
TESTS::
1235
1236
sage: H.<x,y> = HyperplaneArrangements(GF(3))
1237
sage: A = H(x, y)
1238
sage: A.sign_vector([1, 2])
1239
Traceback (most recent call last):
1240
...
1241
ValueError: characteristic must be zero
1242
"""
1243
if self.base_ring().characteristic() != 0:
1244
raise ValueError('characteristic must be zero')
1245
from sage.functions.generalized import sign
1246
values = [hyperplane(p) for hyperplane in self]
1247
signs = vector(ZZ, map(sign, values))
1248
signs.set_immutable()
1249
return signs
1250
1251
def face_vector(self):
1252
r"""
1253
Return the face vector.
1254
1255
OUTPUT:
1256
1257
A vector of integers.
1258
1259
The `d`-th entry is the number of faces of dimension `d`. A
1260
*face* is is the intersection of a region with a hyperplane of
1261
the arrangehment.
1262
1263
EXAMPLES::
1264
1265
sage: A = hyperplane_arrangements.Shi(3)
1266
sage: A.face_vector()
1267
(0, 6, 21, 16)
1268
"""
1269
m = self.whitney_data()[0]
1270
v = list(sum(m.transpose().apply_map(abs)))
1271
v.reverse()
1272
v = vector(ZZ, [0]*(self.dimension() - self.rank()) + v)
1273
v.set_immutable()
1274
return v
1275
1276
@cached_method
1277
def _parallel_hyperplanes(self):
1278
"""
1279
Return the hyperplanes grouped into parallel sets.
1280
1281
OUTPUT:
1282
1283
A tuple with one entry per set of parallel hyperplanes. Each
1284
entry is a tuple of triples, one for each parallel hyperplane
1285
in the parallel set. The triple consists of the hyperplane,
1286
the normal vector `A`, and the constant `b` of the hyperplane
1287
equation `Ax+b`. The normalization is such that `A` is the
1288
same for each hyperplane of the parallel set, and the order is
1289
in increasing order of the `b` values.
1290
1291
In other words, each parallel set of hyperplanes is also
1292
ordered by the order with which a common normal passes through
1293
them.
1294
1295
EXAMPLES::
1296
1297
sage: H.<x,y> = HyperplaneArrangements(QQ)
1298
sage: h = (x + 2*y | 2*x + 4*y + 1 | -x/4 - y/2 + 1); h
1299
Arrangement <-x - 2*y + 4 | x + 2*y | 2*x + 4*y + 1>
1300
sage: h._parallel_hyperplanes()[0]
1301
((Hyperplane -x - 2*y + 4, (1, 2), -4),
1302
(Hyperplane x + 2*y + 0, (1, 2), 0),
1303
(Hyperplane 2*x + 4*y + 1, (1, 2), 1/2))
1304
1305
sage: hyperplane_arrangements.Shi(3)._parallel_hyperplanes()
1306
(((Hyperplane 0*t0 + t1 - t2 - 1, (0, 1, -1), -1),
1307
(Hyperplane 0*t0 + t1 - t2 + 0, (0, 1, -1), 0)),
1308
((Hyperplane t0 - t1 + 0*t2 - 1, (1, -1, 0), -1),
1309
(Hyperplane t0 - t1 + 0*t2 + 0, (1, -1, 0), 0)),
1310
((Hyperplane t0 + 0*t1 - t2 - 1, (1, 0, -1), -1),
1311
(Hyperplane t0 + 0*t1 - t2 + 0, (1, 0, -1), 0)))
1312
"""
1313
V = self.parent().ambient_space()
1314
parallels = dict()
1315
for hyperplane in self:
1316
through_origin = V([list(hyperplane.A()), 0]).primitive(signed=False)
1317
parallel_planes = parallels.get(through_origin, [])
1318
A = through_origin.A()
1319
b = hyperplane.b() * (A / hyperplane.A())
1320
parallel_planes.append([b, (hyperplane, A, b)])
1321
parallels[through_origin] = parallel_planes
1322
parallels = [tuple(tuple(hyperplane[1]
1323
for hyperplane in sorted(parallels[key])))
1324
for key in parallels.keys()]
1325
return tuple(sorted(parallels))
1326
1327
def vertices(self, exclude_sandwiched=False):
1328
"""
1329
Return the vertices.
1330
1331
The vertices are the zero-dimensional faces, see
1332
:meth:`face_vector`.
1333
1334
INPUT:
1335
1336
- ``exclude_sandwiched`` -- boolean (default:
1337
``False``). Whether to exclude hyperplanes that are
1338
sandwiched between parallel hyperplanes. Useful if you only
1339
need the convex hull.
1340
1341
OUTPUT:
1342
1343
The vertices in a sorted tuple. Each vertex is returned as a
1344
vector in the ambient vector space.
1345
1346
EXAMPLES::
1347
1348
sage: A = hyperplane_arrangements.Shi(3).essentialization()
1349
sage: A.dimension()
1350
2
1351
sage: A.face_vector()
1352
(6, 21, 16)
1353
sage: A.vertices()
1354
((-2/3, 1/3), (-1/3, -1/3), (0, -1), (0, 0), (1/3, -2/3), (2/3, -1/3))
1355
sage: point2d(A.vertices(), size=20) + A.plot()
1356
1357
sage: H.<x,y> = HyperplaneArrangements(QQ)
1358
sage: chessboard = []
1359
sage: N = 8
1360
sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)):
1361
....: chessboard.extend([x-x0, y-y0])
1362
sage: chessboard = H(chessboard)
1363
sage: len(chessboard.vertices())
1364
81
1365
sage: chessboard.vertices(exclude_sandwiched=True)
1366
((0, 0), (0, 8), (8, 0), (8, 8))
1367
"""
1368
from sage.matroids.all import Matroid
1369
from sage.combinat.cartesian_product import CartesianProduct
1370
R = self.parent().base_ring()
1371
parallels = self._parallel_hyperplanes()
1372
A_list = [parallel[0][1] for parallel in parallels]
1373
b_list_list = [[-hyperplane[2] for hyperplane in parallel]
1374
for parallel in parallels]
1375
if exclude_sandwiched:
1376
def skip(b_list):
1377
if len(b_list) == 1:
1378
return b_list
1379
return [b_list[0], b_list[-1]]
1380
b_list_list = map(skip, b_list_list)
1381
M = Matroid(groundset=range(len(parallels)), matrix=matrix(A_list).transpose())
1382
d = self.dimension()
1383
# vertices are solutions v * lhs = rhs
1384
lhs = matrix(R, d, d)
1385
rhs = vector(R, d)
1386
vertices = set()
1387
for indices in M.independent_r_sets(d):
1388
for row, i in enumerate(indices):
1389
lhs[row] = A_list[i]
1390
b_list = [b_list_list[i] for i in indices]
1391
for b in CartesianProduct(*b_list):
1392
for i in range(d):
1393
rhs[i] = b[i]
1394
vertex = lhs.solve_right(rhs)
1395
vertex.set_immutable()
1396
vertices.add(vertex)
1397
return tuple(sorted(vertices))
1398
1399
def _make_region(self, hyperplanes):
1400
"""
1401
Helper method to construct a region.
1402
1403
INPUT:
1404
1405
- ``hyperplanes`` -- a list/tuple/iterable of hyperplanes
1406
1407
OUTPUT:
1408
1409
The polyhedron constructed from taking the linear expressions
1410
as inequalities.
1411
1412
EXAMPLES::
1413
1414
sage: H.<x,y> = HyperplaneArrangements(QQ)
1415
sage: h = H(x)
1416
sage: h._make_region([x, 1-x, y, 1-y])
1417
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
1418
"""
1419
ieqs = [h.coefficients() for h in hyperplanes]
1420
from sage.geometry.polyhedron.constructor import Polyhedron
1421
return Polyhedron(ieqs=ieqs, ambient_dim=self.dimension(),
1422
base_ring=self.parent().base_ring())
1423
1424
@cached_method
1425
def regions(self):
1426
r"""
1427
Return the regions of the hyperplane arrangement.
1428
1429
The base field must have characteristic zero.
1430
1431
OUTPUT:
1432
1433
A tuple containing the regions as polyhedra.
1434
1435
The regions are the connected components of the complement of
1436
the union of the hyperplanes as a subset of `\RR^n`.
1437
1438
EXAMPLES::
1439
1440
sage: a = hyperplane_arrangements.braid(2)
1441
sage: a.regions()
1442
(A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex, 1 ray, 1 line,
1443
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex, 1 ray, 1 line)
1444
1445
sage: H.<x,y> = HyperplaneArrangements(QQ)
1446
sage: A = H(x, y+1)
1447
sage: A.regions()
1448
(A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1449
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1450
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1451
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays)
1452
1453
sage: chessboard = []
1454
sage: N = 8
1455
sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)):
1456
....: chessboard.extend([x-x0, y-y0])
1457
sage: chessboard = H(chessboard)
1458
sage: len(chessboard.bounded_regions()) # long time, 359 ms on a Core i7
1459
64
1460
"""
1461
if self.base_ring().characteristic() != 0:
1462
raise ValueError('base field must have characteristic zero')
1463
from sage.geometry.polyhedron.constructor import Polyhedron
1464
R = self.base_ring()
1465
dim = self.dimension()
1466
universe = Polyhedron(eqns=[[0] + [0] * dim], base_ring=R)
1467
regions = [universe]
1468
for hyperplane in self:
1469
ieq = vector(R, hyperplane.coefficients())
1470
pos_half = Polyhedron(ieqs=[ ieq], base_ring=R)
1471
neg_half = Polyhedron(ieqs=[-ieq], base_ring=R)
1472
subdivided = []
1473
for region in regions:
1474
for half_space in pos_half, neg_half:
1475
part = region.intersection(half_space)
1476
if part.dim() == dim:
1477
subdivided.append(part)
1478
regions = subdivided
1479
return tuple(regions)
1480
1481
def region_containing_point(self, p):
1482
r"""
1483
The region in the hyperplane arrangement containing a given point.
1484
1485
The base field must have characteristic zero.
1486
1487
INPUT:
1488
1489
- ``p`` -- point
1490
1491
OUTPUT:
1492
1493
A polyhedron. A ``ValueError`` is raised if the point is not
1494
interior to a region, that is, sits on a hyperplane.
1495
1496
EXAMPLES::
1497
1498
sage: H.<x,y> = HyperplaneArrangements(QQ)
1499
sage: A = H([(1,0), 0], [(0,1), 1], [(0,1), -1], [(1,-1), 0], [(1,1), 0])
1500
sage: A.region_containing_point([1,2])
1501
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices and 2 rays
1502
1503
TESTS::
1504
1505
sage: A = H([(1,1),0], [(2,3),-1], [(4,5),3])
1506
sage: B = A.change_ring(FiniteField(7))
1507
sage: B.region_containing_point((1,2))
1508
Traceback (most recent call last):
1509
...
1510
ValueError: base field must have characteristic zero
1511
1512
sage: A = H([(1,1),0], [(2,3),-1], [(4,5),3])
1513
sage: A.region_containing_point((1,-1))
1514
Traceback (most recent call last):
1515
...
1516
ValueError: point sits on a hyperplane
1517
"""
1518
if self.base_ring().characteristic() != 0:
1519
raise ValueError('base field must have characteristic zero')
1520
sign_vector = self.sign_vector(p)
1521
ieqs = []
1522
for i, hyperplane in enumerate(self):
1523
sign = sign_vector[i]
1524
if sign == 1:
1525
ieqs.append(hyperplane)
1526
elif sign == -1:
1527
ieqs.append(-hyperplane)
1528
else:
1529
assert sign == 0
1530
raise ValueError('point sits on a hyperplane')
1531
return self._make_region(ieqs)
1532
1533
@cached_method
1534
def _bounded_region_indices(self):
1535
r"""
1536
Return the relatively bounded regions.
1537
1538
OUTPUT:
1539
1540
Tuple of integers. The positions of the relatively bounded
1541
regions in :meth:`regions`.
1542
1543
EXAMPLES::
1544
1545
sage: a = hyperplane_arrangements.semiorder(3)
1546
sage: a._bounded_region_indices()
1547
(2, 7, 8, 9, 10, 11, 16)
1548
"""
1549
from sage.geometry.polyhedron.constructor import Polyhedron
1550
normal = Polyhedron(vertices=[[0]*self.dimension()],
1551
lines=[hyperplane.normal() for hyperplane in self])
1552
if normal.dim() == 0:
1553
transverse = lambda poly: poly
1554
else:
1555
transverse = lambda poly: poly.intersection(normal)
1556
return tuple(i for i, region in enumerate(self.regions())
1557
if transverse(region).is_compact())
1558
1559
def bounded_regions(self):
1560
r"""
1561
Return the relatively bounded regions of the arrangement.
1562
1563
A region is relatively bounded if its intersection with the space
1564
spanned by the normals to the hyperplanes is bounded. This is the
1565
same as being bounded in the case that the hyperplane arrangement
1566
is essential. It is assumed that the arrangement is defined over
1567
the rationals.
1568
1569
OUTPUT:
1570
1571
Tuple of polyhedra. The relatively bounded regions of the
1572
arrangement.
1573
1574
.. SEEALSO::
1575
1576
:meth:`unbounded_regions`
1577
1578
EXAMPLES::
1579
1580
sage: A = hyperplane_arrangements.semiorder(3)
1581
sage: A.bounded_regions()
1582
(A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices and 1 line,
1583
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices and 1 line,
1584
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices and 1 line,
1585
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 6 vertices and 1 line,
1586
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices and 1 line,
1587
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices and 1 line,
1588
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices and 1 line)
1589
sage: A.bounded_regions()[0].is_compact() # the regions are only *relatively* bounded
1590
False
1591
sage: A.is_essential()
1592
False
1593
"""
1594
return tuple(self.regions()[i] for i in self._bounded_region_indices())
1595
1596
def unbounded_regions(self):
1597
r"""
1598
Return the relatively bounded regions of the arrangement.
1599
1600
OUTPUT:
1601
1602
Tuple of polyhedra. The regions of the arrangement that are not
1603
relatively bounded. It is assumed that the arrangement is
1604
defined over the rationals.
1605
1606
.. SEEALSO::
1607
1608
:meth:`bounded_regions`
1609
1610
EXAMPLES::
1611
1612
sage: A = hyperplane_arrangements.semiorder(3)
1613
sage: B = A.essentialization()
1614
sage: B.n_regions() - B.n_bounded_regions()
1615
12
1616
sage: B.unbounded_regions()
1617
(A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
1618
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
1619
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1620
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
1621
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1622
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
1623
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1624
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
1625
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1626
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 1 ray,
1627
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays,
1628
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays)
1629
"""
1630
s = set(range(self.n_regions())).difference(set(self._bounded_region_indices()))
1631
return tuple(self.regions()[i] for i in s)
1632
1633
@cached_method
1634
def whitney_data(self):
1635
r"""
1636
Return the Whitney numbers.
1637
1638
.. SEEALSO::
1639
1640
:meth:`whitney_number`,
1641
:meth:`doubly_indexed_whitney_number`
1642
1643
OUTPUT:
1644
1645
A pair of integer matrices. The two matrices are the
1646
doubly-indexed Whitney numbers of the first or second kind,
1647
respectively. The `i,j`-th entry is the `i,j`-th
1648
doubly-indexed Whitney number.
1649
1650
EXAMPLES::
1651
1652
sage: A = hyperplane_arrangements.Shi(3)
1653
sage: A.whitney_data()
1654
(
1655
[ 1 -6 9] [ 1 6 6]
1656
[ 0 6 -15] [ 0 6 15]
1657
[ 0 0 6], [ 0 0 6]
1658
)
1659
"""
1660
p = self.intersection_poset()
1661
r = p.rank_function()
1662
top = r(p.maximal_elements()[0])
1663
from sage.matrix.constructor import zero_matrix
1664
m1 = zero_matrix(ZZ, top+1, top+1)
1665
m2 = zero_matrix(ZZ, top+1, top+1)
1666
for i, j in p.relations_iterator():
1667
m1[r(i), r(j)] += p.mobius_function(i, j)
1668
m2[r(i), r(j)] += 1
1669
m1.set_immutable()
1670
m2.set_immutable()
1671
return (m1, m2)
1672
1673
def doubly_indexed_whitney_number(self, i, j, kind=1):
1674
r"""
1675
Return the `i,j`-th doubly-indexed Whitney number.
1676
1677
If ``kind=1``, this number is obtained by adding the Moebius function
1678
values `mu(x,y)` over all `x, y` in the intersection poset with
1679
`\mathrm{rank}(x) = i` and `\mathrm{rank}(y) = j`.
1680
1681
If `kind=2`, this number is the number of elements `x,y` in the
1682
intersection poset such that `x \leq y` with ranks `i` and `j`,
1683
respectively.
1684
1685
INPUT:
1686
1687
- ``i``, ``j`` -- integers
1688
1689
- ``kind`` -- (default: 1) 1 or 2
1690
1691
OUTPUT:
1692
1693
Integer. The `(i,j)`-th entry of the ``kind`` Whitney number.
1694
1695
.. SEEALSO::
1696
1697
:meth:`whitney_number`,
1698
:meth:`whitney_data`
1699
1700
EXAMPLES::
1701
1702
sage: A = hyperplane_arrangements.Shi(3)
1703
sage: A.doubly_indexed_whitney_number(0, 2)
1704
9
1705
sage: A.whitney_number(2)
1706
9
1707
sage: A.doubly_indexed_whitney_number(1, 2)
1708
-15
1709
1710
REFERENCES:
1711
1712
.. [GZ] Greene; Zaslavsky
1713
"On the Interpretation of Whitney Numbers Through Arrangements of
1714
Hyperplanes, Zonotopes, Non-Radon Partitions, and Orientations of
1715
Graphs"
1716
Transactions of the American Mathematical Society, Vol. 280, No. 1.
1717
(Nov., 1983), pp. 97-126.
1718
"""
1719
if 0 <= i and j <= self.dimension():
1720
if kind == 1:
1721
return self.whitney_data()[0][i, j]
1722
elif kind == 2:
1723
return self.whitney_data()[1][i, j]
1724
raise ValueError('argument out of range')
1725
1726
def whitney_number(self, k, kind=1):
1727
r"""
1728
Return the ``k``-th Whitney number.
1729
1730
If ``kind=1``, this number is obtained by summing the Moebius function
1731
values `mu(0, x)` over all `x` in the intersection poset with
1732
`\mathrm{rank}(x) = k`.
1733
1734
If ``kind=2``, this number is the number of elements `x, y` in the
1735
intersection poset such that `x \leq y` with ranks `i` and `j`,
1736
respectively.
1737
1738
See [GZ]_ for more details.
1739
1740
INPUT:
1741
1742
- ``k`` -- integer
1743
1744
- ``kind`` -- 1 or 2 (default: 1)
1745
1746
OUTPUT:
1747
1748
Integer. The ``k``-th Whitney number.
1749
1750
.. SEEALSO::
1751
1752
:meth:`doubly_indexed_whitney_number`
1753
:meth:`whitney_data`
1754
1755
EXAMPLES::
1756
1757
sage: A = hyperplane_arrangements.Shi(3)
1758
sage: A.whitney_number(0)
1759
1
1760
sage: A.whitney_number(1)
1761
-6
1762
sage: A.whitney_number(2)
1763
9
1764
sage: A.characteristic_polynomial()
1765
x^3 - 6*x^2 + 9*x
1766
sage: A.whitney_number(1,kind=2)
1767
6
1768
sage: p = A.intersection_poset()
1769
sage: r = p.rank_function()
1770
sage: len([i for i in p if r(i) == 1])
1771
6
1772
"""
1773
if k >= 0 and k <= self.dimension():
1774
if kind == 1:
1775
return self.whitney_data()[0][0, k]
1776
elif kind == 2:
1777
return self.whitney_data()[1][0, k]
1778
raise ValueError('argument out of range')
1779
1780
def is_separating_hyperplane(self, region1, region2, hyperplane):
1781
r"""
1782
Test whether the ``hyperplane`` separates the given regions.
1783
1784
INPUT:
1785
1786
- ``region1``, ``region2`` -- polyhedra or list/tuple/iterable
1787
of coordinates which are regions of the arrangement or an interior
1788
point of a region
1789
1790
- ``hyperplane`` -- a hyperplane
1791
1792
OUTPUT:
1793
1794
A boolean. Whether the hyperplane ``hyperplane`` separate the given
1795
regions.
1796
1797
EXAMPLES::
1798
1799
sage: A.<x,y> = hyperplane_arrangements.coordinate(2)
1800
sage: A.is_separating_hyperplane([1,1], [2,1], y)
1801
False
1802
sage: A.is_separating_hyperplane([1,1], [-1,1], x)
1803
True
1804
sage: r = A.region_containing_point([1,1])
1805
sage: s = A.region_containing_point([-1,1])
1806
sage: A.is_separating_hyperplane(r, s, x)
1807
True
1808
"""
1809
if self.base_ring().characteristic() != 0:
1810
raise ValueError('requires characteristic zero')
1811
try:
1812
p1 = region1.representative_point()
1813
except AttributeError:
1814
p1 = list(region1)
1815
try:
1816
p2 = region2.representative_point()
1817
except AttributeError:
1818
p2 = list(region2)
1819
from sage.functions.generalized import sign
1820
s = sign(hyperplane(p1)) * sign(hyperplane(p2))
1821
if s < 0:
1822
return True
1823
if s > 0:
1824
return False
1825
raise ValueError('point lies on hyperplane')
1826
1827
def distance_between_regions(self, region1, region2):
1828
r"""
1829
Return the number of hyperplanes separating the two regions.
1830
1831
INPUT:
1832
1833
- ``region1``, ``region2`` -- regions of the arrangement or
1834
representative points of regions
1835
1836
OUTPUT:
1837
1838
An integer. The number of hyperplanes separating the two regions.
1839
1840
EXAMPLES::
1841
1842
sage: c = hyperplane_arrangements.coordinate(2)
1843
sage: r = c.region_containing_point([-1, -1])
1844
sage: s = c.region_containing_point([1, 1])
1845
sage: c.distance_between_regions(r, s)
1846
2
1847
sage: c.distance_between_regions(s, s)
1848
0
1849
"""
1850
count = sum(1 for hyperplane in self
1851
if self.is_separating_hyperplane(region1, region2, hyperplane))
1852
return ZZ(count)
1853
1854
def distance_enumerator(self, base_region):
1855
r"""
1856
Return the generating function for the number of hyperplanes
1857
at given distance.
1858
1859
INPUT:
1860
1861
- ``base_region`` -- region of arrangement or point in region
1862
1863
OUTPUT:
1864
1865
A polynomial `f(x)` for which the coefficient of `x^i` is the
1866
number of hyperplanes of distance `i` from ``base_region``,
1867
i.e., the number of hyperplanes separated by `i` hyperplanes
1868
from ``base_region``.
1869
1870
EXAMPLES::
1871
1872
sage: c = hyperplane_arrangements.coordinate(3)
1873
sage: c.distance_enumerator(c.region_containing_point([1,1,1]))
1874
x^3 + 3*x^2 + 3*x + 1
1875
"""
1876
d = [self.distance_between_regions(r,base_region) for r in self.regions()]
1877
d = [d.count(i) for i in range(max(d)+1)]
1878
from sage.rings.polynomial.polynomial_ring import polygen
1879
x = polygen(QQ, 'x')
1880
return sum([d[i]*x**i for i in range(len(d))])
1881
1882
@cached_method
1883
def varchenko_matrix(self, names='h'):
1884
r"""
1885
Return the Varchenko matrix of the arrangement.
1886
1887
Let `H_1, \ldots, H_s` and `R_1, \ldots, R_t` denote the hyperplanes
1888
and regions, respectively, of the arrangement. Let `S =
1889
\QQ[h_1, \ldots, h_s]`, a polynomial ring with indeterminate `h_i`
1890
corresponding to hyperplane `H_i`. The Varchenko matrix is
1891
the `t \times t` matrix with `i,j`-th entry the product of
1892
those `h_k` such that `H_k` separates `R_i` and `R_j`.
1893
1894
INPUT:
1895
1896
- ``names`` -- string or list/tuple/iterable of strings. The
1897
variable names for the polynomial ring `S`.
1898
1899
OUTPUT:
1900
1901
The Varchenko matrix.
1902
1903
EXAMPLES::
1904
1905
sage: a = hyperplane_arrangements.coordinate(3)
1906
sage: v = a.varchenko_matrix(); v
1907
[ 1 h2 h1]
1908
[ h2 1 h1*h2]
1909
[ h1 h1*h2 1]
1910
sage: factor(det(v))
1911
(h2 - 1) * (h2 + 1) * (h1 - 1) * (h1 + 1)
1912
"""
1913
from sage.rings.all import PolynomialRing
1914
from sage.matrix.constructor import identity_matrix
1915
from sage.misc.misc import prod
1916
k = len(self)
1917
R = PolynomialRing(QQ, names, k)
1918
h = R.gens()
1919
region = self.regions()
1920
v = identity_matrix(R, k, k)
1921
for i in range(k):
1922
for j in range(i+1, k):
1923
t = prod(h[p] for p in range(k) if
1924
self.is_separating_hyperplane(region[i], region[j], self[p]))
1925
v[i,j] = v[j,i] = t
1926
v.set_immutable()
1927
return v
1928
1929
1930
1931
class HyperplaneArrangements(Parent, UniqueRepresentation):
1932
"""
1933
Hyperplane arrangements.
1934
1935
For more information on hyperplane arrangements, see
1936
:mod:`sage.geometry.hyperplane_arrangements.arrangement`.
1937
1938
INPUT:
1939
1940
- ``base_ring`` -- ring; the base ring
1941
1942
- ``names`` -- tuple of strings; the variable names
1943
1944
EXAMPLES::
1945
1946
sage: H.<x,y> = HyperplaneArrangements(QQ)
1947
sage: x
1948
Hyperplane x + 0*y + 0
1949
sage: x + y
1950
Hyperplane x + y + 0
1951
sage: H(x, y, x-1, y-1)
1952
Arrangement <y - 1 | y | x - 1 | x>
1953
"""
1954
Element = HyperplaneArrangementElement
1955
1956
def __init__(self, base_ring, names=tuple()):
1957
"""
1958
Initialize ``self``.
1959
1960
TESTS::
1961
1962
sage: H.<x,y> = HyperplaneArrangements(QQ)
1963
sage: K = HyperplaneArrangements(QQ, names=('x', 'y'))
1964
sage: H is K
1965
True
1966
sage: type(K)
1967
<class 'sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangements_with_category'>
1968
sage: K.change_ring(RR).gen(0)
1969
Hyperplane 1.00000000000000*x + 0.000000000000000*y + 0.000000000000000
1970
1971
TESTS::
1972
1973
sage: H.<x,y> = HyperplaneArrangements(QQ)
1974
sage: TestSuite(H).run()
1975
sage: K = HyperplaneArrangements(QQ)
1976
sage: TestSuite(K).run()
1977
"""
1978
from sage.categories.all import Fields, Sets
1979
if not base_ring in Fields:
1980
raise ValueError('base ring must be a field')
1981
super(HyperplaneArrangements, self).__init__(category=Sets())
1982
self._base_ring = base_ring
1983
self._names = names
1984
1985
def base_ring(self):
1986
"""
1987
Return the base ring.
1988
1989
OUTPUT:
1990
1991
The base ring of the hyperplane arrangement.
1992
1993
EXAMPLES::
1994
1995
sage: L.<x,y> = HyperplaneArrangements(QQ)
1996
sage: L.base_ring()
1997
Rational Field
1998
"""
1999
return self._base_ring
2000
2001
def change_ring(self, base_ring):
2002
"""
2003
Return hyperplane arrangements over a different base ring.
2004
2005
INPUT:
2006
2007
- ``base_ring`` -- a ring; the new base ring.
2008
2009
OUTPUT:
2010
2011
A new :class:`HyperplaneArrangements` instance over the new
2012
base ring.
2013
2014
EXAMPLES::
2015
2016
sage: L.<x,y> = HyperplaneArrangements(QQ)
2017
sage: L.gen(0)
2018
Hyperplane x + 0*y + 0
2019
sage: L.change_ring(RR).gen(0)
2020
Hyperplane 1.00000000000000*x + 0.000000000000000*y + 0.000000000000000
2021
2022
TESTS::
2023
2024
sage: L.change_ring(QQ) is L
2025
True
2026
"""
2027
return HyperplaneArrangements(base_ring, names=self._names)
2028
2029
@cached_method
2030
def ambient_space(self):
2031
"""
2032
Return the ambient space.
2033
2034
The ambient space is the parent of hyperplanes. That is, new
2035
hyperplanes are always constructed internally from the ambient
2036
space instance.
2037
2038
EXAMPLES::
2039
2040
sage: L.<x, y> = HyperplaneArrangements(QQ)
2041
sage: L.ambient_space()([(1,0), 0])
2042
Hyperplane x + 0*y + 0
2043
sage: L.ambient_space()([(1,0), 0]) == x
2044
True
2045
"""
2046
return AmbientVectorSpace(self.base_ring(), self._names)
2047
2048
def _repr_(self):
2049
"""
2050
Return a string representation.
2051
2052
OUTPUT:
2053
2054
A string.
2055
2056
EXAMPLES::
2057
2058
sage: L.<x, y> = HyperplaneArrangements(QQ); L
2059
Hyperplane arrangements in 2-dimensional linear space over Rational Field with coordinates x, y
2060
"""
2061
return 'Hyperplane arrangements in {0}'.format(self.ambient_space())
2062
2063
def _element_constructor_(self, *args, **kwds):
2064
"""
2065
Construct an element of ``self``.
2066
2067
INPUT:
2068
2069
- ``*args`` -- positional arguments, each defining a
2070
hyperplane; alternatively, a single polytope or a single
2071
hyperplane arrangement
2072
2073
- ``signed`` -- boolean (optional, default: ``True``); whether to
2074
preserve signs of hyperplane equations
2075
2076
- ``warn_duplicates`` -- boolean (optional, default: ``False``);
2077
whether to issue a warning if duplicate hyperplanes were
2078
passed -- note that duplicate hyperplanes are always removed,
2079
whether or not there is a warning shown
2080
2081
- ``check`` -- boolean (optional, default: ``True``); whether to
2082
perform argument checking.
2083
2084
EXAMPLES::
2085
2086
sage: L.<x, y> = HyperplaneArrangements(QQ)
2087
sage: L._element_constructor_(x, y)
2088
Arrangement <y | x>
2089
sage: L._element_constructor_([x, y])
2090
Arrangement <y | x>
2091
sage: L._element_constructor_([0, 1, 0], [0, 0, 1])
2092
Arrangement <y | x>
2093
sage: L._element_constructor_([[0, 1, 0], [0, 0, 1]])
2094
Arrangement <y | x>
2095
2096
sage: L._element_constructor(polytopes.n_cube(2))
2097
Arrangement <-x + 1 | -y + 1 | y + 1 | x + 1>
2098
2099
sage: L(x, x, warn_duplicates=True)
2100
doctest:...: UserWarning: Input contained 2 hyperplanes, but only 1 are distinct.
2101
Arrangement <x>
2102
sage: L(-x, x + y - 1, signed=False)
2103
Arrangement <-x - y + 1 | x>
2104
2105
TESTS::
2106
2107
sage: L()
2108
Empty hyperplane arrangement of dimension 2
2109
sage: L(0) # zero is equivalent to no argument, Trac #8648
2110
Empty hyperplane arrangement of dimension 2
2111
sage: L(0*x) # degenerate hyperplane is NOT allowed
2112
Traceback (most recent call last):
2113
...
2114
ValueError: linear expression must be non-constant to define a hyperplane
2115
sage: L(0*x, y) # ditto
2116
Traceback (most recent call last):
2117
...
2118
ValueError: linear expression must be non-constant to define a hyperplane
2119
"""
2120
if len(args) == 1:
2121
arg = args[0]
2122
if isinstance(arg, HyperplaneArrangementElement) and args[0].parent() is self:
2123
# optimization if argument is already a hyperplane arrangement
2124
return arg
2125
if arg == 0 and not isinstance(arg, Hyperplane):
2126
# zero = neutral element under addition = the empty hyperplane arrangement
2127
args = []
2128
# process keyword arguments
2129
not_char2 = (self.base_ring().characteristic() != 2)
2130
signed = kwds.pop('signed', not_char2)
2131
warn_duplicates = kwds.pop('warn_duplicates', False)
2132
check = kwds.pop('check', True)
2133
if len(kwds) > 0:
2134
raise ValueError('unknown keyword argument')
2135
# process positional arguments
2136
AA = self.ambient_space()
2137
try:
2138
hyperplanes = map(AA, args)
2139
except (TypeError, ValueError, AttributeError):
2140
if len(args) > 1:
2141
raise
2142
arg = args[0]
2143
if hasattr(arg, 'Hrepresentation'):
2144
hyperplanes = [AA(h) for h in arg.Hrepresentation()]
2145
else:
2146
hyperplanes = map(AA, arg)
2147
hyperplanes = [h.primitive(signed) for h in hyperplanes]
2148
n = len(hyperplanes)
2149
hyperplanes = tuple(uniq(hyperplanes))
2150
if warn_duplicates and n != len(hyperplanes):
2151
from warnings import warn
2152
warn('Input contained {0} hyperplanes, but only {1} are distinct.'.format(n, len(hyperplanes)))
2153
# argument checking (optional but recommended)
2154
if check:
2155
if signed and not not_char2:
2156
raise ValueError('cannot be signed in characteristic 2')
2157
hyperplane_set = set(hyperplanes)
2158
for h in hyperplanes:
2159
if h.A() == 0:
2160
raise ValueError('linear expression must be non-constant to define a hyperplane')
2161
if not_char2 and -h in hyperplane_set:
2162
raise ValueError('arrangement cannot simultaneouly have h and -h as hyperplane')
2163
return self.element_class(self, hyperplanes)
2164
2165
@cached_method
2166
def ngens(self):
2167
"""
2168
Return the number of linear variables.
2169
2170
OUTPUT:
2171
2172
An integer.
2173
2174
EXAMPLES::
2175
2176
sage: L.<x, y, z> = HyperplaneArrangements(QQ); L
2177
Hyperplane arrangements in 3-dimensional linear space over Rational Field with coordinates x, y, z
2178
sage: L.ngens()
2179
3
2180
"""
2181
return len(self._names)
2182
2183
@cached_method
2184
def gens(self):
2185
"""
2186
Return the coordinate hyperplanes.
2187
2188
OUTPUT:
2189
2190
A tuple of linear expressions, one for each linear variable.
2191
2192
EXAMPLES::
2193
2194
sage: L = HyperplaneArrangements(QQ, ('x', 'y', 'z'))
2195
sage: L.gens()
2196
(Hyperplane x + 0*y + 0*z + 0,
2197
Hyperplane 0*x + y + 0*z + 0,
2198
Hyperplane 0*x + 0*y + z + 0)
2199
"""
2200
return self.ambient_space().gens()
2201
2202
def gen(self, i):
2203
"""
2204
Return the `i`-th coordinate hyperplane.
2205
2206
INPUT:
2207
2208
- ``i`` -- integer
2209
2210
OUTPUT:
2211
2212
A linear expression.
2213
2214
EXAMPLES::
2215
2216
sage: L.<x, y, z> = HyperplaneArrangements(QQ); L
2217
Hyperplane arrangements in 3-dimensional linear space over Rational Field with coordinates x, y, z
2218
sage: L.gen(0)
2219
Hyperplane x + 0*y + 0*z + 0
2220
"""
2221
return self.gens()[i]
2222
2223
def _coerce_map_from_(self, P):
2224
"""
2225
Return whether there is a coercion.
2226
2227
TESTS::
2228
2229
sage: L.<x> = HyperplaneArrangements(QQ); L
2230
Hyperplane arrangements in 1-dimensional linear space over Rational Field with coordinate x
2231
sage: M.<y> = HyperplaneArrangements(RR); M
2232
Hyperplane arrangements in 1-dimensional linear space over Real Field with 53 bits of precision with coordinate y
2233
2234
sage: L.coerce_map_from(ZZ)
2235
Conversion map:
2236
From: Integer Ring
2237
To: Hyperplane arrangements in 1-dimensional linear space over Rational Field with coordinate x
2238
sage: M.coerce_map_from(L)
2239
Conversion map:
2240
From: Hyperplane arrangements in 1-dimensional linear space over
2241
Rational Field with coordinate x
2242
To: Hyperplane arrangements in 1-dimensional linear space over
2243
Real Field with 53 bits of precision with coordinate y
2244
sage: L.coerce_map_from(M)
2245
"""
2246
if self.ambient_space().has_coerce_map_from(P):
2247
return True
2248
if isinstance(P, HyperplaneArrangements):
2249
return self.base_ring().has_coerce_map_from(P.base_ring())
2250
return super(HyperplaneArrangements, self)._coerce_map_from_(P)
2251
2252
2253