]> gitweb.michael.orlitzky.com - dunshire.git/blob - src/dunshire/cones.py
905dd8edf7092b83ef7c1275b2a09430f18c10e1
[dunshire.git] / src / dunshire / cones.py
1 """
2 Class definitions for all of the symmetric cones (and their superclass,
3 :class:`SymmetricCone`) supported by CVXOPT.
4 """
5
6 from cvxopt import matrix
7 from matrices import eigenvalues, norm
8
9 class SymmetricCone:
10 """
11 An instance of a symmetric (self-dual and homogeneous) cone.
12
13 There are three types of symmetric cones supported by CVXOPT:
14
15 1. The nonnegative orthant in the real n-space.
16 2. The Lorentz "ice cream" cone, or the second-order cone.
17 3. The cone of symmetric positive-semidefinite matrices.
18
19 This class is intended to encompass them all.
20
21 When constructing a single symmetric cone (i.e. not a
22 :class:`CartesianProduct` of them), the only information that we
23 need is its dimension. We take that dimension as a parameter, and
24 store it for later.
25
26 Parameters
27 ----------
28
29 dimension : int
30 The dimension of this cone.
31
32 Raises
33 ------
34
35 ValueError
36 If you try to create a cone with dimension zero or less.
37
38 """
39 def __init__(self, dimension):
40 """
41 A generic constructor for symmetric cones.
42 """
43 if dimension <= 0:
44 raise ValueError('cones must have dimension greater than zero')
45
46 self._dimension = dimension
47
48
49 def __contains__(self, point):
50 """
51 Return whether or not ``point`` belongs to this cone.
52
53 Parameters
54 ----------
55
56 point : matrix
57 The point to test for membership in this cone.
58
59 Raises
60 ------
61
62 NotImplementedError
63 Always, this method must be implemented in subclasses.
64
65 Examples
66 --------
67
68 >>> K = SymmetricCone(5)
69 >>> matrix([1,2]) in K
70 Traceback (most recent call last):
71 ...
72 NotImplementedError
73
74 """
75 raise NotImplementedError
76
77
78 def contains_strict(self, point):
79 """
80 Return whether or not ``point`` belongs to the interior
81 of this cone.
82
83 Parameters
84 ----------
85
86 point : matrix
87 The point to test for strict membership in this cone.
88
89 Raises
90 ------
91
92 NotImplementedError
93 Always, this method must be implemented in subclasses.
94
95 Examples
96 --------
97
98 >>> K = SymmetricCone(5)
99 >>> K.contains_strict(matrix([1,2]))
100 Traceback (most recent call last):
101 ...
102 NotImplementedError
103 """
104 raise NotImplementedError
105
106
107 def dimension(self):
108 """
109 Return the dimension of this symmetric cone.
110
111 The dimension of this symmetric cone is recorded during its
112 creation. This method simply returns the recorded value, and
113 should not need to be overridden in subclasses. We prefer to do
114 any special computation in ``__init__()`` and record the result
115 in ``self._dimension``.
116
117 Returns
118 -------
119
120 int
121 The stored dimension (from when this cone was constructed)
122 of this cone.
123
124 Examples
125 --------
126
127 >>> K = SymmetricCone(5)
128 >>> K.dimension()
129 5
130
131 """
132 return self._dimension
133
134
135 class NonnegativeOrthant(SymmetricCone):
136 """
137 The nonnegative orthant in the given number of dimensions.
138
139 Examples
140 --------
141
142 >>> K = NonnegativeOrthant(3)
143 >>> print(K)
144 Nonnegative orthant in the real 3-space
145
146 """
147 def __str__(self):
148 """
149 Output a human-readable description of myself.
150 """
151 tpl = 'Nonnegative orthant in the real {:d}-space'
152 return tpl.format(self.dimension())
153
154
155 def __contains__(self, point):
156 """
157 Return whether or not ``point`` belongs to this cone.
158
159 Parameters
160 ----------
161
162 point : matrix
163 A :class:`cvxopt.base.matrix` having dimensions ``(n,1)``
164 where ``n`` is the :meth:`dimension` of this cone.
165
166 Returns
167 -------
168
169 bool
170
171 ``True`` if ``point`` belongs to this cone, ``False`` otherwise.
172
173 Raises
174 ------
175
176 TypeError
177 If ``point`` is not a :class:`cvxopt.base.matrix`.
178
179 TypeError
180 If ``point`` has the wrong dimensions.
181
182 Examples
183 --------
184
185 >>> K = NonnegativeOrthant(3)
186 >>> matrix([1,2,3]) in K
187 True
188
189 >>> K = NonnegativeOrthant(3)
190 >>> matrix([1,-0.1,3]) in K
191 False
192
193 >>> K = NonnegativeOrthant(3)
194 >>> [1,2,3] in K
195 Traceback (most recent call last):
196 ...
197 TypeError: the given point is not a cvxopt.base.matrix
198
199 >>> K = NonnegativeOrthant(3)
200 >>> matrix([1,2]) in K
201 Traceback (most recent call last):
202 ...
203 TypeError: the given point has the wrong dimensions
204
205 """
206 if not isinstance(point, matrix):
207 raise TypeError('the given point is not a cvxopt.base.matrix')
208 if not point.size == (self.dimension(), 1):
209 raise TypeError('the given point has the wrong dimensions')
210
211 return all([x >= 0 for x in point])
212
213
214 def contains_strict(self, point):
215 """
216 Return whether or not ``point`` belongs to the interior of this
217 cone.
218
219 Parameters
220 ----------
221
222 point : matrix
223 A :class:`cvxopt.base.matrix` having dimensions ``(n,1)``
224 where ``n`` is the :meth:`dimension` of this cone.
225
226 Returns
227 -------
228
229 bool
230
231 ``True`` if ``point`` belongs to the interior of this cone,
232 ``False`` otherwise.
233
234 Raises
235 ------
236
237 TypeError
238 If ``point`` is not a :class:`cvxopt.base.matrix`.
239
240 TypeError
241 If ``point`` has the wrong dimensions.
242
243 Examples
244 --------
245
246 >>> K = NonnegativeOrthant(3)
247 >>> K.contains_strict(matrix([1,2,3]))
248 True
249
250 >>> K = NonnegativeOrthant(3)
251 >>> K.contains_strict(matrix([1,0,1]))
252 False
253
254 >>> K = NonnegativeOrthant(3)
255 >>> K.contains_strict(matrix([1,-0.1,3]))
256 False
257
258 >>> K = NonnegativeOrthant(3)
259 >>> K.contains_strict([1,2,3])
260 Traceback (most recent call last):
261 ...
262 TypeError: the given point is not a cvxopt.base.matrix
263
264 >>> K = NonnegativeOrthant(3)
265 >>> K.contains_strict(matrix([1,2]))
266 Traceback (most recent call last):
267 ...
268 TypeError: the given point has the wrong dimensions
269
270 """
271 if not isinstance(point, matrix):
272 raise TypeError('the given point is not a cvxopt.base.matrix')
273 if not point.size == (self.dimension(), 1):
274 raise TypeError('the given point has the wrong dimensions')
275
276 return all([x > 0 for x in point])
277
278
279
280 class IceCream(SymmetricCone):
281 """
282 The Lorentz "ice cream" cone in the given number of dimensions.
283
284 Examples
285 --------
286
287 >>> K = IceCream(3)
288 >>> print(K)
289 Lorentz "ice cream" cone in the real 3-space
290
291 """
292 def __str__(self):
293 """
294 Output a human-readable description of myself.
295 """
296 tpl = 'Lorentz "ice cream" cone in the real {:d}-space'
297 return tpl.format(self.dimension())
298
299
300 def __contains__(self, point):
301 """
302 Return whether or not ``point`` belongs to this cone.
303
304 Parameters
305 ----------
306
307 point : matrix
308 A :class:`cvxopt.base.matrix` having dimensions ``(n,1)``
309 where ``n`` is the :meth:`dimension` of this cone.
310
311 Returns
312 -------
313
314 bool
315
316 ``True`` if ``point`` belongs to this cone, ``False`` otherwise.
317
318 Raises
319 ------
320
321 TypeError
322 If ``point`` is not a :class:`cvxopt.base.matrix`.
323
324 TypeError
325 If ``point`` has the wrong dimensions.
326
327 Examples
328 --------
329
330 >>> K = IceCream(3)
331 >>> matrix([1,0.5,0.5]) in K
332 True
333
334 >>> K = IceCream(3)
335 >>> matrix([1,0,1]) in K
336 True
337
338 >>> K = IceCream(3)
339 >>> matrix([1,1,1]) in K
340 False
341
342 >>> K = IceCream(3)
343 >>> [1,2,3] in K
344 Traceback (most recent call last):
345 ...
346 TypeError: the given point is not a cvxopt.base.matrix
347
348 >>> K = IceCream(3)
349 >>> matrix([1,2]) in K
350 Traceback (most recent call last):
351 ...
352 TypeError: the given point has the wrong dimensions
353
354 """
355 if not isinstance(point, matrix):
356 raise TypeError('the given point is not a cvxopt.base.matrix')
357 if not point.size == (self.dimension(), 1):
358 raise TypeError('the given point has the wrong dimensions')
359
360 height = point[0]
361 if self.dimension() == 1:
362 # In one dimension, the ice cream cone is the nonnegative
363 # orthant.
364 return height >= 0
365 else:
366 radius = point[1:]
367 return height >= norm(radius)
368
369
370 def contains_strict(self, point):
371 """
372 Return whether or not ``point`` belongs to the interior
373 of this cone.
374
375 Parameters
376 ----------
377
378 point : matrix
379 A :class:`cvxopt.base.matrix` having dimensions ``(n,1)``
380 where ``n`` is the :meth:`dimension` of this cone.
381
382 Returns
383 -------
384
385 bool
386
387 ``True`` if ``point`` belongs to the interior of this cone,
388 ``False`` otherwise.
389
390 Raises
391 ------
392
393 TypeError
394 If ``point`` is not a :class:`cvxopt.base.matrix`.
395
396 TypeError
397 If ``point`` has the wrong dimensions.
398
399 Examples
400 --------
401
402 >>> K = IceCream(3)
403 >>> K.contains_strict(matrix([1,0.5,0.5]))
404 True
405
406 >>> K = IceCream(3)
407 >>> K.contains_strict(matrix([1,0,1]))
408 False
409
410 >>> K = IceCream(3)
411 >>> K.contains_strict(matrix([1,1,1]))
412 False
413
414 >>> K = IceCream(3)
415 >>> K.contains_strict([1,2,3])
416 Traceback (most recent call last):
417 ...
418 TypeError: the given point is not a cvxopt.base.matrix
419
420 >>> K = IceCream(3)
421 >>> K.contains_strict(matrix([1,2]))
422 Traceback (most recent call last):
423 ...
424 TypeError: the given point has the wrong dimensions
425
426 """
427 if not isinstance(point, matrix):
428 raise TypeError('the given point is not a cvxopt.base.matrix')
429 if not point.size == (self.dimension(), 1):
430 raise TypeError('the given point has the wrong dimensions')
431
432 height = point[0]
433 if self.dimension() == 1:
434 # In one dimension, the ice cream cone is the nonnegative
435 # orthant.
436 return height > 0
437 else:
438 radius = point[1:]
439 return height > norm(radius)
440
441
442 class SymmetricPSD(SymmetricCone):
443 r"""
444 The cone of real symmetric positive-semidefinite matrices.
445
446 This cone has a dimension ``n`` associated with it, but we let ``n``
447 refer to the dimension of the domain of our matrices and not the
448 dimension of the (much larger) space in which the matrices
449 themselves live. In other words, our ``n`` is the ``n`` that appears
450 in the usual notation :math:`S^{n}` for symmetric matrices.
451
452 As a result, the cone ``SymmetricPSD(n)`` lives in a space of dimension
453 :math:`\left(n^{2} + n\right)/2)`.
454
455 Examples
456 --------
457
458 >>> K = SymmetricPSD(3)
459 >>> print(K)
460 Cone of symmetric positive-semidefinite matrices on the real 3-space
461 >>> K.dimension()
462 3
463
464 """
465 def __str__(self):
466 """
467 Output a human-readable description of myself.
468 """
469 tpl = 'Cone of symmetric positive-semidefinite matrices ' \
470 'on the real {:d}-space'
471 return tpl.format(self.dimension())
472
473
474 def __contains__(self, point):
475 """
476 Return whether or not ``point`` belongs to this cone.
477
478 Parameters
479 ----------
480
481 point : matrix
482 A :class:`cvxopt.base.matrix` having dimensions ``(n,n)``
483 where ``n`` is the :meth:`dimension` of this cone.
484
485 Returns
486 -------
487
488 bool
489
490 ``True`` if ``point`` belongs to this cone, ``False`` otherwise.
491
492 Raises
493 ------
494
495 TypeError
496 If ``point`` is not a :class:`cvxopt.base.matrix`.
497
498 TypeError
499 If ``point`` has the wrong dimensions.
500
501 Examples
502 --------
503
504 >>> K = SymmetricPSD(2)
505 >>> matrix([[1,0],[0,1]]) in K
506 True
507
508 >>> K = SymmetricPSD(2)
509 >>> matrix([[0,0],[0,0]]) in K
510 True
511
512 >>> K = SymmetricPSD(3)
513 >>> matrix([[2,-1,0],[-1,2,-1],[0,-1,2]]) in K
514 True
515
516 >>> K = SymmetricPSD(5)
517 >>> A = matrix([[5,4,3,2,1],
518 ... [4,5,4,3,2],
519 ... [3,4,5,4,3],
520 ... [2,3,4,5,4],
521 ... [1,2,3,4,5]])
522 >>> A in K
523 True
524
525 >>> K = SymmetricPSD(5)
526 >>> A = matrix([[1,0,0,0,0],
527 ... [0,1,0,0,0],
528 ... [0,0,0,0,0],
529 ... [0,0,0,1,0],
530 ... [0,0,0,0,1]])
531 >>> A in K
532 True
533
534 >>> K = SymmetricPSD(2)
535 >>> [[1,2],[2,3]] in K
536 Traceback (most recent call last):
537 ...
538 TypeError: the given point is not a cvxopt.base.matrix
539
540 >>> K = SymmetricPSD(3)
541 >>> matrix([[1,2],[3,4]]) in K
542 Traceback (most recent call last):
543 ...
544 TypeError: the given point has the wrong dimensions
545
546 """
547 if not isinstance(point, matrix):
548 raise TypeError('the given point is not a cvxopt.base.matrix')
549 if not point.size == (self.dimension(), self.dimension()):
550 raise TypeError('the given point has the wrong dimensions')
551 if not point.typecode == 'd':
552 point = matrix(point, (self.dimension(), self.dimension()), 'd')
553 return all([e >= 0 for e in eigenvalues(point)])
554
555
556 def contains_strict(self, point):
557 """
558 Return whether or not ``point`` belongs to the interior
559 of this cone.
560
561 Parameters
562 ----------
563
564 point : matrix
565 A :class:`cvxopt.base.matrix` having dimensions ``(n,n)``
566 where ``n`` is the :meth:`dimension` of this cone.
567
568 Returns
569 -------
570
571 bool
572
573 ``True`` if ``point`` belongs to the interior of this cone,
574 ``False`` otherwise.
575
576 Raises
577 ------
578
579 TypeError
580 If ``point`` is not a :class:`cvxopt.base.matrix`.
581
582 TypeError
583 If ``point`` has the wrong dimensions.
584
585 Examples
586 --------
587
588 >>> K = SymmetricPSD(2)
589 >>> K.contains_strict(matrix([[1,0],[0,1]]))
590 True
591
592 >>> K = SymmetricPSD(2)
593 >>> K.contains_strict(matrix([[0,0],[0,0]]))
594 False
595
596 >>> K = SymmetricPSD(3)
597 >>> matrix([[2,-1,0],[-1,2,-1],[0,-1,2]]) in K
598 True
599
600 >>> K = SymmetricPSD(5)
601 >>> A = matrix([[5,4,3,2,1],
602 ... [4,5,4,3,2],
603 ... [3,4,5,4,3],
604 ... [2,3,4,5,4],
605 ... [1,2,3,4,5]])
606 >>> A in K
607 True
608
609 >>> K = SymmetricPSD(5)
610 >>> A = matrix([[1,0,0,0,0],
611 ... [0,1,0,0,0],
612 ... [0,0,0,0,0],
613 ... [0,0,0,1,0],
614 ... [0,0,0,0,1]])
615 >>> K.contains_strict(A)
616 False
617
618 >>> K = SymmetricPSD(2)
619 >>> K.contains_strict([[1,2],[2,3]])
620 Traceback (most recent call last):
621 ...
622 TypeError: the given point is not a cvxopt.base.matrix
623
624 >>> K = SymmetricPSD(3)
625 >>> K.contains_strict(matrix([[1,2],[3,4]]))
626 Traceback (most recent call last):
627 ...
628 TypeError: the given point has the wrong dimensions
629
630 """
631 if not isinstance(point, matrix):
632 raise TypeError('the given point is not a cvxopt.base.matrix')
633 if not point.size == (self.dimension(), self.dimension()):
634 raise TypeError('the given point has the wrong dimensions')
635 if not point.typecode == 'd':
636 point = matrix(point, (self.dimension(), self.dimension()), 'd')
637 return all([e > 0 for e in eigenvalues(point)])
638
639
640 class CartesianProduct(SymmetricCone):
641 """
642 A cartesian product of symmetric cones, which is itself a symmetric
643 cone.
644
645 Examples
646 --------
647
648 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(2))
649 >>> print(K)
650 Cartesian product of dimension 5 with 2 factors:
651 * Nonnegative orthant in the real 3-space
652 * Lorentz "ice cream" cone in the real 2-space
653
654 """
655 def __init__(self, *factors):
656 my_dimension = sum([f.dimension() for f in factors])
657 super().__init__(my_dimension)
658 self._factors = factors
659
660
661 def __str__(self):
662 """
663 Output a human-readable description of myself.
664 """
665 tpl = 'Cartesian product of dimension {:d} with {:d} factors:'
666 tpl += '\n * {!s}' * len(self.factors())
667 format_args = [self.dimension(), len(self.factors())]
668 format_args += list(self.factors())
669 return tpl.format(*format_args)
670
671
672 def __contains__(self, point):
673 """
674 Return whether or not ``point`` belongs to this cone.
675
676 The ``point`` is expected to be a tuple of points which will be
677 tested for membership in this cone's factors. If each point in
678 the tuple belongs to its corresponding factor, then the whole
679 point belongs to this cone. Otherwise, it doesn't.
680
681 Parameters
682 ----------
683
684 point : tuple of matrix
685 A tuple of :class:`cvxopt.base.matrix` corresponding to the
686 :meth:`factors` of this cartesian product.
687
688 Returns
689 -------
690
691 bool
692
693 ``True`` if ``point`` belongs to this cone, ``False`` otherwise.
694
695 Raises
696 ------
697
698 TypeError
699 If ``point`` is not a tuple of :class:`cvxopt.base.matrix`.
700
701 TypeError
702 If any element of ``point`` has the wrong dimensions.
703
704 Examples
705 --------
706
707 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
708 >>> (matrix([1,2,3]), matrix([1,0.5,0.5])) in K
709 True
710
711 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
712 >>> (matrix([0,0,0]), matrix([1,0,1])) in K
713 True
714
715 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
716 >>> (matrix([1,1,1]), matrix([1,1,1])) in K
717 False
718
719 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
720 >>> (matrix([1,-1,1]), matrix([1,0,1])) in K
721 False
722
723 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
724 >>> [[1,2,3],[4,5,6]] in K
725 Traceback (most recent call last):
726 ...
727 TypeError: the given point is not a cvxopt.base.matrix
728
729 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
730 >>> (matrix([1,2]), matrix([3,4,5,6])) in K
731 Traceback (most recent call last):
732 ...
733 TypeError: the given point has the wrong dimensions
734
735 """
736 return all([p in f for (p,f) in zip(point, self.factors())])
737
738
739 def contains_strict(self, point):
740 """
741 Return whether or not ``point`` belongs to the interior
742 of this cone.
743
744 The ``point`` is expected to be a tuple of points which will be
745 tested for membership in this cone's factors. If each point in
746 the tuple belongs to the interior of its corresponding factor,
747 then the whole point belongs to the interior of this
748 cone. Otherwise, it doesn't.
749
750 Parameters
751 ----------
752
753 point : tuple of matrix
754 A tuple of :class:`cvxopt.base.matrix` corresponding to the
755 :meth:`factors` of this cartesian product.
756
757 Returns
758 -------
759
760 bool
761
762 ``True`` if ``point`` belongs to the interior of this cone,
763 ``False`` otherwise.
764
765 Raises
766 ------
767
768 TypeError
769 If ``point`` is not a tuple of :class:`cvxopt.base.matrix`.
770
771 TypeError
772 If any element of ``point`` has the wrong dimensions.
773
774 Examples
775 --------
776
777 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
778 >>> K.contains_strict((matrix([1,2,3]), matrix([1,0.5,0.5])))
779 True
780
781 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
782 >>> K.contains_strict((matrix([0,0,0]), matrix([1,0,1])))
783 False
784
785 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
786 >>> K.contains_strict((matrix([1,1,1]), matrix([1,1,1])))
787 False
788
789 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
790 >>> K.contains_strict((matrix([1,-1,1]), matrix([1,0,1])))
791 False
792
793 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
794 >>> K.contains_strict([[1,2,3],[4,5,6]])
795 Traceback (most recent call last):
796 ...
797 TypeError: the given point is not a cvxopt.base.matrix
798
799 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(3))
800 >>> K.contains_strict((matrix([1,2]), matrix([3,4,5,6])))
801 Traceback (most recent call last):
802 ...
803 TypeError: the given point has the wrong dimensions
804
805 """
806 return all([f.contains_strict(p)
807 for (p,f) in zip(point, self.factors())])
808
809
810
811 def factors(self):
812 """
813 Return a tuple containing the factors (in order) of this
814 cartesian product.
815
816 Returns
817 -------
818
819 tuple of :class:`SymmetricCone`.
820 The factors of this cartesian product.
821
822 Examples
823 --------
824
825 >>> K = CartesianProduct(NonnegativeOrthant(3), IceCream(2))
826 >>> len(K.factors())
827 2
828
829 """
830 return self._factors
831
832
833 def cvxopt_dims(self):
834 """
835 Return a dictionary of dimensions corresponding to the factors
836 of this cartesian product. The format of this dictionary is
837 described in the CVXOPT user's guide:
838
839 http://cvxopt.org/userguide/coneprog.html#linear-cone-programs
840
841 Returns
842 -------
843
844 dict
845 A dimension dictionary suitable to feed to CVXOPT.
846
847 Examples
848 --------
849
850 >>> K = CartesianProduct(NonnegativeOrthant(3),
851 ... IceCream(2),
852 ... IceCream(3))
853 >>> d = K.cvxopt_dims()
854 >>> (d['l'], d['q'], d['s'])
855 (3, [2, 3], [])
856
857 """
858 dims = {'l':0, 'q':[], 's':[]}
859 dims['l'] += sum([K.dimension()
860 for K in self.factors()
861 if isinstance(K, NonnegativeOrthant)])
862 dims['q'] = [K.dimension()
863 for K in self.factors()
864 if isinstance(K, IceCream)]
865 dims['s'] = [K.dimension()
866 for K in self.factors()
867 if isinstance(K, SymmetricPSD)]
868 return dims
869
870