passagemath-polyhedra 10.6.31rc3__cp314-cp314-musllinux_1_2_aarch64.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.
- passagemath_polyhedra-10.6.31rc3.dist-info/METADATA +367 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/METADATA.bak +369 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/RECORD +208 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/WHEEL +5 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_polyhedra.libs/libgcc_s-2d945d6c.so.1 +0 -0
- passagemath_polyhedra.libs/libgmp-28992bcb.so.10.5.0 +0 -0
- passagemath_polyhedra.libs/libgomp-1ede7ee7.so.1.0.0 +0 -0
- passagemath_polyhedra.libs/libstdc++-85f2cd6d.so.6.0.33 +0 -0
- sage/all__sagemath_polyhedra.py +50 -0
- sage/game_theory/all.py +8 -0
- sage/game_theory/catalog.py +6 -0
- sage/game_theory/catalog_normal_form_games.py +923 -0
- sage/game_theory/cooperative_game.py +844 -0
- sage/game_theory/matching_game.py +1181 -0
- sage/game_theory/normal_form_game.py +2697 -0
- sage/game_theory/parser.py +275 -0
- sage/geometry/all__sagemath_polyhedra.py +22 -0
- sage/geometry/cone.py +6940 -0
- sage/geometry/cone_catalog.py +847 -0
- sage/geometry/cone_critical_angles.py +1027 -0
- sage/geometry/convex_set.py +1119 -0
- sage/geometry/fan.py +3743 -0
- sage/geometry/fan_isomorphism.py +389 -0
- sage/geometry/fan_morphism.py +1884 -0
- sage/geometry/hasse_diagram.py +202 -0
- sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
- sage/geometry/hyperplane_arrangement/all.py +1 -0
- sage/geometry/hyperplane_arrangement/arrangement.py +3895 -0
- sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
- sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
- sage/geometry/hyperplane_arrangement/library.py +825 -0
- sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
- sage/geometry/hyperplane_arrangement/plot.py +520 -0
- sage/geometry/integral_points.py +35 -0
- sage/geometry/integral_points_generic_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/integral_points_generic_dense.pyx +7 -0
- sage/geometry/lattice_polytope.py +5894 -0
- sage/geometry/linear_expression.py +773 -0
- sage/geometry/newton_polygon.py +767 -0
- sage/geometry/point_collection.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/point_collection.pyx +1008 -0
- sage/geometry/polyhedral_complex.py +2616 -0
- sage/geometry/polyhedron/all.py +8 -0
- sage/geometry/polyhedron/backend_cdd.py +460 -0
- sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
- sage/geometry/polyhedron/backend_field.py +347 -0
- sage/geometry/polyhedron/backend_normaliz.py +2503 -0
- sage/geometry/polyhedron/backend_number_field.py +168 -0
- sage/geometry/polyhedron/backend_polymake.py +765 -0
- sage/geometry/polyhedron/backend_ppl.py +582 -0
- sage/geometry/polyhedron/base.py +1206 -0
- sage/geometry/polyhedron/base0.py +1444 -0
- sage/geometry/polyhedron/base1.py +886 -0
- sage/geometry/polyhedron/base2.py +812 -0
- sage/geometry/polyhedron/base3.py +1845 -0
- sage/geometry/polyhedron/base4.py +1262 -0
- sage/geometry/polyhedron/base5.py +2700 -0
- sage/geometry/polyhedron/base6.py +1741 -0
- sage/geometry/polyhedron/base7.py +997 -0
- sage/geometry/polyhedron/base_QQ.py +1258 -0
- sage/geometry/polyhedron/base_RDF.py +98 -0
- sage/geometry/polyhedron/base_ZZ.py +934 -0
- sage/geometry/polyhedron/base_mutable.py +215 -0
- sage/geometry/polyhedron/base_number_field.py +122 -0
- sage/geometry/polyhedron/cdd_file_format.py +155 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
- sage/geometry/polyhedron/constructor.py +773 -0
- sage/geometry/polyhedron/double_description.py +753 -0
- sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
- sage/geometry/polyhedron/face.py +1060 -0
- sage/geometry/polyhedron/generating_function.py +1810 -0
- sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
- sage/geometry/polyhedron/library.py +3502 -0
- sage/geometry/polyhedron/misc.py +121 -0
- sage/geometry/polyhedron/modules/all.py +1 -0
- sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
- sage/geometry/polyhedron/palp_database.py +447 -0
- sage/geometry/polyhedron/parent.py +1279 -0
- sage/geometry/polyhedron/plot.py +1986 -0
- sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
- sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
- sage/geometry/polyhedron/representation.py +1723 -0
- sage/geometry/pseudolines.py +515 -0
- sage/geometry/relative_interior.py +445 -0
- sage/geometry/toric_plotter.py +1103 -0
- sage/geometry/triangulation/all.py +2 -0
- sage/geometry/triangulation/base.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/triangulation/base.pyx +963 -0
- sage/geometry/triangulation/data.h +147 -0
- sage/geometry/triangulation/data.pxd +4 -0
- sage/geometry/triangulation/element.py +914 -0
- sage/geometry/triangulation/functions.h +10 -0
- sage/geometry/triangulation/functions.pxd +4 -0
- sage/geometry/triangulation/point_configuration.py +2256 -0
- sage/geometry/triangulation/triangulations.h +49 -0
- sage/geometry/triangulation/triangulations.pxd +7 -0
- sage/geometry/voronoi_diagram.py +319 -0
- sage/interfaces/all__sagemath_polyhedra.py +1 -0
- sage/interfaces/polymake.py +2028 -0
- sage/numerical/all.py +13 -0
- sage/numerical/all__sagemath_polyhedra.py +11 -0
- sage/numerical/backends/all.py +1 -0
- sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
- sage/numerical/backends/cvxopt_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/cvxopt_backend.pyx +1006 -0
- sage/numerical/backends/cvxopt_backend_test.py +19 -0
- sage/numerical/backends/cvxopt_sdp_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
- sage/numerical/backends/cvxpy_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/cvxpy_backend.pxd +41 -0
- sage/numerical/backends/cvxpy_backend.pyx +934 -0
- sage/numerical/backends/cvxpy_backend_test.py +13 -0
- sage/numerical/backends/generic_backend_test.py +24 -0
- sage/numerical/backends/interactivelp_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/interactivelp_backend.pxd +36 -0
- sage/numerical/backends/interactivelp_backend.pyx +1231 -0
- sage/numerical/backends/interactivelp_backend_test.py +12 -0
- sage/numerical/backends/logging_backend.py +391 -0
- sage/numerical/backends/matrix_sdp_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
- sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
- sage/numerical/backends/ppl_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/ppl_backend.pyx +1126 -0
- sage/numerical/backends/ppl_backend_test.py +13 -0
- sage/numerical/backends/scip_backend.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/scip_backend.pxd +22 -0
- sage/numerical/backends/scip_backend.pyx +1289 -0
- sage/numerical/backends/scip_backend_test.py +13 -0
- sage/numerical/interactive_simplex_method.py +5338 -0
- sage/numerical/knapsack.py +665 -0
- sage/numerical/linear_functions.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/linear_functions.pxd +31 -0
- sage/numerical/linear_functions.pyx +1648 -0
- sage/numerical/linear_tensor.py +470 -0
- sage/numerical/linear_tensor_constraints.py +448 -0
- sage/numerical/linear_tensor_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/linear_tensor_element.pxd +6 -0
- sage/numerical/linear_tensor_element.pyx +459 -0
- sage/numerical/mip.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/mip.pxd +40 -0
- sage/numerical/mip.pyx +3667 -0
- sage/numerical/sdp.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/sdp.pxd +39 -0
- sage/numerical/sdp.pyx +1433 -0
- sage/rings/all__sagemath_polyhedra.py +3 -0
- sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
- sage/rings/polynomial/omega.py +982 -0
- sage/schemes/all__sagemath_polyhedra.py +2 -0
- sage/schemes/toric/all.py +10 -0
- sage/schemes/toric/chow_group.py +1248 -0
- sage/schemes/toric/divisor.py +2082 -0
- sage/schemes/toric/divisor_class.cpython-314-aarch64-linux-musl.so +0 -0
- sage/schemes/toric/divisor_class.pyx +322 -0
- sage/schemes/toric/fano_variety.py +1606 -0
- sage/schemes/toric/homset.py +650 -0
- sage/schemes/toric/ideal.py +451 -0
- sage/schemes/toric/library.py +1322 -0
- sage/schemes/toric/morphism.py +1958 -0
- sage/schemes/toric/points.py +1032 -0
- sage/schemes/toric/sheaf/all.py +1 -0
- sage/schemes/toric/sheaf/constructor.py +302 -0
- sage/schemes/toric/sheaf/klyachko.py +921 -0
- sage/schemes/toric/toric_subscheme.py +905 -0
- sage/schemes/toric/variety.py +3460 -0
- sage/schemes/toric/weierstrass.py +1078 -0
- sage/schemes/toric/weierstrass_covering.py +457 -0
- sage/schemes/toric/weierstrass_higher.py +288 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
- sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
- 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)
|