]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/cone/cone.py
Don't count on 2*dim(V) rays generating V.
[sage.d.git] / mjo / cone / cone.py
1 # Sage doesn't load ~/.sage/init.sage during testing (sage -t), so we
2 # have to explicitly mangle our sitedir here so that "mjo.cone"
3 # resolves.
4 from os.path import abspath
5 from site import addsitedir
6 addsitedir(abspath('../../'))
7
8 from sage.all import *
9
10
11 def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
12 r"""
13 Generate a random rational convex polyhedral cone.
14
15 Lower and upper bounds may be provided for both the dimension of the
16 ambient space and the number of generating rays of the cone. If a
17 lower bound is left unspecified, it defaults to zero. Unspecified
18 upper bounds will be chosen randomly.
19
20 The lower bound on the number of rays is limited to twice the
21 maximum dimension of the ambient vector space. To see why, consider
22 the space $\mathbb{R}^{2}$, and suppose we have generated four rays,
23 $\left\{ \pm e_{1}, \pm e_{2} \right\}$. Clearly any other ray in
24 the space is a nonnegative linear combination of those four,
25 so it is hopeless to generate more. It is therefore an error
26 to request more in the form of ``min_rays``.
27
28 .. NOTE:
29
30 If you do not explicitly request more than ``2 * max_dim`` rays,
31 a larger number may still be randomly generated. In that case,
32 the returned cone will simply be equal to the entire space.
33
34 INPUT:
35
36 - ``min_dim`` (default: zero) -- A nonnegative integer representing the
37 minimum dimension of the ambient lattice.
38
39 - ``max_dim`` (default: random) -- A nonnegative integer representing
40 the maximum dimension of the ambient
41 lattice.
42
43 - ``min_rays`` (default: zero) -- A nonnegative integer representing the
44 minimum number of generating rays of the
45 cone.
46
47 - ``max_rays`` (default: random) -- A nonnegative integer representing the
48 maximum number of generating rays of
49 the cone.
50
51 OUTPUT:
52
53 A new, randomly generated cone.
54
55 A ``ValueError` will be thrown under the following conditions:
56
57 * Any of ``min_dim``, ``max_dim``, ``min_rays``, or ``max_rays``
58 are negative.
59
60 * ``max_dim`` is less than ``min_dim``.
61
62 * ``max_rays`` is less than ``min_rays``.
63
64 * ``min_rays`` is greater than twice ``max_dim``.
65
66 EXAMPLES:
67
68 If we set the lower/upper bounds to zero, then our result is
69 predictable::
70
71 sage: random_cone(0,0,0,0)
72 0-d cone in 0-d lattice N
73
74 We can predict the dimension when ``min_dim == max_dim``::
75
76 sage: random_cone(min_dim=4, max_dim=4, min_rays=0, max_rays=0)
77 0-d cone in 4-d lattice N
78
79 Likewise for the number of rays when ``min_rays == max_rays``::
80
81 sage: random_cone(min_dim=10, max_dim=10, min_rays=10, max_rays=10)
82 10-d cone in 10-d lattice N
83
84 TESTS:
85
86 It's hard to test the output of a random process, but we can at
87 least make sure that we get a cone back::
88
89 sage: from sage.geometry.cone import is_Cone # long time
90 sage: K = random_cone() # long time
91 sage: is_Cone(K) # long time
92 True
93
94 The upper/lower bounds are respected::
95
96 sage: K = random_cone(min_dim=5, max_dim=10, min_rays=3, max_rays=4)
97 sage: 5 <= K.lattice_dim() and K.lattice_dim() <= 10
98 True
99 sage: 3 <= K.nrays() and K.nrays() <= 4
100 True
101
102 Ensure that an exception is raised when either lower bound is greater
103 than its respective upper bound::
104
105 sage: random_cone(min_dim=5, max_dim=2)
106 Traceback (most recent call last):
107 ...
108 ValueError: max_dim cannot be less than min_dim.
109
110 sage: random_cone(min_rays=5, max_rays=2)
111 Traceback (most recent call last):
112 ...
113 ValueError: max_rays cannot be less than min_rays.
114
115 And if we request too many rays::
116
117 sage: random_cone(min_rays=5, max_dim=1)
118 Traceback (most recent call last):
119 ...
120 ValueError: min_rays cannot be larger than twice max_dim.
121
122 """
123
124 # Catch obvious mistakes so that we can generate clear error
125 # messages.
126
127 if min_dim < 0:
128 raise ValueError('min_dim must be nonnegative.')
129
130 if min_rays < 0:
131 raise ValueError('min_rays must be nonnegative.')
132
133 if max_dim is not None:
134 if max_dim < 0:
135 raise ValueError('max_dim must be nonnegative.')
136 if (max_dim < min_dim):
137 raise ValueError('max_dim cannot be less than min_dim.')
138 if min_rays > 2*max_dim:
139 raise ValueError('min_rays cannot be larger than twice max_dim.')
140
141 if max_rays is not None:
142 if max_rays < 0:
143 raise ValueError('max_rays must be nonnegative.')
144 if (max_rays < min_rays):
145 raise ValueError('max_rays cannot be less than min_rays.')
146
147
148 def random_min_max(l,u):
149 r"""
150 We need to handle two cases for the upper bounds, and we need to do
151 the same thing for max_dim/max_rays. So we consolidate the logic here.
152 """
153 if u is None:
154 # The upper bound is unspecified; return a random integer
155 # in [l,infinity).
156 return l + ZZ.random_element().abs()
157 else:
158 # We have an upper bound, and it's greater than or equal
159 # to our lower bound. So we generate a random integer in
160 # [0,u-l], and then add it to l to get something in
161 # [l,u]. To understand the "+1", check the
162 # ZZ.random_element() docs.
163 return l + ZZ.random_element(u - l + 1)
164
165 def is_full_space(K):
166 r"""
167 Is this cone equivalent to the full ambient vector space?
168 """
169 return K.lines().dim() == K.lattice_dim()
170
171 d = random_min_max(min_dim, max_dim)
172 r = random_min_max(min_rays, max_rays)
173
174 L = ToricLattice(d)
175
176 # The rays are trickier to generate, since we could generate v and
177 # 2*v as our "two rays." In that case, the resuting cone would
178 # have one generating ray. To avoid such a situation, we start by
179 # generating ``r`` rays where ``r`` is the number we want to end
180 # up with...
181 rays = [L.random_element() for i in range(0, r)]
182
183 # (The lattice parameter is required when no rays are given, so we
184 # pass it just in case ``r == 0``).
185 K = Cone(rays, lattice=L)
186
187 # Now if we generated two of the "same" rays, we'll have fewer
188 # generating rays than ``r``. In that case, we keep making up new
189 # rays and recreating the cone until we get the right number of
190 # independent generators. We can obviously stop if ``K`` is the
191 # entire ambient vector space.
192 while r > K.nrays() and not is_full_space(K):
193 rays.append(L.random_element())
194 K = Cone(rays)
195
196 return K
197
198
199 def discrete_complementarity_set(K):
200 r"""
201 Compute the discrete complementarity set of this cone.
202
203 The complementarity set of this cone is the set of all orthogonal
204 pairs `(x,s)` such that `x` is in this cone, and `s` is in its
205 dual. The discrete complementarity set restricts `x` and `s` to be
206 generators of their respective cones.
207
208 OUTPUT:
209
210 A list of pairs `(x,s)` such that,
211
212 * `x` is in this cone.
213 * `x` is a generator of this cone.
214 * `s` is in this cone's dual.
215 * `s` is a generator of this cone's dual.
216 * `x` and `s` are orthogonal.
217
218 EXAMPLES:
219
220 The discrete complementarity set of the nonnegative orthant consists
221 of pairs of standard basis vectors::
222
223 sage: K = Cone([(1,0),(0,1)])
224 sage: discrete_complementarity_set(K)
225 [((1, 0), (0, 1)), ((0, 1), (1, 0))]
226
227 If the cone consists of a single ray, the second components of the
228 discrete complementarity set should generate the orthogonal
229 complement of that ray::
230
231 sage: K = Cone([(1,0)])
232 sage: discrete_complementarity_set(K)
233 [((1, 0), (0, 1)), ((1, 0), (0, -1))]
234 sage: K = Cone([(1,0,0)])
235 sage: discrete_complementarity_set(K)
236 [((1, 0, 0), (0, 1, 0)),
237 ((1, 0, 0), (0, -1, 0)),
238 ((1, 0, 0), (0, 0, 1)),
239 ((1, 0, 0), (0, 0, -1))]
240
241 When the cone is the entire space, its dual is the trivial cone, so
242 the discrete complementarity set is empty::
243
244 sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
245 sage: discrete_complementarity_set(K)
246 []
247
248 TESTS:
249
250 The complementarity set of the dual can be obtained by switching the
251 components of the complementarity set of the original cone::
252
253 sage: K1 = random_cone(max_dim=10, max_rays=10)
254 sage: K2 = K1.dual()
255 sage: expected = [(x,s) for (s,x) in discrete_complementarity_set(K2)]
256 sage: actual = discrete_complementarity_set(K1)
257 sage: actual == expected
258 True
259
260 """
261 V = K.lattice().vector_space()
262
263 # Convert the rays to vectors so that we can compute inner
264 # products.
265 xs = [V(x) for x in K.rays()]
266 ss = [V(s) for s in K.dual().rays()]
267
268 return [(x,s) for x in xs for s in ss if x.inner_product(s) == 0]
269
270
271 def lyapunov_rank(K):
272 r"""
273 Compute the Lyapunov (or bilinearity) rank of this cone.
274
275 The Lyapunov rank of a cone can be thought of in (mainly) two ways:
276
277 1. The dimension of the Lie algebra of the automorphism group of the
278 cone.
279
280 2. The dimension of the linear space of all Lyapunov-like
281 transformations on the cone.
282
283 INPUT:
284
285 A closed, convex polyhedral cone.
286
287 OUTPUT:
288
289 An integer representing the Lyapunov rank of the cone. If the
290 dimension of the ambient vector space is `n`, then the Lyapunov rank
291 will be between `1` and `n` inclusive; however a rank of `n-1` is
292 not possible (see the first reference).
293
294 .. note::
295
296 In the references, the cones are always assumed to be proper. We
297 do not impose this restriction.
298
299 .. seealso::
300
301 :meth:`is_proper`
302
303 ALGORITHM:
304
305 The codimension formula from the second reference is used. We find
306 all pairs `(x,s)` in the complementarity set of `K` such that `x`
307 and `s` are rays of our cone. It is known that these vectors are
308 sufficient to apply the codimension formula. Once we have all such
309 pairs, we "brute force" the codimension formula by finding all
310 linearly-independent `xs^{T}`.
311
312 REFERENCES:
313
314 1. M.S. Gowda and J. Tao. On the bilinearity rank of a proper cone
315 and Lyapunov-like transformations, Mathematical Programming, 147
316 (2014) 155-170.
317
318 2. G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh, Bilinear
319 optimality constraints for the cone of positive polynomials,
320 Mathematical Programming, Series B, 129 (2011) 5-31.
321
322 EXAMPLES:
323
324 The nonnegative orthant in `\mathbb{R}^{n}` always has rank `n`::
325
326 sage: positives = Cone([(1,)])
327 sage: lyapunov_rank(positives)
328 1
329 sage: quadrant = Cone([(1,0), (0,1)])
330 sage: lyapunov_rank(quadrant)
331 2
332 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
333 sage: lyapunov_rank(octant)
334 3
335
336 The `L^{3}_{1}` cone is known to have a Lyapunov rank of one::
337
338 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
339 sage: lyapunov_rank(L31)
340 1
341
342 Likewise for the `L^{3}_{\infty}` cone::
343
344 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
345 sage: lyapunov_rank(L3infty)
346 1
347
348 The Lyapunov rank should be additive on a product of cones::
349
350 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
351 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
352 sage: K = L31.cartesian_product(octant)
353 sage: lyapunov_rank(K) == lyapunov_rank(L31) + lyapunov_rank(octant)
354 True
355
356 Two isomorphic cones should have the same Lyapunov rank. The cone
357 ``K`` in the following example is isomorphic to the nonnegative
358 octant in `\mathbb{R}^{3}`::
359
360 sage: K = Cone([(1,2,3), (-1,1,0), (1,0,6)])
361 sage: lyapunov_rank(K)
362 3
363
364 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
365 itself::
366
367 sage: K = Cone([(2,2,4), (-1,9,0), (2,0,6)])
368 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
369 True
370
371 TESTS:
372
373 The Lyapunov rank should be additive on a product of cones::
374
375 sage: K1 = random_cone(max_dim=10, max_rays=10)
376 sage: K2 = random_cone(max_dim=10, max_rays=10)
377 sage: K = K1.cartesian_product(K2)
378 sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
379 True
380
381 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
382 itself::
383
384 sage: K = random_cone(max_dim=10, max_rays=10)
385 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
386 True
387
388 """
389 V = K.lattice().vector_space()
390
391 C_of_K = discrete_complementarity_set(K)
392
393 matrices = [x.tensor_product(s) for (x,s) in C_of_K]
394
395 # Sage doesn't think matrices are vectors, so we have to convert
396 # our matrices to vectors explicitly before we can figure out how
397 # many are linearly-indepenedent.
398 #
399 # The space W has the same base ring as V, but dimension
400 # dim(V)^2. So it has the same dimension as the space of linear
401 # transformations on V. In other words, it's just the right size
402 # to create an isomorphism between it and our matrices.
403 W = VectorSpace(V.base_ring(), V.dimension()**2)
404
405 def phi(m):
406 r"""
407 Convert a matrix to a vector isomorphically.
408 """
409 return W(m.list())
410
411 vectors = [phi(m) for m in matrices]
412
413 return (W.dimension() - W.span(vectors).rank())