passagemath-polyhedra 10.6.31rc3__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-polyhedra might be problematic. Click here for more details.
- 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 +206 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/WHEEL +6 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_polyhedra.libs/libgmp-6e109695.so.10.5.0 +0 -0
- passagemath_polyhedra.libs/libgomp-e985bcbb.so.1.0.0 +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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
- sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.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-x86_64-linux-gnu.so +0 -0
- sage/numerical/linear_tensor_element.pxd +6 -0
- sage/numerical/linear_tensor_element.pyx +459 -0
- sage/numerical/mip.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/mip.pxd +40 -0
- sage/numerical/mip.pyx +3667 -0
- sage/numerical/sdp.cpython-314-x86_64-linux-gnu.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-x86_64-linux-gnu.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,1027 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-polyhedra
|
|
2
|
+
# sage.doctest: needs sage.rings.number_field
|
|
3
|
+
r"""
|
|
4
|
+
Find maximal angles between polyhedral convex cones
|
|
5
|
+
|
|
6
|
+
.. WARNING::
|
|
7
|
+
|
|
8
|
+
This module is considered internal and its contents are subject to
|
|
9
|
+
change at any time without (deprecation) warning. The stable
|
|
10
|
+
interface is
|
|
11
|
+
:meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.max_angle`.
|
|
12
|
+
|
|
13
|
+
Finding the maximal (or equivalently, the minimal) angle between two
|
|
14
|
+
polyhedral convex cones is a hard nonconvex optimization problem. The
|
|
15
|
+
problem for a single cone was introduced in [IS2005]_, and was later
|
|
16
|
+
extended in [SS2016]_ to two cones as a generalization of the
|
|
17
|
+
principal angle between two vector subspaces.
|
|
18
|
+
|
|
19
|
+
Seeger and Sossa proposed an algorithm in [SS2016]_ to find maximal
|
|
20
|
+
angles, and [Or2020]_ elaborates on that algorithm. It is this latest
|
|
21
|
+
improvement that is implemented (more or less) by this module. The
|
|
22
|
+
fact that perturbations of pointed cones may not change the answer too
|
|
23
|
+
much [Or2024]_ is taken into consideration to avoid pathological
|
|
24
|
+
cases.
|
|
25
|
+
|
|
26
|
+
This module is internal to SageMath; the interface presented to users
|
|
27
|
+
consists of a public method,
|
|
28
|
+
:meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.max_angle` for
|
|
29
|
+
polyhedral convex cones. Even though all of the functions in this
|
|
30
|
+
module are internal, some are more internal than others. There are a
|
|
31
|
+
few functions that are used only in doctests, and not by any code that
|
|
32
|
+
an end-user would run. Breaking somewhat with tradition, only those
|
|
33
|
+
methods have been prefixed with an underscore.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from sage.functions.trig import arccos
|
|
37
|
+
from sage.matrix.constructor import matrix
|
|
38
|
+
from sage.misc.lazy_import import lazy_import
|
|
39
|
+
from sage.rings.integer_ring import ZZ
|
|
40
|
+
from sage.rings.qqbar import AA
|
|
41
|
+
from sage.rings.rational_field import QQ
|
|
42
|
+
from sage.rings.real_double import RDF
|
|
43
|
+
|
|
44
|
+
lazy_import('sage.symbolic.constants', 'pi')
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _normalize_gevp_solution(gevp_solution):
|
|
48
|
+
r"""
|
|
49
|
+
Normalize the results of :func:`solve_gevp_nonzero` and
|
|
50
|
+
:func:`_solve_gevp_naive`.
|
|
51
|
+
|
|
52
|
+
Those two functions return solutions (pairs of vectors) to an
|
|
53
|
+
eigenvalue problem, but eigenvectors are only unique up to a
|
|
54
|
+
scalar multiple. This function normalizes those results so that
|
|
55
|
+
every eigenvector has a leading entry of positive one. This allows
|
|
56
|
+
us to identity equivalent solutions to those problems.
|
|
57
|
+
|
|
58
|
+
INPUT:
|
|
59
|
+
|
|
60
|
+
A quartet ``gevp_solution`` whose components are, in order:
|
|
61
|
+
|
|
62
|
+
- ``eigenvalue`` -- ignored
|
|
63
|
+
|
|
64
|
+
- ``xi`` -- first component of the `( \xi, \eta )` eigenvector
|
|
65
|
+
|
|
66
|
+
- ``eta`` -- second component of the `( \xi, \eta )` eigenvector
|
|
67
|
+
|
|
68
|
+
- ``multiplicity`` -- ignored
|
|
69
|
+
|
|
70
|
+
OUTPUT:
|
|
71
|
+
|
|
72
|
+
If `c` is the first nonzero component of the concatenated `( \xi,
|
|
73
|
+
\eta )` vector, then a quartet whose components are, in order:
|
|
74
|
+
|
|
75
|
+
- ``eigenvalue`` -- the unmodified ``eigenvalue`` argument
|
|
76
|
+
|
|
77
|
+
- ``xi*(1/c)`` -- the `\xi` component normalized so that the first
|
|
78
|
+
nonzero component of the concatenated vector
|
|
79
|
+
`( \xi, \eta )` is positive one
|
|
80
|
+
|
|
81
|
+
- ``eta*(1/c)`` -- the `\eta` component normalized so that the
|
|
82
|
+
first nonzero component of the concatenated vector
|
|
83
|
+
`( \xi, \eta )` is positive one
|
|
84
|
+
|
|
85
|
+
- ``multiplicity`` -- the unmodified ``multiplicity`` argument
|
|
86
|
+
|
|
87
|
+
If there is no such `c` (that is, if both `\xi` and `\eta` are
|
|
88
|
+
zero), then the entire input quartet is returned unmodified.
|
|
89
|
+
|
|
90
|
+
EXAMPLES::
|
|
91
|
+
|
|
92
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
93
|
+
....: _normalize_gevp_solution)
|
|
94
|
+
sage: s1 = (-1, vector(QQ,[0,-2]), vector(QQ,[1]), 1)
|
|
95
|
+
sage: _normalize_gevp_solution(s1)
|
|
96
|
+
(-1, (0, 1), (-1/2), 1)
|
|
97
|
+
sage: s2 = (1, vector(QQ,[0,0]), vector(QQ,[0,0,-1]), 2)
|
|
98
|
+
sage: _normalize_gevp_solution(s2)
|
|
99
|
+
(1, (0, 0), (0, 0, 1), 2)
|
|
100
|
+
"""
|
|
101
|
+
eigenvalue, xi, eta, multiplicity = gevp_solution
|
|
102
|
+
from itertools import chain
|
|
103
|
+
|
|
104
|
+
# We'll use this default of zero as the scaling factor if we don't
|
|
105
|
+
# find a better one; that is, if xi = eta = 0 -- in which case the
|
|
106
|
+
# additional multiplication by zero is a no-op.
|
|
107
|
+
scale = 0
|
|
108
|
+
for c in chain(xi, eta):
|
|
109
|
+
if c != 0:
|
|
110
|
+
scale = ~c
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
xi *= scale
|
|
114
|
+
xi.set_immutable()
|
|
115
|
+
eta *= scale
|
|
116
|
+
eta.set_immutable()
|
|
117
|
+
return (eigenvalue, xi, eta, multiplicity)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _random_admissible_cone(ambient_dim):
|
|
121
|
+
r"""
|
|
122
|
+
Generate a random cone in a lattice of dimension
|
|
123
|
+
``ambient_dim`` that isn't trivial.
|
|
124
|
+
|
|
125
|
+
This is a convenience method used to simplify some test cases. The
|
|
126
|
+
number of rays that the cone possesses is limited to two more than
|
|
127
|
+
``ambient_dim``; so, for example, you will not get more than five
|
|
128
|
+
rays in a three-dimensional space. This limits the amount of time
|
|
129
|
+
spent in any one test case. In contrast with the definition in
|
|
130
|
+
[Or2020]_ we consider the full ambient space to be admissible (it
|
|
131
|
+
doesn't hurt anything, and [Or2024]_ was forced to allow it).
|
|
132
|
+
|
|
133
|
+
INPUT:
|
|
134
|
+
|
|
135
|
+
- ``ambient_dim`` -- positive integer representing the dimension
|
|
136
|
+
of the ambient lattice in which the returned cone lives
|
|
137
|
+
|
|
138
|
+
OUTPUT:
|
|
139
|
+
|
|
140
|
+
A "random" nontrivial closed convex cone in a lattice of dimension
|
|
141
|
+
``ambient_dim``.
|
|
142
|
+
|
|
143
|
+
A :exc:`ValueError` is raised if ``ambient_dim`` is not
|
|
144
|
+
positive.
|
|
145
|
+
|
|
146
|
+
EXAMPLES:
|
|
147
|
+
|
|
148
|
+
The result has all of the desired properties::
|
|
149
|
+
|
|
150
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
151
|
+
....: _random_admissible_cone )
|
|
152
|
+
sage: K = _random_admissible_cone(5)
|
|
153
|
+
sage: K.lattice_dim()
|
|
154
|
+
5
|
|
155
|
+
sage: K.is_trivial()
|
|
156
|
+
False
|
|
157
|
+
|
|
158
|
+
Unless the ``ambient_dim`` argument is nonsense::
|
|
159
|
+
|
|
160
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
161
|
+
....: _random_admissible_cone )
|
|
162
|
+
sage: K = _random_admissible_cone(0)
|
|
163
|
+
Traceback (most recent call last):
|
|
164
|
+
...
|
|
165
|
+
ValueError: there are no nontrivial cones in dimension 0
|
|
166
|
+
"""
|
|
167
|
+
if ambient_dim < 1 or ambient_dim not in ZZ:
|
|
168
|
+
# The random_cone() method already crashes if we ask the
|
|
169
|
+
# impossible of it, but having this here emits a more sensible
|
|
170
|
+
# error message.
|
|
171
|
+
raise ValueError("there are no nontrivial cones in dimension %d"
|
|
172
|
+
% ambient_dim)
|
|
173
|
+
|
|
174
|
+
args = { 'min_ambient_dim': ambient_dim,
|
|
175
|
+
'max_ambient_dim': ambient_dim,
|
|
176
|
+
'min_rays': 1,
|
|
177
|
+
'max_rays': ambient_dim+2 }
|
|
178
|
+
|
|
179
|
+
from sage.geometry.cone import random_cone
|
|
180
|
+
return random_cone(**args)
|
|
181
|
+
|
|
182
|
+
return K
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def gevp_licis(G):
|
|
186
|
+
r"""
|
|
187
|
+
Return all nonempty subsets of indices for the columns of
|
|
188
|
+
``G`` that correspond to linearly independent sets (of columns of
|
|
189
|
+
``G``).
|
|
190
|
+
|
|
191
|
+
Mnemonic: linearly independent column-index subsets (LICIS).
|
|
192
|
+
|
|
193
|
+
The returned lists are all sorted in the same (the natural) order;
|
|
194
|
+
and are returned as lists so that they may be used to index into
|
|
195
|
+
the rows/columns of matrices.
|
|
196
|
+
|
|
197
|
+
INPUT:
|
|
198
|
+
|
|
199
|
+
- ``G`` -- the matrix whose linearly independent column index sets
|
|
200
|
+
we want
|
|
201
|
+
|
|
202
|
+
OUTPUT:
|
|
203
|
+
|
|
204
|
+
A generator that returns sorted lists of natural numbers. Each
|
|
205
|
+
generated list ``I`` is a set of indices corresponding to columns
|
|
206
|
+
of ``G`` that, when considered as a set, is linearly independent.
|
|
207
|
+
|
|
208
|
+
EXAMPLES:
|
|
209
|
+
|
|
210
|
+
The linearly independent subsets of the matrix corresponding to a
|
|
211
|
+
line (with two generators pointing in opposite directions) are the
|
|
212
|
+
one-element subsets, since the only two-element subset isn't
|
|
213
|
+
linearly independent::
|
|
214
|
+
|
|
215
|
+
sage: from sage.geometry.cone_critical_angles import gevp_licis
|
|
216
|
+
sage: K = Cone([(1,0),(-1,0)])
|
|
217
|
+
sage: G = matrix.column(K.rays())
|
|
218
|
+
sage: list(gevp_licis(G))
|
|
219
|
+
[[0], [1]]
|
|
220
|
+
|
|
221
|
+
The matrix for the trivial cone has no linearly independent
|
|
222
|
+
subsets, since we require them to be nonempty::
|
|
223
|
+
|
|
224
|
+
sage: from sage.geometry.cone_critical_angles import gevp_licis
|
|
225
|
+
sage: trivial_cone = cones.trivial(0)
|
|
226
|
+
sage: trivial_cone.is_trivial()
|
|
227
|
+
True
|
|
228
|
+
sage: list(gevp_licis(matrix.column(trivial_cone.rays())))
|
|
229
|
+
[]
|
|
230
|
+
|
|
231
|
+
All rays in the nonnegative orthant of `R^{n}` are
|
|
232
|
+
linearly independent, so we should get back `2^{n} - 1` subsets
|
|
233
|
+
after accounting for the absence of the empty set::
|
|
234
|
+
|
|
235
|
+
sage: from sage.geometry.cone_critical_angles import gevp_licis
|
|
236
|
+
sage: K = cones.nonnegative_orthant(3)
|
|
237
|
+
sage: G = matrix.column(K.rays())
|
|
238
|
+
sage: len(list(gevp_licis(G))) == 2^(K.nrays()) - 1
|
|
239
|
+
True
|
|
240
|
+
|
|
241
|
+
TESTS:
|
|
242
|
+
|
|
243
|
+
All sets corresponding to the returned indices should be linearly
|
|
244
|
+
independent::
|
|
245
|
+
|
|
246
|
+
sage: from sage.geometry.cone_critical_angles import gevp_licis
|
|
247
|
+
sage: K = random_cone(max_rays=8)
|
|
248
|
+
sage: G = matrix.column(K.rays())
|
|
249
|
+
sage: all( len(s) == K.rays(s).dimension() for s in gevp_licis(G) )
|
|
250
|
+
True
|
|
251
|
+
"""
|
|
252
|
+
from sage.matroids.linear_matroid import LinearMatroid
|
|
253
|
+
|
|
254
|
+
# There's a fast implementation of this for matroids, but we need
|
|
255
|
+
# to drop the empty set from its output and convert the rest to
|
|
256
|
+
# lists that are all sorted in the same order.
|
|
257
|
+
return map(sorted, filter(bool, LinearMatroid(G).independent_sets()))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _solve_gevp_naive(GG, HH, M, I, J):
|
|
261
|
+
r"""
|
|
262
|
+
Solve the generalized eigenvalue problem in Theorem 3
|
|
263
|
+
[Or2020]_ in a very naive way, by (slowly) inverting the matrices
|
|
264
|
+
and finding the eigenvalues in the product space.
|
|
265
|
+
|
|
266
|
+
This is used only for testing, to ensure that the smart way of
|
|
267
|
+
solving the generalized eigenvalue problem via
|
|
268
|
+
:func:`solve_gevp_zero` and :func:`solve_gevp_nonzero` returns the
|
|
269
|
+
same answers as the dumb way.
|
|
270
|
+
|
|
271
|
+
This returns a generator, like :func:`solve_gevp_zero` and
|
|
272
|
+
:func:`solve_gevp_nonzero`.
|
|
273
|
+
|
|
274
|
+
INPUT:
|
|
275
|
+
|
|
276
|
+
See the arguments for :func:`solve_gevp_nonzero`.
|
|
277
|
+
|
|
278
|
+
ALGORITHM:
|
|
279
|
+
|
|
280
|
+
We construct the two matrices `A` and `B` in Theorem 3 [Or2020]_
|
|
281
|
+
in block form, and then use the naive "inverse" method on `B` to
|
|
282
|
+
move it to the left and obtain `M = B^{-1}A`. We then compute the
|
|
283
|
+
right eigenvectors of the whole big matrix `M`.
|
|
284
|
+
|
|
285
|
+
EXAMPLES:
|
|
286
|
+
|
|
287
|
+
A simple usage example, that also appears as Example 3 in
|
|
288
|
+
[Or2020]_::
|
|
289
|
+
|
|
290
|
+
sage: from sage.geometry.cone_critical_angles import _solve_gevp_naive
|
|
291
|
+
sage: K = cones.nonnegative_orthant(2)
|
|
292
|
+
sage: G = matrix.column(K.rays())
|
|
293
|
+
sage: GG = G.transpose() * G
|
|
294
|
+
sage: I = [0]
|
|
295
|
+
sage: J = [1]
|
|
296
|
+
sage: list(_solve_gevp_naive(GG,GG,GG,I,J))
|
|
297
|
+
[(0, (1), (0), 2), (0, (0), (1), 2)]
|
|
298
|
+
|
|
299
|
+
Check Example 4 [Or2020]_ symbolically to ensure that we get
|
|
300
|
+
eigenspaces of dimension `n=2` corresponding to the eigenvalues
|
|
301
|
+
`\cos\theta = -1` and `\cos\theta = 1`::
|
|
302
|
+
|
|
303
|
+
sage: # needs sage.symbolic
|
|
304
|
+
sage: from sage.geometry.cone_critical_angles import _solve_gevp_naive
|
|
305
|
+
sage: g11,g12,g21,g22 = SR.var('g11,g12,g21,g22', domain='real')
|
|
306
|
+
sage: h11,h12,h21,h22 = SR.var('h11,h12,h21,h22', domain='real')
|
|
307
|
+
sage: gs = [[g11,g12], [g21,g22]]
|
|
308
|
+
sage: hs = [[h11,h12], [h21,h22]]
|
|
309
|
+
sage: G = matrix.column(gs)
|
|
310
|
+
sage: H = matrix.column(hs)
|
|
311
|
+
sage: GG = G.transpose() * G
|
|
312
|
+
sage: HH = H.transpose() * H
|
|
313
|
+
sage: M = G.transpose() * H
|
|
314
|
+
sage: I = [0, 1]
|
|
315
|
+
sage: J = [0, 1]
|
|
316
|
+
sage: all( v in [-1,1] and m == 2
|
|
317
|
+
....: for (v,_,_,m) in _solve_gevp_naive(GG,HH,M,I,J) )
|
|
318
|
+
True
|
|
319
|
+
"""
|
|
320
|
+
A = matrix.block([
|
|
321
|
+
[ZZ.zero(), M[I,J]],
|
|
322
|
+
[M.transpose()[J,I], ZZ.zero()]
|
|
323
|
+
])
|
|
324
|
+
B = matrix.block([
|
|
325
|
+
[GG[I,I], ZZ.zero()],
|
|
326
|
+
[ZZ.zero(), HH[J,J]]
|
|
327
|
+
])
|
|
328
|
+
M = B.inverse() * A
|
|
329
|
+
|
|
330
|
+
# We'll format the result to match the solve_gevp_nonzero() return value.
|
|
331
|
+
for (evalue, evectors, multiplicity) in M.eigenvectors_right():
|
|
332
|
+
for z in evectors:
|
|
333
|
+
xi = z[0:len(I)]
|
|
334
|
+
xi.set_immutable()
|
|
335
|
+
eta = z[len(I):]
|
|
336
|
+
eta.set_immutable()
|
|
337
|
+
yield (evalue, xi, eta, multiplicity)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def solve_gevp_zero(M, I, J):
|
|
341
|
+
r"""
|
|
342
|
+
Solve the generalized eigenvalue problem in Theorem 3
|
|
343
|
+
[Or2020]_ for a zero eigenvalue using Propositions 3 and 4
|
|
344
|
+
[Or2020]_.
|
|
345
|
+
|
|
346
|
+
INPUT:
|
|
347
|
+
|
|
348
|
+
- ``M`` -- the matrix whose `(i,j)`-th entry is the inner product
|
|
349
|
+
of `g_{i}` and `h_{j}` as in Proposition 6 [Or2020]_
|
|
350
|
+
|
|
351
|
+
- ``I`` -- a linearly independent column-index set for the matrix
|
|
352
|
+
`G` that appears in Theorem 3 [Or2020]_
|
|
353
|
+
|
|
354
|
+
- ``J`` -- a linearly independent column-index set for the matrix
|
|
355
|
+
`H` that appears in Theorem 3 [Or2020]_
|
|
356
|
+
|
|
357
|
+
OUTPUT:
|
|
358
|
+
|
|
359
|
+
A generator of ``(eigenvalue, xi, eta, multiplicity)`` quartets
|
|
360
|
+
where
|
|
361
|
+
|
|
362
|
+
- ``eigenvalue`` is zero (the eigenvalue of the system)
|
|
363
|
+
|
|
364
|
+
- ``xi`` is the first (length ``len(I)``) component of an
|
|
365
|
+
eigenvector associated with ``eigenvalue``
|
|
366
|
+
|
|
367
|
+
- ``eta`` is the second (length ``len(J)``) component of an
|
|
368
|
+
eigenvector associated with ``eigenvalue``
|
|
369
|
+
|
|
370
|
+
- ``multiplicity`` is the dimension of the eigenspace associated
|
|
371
|
+
with ``eigenvalue``
|
|
372
|
+
|
|
373
|
+
ALGORITHM:
|
|
374
|
+
|
|
375
|
+
Proposition 4 in [Or2020]_ is used.
|
|
376
|
+
|
|
377
|
+
EXAMPLES:
|
|
378
|
+
|
|
379
|
+
This particular configuration results in the zero matrix in the
|
|
380
|
+
eigenvalue problem, so the only solutions correspond to the
|
|
381
|
+
eigenvalue zero::
|
|
382
|
+
|
|
383
|
+
sage: from sage.geometry.cone_critical_angles import solve_gevp_zero
|
|
384
|
+
sage: K = cones.nonnegative_orthant(2)
|
|
385
|
+
sage: G = matrix.column(K.rays())
|
|
386
|
+
sage: GG = G.transpose() * G
|
|
387
|
+
sage: I = [0]
|
|
388
|
+
sage: J = [1]
|
|
389
|
+
sage: list(solve_gevp_zero(GG, I, J))
|
|
390
|
+
[(0, (1), (0), 2), (0, (0), (1), 2)]
|
|
391
|
+
"""
|
|
392
|
+
# A Cartesian product would be more appropriate here, but Sage
|
|
393
|
+
# isn't smart enough to figure out a basis for the product. So,
|
|
394
|
+
# we use the direct sum and then chop it up.
|
|
395
|
+
M_IJ = M[I,J]
|
|
396
|
+
xi_space = M_IJ.left_kernel()
|
|
397
|
+
eta_space = M_IJ.right_kernel()
|
|
398
|
+
|
|
399
|
+
fake_cartprod = xi_space.direct_sum(eta_space)
|
|
400
|
+
multiplicity = fake_cartprod.dimension()
|
|
401
|
+
|
|
402
|
+
for z in fake_cartprod.basis():
|
|
403
|
+
z1 = z[0:len(I)]
|
|
404
|
+
z1.set_immutable()
|
|
405
|
+
z2 = z[len(I):]
|
|
406
|
+
z2.set_immutable()
|
|
407
|
+
|
|
408
|
+
# The base ring of M will either be RDF or AA, which is enough
|
|
409
|
+
# to contain any eigenvalues that will arise... meaning that
|
|
410
|
+
# if we use the corresponding "zero" here, it will match the
|
|
411
|
+
# field of the eigenvalues returned by the nonzero function.
|
|
412
|
+
yield (M.base_ring().zero(), z1, z2, multiplicity)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def solve_gevp_nonzero(GG, HH, M, I, J):
|
|
416
|
+
r"""
|
|
417
|
+
Solve the generalized eigenvalue problem in Theorem 3
|
|
418
|
+
[Or2020]_ for a nonzero eigenvalue using Propositions 3 and 5
|
|
419
|
+
[Or2020]_.
|
|
420
|
+
|
|
421
|
+
INPUT:
|
|
422
|
+
|
|
423
|
+
- ``GG`` -- the matrix whose `(i,j)`-th entry is the inner product
|
|
424
|
+
of `g_{i}` and `g_{j}`, which are in turn the `i`-th and `j`-th
|
|
425
|
+
columns of the matrix `G` in Theorem 3 [Or2020]_
|
|
426
|
+
|
|
427
|
+
- ``HH`` -- the matrix whose `(i,j)`-th entry is the inner product
|
|
428
|
+
of `h_{i}` and `h_{j}`, which are in turn the `i`-th and `j`-th
|
|
429
|
+
columns of the matrix `H` in Theorem 3 [Or2020]_
|
|
430
|
+
|
|
431
|
+
- ``M`` -- the matrix whose `(i,j)`-th entry is the inner product
|
|
432
|
+
of `g_{i}` and `h_{j}` as in Proposition 6 in [Or2020]_
|
|
433
|
+
|
|
434
|
+
- ``I`` -- a linearly independent column-index set for the matrix
|
|
435
|
+
`G` that appears in Theorem 3 [Or2020]_
|
|
436
|
+
|
|
437
|
+
- ``J`` -- a linearly independent column-index set for the matrix
|
|
438
|
+
`H` that appears in Theorem 3 [Or2020]_
|
|
439
|
+
|
|
440
|
+
OUTPUT:
|
|
441
|
+
|
|
442
|
+
A generator of ``(eigenvalue, xi, eta, multiplicity)`` quartets
|
|
443
|
+
where
|
|
444
|
+
|
|
445
|
+
- ``eigenvalue`` is a real eigenvalue of the system
|
|
446
|
+
|
|
447
|
+
- ``xi`` is the first (length ``len(I)``) component of an
|
|
448
|
+
eigenvector associated with ``eigenvalue``
|
|
449
|
+
|
|
450
|
+
- ``eta`` is the second (length ``len(J)``) component of an
|
|
451
|
+
eigenvector associated with ``eigenvalue``
|
|
452
|
+
|
|
453
|
+
- ``multiplicity`` is the dimension of the eigenspace associated
|
|
454
|
+
with ``eigenvalue``
|
|
455
|
+
|
|
456
|
+
Note that we do not return a basis for each eigenspace along with
|
|
457
|
+
its eigenvalue. For the application we have in mind, an eigenspace
|
|
458
|
+
of dimension greater than one (so, ``multiplicity > 1``) is an
|
|
459
|
+
error. As such, our return value is optimized for convenience in
|
|
460
|
+
the non-error case, where there is only one eigenvector (spanning
|
|
461
|
+
a one-dimensional eigenspace) associated with each eigenvalue.
|
|
462
|
+
|
|
463
|
+
ALGORITHM:
|
|
464
|
+
|
|
465
|
+
According to Proposition 5 [Or2020]_, the solutions corresponding
|
|
466
|
+
to nonzero eigenvalues can be found by solving a smaller
|
|
467
|
+
eigenvalue problem in only the variable `\xi`. So, we do that, and
|
|
468
|
+
then solve for `\eta` in terms of `\xi` as described in the
|
|
469
|
+
proposition.
|
|
470
|
+
|
|
471
|
+
EXAMPLES:
|
|
472
|
+
|
|
473
|
+
When the zero solutions are included, this function returns the
|
|
474
|
+
same solutions as the naive method on the Schur cone in three
|
|
475
|
+
dimensions::
|
|
476
|
+
|
|
477
|
+
sage: from itertools import chain
|
|
478
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
479
|
+
....: _normalize_gevp_solution,
|
|
480
|
+
....: _solve_gevp_naive,
|
|
481
|
+
....: gevp_licis,
|
|
482
|
+
....: solve_gevp_nonzero,
|
|
483
|
+
....: solve_gevp_zero)
|
|
484
|
+
sage: K = cones.schur(3)
|
|
485
|
+
sage: gs = [g.change_ring(AA).normalized() for g in K]
|
|
486
|
+
sage: G = matrix.column(gs)
|
|
487
|
+
sage: GG = G.transpose() * G
|
|
488
|
+
sage: G_index_sets = list(gevp_licis(G))
|
|
489
|
+
sage: all(
|
|
490
|
+
....: set(
|
|
491
|
+
....: _normalize_gevp_solution(s)
|
|
492
|
+
....: for s in
|
|
493
|
+
....: chain(
|
|
494
|
+
....: solve_gevp_zero(GG, I, J),
|
|
495
|
+
....: solve_gevp_nonzero(GG, GG, GG, I, J)
|
|
496
|
+
....: )
|
|
497
|
+
....: )
|
|
498
|
+
....: ==
|
|
499
|
+
....: set(
|
|
500
|
+
....: _normalize_gevp_solution(s)
|
|
501
|
+
....: for s in
|
|
502
|
+
....: _solve_gevp_naive(GG,GG,GG,I,J)
|
|
503
|
+
....: )
|
|
504
|
+
....: for I in G_index_sets
|
|
505
|
+
....: for J in G_index_sets
|
|
506
|
+
....: )
|
|
507
|
+
True
|
|
508
|
+
|
|
509
|
+
TESTS:
|
|
510
|
+
|
|
511
|
+
This function should return the same solutions (with zero included,
|
|
512
|
+
of course) as the naive implementation even for random cones::
|
|
513
|
+
|
|
514
|
+
sage: # long time
|
|
515
|
+
sage: from itertools import chain
|
|
516
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
517
|
+
....: _normalize_gevp_solution,
|
|
518
|
+
....: _random_admissible_cone,
|
|
519
|
+
....: _solve_gevp_naive,
|
|
520
|
+
....: gevp_licis,
|
|
521
|
+
....: solve_gevp_nonzero,
|
|
522
|
+
....: solve_gevp_zero)
|
|
523
|
+
sage: n = ZZ.random_element(1,3)
|
|
524
|
+
sage: P = _random_admissible_cone(ambient_dim=n)
|
|
525
|
+
sage: Q = _random_admissible_cone(ambient_dim=n)
|
|
526
|
+
sage: gs = [g.change_ring(AA).normalized() for g in P]
|
|
527
|
+
sage: G = matrix.column(gs)
|
|
528
|
+
sage: GG = G.transpose() * G
|
|
529
|
+
sage: hs = [h.change_ring(AA).normalized() for h in Q]
|
|
530
|
+
sage: H = matrix.column(hs)
|
|
531
|
+
sage: HH = H.transpose() * H
|
|
532
|
+
sage: M = G.transpose() * H
|
|
533
|
+
sage: G_index_sets = list(gevp_licis(G))
|
|
534
|
+
sage: H_index_sets = list(gevp_licis(H))
|
|
535
|
+
sage: all(
|
|
536
|
+
....: set(
|
|
537
|
+
....: _normalize_gevp_solution(s)
|
|
538
|
+
....: for s in
|
|
539
|
+
....: chain(
|
|
540
|
+
....: solve_gevp_zero(M, I, J),
|
|
541
|
+
....: solve_gevp_nonzero(GG, HH, M, I, J)
|
|
542
|
+
....: )
|
|
543
|
+
....: )
|
|
544
|
+
....: ==
|
|
545
|
+
....: set(
|
|
546
|
+
....: _normalize_gevp_solution(s)
|
|
547
|
+
....: for s in
|
|
548
|
+
....: _solve_gevp_naive(GG, HH, M, I, J)
|
|
549
|
+
....: )
|
|
550
|
+
....: for I in G_index_sets
|
|
551
|
+
....: for J in H_index_sets
|
|
552
|
+
....: )
|
|
553
|
+
True
|
|
554
|
+
|
|
555
|
+
According to Proposition 7 [Or2020]_, the only eigenvalues that
|
|
556
|
+
arise when either ``G`` or ``H`` is invertible are `-1`, `0`, and
|
|
557
|
+
`1`::
|
|
558
|
+
|
|
559
|
+
sage: # long time
|
|
560
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
561
|
+
....: _random_admissible_cone,
|
|
562
|
+
....: gevp_licis,
|
|
563
|
+
....: solve_gevp_nonzero)
|
|
564
|
+
sage: n = ZZ.random_element(1,3)
|
|
565
|
+
sage: P = _random_admissible_cone(ambient_dim=n)
|
|
566
|
+
sage: Q = _random_admissible_cone(ambient_dim=n)
|
|
567
|
+
sage: gs = [g.change_ring(AA).normalized() for g in P]
|
|
568
|
+
sage: hs = [h.change_ring(AA).normalized() for h in Q]
|
|
569
|
+
sage: G = matrix.column(gs)
|
|
570
|
+
sage: GG = G.transpose() * G
|
|
571
|
+
sage: H = matrix.column(hs)
|
|
572
|
+
sage: HH = H.transpose() * H
|
|
573
|
+
sage: M = G.transpose() * H
|
|
574
|
+
sage: from itertools import product
|
|
575
|
+
sage: all(
|
|
576
|
+
....: (v in [-1,0,1]
|
|
577
|
+
....: for (v,_,_,_) in solve_gevp_nonzero(GG, HH, M, I, J))
|
|
578
|
+
....: for (I,J) in product(gevp_licis(G),gevp_licis(H))
|
|
579
|
+
....: if len(I) == n or len(J) == n )
|
|
580
|
+
True
|
|
581
|
+
"""
|
|
582
|
+
if len(J) < len(I):
|
|
583
|
+
# We can always opt to solve the smaller problem. Reading the
|
|
584
|
+
# first three assignments below, you should be able to
|
|
585
|
+
# convince yourself that switching GG <-> HH, I <-> J, and
|
|
586
|
+
# transposing M does in fact switch from the "xi problem" to
|
|
587
|
+
# the "eta problem."
|
|
588
|
+
yield from ((l, xi, eta, m)
|
|
589
|
+
for (l, eta, xi, m)
|
|
590
|
+
in solve_gevp_nonzero(HH, GG, M.transpose(), J, I))
|
|
591
|
+
else:
|
|
592
|
+
M_IJ = M[I,J]
|
|
593
|
+
G_I_pinv_H_J = GG[I,I].inverse_positive_definite() * M_IJ
|
|
594
|
+
H_J_pinv_G_I = HH[J,J].inverse_positive_definite() * M_IJ.transpose()
|
|
595
|
+
L = (G_I_pinv_H_J * H_J_pinv_G_I)
|
|
596
|
+
|
|
597
|
+
for (sigma, xis, m) in L.eigenvectors_right():
|
|
598
|
+
if sigma > 0:
|
|
599
|
+
# Avoid recomputing these for each xi in xis
|
|
600
|
+
sigma_sqrt = sigma.sqrt()
|
|
601
|
+
inv_sqrt = ~sigma_sqrt
|
|
602
|
+
pm_sqrt_inv_pairs = [
|
|
603
|
+
(-sigma_sqrt, -inv_sqrt),
|
|
604
|
+
(sigma_sqrt, inv_sqrt)
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
for xi in xis:
|
|
608
|
+
for l, li in pm_sqrt_inv_pairs:
|
|
609
|
+
eta = li * H_J_pinv_G_I*xi
|
|
610
|
+
eta.set_immutable()
|
|
611
|
+
yield (l, xi, eta, m)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def compute_gevp_M(gs, hs):
|
|
615
|
+
r"""
|
|
616
|
+
Compute the matrix `M` whose `(i,j)`-th entry is the inner
|
|
617
|
+
product of ``gs[i]`` and ``hs[j]``.
|
|
618
|
+
|
|
619
|
+
This is the "generalized Gram matrix" appearing in Proposition 6
|
|
620
|
+
in [Or2020]_. For efficiency, we also return the minimal pair,
|
|
621
|
+
whose inner product is minimal among the entries of `M`. This
|
|
622
|
+
allows our consumer to bail out immediately (knowing the optimal
|
|
623
|
+
pair!) if it turns out that the maximal angle is acute; i.e. if
|
|
624
|
+
the smallest entry of `M` is nonnegative.
|
|
625
|
+
|
|
626
|
+
INPUT:
|
|
627
|
+
|
|
628
|
+
- ``gs`` -- a linearly independent list of unit-norm generators
|
|
629
|
+
for the cone `P`
|
|
630
|
+
|
|
631
|
+
- ``hs`` -- a linearly independent list of unit-norm generators
|
|
632
|
+
for the cone `Q`
|
|
633
|
+
|
|
634
|
+
OUTPUT: a tuple containing four elements, in order:
|
|
635
|
+
|
|
636
|
+
- The matrix `M` described in Proposition 6
|
|
637
|
+
|
|
638
|
+
- The minimal entry in the matrix `M`
|
|
639
|
+
|
|
640
|
+
- A vector in ``gs`` that achieves that minimal inner product
|
|
641
|
+
along with the next element of the tuple
|
|
642
|
+
|
|
643
|
+
- A vector in ``hs`` that achieves the minimal inner product
|
|
644
|
+
along with the previous element in the tuple
|
|
645
|
+
|
|
646
|
+
EXAMPLES::
|
|
647
|
+
|
|
648
|
+
sage: from sage.geometry.cone_critical_angles import compute_gevp_M
|
|
649
|
+
sage: P = Cone([ (1,2,0), (3,4,0) ])
|
|
650
|
+
sage: Q = Cone([ (-1,4,1), (5,-2,-1), (-1,-1,5) ])
|
|
651
|
+
sage: gs = [g.change_ring(QQ) for g in P]
|
|
652
|
+
sage: hs = [h.change_ring(QQ) for h in Q]
|
|
653
|
+
sage: M = compute_gevp_M(gs, hs)[0]
|
|
654
|
+
sage: all( M[i][j] == gs[i].inner_product(hs[j])
|
|
655
|
+
....: for i in range(P.nrays())
|
|
656
|
+
....: for j in range(Q.nrays()) )
|
|
657
|
+
True
|
|
658
|
+
|
|
659
|
+
TESTS:
|
|
660
|
+
|
|
661
|
+
The products `(G_{I})^{T}H_{J}` correspond to
|
|
662
|
+
submatrices of the "generalized Gram matrix" `M` in Proposition
|
|
663
|
+
6. Note that SageMath does (row,column) indexing but [Or2020]_
|
|
664
|
+
does (column,row) indexing::
|
|
665
|
+
|
|
666
|
+
sage: from sage.geometry.cone_critical_angles import (
|
|
667
|
+
....: _random_admissible_cone,
|
|
668
|
+
....: compute_gevp_M,
|
|
669
|
+
....: gevp_licis)
|
|
670
|
+
sage: n = ZZ.random_element(1,4)
|
|
671
|
+
sage: n = ZZ.random_element(1,8) # long time
|
|
672
|
+
sage: P = _random_admissible_cone(ambient_dim=n)
|
|
673
|
+
sage: Q = _random_admissible_cone(ambient_dim=n)
|
|
674
|
+
sage: gs = [g.change_ring(QQ) for g in P]
|
|
675
|
+
sage: hs = [h.change_ring(QQ) for h in Q]
|
|
676
|
+
sage: M = compute_gevp_M(gs,hs)[0]
|
|
677
|
+
sage: f = lambda i,j: gs[i].inner_product(hs[j])
|
|
678
|
+
sage: expected_M = matrix(QQ, P.nrays(), Q.nrays(), f)
|
|
679
|
+
sage: M == expected_M
|
|
680
|
+
True
|
|
681
|
+
sage: G = matrix.column(gs)
|
|
682
|
+
sage: H = matrix.column(hs)
|
|
683
|
+
sage: def _test_indexing(I, J):
|
|
684
|
+
....: G_I = G.matrix_from_columns(I)
|
|
685
|
+
....: H_J = H.matrix_from_columns(J)
|
|
686
|
+
....: return (G_I.transpose()*H_J == M[I,J]
|
|
687
|
+
....: and
|
|
688
|
+
....: H_J.transpose()*G_I == M.transpose()[J,I])
|
|
689
|
+
sage: G_index_sets = list(gevp_licis(G))
|
|
690
|
+
sage: H_index_sets = list(gevp_licis(H))
|
|
691
|
+
sage: all( _test_indexing(I,J) for I in G_index_sets
|
|
692
|
+
....: for J in H_index_sets )
|
|
693
|
+
True
|
|
694
|
+
"""
|
|
695
|
+
min_u = gs[0]
|
|
696
|
+
min_v = hs[0]
|
|
697
|
+
min_ip = min_u.inner_product(min_v)
|
|
698
|
+
|
|
699
|
+
M = []
|
|
700
|
+
for g in gs:
|
|
701
|
+
M_i = []
|
|
702
|
+
for h in hs:
|
|
703
|
+
val = g.inner_product(h)
|
|
704
|
+
M_i.append(val)
|
|
705
|
+
if (val < min_ip):
|
|
706
|
+
min_ip = val
|
|
707
|
+
min_u = g
|
|
708
|
+
min_v = h
|
|
709
|
+
M.append(M_i)
|
|
710
|
+
|
|
711
|
+
return (matrix(M), min_ip, min_u, min_v)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def check_gevp_feasibility(cos_theta, xi, eta, G_I, G_I_c_T,
|
|
715
|
+
H_J, H_J_c_T, epsilon):
|
|
716
|
+
r"""
|
|
717
|
+
Determine if a solution to the generalized eigenvalue problem
|
|
718
|
+
in Theorem 3 [Or2020]_ is feasible.
|
|
719
|
+
|
|
720
|
+
Implementation detail: we take four matrices that we are capable
|
|
721
|
+
of computing as parameters instead, because we will be called in a
|
|
722
|
+
nested loop "for all `I`... and for all `J`..." The data
|
|
723
|
+
corresponding to `I` should be computed only once, which means
|
|
724
|
+
that we can't do it here -- it needs to be done outside of the `J`
|
|
725
|
+
loop. For symmetry (and to avoid relying on too many
|
|
726
|
+
cross-function implementation details), we also insist that the
|
|
727
|
+
`J` data be passed in.
|
|
728
|
+
|
|
729
|
+
INPUT:
|
|
730
|
+
|
|
731
|
+
- ``cos_theta`` -- an eigenvalue corresponding to
|
|
732
|
+
`( \xi, \eta )`
|
|
733
|
+
|
|
734
|
+
- ``xi`` -- first component of the `( \xi, \eta )` eigenvector
|
|
735
|
+
|
|
736
|
+
- ``eta`` -- second component of the `( \xi, \eta )` eigenvector
|
|
737
|
+
|
|
738
|
+
- ``G_I`` -- the submatrix of `G` with columns indexed by `I`
|
|
739
|
+
|
|
740
|
+
- ``G_I_c_T`` -- a matrix whose rows are the non-`I` columns of `G`
|
|
741
|
+
|
|
742
|
+
- ``H_J`` -- the submatrix of `H` with columns indexed by `J`
|
|
743
|
+
|
|
744
|
+
- ``H_J_c_T`` -- a matrix whose rows are the non-`J` columns of `H`
|
|
745
|
+
|
|
746
|
+
- ``epsilon`` -- the tolerance to use when making comparisons
|
|
747
|
+
|
|
748
|
+
OUTPUT:
|
|
749
|
+
|
|
750
|
+
A triple containing (in order),
|
|
751
|
+
|
|
752
|
+
- a boolean,
|
|
753
|
+
- a vector in the cone `P` (of the same length as ``xi``), and
|
|
754
|
+
- a vector in the cone `Q` (of the same length as ``eta``).
|
|
755
|
+
|
|
756
|
+
If `( \xi, \eta )` is feasible, we return ``(True, u, v)`` where `u`
|
|
757
|
+
and `v` are the vectors in `P` and `Q` respectively that form the
|
|
758
|
+
the angle `\theta`.
|
|
759
|
+
|
|
760
|
+
If `( \xi, \eta )` is **not** feasible, then we return ``(False, 0, 0)``
|
|
761
|
+
where ``0`` should be interpreted to mean the zero vector in the
|
|
762
|
+
appropriate space.
|
|
763
|
+
|
|
764
|
+
EXAMPLES:
|
|
765
|
+
|
|
766
|
+
If `\xi` has any components less than "zero," it isn't feasible::
|
|
767
|
+
|
|
768
|
+
sage: from sage.geometry.cone_critical_angles import(
|
|
769
|
+
....: check_gevp_feasibility)
|
|
770
|
+
sage: xi = vector(QQ, [-1,1])
|
|
771
|
+
sage: eta = vector(QQ, [1,1,1])
|
|
772
|
+
sage: check_gevp_feasibility(0,xi,eta,None,None,None,None,0)
|
|
773
|
+
(False, (0, 0), (0, 0, 0))
|
|
774
|
+
|
|
775
|
+
If `\eta` has any components less than "zero," it isn't feasible::
|
|
776
|
+
|
|
777
|
+
sage: from sage.geometry.cone_critical_angles import(
|
|
778
|
+
....: check_gevp_feasibility)
|
|
779
|
+
sage: xi = vector(QQ, [2])
|
|
780
|
+
sage: eta = vector(QQ, [1,-4,4,5])
|
|
781
|
+
sage: check_gevp_feasibility(0,xi,eta,None,None,None,None,0)
|
|
782
|
+
(False, (0), (0, 0, 0, 0))
|
|
783
|
+
|
|
784
|
+
If `\xi` and `\eta` are equal and if `G_{I}` and `H_{J}` are not,
|
|
785
|
+
then the copy of `\eta` that's been scaled by the norm of `G_{I}\xi`
|
|
786
|
+
generally won't satisfy its norm-equality constraint::
|
|
787
|
+
|
|
788
|
+
sage: from sage.geometry.cone_critical_angles import(
|
|
789
|
+
....: check_gevp_feasibility)
|
|
790
|
+
sage: xi = vector(QQ, [1,1])
|
|
791
|
+
sage: eta = xi
|
|
792
|
+
sage: G_I = matrix.identity(QQ,2)
|
|
793
|
+
sage: H_J = 2*G_I
|
|
794
|
+
sage: check_gevp_feasibility(0,xi,eta,G_I,None,H_J,None,0) # needs sage.symbolic
|
|
795
|
+
(False, (0, 0), (0, 0))
|
|
796
|
+
|
|
797
|
+
When `\cos\theta` is zero, the inequality (42) in Theorem 7.3
|
|
798
|
+
[SS2016]_ is just an inner product with `v` which we can make
|
|
799
|
+
positive by ensuring that all of the entries of `H_{J}` are
|
|
800
|
+
positive. So, if any of the rows of ``G_I_c_T`` contain a negative
|
|
801
|
+
entry, (42) will fail::
|
|
802
|
+
|
|
803
|
+
sage: from sage.geometry.cone_critical_angles import(
|
|
804
|
+
....: check_gevp_feasibility)
|
|
805
|
+
sage: xi = vector(QQ, [1/2,1/2,1/2,1/2])
|
|
806
|
+
sage: eta = xi
|
|
807
|
+
sage: G_I = matrix.identity(QQ,4)
|
|
808
|
+
sage: G_I_c_T = matrix(QQ, [[0,-1,0,0]])
|
|
809
|
+
sage: H_J = G_I
|
|
810
|
+
sage: check_gevp_feasibility(0,xi,eta,G_I,G_I_c_T,H_J,None,0) # needs sage.symbolic
|
|
811
|
+
(False, (0, 0, 0, 0), (0, 0, 0, 0))
|
|
812
|
+
|
|
813
|
+
Likewise we can make (43) fail in exactly the same way::
|
|
814
|
+
|
|
815
|
+
sage: from sage.geometry.cone_critical_angles import(
|
|
816
|
+
....: check_gevp_feasibility)
|
|
817
|
+
sage: xi = vector(QQ, [1/2,1/2,1/2,1/2])
|
|
818
|
+
sage: eta = xi
|
|
819
|
+
sage: G_I = matrix.identity(QQ,4)
|
|
820
|
+
sage: G_I_c_T = matrix(QQ, [[0,1,0,0]])
|
|
821
|
+
sage: H_J = G_I
|
|
822
|
+
sage: H_J_c_T = matrix(QQ, [[0,-1,0,0]])
|
|
823
|
+
sage: check_gevp_feasibility(0,xi,eta,G_I,G_I_c_T,H_J,H_J_c_T,0) # needs sage.symbolic
|
|
824
|
+
(False, (0, 0, 0, 0), (0, 0, 0, 0))
|
|
825
|
+
|
|
826
|
+
Finally, if we ensure that everything works, we get back a feasible
|
|
827
|
+
result along with the vectors (scaled `\xi` and `\eta`) that worked::
|
|
828
|
+
|
|
829
|
+
sage: from sage.geometry.cone_critical_angles import(
|
|
830
|
+
....: check_gevp_feasibility)
|
|
831
|
+
sage: xi = vector(QQ, [1/2,1/2,1/2,1/2])
|
|
832
|
+
sage: eta = xi
|
|
833
|
+
sage: G_I = matrix.identity(QQ,4)
|
|
834
|
+
sage: G_I_c_T = matrix(QQ, [[0,1,0,0]])
|
|
835
|
+
sage: H_J = G_I
|
|
836
|
+
sage: H_J_c_T = matrix(QQ, [[0,1,0,0]])
|
|
837
|
+
sage: check_gevp_feasibility(0,xi,eta,G_I,G_I_c_T,H_J,H_J_c_T,0) # needs sage.symbolic
|
|
838
|
+
(True, (1/2, 1/2, 1/2, 1/2), (1/2, 1/2, 1/2, 1/2))
|
|
839
|
+
"""
|
|
840
|
+
infeasible_result = (False, 0*xi, 0*eta)
|
|
841
|
+
if min(xi) <= -epsilon or min(eta) <= -epsilon:
|
|
842
|
+
# xi or eta isn't in the interior of the nonnegative orthant,
|
|
843
|
+
# so skip this (non-)solution.
|
|
844
|
+
return infeasible_result
|
|
845
|
+
|
|
846
|
+
# Rescale xi to satisfy (44), and rescale eta by the same amount,
|
|
847
|
+
# because (xi,eta) needs to remain in the same one-dimensional
|
|
848
|
+
# eigenspace.
|
|
849
|
+
scale = ~((G_I*xi).norm())
|
|
850
|
+
xi_hat = xi * scale
|
|
851
|
+
eta_hat = eta * scale
|
|
852
|
+
|
|
853
|
+
# Now check that (45) is satisfied.
|
|
854
|
+
if ((H_J*eta_hat).norm() - 1).abs() > epsilon:
|
|
855
|
+
return infeasible_result
|
|
856
|
+
|
|
857
|
+
# And check that (42,43) are satisfied.
|
|
858
|
+
v = H_J * eta_hat
|
|
859
|
+
rhs = v - cos_theta*G_I*xi_hat
|
|
860
|
+
|
|
861
|
+
if any(x < -epsilon for x in G_I_c_T * rhs):
|
|
862
|
+
return infeasible_result
|
|
863
|
+
|
|
864
|
+
u = G_I * xi_hat
|
|
865
|
+
rhs = u - cos_theta*H_J*eta_hat
|
|
866
|
+
if any(x < -epsilon for x in H_J_c_T * rhs):
|
|
867
|
+
return infeasible_result
|
|
868
|
+
|
|
869
|
+
return (True, u, v)
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def max_angle(P, Q, exact, epsilon):
|
|
873
|
+
r"""
|
|
874
|
+
Find the maximal angle between the cones `P` and `Q`.
|
|
875
|
+
|
|
876
|
+
This implements
|
|
877
|
+
:meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.max_angle`,
|
|
878
|
+
which should be fully documented.
|
|
879
|
+
|
|
880
|
+
EXAMPLES:
|
|
881
|
+
|
|
882
|
+
For the sake of the user interface, the argument validation for
|
|
883
|
+
this function is performed in the associated cone method; we can
|
|
884
|
+
therefore crash it by feeding it invalid input like an
|
|
885
|
+
inadmissible cone::
|
|
886
|
+
|
|
887
|
+
sage: from sage.geometry.cone_critical_angles import max_angle
|
|
888
|
+
sage: K = cones.trivial(3)
|
|
889
|
+
sage: max_angle(K,K,True,0)
|
|
890
|
+
Traceback (most recent call last):
|
|
891
|
+
...
|
|
892
|
+
IndexError: list index out of range
|
|
893
|
+
"""
|
|
894
|
+
# The lattice dimensions of P and Q are guaranteed to be equal
|
|
895
|
+
# because the cone method checks it before calling us.
|
|
896
|
+
n = P.lattice_dim()
|
|
897
|
+
|
|
898
|
+
ring = RDF
|
|
899
|
+
if exact:
|
|
900
|
+
ring = AA
|
|
901
|
+
# For some reason we can go RR -> QQ -> AA, but not
|
|
902
|
+
# straight from RR to AA.
|
|
903
|
+
epsilon = QQ(epsilon)
|
|
904
|
+
epsilon = ring(epsilon)
|
|
905
|
+
|
|
906
|
+
# First check if P is contained in the dual of Q. Keep track of
|
|
907
|
+
# the minimum inner product (and associated vectors) while doing
|
|
908
|
+
# so; then if P is contained in dual(Q), we just return the pair
|
|
909
|
+
# with the smallest inner product.
|
|
910
|
+
gs = [g.change_ring(ring).normalized() for g in P]
|
|
911
|
+
Q_is_P = (P == Q) # This is used again later
|
|
912
|
+
if Q_is_P:
|
|
913
|
+
hs = gs
|
|
914
|
+
else:
|
|
915
|
+
hs = [h.change_ring(ring).normalized() for h in Q]
|
|
916
|
+
|
|
917
|
+
(M, min_ip, min_u, min_v) = compute_gevp_M(gs,hs)
|
|
918
|
+
|
|
919
|
+
if min_ip >= 0: # The maximal angle is acute!
|
|
920
|
+
return (arccos(min_ip), min_u, min_v)
|
|
921
|
+
|
|
922
|
+
# Also check to see if the maximal angle is pi. In particular this
|
|
923
|
+
# is true when either P or Q is the entire ambient space.
|
|
924
|
+
P_and_negative_Q = P.intersection(-Q)
|
|
925
|
+
if not (P_and_negative_Q.is_trivial()):
|
|
926
|
+
u = P_and_negative_Q.ray(0).change_ring(ring).normalized()
|
|
927
|
+
v = -u
|
|
928
|
+
return (pi, u, v)
|
|
929
|
+
|
|
930
|
+
# When P == Q, GG and HH are both just M. We rule out the
|
|
931
|
+
# cardinality ``n`` in the index sets because it will eventually
|
|
932
|
+
# result in a GEVP whose only solutions are lambda in {-1,0,1};
|
|
933
|
+
# none of which we want! We rule out 0 and 1 with the acute check,
|
|
934
|
+
# and -1 with the pi (P_and_negative_Q) check.
|
|
935
|
+
#
|
|
936
|
+
# It's VERY IMPORTANT that we construct lists from the index set
|
|
937
|
+
# generators, because we're going to use them in a nested loop!
|
|
938
|
+
G = matrix.column(gs)
|
|
939
|
+
G_index_sets = [s for s in gevp_licis(G) if not len(s) == n]
|
|
940
|
+
|
|
941
|
+
if Q_is_P:
|
|
942
|
+
GG = M
|
|
943
|
+
H = G
|
|
944
|
+
HH = M
|
|
945
|
+
H_index_sets = G_index_sets
|
|
946
|
+
else:
|
|
947
|
+
GG = G.transpose() * G
|
|
948
|
+
H = matrix.column(hs)
|
|
949
|
+
HH = H.transpose() * H
|
|
950
|
+
H_index_sets = [s for s in gevp_licis(H) if not len(s) == n]
|
|
951
|
+
|
|
952
|
+
# Keep track of the (cos-theta, xi, eta, multiplicity) tuples with
|
|
953
|
+
# multiplicity > 1. These are only a problem if they could
|
|
954
|
+
# potentially be maximal. Therefore, we want to inspect them AFTER
|
|
955
|
+
# we've checked all of the multiplicity=1 angles and found the
|
|
956
|
+
# largest. This allows us to ignore most of the problematic
|
|
957
|
+
# eigenspaces, independent of the order in which we run through I,J.
|
|
958
|
+
big_eigenspaces = []
|
|
959
|
+
|
|
960
|
+
for I in G_index_sets:
|
|
961
|
+
G_I = G.matrix_from_columns(I)
|
|
962
|
+
I_complement = [i for i in range(P.nrays()) if i not in I]
|
|
963
|
+
G_I_c_T = G.matrix_from_columns(I_complement).transpose()
|
|
964
|
+
|
|
965
|
+
for J in H_index_sets:
|
|
966
|
+
J_complement = [j for j in range(Q.nrays()) if j not in J]
|
|
967
|
+
H_J = H.matrix_from_columns(J)
|
|
968
|
+
H_J_c_T = H.matrix_from_columns(J_complement).transpose()
|
|
969
|
+
|
|
970
|
+
for (cos_theta,xi,eta,mult) in solve_gevp_nonzero(GG, HH, M, I, J):
|
|
971
|
+
|
|
972
|
+
if cos_theta >= min_ip:
|
|
973
|
+
# This potential critical angle is smaller than or
|
|
974
|
+
# equal to one that we've already found. Why
|
|
975
|
+
# bother?
|
|
976
|
+
continue
|
|
977
|
+
|
|
978
|
+
if cos_theta == -1:
|
|
979
|
+
# We already ruled this case out with the
|
|
980
|
+
# "P_and_negative_Q" trick.
|
|
981
|
+
continue
|
|
982
|
+
|
|
983
|
+
(is_feasible, u, v) = check_gevp_feasibility(cos_theta,
|
|
984
|
+
xi,
|
|
985
|
+
eta,
|
|
986
|
+
G_I,
|
|
987
|
+
G_I_c_T,
|
|
988
|
+
H_J,
|
|
989
|
+
H_J_c_T,
|
|
990
|
+
epsilon)
|
|
991
|
+
|
|
992
|
+
if is_feasible:
|
|
993
|
+
min_ip = cos_theta
|
|
994
|
+
min_u = u
|
|
995
|
+
min_v = v
|
|
996
|
+
elif mult > 1:
|
|
997
|
+
# Save this for later. The eigenvalue cos_theta
|
|
998
|
+
# might be so big that we can ignore it.
|
|
999
|
+
big_eigenspaces.append((cos_theta, xi, eta, mult))
|
|
1000
|
+
continue
|
|
1001
|
+
|
|
1002
|
+
for (cos_theta, xi, eta, mult) in big_eigenspaces:
|
|
1003
|
+
if cos_theta < min_ip:
|
|
1004
|
+
# The existence of a big eigenspace is only a problem if
|
|
1005
|
+
# cos_theta could actually be minimal.
|
|
1006
|
+
|
|
1007
|
+
if exact and P.is_strictly_convex():
|
|
1008
|
+
if Q_is_P or Q.is_strictly_convex():
|
|
1009
|
+
# The maximal angle composed with the conic hull
|
|
1010
|
+
# is continuous at (gs,hs), so we can retry with
|
|
1011
|
+
# inexact arithmetic. That will "perturb"
|
|
1012
|
+
# everything, hopefully eliminating any larger
|
|
1013
|
+
# eigenspaces and without changing the answer too
|
|
1014
|
+
# much.
|
|
1015
|
+
return max_angle(P, Q, False, epsilon)
|
|
1016
|
+
|
|
1017
|
+
# Either we don't know that the maximal angle of the conic
|
|
1018
|
+
# hull is continuous at (gs,hs), or we're already using
|
|
1019
|
+
# inexact arithmetic. There's nothing left to try. (Note
|
|
1020
|
+
# that the case where either P or Q is the ambient space
|
|
1021
|
+
# was handled much earlier, since in that case the maximal
|
|
1022
|
+
# angle is obviously pi.)
|
|
1023
|
+
raise ValueError('eigenspace of dimension %d > 1 '
|
|
1024
|
+
'corresponding to eigenvalue %s'
|
|
1025
|
+
% (mult, cos_theta))
|
|
1026
|
+
|
|
1027
|
+
return (arccos(min_ip), min_u, min_v)
|